X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=inline;f=git-add--interactive.perl;h=def062a9e2cbc67b78522aa47151d47cd50f0f33;hb=87d2062b3984af5347519c38877b99bce06e6e8d;hp=ca60356d0082123cd1e9572572528d8265be43a3;hpb=4c6e8aa8f001fa9619dc891c501d0d4389583353;p=git.git diff --git a/git-add--interactive.perl b/git-add--interactive.perl index ca60356d0..def062a9e 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -3,6 +3,8 @@ use strict; use Git; +binmode(STDOUT, ":raw"); + my $repo = Git->repository(); my $menu_use_color = $repo->get_colorbool('color.interactive'); @@ -12,6 +14,13 @@ my ($prompt_color, $header_color, $help_color) = $repo->get_color('color.interactive.header', 'bold'), $repo->get_color('color.interactive.help', 'red bold'), ) : (); +my $error_color = (); +if ($menu_use_color) { + my $help_color_spec = ($repo->config('color.interactive.help') or + 'red bold'); + $error_color = $repo->get_color('color.interactive.error', + $help_color_spec); +} my $diff_use_color = $repo->get_colorbool('color.diff'); my ($fraginfo_color) = @@ -33,6 +42,17 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); +my $use_readkey = 0; +sub ReadMode; +sub ReadKey; +if ($repo->config_bool("interactive.singlekey")) { + eval { + require Term::ReadKey; + Term::ReadKey->import; + $use_readkey = 1; + }; +} + sub colored { my $color = shift; my $string = join("", @_); @@ -73,6 +93,47 @@ if (!defined $GIT_DIR) { } chomp($GIT_DIR); +my %cquote_map = ( + "b" => chr(8), + "t" => chr(9), + "n" => chr(10), + "v" => chr(11), + "f" => chr(12), + "r" => chr(13), + "\\" => "\\", + "\042" => "\042", +); + +sub unquote_path { + local ($_) = @_; + my ($retval, $remainder); + if (!/^\042(.*)\042$/) { + return $_; + } + ($_, $retval) = ($1, ""); + while (/^([^\\]*)\\(.*)$/) { + $remainder = $2; + $retval .= $1; + for ($remainder) { + if (/^([0-3][0-7][0-7])(.*)$/) { + $retval .= chr(oct($1)); + $_ = $2; + last; + } + if (/^([\\\042btnvfr])(.*)$/) { + $retval .= $cquote_map{$1}; + $_ = $2; + last; + } + # This is malformed -- just return it as-is for now. + return $_[0]; + } + $_ = $remainder; + } + $retval .= $_; + return $retval; +} + sub refresh { my $fh; open $fh, 'git update-index --refresh |' @@ -86,7 +147,7 @@ sub refresh { sub list_untracked { map { chomp $_; - $_; + unquote_path($_); } run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); } @@ -123,7 +184,8 @@ sub list_modified { if (@ARGV) { @tracked = map { - chomp $_; $_; + chomp $_; + unquote_path($_); } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV); return if (!@tracked); } @@ -135,6 +197,7 @@ sub list_modified { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { my ($change, $bin); + $file = unquote_path($file); if ($add eq '-' && $del eq '-') { $change = 'binary'; $bin = 1; @@ -150,6 +213,7 @@ sub list_modified { } elsif (($adddel, $file) = /^ (create|delete) mode [0-7]+ (.*)$/) { + $file = unquote_path($file); $data{$file}{INDEX_ADDDEL} = $adddel; } } @@ -157,6 +221,7 @@ sub list_modified { for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { + $file = unquote_path($file); if (!exists $data{$file}) { $data{$file} = +{ INDEX => 'unchanged', @@ -178,6 +243,7 @@ sub list_modified { } elsif (($adddel, $file) = /^ (create|delete) mode [0-7]+ (.*)$/) { + $file = unquote_path($file); $data{$file}{FILE_ADDDEL} = $adddel; } } @@ -284,7 +350,8 @@ sub find_unique_prefixes { } %search = %{$search{$letter}}; } - if ($soft_limit && $j + 1 > $soft_limit) { + if (ord($letters[0]) > 127 || + ($soft_limit && $j + 1 > $soft_limit)) { $prefix = undef; $remainder = $ret; } @@ -325,6 +392,10 @@ sub highlight_prefix { return "$prompt_color$prefix$normal_color$remainder"; } +sub error_msg { + print STDERR colored $error_color, @_; +} + sub list_and_choose { my ($opts, @stuff) = @_; my (@chosen, @return); @@ -420,12 +491,12 @@ sub list_and_choose { else { $bottom = $top = find_unique($choice, @stuff); if (!defined $bottom) { - print "Huh ($choice)?\n"; + error_msg "Huh ($choice)?\n"; next TOPLOOP; } } if ($opts->{SINGLETON} && $bottom != $top) { - print "Huh ($choice)?\n"; + error_msg "Huh ($choice)?\n"; next TOPLOOP; } for ($i = $bottom-1; $i <= $top-1; $i++) { @@ -731,6 +802,10 @@ EOF || $ENV{VISUAL} || $ENV{EDITOR} || "vi"; system('sh', '-c', $editor.' "$@"', $editor, $hunkfile); + if ($? != 0) { + return undef; + } + open $fh, '<', $hunkfile or die "failed to open hunk edit file for reading: " . $!; my @newtext = grep { !/^#/ } <$fh>; @@ -758,11 +833,32 @@ sub diff_applies { return close $fh; } +sub _restore_terminal_and_die { + ReadMode 'restore'; + print "\n"; + exit 1; +} + +sub prompt_single_character { + if ($use_readkey) { + local $SIG{TERM} = \&_restore_terminal_and_die; + local $SIG{INT} = \&_restore_terminal_and_die; + ReadMode 'cbreak'; + my $key = ReadKey 0; + ReadMode 'restore'; + print "$key" if defined $key; + print "\n"; + return $key; + } else { + return ; + } +} + sub prompt_yesno { my ($prompt) = @_; while (1) { print colored $prompt_color, $prompt; - my $line = ; + my $line = prompt_single_character; return 0 if $line =~ /^n/i; return 1 if $line =~ /^y/i; } @@ -801,6 +897,7 @@ n - do not stage this hunk 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 g - select a hunk to go to +/ - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk J - leave this hunk undecided, see next hunk k - leave this hunk undecided, see previous undecided hunk @@ -892,7 +989,7 @@ sub patch_update_file { print @{$mode->{DISPLAY}}; print colored $prompt_color, "Stage mode change [y/n/a/d/?]? "; - my $line = ; + my $line = prompt_single_character; if ($line =~ /^y/i) { $mode->{USE} = 1; last; @@ -929,25 +1026,25 @@ sub patch_update_file { for ($i = 0; $i < $ix; $i++) { if (!defined $hunk[$i]{USE}) { $prev = 1; - $other .= '/k'; + $other .= ',k'; last; } } if ($ix) { - $other .= '/K'; + $other .= ',K'; } for ($i = $ix + 1; $i < $num; $i++) { if (!defined $hunk[$i]{USE}) { $next = 1; - $other .= '/j'; + $other .= ',j'; last; } } if ($ix < $num - 1) { - $other .= '/J'; + $other .= ',J'; } if ($num > 1) { - $other .= '/g'; + $other .= ',g'; } for ($i = 0; $i < $num; $i++) { if (!defined $hunk[$i]{USE}) { @@ -958,14 +1055,14 @@ sub patch_update_file { last if (!$undecided); if (hunk_splittable($hunk[$ix]{TEXT})) { - $other .= '/s'; + $other .= ',s'; } - $other .= '/e'; + $other .= ',e'; for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, "Stage this hunk [y/n/a/d$other/?]? "; - my $line = ; + print colored $prompt_color, "Stage this hunk [y,n,a,d,/$other,?]? "; + my $line = prompt_single_character; if ($line) { if ($line =~ /^y/i) { $hunk[$ix]{USE} = 1; @@ -993,14 +1090,17 @@ sub patch_update_file { } print "go to which hunk$extra? "; $response = ; + if (!defined $response) { + $response = ''; + } chomp $response; } if ($response !~ /^\s*\d+\s*$/) { - print STDERR "Invalid number: '$response'\n"; + error_msg "Invalid number: '$response'\n"; } elsif (0 < $response && $response <= $num) { $ix = $response - 1; } else { - print STDERR "Sorry, only $num hunks available.\n"; + error_msg "Sorry, only $num hunks available.\n"; } next; } @@ -1013,30 +1113,76 @@ sub patch_update_file { } next; } - elsif ($other =~ /K/ && $line =~ /^K/) { - $ix--; - next; - } - elsif ($other =~ /J/ && $line =~ /^J/) { - $ix++; + elsif ($line =~ m|^/(.*)|) { + my $regex = $1; + if ($1 eq "") { + print colored $prompt_color, "search for regex? "; + $regex = ; + if (defined $regex) { + chomp $regex; + } + } + my $search_string; + eval { + $search_string = qr{$regex}m; + }; + if ($@) { + my ($err,$exp) = ($@, $1); + $err =~ s/ at .*git-add--interactive line \d+, line \d+.*$//; + error_msg "Malformed search regexp $exp: $err\n"; + next; + } + my $iy = $ix; + while (1) { + my $text = join ("", @{$hunk[$iy]{TEXT}}); + last if ($text =~ $search_string); + $iy++; + $iy = 0 if ($iy >= $num); + if ($ix == $iy) { + error_msg "No hunk matches the given pattern\n"; + last; + } + } + $ix = $iy; next; } - elsif ($other =~ /k/ && $line =~ /^k/) { - while (1) { + elsif ($line =~ /^K/) { + if ($other =~ /K/) { $ix--; - last if (!$ix || - !defined $hunk[$ix]{USE}); + } + else { + error_msg "No previous hunk\n"; } next; } - elsif ($other =~ /j/ && $line =~ /^j/) { - while (1) { + elsif ($line =~ /^J/) { + if ($other =~ /J/) { $ix++; - last if ($ix >= $num || - !defined $hunk[$ix]{USE}); + } + else { + error_msg "No next hunk\n"; + } + next; + } + elsif ($line =~ /^k/) { + if ($other =~ /k/) { + while (1) { + $ix--; + last if (!$ix || + !defined $hunk[$ix]{USE}); + } + } + else { + error_msg "No previous hunk\n"; } next; } + elsif ($line =~ /^j/) { + if ($other !~ /j/) { + error_msg "No next hunk\n"; + next; + } + } elsif ($other =~ /s/ && $line =~ /^s/) { my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); if (1 < @split) {