Skip to content

Commit 44df2e2

Browse files
jmahgitster
authored andcommitted
stash: Don't fail if work dir contains file named 'HEAD'
When performing a plain "git stash" (without --patch), git-diff would fail with "fatal: ambiguous argument 'HEAD': both revision and filename". The output was piped into git-update-index, masking the failed exit status. The output is now sent to a temporary file (which is cleaned up by existing code), and the exit status is checked. The "HEAD" arg to the git-diff invocation has been disambiguated too, of course. In patch mode, "git stash -p" would fail harmlessly, leaving the working dir untouched. Interactive adding is fine, but the resulting tree was diffed with an ambiguous 'HEAD' argument. Use >foo (no space) when redirecting output. In t3904, checks and operations on each file are in the order they'll appear when interactively staging. In t3905, fix a bug in "stash save --include-untracked -q is quiet": The redirected stdout file was considered untracked, and so was removed from the working directory. Use test path helper functions where appropriate. Signed-off-by: Jonathon Mah <[email protected]> Acked-by: Thomas Rast <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 17b4e93 commit 44df2e2

File tree

4 files changed

+80
-31
lines changed

4 files changed

+80
-31
lines changed

git-stash.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ create_stash () {
115115
git read-tree --index-output="$TMPindex" -m $i_tree &&
116116
GIT_INDEX_FILE="$TMPindex" &&
117117
export GIT_INDEX_FILE &&
118-
git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
118+
git diff --name-only -z HEAD -- >"$TMP-stagenames" &&
119+
git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
119120
git write-tree &&
120121
rm -f "$TMPindex"
121122
) ) ||
@@ -134,7 +135,7 @@ create_stash () {
134135
w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
135136
die "$(gettext "Cannot save the current worktree state")"
136137

137-
git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
138+
git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
138139
test -s "$TMP-patch" ||
139140
die "$(gettext "No changes selected")"
140141

@@ -491,7 +492,7 @@ drop_stash () {
491492
die "$(eval_gettext "\${REV}: Could not drop stash entry")"
492493

493494
# clear_stash if we just dropped the last stash entry
494-
git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
495+
git rev-parse --verify "$ref_stash@{0}" >/dev/null 2>&1 || clear_stash
495496
}
496497

497498
apply_to_branch () {

t/t3903-stash.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,4 +601,28 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
601601
test_cmp expect actual
602602
'
603603

604+
cat > expect << EOF
605+
diff --git a/HEAD b/HEAD
606+
new file mode 100644
607+
index 0000000..fe0cbee
608+
--- /dev/null
609+
+++ b/HEAD
610+
@@ -0,0 +1 @@
611+
+file-not-a-ref
612+
EOF
613+
614+
test_expect_success 'stash where working directory contains "HEAD" file' '
615+
git stash clear &&
616+
git reset --hard &&
617+
echo file-not-a-ref > HEAD &&
618+
git add HEAD &&
619+
test_tick &&
620+
git stash &&
621+
git diff-files --quiet &&
622+
git diff-index --cached --quiet HEAD &&
623+
test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
624+
git diff stash^..stash > output &&
625+
test_cmp output expect
626+
'
627+
604628
test_done

t/t3904-stash-patch.sh

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ test_expect_success PERL 'setup' '
77
mkdir dir &&
88
echo parent > dir/foo &&
99
echo dummy > bar &&
10-
git add bar dir/foo &&
10+
echo committed > HEAD &&
11+
git add bar dir/foo HEAD &&
1112
git commit -m initial &&
1213
test_tick &&
1314
test_commit second dir/foo head &&
@@ -17,47 +18,57 @@ test_expect_success PERL 'setup' '
1718
save_head
1819
'
1920

20-
# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
21+
# note: order of files with unstaged changes: HEAD bar dir/foo
2122

2223
test_expect_success PERL 'saying "n" does nothing' '
24+
set_state HEAD HEADfile_work HEADfile_index &&
2325
set_state dir/foo work index &&
24-
(echo n; echo n) | test_must_fail git stash save -p &&
25-
verify_state dir/foo work index &&
26-
verify_saved_state bar
26+
(echo n; echo n; echo n) | test_must_fail git stash save -p &&
27+
verify_state HEAD HEADfile_work HEADfile_index &&
28+
verify_saved_state bar &&
29+
verify_state dir/foo work index
2730
'
2831

2932
test_expect_success PERL 'git stash -p' '
30-
(echo n; echo y) | git stash save -p &&
31-
verify_state dir/foo head index &&
33+
(echo y; echo n; echo y) | git stash save -p &&
34+
verify_state HEAD committed HEADfile_index &&
3235
verify_saved_state bar &&
36+
verify_state dir/foo head index &&
3337
git reset --hard &&
3438
git stash apply &&
35-
verify_state dir/foo work head &&
36-
verify_state bar dummy dummy
39+
verify_state HEAD HEADfile_work committed &&
40+
verify_state bar dummy dummy &&
41+
verify_state dir/foo work head
3742
'
3843

3944
test_expect_success PERL 'git stash -p --no-keep-index' '
40-
set_state dir/foo work index &&
45+
set_state HEAD HEADfile_work HEADfile_index &&
4146
set_state bar bar_work bar_index &&
42-
(echo n; echo y) | git stash save -p --no-keep-index &&
43-
verify_state dir/foo head head &&
47+
set_state dir/foo work index &&
48+
(echo y; echo n; echo y) | git stash save -p --no-keep-index &&
49+
verify_state HEAD committed committed &&
4450
verify_state bar bar_work dummy &&
51+
verify_state dir/foo head head &&
4552
git reset --hard &&
4653
git stash apply --index &&
47-
verify_state dir/foo work index &&
48-
verify_state bar dummy bar_index
54+
verify_state HEAD HEADfile_work HEADfile_index &&
55+
verify_state bar dummy bar_index &&
56+
verify_state dir/foo work index
4957
'
5058

5159
test_expect_success PERL 'git stash --no-keep-index -p' '
52-
set_state dir/foo work index &&
60+
set_state HEAD HEADfile_work HEADfile_index &&
5361
set_state bar bar_work bar_index &&
54-
(echo n; echo y) | git stash save --no-keep-index -p &&
62+
set_state dir/foo work index &&
63+
(echo y; echo n; echo y) | git stash save --no-keep-index -p &&
64+
verify_state HEAD committed committed &&
5565
verify_state dir/foo head head &&
5666
verify_state bar bar_work dummy &&
5767
git reset --hard &&
5868
git stash apply --index &&
59-
verify_state dir/foo work index &&
60-
verify_state bar dummy bar_index
69+
verify_state HEAD HEADfile_work HEADfile_index &&
70+
verify_state bar dummy bar_index &&
71+
verify_state dir/foo work index
6172
'
6273

6374
test_expect_success PERL 'none of this moved HEAD' '

t/t3905-stash-include-untracked.sh

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ test_expect_success 'stash save --include-untracked some dirty working directory
1717
echo 3 > file &&
1818
test_tick &&
1919
echo 1 > file2 &&
20+
echo 1 > HEAD &&
2021
mkdir untracked &&
2122
echo untracked >untracked/untracked &&
2223
git stash --include-untracked &&
@@ -35,6 +36,13 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'
3536
'
3637

3738
cat > expect.diff <<EOF
39+
diff --git a/HEAD b/HEAD
40+
new file mode 100644
41+
index 0000000..d00491f
42+
--- /dev/null
43+
+++ b/HEAD
44+
@@ -0,0 +1 @@
45+
+1
3846
diff --git a/file2 b/file2
3947
new file mode 100644
4048
index 0000000..d00491f
@@ -51,14 +59,16 @@ index 0000000..5a72eb2
5159
+untracked
5260
EOF
5361
cat > expect.lstree <<EOF
62+
HEAD
5463
file2
5564
untracked
5665
EOF
5766

5867
test_expect_success 'stash save --include-untracked stashed the untracked files' '
59-
test "!" -f file2 &&
60-
test ! -e untracked &&
61-
git diff HEAD stash^3 -- file2 untracked >actual &&
68+
test_path_is_missing file2 &&
69+
test_path_is_missing untracked &&
70+
test_path_is_missing HEAD &&
71+
git diff HEAD stash^3 -- HEAD file2 untracked >actual &&
6272
test_cmp expect.diff actual &&
6373
git ls-tree --name-only stash^3: >actual &&
6474
test_cmp expect.lstree actual
@@ -75,6 +85,7 @@ git clean --force --quiet
7585

7686
cat > expect <<EOF
7787
M file
88+
?? HEAD
7889
?? actual
7990
?? expect
8091
?? file2
@@ -116,10 +127,12 @@ test_expect_success 'stash save --include-untracked dirty index got stashed' '
116127

117128
git reset > /dev/null
118129

130+
# Must direct output somewhere where it won't be considered an untracked file
119131
test_expect_success 'stash save --include-untracked -q is quiet' '
120132
echo 1 > file5 &&
121-
git stash save --include-untracked --quiet > output.out 2>&1 &&
122-
test ! -s output.out
133+
git stash save --include-untracked --quiet > .git/stash-output.out 2>&1 &&
134+
test_line_count = 0 .git/stash-output.out &&
135+
rm -f .git/stash-output.out
123136
'
124137

125138
test_expect_success 'stash save --include-untracked removed files' '
@@ -133,7 +146,7 @@ rm -f expect
133146

134147
test_expect_success 'stash save --include-untracked removed files got stashed' '
135148
git stash pop &&
136-
test ! -f file
149+
test_path_is_missing file
137150
'
138151

139152
cat > .gitignore <<EOF
@@ -155,14 +168,14 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '
155168
test_expect_success 'stash save -u can stash with only untracked files different' '
156169
echo 4 > file4 &&
157170
git stash -u &&
158-
test "!" -f file4
171+
test_path_is_missing file4
159172
'
160173

161174
test_expect_success 'stash save --all does not respect .gitignore' '
162175
git stash -a &&
163-
test "!" -f ignored &&
164-
test "!" -e ignored.d &&
165-
test "!" -f .gitignore
176+
test_path_is_missing ignored &&
177+
test_path_is_missing ignored.d &&
178+
test_path_is_missing .gitignore
166179
'
167180

168181
test_expect_success 'stash save --all is stash poppable' '

0 commit comments

Comments
 (0)