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 <me@JonathonMah.com> Acked-by: Thomas Rast <trast@student.ethz.ch> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jonathon Mah committed Dec 30, 2011 at 16:14 UTC 44df2e297025d7a9af6c265dfefb687b09e2537a
4 files changed +80 -31
git-stash.sh
+4 -3
@@ -115,7 +115,8 @@ create_stash () {
115 git read-tree --index-output="$TMPindex" -m $i_tree &&
116 GIT_INDEX_FILE="$TMPindex" &&
117 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" &&
120 git write-tree &&
121 rm -f "$TMPindex"
122 ) ) ||
@@ -134,7 +135,7 @@ create_stash () {
135 w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
136 die "$(gettext "Cannot save the current worktree state")"
137
137 - git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
138 + git diff-tree -p HEAD $w_tree -- >"$TMP-patch" &&
139 test -s "$TMP-patch" ||
140 die "$(gettext "No changes selected")"
141
@@ -491,7 +492,7 @@ drop_stash () {
492 die "$(eval_gettext "\${REV}: Could not drop stash entry")"
493
494 # 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
496 }
497
498 apply_to_branch () {
t/t3903-stash.sh
+24
@@ -601,4 +601,28 @@ test_expect_success 'stash apply shows status same as git status (relative to cu
601 test_cmp expect actual
602 '
603
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 +
628 test_done
t/t3904-stash-patch.sh
+29 -18
@@ -7,7 +7,8 @@ test_expect_success PERL 'setup' '
7 mkdir dir &&
8 echo parent > dir/foo &&
9 echo dummy > bar &&
10 - git add bar dir/foo &&
10 + echo committed > HEAD &&
11 + git add bar dir/foo HEAD &&
12 git commit -m initial &&
13 test_tick &&
14 test_commit second dir/foo head &&
@@ -17,47 +18,57 @@ test_expect_success PERL 'setup' '
18 save_head
19 '
20
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
22
23 test_expect_success PERL 'saying "n" does nothing' '
24 + set_state HEAD HEADfile_work HEADfile_index &&
25 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
30 '
31
32 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 &&
35 verify_saved_state bar &&
36 + verify_state dir/foo head index &&
37 git reset --hard &&
38 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
42 '
43
44 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 &&
46 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 &&
50 verify_state bar bar_work dummy &&
51 + verify_state dir/foo head head &&
52 git reset --hard &&
53 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
57 '
58
59 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 &&
61 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 &&
65 verify_state dir/foo head head &&
66 verify_state bar bar_work dummy &&
67 git reset --hard &&
68 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
72 '
73
74 test_expect_success PERL 'none of this moved HEAD' '
t/t3905-stash-include-untracked.sh
+23 -10
@@ -17,6 +17,7 @@ test_expect_success 'stash save --include-untracked some dirty working directory
17 echo 3 > file &&
18 test_tick &&
19 echo 1 > file2 &&
20 + echo 1 > HEAD &&
21 mkdir untracked &&
22 echo untracked >untracked/untracked &&
23 git stash --include-untracked &&
@@ -35,6 +36,13 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files'
36 '
37
38 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
46 diff --git a/file2 b/file2
47 new file mode 100644
48 index 0000000..d00491f
@@ -51,14 +59,16 @@ index 0000000..5a72eb2
59 +untracked
60 EOF
61 cat > expect.lstree <<EOF
62 +HEAD
63 file2
64 untracked
65 EOF
66
67 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 &&
72 test_cmp expect.diff actual &&
73 git ls-tree --name-only stash^3: >actual &&
74 test_cmp expect.lstree actual
@@ -75,6 +85,7 @@ git clean --force --quiet
85
86 cat > expect <<EOF
87 M file
88 +?? HEAD
89 ?? actual
90 ?? expect
91 ?? file2
@@ -116,10 +127,12 @@ test_expect_success 'stash save --include-untracked dirty index got stashed' '
127
128 git reset > /dev/null
129
130 +# Must direct output somewhere where it won't be considered an untracked file
131 test_expect_success 'stash save --include-untracked -q is quiet' '
132 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
136 '
137
138 test_expect_success 'stash save --include-untracked removed files' '
@@ -133,7 +146,7 @@ rm -f expect
146
147 test_expect_success 'stash save --include-untracked removed files got stashed' '
148 git stash pop &&
136 - test ! -f file
149 + test_path_is_missing file
150 '
151
152 cat > .gitignore <<EOF
@@ -155,14 +168,14 @@ test_expect_success 'stash save --include-untracked respects .gitignore' '
168 test_expect_success 'stash save -u can stash with only untracked files different' '
169 echo 4 > file4 &&
170 git stash -u &&
158 - test "!" -f file4
171 + test_path_is_missing file4
172 '
173
174 test_expect_success 'stash save --all does not respect .gitignore' '
175 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
179 '
180
181 test_expect_success 'stash save --all is stash poppable' '