Skip to content

Commit 6465a29

Browse files
committed
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 <[email protected]>
1 parent d34eaf4 commit 6465a29

File tree

3 files changed

+139
-1
lines changed

3 files changed

+139
-1
lines changed

Documentation/git-stash.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ SYNOPSIS
1515
'git stash' branch <branchname> [<stash>]
1616
'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
1717
[-u|--include-untracked] [-a|--all] [-m|--message <message>]
18+
[--pathspec-from-file=<file> [--pathspec-file-nul]]
1819
[--] [<pathspec>...]]
1920
'git stash' clear
2021
'git stash' create [<message>]
@@ -46,7 +47,7 @@ stash index (e.g. the integer `n` is equivalent to `stash@{n}`).
4647
COMMANDS
4748
--------
4849

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>...]::
5051

5152
Save your local modifications to a new 'stash entry' and roll them
5253
back to HEAD (in the working tree and in the index).
@@ -194,6 +195,23 @@ to learn how to operate the `--patch` mode.
194195
The `--patch` option implies `--keep-index`. You can use
195196
`--no-keep-index` to override this.
196197

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+
197215
-q::
198216
--quiet::
199217
This option is only valid for `apply`, `drop`, `pop`, `push`,

builtin/stash.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static const char * const git_stash_usage[] = {
2727
N_("git stash clear"),
2828
N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
2929
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
30+
" [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
3031
" [--] [<pathspec>...]]"),
3132
N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
3233
" [-u|--include-untracked] [-a|--all] [<message>]"),
@@ -1459,7 +1460,9 @@ static int push_stash(int argc, const char **argv, const char *prefix,
14591460
int patch_mode = 0;
14601461
int include_untracked = 0;
14611462
int quiet = 0;
1463+
int pathspec_file_nul = 0;
14621464
const char *stash_msg = NULL;
1465+
const char *pathspec_from_file = NULL;
14631466
struct pathspec ps;
14641467
struct option options[] = {
14651468
OPT_BOOL('k', "keep-index", &keep_index,
@@ -1473,6 +1476,8 @@ static int push_stash(int argc, const char **argv, const char *prefix,
14731476
N_("include ignore files"), 2),
14741477
OPT_STRING('m', "message", &stash_msg, N_("message"),
14751478
N_("stash message")),
1479+
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
1480+
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
14761481
OPT_END()
14771482
};
14781483

@@ -1495,6 +1500,21 @@ static int push_stash(int argc, const char **argv, const char *prefix,
14951500

14961501
parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
14971502
prefix, argv);
1503+
1504+
if (pathspec_from_file) {
1505+
if (patch_mode)
1506+
die(_("--pathspec-from-file is incompatible with --patch"));
1507+
1508+
if (ps.nr)
1509+
die(_("--pathspec-from-file is incompatible with pathspec arguments"));
1510+
1511+
parse_pathspec_file(&ps, 0,
1512+
PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
1513+
prefix, pathspec_from_file, pathspec_file_nul);
1514+
} else if (pathspec_file_nul) {
1515+
die(_("--pathspec-file-nul requires --pathspec-from-file"));
1516+
}
1517+
14981518
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
14991519
include_untracked);
15001520
}

t/t3909-stash-pathspec-file.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)