summaryrefslogtreecommitdiff
path: root/git-stash.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-stash.sh')
-rwxr-xr-xgit-stash.sh243
1 files changed, 146 insertions, 97 deletions
diff --git a/git-stash.sh b/git-stash.sh
index 1d95447d03..0a9403653d 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -12,12 +12,14 @@ USAGE="list [<options>]
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
+START_DIR=`pwd`
. git-sh-setup
require_work_tree
cd_to_toplevel
TMP="$GIT_DIR/.git-stash.$$"
-trap 'rm -f "$TMP-*"' 0
+TMPindex=${GIT_INDEX_FILE-"$GIT_DIR/index"}.stash.$$
+trap 'rm -f "$TMP-"* "$TMPindex"' 0
ref_stash=refs/stash
@@ -81,14 +83,12 @@ create_stash () {
# 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" &&
+ git read-tree --index-output="$TMPindex" -m $i_tree &&
+ GIT_INDEX_FILE="$TMPindex" &&
export GIT_INDEX_FILE &&
- git read-tree -m $i_tree &&
git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
git write-tree &&
- rm -f "$TMP-index"
+ rm -f "$TMPindex"
) ) ||
die "Cannot save the current worktree state"
@@ -136,11 +136,12 @@ save_stash () {
keep_index=t
;;
--no-keep-index)
- keep_index=
+ keep_index=n
;;
-p|--patch)
patch_mode=t
- keep_index=t
+ # only default to keep if we don't already have an override
+ test -z "$keep_index" && keep_index=t
;;
-q|--quiet)
GIT_QUIET=t
@@ -185,7 +186,7 @@ save_stash () {
then
git reset --hard ${GIT_QUIET:+-q}
- if test -n "$keep_index" && test -n $i_tree
+ if test "$keep_index" = "t" && test -n $i_tree
then
git read-tree --reset -u $i_tree
fi
@@ -193,7 +194,7 @@ save_stash () {
git apply -R < "$TMP-patch" ||
die "Cannot remove worktree changes"
- if test -z "$keep_index"
+ if test "$keep_index" != "t"
then
git reset
fi
@@ -210,67 +211,138 @@ list_stash () {
}
show_stash () {
- have_stash || die 'No stash found'
-
- flags=$(git rev-parse --no-revs --flags "$@")
- if test -z "$flags"
- then
- flags=--stat
- fi
+ assert_stash_like "$@"
- w_commit=$(git rev-parse --quiet --verify --default $ref_stash "$@") &&
- b_commit=$(git rev-parse --quiet --verify "$w_commit^") ||
- die "'$*' is not a stash"
-
- git diff $flags $b_commit $w_commit
+ git diff ${FLAGS:---stat} $b_commit $w_commit
}
-apply_stash () {
- applied_stash=
- unstash_index=
-
- while test $# != 0
+#
+# Parses the remaining options looking for flags and
+# at most one revision defaulting to ${ref_stash}@{0}
+# if none found.
+#
+# Derives related tree and commit objects from the
+# revision, if one is found.
+#
+# stash records the work tree, and is a merge between the
+# base commit (first parent) and the index tree (second parent).
+#
+# REV is set to the symbolic version of the specified stash-like commit
+# IS_STASH_LIKE is non-blank if ${REV} looks like a stash
+# IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
+# s is set to the SHA1 of the stash commit
+# w_commit is set to the commit containing the working tree
+# b_commit is set to the base commit
+# i_commit is set to the commit containing the index tree
+# w_tree is set to the working tree
+# b_tree is set to the base tree
+# i_tree is set to the index tree
+#
+# GIT_QUIET is set to t if -q is specified
+# INDEX_OPTION is set to --index if --index is specified.
+# FLAGS is set to the remaining flags
+#
+# dies if:
+# * too many revisions specified
+# * no revision is specified and there is no stash stack
+# * a revision is specified which cannot be resolve to a SHA1
+# * a non-existent stash reference is specified
+#
+
+parse_flags_and_rev()
+{
+ test "$PARSE_CACHE" = "$*" && return 0 # optimisation
+ PARSE_CACHE="$*"
+
+ IS_STASH_LIKE=
+ IS_STASH_REF=
+ INDEX_OPTION=
+ s=
+ w_commit=
+ b_commit=
+ i_commit=
+ w_tree=
+ b_tree=
+ i_tree=
+
+ REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1
+
+ FLAGS=
+ for opt
do
- case "$1" in
- --index)
- unstash_index=t
+ case "$opt" in
+ -q|--quiet)
+ GIT_QUIET=-t
;;
- -q|--quiet)
- GIT_QUIET=t
+ --index)
+ INDEX_OPTION=--index
;;
- *)
- break
+ -*)
+ FLAGS="${FLAGS}${FLAGS:+ }$opt"
;;
esac
- shift
done
- if test $# = 0
- then
- have_stash || die 'Nothing to apply'
- applied_stash="$ref_stash@{0}"
- else
- applied_stash="$*"
- fi
+ set -- $REV
+
+ case $# in
+ 0)
+ have_stash || die "No stash found."
+ set -- ${ref_stash}@{0}
+ ;;
+ 1)
+ :
+ ;;
+ *)
+ die "Too many revisions specified: $REV"
+ ;;
+ esac
+
+ REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference"
+
+ i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
+ set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
+ s=$1 &&
+ w_commit=$1 &&
+ b_commit=$2 &&
+ w_tree=$3 &&
+ b_tree=$4 &&
+ i_tree=$5 &&
+ IS_STASH_LIKE=t &&
+ test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
+ IS_STASH_REF=t
+}
+
+is_stash_like()
+{
+ parse_flags_and_rev "$@"
+ test -n "$IS_STASH_LIKE"
+}
+
+assert_stash_like() {
+ is_stash_like "$@" || die "'$*' is not a stash-like commit"
+}
- # 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 --quiet --verify --default $ref_stash "$@") &&
- w_tree=$(git rev-parse --quiet --verify "$s:") &&
- b_tree=$(git rev-parse --quiet --verify "$s^1:") &&
- i_tree=$(git rev-parse --quiet --verify "$s^2:") ||
- die "$*: no valid stashed state found"
+is_stash_ref() {
+ is_stash_like "$@" && test -n "$IS_STASH_REF"
+}
+
+assert_stash_ref() {
+ is_stash_ref "$@" || die "'$*' is not a stash reference"
+}
+
+apply_stash () {
+
+ assert_stash_like "$@"
- git update-index -q --refresh &&
- git diff-files --quiet --ignore-submodules ||
- die 'Cannot apply to a dirty working tree, please stage your changes'
+ git update-index -q --refresh || die 'unable to refresh index'
# current index state
c_tree=$(git write-tree) ||
die 'Cannot apply a stash in the middle of a merge'
unstashed_index_tree=
- if test -n "$unstash_index" && test "$b_tree" != "$i_tree" &&
+ if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
test "$c_tree" != "$i_tree"
then
git diff-tree --binary $s^2^..$s^2 | git apply --cached
@@ -290,7 +362,7 @@ apply_stash () {
if test -n "$GIT_QUIET"
then
- export GIT_MERGE_VERBOSITY=0
+ GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY
fi
if git merge-recursive $b_tree -- $c_tree $w_tree
then
@@ -311,11 +383,11 @@ apply_stash () {
then
squelch='>/dev/null 2>&1'
fi
- eval "git status $squelch" || :
+ (cd "$START_DIR" && eval "git status $squelch") || :
else
# Merge conflict; keep the exit status from merge-recursive
status=$?
- if test -n "$unstash_index"
+ if test -n "$INDEX_OPTION"
then
echo >&2 'Index was not unstashed.'
fi
@@ -323,58 +395,38 @@ apply_stash () {
fi
}
-drop_stash () {
- have_stash || die 'No stash entries to drop'
+pop_stash() {
+ assert_stash_ref "$@"
- while test $# != 0
- do
- case "$1" in
- -q|--quiet)
- GIT_QUIET=t
- ;;
- *)
- break
- ;;
- esac
- shift
- done
+ apply_stash "$@" &&
+ drop_stash "$@"
+}
- if test $# = 0
- then
- set x "$ref_stash@{0}"
- shift
- fi
- # Verify supplied argument looks like a stash entry
- s=$(git rev-parse --verify "$@") &&
- git rev-parse --verify "$s:" > /dev/null 2>&1 &&
- git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
- git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
- die "$*: not a valid stashed state"
+drop_stash () {
+ assert_stash_ref "$@"
- git reflog delete --updateref --rewrite "$@" &&
- say "Dropped $* ($s)" || die "$*: Could not drop stash entry"
+ git reflog delete --updateref --rewrite "${REV}" &&
+ say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry"
# clear_stash if we just dropped the last stash entry
git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
}
apply_to_branch () {
- have_stash || die 'Nothing to apply'
-
test -n "$1" || die 'No branch name specified'
branch=$1
+ shift 1
- if test -z "$2"
- then
- set x "$ref_stash@{0}"
- fi
- stash=$2
+ set -- --index "$@"
+ assert_stash_like "$@"
- git checkout -b $branch $stash^ &&
- apply_stash --index $stash &&
- drop_stash $stash
+ git checkout -b $branch $REV^ &&
+ apply_stash "$@" && {
+ test -z "$IS_STASH_REF" || drop_stash "$@"
+ }
}
+PARSE_CACHE='--not-parsed'
# The default command is "save" if nothing but options are given
seen_non_option=
for opt
@@ -422,10 +474,7 @@ drop)
;;
pop)
shift
- if apply_stash "$@"
- then
- drop_stash "$applied_stash"
- fi
+ pop_stash "$@"
;;
branch)
shift