git-svn fix to avoid using strftime %z
[gitweb.git] / git-add--interactive.perl
index b0223c3419301132032fb67519a275e57707df22..5f129a42030917bc5dcab67aeaf67a5f00c17677 100755 (executable)
                $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) =
 
 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("", @_);
@@ -325,6 +343,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 +442,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++) {
@@ -758,11 +780,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 <STDIN>;
+       }
+}
+
 sub prompt_yesno {
        my ($prompt) = @_;
        while (1) {
                print colored $prompt_color, $prompt;
-               my $line = <STDIN>;
+               my $line = prompt_single_character;
                return 0 if $line =~ /^n/i;
                return 1 if $line =~ /^y/i;
        }
@@ -800,6 +843,8 @@ sub help_patch_cmd {
 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
@@ -836,6 +881,47 @@ sub patch_update_cmd {
        }
 }
 
+# Generate a one line summary of a hunk.
+sub summarize_hunk {
+       my $rhunk = shift;
+       my $summary = $rhunk->{TEXT}[0];
+
+       # Keep the line numbers, discard extra context.
+       $summary =~ s/@@(.*?)@@.*/$1 /s;
+       $summary .= " " x (20 - length $summary);
+
+       # Add some user context.
+       for my $line (@{$rhunk->{TEXT}}) {
+               if ($line =~ m/^[+-].*\w/) {
+                       $summary .= $line;
+                       last;
+               }
+       }
+
+       chomp $summary;
+       return substr($summary, 0, 80) . "\n";
+}
+
+
+# Print a one-line summary of each hunk in the array ref in
+# the first argument, starting wih the index in the 2nd.
+sub display_hunks {
+       my ($hunks, $i) = @_;
+       my $ctr = 0;
+       $i ||= 0;
+       for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
+               my $status = " ";
+               if (defined $hunks->[$i]{USE}) {
+                       $status = $hunks->[$i]{USE} ? "+" : "-";
+               }
+               printf "%s%2d: %s",
+                       $status,
+                       $i + 1,
+                       summarize_hunk($hunks->[$i]);
+       }
+       return $i;
+}
+
 sub patch_update_file {
        my ($ix, $num);
        my $path = shift;
@@ -850,7 +936,7 @@ sub patch_update_file {
                        print @{$mode->{DISPLAY}};
                        print colored $prompt_color,
                                "Stage mode change [y/n/a/d/?]? ";
-                       my $line = <STDIN>;
+                       my $line = prompt_single_character;
                        if ($line =~ /^y/i) {
                                $mode->{USE} = 1;
                                last;
@@ -887,22 +973,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';
                }
                for ($i = 0; $i < $num; $i++) {
                        if (!defined $hunk[$i]{USE}) {
@@ -913,14 +1002,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 = <STDIN>;
+               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;
@@ -937,6 +1026,31 @@ sub patch_update_file {
                                }
                                next;
                        }
+                       elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
+                               my $response = $1;
+                               my $no = $ix > 10 ? $ix - 10 : 0;
+                               while ($response eq '') {
+                                       my $extra = "";
+                                       $no = display_hunks(\@hunk, $no);
+                                       if ($no < $num) {
+                                               $extra = " (<ret> to see more)";
+                                       }
+                                       print "go to which hunk$extra? ";
+                                       $response = <STDIN>;
+                                       if (!defined $response) {
+                                               $response = '';
+                                       }
+                                       chomp $response;
+                               }
+                               if ($response !~ /^\s*\d+\s*$/) {
+                                       error_msg "Invalid number: '$response'\n";
+                               } elsif (0 < $response && $response <= $num) {
+                                       $ix = $response - 1;
+                               } else {
+                                       error_msg "Sorry, only $num hunks available.\n";
+                               }
+                               next;
+                       }
                        elsif ($line =~ /^d/i) {
                                while ($ix < $num) {
                                        if (!defined $hunk[$ix]{USE}) {
@@ -946,30 +1060,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 = <STDIN>;
+                                       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+, <STDIN> 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) {