Commit dda1f2a5 authored by Thomas Rast's avatar Thomas Rast Committed by Junio C Hamano

Implement 'git stash save --patch'

This adds a hunk-based mode to git-stash.  You can select hunks from
the difference between HEAD and worktree, and git-stash will build a
stash that reflects these changes.  The index state of the stash is
the same as your current index, and we also let --patch imply
--keep-index.

Note that because the selected hunks are rolled back from the worktree
but not the index, the resulting state may appear somewhat confusing
if you had also staged these changes.  This is not entirely
satisfactory, but due to the way stashes are applied, other solutions
would require a change to the stash format.
Signed-off-by: default avatarThomas Rast <trast@student.ethz.ch>
Signed-off-by: default avatarJunio C Hamano <gitster@pobox.com>
parent 4f353658
...@@ -13,7 +13,7 @@ SYNOPSIS ...@@ -13,7 +13,7 @@ SYNOPSIS
'git stash' drop [-q|--quiet] [<stash>] 'git stash' drop [-q|--quiet] [<stash>]
'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>]
'git stash' branch <branchname> [<stash>] 'git stash' branch <branchname> [<stash>]
'git stash' [save [--keep-index] [-q|--quiet] [<message>]] 'git stash' [save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]]
'git stash' clear 'git stash' clear
'git stash' create 'git stash' create
...@@ -42,7 +42,7 @@ is also possible). ...@@ -42,7 +42,7 @@ is also possible).
OPTIONS OPTIONS
------- -------
save [--keep-index] [-q|--quiet] [<message>]:: save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]::
Save your local modifications to a new 'stash', and run `git reset Save your local modifications to a new 'stash', and run `git reset
--hard` to revert them. This is the default action when no --hard` to revert them. This is the default action when no
...@@ -51,6 +51,16 @@ save [--keep-index] [-q|--quiet] [<message>]:: ...@@ -51,6 +51,16 @@ save [--keep-index] [-q|--quiet] [<message>]::
+ +
If the `--keep-index` option is used, all changes already added to the If the `--keep-index` option is used, all changes already added to the
index are left intact. index are left intact.
+
With `--patch`, you can interactively select hunks from in the diff
between HEAD and the working tree to be stashed. The stash entry is
constructed such that its index state is the same as the index state
of your repository, and its worktree contains only the changes you
selected interactively. The selected changes are then rolled back
from your worktree.
+
The `--patch` option implies `--keep-index`. You can use
`--no-keep-index` to override this.
list [<options>]:: list [<options>]::
......
...@@ -76,6 +76,7 @@ sub colored { ...@@ -76,6 +76,7 @@ sub colored {
sub apply_patch; sub apply_patch;
sub apply_patch_for_checkout_commit; sub apply_patch_for_checkout_commit;
sub apply_patch_for_stash;
my %patch_modes = ( my %patch_modes = (
'stage' => { 'stage' => {
...@@ -87,6 +88,15 @@ sub colored { ...@@ -87,6 +88,15 @@ sub colored {
PARTICIPLE => 'staging', PARTICIPLE => 'staging',
FILTER => 'file-only', FILTER => 'file-only',
}, },
'stash' => {
DIFF => 'diff-index -p HEAD',
APPLY => sub { apply_patch 'apply --cached', @_; },
APPLY_CHECK => 'apply --cached',
VERB => 'Stash',
TARGET => '',
PARTICIPLE => 'stashing',
FILTER => undef,
},
'reset_head' => { 'reset_head' => {
DIFF => 'diff-index -p --cached', DIFF => 'diff-index -p --cached',
APPLY => sub { apply_patch 'apply -R --cached', @_; }, APPLY => sub { apply_patch 'apply -R --cached', @_; },
...@@ -1493,8 +1503,8 @@ sub process_args { ...@@ -1493,8 +1503,8 @@ sub process_args {
'checkout_head' : 'checkout_nothead'); 'checkout_head' : 'checkout_nothead');
$arg = shift @ARGV or die "missing --"; $arg = shift @ARGV or die "missing --";
} }
} elsif ($1 eq 'stage') { } elsif ($1 eq 'stage' or $1 eq 'stash') {
$patch_mode = 'stage'; $patch_mode = $1;
$arg = shift @ARGV or die "missing --"; $arg = shift @ARGV or die "missing --";
} else { } else {
die "unknown --patch mode: $1"; die "unknown --patch mode: $1";
......
...@@ -21,6 +21,14 @@ trap 'rm -f "$TMP-*"' 0 ...@@ -21,6 +21,14 @@ trap 'rm -f "$TMP-*"' 0
ref_stash=refs/stash ref_stash=refs/stash
if git config --get-colorbool color.interactive; then
help_color="$(git config --get-color color.interactive.help 'red bold')"
reset_color="$(git config --get-color '' reset)"
else
help_color=
reset_color=
fi
no_changes () { no_changes () {
git diff-index --quiet --cached HEAD --ignore-submodules -- && git diff-index --quiet --cached HEAD --ignore-submodules -- &&
git diff-files --quiet --ignore-submodules git diff-files --quiet --ignore-submodules
...@@ -68,19 +76,44 @@ create_stash () { ...@@ -68,19 +76,44 @@ create_stash () {
git commit-tree $i_tree -p $b_commit) || git commit-tree $i_tree -p $b_commit) ||
die "Cannot save the current index state" die "Cannot save the current index state"
# state of the working tree if test -z "$patch_mode"
w_tree=$( ( then
# state of the working tree
w_tree=$( (
rm -f "$TMP-index" &&
cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
GIT_INDEX_FILE="$TMP-index" &&
export GIT_INDEX_FILE &&
git read-tree -m $i_tree &&
git add -u &&
git write-tree &&
rm -f "$TMP-index"
) ) ||
die "Cannot save the current worktree state"
else
rm -f "$TMP-index" && rm -f "$TMP-index" &&
cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && GIT_INDEX_FILE="$TMP-index" git read-tree HEAD &&
GIT_INDEX_FILE="$TMP-index" &&
export GIT_INDEX_FILE && # find out what the user wants
git read-tree -m $i_tree && GIT_INDEX_FILE="$TMP-index" \
git add -u && git add--interactive --patch=stash -- &&
git write-tree &&
rm -f "$TMP-index" # state of the working tree
) ) || w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
die "Cannot save the current worktree state" die "Cannot save the current worktree state"
git diff-tree -p HEAD $w_tree > "$TMP-patch" &&
test -s "$TMP-patch" ||
die "No changes selected"
rm -f "$TMP-index" ||
die "Cannot remove temporary index (can't happen)"
fi
# create the stash # create the stash
if test -z "$stash_msg" if test -z "$stash_msg"
then then
...@@ -95,12 +128,20 @@ create_stash () { ...@@ -95,12 +128,20 @@ create_stash () {
save_stash () { save_stash () {
keep_index= keep_index=
patch_mode=
while test $# != 0 while test $# != 0
do do
case "$1" in case "$1" in
--keep-index) --keep-index)
keep_index=t keep_index=t
;; ;;
--no-keep-index)
keep_index=
;;
-p|--patch)
patch_mode=t
keep_index=t
;;
-q|--quiet) -q|--quiet)
GIT_QUIET=t GIT_QUIET=t
;; ;;
...@@ -131,11 +172,22 @@ save_stash () { ...@@ -131,11 +172,22 @@ save_stash () {
die "Cannot save the current status" die "Cannot save the current status"
say Saved working directory and index state "$stash_msg" say Saved working directory and index state "$stash_msg"
git reset --hard ${GIT_QUIET:+-q} if test -z "$patch_mode"
if test -n "$keep_index" && test -n $i_tree
then then
git read-tree --reset -u $i_tree git reset --hard ${GIT_QUIET:+-q}
if test -n "$keep_index" && test -n $i_tree
then
git read-tree --reset -u $i_tree
fi
else
git apply -R < "$TMP-patch" ||
die "Cannot remove worktree changes"
if test -z "$keep_index"
then
git reset
fi
fi fi
} }
......
#!/bin/sh
test_description='git checkout --patch'
. ./lib-patch-mode.sh
test_expect_success 'setup' '
mkdir dir &&
echo parent > dir/foo &&
echo dummy > bar &&
git add bar dir/foo &&
git commit -m initial &&
test_tick &&
test_commit second dir/foo head &&
echo index > dir/foo &&
git add dir/foo &&
set_and_save_state bar bar_work bar_index &&
save_head
'
# note: bar sorts before dir, so the first 'n' is always to skip 'bar'
test_expect_success 'saying "n" does nothing' '
set_state dir/foo work index
(echo n; echo n) | test_must_fail git stash save -p &&
verify_state dir/foo work index &&
verify_saved_state bar
'
test_expect_success 'git stash -p' '
(echo n; echo y) | git stash save -p &&
verify_state dir/foo head index &&
verify_saved_state bar &&
git reset --hard &&
git stash apply &&
verify_state dir/foo work head &&
verify_state bar dummy dummy
'
test_expect_success 'git stash -p --no-keep-index' '
set_state dir/foo work index &&
set_state bar bar_work bar_index &&
(echo n; echo y) | git stash save -p --no-keep-index &&
verify_state dir/foo head head &&
verify_state bar bar_work dummy &&
git reset --hard &&
git stash apply --index &&
verify_state dir/foo work index &&
verify_state bar dummy bar_index
'
test_expect_success 'none of this moved HEAD' '
verify_saved_head
'
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