stash push: 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 `-`. Such use case is not really expected. 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
Feb 17, 2020 at 17:25 UTC
8a98758a8d9dbd807fc2f1576e9141c1b720c5c1
3 files changed
+139
-1
Documentation/git-stash.txt
+19
-1
@@ -15,6 +15,7 @@ SYNOPSIS
15
'git stash' branch <branchname> [<stash>]
16
'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
17
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
18
+ [--pathspec-from-file=<file> [--pathspec-file-nul]]
19
[--] [<pathspec>...]]
20
'git stash' clear
21
'git stash' create [<message>]
@@ -46,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
47
COMMANDS
48
--------
49
49
-push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--] [<pathspec>...]::
50
+push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] [--] [<pathspec>...]::
51
52
Save your local modifications to a new 'stash entry' and roll them
53
back to HEAD (in the working tree and in the index).
@@ -194,6 +195,23 @@ to learn how to operate the `--patch` mode.
195
The `--patch` option implies `--keep-index`. You can use
196
`--no-keep-index` to override this.
197
198
+--pathspec-from-file=<file>::
199
+ This option is only valid for `push` command.
200
++
201
+Pathspec is passed in `<file>` instead of commandline args. If
202
+`<file>` is exactly `-` then standard input is used. Pathspec
203
+elements are separated by LF or CR/LF. Pathspec elements can be
204
+quoted as explained for the configuration variable `core.quotePath`
205
+(see linkgit:git-config[1]). See also `--pathspec-file-nul` and
206
+global `--literal-pathspecs`.
207
+
208
+--pathspec-file-nul::
209
+ This option is only valid for `push` command.
210
++
211
+Only meaningful with `--pathspec-from-file`. Pathspec elements are
212
+separated with NUL character and all other characters are taken
213
+literally (including newlines and quotes).
214
+
215
-q::
216
--quiet::
217
This option is only valid for `apply`, `drop`, `pop`, `push`,
builtin/stash.c
+20
@@ -27,6 +27,7 @@ static const char * const git_stash_usage[] = {
27
N_("git stash clear"),
28
N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
29
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
30
+ " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
31
" [--] [<pathspec>...]]"),
32
N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
33
" [-u|--include-untracked] [-a|--all] [<message>]"),
@@ -1456,7 +1457,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,
1457
int patch_mode = 0;
1458
int include_untracked = 0;
1459
int quiet = 0;
1460
+ int pathspec_file_nul = 0;
1461
const char *stash_msg = NULL;
1462
+ const char *pathspec_from_file = NULL;
1463
struct pathspec ps;
1464
struct option options[] = {
1465
OPT_BOOL('k', "keep-index", &keep_index,
@@ -1470,6 +1473,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
1473
N_("include ignore files"), 2),
1474
OPT_STRING('m', "message", &stash_msg, N_("message"),
1475
N_("stash message")),
1476
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
1477
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
1478
OPT_END()
1479
};
1480
@@ -1492,6 +1497,21 @@ static int push_stash(int argc, const char **argv, const char *prefix,
1497
1498
parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
1499
prefix, argv);
1500
+
1501
+ if (pathspec_from_file) {
1502
+ if (patch_mode)
1503
+ die(_("--pathspec-from-file is incompatible with --patch"));
1504
+
1505
+ if (ps.nr)
1506
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
1507
+
1508
+ parse_pathspec_file(&ps, 0,
1509
+ PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
1510
+ prefix, pathspec_from_file, pathspec_file_nul);
1511
+ } else if (pathspec_file_nul) {
1512
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
1513
+ }
1514
+
1515
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
1516
include_untracked);
1517
}
t/t3909-stash-pathspec-file.sh
new
+100
@@ -0,0 +1,100 @@
1
+#!/bin/sh
2
+
3
+test_description='stash --pathspec-from-file'
4
+
5
+. ./test-lib.sh
6
+
7
+test_tick
8
+
9
+test_expect_success setup '
10
+ >fileA.t &&
11
+ >fileB.t &&
12
+ >fileC.t &&
13
+ >fileD.t &&
14
+ git add fileA.t fileB.t fileC.t fileD.t &&
15
+ git commit -m "Files" &&
16
+
17
+ git tag checkpoint
18
+'
19
+
20
+restore_checkpoint () {
21
+ git reset --hard checkpoint
22
+}
23
+
24
+verify_expect () {
25
+ git stash show --name-status >actual &&
26
+ test_cmp expect actual
27
+}
28
+
29
+test_expect_success 'simplest' '
30
+ restore_checkpoint &&
31
+
32
+ # More files are written to make sure that git didnt ignore
33
+ # --pathspec-from-file, stashing everything
34
+ echo A >fileA.t &&
35
+ echo B >fileB.t &&
36
+ echo C >fileC.t &&
37
+ echo D >fileD.t &&
38
+
39
+ cat >expect <<-\EOF &&
40
+ M fileA.t
41
+ EOF
42
+
43
+ echo fileA.t | git stash push --pathspec-from-file=- &&
44
+ verify_expect
45
+'
46
+
47
+test_expect_success '--pathspec-file-nul' '
48
+ restore_checkpoint &&
49
+
50
+ # More files are written to make sure that git didnt ignore
51
+ # --pathspec-from-file, stashing everything
52
+ echo A >fileA.t &&
53
+ echo B >fileB.t &&
54
+ echo C >fileC.t &&
55
+ echo D >fileD.t &&
56
+
57
+ cat >expect <<-\EOF &&
58
+ M fileA.t
59
+ M fileB.t
60
+ EOF
61
+
62
+ printf "fileA.t\0fileB.t\0" | git stash push --pathspec-from-file=- --pathspec-file-nul &&
63
+ verify_expect
64
+'
65
+
66
+test_expect_success 'only touches what was listed' '
67
+ restore_checkpoint &&
68
+
69
+ # More files are written to make sure that git didnt ignore
70
+ # --pathspec-from-file, stashing everything
71
+ echo A >fileA.t &&
72
+ echo B >fileB.t &&
73
+ echo C >fileC.t &&
74
+ echo D >fileD.t &&
75
+
76
+ cat >expect <<-\EOF &&
77
+ M fileB.t
78
+ M fileC.t
79
+ EOF
80
+
81
+ printf "fileB.t\nfileC.t\n" | git stash push --pathspec-from-file=- &&
82
+ verify_expect
83
+'
84
+
85
+test_expect_success 'error conditions' '
86
+ restore_checkpoint &&
87
+ echo A >fileA.t &&
88
+ echo fileA.t >list &&
89
+
90
+ test_must_fail git stash push --pathspec-from-file=list --patch 2>err &&
91
+ test_i18ngrep -e "--pathspec-from-file is incompatible with --patch" err &&
92
+
93
+ test_must_fail git stash push --pathspec-from-file=list -- fileA.t 2>err &&
94
+ test_i18ngrep -e "--pathspec-from-file is incompatible with pathspec arguments" err &&
95
+
96
+ test_must_fail git stash push --pathspec-file-nul 2>err &&
97
+ test_i18ngrep -e "--pathspec-file-nul requires --pathspec-from-file" err
98
+'
99
+
100
+test_done