Skip to content

Commit 0d4cee6

Browse files
benknoblegitster
authored andcommitted
stash: honor stash.index in apply, pop modes
With stash.index=true, git-stash(1) command now tries to reinstate the index by default in the "apply" and "pop" modes. Not doing so creates a common trap [1], [2]: "git stash apply" is not the reverse of "git stash push" because carefully staged indices are lost and have to be manually recreated. OTOH, this mode is not always desirable and may create more conflicts when applying stashes. As usual, "--no-index" will disable this behavior if you set "stash.index". [1]: https://lore.kernel.org/git/CAPx1GvcxyDDQmCssMjEnt6JoV6qPc5ZUpgPLX3mpUC_4PNYA1w@mail.gmail.com/ [2]: https://lore.kernel.org/git/c5a811ac-8cd3-c389-ac6d-29020a648c87@gmail.com/ Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 1f821ce commit 0d4cee6

5 files changed

Lines changed: 225 additions & 3 deletions

File tree

Documentation/config/stash.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
stash.index::
2+
If this is set to true, `git stash apply` and `git stash pop` will
3+
behave as if `--index` was supplied. Defaults to false. See the
4+
descriptions in linkgit:git-stash[1].
5+
16
stash.showIncludeUntracked::
27
If this is set to true, the `git stash show` command will show
38
the untracked files of a stash entry. Defaults to false. See

builtin/stash.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ static struct strbuf stash_index_path = STRBUF_INIT;
130130
static int show_stat = 1;
131131
static int show_patch;
132132
static int show_include_untracked;
133+
static int use_index;
133134

134135
/*
135136
* w_commit is set to the commit containing the working tree
@@ -662,7 +663,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix,
662663
{
663664
int ret = -1;
664665
int quiet = 0;
665-
int index = 0;
666+
int index = use_index;
666667
struct stash_info info = STASH_INFO_INIT;
667668
struct option options[] = {
668669
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
@@ -759,7 +760,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix,
759760
struct repository *repo UNUSED)
760761
{
761762
int ret = -1;
762-
int index = 0;
763+
int index = use_index;
763764
int quiet = 0;
764765
struct stash_info info = STASH_INFO_INIT;
765766
struct option options[] = {
@@ -864,6 +865,10 @@ static int git_stash_config(const char *var, const char *value,
864865
show_include_untracked = git_config_bool(var, value);
865866
return 0;
866867
}
868+
if (!strcmp(var, "stash.index")) {
869+
use_index = git_config_bool(var, value);
870+
return 0;
871+
}
867872
return git_diff_basic_config(var, value, ctx, cb);
868873
}
869874

t/t3903-stash.sh

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ test_expect_success 'apply stashed changes' '
111111
test 1 = $(git show HEAD:file)
112112
'
113113

114+
test_expect_success 'apply stashed changes with stash.index' '
115+
test_config stash.index true &&
116+
git reset --hard HEAD^ &&
117+
echo 5 >other-file &&
118+
git add other-file &&
119+
test_tick &&
120+
git commit -m other-file &&
121+
git stash apply &&
122+
test 3 = $(cat file) &&
123+
test 2 = $(git show :file) &&
124+
test 1 = $(git show HEAD:file)
125+
'
126+
114127
test_expect_success 'apply stashed changes (including index)' '
115128
git reset --hard HEAD^ &&
116129
echo 6 >other-file &&
@@ -150,6 +163,21 @@ test_expect_success 'drop top stash' '
150163
test 1 = $(git show HEAD:file)
151164
'
152165

166+
test_expect_success 'drop top stash with stash.index' '
167+
test_config stash.index true &&
168+
git reset --hard &&
169+
git stash list >expected &&
170+
echo 7 >file &&
171+
git stash &&
172+
git stash drop &&
173+
git stash list >actual &&
174+
test_cmp expected actual &&
175+
git stash apply &&
176+
test 3 = $(cat file) &&
177+
test 2 = $(git show :file) &&
178+
test 1 = $(git show HEAD:file)
179+
'
180+
153181
test_expect_success 'drop middle stash' '
154182
git reset --hard &&
155183
echo 8 >file &&
@@ -170,6 +198,27 @@ test_expect_success 'drop middle stash' '
170198
test 1 = $(git show HEAD:file)
171199
'
172200

201+
test_expect_success 'drop middle stash with stash.index' '
202+
test_config stash.index true &&
203+
git reset --hard &&
204+
echo 8 >file &&
205+
git stash &&
206+
echo 9 >file &&
207+
git stash &&
208+
git stash drop stash@{1} &&
209+
test 2 = $(git stash list | wc -l) &&
210+
git stash apply &&
211+
test 9 = $(cat file) &&
212+
test 1 = $(git show :file) &&
213+
test 1 = $(git show HEAD:file) &&
214+
git reset --hard &&
215+
git stash drop &&
216+
git stash apply &&
217+
test 3 = $(cat file) &&
218+
test 2 = $(git show :file) &&
219+
test 1 = $(git show HEAD:file)
220+
'
221+
173222
test_expect_success 'drop middle stash by index' '
174223
git reset --hard &&
175224
echo 8 >file &&
@@ -236,6 +285,17 @@ test_expect_success 'stash pop' '
236285
test 0 = $(git stash list | wc -l)
237286
'
238287

288+
test_expect_success 'stash pop with stash.index' '
289+
test_config stash.index true &&
290+
git reset --hard &&
291+
setup_stash &&
292+
git stash pop &&
293+
test 3 = $(cat file) &&
294+
test 2 = $(git show :file) &&
295+
test 1 = $(git show HEAD:file) &&
296+
test 0 = $(git stash list | wc -l)
297+
'
298+
239299
cat >expect <<EOF
240300
diff --git a/file2 b/file2
241301
new file mode 100644
@@ -328,6 +388,22 @@ test_expect_success 'pop -q works and is quiet' '
328388
test_must_be_empty output.out
329389
'
330390

391+
test_expect_success 'pop -q works and is quiet with stash.index' '
392+
# Added file, deleted file, modified file all staged for commit
393+
echo foo >new-file &&
394+
echo test >file &&
395+
git add new-file file &&
396+
git rm other-file &&
397+
git stash &&
398+
399+
test_config stash.index true &&
400+
git stash pop -q >output.out 2>&1 &&
401+
echo test >expect &&
402+
git show :file >actual &&
403+
test_cmp expect actual &&
404+
test_must_be_empty output.out
405+
'
406+
331407
test_expect_success 'pop -q --index works and is quiet' '
332408
echo foo >file &&
333409
git add file &&
@@ -1178,6 +1254,19 @@ test_expect_success 'stash -- <pathspec> stashes and restores the file' '
11781254
test_path_is_file bar
11791255
'
11801256

1257+
test_expect_success 'stash -- <pathspec> stashes and restores the file with stash.index' '
1258+
test_config stash.index true &&
1259+
>foo &&
1260+
>bar &&
1261+
git add foo bar &&
1262+
git stash push -- foo &&
1263+
test_path_is_file bar &&
1264+
test_path_is_missing foo &&
1265+
git stash pop --no-index &&
1266+
test_path_is_file foo &&
1267+
test_path_is_file bar
1268+
'
1269+
11811270
test_expect_success 'stash -- <pathspec> stashes in subdirectory' '
11821271
mkdir sub &&
11831272
>foo &&
@@ -1194,6 +1283,24 @@ test_expect_success 'stash -- <pathspec> stashes in subdirectory' '
11941283
test_path_is_file bar
11951284
'
11961285

1286+
test_expect_success 'stash -- <pathspec> stashes in subdirectory with stash.index' '
1287+
test_config stash.index true &&
1288+
rm -r sub &&
1289+
mkdir sub &&
1290+
>foo &&
1291+
>bar &&
1292+
git add foo bar &&
1293+
(
1294+
cd sub &&
1295+
git stash push -- ../foo
1296+
) &&
1297+
test_path_is_file bar &&
1298+
test_path_is_missing foo &&
1299+
git stash pop --no-index &&
1300+
test_path_is_file foo &&
1301+
test_path_is_file bar
1302+
'
1303+
11971304
test_expect_success 'stash with multiple pathspec arguments' '
11981305
>foo &&
11991306
>bar &&
@@ -1209,6 +1316,22 @@ test_expect_success 'stash with multiple pathspec arguments' '
12091316
test_path_is_file extra
12101317
'
12111318

1319+
test_expect_success 'stash with multiple pathspec arguments with stash.index' '
1320+
test_config stash.index true &&
1321+
>foo &&
1322+
>bar &&
1323+
>extra &&
1324+
git add foo bar extra &&
1325+
git stash push -- foo bar &&
1326+
test_path_is_missing bar &&
1327+
test_path_is_missing foo &&
1328+
test_path_is_file extra &&
1329+
git stash pop --no-index &&
1330+
test_path_is_file foo &&
1331+
test_path_is_file bar &&
1332+
test_path_is_file extra
1333+
'
1334+
12121335
test_expect_success 'stash with file including $IFS character' '
12131336
>"foo bar" &&
12141337
>foo &&
@@ -1224,6 +1347,22 @@ test_expect_success 'stash with file including $IFS character' '
12241347
test_path_is_file bar
12251348
'
12261349

1350+
test_expect_success 'stash with file including $IFS character with stash.index' '
1351+
test_config stash.index true &&
1352+
>"foo bar" &&
1353+
>foo &&
1354+
>bar &&
1355+
git add foo* &&
1356+
git stash push -- "foo b*" &&
1357+
test_path_is_missing "foo bar" &&
1358+
test_path_is_file foo &&
1359+
test_path_is_file bar &&
1360+
git stash pop --no-index &&
1361+
test_path_is_file "foo bar" &&
1362+
test_path_is_file foo &&
1363+
test_path_is_file bar
1364+
'
1365+
12271366
test_expect_success 'stash with pathspec matching multiple paths' '
12281367
echo original >file &&
12291368
echo original >other-file &&
@@ -1312,6 +1451,22 @@ test_expect_success 'stash without verb with pathspec' '
13121451
test_path_is_file bar
13131452
'
13141453

1454+
test_expect_success 'stash without verb with pathspec with stash.index' '
1455+
test_config stash.index true &&
1456+
>"foo bar" &&
1457+
>foo &&
1458+
>bar &&
1459+
git add foo* &&
1460+
git stash -- "foo b*" &&
1461+
test_path_is_missing "foo bar" &&
1462+
test_path_is_file foo &&
1463+
test_path_is_file bar &&
1464+
git stash pop --no-index &&
1465+
test_path_is_file "foo bar" &&
1466+
test_path_is_file foo &&
1467+
test_path_is_file bar
1468+
'
1469+
13151470
test_expect_success 'stash -k -- <pathspec> leaves unstaged files intact' '
13161471
git reset &&
13171472
>foo &&

t/t3904-stash-patch.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ test_expect_success 'git stash -p' '
4242
verify_state dir/foo work head
4343
'
4444

45+
test_expect_success 'git stash -p with stash.index' '
46+
test_config stash.index true &&
47+
set_state HEAD HEADfile_work HEADfile_index &&
48+
set_state dir/foo work index &&
49+
test_write_lines y n y | git stash save -p &&
50+
git reset --hard &&
51+
git stash apply &&
52+
verify_state HEAD HEADfile_work HEADfile_index &&
53+
verify_state dir/foo head index
54+
'
55+
4556
test_expect_success 'git stash -p --no-keep-index' '
4657
set_state HEAD HEADfile_work HEADfile_index &&
4758
set_state bar bar_work bar_index &&

t/t3905-stash-include-untracked.sh

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ test_description='Test git stash --include-untracked'
77

88
. ./test-lib.sh
99

10-
test_expect_success 'stash save --include-untracked some dirty working directory' '
10+
setup() {
1111
echo 1 >file &&
1212
git add file &&
1313
test_tick &&
@@ -23,6 +23,10 @@ test_expect_success 'stash save --include-untracked some dirty working directory
2323
git stash --include-untracked &&
2424
git diff-files --quiet &&
2525
git diff-index --cached --quiet HEAD
26+
}
27+
28+
test_expect_success 'stash save --include-untracked some dirty working directory' '
29+
setup
2630
'
2731

2832
test_expect_success 'stash save --include-untracked cleaned the untracked files' '
@@ -108,6 +112,32 @@ test_expect_success 'stash pop after save --include-untracked leaves files untra
108112
test_cmp untracked_expect untracked/untracked
109113
'
110114

115+
test_expect_success 'stash pop after save --include-untracked leaves files untracked again with stash.index' '
116+
git init repo &&
117+
test_when_finished rm -r repo &&
118+
(
119+
cd repo &&
120+
git config stash.index true &&
121+
setup &&
122+
cat >expect <<-EOF &&
123+
MM file
124+
?? HEAD
125+
?? actual
126+
?? expect
127+
?? file2
128+
?? untracked/
129+
EOF
130+
131+
git stash pop &&
132+
git status --porcelain >actual &&
133+
test_cmp expect actual &&
134+
echo 1 >expect_file2 &&
135+
test_cmp expect_file2 file2 &&
136+
echo untracked >untracked_expect &&
137+
test_cmp untracked_expect untracked/untracked
138+
)
139+
'
140+
111141
test_expect_success 'clean up untracked/ directory to prepare for next tests' '
112142
git clean --force --quiet -d
113143
'
@@ -221,6 +251,22 @@ test_expect_success 'stash push with $IFS character' '
221251
test_path_is_file bar
222252
'
223253

254+
test_expect_success 'stash push with $IFS character with stash.index' '
255+
test_config stash.index true &&
256+
>"foo bar" &&
257+
>foo &&
258+
>bar &&
259+
git add foo* &&
260+
git stash push --include-untracked -- "foo b*" &&
261+
test_path_is_missing "foo bar" &&
262+
test_path_is_file foo &&
263+
test_path_is_file bar &&
264+
git stash pop --no-index &&
265+
test_path_is_file "foo bar" &&
266+
test_path_is_file foo &&
267+
test_path_is_file bar
268+
'
269+
224270
test_expect_success 'stash previously ignored file' '
225271
cat >.gitignore <<-EOF &&
226272
ignored

0 commit comments

Comments
 (0)