Re: [PATCH v3] git-add--interactive: manual hunk editing mode

Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]
From: Thomas Rast
Date: Wednesday, June 11, 2008 - 2:02 am

Jeff King wrote:

Ok, I finally see what you meant earlier.  So how about this, which is
basically "middle ground":

As in the parent, editing is restricted to a single hunk, no splitting
allowed (almost---you can still insert @ lines if you are good at
counting, but you're on your own).

However, as in the earlier versions, the hunk is placed in the editing
loop again.  In this version, I made it so it will be marked for use
as if you had entered 'y', so if it was the last (or only) hunk, the
files will be patched immediately.  But if you Ctrl-C out in the
middle, nothing has been changed yet.

The following two remarks also apply:

[...]

By the way, current 'add -p' recounts the headers to account for all
hunks that the user answered 'n'.  I haven't given it enough thought
to put it in the code yet, but it may be possible to rip that out as
well and unconditionally --recount.  Only the preimage line numbers
matter, and those never change.

-- 8< --
 git-add--interactive.perl |  128 ++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 127 insertions(+), 1 deletions(-)

diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 903953e..8c39149 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -2,6 +2,7 @@
 
 use strict;
 use Git;
+use File::Temp;
 
 my $repo = Git->repository();
 
@@ -18,6 +19,18 @@ my ($fraginfo_color) =
 	$diff_use_color ? (
 		$repo->get_color('color.diff.frag', 'cyan'),
 	) : ();
+my ($diff_plain_color) =
+	$diff_use_color ? (
+		$repo->get_color('color.diff.plain', ''),
+	) : ();
+my ($diff_old_color) =
+	$diff_use_color ? (
+		$repo->get_color('color.diff.old', 'red'),
+	) : ();
+my ($diff_new_color) =
+	$diff_use_color ? (
+		$repo->get_color('color.diff.new', 'green'),
+	) : ();
 
 my $normal_color = $repo->get_color("", "reset");
 
@@ -770,6 +783,108 @@ sub coalesce_overlapping_hunks {
 	return @out;
 }
 
+sub color_diff {
+	return map {
+		colored((/^@/  ? $fraginfo_color :
+			 /^\+/ ? $diff_new_color :
+			 /^-/  ? $diff_old_color :
+			 $diff_plain_color),
+			$_);
+	} @_;
+}
+
+sub edit_hunk_manually {
+	my ($oldtext) = @_;
+
+	my $t = File::Temp->new(
+		TEMPLATE => $repo->repo_path . "/git-hunk-edit.XXXXXX",
+		SUFFIX => '.diff'
+	);
+	print $t "# Manual hunk edit mode -- see bottom for a quick guide\n";
+	print $t @$oldtext;
+	print $t <<EOF;
+# ---
+# To remove '-' lines, make them ' ' lines (context).
+# To remove '+' lines, delete them.
+# Lines starting with # will be removed.
+#
+# If the patch applies cleanly, the hunk will immediately be marked
+# for staging as if you had answered 'y'.  (However, if you remove
+# everything, nothing happens.)  Otherwise, you will be asked to edit
+# again.
+#
+# Do not add @ lines unless you know what you are doing.  The original
+# @ line will be reinserted if you remove it.
+EOF
+	close $t;
+
+	my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor")
+		|| $ENV{VISUAL} || $ENV{EDITOR} || "vi";
+	system('sh', '-c', $editor.' "$@"', $editor, $t);
+
+	open my $fh, '<', $t
+		or die "failed to open hunk edit file for reading: " . $!;
+	my @newtext = grep { !/^#/ } <$fh>;
+	close $fh;
+
+	# Abort if nothing remains
+	if (@newtext == 0) {
+		return undef;
+	}
+
+	# Reinsert the first hunk header if the user accidentally deleted it
+	if ($newtext[0] !~ /^@/) {
+		unshift @newtext, $oldtext->[0];
+	}
+	return \@newtext;
+}
+
+sub diff_applies {
+	my $fh;
+	open $fh, '| git apply --recount --cached --check';
+	for my $h (@_) {
+		print $fh @{$h->{TEXT}};
+	}
+	return close $fh;
+}
+
+sub prompt_yesno {
+	my ($prompt) = @_;
+	while (1) {
+		print colored $prompt_color, $prompt;
+		my $line = <STDIN>;
+		return 0 if $line =~ /^n/i;
+		return 1 if $line =~ /^y/i;
+	}
+}
+
+sub edit_hunk_loop {
+	my ($head, $hunk, $ix) = @_;
+	my $text = $hunk->[$ix]->{TEXT};
+
+	while (1) {
+		$text = edit_hunk_manually($text);
+		if (!defined $text) {
+			return undef;
+		}
+		my $newhunk = { TEXT => $text, USE => 1 };
+		if (diff_applies($head,
+				 @{$hunk}[0..$ix-1],
+				 $newhunk,
+				 @{$hunk}[$ix+1..$#{$hunk}])) {
+			my @display = color_diff(@{$text});
+			$newhunk->{DISPLAY} = \@display;
+			return $newhunk;
+		}
+		else {
+			prompt_yesno(
+				'Your edited hunk does not apply. Edit again '
+				. '(saying "no" discards!) [y/n]? '
+				) or return undef;
+		}
+	}
+}
+
 sub help_patch_cmd {
 	print colored $help_color, <<\EOF ;
 y - stage this hunk
@@ -781,6 +896,7 @@ J - leave this hunk undecided, see next hunk
 k - leave this hunk undecided, see previous undecided hunk
 K - leave this hunk undecided, see previous hunk
 s - split the current hunk into smaller hunks
+e - manually edit the current hunk
 ? - print help
 EOF
 }
@@ -846,6 +962,7 @@ sub patch_update_file {
 
 	$num = scalar @hunk;
 	$ix = 0;
+	my $need_recount = 0;
 
 	while (1) {
 		my ($prev, $next, $other, $undecided, $i);
@@ -885,6 +1002,7 @@ sub patch_update_file {
 		if (hunk_splittable($hunk[$ix]{TEXT})) {
 			$other .= '/s';
 		}
+		$other .= '/e';
 		for (@{$hunk[$ix]{DISPLAY}}) {
 			print;
 		}
@@ -949,6 +1067,13 @@ sub patch_update_file {
 				$num = scalar @hunk;
 				next;
 			}
+			elsif ($line =~ /^e/) {
+				my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
+				if (defined $newhunk) {
+					splice @hunk, $ix, 1, $newhunk;
+					$need_recount = 1;
+				}
+			}
 			else {
 				help_patch_cmd($other);
 				next;
@@ -1002,7 +1127,8 @@ sub patch_update_file {
 	if (@result) {
 		my $fh;
 
-		open $fh, '| git apply --cached';
+		open $fh, '| git apply --cached'
+			. ($need_recount ? ' --recount' : '');
 		for (@{$head->{TEXT}}, @result) {
 			print $fh $_;
 		}
-- 
1.5.6.rc1.134.g8dfb.dirty


--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]

Messages in current thread:
[PATCH] git-add--interactive: manual hunk editing mode, Thomas Rast, (Fri May 23, 1:21 pm)
Re: [PATCH] git-add--interactive: manual hunk editing mode, Johannes Schindelin, (Thu May 29, 9:12 am)
Re: [PATCH] git-add--interactive: manual hunk editing mode, Johannes Schindelin, (Fri May 30, 2:49 am)
Re: [PATCH] git-add--interactive: manual hunk editing mode, Jakub Narebski, (Fri May 30, 3:46 am)
Re: [PATCH] git-add--interactive: manual hunk editing mode, Junio C Hamano, (Fri May 30, 2:35 pm)
Re: [RFC PATCH] git-add--interactive: manual hunk editing ..., Johannes Schindelin, (Thu Jun 5, 3:28 am)
Re: [RFC PATCH] git-add--interactive: manual hunk editing ..., Johannes Schindelin, (Fri Jun 6, 7:31 am)
Re: [RFC PATCH] git-add--interactive: manual hunk editing ..., Johannes Schindelin, (Sun Jun 8, 4:02 pm)
Re: [RFC PATCH] git-add--interactive: manual hunk editing ..., Johannes Schindelin, (Sun Jun 8, 4:06 pm)
Re: [PATCH v3] git-add--interactive: manual hunk editing mode, Johannes Schindelin, (Sun Jun 8, 4:19 pm)
Re: [PATCH v3] git-add--interactive: manual hunk editing mode, Johannes Schindelin, (Mon Jun 9, 9:13 am)
Re: [PATCH v3] git-add--interactive: manual hunk editing mode, Andreas Ericsson, (Tue Jun 10, 4:19 am)
Re: [PATCH v3] git-add--interactive: manual hunk editing mode, Thomas Rast, (Wed Jun 11, 2:02 am)
Re: [PATCH v4] git-add--interactive: manual hunk editing mode, Johannes Schindelin, (Mon Jun 23, 11:54 am)
apply --recount, was Re: [PATCH v4] git-add--interactive: ..., Johannes Schindelin, (Mon Jun 23, 2:16 pm)
[PATCH 0/3] Manual editing for 'add' and 'add -p', Thomas Rast, (Tue Jun 24, 12:07 pm)
Re: [PATCH 0/3] Manual editing for 'add' and 'add -p', Miklos Vajna, (Tue Jun 24, 12:53 pm)
Re: [PATCH 1/3] Allow git-apply to ignore the hunk headers ..., Johannes Schindelin, (Fri Jun 27, 10:43 am)