diff options
Diffstat (limited to 'git-add--interactive.perl')
-rwxr-xr-x | git-add--interactive.perl | 203 |
1 files changed, 152 insertions, 51 deletions
diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 964c3a7542..20eb81cc92 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -205,8 +205,15 @@ my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path')) } } -sub get_empty_tree { - return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; +{ + my $empty_tree; + sub get_empty_tree { + return $empty_tree if defined $empty_tree; + + $empty_tree = run_cmd_pipe(qw(git hash-object -t tree /dev/null)); + chomp $empty_tree; + return $empty_tree; + } } sub get_diff_reference { @@ -677,7 +684,7 @@ sub add_untracked_cmd { sub run_git_apply { my $cmd = shift; my $fh; - open $fh, '| git ' . $cmd . " --recount --allow-overlap"; + open $fh, '| git ' . $cmd . " --allow-overlap"; print $fh @_; return close $fh; } @@ -705,6 +712,14 @@ sub parse_diff { } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; + if (@colored && @colored != @diff) { + print STDERR + "fatal: mismatched output from interactive.diffFilter\n", + "hint: Your filter must maintain a one-to-one correspondence\n", + "hint: between its input and output lines.\n"; + exit 1; + } + for (my $i = 0; $i < @diff; $i++) { if ($diff[$i] =~ /^@@ /) { push @hunk, { TEXT => [], DISPLAY => [], @@ -751,6 +766,15 @@ sub parse_hunk_header { return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); } +sub format_hunk_header { + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_; + return ("@@ -$o_ofs" . + (($o_cnt != 1) ? ",$o_cnt" : '') . + " +$n_ofs" . + (($n_cnt != 1) ? ",$n_cnt" : '') . + " @@\n"); +} + sub split_hunk { my ($text, $display) = @_; my @split = (); @@ -784,6 +808,11 @@ sub split_hunk { while (++$i < @$text) { my $line = $text->[$i]; my $display = $display->[$i]; + if ($line =~ /^\\/) { + push @{$this->{TEXT}}, $line; + push @{$this->{DISPLAY}}, $display; + next; + } if ($line =~ /^ /) { if ($this->{ADDDEL} && !defined $next_hunk_start) { @@ -838,11 +867,7 @@ sub split_hunk { my $o_cnt = $hunk->{OCNT}; my $n_cnt = $hunk->{NCNT}; - my $head = ("@@ -$o_ofs" . - (($o_cnt != 1) ? ",$o_cnt" : '') . - " +$n_ofs" . - (($n_cnt != 1) ? ",$n_cnt" : '') . - " @@\n"); + my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt); my $display_head = $head; unshift @{$hunk->{TEXT}}, $head; if ($diff_use_color) { @@ -886,6 +911,9 @@ sub merge_hunk { $n_cnt++; push @line, $line; next; + } elsif ($line =~ /^\\/) { + push @line, $line; + next; } last if ($o1_ofs <= $ofs); @@ -904,6 +932,9 @@ sub merge_hunk { $n_cnt++; push @line, $line; next; + } elsif ($line =~ /^\\/) { + push @line, $line; + next; } $ofs++; $o_cnt++; @@ -912,11 +943,7 @@ sub merge_hunk { } push @line, $line; } - my $head = ("@@ -$o0_ofs" . - (($o_cnt != 1) ? ",$o_cnt" : '') . - " +$n0_ofs" . - (($n_cnt != 1) ? ",$n_cnt" : '') . - " @@\n"); + my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt); @{$prev->{TEXT}} = ($head, @line); } @@ -925,14 +952,35 @@ sub coalesce_overlapping_hunks { my @out = (); my ($last_o_ctx, $last_was_dirty); + my $ofs_delta = 0; - for (grep { $_->{USE} } @in) { + for (@in) { if ($_->{TYPE} ne 'hunk') { push @out, $_; next; } my $text = $_->{TEXT}; - my ($o_ofs) = parse_hunk_header($text->[0]); + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = + parse_hunk_header($text->[0]); + unless ($_->{USE}) { + $ofs_delta += $o_cnt - $n_cnt; + # If this hunk has been edited then subtract + # the delta that is due to the edit. + if ($_->{OFS_DELTA}) { + $ofs_delta -= $_->{OFS_DELTA}; + } + next; + } + if ($ofs_delta) { + $n_ofs += $ofs_delta; + $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt, + $n_ofs, $n_cnt); + } + # If this hunk was edited then adjust the offset delta + # to reflect the edit. + if ($_->{OFS_DELTA}) { + $ofs_delta += $_->{OFS_DELTA}; + } if (defined $last_o_ctx && $o_ofs <= $last_o_ctx && !$_->{DIRTY} && @@ -1004,6 +1052,30 @@ marked for discarding."), marked for applying."), ); +sub recount_edited_hunk { + local $_; + my ($oldtext, $newtext) = @_; + my ($o_cnt, $n_cnt) = (0, 0); + for (@{$newtext}[1..$#{$newtext}]) { + my $mode = substr($_, 0, 1); + if ($mode eq '-') { + $o_cnt++; + } elsif ($mode eq '+') { + $n_cnt++; + } elsif ($mode eq ' ' or $mode eq "\n") { + $o_cnt++; + $n_cnt++; + } + } + my ($o_ofs, undef, $n_ofs, undef) = + parse_hunk_header($newtext->[0]); + $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt); + my (undef, $orig_o_cnt, undef, $orig_n_cnt) = + parse_hunk_header($oldtext->[0]); + # Return the change in the number of lines inserted by this hunk + return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt; +} + sub edit_hunk_manually { my ($oldtext) = @_; @@ -1102,25 +1174,32 @@ sub prompt_yesno { } sub edit_hunk_loop { - my ($head, $hunk, $ix) = @_; - my $text = $hunk->[$ix]->{TEXT}; + my ($head, $hunks, $ix) = @_; + my $hunk = $hunks->[$ix]; + my $text = $hunk->{TEXT}; while (1) { - $text = edit_hunk_manually($text); - if (!defined $text) { + my $newtext = edit_hunk_manually($text); + if (!defined $newtext) { return undef; } my $newhunk = { - TEXT => $text, - TYPE => $hunk->[$ix]->{TYPE}, + TEXT => $newtext, + TYPE => $hunk->{TYPE}, USE => 1, DIRTY => 1, }; + $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext); + # If this hunk has already been edited then add the + # offset delta of the previous edit to get the real + # delta from the original unedited hunk. + $hunk->{OFS_DELTA} and + $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA}; if (diff_applies($head, - @{$hunk}[0..$ix-1], + @{$hunks}[0..$ix-1], $newhunk, - @{$hunk}[$ix+1..$#{$hunk}])) { - $newhunk->{DISPLAY} = [color_diff(@{$text})]; + @{$hunks}[$ix+1..$#{$hunks}])) { + $newhunk->{DISPLAY} = [color_diff(@{$newtext})]; return $newhunk; } else { @@ -1184,7 +1263,13 @@ d - do not apply this hunk or any of the later hunks in the file"), ); sub help_patch_cmd { - print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", __ <<EOF ; + local $_; + my $other = $_[0] . ",?"; + print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", + map { "$_\n" } grep { + my $c = quotemeta(substr($_, 0, 1)); + $other =~ /,$c/ + } split "\n", __ <<EOF ; g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -1302,39 +1387,39 @@ sub display_hunks { my %patch_update_prompt_modes = ( stage => { - mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "), - deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "), - hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "), + mode => N__("Stage mode change [y,n,q,a,d%s,?]? "), + deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "), + hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "), }, stash => { - mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "), - deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "), - hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "), + mode => N__("Stash mode change [y,n,q,a,d%s,?]? "), + deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "), + hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "), }, reset_head => { - mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "), - deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "), - hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "), + mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "), + deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "), + hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "), }, reset_nothead => { - mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "), - deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "), - hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "), + mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "), + deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "), + hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "), }, checkout_index => { - mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "), - deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "), - hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "), + mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "), + deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "), + hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), }, checkout_head => { - mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "), - deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "), - hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "), + mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), + deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "), + hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), }, checkout_nothead => { - mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "), - deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "), - hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "), + mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), + deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "), + hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), }, ); @@ -1390,7 +1475,7 @@ sub patch_update_file { $other .= ',J'; } if ($num > 1) { - $other .= ',g'; + $other .= ',g,/'; } for ($i = 0; $i < $num; $i++) { if (!defined $hunk[$i]{USE}) { @@ -1431,8 +1516,12 @@ sub patch_update_file { } next; } - elsif ($other =~ /g/ && $line =~ /^g(.*)/) { + elsif ($line =~ /^g(.*)/) { my $response = $1; + unless ($other =~ /g/) { + error_msg __("No other hunks to goto\n"); + next; + } my $no = $ix > 10 ? $ix - 10 : 0; while ($response eq '') { $no = display_hunks(\@hunk, $no); @@ -1478,7 +1567,11 @@ sub patch_update_file { } elsif ($line =~ m|^/(.*)|) { my $regex = $1; - if ($1 eq "") { + unless ($other =~ m|/|) { + error_msg __("No other hunks to search\n"); + next; + } + if ($regex eq "") { print colored $prompt_color, __("search for regex? "); $regex = <STDIN>; if (defined $regex) { @@ -1546,7 +1639,11 @@ sub patch_update_file { next; } } - elsif ($other =~ /s/ && $line =~ /^s/) { + elsif ($line =~ /^s/) { + unless ($other =~ /s/) { + error_msg __("Sorry, cannot split this hunk\n"); + next; + } my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); if (1 < @split) { print colored $header_color, sprintf( @@ -1558,7 +1655,11 @@ sub patch_update_file { $num = scalar @hunk; next; } - elsif ($other =~ /e/ && $line =~ /^e/) { + elsif ($line =~ /^e/) { + unless ($other =~ /e/) { + error_msg __("Sorry, cannot edit this hunk\n"); + next; + } my $newhunk = edit_hunk_loop($head, \@hunk, $ix); if (defined $newhunk) { splice @hunk, $ix, 1, $newhunk; |