commit: support the --pathspec-from-file option
Decisions taken for simplicity: 1) For now, `--pathspec-from-file` is declared incompatible with `--interactive/--patch`, even when <file> is not `stdin`. Such use case it not really expected. Also, it would require changes to `interactive_add()`. 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
Nov 19, 2019 at 16:48 UTC
e440fc5888bd5faf1775fc61621caa3c5fab96b6
3 files changed
+166
-5
Documentation/git-commit.txt
+15
-1
@@ -13,7 +13,8 @@ SYNOPSIS
13
[-F <file> | -m <msg>] [--reset-author] [--allow-empty]
14
[--allow-empty-message] [--no-verify] [-e] [--author=<author>]
15
[--date=<date>] [--cleanup=<mode>] [--[no-]status]
16
- [-i | -o] [-S[<keyid>]] [--] [<pathspec>...]
16
+ [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
17
+ [-S[<keyid>]] [--] [<pathspec>...]
18
19
DESCRIPTION
20
-----------
@@ -278,6 +279,19 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].)
279
already been staged. If used together with `--allow-empty`
280
paths are also not required, and an empty commit will be created.
281
282
+--pathspec-from-file=<file>::
283
+ Pathspec is passed in `<file>` instead of commandline args. If
284
+ `<file>` is exactly `-` then standard input is used. Pathspec
285
+ elements are separated by LF or CR/LF. Pathspec elements can be
286
+ quoted as explained for the configuration variable `core.quotePath`
287
+ (see linkgit:git-config[1]). See also `--pathspec-file-nul` and
288
+ global `--literal-pathspecs`.
289
+
290
+--pathspec-file-nul::
291
+ Only meaningful with `--pathspec-from-file`. Pathspec elements are
292
+ separated with NUL character and all other characters are taken
293
+ literally (including newlines and quotes).
294
+
295
-u[<mode>]::
296
--untracked-files[=<mode>]::
297
Show untracked files.
builtin/commit.c
+21
-4
@@ -107,9 +107,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff;
107
static int edit_flag = -1; /* unspecified */
108
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
109
static int config_commit_verbose = -1; /* unspecified */
110
-static int no_post_rewrite, allow_empty_message;
110
+static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
111
static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
112
-static char *sign_commit;
112
+static char *sign_commit, *pathspec_from_file;
113
114
/*
115
* The default commit message cleanup mode will remove the lines
@@ -343,6 +343,23 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
343
PATHSPEC_PREFER_FULL,
344
prefix, argv);
345
346
+ if (pathspec_from_file) {
347
+ if (interactive)
348
+ die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
349
+
350
+ if (pathspec.nr)
351
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
352
+
353
+ parse_pathspec_file(&pathspec, 0,
354
+ PATHSPEC_PREFER_FULL,
355
+ prefix, pathspec_from_file, pathspec_file_nul);
356
+ } else if (pathspec_file_nul) {
357
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
358
+ }
359
+
360
+ if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
361
+ die(_("No paths with --include/--only does not make sense."));
362
+
363
if (read_cache_preload(&pathspec) < 0)
364
die(_("index file corrupt"));
365
@@ -1198,8 +1215,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
1215
1216
if (also + only + all + interactive > 1)
1217
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
1201
- if (argc == 0 && (also || (only && !amend && !allow_empty)))
1202
- die(_("No paths with --include/--only does not make sense."));
1218
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
1219
1220
handle_untracked_files_arg(s);
@@ -1535,6 +1550,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
1550
OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
1551
OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
1552
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
1553
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
1554
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
1555
/* end commit contents options */
1556
1557
OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
t/t7526-commit-pathspec-file.sh
new
+130
@@ -0,0 +1,130 @@
1
+#!/bin/sh
2
+
3
+test_description='commit --pathspec-from-file'
4
+
5
+. ./test-lib.sh
6
+
7
+test_tick
8
+
9
+test_expect_success setup '
10
+ test_commit file0 &&
11
+ git tag checkpoint &&
12
+
13
+ echo A >fileA.t &&
14
+ echo B >fileB.t &&
15
+ echo C >fileC.t &&
16
+ echo D >fileD.t &&
17
+ git add fileA.t fileB.t fileC.t fileD.t
18
+'
19
+
20
+restore_checkpoint () {
21
+ git reset --soft checkpoint
22
+}
23
+
24
+verify_expect () {
25
+ git diff-tree --no-commit-id --name-status -r HEAD >actual &&
26
+ test_cmp expect actual
27
+}
28
+
29
+test_expect_success '--pathspec-from-file from stdin' '
30
+ restore_checkpoint &&
31
+
32
+ echo fileA.t | git commit --pathspec-from-file=- -m "Commit" &&
33
+
34
+ cat >expect <<-\EOF &&
35
+ A fileA.t
36
+ EOF
37
+ verify_expect
38
+'
39
+
40
+test_expect_success '--pathspec-from-file from file' '
41
+ restore_checkpoint &&
42
+
43
+ echo fileA.t >list &&
44
+ git commit --pathspec-from-file=list -m "Commit" &&
45
+
46
+ cat >expect <<-\EOF &&
47
+ A fileA.t
48
+ EOF
49
+ verify_expect
50
+'
51
+
52
+test_expect_success 'NUL delimiters' '
53
+ restore_checkpoint &&
54
+
55
+ printf "fileA.t\0fileB.t\0" | git commit --pathspec-from-file=- --pathspec-file-nul -m "Commit" &&
56
+
57
+ cat >expect <<-\EOF &&
58
+ A fileA.t
59
+ A fileB.t
60
+ EOF
61
+ verify_expect
62
+'
63
+
64
+test_expect_success 'LF delimiters' '
65
+ restore_checkpoint &&
66
+
67
+ printf "fileA.t\nfileB.t\n" | git commit --pathspec-from-file=- -m "Commit" &&
68
+
69
+ cat >expect <<-\EOF &&
70
+ A fileA.t
71
+ A fileB.t
72
+ EOF
73
+ verify_expect
74
+'
75
+
76
+test_expect_success 'no trailing delimiter' '
77
+ restore_checkpoint &&
78
+
79
+ printf "fileA.t\nfileB.t" | git commit --pathspec-from-file=- -m "Commit" &&
80
+
81
+ cat >expect <<-\EOF &&
82
+ A fileA.t
83
+ A fileB.t
84
+ EOF
85
+ verify_expect
86
+'
87
+
88
+test_expect_success 'CRLF delimiters' '
89
+ restore_checkpoint &&
90
+
91
+ printf "fileA.t\r\nfileB.t\r\n" | git commit --pathspec-from-file=- -m "Commit" &&
92
+
93
+ cat >expect <<-\EOF &&
94
+ A fileA.t
95
+ A fileB.t
96
+ EOF
97
+ verify_expect
98
+'
99
+
100
+test_expect_success 'quotes' '
101
+ restore_checkpoint &&
102
+
103
+ printf "\"file\\101.t\"" | git commit --pathspec-from-file=- -m "Commit" &&
104
+
105
+ cat >expect <<-\EOF &&
106
+ A fileA.t
107
+ EOF
108
+ verify_expect expect
109
+'
110
+
111
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
112
+ restore_checkpoint &&
113
+
114
+ printf "\"file\\101.t\"" >list &&
115
+ test_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m "Commit"
116
+'
117
+
118
+test_expect_success 'only touches what was listed' '
119
+ restore_checkpoint &&
120
+
121
+ printf "fileB.t\nfileC.t\n" | git commit --pathspec-from-file=- -m "Commit" &&
122
+
123
+ cat >expect <<-\EOF &&
124
+ A fileB.t
125
+ A fileC.t
126
+ EOF
127
+ verify_expect
128
+'
129
+
130
+test_done