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.

Add an optional filename argument to git stash push, which allows for
stashing a single (or multiple) files.

Signed-off-by: Thomas Gummerer <t.gumme...@gmail.com>
---
 Documentation/git-stash.txt |  8 ++++++++
 git-stash.sh                | 42 ++++++++++++++++++++++++++++++++++--------
 t/t3903-stash.sh            | 27 +++++++++++++++++++++++++++
 3 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index 0ad5335a3e..871a3b246c 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -15,6 +15,9 @@ SYNOPSIS
 'git stash' branch <branchname> [<stash>]
 '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>]]
+            [--] [<paths>...]
 'git stash' clear
 'git stash' create [<message>]
 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>
@@ -46,6 +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>] [--] [<paths>...]::
 
        Save your local modifications to a new 'stash', and revert the
        the changes in the working tree to match the index.
@@ -55,6 +59,10 @@ save [-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.
 +
+If the paths argument is given in 'git stash push', only these files
+are put in the new 'stash'.  In addition only the indicated files are
+changed in the working tree to match the index.
++
 If the `--keep-index` option is used, all changes already added to the
 index are left intact.
 +
diff --git a/git-stash.sh b/git-stash.sh
index d6b4ae3290..7dcce629bd 100755
--- a/git-stash.sh
+++ b/git-stash.sh
@@ -41,7 +41,7 @@ no_changes () {
 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 -- $1
 }
 
 clear_stash () {
@@ -56,6 +56,23 @@ clear_stash () {
 }
 
 create_stash () {
+       files=
+       while test $# != 0
+       do
+               case "$1" in
+               --)
+                       shift
+                       break
+                       ;;
+               --files)
+                       ;;
+               *)
+                       files="$1 $files"
+                       ;;
+               esac
+               shift
+       done
+
        stash_msg="$1"
        untracked="$2"
 
@@ -92,7 +109,7 @@ create_stash () {
                # Untracked files are stored by themselves in a parentless 
commit, for
                # ease of unpacking later.
                u_commit=$(
-                       untracked_files | (
+                       untracked_files $files | (
                                GIT_INDEX_FILE="$TMPindex" &&
                                export GIT_INDEX_FILE &&
                                rm -f "$TMPindex" &&
@@ -115,7 +132,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 -- $files 
>"$TMP-stagenames" &&
                        git update-index -z --add --remove --stdin 
<"$TMP-stagenames" &&
                        git write-tree &&
                        rm -f "$TMPindex"
@@ -129,7 +146,7 @@ create_stash () {
 
                # find out what the user wants
                GIT_INDEX_FILE="$TMP-index" \
-                       git add--interactive --patch=stash -- &&
+                       git add--interactive --patch=stash -- $files &&
 
                # state of the working tree
                w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) ||
@@ -251,6 +268,8 @@ push_stash () {
                shift
        done
 
+       files="$*"
+
        if test -n "$patch_mode" && test -n "$untracked"
        then
                die "$(gettext "Can't use --patch and --include-untracked or 
--all at the same time")"
@@ -265,18 +284,25 @@ push_stash () {
        git reflog exists $ref_stash ||
                clear_stash || die "$(gettext "Cannot initialize stash")"
 
-       create_stash "$stash_msg" $untracked
+       create_stash --files $files -- "$stash_msg" "$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 -n "$files"
+               then
+                       git reset -- $files
+                       git checkout HEAD -- $(git ls-files --modified -- 
$files)
+                       git clean --force --quiet -- $(git ls-files --others -- 
$files)
+               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 -- $files
                fi
 
                if test "$keep_index" = "t" && test -n "$i_tree"
@@ -702,7 +728,7 @@ clear)
        ;;
 create)
        shift
-       create_stash "$*" && echo "$w_commit"
+       create_stash -- "$*" && echo "$w_commit"
        ;;
 store)
        shift
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 0171b824c9..3e763ff766 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -784,4 +784,31 @@ test_expect_success 'push -m shows right message' '
        test_cmp expect actual
 '
 
+test_expect_success 'stash -- <filename> 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 filename 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_done
-- 
2.11.0.483.g087da7b7c

Reply via email to