summaryrefslogtreecommitdiff
path: root/git-stash.sh
diff options
context:
space:
mode:
Diffstat (limited to 'git-stash.sh')
-rwxr-xr-xgit-stash.sh220
1 files changed, 144 insertions, 76 deletions
diff --git a/git-stash.sh b/git-stash.sh
index 3a0685f189..7561b374d2 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -57,7 +57,7 @@ create_stash () {
# state of the base commit
if b_commit=$(git rev-parse --verify HEAD)
then
- head=$(git log --no-color --abbrev-commit --pretty=oneline -n 1 HEAD --)
+ head=$(git rev-list --oneline -n 1 HEAD --)
else
die "You do not have the initial commit yet"
fi
@@ -86,7 +86,7 @@ create_stash () {
GIT_INDEX_FILE="$TMP-index" &&
export GIT_INDEX_FILE &&
git read-tree -m $i_tree &&
- git add -u &&
+ git diff --name-only -z HEAD | git update-index -z --add --remove --stdin &&
git write-tree &&
rm -f "$TMP-index"
) ) ||
@@ -151,6 +151,7 @@ save_stash () {
;;
-*)
echo "error: unknown option for 'stash save': $1"
+ echo " To provide a message, use git stash save -- '$1'"
usage
;;
*)
@@ -209,48 +210,139 @@ list_stash () {
}
show_stash () {
- flags=$(git rev-parse --no-revs --flags "$@")
- if test -z "$flags"
- then
- flags=--stat
- fi
+ assert_stash_like "$@"
- w_commit=$(git rev-parse --verify --default $ref_stash "$@") &&
- b_commit=$(git rev-parse --verify "$w_commit^") &&
- git diff $flags $b_commit $w_commit
+ git diff ${FLAGS:---stat} $b_commit $w_commit
}
-apply_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 "$@" 2>/dev/null)
+
+ 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
+ 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
+
+ if test "${REV}" != "${REV%{*\}}"
then
- have_stash || die 'Nothing to apply'
+ # maintainers: it would be better if git rev-parse indicated
+ # this condition with a non-zero status code but as of 1.7.2.1 it
+ # it did not. So, we use non-empty stderr output as a proxy for the
+ # condition of interest.
+ test -z "$(git rev-parse "$REV" 2>&1 >/dev/null)" || die "$REV does not exist in the stash log"
fi
- # 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_like()
+{
+ parse_flags_and_rev "$@"
+ test -n "$IS_STASH_LIKE"
+}
+
+assert_stash_like() {
+ is_stash_like "$@" || die "'$*' is not a stash-like commit"
+}
+
+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 ||
@@ -261,7 +353,7 @@ apply_stash () {
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
@@ -281,7 +373,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
@@ -306,7 +398,7 @@ apply_stash () {
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
@@ -314,58 +406,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
@@ -413,11 +485,7 @@ drop)
;;
pop)
shift
- if apply_stash "$@"
- then
- test -z "$unstash_index" || shift
- drop_stash "$@"
- fi
+ pop_stash "$@"
;;
branch)
shift