Skip to content

Commit 22f4128

Browse files
committed
Merge branch 'dc/stash-con-untracked'
* dc/stash-con-untracked: stash: Add --include-untracked option to stash and remove all untracked files Conflicts: git-stash.sh
2 parents 9c81e64 + 7875130 commit 22f4128

File tree

3 files changed

+228
-7
lines changed

3 files changed

+228
-7
lines changed

Documentation/git-stash.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ SYNOPSIS
1313
'git stash' drop [-q|--quiet] [<stash>]
1414
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
1515
'git stash' branch <branchname> [<stash>]
16-
'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
16+
'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
17+
[-u|--include-untracked] [-a|--all] [<message>]]
1718
'git stash' clear
1819
'git stash' create
1920

@@ -42,7 +43,7 @@ is also possible).
4243
OPTIONS
4344
-------
4445

45-
save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
46+
save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
4647

4748
Save your local modifications to a new 'stash', and run `git reset
4849
--hard` to revert them. The <message> part is optional and gives
@@ -54,6 +55,11 @@ save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
5455
If the `--keep-index` option is used, all changes already added to the
5556
index are left intact.
5657
+
58+
If the `--include-untracked` option is used, all untracked files are also
59+
stashed and then cleaned up with `git clean`, leaving the working directory
60+
in a very clean state. If the `--all` option is used instead then the
61+
ignored files are stashed and cleaned in addition to the untracked files.
62+
+
5763
With `--patch`, you can interactively select hunks from the diff
5864
between HEAD and the working tree to be stashed. The stash entry is
5965
constructed such that its index state is the same as the index state

git-stash.sh

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ USAGE="list [<options>]
77
or: $dashless drop [-q|--quiet] [<stash>]
88
or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
99
or: $dashless branch <branchname> [<stash>]
10-
or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
10+
or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
11+
[-u|--include-untracked] [-a|--all] [<message>]]
1112
or: $dashless clear"
1213

1314
SUBDIRECTORY_OK=Yes
@@ -34,7 +35,14 @@ fi
3435

3536
no_changes () {
3637
git diff-index --quiet --cached HEAD --ignore-submodules -- &&
37-
git diff-files --quiet --ignore-submodules
38+
git diff-files --quiet --ignore-submodules &&
39+
(test -z "$untracked" || test -z "$(untracked_files)")
40+
}
41+
42+
untracked_files () {
43+
excl_opt=--exclude-standard
44+
test "$untracked" = "all" && excl_opt=
45+
git ls-files -o -z $excl_opt
3846
}
3947

4048
clear_stash () {
@@ -50,6 +58,7 @@ clear_stash () {
5058

5159
create_stash () {
5260
stash_msg="$1"
61+
untracked="$2"
5362

5463
git update-index -q --refresh
5564
if no_changes
@@ -79,6 +88,25 @@ create_stash () {
7988
git commit-tree $i_tree -p $b_commit) ||
8089
die "$(gettext "Cannot save the current index state")"
8190

91+
if test -n "$untracked"
92+
then
93+
# Untracked files are stored by themselves in a parentless commit, for
94+
# ease of unpacking later.
95+
u_commit=$(
96+
untracked_files | (
97+
export GIT_INDEX_FILE="$TMPindex"
98+
rm -f "$TMPindex" &&
99+
git update-index -z --add --remove --stdin &&
100+
u_tree=$(git write-tree) &&
101+
printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree &&
102+
rm -f "$TMPindex"
103+
) ) || die "Cannot save the untracked files"
104+
105+
untracked_commit_option="-p $u_commit";
106+
else
107+
untracked_commit_option=
108+
fi
109+
82110
if test -z "$patch_mode"
83111
then
84112

@@ -123,13 +151,14 @@ create_stash () {
123151
stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
124152
fi
125153
w_commit=$(printf '%s\n' "$stash_msg" |
126-
git commit-tree $w_tree -p $b_commit -p $i_commit) ||
127-
die "$(gettext "Cannot record working tree state")"
154+
git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
155+
die "$(gettext "Cannot record working tree state")"
128156
}
129157

130158
save_stash () {
131159
keep_index=
132160
patch_mode=
161+
untracked=
133162
while test $# != 0
134163
do
135164
case "$1" in
@@ -147,6 +176,12 @@ save_stash () {
147176
-q|--quiet)
148177
GIT_QUIET=t
149178
;;
179+
-u|--include-untracked)
180+
untracked=untracked
181+
;;
182+
-a|--all)
183+
untracked=all
184+
;;
150185
--)
151186
shift
152187
break
@@ -174,6 +209,11 @@ save_stash () {
174209
shift
175210
done
176211

212+
if test -n "$patch_mode" && test -n "$untracked"
213+
then
214+
die "Can't use --patch and ---include-untracked or --all at the same time"
215+
fi
216+
177217
stash_msg="$*"
178218

179219
git update-index -q --refresh
@@ -185,7 +225,7 @@ save_stash () {
185225
test -f "$GIT_DIR/logs/$ref_stash" ||
186226
clear_stash || die "$(gettext "Cannot initialize stash")"
187227

188-
create_stash "$stash_msg"
228+
create_stash "$stash_msg" $untracked
189229

190230
# Make sure the reflog for stash is kept.
191231
: >>"$GIT_DIR/logs/$ref_stash"
@@ -197,6 +237,11 @@ save_stash () {
197237
if test -z "$patch_mode"
198238
then
199239
git reset --hard ${GIT_QUIET:+-q}
240+
test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
241+
if test -n "$untracked"
242+
then
243+
git clean --force --quiet $CLEAN_X_OPTION
244+
fi
200245

201246
if test "$keep_index" = "t" && test -n $i_tree
202247
then
@@ -246,9 +291,11 @@ show_stash () {
246291
# w_commit is set to the commit containing the working tree
247292
# b_commit is set to the base commit
248293
# i_commit is set to the commit containing the index tree
294+
# u_commit is set to the commit containing the untracked files tree
249295
# w_tree is set to the working tree
250296
# b_tree is set to the base tree
251297
# i_tree is set to the index tree
298+
# u_tree is set to the untracked files tree
252299
#
253300
# GIT_QUIET is set to t if -q is specified
254301
# INDEX_OPTION is set to --index if --index is specified.
@@ -273,9 +320,11 @@ parse_flags_and_rev()
273320
w_commit=
274321
b_commit=
275322
i_commit=
323+
u_commit=
276324
w_tree=
277325
b_tree=
278326
i_tree=
327+
u_tree=
279328

280329
REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1
281330

@@ -326,6 +375,9 @@ parse_flags_and_rev()
326375
IS_STASH_LIKE=t &&
327376
test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
328377
IS_STASH_REF=t
378+
379+
u_commit=$(git rev-parse --quiet --verify $REV^3 2>/dev/null) &&
380+
u_tree=$(git rev-parse $REV^3: 2>/dev/null)
329381
}
330382

331383
is_stash_like()
@@ -374,6 +426,14 @@ apply_stash () {
374426
git reset
375427
fi
376428

429+
if test -n "$u_tree"
430+
then
431+
GIT_INDEX_FILE="$TMPindex" git-read-tree "$u_tree" &&
432+
GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
433+
rm -f "$TMPindex" ||
434+
die 'Could not restore untracked files from stash'
435+
fi
436+
377437
eval "
378438
GITHEAD_$w_tree='Stashed changes' &&
379439
GITHEAD_$c_tree='Updated upstream' &&

t/t3905-stash-include-untracked.sh

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2011 David Caldwell
4+
#
5+
6+
test_description='Test git stash --include-untracked'
7+
8+
. ./test-lib.sh
9+
10+
test_expect_success 'stash save --include-untracked some dirty working directory' '
11+
echo 1 > file &&
12+
git add file &&
13+
test_tick &&
14+
git commit -m initial &&
15+
echo 2 > file &&
16+
git add file &&
17+
echo 3 > file &&
18+
test_tick &&
19+
echo 1 > file2 &&
20+
git stash --include-untracked &&
21+
git diff-files --quiet &&
22+
git diff-index --cached --quiet HEAD
23+
'
24+
25+
cat > expect <<EOF
26+
?? expect
27+
?? output
28+
EOF
29+
30+
test_expect_success 'stash save --include-untracked cleaned the untracked files' '
31+
git status --porcelain > output
32+
test_cmp output expect
33+
'
34+
35+
cat > expect.diff <<EOF
36+
diff --git a/file2 b/file2
37+
new file mode 100644
38+
index 0000000..d00491f
39+
--- /dev/null
40+
+++ b/file2
41+
@@ -0,0 +1 @@
42+
+1
43+
EOF
44+
cat > expect.lstree <<EOF
45+
file2
46+
EOF
47+
48+
test_expect_success 'stash save --include-untracked stashed the untracked files' '
49+
test "!" -f file2 &&
50+
git diff HEAD..stash^3 -- file2 > output &&
51+
test_cmp output expect.diff &&
52+
git ls-tree --name-only stash^3: > output &&
53+
test_cmp output expect.lstree
54+
'
55+
test_expect_success 'stash save --patch --include-untracked fails' '
56+
test_must_fail git stash --patch --include-untracked
57+
'
58+
59+
test_expect_success 'stash save --patch --all fails' '
60+
test_must_fail git stash --patch --all
61+
'
62+
63+
git clean --force --quiet
64+
65+
cat > expect <<EOF
66+
M file
67+
?? expect
68+
?? file2
69+
?? output
70+
EOF
71+
72+
test_expect_success 'stash pop after save --include-untracked leaves files untracked again' '
73+
git stash pop &&
74+
git status --porcelain > output
75+
test_cmp output expect
76+
'
77+
78+
git clean --force --quiet
79+
80+
test_expect_success 'stash save -u dirty index' '
81+
echo 4 > file3 &&
82+
git add file3 &&
83+
test_tick &&
84+
git stash -u
85+
'
86+
87+
cat > expect <<EOF
88+
diff --git a/file3 b/file3
89+
new file mode 100644
90+
index 0000000..b8626c4
91+
--- /dev/null
92+
+++ b/file3
93+
@@ -0,0 +1 @@
94+
+4
95+
EOF
96+
97+
test_expect_success 'stash save --include-untracked dirty index got stashed' '
98+
git stash pop --index &&
99+
git diff --cached > output &&
100+
test_cmp output expect
101+
'
102+
103+
git reset > /dev/null
104+
105+
test_expect_success 'stash save --include-untracked -q is quiet' '
106+
echo 1 > file5 &&
107+
git stash save --include-untracked --quiet > output.out 2>&1 &&
108+
test ! -s output.out
109+
'
110+
111+
test_expect_success 'stash save --include-untracked removed files' '
112+
rm -f file &&
113+
git stash save --include-untracked &&
114+
echo 1 > expect &&
115+
test_cmp file expect
116+
'
117+
118+
rm -f expect
119+
120+
test_expect_success 'stash save --include-untracked removed files got stashed' '
121+
git stash pop &&
122+
test ! -f file
123+
'
124+
125+
cat > .gitignore <<EOF
126+
.gitignore
127+
ignored
128+
EOF
129+
130+
test_expect_success 'stash save --include-untracked respects .gitignore' '
131+
echo ignored > ignored &&
132+
git stash -u &&
133+
test -s ignored &&
134+
test -s .gitignore
135+
'
136+
137+
test_expect_success 'stash save -u can stash with only untracked files different' '
138+
echo 4 > file4 &&
139+
git stash -u
140+
test "!" -f file4
141+
'
142+
143+
test_expect_success 'stash save --all does not respect .gitignore' '
144+
git stash -a &&
145+
test "!" -f ignored &&
146+
test "!" -f .gitignore
147+
'
148+
149+
test_expect_success 'stash save --all is stash poppable' '
150+
git stash pop &&
151+
test -s ignored &&
152+
test -s .gitignore
153+
'
154+
155+
test_done

0 commit comments

Comments
 (0)