add: support the --pathspec-from-file option
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>
Alexandr Miloslavskiy committed
Dec 3, 2019 at 14:02 UTC
bebb5d6d6bf64d5b5f175af81db1e0e2fa1c5ccb
3 files changed
+168
-5
Documentation/git-add.txt
+15
-1
@@ -11,7 +11,8 @@ SYNOPSIS
11
'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
12
[--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
13
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
14
- [--chmod=(+|-)x] [--] [<pathspec>...]
14
+ [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
15
+ [--] [<pathspec>...]
16
17
DESCRIPTION
18
-----------
@@ -187,6 +188,19 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files.
188
bit is only changed in the index, the files on disk are left
189
unchanged.
190
191
+--pathspec-from-file=<file>::
192
+ Pathspec is passed in `<file>` instead of commandline args. If
193
+ `<file>` is exactly `-` then standard input is used. Pathspec
194
+ elements are separated by LF or CR/LF. Pathspec elements can be
195
+ quoted as explained for the configuration variable `core.quotePath`
196
+ (see linkgit:git-config[1]). See also `--pathspec-file-nul` and
197
+ global `--literal-pathspecs`.
198
+
199
+--pathspec-file-nul::
200
+ Only meaningful with `--pathspec-from-file`. Pathspec elements are
201
+ separated with NUL character and all other characters are taken
202
+ literally (including newlines and quotes).
203
+
204
\--::
205
This option can be used to separate command-line options from
206
the list of files, (useful when filenames might be mistaken
builtin/add.c
+26
-4
@@ -28,6 +28,8 @@ static const char * const builtin_add_usage[] = {
28
static int patch_interactive, add_interactive, edit_interactive;
29
static int take_worktree_changes;
30
static int add_renormalize;
31
+static int pathspec_file_nul;
32
+static const char *pathspec_from_file;
33
34
struct update_callback_data {
35
int flags;
@@ -309,6 +311,8 @@ static struct option builtin_add_options[] = {
311
N_("override the executable bit of the listed files")),
312
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
313
N_("warn when adding an embedded repository")),
314
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
315
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
316
OPT_END(),
317
};
318
@@ -402,11 +406,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)
406
builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
407
if (patch_interactive)
408
add_interactive = 1;
405
- if (add_interactive)
409
+ if (add_interactive) {
410
+ if (pathspec_from_file)
411
+ die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
412
exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
413
+ }
414
408
- if (edit_interactive)
415
+ if (edit_interactive) {
416
+ if (pathspec_from_file)
417
+ die(_("--pathspec-from-file is incompatible with --edit"));
418
return(edit_patch(argc, argv, prefix));
419
+ }
420
argc--;
421
argv++;
422
@@ -439,13 +449,25 @@ int cmd_add(int argc, const char **argv, const char *prefix)
449
PATHSPEC_SYMLINK_LEADING_PATH,
450
prefix, argv);
451
442
- if (require_pathspec && argc == 0) {
452
+ if (pathspec_from_file) {
453
+ if (pathspec.nr)
454
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
455
+
456
+ parse_pathspec_file(&pathspec, PATHSPEC_ATTR,
457
+ PATHSPEC_PREFER_FULL |
458
+ PATHSPEC_SYMLINK_LEADING_PATH,
459
+ prefix, pathspec_from_file, pathspec_file_nul);
460
+ } else if (pathspec_file_nul) {
461
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
462
+ }
463
+
464
+ if (require_pathspec && pathspec.nr == 0) {
465
fprintf(stderr, _("Nothing specified, nothing added.\n"));
466
fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
467
return 0;
468
}
469
448
- if (!take_worktree_changes && addremove_explicit < 0 && argc)
470
+ if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)
471
/* Turn "git add pathspec..." to "git add -A pathspec..." */
472
addremove = 1;
473
t/t3704-add-pathspec-file.sh
new
+127
@@ -0,0 +1,127 @@
1
+#!/bin/sh
2
+
3
+test_description='add --pathspec-from-file'
4
+
5
+. ./test-lib.sh
6
+
7
+test_tick
8
+
9
+test_expect_success setup '
10
+ test_commit file0 &&
11
+ echo A >fileA.t &&
12
+ echo B >fileB.t &&
13
+ echo C >fileC.t &&
14
+ echo D >fileD.t
15
+'
16
+
17
+restore_checkpoint () {
18
+ git reset
19
+}
20
+
21
+verify_expect () {
22
+ git status --porcelain --untracked-files=no -- fileA.t fileB.t fileC.t fileD.t >actual &&
23
+ test_cmp expect actual
24
+}
25
+
26
+test_expect_success '--pathspec-from-file from stdin' '
27
+ restore_checkpoint &&
28
+
29
+ echo fileA.t | git add --pathspec-from-file=- &&
30
+
31
+ cat >expect <<-\EOF &&
32
+ A fileA.t
33
+ EOF
34
+ verify_expect
35
+'
36
+
37
+test_expect_success '--pathspec-from-file from file' '
38
+ restore_checkpoint &&
39
+
40
+ echo fileA.t >list &&
41
+ git add --pathspec-from-file=list &&
42
+
43
+ cat >expect <<-\EOF &&
44
+ A fileA.t
45
+ EOF
46
+ verify_expect
47
+'
48
+
49
+test_expect_success 'NUL delimiters' '
50
+ restore_checkpoint &&
51
+
52
+ printf "fileA.t\0fileB.t\0" | git add --pathspec-from-file=- --pathspec-file-nul &&
53
+
54
+ cat >expect <<-\EOF &&
55
+ A fileA.t
56
+ A fileB.t
57
+ EOF
58
+ verify_expect
59
+'
60
+
61
+test_expect_success 'LF delimiters' '
62
+ restore_checkpoint &&
63
+
64
+ printf "fileA.t\nfileB.t\n" | git add --pathspec-from-file=- &&
65
+
66
+ cat >expect <<-\EOF &&
67
+ A fileA.t
68
+ A fileB.t
69
+ EOF
70
+ verify_expect
71
+'
72
+
73
+test_expect_success 'no trailing delimiter' '
74
+ restore_checkpoint &&
75
+
76
+ printf "fileA.t\nfileB.t" | git add --pathspec-from-file=- &&
77
+
78
+ cat >expect <<-\EOF &&
79
+ A fileA.t
80
+ A fileB.t
81
+ EOF
82
+ verify_expect
83
+'
84
+
85
+test_expect_success 'CRLF delimiters' '
86
+ restore_checkpoint &&
87
+
88
+ printf "fileA.t\r\nfileB.t\r\n" | git add --pathspec-from-file=- &&
89
+
90
+ cat >expect <<-\EOF &&
91
+ A fileA.t
92
+ A fileB.t
93
+ EOF
94
+ verify_expect
95
+'
96
+
97
+test_expect_success 'quotes' '
98
+ restore_checkpoint &&
99
+
100
+ printf "\"file\\101.t\"" | git add --pathspec-from-file=- &&
101
+
102
+ cat >expect <<-\EOF &&
103
+ A fileA.t
104
+ EOF
105
+ verify_expect
106
+'
107
+
108
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
109
+ restore_checkpoint &&
110
+
111
+ printf "\"file\\101.t\"" >list &&
112
+ test_must_fail git add --pathspec-from-file=list --pathspec-file-nul
113
+'
114
+
115
+test_expect_success 'only touches what was listed' '
116
+ restore_checkpoint &&
117
+
118
+ printf "fileB.t\nfileC.t\n" | git add --pathspec-from-file=- &&
119
+
120
+ cat >expect <<-\EOF &&
121
+ A fileB.t
122
+ A fileC.t
123
+ EOF
124
+ verify_expect
125
+'
126
+
127
+test_done