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>
This commit is contained in:
Alexandr Miloslavskiy 2019-12-03 14:02:13 +00:00 committed by Junio C Hamano
parent 21bb3083c3
commit bebb5d6d6b
3 changed files with 168 additions and 5 deletions

View File

@ -11,7 +11,8 @@ SYNOPSIS
'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p] 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
[--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize] [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
[--chmod=(+|-)x] [--] [<pathspec>...] [--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[--] [<pathspec>...]
DESCRIPTION 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 bit is only changed in the index, the files on disk are left
unchanged. 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 This option can be used to separate command-line options from
the list of files, (useful when filenames might be mistaken the list of files, (useful when filenames might be mistaken

View File

@ -28,6 +28,8 @@ static const char * const builtin_add_usage[] = {
static int patch_interactive, add_interactive, edit_interactive; static int patch_interactive, add_interactive, edit_interactive;
static int take_worktree_changes; static int take_worktree_changes;
static int add_renormalize; static int add_renormalize;
static int pathspec_file_nul;
static const char *pathspec_from_file;
struct update_callback_data { struct update_callback_data {
int flags; int flags;
@ -309,6 +311,8 @@ static struct option builtin_add_options[] = {
N_("override the executable bit of the listed files")), N_("override the executable bit of the listed files")),
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
N_("warn when adding an embedded repository")), N_("warn when adding an embedded repository")),
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(), OPT_END(),
}; };
@ -402,11 +406,17 @@ int cmd_add(int argc, const char **argv, const char *prefix)
builtin_add_usage, PARSE_OPT_KEEP_ARGV0); builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
if (patch_interactive) if (patch_interactive)
add_interactive = 1; 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)); 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)); return(edit_patch(argc, argv, prefix));
}
argc--; argc--;
argv++; argv++;
@ -439,13 +449,25 @@ int cmd_add(int argc, const char **argv, const char *prefix)
PATHSPEC_SYMLINK_LEADING_PATH, PATHSPEC_SYMLINK_LEADING_PATH,
prefix, argv); 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, _("Nothing specified, nothing added.\n"));
fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
return 0; 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..." */ /* Turn "git add pathspec..." to "git add -A pathspec..." */
addremove = 1; addremove = 1;

127
t/t3704-add-pathspec-file.sh Executable file
View File

@ -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