Skip to content

Commit b480d38

Browse files
committed
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
2 parents 306d7e5 + 8d66bb0 commit b480d38

File tree

3 files changed

+268
-83
lines changed

3 files changed

+268
-83
lines changed

Documentation/git-stash.txt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,22 @@ tree's changes, but also the index's ones. However, this can fail, when you
104104
have conflicts (which are stored in the index, where you therefore can no
105105
longer apply the changes as they were originally).
106106
+
107-
When no `<stash>` is given, `stash@\{0}` is assumed.
107+
When no `<stash>` is given, `stash@\{0}` is assumed, otherwise `<stash>` must
108+
be a reference of the form `stash@\{<revision>}`.
108109

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

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

113116
branch <branchname> [<stash>]::
114117

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

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

137143
create::
138144

git-stash.sh

Lines changed: 145 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -210,56 +210,146 @@ list_stash () {
210210
}
211211

212212
show_stash () {
213-
have_stash || die 'No stash found'
213+
assert_stash_like "$@"
214214

215-
flags=$(git rev-parse --no-revs --flags "$@")
216-
if test -z "$flags"
217-
then
218-
flags=--stat
219-
fi
220-
221-
w_commit=$(git rev-parse --quiet --verify --default $ref_stash "$@") &&
222-
b_commit=$(git rev-parse --quiet --verify "$w_commit^") ||
223-
die "'$*' is not a stash"
224-
225-
git diff $flags $b_commit $w_commit
215+
git diff ${FLAGS:---stat} $b_commit $w_commit
226216
}
227217

228-
apply_stash () {
229-
applied_stash=
230-
unstash_index=
231-
232-
while test $# != 0
218+
#
219+
# Parses the remaining options looking for flags and
220+
# at most one revision defaulting to ${ref_stash}@{0}
221+
# if none found.
222+
#
223+
# Derives related tree and commit objects from the
224+
# revision, if one is found.
225+
#
226+
# stash records the work tree, and is a merge between the
227+
# base commit (first parent) and the index tree (second parent).
228+
#
229+
# REV is set to the symbolic version of the specified stash-like commit
230+
# IS_STASH_LIKE is non-blank if ${REV} looks like a stash
231+
# IS_STASH_REF is non-blank if the ${REV} looks like a stash ref
232+
# s is set to the SHA1 of the stash commit
233+
# w_commit is set to the commit containing the working tree
234+
# b_commit is set to the base commit
235+
# i_commit is set to the commit containing the index tree
236+
# w_tree is set to the working tree
237+
# b_tree is set to the base tree
238+
# i_tree is set to the index tree
239+
#
240+
# GIT_QUIET is set to t if -q is specified
241+
# INDEX_OPTION is set to --index if --index is specified.
242+
# FLAGS is set to the remaining flags
243+
#
244+
# dies if:
245+
# * too many revisions specified
246+
# * no revision is specified and there is no stash stack
247+
# * a revision is specified which cannot be resolve to a SHA1
248+
# * a non-existent stash reference is specified
249+
#
250+
251+
parse_flags_and_rev()
252+
{
253+
test "$PARSE_CACHE" = "$*" && return 0 # optimisation
254+
PARSE_CACHE="$*"
255+
256+
IS_STASH_LIKE=
257+
IS_STASH_REF=
258+
INDEX_OPTION=
259+
s=
260+
w_commit=
261+
b_commit=
262+
i_commit=
263+
w_tree=
264+
b_tree=
265+
i_tree=
266+
267+
REV=$(git rev-parse --no-flags --symbolic "$@" 2>/dev/null)
268+
FLAGS=$(git rev-parse --no-revs -- "$@" 2>/dev/null)
269+
270+
set -- $FLAGS
271+
272+
FLAGS=
273+
while test $# -ne 0
233274
do
234275
case "$1" in
235-
--index)
236-
unstash_index=t
276+
-q|--quiet)
277+
GIT_QUIET=-t
237278
;;
238-
-q|--quiet)
239-
GIT_QUIET=t
279+
--index)
280+
INDEX_OPTION=--index
240281
;;
241-
*)
242-
break
282+
--)
283+
:
284+
;;
285+
*)
286+
FLAGS="${FLAGS}${FLAGS:+ }$1"
243287
;;
244288
esac
245289
shift
246290
done
247291

248-
if test $# = 0
292+
set -- $REV
293+
294+
case $# in
295+
0)
296+
have_stash || die "No stash found."
297+
set -- ${ref_stash}@{0}
298+
;;
299+
1)
300+
:
301+
;;
302+
*)
303+
die "Too many revisions specified: $REV"
304+
;;
305+
esac
306+
307+
REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference"
308+
309+
i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) &&
310+
set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) &&
311+
s=$1 &&
312+
w_commit=$1 &&
313+
b_commit=$2 &&
314+
w_tree=$3 &&
315+
b_tree=$4 &&
316+
i_tree=$5 &&
317+
IS_STASH_LIKE=t &&
318+
test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" &&
319+
IS_STASH_REF=t
320+
321+
if test "${REV}" != "${REV%{*\}}"
249322
then
250-
have_stash || die 'Nothing to apply'
251-
applied_stash="$ref_stash@{0}"
252-
else
253-
applied_stash="$*"
323+
# maintainers: it would be better if git rev-parse indicated
324+
# this condition with a non-zero status code but as of 1.7.2.1 it
325+
# it did not. So, we use non-empty stderr output as a proxy for the
326+
# condition of interest.
327+
test -z "$(git rev-parse "$REV" 2>&1 >/dev/null)" || die "$REV does not exist in the stash log"
254328
fi
255329

256-
# stash records the work tree, and is a merge between the
257-
# base commit (first parent) and the index tree (second parent).
258-
s=$(git rev-parse --quiet --verify --default $ref_stash "$@") &&
259-
w_tree=$(git rev-parse --quiet --verify "$s:") &&
260-
b_tree=$(git rev-parse --quiet --verify "$s^1:") &&
261-
i_tree=$(git rev-parse --quiet --verify "$s^2:") ||
262-
die "$*: no valid stashed state found"
330+
}
331+
332+
is_stash_like()
333+
{
334+
parse_flags_and_rev "$@"
335+
test -n "$IS_STASH_LIKE"
336+
}
337+
338+
assert_stash_like() {
339+
is_stash_like "$@" || die "'$*' is not a stash-like commit"
340+
}
341+
342+
is_stash_ref() {
343+
is_stash_like "$@" && test -n "$IS_STASH_REF"
344+
}
345+
346+
assert_stash_ref() {
347+
is_stash_ref "$@" || die "'$*' is not a stash reference"
348+
}
349+
350+
apply_stash () {
351+
352+
assert_stash_like "$@"
263353

264354
git update-index -q --refresh &&
265355
git diff-files --quiet --ignore-submodules ||
@@ -270,7 +360,7 @@ apply_stash () {
270360
die 'Cannot apply a stash in the middle of a merge'
271361

272362
unstashed_index_tree=
273-
if test -n "$unstash_index" && test "$b_tree" != "$i_tree" &&
363+
if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" &&
274364
test "$c_tree" != "$i_tree"
275365
then
276366
git diff-tree --binary $s^2^..$s^2 | git apply --cached
@@ -315,66 +405,46 @@ apply_stash () {
315405
else
316406
# Merge conflict; keep the exit status from merge-recursive
317407
status=$?
318-
if test -n "$unstash_index"
408+
if test -n "$INDEX_OPTION"
319409
then
320410
echo >&2 'Index was not unstashed.'
321411
fi
322412
exit $status
323413
fi
324414
}
325415

326-
drop_stash () {
327-
have_stash || die 'No stash entries to drop'
416+
pop_stash() {
417+
assert_stash_ref "$@"
328418

329-
while test $# != 0
330-
do
331-
case "$1" in
332-
-q|--quiet)
333-
GIT_QUIET=t
334-
;;
335-
*)
336-
break
337-
;;
338-
esac
339-
shift
340-
done
419+
apply_stash "$@" &&
420+
drop_stash "$@"
421+
}
341422

342-
if test $# = 0
343-
then
344-
set x "$ref_stash@{0}"
345-
shift
346-
fi
347-
# Verify supplied argument looks like a stash entry
348-
s=$(git rev-parse --verify "$@") &&
349-
git rev-parse --verify "$s:" > /dev/null 2>&1 &&
350-
git rev-parse --verify "$s^1:" > /dev/null 2>&1 &&
351-
git rev-parse --verify "$s^2:" > /dev/null 2>&1 ||
352-
die "$*: not a valid stashed state"
423+
drop_stash () {
424+
assert_stash_ref "$@"
353425

354-
git reflog delete --updateref --rewrite "$@" &&
355-
say "Dropped $* ($s)" || die "$*: Could not drop stash entry"
426+
git reflog delete --updateref --rewrite "${REV}" &&
427+
say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry"
356428

357429
# clear_stash if we just dropped the last stash entry
358430
git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash
359431
}
360432

361433
apply_to_branch () {
362-
have_stash || die 'Nothing to apply'
363-
364434
test -n "$1" || die 'No branch name specified'
365435
branch=$1
436+
shift 1
366437

367-
if test -z "$2"
368-
then
369-
set x "$ref_stash@{0}"
370-
fi
371-
stash=$2
438+
set -- --index "$@"
439+
assert_stash_like "$@"
372440

373-
git checkout -b $branch $stash^ &&
374-
apply_stash --index $stash &&
375-
drop_stash $stash
441+
git checkout -b $branch $REV^ &&
442+
apply_stash "$@"
443+
444+
test -z "$IS_STASH_REF" || drop_stash "$@"
376445
}
377446

447+
PARSE_CACHE='--not-parsed'
378448
# The default command is "save" if nothing but options are given
379449
seen_non_option=
380450
for opt
@@ -422,10 +492,7 @@ drop)
422492
;;
423493
pop)
424494
shift
425-
if apply_stash "$@"
426-
then
427-
drop_stash "$applied_stash"
428-
fi
495+
pop_stash "$@"
429496
;;
430497
branch)
431498
shift

0 commit comments

Comments
 (0)