git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC PATCH v2 3/3] Implement unstage --patch and discard --patch

From
Thomas Rast <trast@student.ethz.ch>
Date
Jul 24, 2009, 16:24 UTC
Message-ID
<9cad6176fe941bd56bad7fed49cc55a96a7040ae.1248452530.git.trast@student.ethz.ch>
In-Reply-To
<2d756f643fc7d1b50cf590c95467fa029b49ff46.1248426652.git.trast@student.ethz.ch>

This implements a new optional argument --patch=<mode> in the git-add--interactive helper. The modes are:

'stage' (default, as before): offer hunks from 'git diff' and stage
  the ones selected by the user.
'unstage': offer hunks from 'git diff --cached', and unstage the ones
  selected.
'discard': offer hunks from 'git diff', and discard (i.e., undo the
  edit in the working tree) the ones selected.

With this in hand, we can then easily add a --patch option to the git-unstage and git-discard scripts that run git-add--interactive in the corresponding modes.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
 Documentation/git-discard.txt |    7 ++++
 Documentation/git-unstage.txt |    7 ++++
 git-add--interactive.perl     |   69 +++++++++++++++++++++++++++++++---------
 git-discard.sh                |    9 ++++-
 git-unstage.sh                |    9 ++++-
 5 files changed, 81 insertions(+), 20 deletions(-)
diff --git a/Documentation/git-discard.txt b/Documentation/git-discard.txt
index 4db14f0..2c063d1 100644
--- a/Documentation/git-discard.txt
+++ b/Documentation/git-discard.txt
@@ -24,6 +24,13 @@ lost.
 (This is the same as `git checkout \-- <paths>`.)
 
 
+OPTIONS
+-------
+-p::
+--patch::
+	Interactively select hunks to discard.
+
+
 SEE ALSO
 --------
 linkgit:git-checkout[1]
diff --git a/Documentation/git-unstage.txt b/Documentation/git-unstage.txt
index 49d09fb..d3da3e1 100644
--- a/Documentation/git-unstage.txt
+++ b/Documentation/git-unstage.txt
@@ -21,6 +21,13 @@ is not affected.  (This is the same as `git reset \-- <paths>` unless
 you are on an unborn branch.)
 
 
+OPTIONS
+-------
+-p::
+--patch::
+	Interactively select hunks to unstage.
+
+
 SEE ALSO
 --------
 linkgit:git-reset[1]
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index df9f231..502a0e4 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -73,6 +73,30 @@
 # command line options
 my $patch_mode;
 
+my %patch_modes = (
+	'stage' => {
+		DIFF => 'diff-files -p',
+		APPLY => 'apply --cached',
+		VERB => 'Stage',
+		PARTICIPLE => 'Staging',
+		FILTER => 'file-only',
+	},
+	'unstage' => {
+		DIFF => 'diff-index -p --cached HEAD',
+		APPLY => 'apply -R --cached',
+		VERB => 'UNstage',
+		PARTICIPLE => 'UNstaging',
+		FILTER => 'index-only',
+	},
+	'discard' => {
+		DIFF => 'diff-files -p',
+		APPLY => 'apply -R',
+		VERB => 'RESET',
+		PARTICIPLE => 'RESETTING',
+		FILTER => 'file-only',
+	},
+);
+
 sub run_cmd_pipe {
 	if ($^O eq 'MSWin32' || $^O eq 'msys') {
 		my @invalid = grep {m/[":*]/} @_;
@@ -615,10 +639,11 @@
 
 sub parse_diff {
 	my ($path) = @_;
-	my @diff = run_cmd_pipe(qw(git diff-files -p --), $path);
+	my @diff_cmd = split(" ", $patch_modes{$patch_mode}{DIFF});
+	my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
 	my @colored = ();
 	if ($diff_use_color) {
-		@colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);
+		@colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path);
 	}
 	my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
 
@@ -877,6 +902,7 @@
 		or die "failed to open hunk edit file for writing: " . $!;
 	print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
 	print $fh @$oldtext;
+	my $participle = $patch_modes{$patch_mode}{PARTICIPLE};
 	print $fh <<EOF;
 # ---
 # To remove '-' lines, make them ' ' lines (context).
@@ -884,7 +910,7 @@
 # Lines starting with # will be removed.
 #
 # If the patch applies cleanly, the edited hunk will immediately be
-# marked for staging. If it does not apply cleanly, you will be given
+# marked for $participle. If it does not apply cleanly, you will be given
 # an opportunity to edit again. If all lines of the hunk are removed,
 # then the edit is aborted and the hunk is left unchanged.
 EOF
@@ -918,7 +944,7 @@
 
 sub diff_applies {
 	my $fh;
-	open $fh, '| git apply --recount --cached --check';
+	open $fh, '| git '.$patch_modes{$patch_mode}{APPLY}.' --recount --check';
 	for my $h (@_) {
 		print $fh @{$h->{TEXT}};
 	}
@@ -988,12 +1014,13 @@
 }
 
 sub help_patch_cmd {
-	print colored $help_color, <<\EOF ;
-y - stage this hunk
-n - do not stage this hunk
-q - quit, do not stage this hunk nor any of the remaining ones
-a - stage this and all the remaining hunks in the file
-d - do not stage this hunk nor any of the remaining hunks in the file
+	my $verb = lc $patch_modes{$patch_mode}{VERB};
+	print colored $help_color, <<EOF ;
+y - $verb this hunk
+n - do not $verb this hunk
+q - quit, do not $verb this hunk nor any of the remaining ones
+a - $verb this and all the remaining hunks in the file
+d - do not $verb this hunk nor any of the remaining hunks in the file
 g - select a hunk to go to
 / - search for a hunk matching the given regex
 j - leave this hunk undecided, see next undecided hunk
@@ -1007,7 +1034,11 @@
 }
 
 sub patch_update_cmd {
-	my @all_mods = list_modified('file-only');
+	my $filter = 'file-only';
+	if ($patch_mode) {
+		$filter = $patch_modes{$patch_mode}{FILTER};
+	}
+	my @all_mods = list_modified($filter);
 	my @mods = grep { !($_->{BINARY}) } @all_mods;
 	my @them;
 
@@ -1138,8 +1169,8 @@
 		for (@{$hunk[$ix]{DISPLAY}}) {
 			print;
 		}
-		print colored $prompt_color, 'Stage ',
-		  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),
+		print colored $prompt_color, $patch_modes{$patch_mode}{VERB},
+		  ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'),
 		  " [y,n,q,a,d,/$other,?]? ";
 		my $line = prompt_single_character;
 		if ($line) {
@@ -1314,7 +1345,7 @@
 	if (@result) {
 		my $fh;
 
-		open $fh, '| git apply --cached --recount';
+		open $fh, '| git '.$patch_modes{$patch_mode}{APPLY}.' --recount';
 		for (@{$head->{TEXT}}, @result) {
 			print $fh $_;
 		}
@@ -1363,8 +1394,14 @@
 sub process_args {
 	return unless @ARGV;
 	my $arg = shift @ARGV;
-	if ($arg eq "--patch") {
-		$patch_mode = 1;
+	if ($arg =~ /--patch(?:=(.*))?/) {
+		if (defined $1 && defined $patch_modes{$1}) {
+			$patch_mode = $1;
+		} elsif (defined $1) {
+			die "unknown --patch mode: $1";
+		} else {
+			$patch_mode = 'stage';
+		}
 		$arg = shift @ARGV or die "missing --";
 		die "invalid argument $arg, expecting --"
 		    unless $arg eq "--";
diff --git a/git-discard.sh b/git-discard.sh
index 595df98..628346d 100644
--- a/git-discard.sh
+++ b/git-discard.sh
@@ -3,13 +3,18 @@
 SUBDIRECTORY_OK=Yes
 OPTIONS_KEEPDASHDASH=
 OPTIONS_SPEC="\
-git discard file ...
---"
+git discard [-p] file ...
+--
+p,patch         interactively select hunks to discard"
 
 . git-sh-setup
 
 
 case "$1" in
+    -p|--patch)
+	shift
+	exec git add--interactive --patch=discard "$@"
+	;;
     --)
 	if [ $# -eq 1 ]; then
 	    die "You must specify at least one file to discard changes from"
diff --git a/git-unstage.sh b/git-unstage.sh
index 7f99adf..921e12b 100644
--- a/git-unstage.sh
+++ b/git-unstage.sh
@@ -3,13 +3,18 @@
 SUBDIRECTORY_OK=Yes
 OPTIONS_KEEPDASHDASH=
 OPTIONS_SPEC="\
-git unstage file ...
---"
+git unstage [-p] file ...
+--
+p,patch         interactively select hunks to unstage"
 
 . git-sh-setup
 
 
 case "$1" in
+    -p|--patch)
+	shift
+	exec git add--interactive --patch=unstage "$@"
+	;;
     --)
 	if [ $# -eq 1 ]; then
 	    die "You must specify at least one file to unstage"
-- 
1.6.4.rc2.217.g74c0b.dirty
Previous: Pierre HabouzitNext: Matthias Kestenholz
Message 14 of 76 in “git-add -p: be able to undo a given hunk”
  1. git-add -p: be able to undo a given hunkPierre Habouzit, Jul 23, 2009
  2. Thomas RastJul 23, 2009
  3. Pierre HabouzitJul 23, 2009
  4. Implement unstage and reset modes for git-add--interactiveThomas Rast, Jul 24, 2009
  5. 1/3 Introduce git-unstageThomas Rast, Jul 24, 2009
  6. Bert WesargJul 24, 2009
  7. Bert WesargJul 24, 2009
  8. Elijah NewrenJul 24, 2009
  9. 2/3 Introduce git-discardThomas Rast, Jul 24, 2009
  10. Elijah NewrenJul 24, 2009
  11. Bert WesargJul 24, 2009
  12. Elijah NewrenJul 24, 2009
  13. Pierre HabouzitJul 25, 2009
  14. 3/3 Implement unstage --patch and discard --patchThomas Rast, Jul 24, 2009
  15. Matthias KestenholzJul 24, 2009
  16. Bert WesargJul 24, 2009
  17. Junio C HamanoJul 24, 2009
  18. Nanako ShiraishiJul 24, 2009
  19. Thomas RastJul 24, 2009
  20. Junio C HamanoJul 24, 2009
  21. 0/5 {checkout,reset,stash} --patchThomas Rast, Jul 25, 2009
  22. 1/5 git-apply--interactive: Refactor patch mode codeThomas Rast, Jul 25, 2009
  23. 2/5 builtin-add: refactor the meat of interactive_add()Thomas Rast, Jul 25, 2009
  24. 3/5 Implement 'git reset --patch'Thomas Rast, Jul 25, 2009
  25. 4/5 Implement 'git checkout --patch'Thomas Rast, Jul 25, 2009
  26. 5/5 Implement 'git stash save --patch'Thomas Rast, Jul 25, 2009
  27. Sverre RabbelierJul 26, 2009
  28. Thomas RastJul 26, 2009
  29. Thomas RastJul 27, 2009
  30. 0/5 {checkout,reset,stash} --patchThomas Rast, Jul 28, 2009
  31. 1/5 git-apply--interactive: Refactor patch mode codeThomas Rast, Jul 28, 2009
  32. 2/5 builtin-add: refactor the meat of interactive_add()Thomas Rast, Jul 28, 2009
  33. 3/5 Implement 'git reset --patch'Thomas Rast, Jul 28, 2009
  34. 4/5 Implement 'git checkout --patch'Thomas Rast, Jul 28, 2009
  35. 5/5 Implement 'git stash save --patch'Thomas Rast, Jul 28, 2009
  36. 6/5 DWIM 'git stash save -p' for 'git stash -p'Thomas Rast, Jul 28, 2009
  37. Jeff KingAug 9, 2009
  38. Thomas RastAug 9, 2009
  39. 0/5 Re: {checkout,reset,stash} --patchNicolas Sebrecht, Aug 9, 2009
  40. Thomas RastAug 9, 2009
  41. 0/5 Re: {checkout,reset,stash} --patchNicolas Sebrecht, Aug 9, 2009
  42. Thomas RastAug 9, 2009
  43. 0/5 Re: {checkout,reset,stash} --patchNicolas Sebrecht, Aug 9, 2009
  44. Thomas RastAug 10, 2009
  45. 0/6 {checkout,reset,stash} --patchThomas Rast, Aug 13, 2009
  46. 1/6 git-apply--interactive: Refactor patch mode codeThomas Rast, Aug 13, 2009
  47. 2/6 Add a small patch-mode testing libraryThomas Rast, Aug 13, 2009
  48. 3/6 builtin-add: refactor the meat of interactive_add()Thomas Rast, Aug 13, 2009
  49. 4/6 Implement 'git reset --patch'Thomas Rast, Aug 13, 2009
  50. 4/6 Implement 'git reset --patch'Thomas Rast, Aug 15, 2009
  51. 5/6 Implement 'git checkout --patch'Thomas Rast, Aug 13, 2009
  52. 5/6 Implement 'git checkout --patch'Thomas Rast, Aug 15, 2009
  53. 6/6 Implement 'git stash save --patch'Thomas Rast, Aug 13, 2009
  54. 7/6 DWIM 'git stash save -p' for 'git stash -p'Thomas Rast, Aug 13, 2009
  55. 0/6 Re: {checkout,reset,stash} --patchNicolas Sebrecht, Aug 14, 2009
  56. Jeff KingAug 15, 2009
  57. Junio C HamanoAug 15, 2009
  58. Thomas RastAug 15, 2009
  59. Thomas RastAug 15, 2009
  60. Jeff KingAug 18, 2009
  61. Thomas RastAug 19, 2009
  62. Jeff KingAug 19, 2009
  63. Junio C HamanoJul 23, 2009
  64. Nanako ShiraishiJul 24, 2009
  65. Junio C HamanoJul 24, 2009
  66. Jeff KingJul 24, 2009
  67. Junio C HamanoJul 25, 2009
  68. Thomas RastJul 25, 2009
  69. Pierre HabouzitJul 25, 2009
  70. Pierre HabouzitJul 25, 2009
  71. Jeff KingJul 26, 2009
  72. Pierre HabouzitJul 27, 2009
  73. Jeff KingJul 27, 2009
  74. Thomas RastJul 27, 2009
  75. Jeff KingJul 27, 2009
  76. Pierre HabouzitJul 24, 2009

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.