提交 44c3f09f 编写于 作者: J Junio C Hamano

Merge branch 'tg/stash-push'

"git stash save" takes a pathspec so that the local changes can be
stashed away only partially.

* tg/stash-push:
  stash: allow pathspecs in the no verb form
  stash: use stash_push for no verb form
  stash: teach 'push' (and 'create_stash') to honor pathspec
  stash: refactor stash_create
  stash: add test for the create command line arguments
  stash: introduce push verb
......@@ -13,8 +13,11 @@ SYNOPSIS
'git stash' drop [-q|--quiet] [<stash>]
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>]
'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [<message>]]
'git stash' save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [<message>]
'git stash' [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [-m|--message <message>]]
[--] [<pathspec>...]]
'git stash' clear
'git stash' create [<message>]
'git stash' store [-m|--message <message>] [-q|--quiet] <commit>
......@@ -46,14 +49,24 @@ OPTIONS
-------
save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [-m|--message <message>] [--] [<pathspec>...]::
Save your local modifications to a new 'stash' and roll them
back to HEAD (in the working tree and in the index).
The <message> part is optional and gives
the description along with the stashed state. For quickly making
a snapshot, you can omit _both_ "save" and <message>, but giving
only <message> does not trigger this action to prevent a misspelled
subcommand from making an unwanted stash.
the description along with the stashed state.
+
For quickly making a snapshot, you can omit "push". In this mode,
non-option arguments are not allowed to prevent a misspelled
subcommand from making an unwanted stash. The two exceptions to this
are `stash -p` which acts as alias for `stash push -p` and pathspecs,
which are allowed after a double hyphen `--` for disambiguation.
+
When pathspec is given to 'git stash push', the new stash records the
modified states only for the files that match the pathspec. The index
entries and working tree files are then rolled back to the state in
HEAD only for these files, too, leaving files that do not match the
pathspec intact.
+
If the `--keep-index` option is used, all changes already added to the
index are left intact.
......
......@@ -7,8 +7,11 @@ USAGE="list [<options>]
or: $dashless drop [-q|--quiet] [<stash>]
or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
or: $dashless branch <branchname> [<stash>]
or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [<message>]]
or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [<message>]
or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [-m <message>]
[-- <pathspec>...]]
or: $dashless clear"
SUBDIRECTORY_OK=Yes
......@@ -33,15 +36,15 @@ else
fi
no_changes () {
git diff-index --quiet --cached HEAD --ignore-submodules -- &&
git diff-files --quiet --ignore-submodules &&
git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" &&
git diff-files --quiet --ignore-submodules -- "$@" &&
(test -z "$untracked" || test -z "$(untracked_files)")
}
untracked_files () {
excl_opt=--exclude-standard
test "$untracked" = "all" && excl_opt=
git ls-files -o -z $excl_opt
git ls-files -o -z $excl_opt -- "$@"
}
clear_stash () {
......@@ -56,11 +59,29 @@ clear_stash () {
}
create_stash () {
stash_msg="$1"
untracked="$2"
stash_msg=
untracked=
while test $# != 0
do
case "$1" in
-m|--message)
shift
stash_msg=${1?"BUG: create_stash () -m requires an argument"}
;;
-u|--include-untracked)
shift
untracked=${1?"BUG: create_stash () -u requires an argument"}
;;
--)
shift
break
;;
esac
shift
done
git update-index -q --refresh
if no_changes
if no_changes "$@"
then
exit 0
fi
......@@ -92,7 +113,7 @@ create_stash () {
# Untracked files are stored by themselves in a parentless commit, for
# ease of unpacking later.
u_commit=$(
untracked_files | (
untracked_files "$@" | (
GIT_INDEX_FILE="$TMPindex" &&
export GIT_INDEX_FILE &&
rm -f "$TMPindex" &&
......@@ -115,7 +136,7 @@ create_stash () {
git read-tree --index-output="$TMPindex" -m $i_tree &&
GIT_INDEX_FILE="$TMPindex" &&
export GIT_INDEX_FILE &&
git diff-index --name-only -z HEAD -- >"$TMP-stagenames" &&
git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
git write-tree &&
rm -f "$TMPindex"
......@@ -129,7 +150,7 @@ create_stash () {
# find out what the user wants
GIT_INDEX_FILE="$TMP-index" \
git add--interactive --patch=stash -- &&
git add--interactive --patch=stash -- "$@" &&
# state of the working tree
w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
......@@ -189,10 +210,11 @@ store_stash () {
return $ret
}
save_stash () {
push_stash () {
keep_index=
patch_mode=
untracked=
stash_msg=
while test $# != 0
do
case "$1" in
......@@ -216,6 +238,11 @@ save_stash () {
-a|--all)
untracked=all
;;
-m|--message)
shift
test -z ${1+x} && usage
stash_msg=$1
;;
--help)
show_help
;;
......@@ -251,29 +278,38 @@ save_stash () {
die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
fi
stash_msg="$*"
test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1
git update-index -q --refresh
if no_changes
if no_changes "$@"
then
say "$(gettext "No local changes to save")"
exit 0
fi
git reflog exists $ref_stash ||
clear_stash || die "$(gettext "Cannot initialize stash")"
create_stash "$stash_msg" $untracked
create_stash -m "$stash_msg" -u "$untracked" -- "$@"
store_stash -m "$stash_msg" -q $w_commit ||
die "$(gettext "Cannot save the current status")"
say "$(eval_gettext "Saved working directory and index state \$stash_msg")"
if test -z "$patch_mode"
then
git reset --hard ${GIT_QUIET:+-q}
if test $# != 0
then
git reset ${GIT_QUIET:+-q} -- "$@"
git ls-files -z --modified -- "$@" |
git checkout-index -z --force --stdin
git clean --force ${GIT_QUIET:+-q} -d -- "$@"
else
git reset --hard ${GIT_QUIET:+-q}
fi
test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION=
if test -n "$untracked"
then
git clean --force --quiet -d $CLEAN_X_OPTION
git clean --force --quiet -d $CLEAN_X_OPTION -- "$@"
fi
if test "$keep_index" = "t" && test -n "$i_tree"
......@@ -291,6 +327,36 @@ save_stash () {
fi
}
save_stash () {
push_options=
while test $# != 0
do
case "$1" in
--)
shift
break
;;
-*)
# pass all options through to push_stash
push_options="$push_options $1"
;;
*)
break
;;
esac
shift
done
stash_msg="$*"
if test -z "$stash_msg"
then
push_stash $push_options
else
push_stash $push_options -m "$stash_msg"
fi
}
have_stash () {
git rev-parse --verify --quiet $ref_stash >/dev/null
}
......@@ -590,18 +656,21 @@ apply_to_branch () {
}
}
test "$1" = "-p" && set "push" "$@"
PARSE_CACHE='--not-parsed'
# The default command is "save" if nothing but options are given
# The default command is "push" if nothing but options are given
seen_non_option=
for opt
do
case "$opt" in
--) break ;;
-*) ;;
*) seen_non_option=t; break ;;
esac
done
test -n "$seen_non_option" || set "save" "$@"
test -n "$seen_non_option" || set "push" "$@"
# Main command set
case "$1" in
......@@ -617,6 +686,10 @@ save)
shift
save_stash "$@"
;;
push)
shift
push_stash "$@"
;;
apply)
shift
apply_stash "$@"
......@@ -627,7 +700,7 @@ clear)
;;
create)
shift
create_stash "$*" && echo "$w_commit"
create_stash -m "$*" && echo "$w_commit"
;;
store)
shift
......@@ -648,7 +721,7 @@ branch)
*)
case $# in
0)
save_stash &&
push_stash &&
say "$(gettext "(To restore them type \"git stash apply\")")"
;;
*)
......
......@@ -274,9 +274,7 @@ test_expect_success 'stash --invalid-option' '
git add file2 &&
test_must_fail git stash --invalid-option &&
test_must_fail git stash save --invalid-option &&
test bar5,bar6 = $(cat file),$(cat file2) &&
git stash -- -message-starting-with-dash &&
test bar,bar2 = $(cat file),$(cat file2)
test bar5,bar6 = $(cat file),$(cat file2)
'
test_expect_success 'stash an added file' '
......@@ -775,4 +773,138 @@ test_expect_success 'stash is not confused by partial renames' '
test_path_is_missing file
'
test_expect_success 'push -m shows right message' '
>foo &&
git add foo &&
git stash push -m "test message" &&
echo "stash@{0}: On master: test message" >expect &&
git stash list -1 >actual &&
test_cmp expect actual
'
test_expect_success 'create stores correct message' '
>foo &&
git add foo &&
STASH_ID=$(git stash create "create test message") &&
echo "On master: create test message" >expect &&
git show --pretty=%s -s ${STASH_ID} >actual &&
test_cmp expect actual
'
test_expect_success 'create with multiple arguments for the message' '
>foo &&
git add foo &&
STASH_ID=$(git stash create test untracked) &&
echo "On master: test untracked" >expect &&
git show --pretty=%s -s ${STASH_ID} >actual &&
test_cmp expect actual
'
test_expect_success 'stash -- <pathspec> stashes and restores the file' '
>foo &&
>bar &&
git add foo bar &&
git stash push -- foo &&
test_path_is_file bar &&
test_path_is_missing foo &&
git stash pop &&
test_path_is_file foo &&
test_path_is_file bar
'
test_expect_success 'stash with multiple pathspec arguments' '
>foo &&
>bar &&
>extra &&
git add foo bar extra &&
git stash push -- foo bar &&
test_path_is_missing bar &&
test_path_is_missing foo &&
test_path_is_file extra &&
git stash pop &&
test_path_is_file foo &&
test_path_is_file bar &&
test_path_is_file extra
'
test_expect_success 'stash with file including $IFS character' '
>"foo bar" &&
>foo &&
>bar &&
git add foo* &&
git stash push -- "foo b*" &&
test_path_is_missing "foo bar" &&
test_path_is_file foo &&
test_path_is_file bar &&
git stash pop &&
test_path_is_file "foo bar" &&
test_path_is_file foo &&
test_path_is_file bar
'
test_expect_success 'stash with pathspec matching multiple paths' '
echo original >file &&
echo original >other-file &&
git commit -m "two" file other-file &&
echo modified >file &&
echo modified >other-file &&
git stash push -- "*file" &&
echo original >expect &&
test_cmp expect file &&
test_cmp expect other-file &&
git stash pop &&
echo modified >expect &&
test_cmp expect file &&
test_cmp expect other-file
'
test_expect_success 'stash push -p with pathspec shows no changes only once' '
>foo &&
git add foo &&
git commit -m "tmp" &&
git stash push -p foo >actual &&
echo "No local changes to save" >expect &&
git reset --hard HEAD~ &&
test_cmp expect actual
'
test_expect_success 'stash push with pathspec shows no changes when there are none' '
>foo &&
git add foo &&
git commit -m "tmp" &&
git stash push foo >actual &&
echo "No local changes to save" >expect &&
git reset --hard HEAD~ &&
test_cmp expect actual
'
test_expect_success 'stash push with pathspec not in the repository errors out' '
>untracked &&
test_must_fail git stash push untracked &&
test_path_is_file untracked
'
test_expect_success 'untracked files are left in place when -u is not given' '
>file &&
git add file &&
>untracked &&
git stash push file &&
test_path_is_file untracked
'
test_expect_success 'stash without verb with pathspec' '
>"foo bar" &&
>foo &&
>bar &&
git add foo* &&
git stash -- "foo b*" &&
test_path_is_missing "foo bar" &&
test_path_is_file foo &&
test_path_is_file bar &&
git stash pop &&
test_path_is_file "foo bar" &&
test_path_is_file foo &&
test_path_is_file bar
'
test_done
......@@ -185,4 +185,30 @@ test_expect_success 'stash save --all is stash poppable' '
test -s .gitignore
'
test_expect_success 'stash push --include-untracked with pathspec' '
>foo &&
>bar &&
git stash push --include-untracked -- foo &&
test_path_is_file bar &&
test_path_is_missing foo &&
git stash pop &&
test_path_is_file bar &&
test_path_is_file foo
'
test_expect_success 'stash push with $IFS character' '
>"foo bar" &&
>foo &&
>bar &&
git add foo* &&
git stash push --include-untracked -- "foo b*" &&
test_path_is_missing "foo bar" &&
test_path_is_file foo &&
test_path_is_file bar &&
git stash pop &&
test_path_is_file "foo bar" &&
test_path_is_file foo &&
test_path_is_file bar
'
test_done
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册