]> git.ipfire.org Git - thirdparty/git.git/commitdiff
add: support the --pathspec-from-file option
authorAlexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Tue, 3 Dec 2019 14:02:13 +0000 (14:02 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 4 Dec 2019 18:10:37 +0000 (10:10 -0800)
Decisions taken for simplicity:
1) For now, `--pathspec-from-file` is declared incompatible with
   `--interactive/--patch/--edit`, even when <file> is not `stdin`.
   Such use case it not really expected. Also, it would require changes
   to `interactive_add()` and `edit_patch()`.
2) It is not allowed to pass pathspec in both args and file.

Signed-off-by: Alexandr Miloslavskiy <alexandr.miloslavskiy@syntevo.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-add.txt
builtin/add.c
t/t3704-add-pathspec-file.sh [new file with mode: 0755]

index 8b0e4c7fa8c5922801164a7f847a7baecc456402..be5e3ac54b858778c8454970e8abae81b0c4841f 100644 (file)
@@ -11,7 +11,8 @@ SYNOPSIS
 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
          [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
          [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
-         [--chmod=(+|-)x] [--] [<pathspec>...]
+         [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
+         [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -187,6 +188,19 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
        bit is only changed in the index, the files on disk are left
        unchanged.
 
+--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).
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
index 4fabdc72e6d8ddc15aaca5811ec0570bf0a9d7fc..9f6b263abaaf61149a71eda21dabd017a0708610 100644 (file)
@@ -28,6 +28,8 @@ static const char * const builtin_add_usage[] = {
 static int patch_interactive, add_interactive, edit_interactive;
 static int take_worktree_changes;
 static int add_renormalize;
+static int pathspec_file_nul;
+static const char *pathspec_from_file;
 
 struct update_callback_data {
        int flags;
@@ -309,6 +311,8 @@ static struct option builtin_add_options[] = {
                   N_("override the executable bit of the listed files")),
        OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
                        N_("warn when adding an embedded repository")),
+       OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+       OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
        OPT_END(),
 };
 
@@ -402,11 +406,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                          builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
        if (patch_interactive)
                add_interactive = 1;
-       if (add_interactive)
+       if (add_interactive) {
+               if (pathspec_from_file)
+                       die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
                exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
+       }
 
-       if (edit_interactive)
+       if (edit_interactive) {
+               if (pathspec_from_file)
+                       die(_("--pathspec-from-file is incompatible with --edit"));
                return(edit_patch(argc, argv, prefix));
+       }
        argc--;
        argv++;
 
@@ -439,13 +449,25 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                       PATHSPEC_SYMLINK_LEADING_PATH,
                       prefix, argv);
 
-       if (require_pathspec && argc == 0) {
+       if (pathspec_from_file) {
+               if (pathspec.nr)
+                       die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+               parse_pathspec_file(&pathspec, PATHSPEC_ATTR,
+                                   PATHSPEC_PREFER_FULL |
+                                   PATHSPEC_SYMLINK_LEADING_PATH,
+                                   prefix, pathspec_from_file, pathspec_file_nul);
+       } else if (pathspec_file_nul) {
+               die(_("--pathspec-file-nul requires --pathspec-from-file"));
+       }
+
+       if (require_pathspec && pathspec.nr == 0) {
                fprintf(stderr, _("Nothing specified, nothing added.\n"));
                fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
                return 0;
        }
 
-       if (!take_worktree_changes && addremove_explicit < 0 && argc)
+       if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)
                /* Turn "git add pathspec..." to "git add -A pathspec..." */
                addremove = 1;
 
diff --git a/t/t3704-add-pathspec-file.sh b/t/t3704-add-pathspec-file.sh
new file mode 100755 (executable)
index 0000000..3cfdb66
--- /dev/null
@@ -0,0 +1,127 @@
+#!/bin/sh
+
+test_description='add --pathspec-from-file'
+
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success setup '
+       test_commit file0 &&
+       echo A >fileA.t &&
+       echo B >fileB.t &&
+       echo C >fileC.t &&
+       echo D >fileD.t
+'
+
+restore_checkpoint () {
+       git reset
+}
+
+verify_expect () {
+       git status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success '--pathspec-from-file from stdin' '
+       restore_checkpoint &&
+
+       echo fileA.t | git add --pathspec-from-file=- &&
+
+       cat >expect <<-\EOF &&
+       A  fileA.t
+       EOF
+       verify_expect
+'
+
+test_expect_success '--pathspec-from-file from file' '
+       restore_checkpoint &&
+
+       echo fileA.t >list &&
+       git add --pathspec-from-file=list &&
+
+       cat >expect <<-\EOF &&
+       A  fileA.t
+       EOF
+       verify_expect
+'
+
+test_expect_success 'NUL delimiters' '
+       restore_checkpoint &&
+
+       printf "fileA.t\0fileB.t\0" | git add --pathspec-from-file=- --pathspec-file-nul &&
+
+       cat >expect <<-\EOF &&
+       A  fileA.t
+       A  fileB.t
+       EOF
+       verify_expect
+'
+
+test_expect_success 'LF delimiters' '
+       restore_checkpoint &&
+
+       printf "fileA.t\nfileB.t\n" | git add --pathspec-from-file=- &&
+
+       cat >expect <<-\EOF &&
+       A  fileA.t
+       A  fileB.t
+       EOF
+       verify_expect
+'
+
+test_expect_success 'no trailing delimiter' '
+       restore_checkpoint &&
+
+       printf "fileA.t\nfileB.t" | git add --pathspec-from-file=- &&
+
+       cat >expect <<-\EOF &&
+       A  fileA.t
+       A  fileB.t
+       EOF
+       verify_expect
+'
+
+test_expect_success 'CRLF delimiters' '
+       restore_checkpoint &&
+
+       printf "fileA.t\r\nfileB.t\r\n" | git add --pathspec-from-file=- &&
+
+       cat >expect <<-\EOF &&
+       A  fileA.t
+       A  fileB.t
+       EOF
+       verify_expect
+'
+
+test_expect_success 'quotes' '
+       restore_checkpoint &&
+
+       printf "\"file\\101.t\"" | git add --pathspec-from-file=- &&
+
+       cat >expect <<-\EOF &&
+       A  fileA.t
+       EOF
+       verify_expect
+'
+
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
+       restore_checkpoint &&
+
+       printf "\"file\\101.t\"" >list &&
+       test_must_fail git add --pathspec-from-file=list --pathspec-file-nul
+'
+
+test_expect_success 'only touches what was listed' '
+       restore_checkpoint &&
+
+       printf "fileB.t\nfileC.t\n" | git add --pathspec-from-file=- &&
+
+       cat >expect <<-\EOF &&
+       A  fileB.t
+       A  fileC.t
+       EOF
+       verify_expect
+'
+
+test_done