Skip to content

Commit

Permalink
Merge branch 'pb/stash-refuse-to-kill'
Browse files Browse the repository at this point in the history
"git stash save" is not just about "saving" the local changes, but
also is to restore the working tree state to that of HEAD. If you
changed a non-directory into a directory in the local change, you
may have untracked files in that directory, which have to be killed
while doing so, unless you run it with --include-untracked.  Teach
the command to detect and error out before spreading the damage.

This needed a small fix to "ls-files --killed".

* pb/stash-refuse-to-kill:
  git stash: avoid data loss when "git stash save" kills a directory
  treat_directory(): do not declare submodules to be untracked
  • Loading branch information
Junio C Hamano committed Jul 11, 2013
2 parents 77f3c3f + a736531 commit d26792a
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 8 deletions.
12 changes: 10 additions & 2 deletions Documentation/git-stash.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ SYNOPSIS
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>]
'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [<message>]]
[-u|--include-untracked] [-a|--all] [-f|--force]
[<message>]]
'git stash' clear
'git stash' create [<message>]
'git stash' store [-m|--message <message>] [-q|--quiet] <commit>
Expand Down Expand Up @@ -44,7 +45,7 @@ is also possible).
OPTIONS
-------

save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-f|--force] [<message>]::

Save your local modifications to a new 'stash', and run `git reset
--hard` to revert them. The <message> part is optional and gives
Expand All @@ -71,6 +72,13 @@ linkgit:git-add[1] to learn how to operate the `--patch` mode.
+
The `--patch` option implies `--keep-index`. You can use
`--no-keep-index` to override this.
+
In some cases, saving a stash could mean irretrievably removing some
data - if a directory with untracked files replaces a tracked file of
the same name, the new untracked files are not saved (except in case
of `--include-untracked`) but the original tracked file shall be restored.
By default, `stash save` will abort in such a case; `--force` will allow
it to remove the untracked files.

list [<options>]::

Expand Down
4 changes: 1 addition & 3 deletions dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -1036,9 +1036,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
return path_recurse;

case index_gitdir:
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
return path_none;
return path_untracked;
return path_none;

case index_nonexistent:
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
Expand Down
12 changes: 12 additions & 0 deletions git-stash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ save_stash () {
keep_index=
patch_mode=
untracked=
force=
while test $# != 0
do
case "$1" in
Expand All @@ -215,6 +216,9 @@ save_stash () {
-u|--include-untracked)
untracked=untracked
;;
-f|--force)
force=t
;;
-a|--all)
untracked=all
;;
Expand Down Expand Up @@ -258,6 +262,14 @@ save_stash () {
say "$(gettext "No local changes to save")"
exit 0
fi
if test -z "$untracked$force" &&
test -n "$(git ls-files --killed | head -n 1)"
then
say "$(gettext "The following untracked files would NOT be saved but need to be removed by stash save:")"
test -n "$GIT_QUIET" || git ls-files --killed | sed 's/^/\t/'
say "$(gettext "Aborting. Consider using either the --force or --include-untracked option.")" >&2
exit 1
fi
test -f "$GIT_DIR/logs/$ref_stash" ||
clear_stash || die "$(gettext "Cannot initialize stash")"

Expand Down
23 changes: 20 additions & 3 deletions t/t3010-ls-files-killed-modified.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ This test prepares the following in the cache:
path1 - a symlink
path2/file2 - a file in a directory
path3/file3 - a file in a directory
submod1/ - a submodule
submod2/ - another submodule
and the following on the filesystem:
Expand All @@ -21,9 +23,11 @@ and the following on the filesystem:
path4 - a file
path5 - a symlink
path6/file6 - a file in a directory
submod1/ - a submodule (modified from the cache)
submod2/ - a submodule (matches the cache)
git ls-files -k should report that existing filesystem
objects except path4, path5 and path6/file6 to be killed.
git ls-files -k should report that existing filesystem objects
path0/*, path1/*, path2 and path3 to be killed.
Also for modification test, the cache and working tree have:
Expand All @@ -33,7 +37,7 @@ Also for modification test, the cache and working tree have:
path10 - a non-empty file, cache dirtied.
We should report path0, path1, path2/file2, path3/file3, path7 and path8
modified without reporting path9 and path10.
modified without reporting path9 and path10. submod1 is also modified.
'
. ./test-lib.sh

Expand All @@ -48,6 +52,18 @@ test_expect_success 'git update-index --add to add various paths.' '
: >path9 &&
date >path10 &&
git update-index --add -- path0 path?/file? path7 path8 path9 path10 &&
for i in 1 2
do
git init submod$i &&
(
cd submod$i && git commit --allow-empty -m "empty $i"
) || break
done &&
git update-index --add submod[12]
(
cd submod1 &&
git commit --allow-empty -m "empty 1 (updated)"
) &&
rm -fr path? # leave path10 alone
'

Expand Down Expand Up @@ -94,6 +110,7 @@ test_expect_success 'validate git ls-files -m output.' '
path3/file3
path7
path8
submod1
EOF
test_cmp .expected .output
'
Expand Down
18 changes: 18 additions & 0 deletions t/t3903-stash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -673,4 +673,22 @@ test_expect_success 'store updates stash ref and reflog' '
grep quux bazzy
'

test_expect_success 'stash a change to turn a non-directory to a directory' '
git reset --hard &&
>testfile &&
git add testfile &&
git commit -m "add testfile as a regular file" &&
rm testfile &&
mkdir testfile &&
>testfile/file &&
test_must_fail git stash save "recover regular file" &&
test -f testfile/file
'

test_expect_success 'stash a change to turn a non-directory to a directory (forced)' '
git stash save --force "recover regular file (forced)" &&
! test -f testfile/file &&
test -f testfile
'

test_done

0 comments on commit d26792a

Please sign in to comment.