Merge branch 'js/detached-stash'

* 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
This commit is contained in:
Junio C Hamano 2010-09-03 22:45:58 -07:00
Родитель 306d7e5556 8d66bb0587
Коммит b480d38dab
3 изменённых файлов: 268 добавлений и 83 удалений

Просмотреть файл

@ -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
@ -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::

Просмотреть файл

@ -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=
#
# 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
#
while test $# != 0
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 ||
@ -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
@ -315,7 +405,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 +413,38 @@ apply_stash () {
fi
}
pop_stash() {
assert_stash_ref "$@"
apply_stash "$@" &&
drop_stash "$@"
}
drop_stash () {
have_stash || die 'No stash entries to drop'
assert_stash_ref "$@"
while test $# != 0
do
case "$1" in
-q|--quiet)
GIT_QUIET=t
;;
*)
break
;;
esac
shift
done
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"
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 +492,7 @@ drop)
;;
pop)
shift
if apply_stash "$@"
then
drop_stash "$applied_stash"
fi
pop_stash "$@"
;;
branch)
shift

Просмотреть файл

@ -378,4 +378,116 @@ test_expect_failure 'stash file to directory' '
test foo = "$(cat file/file)"
'
test_expect_success 'stash branch - no stashes on stack, stash-like argument' '
git stash clear &&
test_when_finished "git reset --hard HEAD" &&
git reset --hard &&
echo foo >> file &&
STASH_ID=$(git stash create) &&
git reset --hard &&
git stash branch stash-branch ${STASH_ID} &&
test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
test $(git ls-files --modified | wc -l) -eq 1
'
test_expect_success 'stash branch - stashes on stack, stash-like argument' '
git stash clear &&
test_when_finished "git reset --hard HEAD" &&
git reset --hard &&
echo foo >> file &&
git stash &&
test_when_finished "git stash drop" &&
echo bar >> file &&
STASH_ID=$(git stash create) &&
git reset --hard &&
git stash branch stash-branch ${STASH_ID} &&
test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" &&
test $(git ls-files --modified | wc -l) -eq 1
'
test_expect_success 'stash show - stashes on stack, stash-like argument' '
git stash clear &&
test_when_finished "git reset --hard HEAD" &&
git reset --hard &&
echo foo >> file &&
git stash &&
test_when_finished "git stash drop" &&
echo bar >> file &&
STASH_ID=$(git stash create) &&
git reset --hard &&
git stash show ${STASH_ID}
'
test_expect_success 'stash show - no stashes on stack, stash-like argument' '
git stash clear &&
test_when_finished "git reset --hard HEAD" &&
git reset --hard &&
echo foo >> file &&
STASH_ID=$(git stash create) &&
git reset --hard &&
git stash show ${STASH_ID}
'
test_expect_success 'stash drop - fail early if specified stash is not a stash reference' '
git stash clear &&
test_when_finished "git reset --hard HEAD && git stash clear" &&
git reset --hard &&
echo foo > file &&
git stash &&
echo bar > file &&
git stash &&
test_must_fail git stash drop $(git rev-parse stash@{0}) &&
git stash pop &&
test bar = "$(cat file)" &&
git reset --hard HEAD
'
test_expect_success 'stash pop - fail early if specified stash is not a stash reference' '
git stash clear &&
test_when_finished "git reset --hard HEAD && git stash clear" &&
git reset --hard &&
echo foo > file &&
git stash &&
echo bar > file &&
git stash &&
test_must_fail git stash pop $(git rev-parse stash@{0}) &&
git stash pop &&
test bar = "$(cat file)" &&
git reset --hard HEAD
'
test_expect_success 'ref with non-existant reflog' '
git stash clear &&
echo bar5 > file &&
echo bar6 > file2 &&
git add file2 &&
git stash &&
! "git rev-parse --quiet --verify does-not-exist" &&
test_must_fail git stash drop does-not-exist &&
test_must_fail git stash drop does-not-exist@{0} &&
test_must_fail git stash pop does-not-exist &&
test_must_fail git stash pop does-not-exist@{0} &&
test_must_fail git stash apply does-not-exist &&
test_must_fail git stash apply does-not-exist@{0} &&
test_must_fail git stash show does-not-exist &&
test_must_fail git stash show does-not-exist@{0} &&
test_must_fail git stash branch tmp does-not-exist &&
test_must_fail git stash branch tmp does-not-exist@{0} &&
git stash drop
'
test_expect_success 'invalid ref of the form stash@{n}, n >= N' '
git stash clear &&
test_must_fail git stash drop stash@{0} &&
echo bar5 > file &&
echo bar6 > file2 &&
git add file2 &&
git stash &&
test_must_fail git drop stash@{1} &&
test_must_fail git pop stash@{1} &&
test_must_fail git apply stash@{1} &&
test_must_fail git show stash@{1} &&
test_must_fail git branch tmp stash@{1} &&
git stash drop
'
test_done