Skip to content
Snippets Groups Projects
Commit df6bba09 authored by Thomas Gummerer's avatar Thomas Gummerer Committed by Junio C Hamano
Browse files

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 <gitster@pobox.com>
Signed-off-by: default avatarThomas Gummerer <t.gummerer@gmail.com>
Signed-off-by: default avatarJunio C Hamano <gitster@pobox.com>
parent 9ca6326d
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -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>
Loading
Loading
@@ -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).
Loading
Loading
@@ -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.
+
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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 () {
Loading
Loading
@@ -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
Loading
Loading
@@ -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" &&
Loading
Loading
@@ -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"
Loading
Loading
@@ -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) ||
Loading
Loading
@@ -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
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"
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment