Skip to content

Commit

Permalink
git stash: avoid data loss when "git stash save" kills a directory
Browse files Browse the repository at this point in the history
"stash save" is about saving the local change to the working tree,
but also about restoring the state of the last commit to the working
tree.  When a local change is to turn a non-directory to a directory,
in order to restore the non-directory, everything in the directory
needs to be removed.

Which is fine when running "git stash save --include-untracked",
but without that option, untracked, newly created files in the
directory will have to be discarded, if the state you are restoring
to has a non-directory at the same path as the directory.

Introduce a safety valve to fail the operation in such case, using
the "ls-files --killed" which was designed for this exact purpose.

The "stash save" is stopped when untracked files need to be
discarded because their leading path ceased to be a directory, and
the user is required to pass --force to really have the data
removed.

Signed-off-by: Petr Baudis <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
  • Loading branch information
pasky authored and gitster committed Jul 1, 2013
1 parent 26c986e commit a736531
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 2 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
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
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 a736531

Please sign in to comment.