1818 $diff_use_color ? (
1919 $repo -> get_color(' color.diff.frag' , ' cyan' ),
2020 ) : ();
21+ my ($diff_plain_color ) =
22+ $diff_use_color ? (
23+ $repo -> get_color(' color.diff.plain' , ' ' ),
24+ ) : ();
25+ my ($diff_old_color ) =
26+ $diff_use_color ? (
27+ $repo -> get_color(' color.diff.old' , ' red' ),
28+ ) : ();
29+ my ($diff_new_color ) =
30+ $diff_use_color ? (
31+ $repo -> get_color(' color.diff.new' , ' green' ),
32+ ) : ();
2133
2234my $normal_color = $repo -> get_color(" " , " reset" );
2335
@@ -682,92 +694,104 @@ sub split_hunk {
682694 return @split ;
683695}
684696
685- sub find_last_o_ctx {
686- my ($it ) = @_ ;
687- my $text = $it -> {TEXT };
688- my ($o_ofs , $o_cnt ) = parse_hunk_header($text -> [0]);
689- my $i = @{$text };
690- my $last_o_ctx = $o_ofs + $o_cnt ;
691- while (0 < --$i ) {
692- my $line = $text -> [$i ];
693- if ($line =~ / ^ / ) {
694- $last_o_ctx --;
695- next ;
696- }
697- last ;
698- }
699- return $last_o_ctx ;
697+
698+ sub color_diff {
699+ return map {
700+ colored((/ ^@/ ? $fraginfo_color :
701+ / ^\+ / ? $diff_new_color :
702+ / ^-/ ? $diff_old_color :
703+ $diff_plain_color ),
704+ $_ );
705+ } @_ ;
700706}
701707
702- sub merge_hunk {
703- my ($prev , $this ) = @_ ;
704- my ($o0_ofs , $o0_cnt , $n0_ofs , $n0_cnt ) =
705- parse_hunk_header($prev -> {TEXT }[0]);
706- my ($o1_ofs , $o1_cnt , $n1_ofs , $n1_cnt ) =
707- parse_hunk_header($this -> {TEXT }[0]);
708-
709- my (@line , $i , $ofs , $o_cnt , $n_cnt );
710- $ofs = $o0_ofs ;
711- $o_cnt = $n_cnt = 0;
712- for ($i = 1; $i < @{$prev -> {TEXT }}; $i ++) {
713- my $line = $prev -> {TEXT }[$i ];
714- if ($line =~ / ^\+ / ) {
715- $n_cnt ++;
716- push @line , $line ;
717- next ;
718- }
708+ sub edit_hunk_manually {
709+ my ($oldtext ) = @_ ;
719710
720- last if ($o1_ofs <= $ofs );
711+ my $hunkfile = $repo -> repo_path . " /addp-hunk-edit.diff" ;
712+ my $fh ;
713+ open $fh , ' >' , $hunkfile
714+ or die " failed to open hunk edit file for writing: " . $! ;
715+ print $fh " # Manual hunk edit mode -- see bottom for a quick guide\n " ;
716+ print $fh @$oldtext ;
717+ print $fh <<EOF ;
718+ # ---
719+ # To remove '-' lines, make them ' ' lines (context).
720+ # To remove '+' lines, delete them.
721+ # Lines starting with # will be removed.
722+ #
723+ # If the patch applies cleanly, the edited hunk will immediately be
724+ # marked for staging. If it does not apply cleanly, you will be given
725+ # an opportunity to edit again. If all lines of the hunk are removed,
726+ # then the edit is aborted and the hunk is left unchanged.
727+ EOF
728+ close $fh ;
721729
722- $o_cnt ++;
723- $ofs ++;
724- if ($line =~ / ^ / ) {
725- $n_cnt ++;
726- }
727- push @line , $line ;
730+ my $editor = $ENV {GIT_EDITOR } || $repo -> config(" core.editor" )
731+ || $ENV {VISUAL } || $ENV {EDITOR } || " vi" ;
732+ system (' sh' , ' -c' , $editor .' "$@"' , $editor , $hunkfile );
733+
734+ open $fh , ' <' , $hunkfile
735+ or die " failed to open hunk edit file for reading: " . $! ;
736+ my @newtext = grep { !/^# / } <$fh>;
737+ close $fh ;
738+ unlink $hunkfile ;
739+
740+ # Abort if nothing remains
741+ if (!grep { / \S / } @newtext ) {
742+ return undef ;
728743 }
729744
730- for ($i = 1; $i < @{$this -> {TEXT }}; $i ++) {
731- my $line = $this -> {TEXT }[$i ];
732- if ($line =~ / ^\+ / ) {
733- $n_cnt ++;
734- push @line , $line ;
735- next ;
736- }
737- $ofs ++;
738- $o_cnt ++;
739- if ($line =~ / ^ / ) {
740- $n_cnt ++;
741- }
742- push @line , $line ;
745+ # Reinsert the first hunk header if the user accidentally deleted it
746+ if ($newtext [0] !~ / ^@/ ) {
747+ unshift @newtext , $oldtext -> [0];
748+ }
749+ return \@newtext ;
750+ }
751+
752+ sub diff_applies {
753+ my $fh ;
754+ open $fh , ' | git apply --recount --cached --check' ;
755+ for my $h (@_ ) {
756+ print $fh @{$h -> {TEXT }};
743757 }
744- my $head = (" @@ -$o0_ofs " .
745- (($o_cnt != 1) ? " ,$o_cnt " : ' ' ) .
746- " +$n0_ofs " .
747- (($n_cnt != 1) ? " ,$n_cnt " : ' ' ) .
748- " @@\n " );
749- @{$prev -> {TEXT }} = ($head , @line );
758+ return close $fh ;
750759}
751760
752- sub coalesce_overlapping_hunks {
753- my (@in ) = @_ ;
754- my @out = ();
761+ sub prompt_yesno {
762+ my ($prompt ) = @_ ;
763+ while (1) {
764+ print colored $prompt_color , $prompt ;
765+ my $line = <STDIN >;
766+ return 0 if $line =~ / ^n/i ;
767+ return 1 if $line =~ / ^y/i ;
768+ }
769+ }
755770
756- my ($last_o_ctx );
771+ sub edit_hunk_loop {
772+ my ($head , $hunk , $ix ) = @_ ;
773+ my $text = $hunk -> [$ix ]-> {TEXT };
757774
758- for (grep { $_ -> {USE } } @in ) {
759- my $text = $_ -> {TEXT };
760- my ($o_ofs ) = parse_hunk_header($text -> [0]);
761- if (defined $last_o_ctx &&
762- $o_ofs <= $last_o_ctx ) {
763- merge_hunk($out [-1], $_ );
775+ while (1) {
776+ $text = edit_hunk_manually($text );
777+ if (!defined $text ) {
778+ return undef ;
779+ }
780+ my $newhunk = { TEXT => $text , USE => 1 };
781+ if (diff_applies($head ,
782+ @{$hunk }[0..$ix -1],
783+ $newhunk ,
784+ @{$hunk }[$ix +1..$# {$hunk }])) {
785+ $newhunk -> {DISPLAY } = [color_diff(@{$text })];
786+ return $newhunk ;
764787 }
765788 else {
766- push @out , $_ ;
789+ prompt_yesno(
790+ ' Your edited hunk does not apply. Edit again '
791+ . ' (saying "no" discards!) [y/n]? '
792+ ) or return undef ;
767793 }
768- $last_o_ctx = find_last_o_ctx($out [-1]);
769794 }
770- return @out ;
771795}
772796
773797sub help_patch_cmd {
@@ -781,6 +805,7 @@ sub help_patch_cmd {
781805k - leave this hunk undecided, see previous undecided hunk
782806K - leave this hunk undecided, see previous hunk
783807s - split the current hunk into smaller hunks
808+ e - manually edit the current hunk
784809? - print help
785810EOF
786811}
@@ -885,6 +910,7 @@ sub patch_update_file {
885910 if (hunk_splittable($hunk [$ix ]{TEXT })) {
886911 $other .= ' /s' ;
887912 }
913+ $other .= ' /e' ;
888914 for (@{$hunk [$ix ]{DISPLAY }}) {
889915 print ;
890916 }
@@ -949,6 +975,12 @@ sub patch_update_file {
949975 $num = scalar @hunk ;
950976 next ;
951977 }
978+ elsif ($line =~ / ^e/ ) {
979+ my $newhunk = edit_hunk_loop($head , \@hunk , $ix );
980+ if (defined $newhunk ) {
981+ splice @hunk , $ix , 1, $newhunk ;
982+ }
983+ }
952984 else {
953985 help_patch_cmd($other );
954986 next ;
@@ -962,47 +994,21 @@ sub patch_update_file {
962994 }
963995 }
964996
965- @hunk = coalesce_overlapping_hunks(@hunk );
966-
967997 my $n_lofs = 0;
968998 my @result = ();
969999 if ($mode -> {USE }) {
9701000 push @result , @{$mode -> {TEXT }};
9711001 }
9721002 for (@hunk ) {
973- my $text = $_ -> {TEXT };
974- my ($o_ofs , $o_cnt , $n_ofs , $n_cnt ) =
975- parse_hunk_header($text -> [0]);
976-
977- if (!$_ -> {USE }) {
978- # We would have added ($n_cnt - $o_cnt) lines
979- # to the postimage if we were to use this hunk,
980- # but we didn't. So the line number that the next
981- # hunk starts at would be shifted by that much.
982- $n_lofs -= ($n_cnt - $o_cnt );
983- next ;
984- }
985- else {
986- if ($n_lofs ) {
987- $n_ofs += $n_lofs ;
988- $text -> [0] = (" @@ -$o_ofs " .
989- (($o_cnt != 1)
990- ? " ,$o_cnt " : ' ' ) .
991- " +$n_ofs " .
992- (($n_cnt != 1)
993- ? " ,$n_cnt " : ' ' ) .
994- " @@\n " );
995- }
996- for (@$text ) {
997- push @result , $_ ;
998- }
1003+ if ($_ -> {USE }) {
1004+ push @result , @{$_ -> {TEXT }};
9991005 }
10001006 }
10011007
10021008 if (@result ) {
10031009 my $fh ;
10041010
1005- open $fh , ' | git apply --cached' ;
1011+ open $fh , ' | git apply --cached --recount ' ;
10061012 for (@{$head -> {TEXT }}, @result ) {
10071013 print $fh $_ ;
10081014 }
0 commit comments