]> git.ipfire.org Git - thirdparty/git.git/commitdiff
reset: support the `--pathspec-from-file` option
authorAlexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Tue, 19 Nov 2019 16:48:53 +0000 (16:48 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 20 Nov 2019 04:01:53 +0000 (13:01 +0900)
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>
Documentation/git-reset.txt
builtin/reset.c
t/t7107-reset-pathspec-file.sh [new file with mode: 0755]

index d517a43e738bb98f7db4e926d9e53443a71a35c9..932080c55d2c232236c630a26c9968874882b1f3 100644 (file)
@@ -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.)
 +
@@ -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.
 
index 9291c0fd726c540ed5643d1e408beffc44165824..246bf9d737de9517760afdc821f4c86dccaf75a3 100644 (file)
@@ -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
 };
@@ -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;
@@ -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()
        };
 
@@ -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 */
diff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh
new file mode 100755 (executable)
index 0000000..6b1a731
--- /dev/null
@@ -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