зеркало из https://github.com/microsoft/git.git
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:
Коммит
b480d38dab
|
@ -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::
|
||||
|
||||
|
|
223
git-stash.sh
223
git-stash.sh
|
@ -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
|
||||
|
|
112
t/t3903-stash.sh
112
t/t3903-stash.sh
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче