Skip to content

Commit 7875130

Browse files
caldwellgitster
authored andcommitted
stash: Add --include-untracked option to stash and remove all untracked files
The --include-untracked option acts like the normal "git stash save" but also adds all untracked files in the working directory to the stash and then calls "git clean --force --quiet" to restore the working directory to a pristine state. This is useful for projects that need to run release scripts. With this option, the release scripts can be from the main working directory so one does not have to maintain a "clean" directory in parallel just for releasing. Basically the work-flow becomes: $ git tag release-1.0 $ git stash --include-untracked $ make release $ git clean -f $ git stash pop "git stash" alone is not enough in this case--it leaves untracked files lying around that might mess up a release process that expects everything to be very clean or might let a release succeed that should actually fail (due to a new source file being created that hasn't been committed yet). Signed-off-by: David Caldwell <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 99ac63b commit 7875130

File tree

3 files changed

+227
-6
lines changed

3 files changed

+227
-6
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: 64 additions & 4 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
@@ -33,7 +34,14 @@ fi
3334

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

3947
clear_stash () {
@@ -49,6 +57,7 @@ clear_stash () {
4957

5058
create_stash () {
5159
stash_msg="$1"
60+
untracked="$2"
5261

5362
git update-index -q --refresh
5463
if no_changes
@@ -78,6 +87,25 @@ create_stash () {
7887
git commit-tree $i_tree -p $b_commit) ||
7988
die "Cannot save the current index state"
8089

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

@@ -122,13 +150,14 @@ create_stash () {
122150
stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
123151
fi
124152
w_commit=$(printf '%s\n' "$stash_msg" |
125-
git commit-tree $w_tree -p $b_commit -p $i_commit) ||
153+
git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) ||
126154
die "Cannot record working tree state"
127155
}
128156

129157
save_stash () {
130158
keep_index=
131159
patch_mode=
160+
untracked=
132161
while test $# != 0
133162
do
134163
case "$1" in
@@ -146,6 +175,12 @@ save_stash () {
146175
-q|--quiet)
147176
GIT_QUIET=t
148177
;;
178+
-u|--include-untracked)
179+
untracked=untracked
180+
;;
181+
-a|--all)
182+
untracked=all
183+
;;
149184
--)
150185
shift
151186
break
@@ -162,6 +197,11 @@ save_stash () {
162197
shift
163198
done
164199

200+
if test -n "$patch_mode" && test -n "$untracked"
201+
then
202+
die "Can't use --patch and ---include-untracked or --all at the same time"
203+
fi
204+
165205
stash_msg="$*"
166206

167207
git update-index -q --refresh
@@ -173,7 +213,7 @@ save_stash () {
173213
test -f "$GIT_DIR/logs/$ref_stash" ||
174214
clear_stash || die "Cannot initialize stash"
175215

176-
create_stash "$stash_msg"
216+
create_stash "$stash_msg" $untracked
177217

178218
# Make sure the reflog for stash is kept.
179219
: >>"$GIT_DIR/logs/$ref_stash"
@@ -185,6 +225,11 @@ save_stash () {
185225
if test -z "$patch_mode"
186226
then
187227
git reset --hard ${GIT_QUIET:+-q}
228+
test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
229+
if test -n "$untracked"
230+
then
231+
git clean --force --quiet $CLEAN_X_OPTION
232+
fi
188233

189234
if test "$keep_index" = "t" && test -n $i_tree
190235
then
@@ -234,9 +279,11 @@ show_stash () {
234279
# w_commit is set to the commit containing the working tree
235280
# b_commit is set to the base commit
236281
# i_commit is set to the commit containing the index tree
282+
# u_commit is set to the commit containing the untracked files tree
237283
# w_tree is set to the working tree
238284
# b_tree is set to the base tree
239285
# i_tree is set to the index tree
286+
# u_tree is set to the untracked files tree
240287
#
241288
# GIT_QUIET is set to t if -q is specified
242289
# INDEX_OPTION is set to --index if --index is specified.
@@ -261,9 +308,11 @@ parse_flags_and_rev()
261308
w_commit=
262309
b_commit=
263310
i_commit=
311+
u_commit=
264312
w_tree=
265313
b_tree=
266314
i_tree=
315+
u_tree=
267316

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

@@ -311,6 +360,9 @@ parse_flags_and_rev()
311360
IS_STASH_LIKE=t &&
312361
test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
313362
IS_STASH_REF=t
363+
364+
u_commit=$(git rev-parse --quiet --verify $REV^3 2>/dev/null) &&
365+
u_tree=$(git rev-parse $REV^3: 2>/dev/null)
314366
}
315367

316368
is_stash_like()
@@ -353,6 +405,14 @@ apply_stash () {
353405
git reset
354406
fi
355407

408+
if test -n "$u_tree"
409+
then
410+
GIT_INDEX_FILE="$TMPindex" git-read-tree "$u_tree" &&
411+
GIT_INDEX_FILE="$TMPindex" git checkout-index --all &&
412+
rm -f "$TMPindex" ||
413+
die 'Could not restore untracked files from stash'
414+
fi
415+
356416
eval "
357417
GITHEAD_$w_tree='Stashed changes' &&
358418
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)