All of lore.kernel.org
 help / color / mirror / Atom feed
From: Thomas Gummerer <t.gummerer@gmail.com>
To: git@vger.kernel.org
Cc: "Junio C Hamano" <gitster@pobox.com>, "Jeff King" <peff@peff.net>,
	"Johannes Schindelin" <Johannes.Schindelin@gmx.de>,
	"Øyvind A . Holm" <sunny@sunbase.org>,
	"Jakub Narębski" <jnareb@gmail.com>,
	"Matthieu Moy" <Matthieu.Moy@grenoble-inp.fr>,
	"Thomas Gummerer" <t.gummerer@gmail.com>
Subject: [PATCH v5 4/6] stash: teach 'push' (and 'create_stash') to honor pathspec
Date: Fri, 17 Feb 2017 22:41:39 +0000	[thread overview]
Message-ID: <20170217224141.19183-5-t.gummerer@gmail.com> (raw)
In-Reply-To: <20170217224141.19183-1-t.gummerer@gmail.com>

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>
---
 Documentation/git-stash.txt        | 11 ++++++
 git-stash.sh                       | 48 +++++++++++++++++++------
 t/t3903-stash.sh                   | 72 ++++++++++++++++++++++++++++++++++++++
 t/t3905-stash-include-untracked.sh | 26 ++++++++++++++
 4 files changed, 146 insertions(+), 11 deletions(-)

diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
index d93c47446a..086c229db0 100644
--- a/Documentation/git-stash.txt
+++ b/Documentation/git-stash.txt
@@ -15,9 +15,13 @@ 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>]]
+	     [--] [<pathspec>...]
 'git stash' clear
 'git stash' create [<message>]
 'git stash' create [-m <message>] [-u|--include-untracked <untracked|all>]
+	     [-- <pathspec>...]
 'git stash' store [-m|--message <message>] [-q|--quiet] <commit>
 
 DESCRIPTION
@@ -47,6 +51,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>] [--] [<pathspec>...]::
 
 	Save your local modifications to a new 'stash', and run `git reset
 	--hard` to revert them.  The <message> part is optional and gives
@@ -55,6 +60,12 @@ 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.
 +
+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.
 +
diff --git a/git-stash.sh b/git-stash.sh
index e76741d37d..6b56f84d81 100755
--- a/git-stash.sh
+++ b/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 () {
@@ -72,12 +73,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
@@ -109,7 +114,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" &&
@@ -132,7 +137,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"
@@ -146,7 +151,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) ||
@@ -275,7 +280,7 @@ push_stash () {
 	fi
 
 	git update-index -q --refresh
-	if no_changes
+	if no_changes "$@"
 	then
 		say "$(gettext "No local changes to save")"
 		exit 0
@@ -283,18 +288,39 @@ push_stash () {
 	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
+			saved_untracked=
+			if test -n "$(git ls-files --others -- "$@")"
+			then
+				saved_untracked=$(
+					git ls-files -z --others -- "$@" |
+					    xargs -0 git stash create -u all --)
+			fi
+			git ls-files -z -- "$@" | xargs -0 git reset ${GIT_QUIET:+-q} --
+			git ls-files -z --modified -- "$@" | xargs -0 git checkout ${GIT_QUIET:+-q} HEAD --
+			if test -n "$(git ls-files -z --others -- "$@")"
+			then
+				git ls-files -z --others -- "$@" | xargs -0 git clean --force -d ${GIT_QUIET:+-q} --
+			fi
+			if test -n "$saved_untracked"
+			then
+				git stash pop -q $saved_untracked
+			fi
+		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"
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index ffe3549ea5..4fb800eec8 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -802,4 +802,76 @@ test_expect_success 'create with multiple arguments for the 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_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 push -p with pathspec shows no changes only onece' '
+	>file &&
+	git add file &&
+	git stash push -p not-file >actual &&
+	echo "No local changes to save" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'stash push with pathspec shows no changes when there are none' '
+	>file &&
+	git add file &&
+	git stash push not-file >actual &&
+	echo "No local changes to save" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'untracked file is not removed when using pathspecs' '
+	>untracked &&
+	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
diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh
index f372fc8ca8..193adc7b68 100755
--- a/t/t3905-stash-include-untracked.sh
+++ b/t/t3905-stash-include-untracked.sh
@@ -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
-- 
2.11.0.301.g27b9849079.dirty


  parent reply	other threads:[~2017-02-17 22:41 UTC|newest]

Thread overview: 61+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <mailto:20170205202642.14216-1-t.gummerer@gmail.com>
2017-02-12 21:54 ` [PATCH v4 0/7] stash: support pathspec argument Thomas Gummerer
2017-02-12 21:54   ` [PATCH v4 1/7] Documentation/stash: remove mention of git reset --hard Thomas Gummerer
2017-02-12 21:54   ` [PATCH v4 2/7] stash: introduce push verb Thomas Gummerer
2017-02-12 21:54   ` [PATCH v4 3/7] stash: add test for the create command line arguments Thomas Gummerer
2017-02-12 21:54   ` [PATCH v4 4/7] stash: introduce new format create Thomas Gummerer
     [not found]     ` <vpqefz0ohub.fsf@anie.imag.fr>
2017-02-14 21:40       ` Thomas Gummerer
2017-02-12 21:54   ` [PATCH v4 5/7] stash: teach 'push' (and 'create') to honor pathspec Thomas Gummerer
2017-02-12 21:54   ` [PATCH v4 6/7] stash: use stash_push for no verb form Thomas Gummerer
2017-02-12 21:54   ` [PATCH v4 7/7] stash: allow pathspecs in the " Thomas Gummerer
2017-02-12 22:07   ` [PATCH v4 0/7] stash: support pathspec argument Thomas Gummerer
2017-02-17 22:41   ` [PATCH v5 0/6] " Thomas Gummerer
2017-02-17 22:41     ` [PATCH v5 1/6] stash: introduce push verb Thomas Gummerer
2017-02-17 22:41     ` [PATCH v5 2/6] stash: add test for the create command line arguments Thomas Gummerer
2017-02-17 22:41     ` [PATCH v5 3/6] stash: refactor stash_create Thomas Gummerer
2017-02-17 23:48       ` Jeff King
2017-02-19  9:17         ` Thomas Gummerer
2017-02-17 22:41     ` Thomas Gummerer [this message]
2017-02-17 22:41     ` [PATCH v5 5/6] stash: use stash_push for no verb form Thomas Gummerer
2017-02-17 22:41     ` [PATCH v5 6/6] stash: allow pathspecs in the " Thomas Gummerer
2017-02-17 23:46       ` Jeff King
2017-02-19  9:18         ` Thomas Gummerer
2017-02-17 23:01     ` [PATCH v5 0/6] stash: support pathspec argument Junio C Hamano
2017-02-17 23:12       ` Thomas Gummerer
2017-02-17 23:14         ` Junio C Hamano
     [not found]     ` <xmqqr32wph97.fsf@gitster.mtv.corp.google.com>
2017-02-17 23:06       ` Thomas Gummerer
2017-02-19 11:03     ` [PATCH v6 " Thomas Gummerer
2017-02-19 11:03       ` [PATCH v6 1/6] stash: introduce push verb Thomas Gummerer
2017-02-19 11:03       ` [PATCH v6 2/6] stash: add test for the create command line arguments Thomas Gummerer
2017-02-19 11:03       ` [PATCH v6 3/6] stash: refactor stash_create Thomas Gummerer
2017-02-19 11:03       ` [PATCH v6 4/6] stash: teach 'push' (and 'create_stash') to honor pathspec Thomas Gummerer
2017-02-21 16:55         ` Junio C Hamano
2017-02-25 20:27           ` Thomas Gummerer
2017-02-19 11:03       ` [PATCH v6 5/6] stash: use stash_push for no verb form Thomas Gummerer
2017-02-19 11:03       ` [PATCH v6 6/6] stash: allow pathspecs in the " Thomas Gummerer
2017-02-20  7:57       ` [PATCH v6 0/6] stash: support pathspec argument Jeff King
2017-02-20  8:22       ` Junio C Hamano
2017-02-20 19:39       ` Junio C Hamano
2017-02-25 15:57         ` Thomas Gummerer
2017-02-25 21:33       ` [PATCH v7 " Thomas Gummerer
2017-02-25 21:33         ` [PATCH v7 1/6] stash: introduce push verb Thomas Gummerer
2017-02-25 21:33         ` [PATCH v7 2/6] stash: add test for the create command line arguments Thomas Gummerer
2017-02-25 21:33         ` [PATCH v7 3/6] stash: refactor stash_create Thomas Gummerer
2017-02-25 21:33         ` [PATCH v7 4/6] stash: teach 'push' (and 'create_stash') to honor pathspec Thomas Gummerer
2017-02-27 20:32           ` Junio C Hamano
2017-02-27 20:35           ` Junio C Hamano
2017-02-27 21:56             ` Thomas Gummerer
2017-02-27 22:58               ` Junio C Hamano
2017-02-27 21:09           ` Junio C Hamano
2017-02-27 21:53             ` Thomas Gummerer
2017-02-25 21:33         ` [PATCH v7 5/6] stash: use stash_push for no verb form Thomas Gummerer
2017-02-25 21:33         ` [PATCH v7 6/6] stash: allow pathspecs in the " Thomas Gummerer
2017-02-28 20:33         ` [PATCH v8 0/6] stash: support pathspec argument Thomas Gummerer
2017-02-28 20:33           ` [PATCH v8 1/6] stash: introduce push verb Thomas Gummerer
2017-02-28 20:33           ` [PATCH v8 2/6] stash: add test for the create command line arguments Thomas Gummerer
2017-02-28 20:33           ` [PATCH v8 3/6] stash: refactor stash_create Thomas Gummerer
2017-02-28 20:33           ` [PATCH v8 4/6] stash: teach 'push' (and 'create_stash') to honor pathspec Thomas Gummerer
2017-02-28 22:15             ` Junio C Hamano
2017-03-01 21:57               ` Thomas Gummerer
2017-03-01 22:43                 ` Junio C Hamano
2017-02-28 20:33           ` [PATCH v8 5/6] stash: use stash_push for no verb form Thomas Gummerer
2017-02-28 20:33           ` [PATCH v8 6/6] stash: allow pathspecs in the " Thomas Gummerer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170217224141.19183-5-t.gummerer@gmail.com \
    --to=t.gummerer@gmail.com \
    --cc=Johannes.Schindelin@gmx.de \
    --cc=Matthieu.Moy@grenoble-inp.fr \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=jnareb@gmail.com \
    --cc=peff@peff.net \
    --cc=sunny@sunbase.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.