Skip to content

Commit

Permalink
Merge branch 'js/detached-stash'
Browse files Browse the repository at this point in the history
* js/detached-stash:
  t3903: fix broken test_must_fail calls
  detached-stash: update Documentation
  detached-stash: tests of git stash with stash-like arguments
  detached-stash: simplify git stash show
  detached-stash: simplify git stash branch
  detached-stash: refactor git stash pop implementation
  detached-stash: simplify stash_drop
  detached-stash: simplify stash_apply
  detached-stash: work around git rev-parse failure to detect bad log refs
  detached-stash: introduce parse_flags_and_revs function
  • Loading branch information
Junio C Hamano committed Sep 4, 2010
2 parents 306d7e5 + 8d66bb0 commit b480d38
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 83 deletions.
16 changes: 11 additions & 5 deletions Documentation/git-stash.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,22 @@ tree's changes, but also the index's ones. However, this can fail, when you
have conflicts (which are stored in the index, where you therefore can no
longer apply the changes as they were originally).
+
When no `<stash>` is given, `stash@\{0}` is assumed.
When no `<stash>` is given, `stash@\{0}` is assumed, otherwise `<stash>` must
be a reference of the form `stash@\{<revision>}`.

apply [--index] [-q|--quiet] [<stash>]::

Like `pop`, but do not remove the state from the stash list.
Like `pop`, but do not remove the state from the stash list. Unlike `pop`,
`<stash>` may be any commit that looks like a commit created by
`stash save` or `stash create`.

branch <branchname> [<stash>]::

Creates and checks out a new branch named `<branchname>` starting from
the commit at which the `<stash>` was originally created, applies the
changes recorded in `<stash>` to the new working tree and index, then
drops the `<stash>` if that completes successfully. When no `<stash>`
changes recorded in `<stash>` to the new working tree and index.
If that succeeds, and `<stash>` is a reference of the form
`stash@{<revision>}`, it then drops the `<stash>`. When no `<stash>`
is given, applies the latest one.
+
This is useful if the branch on which you ran `git stash save` has
Expand All @@ -132,7 +136,9 @@ clear::
drop [-q|--quiet] [<stash>]::

Remove a single stashed state from the stash list. When no `<stash>`
is given, it removes the latest one. i.e. `stash@\{0}`
is given, it removes the latest one. i.e. `stash@\{0}`, otherwise
`<stash>` must a valid stash log reference of the form
`stash@\{<revision>}`.

create::

Expand Down
223 changes: 145 additions & 78 deletions git-stash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -210,56 +210,146 @@ 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=$(git rev-parse --no-revs -- "$@" 2>/dev/null)

set -- $FLAGS

FLAGS=
while test $# -ne 0
do
case "$1" in
--index)
unstash_index=t
-q|--quiet)
GIT_QUIET=-t
;;
-q|--quiet)
GIT_QUIET=t
--index)
INDEX_OPTION=--index
;;
*)
break
--)
:
;;
*)
FLAGS="${FLAGS}${FLAGS:+ }$1"
;;
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 ||
Expand All @@ -270,7 +360,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
Expand Down Expand Up @@ -315,66 +405,46 @@ 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
exit $status
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
Expand Down Expand Up @@ -422,10 +492,7 @@ drop)
;;
pop)
shift
if apply_stash "$@"
then
drop_stash "$applied_stash"
fi
pop_stash "$@"
;;
branch)
shift
Expand Down
Loading

0 comments on commit b480d38

Please sign in to comment.