reset: support the `--pathspec-from-file` option
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>
Alexandr Miloslavskiy committed
Nov 19, 2019 at 16:48 UTC
64bac8df972782b3b3dd2841699f076c7573c1db
3 files changed
+192
-5
Documentation/git-reset.txt
+18
-3
@@ -9,18 +9,20 @@ SYNOPSIS
9
--------
10
[verse]
11
'git reset' [-q] [<tree-ish>] [--] <pathspec>...
12
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
13
'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
14
'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
15
16
DESCRIPTION
17
-----------
17
-In the first and second form, copy entries from `<tree-ish>` to the index.
18
-In the third form, set the current branch head (`HEAD`) to `<commit>`,
18
+In the first three forms, copy entries from `<tree-ish>` to the index.
19
+In the last form, set the current branch head (`HEAD`) to `<commit>`,
20
optionally modifying index and working tree to match.
21
The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms.
22
23
'git reset' [-q] [<tree-ish>] [--] <pathspec>...::
23
- This form resets the index entries for all paths that match the
24
+'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]::
25
+ These forms reset the index entries for all paths that match the
26
`<pathspec>` to their state at `<tree-ish>`. (It does not affect
27
the working tree or the current branch.)
28
+
@@ -101,6 +103,19 @@ OPTIONS
103
`reset.quiet` config option. `--quiet` and `--no-quiet` will
104
override the default behavior.
105
106
+--pathspec-from-file=<file>::
107
+ Pathspec is passed in `<file>` instead of commandline args. If
108
+ `<file>` is exactly `-` then standard input is used. Pathspec
109
+ elements are separated by LF or CR/LF. Pathspec elements can be
110
+ quoted as explained for the configuration variable `core.quotePath`
111
+ (see linkgit:git-config[1]). See also `--pathspec-file-nul` and
112
+ global `--literal-pathspecs`.
113
+
114
+--pathspec-file-nul::
115
+ Only meaningful with `--pathspec-from-file`. Pathspec elements are
116
+ separated with NUL character and all other characters are taken
117
+ literally (including newlines and quotes).
118
+
119
\--::
120
Do not interpret any more arguments as options.
121
builtin/reset.c
+19
-2
@@ -31,6 +31,7 @@
31
static const char * const git_reset_usage[] = {
32
N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
33
N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
34
+ N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
35
N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
36
NULL
37
};
@@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
285
int cmd_reset(int argc, const char **argv, const char *prefix)
286
{
287
int reset_type = NONE, update_ref_status = 0, quiet = 0;
287
- int patch_mode = 0, unborn;
288
- const char *rev;
288
+ int patch_mode = 0, pathspec_file_nul = 0, unborn;
289
+ const char *rev, *pathspec_from_file = NULL;
290
struct object_id oid;
291
struct pathspec pathspec;
292
int intent_to_add = 0;
@@ -306,6 +307,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
307
OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
308
OPT_BOOL('N', "intent-to-add", &intent_to_add,
309
N_("record only the fact that removed paths will be added later")),
310
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
311
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
312
OPT_END()
313
};
314
@@ -316,6 +319,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
319
PARSE_OPT_KEEP_DASHDASH);
320
parse_args(&pathspec, argv, prefix, patch_mode, &rev);
321
322
+ if (pathspec_from_file) {
323
+ if (patch_mode)
324
+ die(_("--pathspec-from-file is incompatible with --patch"));
325
+
326
+ if (pathspec.nr)
327
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
328
+
329
+ parse_pathspec_file(&pathspec, 0,
330
+ PATHSPEC_PREFER_FULL,
331
+ prefix, pathspec_from_file, pathspec_file_nul);
332
+ } else if (pathspec_file_nul) {
333
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
334
+ }
335
+
336
unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
337
if (unborn) {
338
/* reset on unborn branch: treat as reset to empty tree */
t/t7107-reset-pathspec-file.sh
new
+155
@@ -0,0 +1,155 @@
1
+#!/bin/sh
2
+
3
+test_description='reset --pathspec-from-file'
4
+
5
+. ./test-lib.sh
6
+
7
+test_tick
8
+
9
+test_expect_success setup '
10
+ echo A >fileA.t &&
11
+ echo B >fileB.t &&
12
+ echo C >fileC.t &&
13
+ echo D >fileD.t &&
14
+ git add . &&
15
+ git commit --include . -m "Commit" &&
16
+ git tag checkpoint
17
+'
18
+
19
+restore_checkpoint () {
20
+ git reset --hard checkpoint
21
+}
22
+
23
+verify_expect () {
24
+ git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual &&
25
+ test_cmp expect actual
26
+}
27
+
28
+test_expect_success '--pathspec-from-file from stdin' '
29
+ restore_checkpoint &&
30
+
31
+ git rm fileA.t &&
32
+ echo fileA.t | git reset --pathspec-from-file=- &&
33
+
34
+ cat >expect <<-\EOF &&
35
+ D fileA.t
36
+ EOF
37
+ verify_expect
38
+'
39
+
40
+test_expect_success '--pathspec-from-file from file' '
41
+ restore_checkpoint &&
42
+
43
+ git rm fileA.t &&
44
+ echo fileA.t >list &&
45
+ git reset --pathspec-from-file=list &&
46
+
47
+ cat >expect <<-\EOF &&
48
+ D fileA.t
49
+ EOF
50
+ verify_expect
51
+'
52
+
53
+test_expect_success 'NUL delimiters' '
54
+ restore_checkpoint &&
55
+
56
+ git rm fileA.t fileB.t &&
57
+ printf "fileA.t\0fileB.t\0" | git reset --pathspec-from-file=- --pathspec-file-nul &&
58
+
59
+ cat >expect <<-\EOF &&
60
+ D fileA.t
61
+ D fileB.t
62
+ EOF
63
+ verify_expect
64
+'
65
+
66
+test_expect_success 'LF delimiters' '
67
+ restore_checkpoint &&
68
+
69
+ git rm fileA.t fileB.t &&
70
+ printf "fileA.t\nfileB.t\n" | git reset --pathspec-from-file=- &&
71
+
72
+ cat >expect <<-\EOF &&
73
+ D fileA.t
74
+ D fileB.t
75
+ EOF
76
+ verify_expect
77
+'
78
+
79
+test_expect_success 'no trailing delimiter' '
80
+ restore_checkpoint &&
81
+
82
+ git rm fileA.t fileB.t &&
83
+ printf "fileA.t\nfileB.t" | git reset --pathspec-from-file=- &&
84
+
85
+ cat >expect <<-\EOF &&
86
+ D fileA.t
87
+ D fileB.t
88
+ EOF
89
+ verify_expect
90
+'
91
+
92
+test_expect_success 'CRLF delimiters' '
93
+ restore_checkpoint &&
94
+
95
+ git rm fileA.t fileB.t &&
96
+ printf "fileA.t\r\nfileB.t\r\n" | git reset --pathspec-from-file=- &&
97
+
98
+ cat >expect <<-\EOF &&
99
+ D fileA.t
100
+ D fileB.t
101
+ EOF
102
+ verify_expect
103
+'
104
+
105
+test_expect_success 'quotes' '
106
+ restore_checkpoint &&
107
+
108
+ git rm fileA.t &&
109
+ printf "\"file\\101.t\"" | git reset --pathspec-from-file=- &&
110
+
111
+ cat >expect <<-\EOF &&
112
+ D fileA.t
113
+ EOF
114
+ verify_expect
115
+'
116
+
117
+test_expect_success 'quotes not compatible with --pathspec-file-nul' '
118
+ restore_checkpoint &&
119
+
120
+ git rm fileA.t &&
121
+ printf "\"file\\101.t\"" >list &&
122
+ # Note: "git reset" has not yet learned to fail on wrong pathspecs
123
+ git reset --pathspec-from-file=list --pathspec-file-nul &&
124
+
125
+ cat >expect <<-\EOF &&
126
+ D fileA.t
127
+ EOF
128
+ test_must_fail verify_expect
129
+'
130
+
131
+test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' '
132
+ restore_checkpoint &&
133
+
134
+ git rm fileA.t &&
135
+ echo fileA.t >list &&
136
+ test_must_fail git reset --soft --pathspec-from-file=list &&
137
+ test_must_fail git reset --hard --pathspec-from-file=list
138
+'
139
+
140
+test_expect_success 'only touches what was listed' '
141
+ restore_checkpoint &&
142
+
143
+ git rm fileA.t fileB.t fileC.t fileD.t &&
144
+ printf "fileB.t\nfileC.t\n" | git reset --pathspec-from-file=- &&
145
+
146
+ cat >expect <<-\EOF &&
147
+ D fileA.t
148
+ D fileB.t
149
+ D fileC.t
150
+ D fileD.t
151
+ EOF
152
+ verify_expect
153
+'
154
+
155
+test_done