Merge branch 'ak/sequencer-fix-find-uniq-abbrev'
[git] / git-add--interactive.perl
1 #!/usr/bin/perl
2
3 use 5.008;
4 use strict;
5 use warnings;
6 use Git qw(unquote_path);
7 use Git::I18N;
8
9 binmode(STDOUT, ":raw");
10
11 my $repo = Git->repository();
12
13 my $menu_use_color = $repo->get_colorbool('color.interactive');
14 my ($prompt_color, $header_color, $help_color) =
15         $menu_use_color ? (
16                 $repo->get_color('color.interactive.prompt', 'bold blue'),
17                 $repo->get_color('color.interactive.header', 'bold'),
18                 $repo->get_color('color.interactive.help', 'red bold'),
19         ) : ();
20 my $error_color = ();
21 if ($menu_use_color) {
22         my $help_color_spec = ($repo->config('color.interactive.help') or
23                                 'red bold');
24         $error_color = $repo->get_color('color.interactive.error',
25                                         $help_color_spec);
26 }
27
28 my $diff_use_color = $repo->get_colorbool('color.diff');
29 my ($fraginfo_color) =
30         $diff_use_color ? (
31                 $repo->get_color('color.diff.frag', 'cyan'),
32         ) : ();
33 my ($diff_plain_color) =
34         $diff_use_color ? (
35                 $repo->get_color('color.diff.plain', ''),
36         ) : ();
37 my ($diff_old_color) =
38         $diff_use_color ? (
39                 $repo->get_color('color.diff.old', 'red'),
40         ) : ();
41 my ($diff_new_color) =
42         $diff_use_color ? (
43                 $repo->get_color('color.diff.new', 'green'),
44         ) : ();
45
46 my $normal_color = $repo->get_color("", "reset");
47
48 my $diff_algorithm = $repo->config('diff.algorithm');
49 my $diff_filter = $repo->config('interactive.difffilter');
50
51 my $use_readkey = 0;
52 my $use_termcap = 0;
53 my %term_escapes;
54
55 sub ReadMode;
56 sub ReadKey;
57 if ($repo->config_bool("interactive.singlekey")) {
58         eval {
59                 require Term::ReadKey;
60                 Term::ReadKey->import;
61                 $use_readkey = 1;
62         };
63         if (!$use_readkey) {
64                 print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n";
65         }
66         eval {
67                 require Term::Cap;
68                 my $termcap = Term::Cap->Tgetent;
69                 foreach (values %$termcap) {
70                         $term_escapes{$_} = 1 if /^\e/;
71                 }
72                 $use_termcap = 1;
73         };
74 }
75
76 sub colored {
77         my $color = shift;
78         my $string = join("", @_);
79
80         if (defined $color) {
81                 # Put a color code at the beginning of each line, a reset at the end
82                 # color after newlines that are not at the end of the string
83                 $string =~ s/(\n+)(.)/$1$color$2/g;
84                 # reset before newlines
85                 $string =~ s/(\n+)/$normal_color$1/g;
86                 # codes at beginning and end (if necessary):
87                 $string =~ s/^/$color/;
88                 $string =~ s/$/$normal_color/ unless $string =~ /\n$/;
89         }
90         return $string;
91 }
92
93 # command line options
94 my $patch_mode_only;
95 my $patch_mode;
96 my $patch_mode_revision;
97
98 sub apply_patch;
99 sub apply_patch_for_checkout_commit;
100 sub apply_patch_for_stash;
101
102 my %patch_modes = (
103         'stage' => {
104                 DIFF => 'diff-files -p',
105                 APPLY => sub { apply_patch 'apply --cached', @_; },
106                 APPLY_CHECK => 'apply --cached',
107                 FILTER => 'file-only',
108                 IS_REVERSE => 0,
109         },
110         'stash' => {
111                 DIFF => 'diff-index -p HEAD',
112                 APPLY => sub { apply_patch 'apply --cached', @_; },
113                 APPLY_CHECK => 'apply --cached',
114                 FILTER => undef,
115                 IS_REVERSE => 0,
116         },
117         'reset_head' => {
118                 DIFF => 'diff-index -p --cached',
119                 APPLY => sub { apply_patch 'apply -R --cached', @_; },
120                 APPLY_CHECK => 'apply -R --cached',
121                 FILTER => 'index-only',
122                 IS_REVERSE => 1,
123         },
124         'reset_nothead' => {
125                 DIFF => 'diff-index -R -p --cached',
126                 APPLY => sub { apply_patch 'apply --cached', @_; },
127                 APPLY_CHECK => 'apply --cached',
128                 FILTER => 'index-only',
129                 IS_REVERSE => 0,
130         },
131         'checkout_index' => {
132                 DIFF => 'diff-files -p',
133                 APPLY => sub { apply_patch 'apply -R', @_; },
134                 APPLY_CHECK => 'apply -R',
135                 FILTER => 'file-only',
136                 IS_REVERSE => 1,
137         },
138         'checkout_head' => {
139                 DIFF => 'diff-index -p',
140                 APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
141                 APPLY_CHECK => 'apply -R',
142                 FILTER => undef,
143                 IS_REVERSE => 1,
144         },
145         'checkout_nothead' => {
146                 DIFF => 'diff-index -R -p',
147                 APPLY => sub { apply_patch_for_checkout_commit '', @_ },
148                 APPLY_CHECK => 'apply',
149                 FILTER => undef,
150                 IS_REVERSE => 0,
151         },
152         'worktree_head' => {
153                 DIFF => 'diff-index -p',
154                 APPLY => sub { apply_patch 'apply -R', @_ },
155                 APPLY_CHECK => 'apply -R',
156                 FILTER => undef,
157                 IS_REVERSE => 1,
158         },
159         'worktree_nothead' => {
160                 DIFF => 'diff-index -R -p',
161                 APPLY => sub { apply_patch 'apply', @_ },
162                 APPLY_CHECK => 'apply',
163                 FILTER => undef,
164                 IS_REVERSE => 0,
165         },
166 );
167
168 $patch_mode = 'stage';
169 my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
170
171 sub run_cmd_pipe {
172         if ($^O eq 'MSWin32') {
173                 my @invalid = grep {m/[":*]/} @_;
174                 die "$^O does not support: @invalid\n" if @invalid;
175                 my @args = map { m/ /o ? "\"$_\"": $_ } @_;
176                 return qx{@args};
177         } else {
178                 my $fh = undef;
179                 open($fh, '-|', @_) or die;
180                 my @out = <$fh>;
181                 close $fh || die "Cannot close @_ ($!)";
182                 return @out;
183         }
184 }
185
186 my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
187
188 if (!defined $GIT_DIR) {
189         exit(1); # rev-parse would have already said "not a git repo"
190 }
191 chomp($GIT_DIR);
192
193 sub refresh {
194         my $fh;
195         open $fh, 'git update-index --refresh |'
196             or die;
197         while (<$fh>) {
198                 ;# ignore 'needs update'
199         }
200         close $fh;
201 }
202
203 sub list_untracked {
204         map {
205                 chomp $_;
206                 unquote_path($_);
207         }
208         run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
209 }
210
211 # TRANSLATORS: you can adjust this to align "git add -i" status menu
212 my $status_fmt = __('%12s %12s %s');
213 my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
214
215 {
216         my $initial;
217         sub is_initial_commit {
218                 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
219                         unless defined $initial;
220                 return $initial;
221         }
222 }
223
224 {
225         my $empty_tree;
226         sub get_empty_tree {
227                 return $empty_tree if defined $empty_tree;
228
229                 ($empty_tree) = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
230                 chomp $empty_tree;
231                 return $empty_tree;
232         }
233 }
234
235 sub get_diff_reference {
236         my $ref = shift;
237         if (defined $ref and $ref ne 'HEAD') {
238                 return $ref;
239         } elsif (is_initial_commit()) {
240                 return get_empty_tree();
241         } else {
242                 return 'HEAD';
243         }
244 }
245
246 # Returns list of hashes, contents of each of which are:
247 # VALUE:        pathname
248 # BINARY:       is a binary path
249 # INDEX:        is index different from HEAD?
250 # FILE:         is file different from index?
251 # INDEX_ADDDEL: is it add/delete between HEAD and index?
252 # FILE_ADDDEL:  is it add/delete between index and file?
253 # UNMERGED:     is the path unmerged
254
255 sub list_modified {
256         my ($only) = @_;
257         my (%data, @return);
258         my ($add, $del, $adddel, $file);
259
260         my $reference = get_diff_reference($patch_mode_revision);
261         for (run_cmd_pipe(qw(git diff-index --cached
262                              --numstat --summary), $reference,
263                              '--', @ARGV)) {
264                 if (($add, $del, $file) =
265                     /^([-\d]+)  ([-\d]+)        (.*)/) {
266                         my ($change, $bin);
267                         $file = unquote_path($file);
268                         if ($add eq '-' && $del eq '-') {
269                                 $change = __('binary');
270                                 $bin = 1;
271                         }
272                         else {
273                                 $change = "+$add/-$del";
274                         }
275                         $data{$file} = {
276                                 INDEX => $change,
277                                 BINARY => $bin,
278                                 FILE => __('nothing'),
279                         }
280                 }
281                 elsif (($adddel, $file) =
282                        /^ (create|delete) mode [0-7]+ (.*)$/) {
283                         $file = unquote_path($file);
284                         $data{$file}{INDEX_ADDDEL} = $adddel;
285                 }
286         }
287
288         for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
289                 if (($add, $del, $file) =
290                     /^([-\d]+)  ([-\d]+)        (.*)/) {
291                         $file = unquote_path($file);
292                         my ($change, $bin);
293                         if ($add eq '-' && $del eq '-') {
294                                 $change = __('binary');
295                                 $bin = 1;
296                         }
297                         else {
298                                 $change = "+$add/-$del";
299                         }
300                         $data{$file}{FILE} = $change;
301                         if ($bin) {
302                                 $data{$file}{BINARY} = 1;
303                         }
304                 }
305                 elsif (($adddel, $file) =
306                        /^ (create|delete) mode [0-7]+ (.*)$/) {
307                         $file = unquote_path($file);
308                         $data{$file}{FILE_ADDDEL} = $adddel;
309                 }
310                 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
311                         $file = unquote_path($2);
312                         if (!exists $data{$file}) {
313                                 $data{$file} = +{
314                                         INDEX => __('unchanged'),
315                                         BINARY => 0,
316                                 };
317                         }
318                         if ($1 eq 'U') {
319                                 $data{$file}{UNMERGED} = 1;
320                         }
321                 }
322         }
323
324         for (sort keys %data) {
325                 my $it = $data{$_};
326
327                 if ($only) {
328                         if ($only eq 'index-only') {
329                                 next if ($it->{INDEX} eq __('unchanged'));
330                         }
331                         if ($only eq 'file-only') {
332                                 next if ($it->{FILE} eq __('nothing'));
333                         }
334                 }
335                 push @return, +{
336                         VALUE => $_,
337                         %$it,
338                 };
339         }
340         return @return;
341 }
342
343 sub find_unique {
344         my ($string, @stuff) = @_;
345         my $found = undef;
346         for (my $i = 0; $i < @stuff; $i++) {
347                 my $it = $stuff[$i];
348                 my $hit = undef;
349                 if (ref $it) {
350                         if ((ref $it) eq 'ARRAY') {
351                                 $it = $it->[0];
352                         }
353                         else {
354                                 $it = $it->{VALUE};
355                         }
356                 }
357                 eval {
358                         if ($it =~ /^$string/) {
359                                 $hit = 1;
360                         };
361                 };
362                 if (defined $hit && defined $found) {
363                         return undef;
364                 }
365                 if ($hit) {
366                         $found = $i + 1;
367                 }
368         }
369         return $found;
370 }
371
372 # inserts string into trie and updates count for each character
373 sub update_trie {
374         my ($trie, $string) = @_;
375         foreach (split //, $string) {
376                 $trie = $trie->{$_} ||= {COUNT => 0};
377                 $trie->{COUNT}++;
378         }
379 }
380
381 # returns an array of tuples (prefix, remainder)
382 sub find_unique_prefixes {
383         my @stuff = @_;
384         my @return = ();
385
386         # any single prefix exceeding the soft limit is omitted
387         # if any prefix exceeds the hard limit all are omitted
388         # 0 indicates no limit
389         my $soft_limit = 0;
390         my $hard_limit = 3;
391
392         # build a trie modelling all possible options
393         my %trie;
394         foreach my $print (@stuff) {
395                 if ((ref $print) eq 'ARRAY') {
396                         $print = $print->[0];
397                 }
398                 elsif ((ref $print) eq 'HASH') {
399                         $print = $print->{VALUE};
400                 }
401                 update_trie(\%trie, $print);
402                 push @return, $print;
403         }
404
405         # use the trie to find the unique prefixes
406         for (my $i = 0; $i < @return; $i++) {
407                 my $ret = $return[$i];
408                 my @letters = split //, $ret;
409                 my %search = %trie;
410                 my ($prefix, $remainder);
411                 my $j;
412                 for ($j = 0; $j < @letters; $j++) {
413                         my $letter = $letters[$j];
414                         if ($search{$letter}{COUNT} == 1) {
415                                 $prefix = substr $ret, 0, $j + 1;
416                                 $remainder = substr $ret, $j + 1;
417                                 last;
418                         }
419                         else {
420                                 my $prefix = substr $ret, 0, $j;
421                                 return ()
422                                     if ($hard_limit && $j + 1 > $hard_limit);
423                         }
424                         %search = %{$search{$letter}};
425                 }
426                 if (ord($letters[0]) > 127 ||
427                     ($soft_limit && $j + 1 > $soft_limit)) {
428                         $prefix = undef;
429                         $remainder = $ret;
430                 }
431                 $return[$i] = [$prefix, $remainder];
432         }
433         return @return;
434 }
435
436 # filters out prefixes which have special meaning to list_and_choose()
437 sub is_valid_prefix {
438         my $prefix = shift;
439         return (defined $prefix) &&
440             !($prefix =~ /[\s,]/) && # separators
441             !($prefix =~ /^-/) &&    # deselection
442             !($prefix =~ /^\d+/) &&  # selection
443             ($prefix ne '*') &&      # "all" wildcard
444             ($prefix ne '?');        # prompt help
445 }
446
447 # given a prefix/remainder tuple return a string with the prefix highlighted
448 # for now use square brackets; later might use ANSI colors (underline, bold)
449 sub highlight_prefix {
450         my $prefix = shift;
451         my $remainder = shift;
452
453         if (!defined $prefix) {
454                 return $remainder;
455         }
456
457         if (!is_valid_prefix($prefix)) {
458                 return "$prefix$remainder";
459         }
460
461         if (!$menu_use_color) {
462                 return "[$prefix]$remainder";
463         }
464
465         return "$prompt_color$prefix$normal_color$remainder";
466 }
467
468 sub error_msg {
469         print STDERR colored $error_color, @_;
470 }
471
472 sub list_and_choose {
473         my ($opts, @stuff) = @_;
474         my (@chosen, @return);
475         if (!@stuff) {
476             return @return;
477         }
478         my $i;
479         my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
480
481       TOPLOOP:
482         while (1) {
483                 my $last_lf = 0;
484
485                 if ($opts->{HEADER}) {
486                         if (!$opts->{LIST_FLAT}) {
487                                 print "     ";
488                         }
489                         print colored $header_color, "$opts->{HEADER}\n";
490                 }
491                 for ($i = 0; $i < @stuff; $i++) {
492                         my $chosen = $chosen[$i] ? '*' : ' ';
493                         my $print = $stuff[$i];
494                         my $ref = ref $print;
495                         my $highlighted = highlight_prefix(@{$prefixes[$i]})
496                             if @prefixes;
497                         if ($ref eq 'ARRAY') {
498                                 $print = $highlighted || $print->[0];
499                         }
500                         elsif ($ref eq 'HASH') {
501                                 my $value = $highlighted || $print->{VALUE};
502                                 $print = sprintf($status_fmt,
503                                     $print->{INDEX},
504                                     $print->{FILE},
505                                     $value);
506                         }
507                         else {
508                                 $print = $highlighted || $print;
509                         }
510                         printf("%s%2d: %s", $chosen, $i+1, $print);
511                         if (($opts->{LIST_FLAT}) &&
512                             (($i + 1) % ($opts->{LIST_FLAT}))) {
513                                 print "\t";
514                                 $last_lf = 0;
515                         }
516                         else {
517                                 print "\n";
518                                 $last_lf = 1;
519                         }
520                 }
521                 if (!$last_lf) {
522                         print "\n";
523                 }
524
525                 return if ($opts->{LIST_ONLY});
526
527                 print colored $prompt_color, $opts->{PROMPT};
528                 if ($opts->{SINGLETON}) {
529                         print "> ";
530                 }
531                 else {
532                         print ">> ";
533                 }
534                 my $line = <STDIN>;
535                 if (!$line) {
536                         print "\n";
537                         $opts->{ON_EOF}->() if $opts->{ON_EOF};
538                         last;
539                 }
540                 chomp $line;
541                 last if $line eq '';
542                 if ($line eq '?') {
543                         $opts->{SINGLETON} ?
544                             singleton_prompt_help_cmd() :
545                             prompt_help_cmd();
546                         next TOPLOOP;
547                 }
548                 for my $choice (split(/[\s,]+/, $line)) {
549                         my $choose = 1;
550                         my ($bottom, $top);
551
552                         # Input that begins with '-'; unchoose
553                         if ($choice =~ s/^-//) {
554                                 $choose = 0;
555                         }
556                         # A range can be specified like 5-7 or 5-.
557                         if ($choice =~ /^(\d+)-(\d*)$/) {
558                                 ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
559                         }
560                         elsif ($choice =~ /^\d+$/) {
561                                 $bottom = $top = $choice;
562                         }
563                         elsif ($choice eq '*') {
564                                 $bottom = 1;
565                                 $top = 1 + @stuff;
566                         }
567                         else {
568                                 $bottom = $top = find_unique($choice, @stuff);
569                                 if (!defined $bottom) {
570                                         error_msg sprintf(__("Huh (%s)?\n"), $choice);
571                                         next TOPLOOP;
572                                 }
573                         }
574                         if ($opts->{SINGLETON} && $bottom != $top) {
575                                 error_msg sprintf(__("Huh (%s)?\n"), $choice);
576                                 next TOPLOOP;
577                         }
578                         for ($i = $bottom-1; $i <= $top-1; $i++) {
579                                 next if (@stuff <= $i || $i < 0);
580                                 $chosen[$i] = $choose;
581                         }
582                 }
583                 last if ($opts->{IMMEDIATE} || $line eq '*');
584         }
585         for ($i = 0; $i < @stuff; $i++) {
586                 if ($chosen[$i]) {
587                         push @return, $stuff[$i];
588                 }
589         }
590         return @return;
591 }
592
593 sub singleton_prompt_help_cmd {
594         print colored $help_color, __ <<'EOF' ;
595 Prompt help:
596 1          - select a numbered item
597 foo        - select item based on unique prefix
598            - (empty) select nothing
599 EOF
600 }
601
602 sub prompt_help_cmd {
603         print colored $help_color, __ <<'EOF' ;
604 Prompt help:
605 1          - select a single item
606 3-5        - select a range of items
607 2-3,6-9    - select multiple ranges
608 foo        - select item based on unique prefix
609 -...       - unselect specified items
610 *          - choose all items
611            - (empty) finish selecting
612 EOF
613 }
614
615 sub status_cmd {
616         list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
617                         list_modified());
618         print "\n";
619 }
620
621 sub say_n_paths {
622         my $did = shift @_;
623         my $cnt = scalar @_;
624         if ($did eq 'added') {
625                 printf(__n("added %d path\n", "added %d paths\n",
626                            $cnt), $cnt);
627         } elsif ($did eq 'updated') {
628                 printf(__n("updated %d path\n", "updated %d paths\n",
629                            $cnt), $cnt);
630         } elsif ($did eq 'reverted') {
631                 printf(__n("reverted %d path\n", "reverted %d paths\n",
632                            $cnt), $cnt);
633         } else {
634                 printf(__n("touched %d path\n", "touched %d paths\n",
635                            $cnt), $cnt);
636         }
637 }
638
639 sub update_cmd {
640         my @mods = list_modified('file-only');
641         return if (!@mods);
642
643         my @update = list_and_choose({ PROMPT => __('Update'),
644                                        HEADER => $status_head, },
645                                      @mods);
646         if (@update) {
647                 system(qw(git update-index --add --remove --),
648                        map { $_->{VALUE} } @update);
649                 say_n_paths('updated', @update);
650         }
651         print "\n";
652 }
653
654 sub revert_cmd {
655         my @update = list_and_choose({ PROMPT => __('Revert'),
656                                        HEADER => $status_head, },
657                                      list_modified());
658         if (@update) {
659                 if (is_initial_commit()) {
660                         system(qw(git rm --cached),
661                                 map { $_->{VALUE} } @update);
662                 }
663                 else {
664                         my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
665                                                  map { $_->{VALUE} } @update);
666                         my $fh;
667                         open $fh, '| git update-index --index-info'
668                             or die;
669                         for (@lines) {
670                                 print $fh $_;
671                         }
672                         close($fh);
673                         for (@update) {
674                                 if ($_->{INDEX_ADDDEL} &&
675                                     $_->{INDEX_ADDDEL} eq 'create') {
676                                         system(qw(git update-index --force-remove --),
677                                                $_->{VALUE});
678                                         printf(__("note: %s is untracked now.\n"), $_->{VALUE});
679                                 }
680                         }
681                 }
682                 refresh();
683                 say_n_paths('reverted', @update);
684         }
685         print "\n";
686 }
687
688 sub add_untracked_cmd {
689         my @add = list_and_choose({ PROMPT => __('Add untracked') },
690                                   list_untracked());
691         if (@add) {
692                 system(qw(git update-index --add --), @add);
693                 say_n_paths('added', @add);
694         } else {
695                 print __("No untracked files.\n");
696         }
697         print "\n";
698 }
699
700 sub run_git_apply {
701         my $cmd = shift;
702         my $fh;
703         open $fh, '| git ' . $cmd . " --allow-overlap";
704         print $fh @_;
705         return close $fh;
706 }
707
708 sub parse_diff {
709         my ($path) = @_;
710         my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
711         if (defined $diff_algorithm) {
712                 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
713         }
714         if (defined $patch_mode_revision) {
715                 push @diff_cmd, get_diff_reference($patch_mode_revision);
716         }
717         my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
718         my @colored = ();
719         if ($diff_use_color) {
720                 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
721                 if (defined $diff_filter) {
722                         # quotemeta is overkill, but sufficient for shell-quoting
723                         my $diff = join(' ', map { quotemeta } @display_cmd);
724                         @display_cmd = ("$diff | $diff_filter");
725                 }
726
727                 @colored = run_cmd_pipe(@display_cmd);
728         }
729         my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
730
731         if (@colored && @colored != @diff) {
732                 print STDERR
733                   "fatal: mismatched output from interactive.diffFilter\n",
734                   "hint: Your filter must maintain a one-to-one correspondence\n",
735                   "hint: between its input and output lines.\n";
736                 exit 1;
737         }
738
739         for (my $i = 0; $i < @diff; $i++) {
740                 if ($diff[$i] =~ /^@@ /) {
741                         push @hunk, { TEXT => [], DISPLAY => [],
742                                 TYPE => 'hunk' };
743                 }
744                 push @{$hunk[-1]{TEXT}}, $diff[$i];
745                 push @{$hunk[-1]{DISPLAY}},
746                         (@colored ? $colored[$i] : $diff[$i]);
747         }
748         return @hunk;
749 }
750
751 sub parse_diff_header {
752         my $src = shift;
753
754         my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
755         my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
756         my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
757         my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' };
758
759         for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
760                 my $dest =
761                    $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
762                    $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
763                    $src->{TEXT}->[$i] =~ /^new file/ ? $addition :
764                    $head;
765                 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
766                 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
767         }
768         return ($head, $mode, $deletion, $addition);
769 }
770
771 sub hunk_splittable {
772         my ($text) = @_;
773
774         my @s = split_hunk($text);
775         return (1 < @s);
776 }
777
778 sub parse_hunk_header {
779         my ($line) = @_;
780         my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
781             $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
782         $o_cnt = 1 unless defined $o_cnt;
783         $n_cnt = 1 unless defined $n_cnt;
784         return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
785 }
786
787 sub format_hunk_header {
788         my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
789         return ("@@ -$o_ofs" .
790                 (($o_cnt != 1) ? ",$o_cnt" : '') .
791                 " +$n_ofs" .
792                 (($n_cnt != 1) ? ",$n_cnt" : '') .
793                 " @@\n");
794 }
795
796 sub split_hunk {
797         my ($text, $display) = @_;
798         my @split = ();
799         if (!defined $display) {
800                 $display = $text;
801         }
802         # If there are context lines in the middle of a hunk,
803         # it can be split, but we would need to take care of
804         # overlaps later.
805
806         my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
807         my $hunk_start = 1;
808
809       OUTER:
810         while (1) {
811                 my $next_hunk_start = undef;
812                 my $i = $hunk_start - 1;
813                 my $this = +{
814                         TEXT => [],
815                         DISPLAY => [],
816                         TYPE => 'hunk',
817                         OLD => $o_ofs,
818                         NEW => $n_ofs,
819                         OCNT => 0,
820                         NCNT => 0,
821                         ADDDEL => 0,
822                         POSTCTX => 0,
823                         USE => undef,
824                 };
825
826                 while (++$i < @$text) {
827                         my $line = $text->[$i];
828                         my $display = $display->[$i];
829                         if ($line =~ /^\\/) {
830                                 push @{$this->{TEXT}}, $line;
831                                 push @{$this->{DISPLAY}}, $display;
832                                 next;
833                         }
834                         if ($line =~ /^ /) {
835                                 if ($this->{ADDDEL} &&
836                                     !defined $next_hunk_start) {
837                                         # We have seen leading context and
838                                         # adds/dels and then here is another
839                                         # context, which is trailing for this
840                                         # split hunk and leading for the next
841                                         # one.
842                                         $next_hunk_start = $i;
843                                 }
844                                 push @{$this->{TEXT}}, $line;
845                                 push @{$this->{DISPLAY}}, $display;
846                                 $this->{OCNT}++;
847                                 $this->{NCNT}++;
848                                 if (defined $next_hunk_start) {
849                                         $this->{POSTCTX}++;
850                                 }
851                                 next;
852                         }
853
854                         # add/del
855                         if (defined $next_hunk_start) {
856                                 # We are done with the current hunk and
857                                 # this is the first real change for the
858                                 # next split one.
859                                 $hunk_start = $next_hunk_start;
860                                 $o_ofs = $this->{OLD} + $this->{OCNT};
861                                 $n_ofs = $this->{NEW} + $this->{NCNT};
862                                 $o_ofs -= $this->{POSTCTX};
863                                 $n_ofs -= $this->{POSTCTX};
864                                 push @split, $this;
865                                 redo OUTER;
866                         }
867                         push @{$this->{TEXT}}, $line;
868                         push @{$this->{DISPLAY}}, $display;
869                         $this->{ADDDEL}++;
870                         if ($line =~ /^-/) {
871                                 $this->{OCNT}++;
872                         }
873                         else {
874                                 $this->{NCNT}++;
875                         }
876                 }
877
878                 push @split, $this;
879                 last;
880         }
881
882         for my $hunk (@split) {
883                 $o_ofs = $hunk->{OLD};
884                 $n_ofs = $hunk->{NEW};
885                 my $o_cnt = $hunk->{OCNT};
886                 my $n_cnt = $hunk->{NCNT};
887
888                 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
889                 my $display_head = $head;
890                 unshift @{$hunk->{TEXT}}, $head;
891                 if ($diff_use_color) {
892                         $display_head = colored($fraginfo_color, $head);
893                 }
894                 unshift @{$hunk->{DISPLAY}}, $display_head;
895         }
896         return @split;
897 }
898
899 sub find_last_o_ctx {
900         my ($it) = @_;
901         my $text = $it->{TEXT};
902         my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
903         my $i = @{$text};
904         my $last_o_ctx = $o_ofs + $o_cnt;
905         while (0 < --$i) {
906                 my $line = $text->[$i];
907                 if ($line =~ /^ /) {
908                         $last_o_ctx--;
909                         next;
910                 }
911                 last;
912         }
913         return $last_o_ctx;
914 }
915
916 sub merge_hunk {
917         my ($prev, $this) = @_;
918         my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
919             parse_hunk_header($prev->{TEXT}[0]);
920         my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
921             parse_hunk_header($this->{TEXT}[0]);
922
923         my (@line, $i, $ofs, $o_cnt, $n_cnt);
924         $ofs = $o0_ofs;
925         $o_cnt = $n_cnt = 0;
926         for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
927                 my $line = $prev->{TEXT}[$i];
928                 if ($line =~ /^\+/) {
929                         $n_cnt++;
930                         push @line, $line;
931                         next;
932                 } elsif ($line =~ /^\\/) {
933                         push @line, $line;
934                         next;
935                 }
936
937                 last if ($o1_ofs <= $ofs);
938
939                 $o_cnt++;
940                 $ofs++;
941                 if ($line =~ /^ /) {
942                         $n_cnt++;
943                 }
944                 push @line, $line;
945         }
946
947         for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
948                 my $line = $this->{TEXT}[$i];
949                 if ($line =~ /^\+/) {
950                         $n_cnt++;
951                         push @line, $line;
952                         next;
953                 } elsif ($line =~ /^\\/) {
954                         push @line, $line;
955                         next;
956                 }
957                 $ofs++;
958                 $o_cnt++;
959                 if ($line =~ /^ /) {
960                         $n_cnt++;
961                 }
962                 push @line, $line;
963         }
964         my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
965         @{$prev->{TEXT}} = ($head, @line);
966 }
967
968 sub coalesce_overlapping_hunks {
969         my (@in) = @_;
970         my @out = ();
971
972         my ($last_o_ctx, $last_was_dirty);
973         my $ofs_delta = 0;
974
975         for (@in) {
976                 if ($_->{TYPE} ne 'hunk') {
977                         push @out, $_;
978                         next;
979                 }
980                 my $text = $_->{TEXT};
981                 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
982                                                 parse_hunk_header($text->[0]);
983                 unless ($_->{USE}) {
984                         $ofs_delta += $o_cnt - $n_cnt;
985                         # If this hunk has been edited then subtract
986                         # the delta that is due to the edit.
987                         if ($_->{OFS_DELTA}) {
988                                 $ofs_delta -= $_->{OFS_DELTA};
989                         }
990                         next;
991                 }
992                 if ($ofs_delta) {
993                         if ($patch_mode_flavour{IS_REVERSE}) {
994                                 $o_ofs -= $ofs_delta;
995                         } else {
996                                 $n_ofs += $ofs_delta;
997                         }
998                         $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
999                                                              $n_ofs, $n_cnt);
1000                 }
1001                 # If this hunk was edited then adjust the offset delta
1002                 # to reflect the edit.
1003                 if ($_->{OFS_DELTA}) {
1004                         $ofs_delta += $_->{OFS_DELTA};
1005                 }
1006                 if (defined $last_o_ctx &&
1007                     $o_ofs <= $last_o_ctx &&
1008                     !$_->{DIRTY} &&
1009                     !$last_was_dirty) {
1010                         merge_hunk($out[-1], $_);
1011                 }
1012                 else {
1013                         push @out, $_;
1014                 }
1015                 $last_o_ctx = find_last_o_ctx($out[-1]);
1016                 $last_was_dirty = $_->{DIRTY};
1017         }
1018         return @out;
1019 }
1020
1021 sub reassemble_patch {
1022         my $head = shift;
1023         my @patch;
1024
1025         # Include everything in the header except the beginning of the diff.
1026         push @patch, (grep { !/^[-+]{3}/ } @$head);
1027
1028         # Then include any headers from the hunk lines, which must
1029         # come before any actual hunk.
1030         while (@_ && $_[0] !~ /^@/) {
1031                 push @patch, shift;
1032         }
1033
1034         # Then begin the diff.
1035         push @patch, grep { /^[-+]{3}/ } @$head;
1036
1037         # And then the actual hunks.
1038         push @patch, @_;
1039
1040         return @patch;
1041 }
1042
1043 sub color_diff {
1044         return map {
1045                 colored((/^@/  ? $fraginfo_color :
1046                          /^\+/ ? $diff_new_color :
1047                          /^-/  ? $diff_old_color :
1048                          $diff_plain_color),
1049                         $_);
1050         } @_;
1051 }
1052
1053 my %edit_hunk_manually_modes = (
1054         stage => N__(
1055 "If the patch applies cleanly, the edited hunk will immediately be
1056 marked for staging."),
1057         stash => N__(
1058 "If the patch applies cleanly, the edited hunk will immediately be
1059 marked for stashing."),
1060         reset_head => N__(
1061 "If the patch applies cleanly, the edited hunk will immediately be
1062 marked for unstaging."),
1063         reset_nothead => N__(
1064 "If the patch applies cleanly, the edited hunk will immediately be
1065 marked for applying."),
1066         checkout_index => N__(
1067 "If the patch applies cleanly, the edited hunk will immediately be
1068 marked for discarding."),
1069         checkout_head => N__(
1070 "If the patch applies cleanly, the edited hunk will immediately be
1071 marked for discarding."),
1072         checkout_nothead => N__(
1073 "If the patch applies cleanly, the edited hunk will immediately be
1074 marked for applying."),
1075         worktree_head => N__(
1076 "If the patch applies cleanly, the edited hunk will immediately be
1077 marked for discarding."),
1078         worktree_nothead => N__(
1079 "If the patch applies cleanly, the edited hunk will immediately be
1080 marked for applying."),
1081 );
1082
1083 sub recount_edited_hunk {
1084         local $_;
1085         my ($oldtext, $newtext) = @_;
1086         my ($o_cnt, $n_cnt) = (0, 0);
1087         for (@{$newtext}[1..$#{$newtext}]) {
1088                 my $mode = substr($_, 0, 1);
1089                 if ($mode eq '-') {
1090                         $o_cnt++;
1091                 } elsif ($mode eq '+') {
1092                         $n_cnt++;
1093                 } elsif ($mode eq ' ' or $mode eq "\n") {
1094                         $o_cnt++;
1095                         $n_cnt++;
1096                 }
1097         }
1098         my ($o_ofs, undef, $n_ofs, undef) =
1099                                         parse_hunk_header($newtext->[0]);
1100         $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1101         my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1102                                         parse_hunk_header($oldtext->[0]);
1103         # Return the change in the number of lines inserted by this hunk
1104         return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1105 }
1106
1107 sub edit_hunk_manually {
1108         my ($oldtext) = @_;
1109
1110         my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1111         my $fh;
1112         open $fh, '>', $hunkfile
1113                 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
1114         print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
1115         print $fh @$oldtext;
1116         my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1117         my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
1118         my $comment_line_char = Git::get_comment_line_char;
1119         print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1120 ---
1121 To remove '%s' lines, make them ' ' lines (context).
1122 To remove '%s' lines, delete them.
1123 Lines starting with %s will be removed.
1124 EOF
1125 __($edit_hunk_manually_modes{$patch_mode}),
1126 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1127 __ <<EOF2 ;
1128 If it does not apply cleanly, you will be given an opportunity to
1129 edit again.  If all lines of the hunk are removed, then the edit is
1130 aborted and the hunk is left unchanged.
1131 EOF2
1132         close $fh;
1133
1134         chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
1135         system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1136
1137         if ($? != 0) {
1138                 return undef;
1139         }
1140
1141         open $fh, '<', $hunkfile
1142                 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
1143         my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1144         close $fh;
1145         unlink $hunkfile;
1146
1147         # Abort if nothing remains
1148         if (!grep { /\S/ } @newtext) {
1149                 return undef;
1150         }
1151
1152         # Reinsert the first hunk header if the user accidentally deleted it
1153         if ($newtext[0] !~ /^@/) {
1154                 unshift @newtext, $oldtext->[0];
1155         }
1156         return \@newtext;
1157 }
1158
1159 sub diff_applies {
1160         return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
1161                              map { @{$_->{TEXT}} } @_);
1162 }
1163
1164 sub _restore_terminal_and_die {
1165         ReadMode 'restore';
1166         print "\n";
1167         exit 1;
1168 }
1169
1170 sub prompt_single_character {
1171         if ($use_readkey) {
1172                 local $SIG{TERM} = \&_restore_terminal_and_die;
1173                 local $SIG{INT} = \&_restore_terminal_and_die;
1174                 ReadMode 'cbreak';
1175                 my $key = ReadKey 0;
1176                 ReadMode 'restore';
1177                 if ($use_termcap and $key eq "\e") {
1178                         while (!defined $term_escapes{$key}) {
1179                                 my $next = ReadKey 0.5;
1180                                 last if (!defined $next);
1181                                 $key .= $next;
1182                         }
1183                         $key =~ s/\e/^[/;
1184                 }
1185                 print "$key" if defined $key;
1186                 print "\n";
1187                 return $key;
1188         } else {
1189                 return <STDIN>;
1190         }
1191 }
1192
1193 sub prompt_yesno {
1194         my ($prompt) = @_;
1195         while (1) {
1196                 print colored $prompt_color, $prompt;
1197                 my $line = prompt_single_character;
1198                 return undef unless defined $line;
1199                 return 0 if $line =~ /^n/i;
1200                 return 1 if $line =~ /^y/i;
1201         }
1202 }
1203
1204 sub edit_hunk_loop {
1205         my ($head, $hunks, $ix) = @_;
1206         my $hunk = $hunks->[$ix];
1207         my $text = $hunk->{TEXT};
1208
1209         while (1) {
1210                 my $newtext = edit_hunk_manually($text);
1211                 if (!defined $newtext) {
1212                         return undef;
1213                 }
1214                 my $newhunk = {
1215                         TEXT => $newtext,
1216                         TYPE => $hunk->{TYPE},
1217                         USE => 1,
1218                         DIRTY => 1,
1219                 };
1220                 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1221                 # If this hunk has already been edited then add the
1222                 # offset delta of the previous edit to get the real
1223                 # delta from the original unedited hunk.
1224                 $hunk->{OFS_DELTA} and
1225                                 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
1226                 if (diff_applies($head,
1227                                  @{$hunks}[0..$ix-1],
1228                                  $newhunk,
1229                                  @{$hunks}[$ix+1..$#{$hunks}])) {
1230                         $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
1231                         return $newhunk;
1232                 }
1233                 else {
1234                         prompt_yesno(
1235                                 # TRANSLATORS: do not translate [y/n]
1236                                 # The program will only accept that input
1237                                 # at this point.
1238                                 # Consider translating (saying "no" discards!) as
1239                                 # (saying "n" for "no" discards!) if the translation
1240                                 # of the word "no" does not start with n.
1241                                 __('Your edited hunk does not apply. Edit again '
1242                                    . '(saying "no" discards!) [y/n]? ')
1243                                 ) or return undef;
1244                 }
1245         }
1246 }
1247
1248 my %help_patch_modes = (
1249         stage => N__(
1250 "y - stage this hunk
1251 n - do not stage this hunk
1252 q - quit; do not stage this hunk or any of the remaining ones
1253 a - stage this hunk and all later hunks in the file
1254 d - do not stage this hunk or any of the later hunks in the file"),
1255         stash => N__(
1256 "y - stash this hunk
1257 n - do not stash this hunk
1258 q - quit; do not stash this hunk or any of the remaining ones
1259 a - stash this hunk and all later hunks in the file
1260 d - do not stash this hunk or any of the later hunks in the file"),
1261         reset_head => N__(
1262 "y - unstage this hunk
1263 n - do not unstage this hunk
1264 q - quit; do not unstage this hunk or any of the remaining ones
1265 a - unstage this hunk and all later hunks in the file
1266 d - do not unstage this hunk or any of the later hunks in the file"),
1267         reset_nothead => N__(
1268 "y - apply this hunk to index
1269 n - do not apply this hunk to index
1270 q - quit; do not apply this hunk or any of the remaining ones
1271 a - apply this hunk and all later hunks in the file
1272 d - do not apply this hunk or any of the later hunks in the file"),
1273         checkout_index => N__(
1274 "y - discard this hunk from worktree
1275 n - do not discard this hunk from worktree
1276 q - quit; do not discard this hunk or any of the remaining ones
1277 a - discard this hunk and all later hunks in the file
1278 d - do not discard this hunk or any of the later hunks in the file"),
1279         checkout_head => N__(
1280 "y - discard this hunk from index and worktree
1281 n - do not discard this hunk from index and worktree
1282 q - quit; do not discard this hunk or any of the remaining ones
1283 a - discard this hunk and all later hunks in the file
1284 d - do not discard this hunk or any of the later hunks in the file"),
1285         checkout_nothead => N__(
1286 "y - apply this hunk to index and worktree
1287 n - do not apply this hunk to index and worktree
1288 q - quit; do not apply this hunk or any of the remaining ones
1289 a - apply this hunk and all later hunks in the file
1290 d - do not apply this hunk or any of the later hunks in the file"),
1291         worktree_head => N__(
1292 "y - discard this hunk from worktree
1293 n - do not discard this hunk from worktree
1294 q - quit; do not discard this hunk or any of the remaining ones
1295 a - discard this hunk and all later hunks in the file
1296 d - do not discard this hunk or any of the later hunks in the file"),
1297         worktree_nothead => N__(
1298 "y - apply this hunk to worktree
1299 n - do not apply this hunk to worktree
1300 q - quit; do not apply this hunk or any of the remaining ones
1301 a - apply this hunk and all later hunks in the file
1302 d - do not apply this hunk or any of the later hunks in the file"),
1303 );
1304
1305 sub help_patch_cmd {
1306         local $_;
1307         my $other = $_[0] . ",?";
1308         print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1309                 map { "$_\n" } grep {
1310                         my $c = quotemeta(substr($_, 0, 1));
1311                         $other =~ /,$c/
1312                 } split "\n", __ <<EOF ;
1313 g - select a hunk to go to
1314 / - search for a hunk matching the given regex
1315 j - leave this hunk undecided, see next undecided hunk
1316 J - leave this hunk undecided, see next hunk
1317 k - leave this hunk undecided, see previous undecided hunk
1318 K - leave this hunk undecided, see previous hunk
1319 s - split the current hunk into smaller hunks
1320 e - manually edit the current hunk
1321 ? - print help
1322 EOF
1323 }
1324
1325 sub apply_patch {
1326         my $cmd = shift;
1327         my $ret = run_git_apply $cmd, @_;
1328         if (!$ret) {
1329                 print STDERR @_;
1330         }
1331         return $ret;
1332 }
1333
1334 sub apply_patch_for_checkout_commit {
1335         my $reverse = shift;
1336         my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1337         my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
1338
1339         if ($applies_worktree && $applies_index) {
1340                 run_git_apply 'apply '.$reverse.' --cached', @_;
1341                 run_git_apply 'apply '.$reverse, @_;
1342                 return 1;
1343         } elsif (!$applies_index) {
1344                 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1345                 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
1346                         return run_git_apply 'apply '.$reverse, @_;
1347                 } else {
1348                         print colored $error_color, __("Nothing was applied.\n");
1349                         return 0;
1350                 }
1351         } else {
1352                 print STDERR @_;
1353                 return 0;
1354         }
1355 }
1356
1357 sub patch_update_cmd {
1358         my @all_mods = list_modified($patch_mode_flavour{FILTER});
1359         error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
1360                 for grep { $_->{UNMERGED} } @all_mods;
1361         @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1362
1363         my @mods = grep { !($_->{BINARY}) } @all_mods;
1364         my @them;
1365
1366         if (!@mods) {
1367                 if (@all_mods) {
1368                         print STDERR __("Only binary files changed.\n");
1369                 } else {
1370                         print STDERR __("No changes.\n");
1371                 }
1372                 return 0;
1373         }
1374         if ($patch_mode_only) {
1375                 @them = @mods;
1376         }
1377         else {
1378                 @them = list_and_choose({ PROMPT => __('Patch update'),
1379                                           HEADER => $status_head, },
1380                                         @mods);
1381         }
1382         for (@them) {
1383                 return 0 if patch_update_file($_->{VALUE});
1384         }
1385 }
1386
1387 # Generate a one line summary of a hunk.
1388 sub summarize_hunk {
1389         my $rhunk = shift;
1390         my $summary = $rhunk->{TEXT}[0];
1391
1392         # Keep the line numbers, discard extra context.
1393         $summary =~ s/@@(.*?)@@.*/$1 /s;
1394         $summary .= " " x (20 - length $summary);
1395
1396         # Add some user context.
1397         for my $line (@{$rhunk->{TEXT}}) {
1398                 if ($line =~ m/^[+-].*\w/) {
1399                         $summary .= $line;
1400                         last;
1401                 }
1402         }
1403
1404         chomp $summary;
1405         return substr($summary, 0, 80) . "\n";
1406 }
1407
1408
1409 # Print a one-line summary of each hunk in the array ref in
1410 # the first argument, starting with the index in the 2nd.
1411 sub display_hunks {
1412         my ($hunks, $i) = @_;
1413         my $ctr = 0;
1414         $i ||= 0;
1415         for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1416                 my $status = " ";
1417                 if (defined $hunks->[$i]{USE}) {
1418                         $status = $hunks->[$i]{USE} ? "+" : "-";
1419                 }
1420                 printf "%s%2d: %s",
1421                         $status,
1422                         $i + 1,
1423                         summarize_hunk($hunks->[$i]);
1424         }
1425         return $i;
1426 }
1427
1428 my %patch_update_prompt_modes = (
1429         stage => {
1430                 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1431                 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1432                 addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
1433                 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
1434         },
1435         stash => {
1436                 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1437                 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1438                 addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
1439                 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
1440         },
1441         reset_head => {
1442                 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1443                 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1444                 addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
1445                 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
1446         },
1447         reset_nothead => {
1448                 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1449                 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1450                 addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
1451                 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
1452         },
1453         checkout_index => {
1454                 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1455                 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1456                 addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1457                 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1458         },
1459         checkout_head => {
1460                 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1461                 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1462                 addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
1463                 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
1464         },
1465         checkout_nothead => {
1466                 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1467                 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1468                 addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
1469                 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
1470         },
1471         worktree_head => {
1472                 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1473                 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1474                 addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1475                 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1476         },
1477         worktree_nothead => {
1478                 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1479                 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1480                 addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
1481                 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1482         },
1483 );
1484
1485 sub patch_update_file {
1486         my $quit = 0;
1487         my ($ix, $num);
1488         my $path = shift;
1489         my ($head, @hunk) = parse_diff($path);
1490         ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
1491         for (@{$head->{DISPLAY}}) {
1492                 print;
1493         }
1494
1495         if (@{$mode->{TEXT}}) {
1496                 unshift @hunk, $mode;
1497         }
1498         if (@{$deletion->{TEXT}}) {
1499                 foreach my $hunk (@hunk) {
1500                         push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1501                         push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1502                 }
1503                 @hunk = ($deletion);
1504         } elsif (@{$addition->{TEXT}}) {
1505                 foreach my $hunk (@hunk) {
1506                         push @{$addition->{TEXT}}, @{$hunk->{TEXT}};
1507                         push @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}};
1508                 }
1509                 @hunk = ($addition);
1510         }
1511
1512         $num = scalar @hunk;
1513         $ix = 0;
1514
1515         while (1) {
1516                 my ($prev, $next, $other, $undecided, $i);
1517                 $other = '';
1518
1519                 if ($num <= $ix) {
1520                         $ix = 0;
1521                 }
1522                 for ($i = 0; $i < $ix; $i++) {
1523                         if (!defined $hunk[$i]{USE}) {
1524                                 $prev = 1;
1525                                 $other .= ',k';
1526                                 last;
1527                         }
1528                 }
1529                 if ($ix) {
1530                         $other .= ',K';
1531                 }
1532                 for ($i = $ix + 1; $i < $num; $i++) {
1533                         if (!defined $hunk[$i]{USE}) {
1534                                 $next = 1;
1535                                 $other .= ',j';
1536                                 last;
1537                         }
1538                 }
1539                 if ($ix < $num - 1) {
1540                         $other .= ',J';
1541                 }
1542                 if ($num > 1) {
1543                         $other .= ',g,/';
1544                 }
1545                 for ($i = 0; $i < $num; $i++) {
1546                         if (!defined $hunk[$i]{USE}) {
1547                                 $undecided = 1;
1548                                 last;
1549                         }
1550                 }
1551                 last if (!$undecided);
1552
1553                 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1554                     hunk_splittable($hunk[$ix]{TEXT})) {
1555                         $other .= ',s';
1556                 }
1557                 if ($hunk[$ix]{TYPE} eq 'hunk') {
1558                         $other .= ',e';
1559                 }
1560                 for (@{$hunk[$ix]{DISPLAY}}) {
1561                         print;
1562                 }
1563                 print colored $prompt_color, "(", ($ix+1), "/$num) ",
1564                         sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1565
1566                 my $line = prompt_single_character;
1567                 last unless defined $line;
1568                 if ($line) {
1569                         if ($line =~ /^y/i) {
1570                                 $hunk[$ix]{USE} = 1;
1571                         }
1572                         elsif ($line =~ /^n/i) {
1573                                 $hunk[$ix]{USE} = 0;
1574                         }
1575                         elsif ($line =~ /^a/i) {
1576                                 while ($ix < $num) {
1577                                         if (!defined $hunk[$ix]{USE}) {
1578                                                 $hunk[$ix]{USE} = 1;
1579                                         }
1580                                         $ix++;
1581                                 }
1582                                 next;
1583                         }
1584                         elsif ($line =~ /^g(.*)/) {
1585                                 my $response = $1;
1586                                 unless ($other =~ /g/) {
1587                                         error_msg __("No other hunks to goto\n");
1588                                         next;
1589                                 }
1590                                 my $no = $ix > 10 ? $ix - 10 : 0;
1591                                 while ($response eq '') {
1592                                         $no = display_hunks(\@hunk, $no);
1593                                         if ($no < $num) {
1594                                                 print __("go to which hunk (<ret> to see more)? ");
1595                                         } else {
1596                                                 print __("go to which hunk? ");
1597                                         }
1598                                         $response = <STDIN>;
1599                                         if (!defined $response) {
1600                                                 $response = '';
1601                                         }
1602                                         chomp $response;
1603                                 }
1604                                 if ($response !~ /^\s*\d+\s*$/) {
1605                                         error_msg sprintf(__("Invalid number: '%s'\n"),
1606                                                              $response);
1607                                 } elsif (0 < $response && $response <= $num) {
1608                                         $ix = $response - 1;
1609                                 } else {
1610                                         error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1611                                                               "Sorry, only %d hunks available.\n", $num), $num);
1612                                 }
1613                                 next;
1614                         }
1615                         elsif ($line =~ /^d/i) {
1616                                 while ($ix < $num) {
1617                                         if (!defined $hunk[$ix]{USE}) {
1618                                                 $hunk[$ix]{USE} = 0;
1619                                         }
1620                                         $ix++;
1621                                 }
1622                                 next;
1623                         }
1624                         elsif ($line =~ /^q/i) {
1625                                 for ($i = 0; $i < $num; $i++) {
1626                                         if (!defined $hunk[$i]{USE}) {
1627                                                 $hunk[$i]{USE} = 0;
1628                                         }
1629                                 }
1630                                 $quit = 1;
1631                                 last;
1632                         }
1633                         elsif ($line =~ m|^/(.*)|) {
1634                                 my $regex = $1;
1635                                 unless ($other =~ m|/|) {
1636                                         error_msg __("No other hunks to search\n");
1637                                         next;
1638                                 }
1639                                 if ($regex eq "") {
1640                                         print colored $prompt_color, __("search for regex? ");
1641                                         $regex = <STDIN>;
1642                                         if (defined $regex) {
1643                                                 chomp $regex;
1644                                         }
1645                                 }
1646                                 my $search_string;
1647                                 eval {
1648                                         $search_string = qr{$regex}m;
1649                                 };
1650                                 if ($@) {
1651                                         my ($err,$exp) = ($@, $1);
1652                                         $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1653                                         error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
1654                                         next;
1655                                 }
1656                                 my $iy = $ix;
1657                                 while (1) {
1658                                         my $text = join ("", @{$hunk[$iy]{TEXT}});
1659                                         last if ($text =~ $search_string);
1660                                         $iy++;
1661                                         $iy = 0 if ($iy >= $num);
1662                                         if ($ix == $iy) {
1663                                                 error_msg __("No hunk matches the given pattern\n");
1664                                                 last;
1665                                         }
1666                                 }
1667                                 $ix = $iy;
1668                                 next;
1669                         }
1670                         elsif ($line =~ /^K/) {
1671                                 if ($other =~ /K/) {
1672                                         $ix--;
1673                                 }
1674                                 else {
1675                                         error_msg __("No previous hunk\n");
1676                                 }
1677                                 next;
1678                         }
1679                         elsif ($line =~ /^J/) {
1680                                 if ($other =~ /J/) {
1681                                         $ix++;
1682                                 }
1683                                 else {
1684                                         error_msg __("No next hunk\n");
1685                                 }
1686                                 next;
1687                         }
1688                         elsif ($line =~ /^k/) {
1689                                 if ($other =~ /k/) {
1690                                         while (1) {
1691                                                 $ix--;
1692                                                 last if (!$ix ||
1693                                                          !defined $hunk[$ix]{USE});
1694                                         }
1695                                 }
1696                                 else {
1697                                         error_msg __("No previous hunk\n");
1698                                 }
1699                                 next;
1700                         }
1701                         elsif ($line =~ /^j/) {
1702                                 if ($other !~ /j/) {
1703                                         error_msg __("No next hunk\n");
1704                                         next;
1705                                 }
1706                         }
1707                         elsif ($line =~ /^s/) {
1708                                 unless ($other =~ /s/) {
1709                                         error_msg __("Sorry, cannot split this hunk\n");
1710                                         next;
1711                                 }
1712                                 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
1713                                 if (1 < @split) {
1714                                         print colored $header_color, sprintf(
1715                                                 __n("Split into %d hunk.\n",
1716                                                     "Split into %d hunks.\n",
1717                                                     scalar(@split)), scalar(@split));
1718                                 }
1719                                 splice (@hunk, $ix, 1, @split);
1720                                 $num = scalar @hunk;
1721                                 next;
1722                         }
1723                         elsif ($line =~ /^e/) {
1724                                 unless ($other =~ /e/) {
1725                                         error_msg __("Sorry, cannot edit this hunk\n");
1726                                         next;
1727                                 }
1728                                 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1729                                 if (defined $newhunk) {
1730                                         splice @hunk, $ix, 1, $newhunk;
1731                                 }
1732                         }
1733                         else {
1734                                 help_patch_cmd($other);
1735                                 next;
1736                         }
1737                         # soft increment
1738                         while (1) {
1739                                 $ix++;
1740                                 last if ($ix >= $num ||
1741                                          !defined $hunk[$ix]{USE});
1742                         }
1743                 }
1744         }
1745
1746         @hunk = coalesce_overlapping_hunks(@hunk);
1747
1748         my $n_lofs = 0;
1749         my @result = ();
1750         for (@hunk) {
1751                 if ($_->{USE}) {
1752                         push @result, @{$_->{TEXT}};
1753                 }
1754         }
1755
1756         if (@result) {
1757                 my @patch = reassemble_patch($head->{TEXT}, @result);
1758                 my $apply_routine = $patch_mode_flavour{APPLY};
1759                 &$apply_routine(@patch);
1760                 refresh();
1761         }
1762
1763         print "\n";
1764         return $quit;
1765 }
1766
1767 sub diff_cmd {
1768         my @mods = list_modified('index-only');
1769         @mods = grep { !($_->{BINARY}) } @mods;
1770         return if (!@mods);
1771         my (@them) = list_and_choose({ PROMPT => __('Review diff'),
1772                                      IMMEDIATE => 1,
1773                                      HEADER => $status_head, },
1774                                    @mods);
1775         return if (!@them);
1776         my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
1777         system(qw(git diff -p --cached), $reference, '--',
1778                 map { $_->{VALUE} } @them);
1779 }
1780
1781 sub quit_cmd {
1782         print __("Bye.\n");
1783         exit(0);
1784 }
1785
1786 sub help_cmd {
1787 # TRANSLATORS: please do not translate the command names
1788 # 'status', 'update', 'revert', etc.
1789         print colored $help_color, __ <<'EOF' ;
1790 status        - show paths with changes
1791 update        - add working tree state to the staged set of changes
1792 revert        - revert staged set of changes back to the HEAD version
1793 patch         - pick hunks and update selectively
1794 diff          - view diff between HEAD and index
1795 add untracked - add contents of untracked files to the staged set of changes
1796 EOF
1797 }
1798
1799 sub process_args {
1800         return unless @ARGV;
1801         my $arg = shift @ARGV;
1802         if ($arg =~ /--patch(?:=(.*))?/) {
1803                 if (defined $1) {
1804                         if ($1 eq 'reset') {
1805                                 $patch_mode = 'reset_head';
1806                                 $patch_mode_revision = 'HEAD';
1807                                 $arg = shift @ARGV or die __("missing --");
1808                                 if ($arg ne '--') {
1809                                         $patch_mode_revision = $arg;
1810                                         $patch_mode = ($arg eq 'HEAD' ?
1811                                                        'reset_head' : 'reset_nothead');
1812                                         $arg = shift @ARGV or die __("missing --");
1813                                 }
1814                         } elsif ($1 eq 'checkout') {
1815                                 $arg = shift @ARGV or die __("missing --");
1816                                 if ($arg eq '--') {
1817                                         $patch_mode = 'checkout_index';
1818                                 } else {
1819                                         $patch_mode_revision = $arg;
1820                                         $patch_mode = ($arg eq 'HEAD' ?
1821                                                        'checkout_head' : 'checkout_nothead');
1822                                         $arg = shift @ARGV or die __("missing --");
1823                                 }
1824                         } elsif ($1 eq 'worktree') {
1825                                 $arg = shift @ARGV or die __("missing --");
1826                                 if ($arg eq '--') {
1827                                         $patch_mode = 'checkout_index';
1828                                 } else {
1829                                         $patch_mode_revision = $arg;
1830                                         $patch_mode = ($arg eq 'HEAD' ?
1831                                                        'worktree_head' : 'worktree_nothead');
1832                                         $arg = shift @ARGV or die __("missing --");
1833                                 }
1834                         } elsif ($1 eq 'stage' or $1 eq 'stash') {
1835                                 $patch_mode = $1;
1836                                 $arg = shift @ARGV or die __("missing --");
1837                         } else {
1838                                 die sprintf(__("unknown --patch mode: %s"), $1);
1839                         }
1840                 } else {
1841                         $patch_mode = 'stage';
1842                         $arg = shift @ARGV or die __("missing --");
1843                 }
1844                 die sprintf(__("invalid argument %s, expecting --"),
1845                                $arg) unless $arg eq "--";
1846                 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1847                 $patch_mode_only = 1;
1848         }
1849         elsif ($arg ne "--") {
1850                 die sprintf(__("invalid argument %s, expecting --"), $arg);
1851         }
1852 }
1853
1854 sub main_loop {
1855         my @cmd = ([ 'status', \&status_cmd, ],
1856                    [ 'update', \&update_cmd, ],
1857                    [ 'revert', \&revert_cmd, ],
1858                    [ 'add untracked', \&add_untracked_cmd, ],
1859                    [ 'patch', \&patch_update_cmd, ],
1860                    [ 'diff', \&diff_cmd, ],
1861                    [ 'quit', \&quit_cmd, ],
1862                    [ 'help', \&help_cmd, ],
1863         );
1864         while (1) {
1865                 my ($it) = list_and_choose({ PROMPT => __('What now'),
1866                                              SINGLETON => 1,
1867                                              LIST_FLAT => 4,
1868                                              HEADER => __('*** Commands ***'),
1869                                              ON_EOF => \&quit_cmd,
1870                                              IMMEDIATE => 1 }, @cmd);
1871                 if ($it) {
1872                         eval {
1873                                 $it->[1]->();
1874                         };
1875                         if ($@) {
1876                                 print "$@";
1877                         }
1878                 }
1879         }
1880 }
1881
1882 process_args();
1883 refresh();
1884 if ($patch_mode_only) {
1885         patch_update_cmd();
1886 }
1887 else {
1888         status_cmd();
1889         main_loop();
1890 }