gitweb: allow action specialization in page header
[gitweb.git] / git-stash.sh
index 59db3dc38e72fda88d521171a174c08b919677a9..7561b374d2ec90fe774e98e39a121a1a3a3cdfa9 100755 (executable)
@@ -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"
                ) ) ||
@@ -210,56 +210,139 @@ list_stash () {
 }
 
 show_stash () {
-       have_stash || die 'No stash found'
+       assert_stash_like "$@"
 
-       flags=$(git rev-parse --no-revs --flags "$@")
-       if test -z "$flags"
-       then
-               flags=--stat
-       fi
-
-       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 "$@" 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'
-               applied_stash="$ref_stash@{0}"
-       else
-               applied_stash="$*"
+               # 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 ||
@@ -270,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
@@ -290,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
@@ -315,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
@@ -323,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
@@ -422,10 +485,7 @@ drop)
        ;;
 pop)
        shift
-       if apply_stash "$@"
-       then
-               drop_stash "$applied_stash"
-       fi
+       pop_stash "$@"
        ;;
 branch)
        shift