[PATCH] git-add -p: be able to undo a given hunk

Subsystems: documentation, the rest

STALE3730d

16 messages, 6 authors, 2016-06-15 · open the first message on its own page

[PATCH] git-add -p: be able to undo a given hunk

From: Pierre Habouzit <hidden>
Date: 2016-06-15 22:47:05

One of my most frequent use case for git-add -p is when I had an intense
debug session with quite a lot of debug() traces added. I then want only
to select the hunks corresponding to the bugfixes and throw away the debug
ones.

With this new operation, instead of not staging hunks I don't want and
will eventually undo, I can just undo them.

Signed-off-by: Pierre Habouzit <redacted>
---

    I reckon this is a tad late given we're already at -rc2, but that's
    an itch that has scratched me for quite some time already, and I had
    to scratch it today...

    the change looks pretty safe to me though.

    The only think that looks odd in the patch is the removal of the
    if ($_->{USE}) clause from the TEXT copying loops, but
    coalesce_overlapping_hunks already ensures that only ->{USE}d hunks
    remain. I just have modified it to deal with ->{UNDO}ed hunks the
    same way.

 Documentation/git-add.txt |    1 +
 git-add--interactive.perl |   38 ++++++++++++++++++++++++++++++--------
 2 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index ab1943c..7b173dc 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -254,6 +254,7 @@ patch::
 
        y - stage this hunk
        n - do not stage this hunk
+       u - do not stage this hunk and revert it
        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
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index df9f231..945de9d 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -693,6 +693,7 @@ sub split_hunk {
 			ADDDEL => 0,
 			POSTCTX => 0,
 			USE => undef,
+			UNDO => undef,
 		};
 
 		while (++$i < @$text) {
@@ -835,12 +836,13 @@ sub merge_hunk {
 }
 
 sub coalesce_overlapping_hunks {
+	my $field = shift;
 	my (@in) = @_;
 	my @out = ();
 
 	my ($last_o_ctx, $last_was_dirty);
 
-	for (grep { $_->{USE} } @in) {
+	for (grep { $_->{$field} } @in) {
 		my $text = $_->{TEXT};
 		my ($o_ofs) = parse_hunk_header($text->[0]);
 		if (defined $last_o_ctx &&
@@ -991,6 +993,7 @@ sub help_patch_cmd {
 	print colored $help_color, <<\EOF ;
 y - stage this hunk
 n - do not stage this hunk
+u - do not stage this hunk and revert it
 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
@@ -1140,7 +1143,7 @@ sub patch_update_file {
 		}
 		print colored $prompt_color, 'Stage ',
 		  ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),
-		  " [y,n,q,a,d,/$other,?]? ";
+		  " [y,n,u,q,a,d,/$other,?]? ";
 		my $line = prompt_single_character;
 		if ($line) {
 			if ($line =~ /^y/i) {
@@ -1149,6 +1152,10 @@ sub patch_update_file {
 			elsif ($line =~ /^n/i) {
 				$hunk[$ix]{USE} = 0;
 			}
+			elsif ($line =~ /^u/) {
+				$hunk[$ix]{USE} = 0;
+				$hunk[$ix]{UNDO} = 1;
+			}
 			elsif ($line =~ /^a/i) {
 				while ($ix < $num) {
 					if (!defined $hunk[$ix]{USE}) {
@@ -1301,14 +1308,14 @@ sub patch_update_file {
 		}
 	}
 
-	@hunk = coalesce_overlapping_hunks(@hunk);
-
 	my $n_lofs = 0;
 	my @result = ();
-	for (@hunk) {
-		if ($_->{USE}) {
-			push @result, @{$_->{TEXT}};
-		}
+	my @undo = ();
+	for (coalesce_overlapping_hunks("USE", @hunk)) {
+		push @result, @{$_->{TEXT}};
+	}
+	for (coalesce_overlapping_hunks("UNDO", @hunk)) {
+		push @undo, @{$_->{TEXT}};
 	}
 
 	if (@result) {
@@ -1326,6 +1333,21 @@ sub patch_update_file {
 		refresh();
 	}
 
+	if (@undo) {
+		my $fh;
+
+		open $fh, '| git apply -R';
+		for (@{$head->{TEXT}}, @undo) {
+			print $fh $_;
+		}
+		if (!close $fh) {
+			for (@{$head->{TEXT}}, @undo) {
+				print STDERR $_;
+			}
+		}
+		refresh();
+	}
+
 	print "\n";
 	return $quit;
 }
-- 
1.6.4.rc1.189.g9f628.dirty

Re: [PATCH] git-add -p: be able to undo a given hunk

From: Thomas Rast <hidden>
Date: 2016-06-15 22:47:05

Pierre Habouzit wrote:
One of my most frequent use case for git-add -p is when I had an intense
debug session with quite a lot of debug() traces added. I then want only
to select the hunks corresponding to the bugfixes and throw away the debug
ones.

With this new operation, instead of not staging hunks I don't want and
will eventually undo, I can just undo them.
This is what 'git checkout --interactive -- $file' or 'git reset
--interactive --hard' would accomplish, if they existed.  I wonder if
there would be a way to avoid yet more command letters, and instead
have "modes" that affect what happens with hunks you said 'y' to.  For
example:

  add -p		apply --cached
  undo -p		apply -R
  unstage -p		apply -R --cached
    [with hunks coming from diff --cached obviously]

(I picked 'undo' and 'unstage' semi-randomly, but it's not, after all,
an 'add' operation any more and the user doesn't need to know that the
program doing this is in fact git-add--interactive.)
+       u - do not stage this hunk and revert it
you're overloading terminology a bit too much for my taste.  It has
nothing to do with what git-revert does, and we shouldn't confuse
people more about that.
 Documentation/git-add.txt |    1 +
 git-add--interactive.perl |   38 ++++++++++++++++++++++++++++++--------
Tests?
quoted hunk
@@ -693,6 +693,7 @@ sub split_hunk {
 			ADDDEL => 0,
 			POSTCTX => 0,
 			USE => undef,
+			UNDO => undef,
 		};
Why not fold this into a single field?  It could, say, take values 0,
1, or '-R'.  It could probably be renamed to ACTION, but USE would be
fine if you want to avoid the code churn.  Then you shouldn't need
_completely_ separate handling during application.
quoted hunk
@@ -1149,6 +1152,10 @@ sub patch_update_file {
 			elsif ($line =~ /^n/i) {
 				$hunk[$ix]{USE} = 0;
+				$hunk[$ix]{UNDO} = 0;

and similarly for [yad] too, on the grounds that the user can go back
and change his choices with [KJ].  Of course that is not necessary if
you go the ACTION way outlined above.
+	if (@undo) {
+		my $fh;
+
+		open $fh, '| git apply -R';
This probably needs a --recount to cope with the case where the hunk
headers became stale/invalid through user [e]diting.
+		for (@{$head->{TEXT}}, @undo) {
+			print $fh $_;
+		}
+		if (!close $fh) {
+			for (@{$head->{TEXT}}, @undo) {
+				print STDERR $_;
+			}
+		}
+		refresh();
+	}
-- 
Thomas Rast
trast@{inf,student}.ethz.ch

Re: [PATCH] git-add -p: be able to undo a given hunk

From: Pierre Habouzit <hidden>
Date: 2016-06-15 22:47:05

On Thu, Jul 23, 2009 at 10:41:31AM +0200, Thomas Rast wrote:
Pierre Habouzit wrote:
quoted
One of my most frequent use case for git-add -p is when I had an intense
debug session with quite a lot of debug() traces added. I then want only
to select the hunks corresponding to the bugfixes and throw away the debug
ones.

With this new operation, instead of not staging hunks I don't want and
will eventually undo, I can just undo them.
This is what 'git checkout --interactive -- $file' or 'git reset
--interactive --hard' would accomplish, if they existed.  I wonder if
there would be a way to avoid yet more command letters, and instead
have "modes" that affect what happens with hunks you said 'y' to.  For
example:

  add -p		apply --cached
  undo -p		apply -R
  unstage -p		apply -R --cached
    [with hunks coming from diff --cached obviously]

(I picked 'undo' and 'unstage' semi-randomly, but it's not, after all,
an 'add' operation any more and the user doesn't need to know that the
program doing this is in fact git-add--interactive.)
The point is I want to do them at once, see my use case: I want to stage
the hunks from my bugfix, and remove the debugging bits of it at the
same time, I don't want to run two commands.

I see git add -p as a triaging command, giving me the choice to do the
three things you can do with a hunk:
  - stage it now (y)
  - stage it later (n)
  - never stage it, it was intermediate code, debug, whatever (u).
quoted
+       u - do not stage this hunk and revert it
you're overloading terminology a bit too much for my taste.  It has
nothing to do with what git-revert does, and we shouldn't confuse
people more about that.
Right, revert is probably too overloaded, let it be "forget" or "drop"
instead if you want, I don't care much.
quoted
 Documentation/git-add.txt |    1 +
 git-add--interactive.perl |   38 ++++++++++++++++++++++++++++++--------
Tests?
Riight, will do :)
quoted
@@ -693,6 +693,7 @@ sub split_hunk {
 			ADDDEL => 0,
 			POSTCTX => 0,
 			USE => undef,
+			UNDO => undef,
 		};
Why not fold this into a single field?  It could, say, take values 0,
1, or '-R'.  It could probably be renamed to ACTION, but USE would be
fine if you want to avoid the code churn.  Then you shouldn't need
_completely_ separate handling during application.
quoted
@@ -1149,6 +1152,10 @@ sub patch_update_file {
 			elsif ($line =~ /^n/i) {
 				$hunk[$ix]{USE} = 0;
+				$hunk[$ix]{UNDO} = 0;

and similarly for [yad] too, on the grounds that the user can go back
and change his choices with [KJ].  Of course that is not necessary if
you go the ACTION way outlined above.
I hesitated to do so, but yes it makes perfect sense.
quoted
+	if (@undo) {
+		my $fh;
+
+		open $fh, '| git apply -R';
This probably needs a --recount to cope with the case where the hunk
headers became stale/invalid through user [e]diting.
Indeed.

-- 
Intersec <http://www.intersec.com>
Pierre Habouzit [off-list ref]
Tél : +33 (0)1 5570 3346
Mob : +33 (0)6 1636 8131
Fax : +33 (0)1 5570 3332
37 Rue Pierre Lhomme
92400 Courbevoie

[RFC PATCH] Implement unstage and reset modes for git-add--interactive

From: Thomas Rast <hidden>
Date: 2016-06-15 22:47:05

This is just the required change to git-add--interactive.  Since the
use of this script is an implementation detail, the next step would be
to wrap this option in other commands such as 'git reset --patch' and
'git unstage --patch'.

Since there is no frontend support whatsoever, you have to run

  git add--interactive --patch=unstage --

manually (including the --), and similarly for --patch=reset.
---

I wrote:
This is what 'git checkout --interactive -- $file' or 'git reset
--interactive --hard' would accomplish, if they existed.  I wonder if
there would be a way to avoid yet more command letters, and instead
have "modes" that affect what happens with hunks you said 'y' to.  For
example:

  add -p                apply --cached
  undo -p               apply -R
  unstage -p            apply -R --cached
    [with hunks coming from diff --cached obviously]

(I picked 'undo' and 'unstage' semi-randomly, but it's not, after all,
an 'add' operation any more and the user doesn't need to know that the
program doing this is in fact git-add--interactive.)
Like so, maybe.  I realise that it does _not_ fill Pierre's need for a
command that can do it all in one go, but if people like this I'll
make a real patch series.

I'm not completely happy with the idea of reset --interactive by the
way, as it's not a reset in the 'reset $commit' sense.



 git-add--interactive.perl |   60 +++++++++++++++++++++++++++++++++-----------
 1 files changed, 45 insertions(+), 15 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index df9f231..ad322df 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -73,6 +73,27 @@
 # command line options
 my $patch_mode;
 
+my %patch_modes = (
+	'stage' => {
+		DIFF => 'diff-files -p',
+		APPLY => 'apply --cached',
+		VERB => 'Stage',
+		PARTICIPLE => 'Staging',
+	},
+	'unstage' => {
+		DIFF => 'diff-index -p --cached HEAD',
+		APPLY => 'apply -R --cached',
+		VERB => 'UNstage',
+		PARTICIPLE => 'UNstaging',
+	},
+	'reset' => {
+		DIFF => 'diff-files -p',
+		APPLY => 'apply -R',
+		VERB => 'RESET',
+		PARTICIPLE => 'RESETTING',
+	},
+);
+
 sub run_cmd_pipe {
 	if ($^O eq 'MSWin32' || $^O eq 'msys') {
 		my @invalid = grep {m/[":*]/} @_;
@@ -615,10 +636,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 +899,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 +907,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 +941,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 +1011,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
@@ -1138,8 +1162,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 +1338,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 +1387,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 "--";
-- 
1.6.4.rc2.215.g4f661

[RFC PATCH v2 1/3] Introduce git-unstage

From: Thomas Rast <hidden>
Date: 2016-06-15 22:47:05

The new command 'git unstage' is the precise opposite of 'git stage'
(i.e., git-add).  As such, it is the same as 'git reset --' unless the
current branch is unborn.

Signed-off-by: Thomas Rast <redacted>
---

So I decided I had wished for these frequently enough to actually
implement them.  I ran out of time before getting to the tests, but
the rest should be there.

v1 had a bug where it would not correctly detect the changed files,
this is fixed in the new patch (now 3/3).


 Documentation/git-unstage.txt |   26 ++++++++++++++++++++++++++
 Makefile                      |    1 +
 git-unstage.sh                |   26 ++++++++++++++++++++++++++
 wt-status.c                   |    6 +-----
 4 files changed, 54 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/git-unstage.txt
 create mode 100644 git-unstage.sh
diff --git a/Documentation/git-unstage.txt b/Documentation/git-unstage.txt
new file mode 100644
index 0000000..49d09fb
--- /dev/null
+++ b/Documentation/git-unstage.txt
@@ -0,0 +1,26 @@
+git-unstage(1)
+==============
+
+NAME
+----
+git-unstage - Remove changes to a file from the staging area
+
+
+SYNOPSIS
+--------
+[verse]
+'git unstage' <paths> ...
+
+
+DESCRIPTION
+-----------
+
+Overwrites the staged changes to the 'paths' with the values from
+HEAD, so that they are not included in the next commit.  The worktree
+is not affected.  (This is the same as `git reset \-- <paths>` unless
+you are on an unborn branch.)
+
+
+SEE ALSO
+--------
+linkgit:git-reset[1]
diff --git a/Makefile b/Makefile
index 75b9dcb..9e48fdc 100644
--- a/Makefile
+++ b/Makefile
@@ -329,6 +329,7 @@ SCRIPT_SH += git-request-pull.sh
 SCRIPT_SH += git-sh-setup.sh
 SCRIPT_SH += git-stash.sh
 SCRIPT_SH += git-submodule.sh
+SCRIPT_SH += git-unstage.sh
 SCRIPT_SH += git-web--browse.sh
 
 SCRIPT_PERL += git-add--interactive.perl
diff --git a/git-unstage.sh b/git-unstage.sh
new file mode 100644
index 0000000..7f99adf
--- /dev/null
+++ b/git-unstage.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git unstage file ...
+--"
+
+. git-sh-setup
+
+
+case "$1" in
+    --)
+	if [ $# -eq 1 ]; then
+	    die "You must specify at least one file to unstage"
+	fi
+	if git rev-parse -q --verify HEAD >/dev/null; then
+	    exec git reset "$@"
+	else
+	    exec git rm --cached "$@"
+	fi
+	;;
+    *)
+	usage
+	;;
+esac
diff --git a/wt-status.c b/wt-status.c
index 47735d8..f1a74a4 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -62,11 +62,7 @@ static void wt_status_print_cached_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER);
 	color_fprintf_ln(s->fp, c, "# Changes to be committed:");
-	if (!s->is_initial) {
-		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
-	} else {
-		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-	}
+	color_fprintf_ln(s->fp, c, "#   (use \"git unstage %s <file>...\" to unstage)", s->reference);
 	color_fprintf_ln(s->fp, c, "#");
 }
 
-- 
1.6.4.rc2.217.g74c0b.dirty

[RFC PATCH v2 2/3] Introduce git-discard

From: Thomas Rast <hidden>
Date: 2016-06-15 22:47:05

The new command 'git discard' is precisely the same as 'git checkout --'.

Signed-off-by: Thomas Rast <redacted>
---
 Documentation/git-discard.txt |   29 +++++++++++++++++++++++++++++
 Makefile                      |    1 +
 git-discard.sh                |   22 ++++++++++++++++++++++
 wt-status.c                   |    2 +-
 4 files changed, 53 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/git-discard.txt
 create mode 100644 git-discard.sh
diff --git a/Documentation/git-discard.txt b/Documentation/git-discard.txt
new file mode 100644
index 0000000..4db14f0
--- /dev/null
+++ b/Documentation/git-discard.txt
@@ -0,0 +1,29 @@
+git-discard(1)
+==============
+
+NAME
+----
+git-discard - Remove changes to a file from the worktree
+
+
+SYNOPSIS
+--------
+[verse]
+'git discard' <paths> ...
+
+
+DESCRIPTION
+-----------
+
+Overwrites your edits to the 'paths' with the values from the staging
+area, effectively throwing them away entirely.
+
+*WARNING:* All unstaged changes to the 'paths' are *irreversibly*
+lost.
+
+(This is the same as `git checkout \-- <paths>`.)
+
+
+SEE ALSO
+--------
+linkgit:git-checkout[1]
diff --git a/Makefile b/Makefile
index 9e48fdc..814d4b6 100644
--- a/Makefile
+++ b/Makefile
@@ -312,6 +312,7 @@ TEST_PROGRAMS =
 SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
 SCRIPT_SH += git-difftool--helper.sh
+SCRIPT_SH += git-discard.sh
 SCRIPT_SH += git-filter-branch.sh
 SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
diff --git a/git-discard.sh b/git-discard.sh
new file mode 100644
index 0000000..595df98
--- /dev/null
+++ b/git-discard.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+SUBDIRECTORY_OK=Yes
+OPTIONS_KEEPDASHDASH=
+OPTIONS_SPEC="\
+git discard file ...
+--"
+
+. git-sh-setup
+
+
+case "$1" in
+    --)
+	if [ $# -eq 1 ]; then
+	    die "You must specify at least one file to discard changes from"
+	fi
+	exec git checkout "$@"
+	;;
+    *)
+	usage
+	;;
+esac
diff --git a/wt-status.c b/wt-status.c
index f1a74a4..1dd4bed 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -75,7 +75,7 @@ static void wt_status_print_dirty_header(struct wt_status *s,
 		color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
 	else
 		color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
-	color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
+	color_fprintf_ln(s->fp, c, "#   (use \"git discard <file>...\" to discard changes in working directory)");
 	color_fprintf_ln(s->fp, c, "#");
 }
 
-- 
1.6.4.rc2.217.g74c0b.dirty

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

From: Thomas Rast <hidden>
Date: 2016-06-15 22:47:05

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 <redacted>
---
 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

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

From: Matthias Kestenholz <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 6:24 PM, Thomas Rast[off-list ref] wrote:
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.
While I do not really think even more git commands are so helpful, I
like the proposed command names, and I really like that it would be
possible to remove single hunks from the index. I have come to rely
very much on git add -p to make self-contained and clear commits and
think it would be a great feature if I could use this the other way
round too.


Thanks,
Matthias

Re: [RFC PATCH v2 1/3] Introduce git-unstage

From: Bert Wesarg <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 18:24, Thomas Rast[off-list ref] wrote:
As such, it is the same as 'git reset --' ...
Nope!
+           exec git reset "$@"
See!

Bert

Re: [RFC PATCH v2 2/3] Introduce git-discard

From: Elijah Newren <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 10:24 AM, Thomas Rast[off-list ref] wrote:
The new command 'git discard' is precisely the same as 'git checkout --'.
+(This is the same as `git checkout \-- <paths>`.)
Actually, there's an important difference:
+       exec git checkout "$@"
$ git branch foo HEAD~20
$ touch foo && git add foo
$ echo hi >> foo
$ git discard foo
A     foo
Switched to branch "foo"

You really do need that "--".

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

From: Bert Wesarg <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 18:24, Thomas Rast[off-list ref] wrote:
'discard': offer hunks from 'git diff', and discard (i.e., undo the
 edit in the working tree) the ones selected.
Great, I really miss this feature in git-gui, maybe its easy to add
this there too. I may look into this by my self, but not this weekend.

Bert

Re: [RFC PATCH v2 1/3] Introduce git-unstage

From: Bert Wesarg <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 19:59, Bert Wesarg[off-list ref] wrote:
On Fri, Jul 24, 2009 at 18:24, Thomas Rast[off-list ref] wrote:
quoted
As such, it is the same as 'git reset --' ...
Nope!
quoted
+           exec git reset "$@"
See!
Sorry, it was not obvious clear to me, that you don't shift away '--'.
Does the option parsing add '--' if it is missing?

Bert
Bert

Re: [RFC PATCH v2 2/3] Introduce git-discard

From: Bert Wesarg <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 20:02, Elijah Newren[off-list ref] wrote:
On Fri, Jul 24, 2009 at 10:24 AM, Thomas Rast[off-list ref] wrote:
quoted
The new command 'git discard' is precisely the same as 'git checkout --'.
+(This is the same as `git checkout \-- <paths>`.)
Actually, there's an important difference:
quoted
+       exec git checkout "$@"
$ git branch foo HEAD~20
$ touch foo && git add foo
$ echo hi >> foo
$ git discard foo
A     foo
Switched to branch "foo"

You really do need that "--".
You fall into the same trap as me for patch 1/3. He does not shift
away the '--', so it is still in "$@".

Bert.

Re: [RFC PATCH v2 1/3] Introduce git-unstage

From: Elijah Newren <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 10:24 AM, Thomas Rast[off-list ref] wrote:
The new command 'git unstage' is the precise opposite of 'git stage'
(i.e., git-add).  As such, it is the same as 'git reset --' unless the
current branch is unborn.
I really, really like the idea of being able to do something like
unstage -p.  That's sweet, thanks for working on that.

Some potential issues, though:

git unstage may cause issues for some people if they try to use it
when in the middle of a merge (why reset back to HEAD rather than one
of the other branches being merged?).  With git reset, one can specify
which commit to reset the index back to.  With your 'unstage' command,
you not only assume 'HEAD' but don't allow specifying any other value.
 You could add a commit argument here, but that may be problematic
wording-wise, in that you're allowing to 'unstage' back to a specified
commit, with the possibly perplexing result that you have 'staged'
something else.

Also, you have added two new commands, unstage and discard, which are
two pieces of an hg-like revert (revert edits).  We still don't have a
command for a full hg-like revert, though (git checkout does not cover
the bases), and to cover that we'd have to add a third command.
Perhaps the three could be combined?


Elijah

Re: [RFC PATCH v2 2/3] Introduce git-discard

From: Elijah Newren <hidden>
Date: 2016-06-15 22:47:05

On Fri, Jul 24, 2009 at 12:12 PM, Bert Wesarg[off-list ref] wrote:
You fall into the same trap as me for patch 1/3. He does not shift
away the '--', so it is still in "$@".
Indeed; I somehow missed that '--' was silently added by git.  I guess
that's what I get for shooting off my mouth without actually trying
it.  Sorry for the noise.

Re: [RFC PATCH v2 2/3] Introduce git-discard

From: Pierre Habouzit <hidden>
Date: 2016-06-15 22:47:06

On Fri, Jul 24, 2009 at 06:24:35PM +0200, Thomas Rast wrote:
+*WARNING:* All unstaged changes to the 'paths' are *irreversibly*
+lost.
FWIW, as git checkout -- <path> is already doing that, and that
git-discard is clearly something meant as some UI sugar, I would make
that operation a "safe" porcelain by puting the discarded hunks in a
stash.

I'm shamelessly stealing this idea from Jeff, but it's probably an
excellent idea to have a strash¹. It makes git even safer to use,
especially to beginners, to whom we would talk about git-discard instead
of git checkout.

¹: stash-trash
-- 
Intersec <http://www.intersec.com>
Pierre Habouzit [off-list ref]
Tél : +33 (0)1 5570 3346
Mob : +33 (0)6 1636 8131
Fax : +33 (0)1 5570 3332
37 Rue Pierre Lhomme
92400 Courbevoie
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help