Commit df6bba09 authored by Thomas Gummerer's avatar Thomas Gummerer Committed by Junio C Hamano

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: default avatarJunio C Hamano <[email protected]>
Signed-off-by: default avatarThomas Gummerer <[email protected]>
Signed-off-by: default avatarJunio C Hamano <[email protected]>
parent 9ca6326d
......@@ -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.
+
......
......@@ -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 -- "[email protected]" &&
git diff-files --quiet --ignore-submodules -- "[email protected]" &&
(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 -- "[email protected]"
}
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 "[email protected]"
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 "[email protected]" | (
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 -- "[email protected]" >"$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 -- "[email protected]" &&
# 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 -- "[email protected]" >/dev/null || exit 1
git update-index -q --refresh
if no_changes
if no_changes "[email protected]"
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" -- "[email protected]"
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} -- "[email protected]"
git ls-files -z --modified -- "[email protected]" |
git checkout-index -z --force --stdin
git clean --force ${GIT_QUIET:+-q} -d -- "[email protected]"
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 -- "[email protected]"
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment