stash: teach 'push' (and 'create_stash') to honor pathspec
While working on a repository, it's often helpful to stash the changes of a single or multiple files, and leave others alone. Unfortunately git currently offers no such option. git stash -p can be used to work around this, but it's often impractical when there are a lot of changes over multiple files. Allow 'git stash push' to take pathspec to specify which paths to stash. Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
9ca6326dff
commit
df6bba0937
@ -17,6 +17,7 @@ SYNOPSIS
|
||||
[-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>
|
||||
@ -48,7 +49,7 @@ 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>]::
|
||||
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).
|
||||
@ -58,6 +59,12 @@ push [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q
|
||||
only <message> does not trigger this action to prevent a misspelled
|
||||
subcommand from making an unwanted stash.
|
||||
+
|
||||
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.
|
||||
+
|
||||
|
36
git-stash.sh
36
git-stash.sh
@ -11,6 +11,7 @@ USAGE="list [<options>]
|
||||
[-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
|
||||
@ -35,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 () {
|
||||
@ -71,12 +72,16 @@ create_stash () {
|
||||
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
|
||||
@ -108,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" &&
|
||||
@ -131,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"
|
||||
@ -145,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) ||
|
||||
@ -273,27 +278,38 @@ push_stash () {
|
||||
die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")"
|
||||
fi
|
||||
|
||||
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 -m "$stash_msg" -u "$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
|
||||
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"
|
||||
|
@ -802,4 +802,96 @@ test_expect_success 'create with multiple arguments for the message' '
|
||||
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_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
|
||||
|
Loading…
Reference in New Issue
Block a user