Skip to content

Commit

Permalink
reset: support the --pathspec-from-file option
Browse files Browse the repository at this point in the history
Decisions taken for simplicity:
1) For now, `--pathspec-from-file` is declared incompatible with
   `--patch`, even when <file> is not `stdin`. Such use case it not
   really expected. Also, it is harder to support in `git commit`, so
   I decided to make it incompatible in all places.
2) It is not allowed to pass pathspec in both args and file.

Co-authored-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
2 people authored and gitster committed Nov 20, 2019
1 parent d137b50 commit 64bac8d
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 5 deletions.
21 changes: 18 additions & 3 deletions Documentation/git-reset.txt
Expand Up @@ -9,18 +9,20 @@ SYNOPSIS
--------
[verse]
'git reset' [-q] [<tree-ish>] [--] <pathspec>...
'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

DESCRIPTION
-----------
In the first and second form, copy entries from `<tree-ish>` to the index.
In the third form, set the current branch head (`HEAD`) to `<commit>`,
In the first three forms, copy entries from `<tree-ish>` to the index.
In the last form, set the current branch head (`HEAD`) to `<commit>`,
optionally modifying index and working tree to match.
The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.

'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
This form resets the index entries for all paths that match the
'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]::
These forms reset the index entries for all paths that match the
`<pathspec>` to their state at `<tree-ish>`. (It does not affect
the working tree or the current branch.)
+
Expand Down Expand Up @@ -101,6 +103,19 @@ OPTIONS
`reset.quiet` config option. `--quiet` and `--no-quiet` will
override the default behavior.

--pathspec-from-file=<file>::
Pathspec is passed in `<file>` instead of commandline args. If
`<file>` is exactly `-` then standard input is used. Pathspec
elements are separated by LF or CR/LF. Pathspec elements can be
quoted as explained for the configuration variable `core.quotePath`
(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
global `--literal-pathspecs`.

--pathspec-file-nul::
Only meaningful with `--pathspec-from-file`. Pathspec elements are
separated with NUL character and all other characters are taken
literally (including newlines and quotes).

\--::
Do not interpret any more arguments as options.

Expand Down
21 changes: 19 additions & 2 deletions builtin/reset.c
Expand Up @@ -31,6 +31,7 @@
static const char * const git_reset_usage[] = {
N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
NULL
};
Expand Down Expand Up @@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
int cmd_reset(int argc, const char **argv, const char *prefix)
{
int reset_type = NONE, update_ref_status = 0, quiet = 0;
int patch_mode = 0, unborn;
const char *rev;
int patch_mode = 0, pathspec_file_nul = 0, unborn;
const char *rev, *pathspec_from_file = NULL;
struct object_id oid;
struct pathspec pathspec;
int intent_to_add = 0;
Expand All @@ -306,6 +307,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
OPT_BOOL('N', "intent-to-add", &intent_to_add,
N_("record only the fact that removed paths will be added later")),
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END()
};

Expand All @@ -316,6 +319,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_DASHDASH);
parse_args(&pathspec, argv, prefix, patch_mode, &rev);

if (pathspec_from_file) {
if (patch_mode)
die(_("--pathspec-from-file is incompatible with --patch"));

if (pathspec.nr)
die(_("--pathspec-from-file is incompatible with pathspec arguments"));

parse_pathspec_file(&pathspec, 0,
PATHSPEC_PREFER_FULL,
prefix, pathspec_from_file, pathspec_file_nul);
} else if (pathspec_file_nul) {
die(_("--pathspec-file-nul requires --pathspec-from-file"));
}

unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
Expand Down
155 changes: 155 additions & 0 deletions t/t7107-reset-pathspec-file.sh
@@ -0,0 +1,155 @@
#!/bin/sh

test_description='reset --pathspec-from-file'

. ./test-lib.sh

test_tick

test_expect_success setup '
echo A >fileA.t &&
echo B >fileB.t &&
echo C >fileC.t &&
echo D >fileD.t &&
git add . &&
git commit --include . -m "Commit" &&
git tag checkpoint
'

restore_checkpoint () {
git reset --hard checkpoint
}

verify_expect () {
git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual &&
test_cmp expect actual
}

test_expect_success '--pathspec-from-file from stdin' '
restore_checkpoint &&
git rm fileA.t &&
echo fileA.t | git reset --pathspec-from-file=- &&
cat >expect <<-\EOF &&
D fileA.t
EOF
verify_expect
'

test_expect_success '--pathspec-from-file from file' '
restore_checkpoint &&
git rm fileA.t &&
echo fileA.t >list &&
git reset --pathspec-from-file=list &&
cat >expect <<-\EOF &&
D fileA.t
EOF
verify_expect
'

test_expect_success 'NUL delimiters' '
restore_checkpoint &&
git rm fileA.t fileB.t &&
printf "fileA.t\0fileB.t\0" | git reset --pathspec-from-file=- --pathspec-file-nul &&
cat >expect <<-\EOF &&
D fileA.t
D fileB.t
EOF
verify_expect
'

test_expect_success 'LF delimiters' '
restore_checkpoint &&
git rm fileA.t fileB.t &&
printf "fileA.t\nfileB.t\n" | git reset --pathspec-from-file=- &&
cat >expect <<-\EOF &&
D fileA.t
D fileB.t
EOF
verify_expect
'

test_expect_success 'no trailing delimiter' '
restore_checkpoint &&
git rm fileA.t fileB.t &&
printf "fileA.t\nfileB.t" | git reset --pathspec-from-file=- &&
cat >expect <<-\EOF &&
D fileA.t
D fileB.t
EOF
verify_expect
'

test_expect_success 'CRLF delimiters' '
restore_checkpoint &&
git rm fileA.t fileB.t &&
printf "fileA.t\r\nfileB.t\r\n" | git reset --pathspec-from-file=- &&
cat >expect <<-\EOF &&
D fileA.t
D fileB.t
EOF
verify_expect
'

test_expect_success 'quotes' '
restore_checkpoint &&
git rm fileA.t &&
printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
cat >expect <<-\EOF &&
D fileA.t
EOF
verify_expect
'

test_expect_success 'quotes not compatible with --pathspec-file-nul' '
restore_checkpoint &&
git rm fileA.t &&
printf "\"file\\101.t\"" >list &&
# Note: "git reset" has not yet learned to fail on wrong pathspecs
git reset --pathspec-from-file=list --pathspec-file-nul &&
cat >expect <<-\EOF &&
D fileA.t
EOF
test_must_fail verify_expect
'

test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' '
restore_checkpoint &&
git rm fileA.t &&
echo fileA.t >list &&
test_must_fail git reset --soft --pathspec-from-file=list &&
test_must_fail git reset --hard --pathspec-from-file=list
'

test_expect_success 'only touches what was listed' '
restore_checkpoint &&
git rm fileA.t fileB.t fileC.t fileD.t &&
printf "fileB.t\nfileC.t\n" | git reset --pathspec-from-file=- &&
cat >expect <<-\EOF &&
D fileA.t
D fileB.t
D fileC.t
D fileD.t
EOF
verify_expect
'

test_done

0 comments on commit 64bac8d

Please sign in to comment.