mirror of
https://github.com/git/git.git
synced 2025-03-15 13:51:54 +00:00
Two bugs that made the command practically unusable were fixed with this. - A stash created with a clean index does not have any difference between the base tree and the index tree. Trying to apply the diff between them to the index would error out with "No changes". Even when the user asked to unstash with --index, do not bother with --index action if the base tree and the index tree match. - After successfully performing the working tree merge, the index was reloaded from an earlier state of unstashed index with "read-tree"; this left all the paths cache dirty. By moving the call to git-status after this read-tree, match the cached stat information in the index. Signed-off-by: Junio C Hamano <gitster@pobox.com>
215 lines
4.5 KiB
Bash
Executable File
215 lines
4.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# Copyright (c) 2007, Nanako Shiraishi
|
|
|
|
USAGE='[ | list | show | apply | clear]'
|
|
|
|
SUBDIRECTORY_OK=Yes
|
|
. git-sh-setup
|
|
require_work_tree
|
|
cd_to_toplevel
|
|
|
|
TMP="$GIT_DIR/.git-stash.$$"
|
|
trap 'rm -f "$TMP-*"' 0
|
|
|
|
ref_stash=refs/stash
|
|
|
|
no_changes () {
|
|
git diff-index --quiet --cached HEAD &&
|
|
git diff-files --quiet
|
|
}
|
|
|
|
clear_stash () {
|
|
if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
|
|
then
|
|
git update-ref -d refs/stash $current
|
|
fi
|
|
}
|
|
|
|
save_stash () {
|
|
stash_msg="$1"
|
|
|
|
if no_changes
|
|
then
|
|
echo >&2 'No local changes to save'
|
|
exit 0
|
|
fi
|
|
test -f "$GIT_DIR/logs/$ref_stash" ||
|
|
clear_stash || die "Cannot initialize stash"
|
|
|
|
# Make sure the reflog for stash is kept.
|
|
: >>"$GIT_DIR/logs/$ref_stash"
|
|
|
|
# state of the base commit
|
|
if b_commit=$(git rev-parse --verify HEAD)
|
|
then
|
|
head=$(git log --abbrev-commit --pretty=oneline -n 1 HEAD)
|
|
else
|
|
die "You do not have the initial commit yet"
|
|
fi
|
|
|
|
if branch=$(git symbolic-ref -q HEAD)
|
|
then
|
|
branch=${branch#refs/heads/}
|
|
else
|
|
branch='(no branch)'
|
|
fi
|
|
msg=$(printf '%s: %s' "$branch" "$head")
|
|
|
|
# state of the index
|
|
i_tree=$(git write-tree) &&
|
|
i_commit=$(printf 'index on %s' "$msg" |
|
|
git commit-tree $i_tree -p $b_commit) ||
|
|
die "Cannot save the current index state"
|
|
|
|
# state of the working tree
|
|
w_tree=$( (
|
|
rm -f "$TMP-index" &&
|
|
cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
|
|
GIT_INDEX_FILE="$TMP-index" &&
|
|
export GIT_INDEX_FILE &&
|
|
git read-tree -m $i_tree &&
|
|
git add -u &&
|
|
git write-tree &&
|
|
rm -f "$TMP-index"
|
|
) ) ||
|
|
die "Cannot save the current worktree state"
|
|
|
|
# create the stash
|
|
if test -z "$stash_msg"
|
|
then
|
|
stash_msg=$(printf 'WIP on %s' "$msg")
|
|
else
|
|
stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
|
|
fi
|
|
w_commit=$(printf '%s\n' "$stash_msg" |
|
|
git commit-tree $w_tree -p $b_commit -p $i_commit) ||
|
|
die "Cannot record working tree state"
|
|
|
|
git update-ref -m "$stash_msg" $ref_stash $w_commit ||
|
|
die "Cannot save the current status"
|
|
printf >&2 'Saved "%s"\n' "$stash_msg"
|
|
}
|
|
|
|
have_stash () {
|
|
git rev-parse --verify $ref_stash >/dev/null 2>&1
|
|
}
|
|
|
|
list_stash () {
|
|
have_stash || return 0
|
|
git log --pretty=oneline -g "$@" $ref_stash |
|
|
sed -n -e 's/^[.0-9a-f]* refs\///p'
|
|
}
|
|
|
|
show_stash () {
|
|
flags=$(git rev-parse --no-revs --flags "$@")
|
|
if test -z "$flags"
|
|
then
|
|
flags=--stat
|
|
fi
|
|
s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@")
|
|
|
|
w_commit=$(git rev-parse --verify "$s") &&
|
|
b_commit=$(git rev-parse --verify "$s^") &&
|
|
git diff $flags $b_commit $w_commit
|
|
}
|
|
|
|
apply_stash () {
|
|
git diff-files --quiet ||
|
|
die 'Cannot restore on top of a dirty state'
|
|
|
|
unstash_index=
|
|
case "$1" in
|
|
--index)
|
|
unstash_index=t
|
|
shift
|
|
esac
|
|
|
|
# current index state
|
|
c_tree=$(git write-tree) ||
|
|
die 'Cannot apply a stash in the middle of a merge'
|
|
|
|
# stash records the work tree, and is a merge between the
|
|
# base commit (first parent) and the index tree (second parent).
|
|
s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@") &&
|
|
w_tree=$(git rev-parse --verify "$s:") &&
|
|
b_tree=$(git rev-parse --verify "$s^1:") &&
|
|
i_tree=$(git rev-parse --verify "$s^2:") ||
|
|
die "$*: no valid stashed state found"
|
|
|
|
unstashed_index_tree=
|
|
if test -n "$unstash_index" && test "$b_tree" != "$i_tree"
|
|
then
|
|
git diff --binary $s^2^..$s^2 | git apply --cached
|
|
test $? -ne 0 &&
|
|
die 'Conflicts in index. Try without --index.'
|
|
unstashed_index_tree=$(git-write-tree) ||
|
|
die 'Could not save index tree'
|
|
git reset
|
|
fi
|
|
|
|
eval "
|
|
GITHEAD_$w_tree='Stashed changes' &&
|
|
GITHEAD_$c_tree='Updated upstream' &&
|
|
GITHEAD_$b_tree='Version stash was based on' &&
|
|
export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
|
|
"
|
|
|
|
if git-merge-recursive $b_tree -- $c_tree $w_tree
|
|
then
|
|
# No conflict
|
|
a="$TMP-added" &&
|
|
git diff --cached --name-only --diff-filter=A $c_tree >"$a" &&
|
|
git read-tree --reset $c_tree &&
|
|
git update-index --add --stdin <"$a" ||
|
|
die "Cannot unstage modified files"
|
|
rm -f "$a"
|
|
if test -n "$unstashed_index_tree"
|
|
then
|
|
git read-tree "$unstashed_index_tree"
|
|
fi
|
|
git status || :
|
|
else
|
|
# Merge conflict; keep the exit status from merge-recursive
|
|
status=$?
|
|
if test -n "$unstash_index"
|
|
then
|
|
echo >&2 'Index was not unstashed.'
|
|
fi
|
|
exit $status
|
|
fi
|
|
}
|
|
|
|
# Main command set
|
|
case "$1" in
|
|
list)
|
|
shift
|
|
if test $# = 0
|
|
then
|
|
set x -n 10
|
|
shift
|
|
fi
|
|
list_stash "$@"
|
|
;;
|
|
show)
|
|
shift
|
|
show_stash "$@"
|
|
;;
|
|
apply)
|
|
shift
|
|
apply_stash "$@"
|
|
;;
|
|
clear)
|
|
clear_stash
|
|
;;
|
|
help | usage)
|
|
usage
|
|
;;
|
|
*)
|
|
if test $# -gt 0 && test "$1" = save
|
|
then
|
|
shift
|
|
fi
|
|
save_stash "$*" && git-reset --hard
|
|
;;
|
|
esac
|