Git 2.27.1
[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
758         for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
759                 my $dest =
760                    $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
761                    $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
762                    $head;
763                 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
764                 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
765         }
766         return ($head, $mode, $deletion);
767 }
768
769 sub hunk_splittable {
770         my ($text) = @_;
771
772         my @s = split_hunk($text);
773         return (1 < @s);
774 }
775
776 sub parse_hunk_header {
777         my ($line) = @_;
778         my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
779             $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
780         $o_cnt = 1 unless defined $o_cnt;
781         $n_cnt = 1 unless defined $n_cnt;
782         return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
783 }
784
785 sub format_hunk_header {
786         my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
787         return ("@@ -$o_ofs" .
788                 (($o_cnt != 1) ? ",$o_cnt" : '') .
789                 " +$n_ofs" .
790                 (($n_cnt != 1) ? ",$n_cnt" : '') .
791                 " @@\n");
792 }
793
794 sub split_hunk {
795         my ($text, $display) = @_;
796         my @split = ();
797         if (!defined $display) {
798                 $display = $text;
799         }
800         # If there are context lines in the middle of a hunk,
801         # it can be split, but we would need to take care of
802         # overlaps later.
803
804         my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
805         my $hunk_start = 1;
806
807       OUTER:
808         while (1) {
809                 my $next_hunk_start = undef;
810                 my $i = $hunk_start - 1;
811                 my $this = +{
812                         TEXT => [],
813                         DISPLAY => [],
814                         TYPE => 'hunk',
815                         OLD => $o_ofs,
816                         NEW => $n_ofs,
817                         OCNT => 0,
818                         NCNT => 0,
819                         ADDDEL => 0,
820                         POSTCTX => 0,
821                         USE => undef,
822                 };
823
824                 while (++$i < @$text) {
825                         my $line = $text->[$i];
826                         my $display = $display->[$i];
827                         if ($line =~ /^\\/) {
828                                 push @{$this->{TEXT}}, $line;
829                                 push @{$this->{DISPLAY}}, $display;
830                                 next;
831                         }
832                         if ($line =~ /^ /) {
833                                 if ($this->{ADDDEL} &&
834                                     !defined $next_hunk_start) {
835                                         # We have seen leading context and
836                                         # adds/dels and then here is another
837                                         # context, which is trailing for this
838                                         # split hunk and leading for the next
839                                         # one.
840                                         $next_hunk_start = $i;
841                                 }
842                                 push @{$this->{TEXT}}, $line;
843                                 push @{$this->{DISPLAY}}, $display;
844                                 $this->{OCNT}++;
845                                 $this->{NCNT}++;
846                                 if (defined $next_hunk_start) {
847                                         $this->{POSTCTX}++;
848                                 }
849                                 next;
850                         }
851
852                         # add/del
853                         if (defined $next_hunk_start) {
854                                 # We are done with the current hunk and
855                                 # this is the first real change for the
856                                 # next split one.
857                                 $hunk_start = $next_hunk_start;
858                                 $o_ofs = $this->{OLD} + $this->{OCNT};
859                                 $n_ofs = $this->{NEW} + $this->{NCNT};
860                                 $o_ofs -= $this->{POSTCTX};
861                                 $n_ofs -= $this->{POSTCTX};
862                                 push @split, $this;
863                                 redo OUTER;
864                         }
865                         push @{$this->{TEXT}}, $line;
866                         push @{$this->{DISPLAY}}, $display;
867                         $this->{ADDDEL}++;
868                         if ($line =~ /^-/) {
869                                 $this->{OCNT}++;
870                         }
871                         else {
872                                 $this->{NCNT}++;
873                         }
874                 }
875
876                 push @split, $this;
877                 last;
878         }
879
880         for my $hunk (@split) {
881                 $o_ofs = $hunk->{OLD};
882                 $n_ofs = $hunk->{NEW};
883                 my $o_cnt = $hunk->{OCNT};
884                 my $n_cnt = $hunk->{NCNT};
885
886                 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
887                 my $display_head = $head;
888                 unshift @{$hunk->{TEXT}}, $head;
889                 if ($diff_use_color) {
890                         $display_head = colored($fraginfo_color, $head);
891                 }
892                 unshift @{$hunk->{DISPLAY}}, $display_head;
893         }
894         return @split;
895 }
896
897 sub find_last_o_ctx {
898         my ($it) = @_;
899         my $text = $it->{TEXT};
900         my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
901         my $i = @{$text};
902         my $last_o_ctx = $o_ofs + $o_cnt;
903         while (0 < --$i) {
904                 my $line = $text->[$i];
905                 if ($line =~ /^ /) {
906                         $last_o_ctx--;
907                         next;
908                 }
909                 last;
910         }
911         return $last_o_ctx;
912 }
913
914 sub merge_hunk {
915         my ($prev, $this) = @_;
916         my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
917             parse_hunk_header($prev->{TEXT}[0]);
918         my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
919             parse_hunk_header($this->{TEXT}[0]);
920
921         my (@line, $i, $ofs, $o_cnt, $n_cnt);
922         $ofs = $o0_ofs;
923         $o_cnt = $n_cnt = 0;
924         for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
925                 my $line = $prev->{TEXT}[$i];
926                 if ($line =~ /^\+/) {
927                         $n_cnt++;
928                         push @line, $line;
929                         next;
930                 } elsif ($line =~ /^\\/) {
931                         push @line, $line;
932                         next;
933                 }
934
935                 last if ($o1_ofs <= $ofs);
936
937                 $o_cnt++;
938                 $ofs++;
939                 if ($line =~ /^ /) {
940                         $n_cnt++;
941                 }
942                 push @line, $line;
943         }
944
945         for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
946                 my $line = $this->{TEXT}[$i];
947                 if ($line =~ /^\+/) {
948                         $n_cnt++;
949                         push @line, $line;
950                         next;
951                 } elsif ($line =~ /^\\/) {
952                         push @line, $line;
953                         next;
954                 }
955                 $ofs++;
956                 $o_cnt++;
957                 if ($line =~ /^ /) {
958                         $n_cnt++;
959                 }
960                 push @line, $line;
961         }
962         my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
963         @{$prev->{TEXT}} = ($head, @line);
964 }
965
966 sub coalesce_overlapping_hunks {
967         my (@in) = @_;
968         my @out = ();
969
970         my ($last_o_ctx, $last_was_dirty);
971         my $ofs_delta = 0;
972
973         for (@in) {
974                 if ($_->{TYPE} ne 'hunk') {
975                         push @out, $_;
976                         next;
977                 }
978                 my $text = $_->{TEXT};
979                 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
980                                                 parse_hunk_header($text->[0]);
981                 unless ($_->{USE}) {
982                         $ofs_delta += $o_cnt - $n_cnt;
983                         # If this hunk has been edited then subtract
984                         # the delta that is due to the edit.
985                         if ($_->{OFS_DELTA}) {
986                                 $ofs_delta -= $_->{OFS_DELTA};
987                         }
988                         next;
989                 }
990                 if ($ofs_delta) {
991                         if ($patch_mode_flavour{IS_REVERSE}) {
992                                 $o_ofs -= $ofs_delta;
993                         } else {
994                                 $n_ofs += $ofs_delta;
995                         }
996                         $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
997                                                              $n_ofs, $n_cnt);
998                 }
999                 # If this hunk was edited then adjust the offset delta
1000                 # to reflect the edit.
1001                 if ($_->{OFS_DELTA}) {
1002                         $ofs_delta += $_->{OFS_DELTA};
1003                 }
1004                 if (defined $last_o_ctx &&
1005                     $o_ofs <= $last_o_ctx &&
1006                     !$_->{DIRTY} &&
1007                     !$last_was_dirty) {
1008                         merge_hunk($out[-1], $_);
1009                 }
1010                 else {
1011                         push @out, $_;
1012                 }
1013                 $last_o_ctx = find_last_o_ctx($out[-1]);
1014                 $last_was_dirty = $_->{DIRTY};
1015         }
1016         return @out;
1017 }
1018
1019 sub reassemble_patch {
1020         my $head = shift;
1021         my @patch;
1022
1023         # Include everything in the header except the beginning of the diff.
1024         push @patch, (grep { !/^[-+]{3}/ } @$head);
1025
1026         # Then include any headers from the hunk lines, which must
1027         # come before any actual hunk.
1028         while (@_ && $_[0] !~ /^@/) {
1029                 push @patch, shift;
1030         }
1031
1032         # Then begin the diff.
1033         push @patch, grep { /^[-+]{3}/ } @$head;
1034
1035         # And then the actual hunks.
1036         push @patch, @_;
1037
1038         return @patch;
1039 }
1040
1041 sub color_diff {
1042         return map {
1043                 colored((/^@/  ? $fraginfo_color :
1044                          /^\+/ ? $diff_new_color :
1045                          /^-/  ? $diff_old_color :
1046                          $diff_plain_color),
1047                         $_);
1048         } @_;
1049 }
1050
1051 my %edit_hunk_manually_modes = (
1052         stage => N__(
1053 "If the patch applies cleanly, the edited hunk will immediately be
1054 marked for staging."),
1055         stash => N__(
1056 "If the patch applies cleanly, the edited hunk will immediately be
1057 marked for stashing."),
1058         reset_head => N__(
1059 "If the patch applies cleanly, the edited hunk will immediately be
1060 marked for unstaging."),
1061         reset_nothead => N__(
1062 "If the patch applies cleanly, the edited hunk will immediately be
1063 marked for applying."),
1064         checkout_index => N__(
1065 "If the patch applies cleanly, the edited hunk will immediately be
1066 marked for discarding."),
1067         checkout_head => N__(
1068 "If the patch applies cleanly, the edited hunk will immediately be
1069 marked for discarding."),
1070         checkout_nothead => N__(
1071 "If the patch applies cleanly, the edited hunk will immediately be
1072 marked for applying."),
1073         worktree_head => N__(
1074 "If the patch applies cleanly, the edited hunk will immediately be
1075 marked for discarding."),
1076         worktree_nothead => N__(
1077 "If the patch applies cleanly, the edited hunk will immediately be
1078 marked for applying."),
1079 );
1080
1081 sub recount_edited_hunk {
1082         local $_;
1083         my ($oldtext, $newtext) = @_;
1084         my ($o_cnt, $n_cnt) = (0, 0);
1085         for (@{$newtext}[1..$#{$newtext}]) {
1086                 my $mode = substr($_, 0, 1);
1087                 if ($mode eq '-') {
1088                         $o_cnt++;
1089                 } elsif ($mode eq '+') {
1090                         $n_cnt++;
1091                 } elsif ($mode eq ' ' or $mode eq "\n") {
1092                         $o_cnt++;
1093                         $n_cnt++;
1094                 }
1095         }
1096         my ($o_ofs, undef, $n_ofs, undef) =
1097                                         parse_hunk_header($newtext->[0]);
1098         $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1099         my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1100                                         parse_hunk_header($oldtext->[0]);
1101         # Return the change in the number of lines inserted by this hunk
1102         return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1103 }
1104
1105 sub edit_hunk_manually {
1106         my ($oldtext) = @_;
1107
1108         my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1109         my $fh;
1110         open $fh, '>', $hunkfile
1111                 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
1112         print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
1113         print $fh @$oldtext;
1114         my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1115         my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
1116         my $comment_line_char = Git::get_comment_line_char;
1117         print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1118 ---
1119 To remove '%s' lines, make them ' ' lines (context).
1120 To remove '%s' lines, delete them.
1121 Lines starting with %s will be removed.
1122 EOF
1123 __($edit_hunk_manually_modes{$patch_mode}),
1124 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1125 __ <<EOF2 ;
1126 If it does not apply cleanly, you will be given an opportunity to
1127 edit again.  If all lines of the hunk are removed, then the edit is
1128 aborted and the hunk is left unchanged.
1129 EOF2
1130         close $fh;
1131
1132         chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
1133         system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1134
1135         if ($? != 0) {
1136                 return undef;
1137         }
1138
1139         open $fh, '<', $hunkfile
1140                 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
1141         my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1142         close $fh;
1143         unlink $hunkfile;
1144
1145         # Abort if nothing remains
1146         if (!grep { /\S/ } @newtext) {
1147                 return undef;
1148         }
1149
1150         # Reinsert the first hunk header if the user accidentally deleted it
1151         if ($newtext[0] !~ /^@/) {
1152                 unshift @newtext, $oldtext->[0];
1153         }
1154         return \@newtext;
1155 }
1156
1157 sub diff_applies {
1158         return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
1159                              map { @{$_->{TEXT}} } @_);
1160 }
1161
1162 sub _restore_terminal_and_die {
1163         ReadMode 'restore';
1164         print "\n";
1165         exit 1;
1166 }
1167
1168 sub prompt_single_character {
1169         if ($use_readkey) {
1170                 local $SIG{TERM} = \&_restore_terminal_and_die;
1171                 local $SIG{INT} = \&_restore_terminal_and_die;
1172                 ReadMode 'cbreak';
1173                 my $key = ReadKey 0;
1174                 ReadMode 'restore';
1175                 if ($use_termcap and $key eq "\e") {
1176                         while (!defined $term_escapes{$key}) {
1177                                 my $next = ReadKey 0.5;
1178                                 last if (!defined $next);
1179                                 $key .= $next;
1180                         }
1181                         $key =~ s/\e/^[/;
1182                 }
1183                 print "$key" if defined $key;
1184                 print "\n";
1185                 return $key;
1186         } else {
1187                 return <STDIN>;
1188         }
1189 }
1190
1191 sub prompt_yesno {
1192         my ($prompt) = @_;
1193         while (1) {
1194                 print colored $prompt_color, $prompt;
1195                 my $line = prompt_single_character;
1196                 return undef unless defined $line;
1197                 return 0 if $line =~ /^n/i;
1198                 return 1 if $line =~ /^y/i;
1199         }
1200 }
1201
1202 sub edit_hunk_loop {
1203         my ($head, $hunks, $ix) = @_;
1204         my $hunk = $hunks->[$ix];
1205         my $text = $hunk->{TEXT};
1206
1207         while (1) {
1208                 my $newtext = edit_hunk_manually($text);
1209                 if (!defined $newtext) {
1210                         return undef;
1211                 }
1212                 my $newhunk = {
1213                         TEXT => $newtext,
1214                         TYPE => $hunk->{TYPE},
1215                         USE => 1,
1216                         DIRTY => 1,
1217                 };
1218                 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1219                 # If this hunk has already been edited then add the
1220                 # offset delta of the previous edit to get the real
1221                 # delta from the original unedited hunk.
1222                 $hunk->{OFS_DELTA} and
1223                                 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
1224                 if (diff_applies($head,
1225                                  @{$hunks}[0..$ix-1],
1226                                  $newhunk,
1227                                  @{$hunks}[$ix+1..$#{$hunks}])) {
1228                         $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
1229                         return $newhunk;
1230                 }
1231                 else {
1232                         prompt_yesno(
1233                                 # TRANSLATORS: do not translate [y/n]
1234                                 # The program will only accept that input
1235                                 # at this point.
1236                                 # Consider translating (saying "no" discards!) as
1237                                 # (saying "n" for "no" discards!) if the translation
1238                                 # of the word "no" does not start with n.
1239                                 __('Your edited hunk does not apply. Edit again '
1240                                    . '(saying "no" discards!) [y/n]? ')
1241                                 ) or return undef;
1242                 }
1243         }
1244 }
1245
1246 my %help_patch_modes = (
1247         stage => N__(
1248 "y - stage this hunk
1249 n - do not stage this hunk
1250 q - quit; do not stage this hunk or any of the remaining ones
1251 a - stage this hunk and all later hunks in the file
1252 d - do not stage this hunk or any of the later hunks in the file"),
1253         stash => N__(
1254 "y - stash this hunk
1255 n - do not stash this hunk
1256 q - quit; do not stash this hunk or any of the remaining ones
1257 a - stash this hunk and all later hunks in the file
1258 d - do not stash this hunk or any of the later hunks in the file"),
1259         reset_head => N__(
1260 "y - unstage this hunk
1261 n - do not unstage this hunk
1262 q - quit; do not unstage this hunk or any of the remaining ones
1263 a - unstage this hunk and all later hunks in the file
1264 d - do not unstage this hunk or any of the later hunks in the file"),
1265         reset_nothead => N__(
1266 "y - apply this hunk to index
1267 n - do not apply this hunk to index
1268 q - quit; do not apply this hunk or any of the remaining ones
1269 a - apply this hunk and all later hunks in the file
1270 d - do not apply this hunk or any of the later hunks in the file"),
1271         checkout_index => N__(
1272 "y - discard this hunk from worktree
1273 n - do not discard this hunk from worktree
1274 q - quit; do not discard this hunk or any of the remaining ones
1275 a - discard this hunk and all later hunks in the file
1276 d - do not discard this hunk or any of the later hunks in the file"),
1277         checkout_head => N__(
1278 "y - discard this hunk from index and worktree
1279 n - do not discard this hunk from index and worktree
1280 q - quit; do not discard this hunk or any of the remaining ones
1281 a - discard this hunk and all later hunks in the file
1282 d - do not discard this hunk or any of the later hunks in the file"),
1283         checkout_nothead => N__(
1284 "y - apply this hunk to index and worktree
1285 n - do not apply this hunk to index and worktree
1286 q - quit; do not apply this hunk or any of the remaining ones
1287 a - apply this hunk and all later hunks in the file
1288 d - do not apply this hunk or any of the later hunks in the file"),
1289         worktree_head => N__(
1290 "y - discard this hunk from worktree
1291 n - do not discard this hunk from worktree
1292 q - quit; do not discard this hunk or any of the remaining ones
1293 a - discard this hunk and all later hunks in the file
1294 d - do not discard this hunk or any of the later hunks in the file"),
1295         worktree_nothead => N__(
1296 "y - apply this hunk to worktree
1297 n - do not apply this hunk to worktree
1298 q - quit; do not apply this hunk or any of the remaining ones
1299 a - apply this hunk and all later hunks in the file
1300 d - do not apply this hunk or any of the later hunks in the file"),
1301 );
1302
1303 sub help_patch_cmd {
1304         local $_;
1305         my $other = $_[0] . ",?";
1306         print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1307                 map { "$_\n" } grep {
1308                         my $c = quotemeta(substr($_, 0, 1));
1309                         $other =~ /,$c/
1310                 } split "\n", __ <<EOF ;
1311 g - select a hunk to go to
1312 / - search for a hunk matching the given regex
1313 j - leave this hunk undecided, see next undecided hunk
1314 J - leave this hunk undecided, see next hunk
1315 k - leave this hunk undecided, see previous undecided hunk
1316 K - leave this hunk undecided, see previous hunk
1317 s - split the current hunk into smaller hunks
1318 e - manually edit the current hunk
1319 ? - print help
1320 EOF
1321 }
1322
1323 sub apply_patch {
1324         my $cmd = shift;
1325         my $ret = run_git_apply $cmd, @_;
1326         if (!$ret) {
1327                 print STDERR @_;
1328         }
1329         return $ret;
1330 }
1331
1332 sub apply_patch_for_checkout_commit {
1333         my $reverse = shift;
1334         my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1335         my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
1336
1337         if ($applies_worktree && $applies_index) {
1338                 run_git_apply 'apply '.$reverse.' --cached', @_;
1339                 run_git_apply 'apply '.$reverse, @_;
1340                 return 1;
1341         } elsif (!$applies_index) {
1342                 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1343                 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
1344                         return run_git_apply 'apply '.$reverse, @_;
1345                 } else {
1346                         print colored $error_color, __("Nothing was applied.\n");
1347                         return 0;
1348                 }
1349         } else {
1350                 print STDERR @_;
1351                 return 0;
1352         }
1353 }
1354
1355 sub patch_update_cmd {
1356         my @all_mods = list_modified($patch_mode_flavour{FILTER});
1357         error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
1358                 for grep { $_->{UNMERGED} } @all_mods;
1359         @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1360
1361         my @mods = grep { !($_->{BINARY}) } @all_mods;
1362         my @them;
1363
1364         if (!@mods) {
1365                 if (@all_mods) {
1366                         print STDERR __("Only binary files changed.\n");
1367                 } else {
1368                         print STDERR __("No changes.\n");
1369                 }
1370                 return 0;
1371         }
1372         if ($patch_mode_only) {
1373                 @them = @mods;
1374         }
1375         else {
1376                 @them = list_and_choose({ PROMPT => __('Patch update'),
1377                                           HEADER => $status_head, },
1378                                         @mods);
1379         }
1380         for (@them) {
1381                 return 0 if patch_update_file($_->{VALUE});
1382         }
1383 }
1384
1385 # Generate a one line summary of a hunk.
1386 sub summarize_hunk {
1387         my $rhunk = shift;
1388         my $summary = $rhunk->{TEXT}[0];
1389
1390         # Keep the line numbers, discard extra context.
1391         $summary =~ s/@@(.*?)@@.*/$1 /s;
1392         $summary .= " " x (20 - length $summary);
1393
1394         # Add some user context.
1395         for my $line (@{$rhunk->{TEXT}}) {
1396                 if ($line =~ m/^[+-].*\w/) {
1397                         $summary .= $line;
1398                         last;
1399                 }
1400         }
1401
1402         chomp $summary;
1403         return substr($summary, 0, 80) . "\n";
1404 }
1405
1406
1407 # Print a one-line summary of each hunk in the array ref in
1408 # the first argument, starting with the index in the 2nd.
1409 sub display_hunks {
1410         my ($hunks, $i) = @_;
1411         my $ctr = 0;
1412         $i ||= 0;
1413         for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1414                 my $status = " ";
1415                 if (defined $hunks->[$i]{USE}) {
1416                         $status = $hunks->[$i]{USE} ? "+" : "-";
1417                 }
1418                 printf "%s%2d: %s",
1419                         $status,
1420                         $i + 1,
1421                         summarize_hunk($hunks->[$i]);
1422         }
1423         return $i;
1424 }
1425
1426 my %patch_update_prompt_modes = (
1427         stage => {
1428                 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1429                 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1430                 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
1431         },
1432         stash => {
1433                 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1434                 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1435                 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
1436         },
1437         reset_head => {
1438                 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1439                 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1440                 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
1441         },
1442         reset_nothead => {
1443                 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1444                 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1445                 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
1446         },
1447         checkout_index => {
1448                 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1449                 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1450                 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1451         },
1452         checkout_head => {
1453                 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1454                 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1455                 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
1456         },
1457         checkout_nothead => {
1458                 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1459                 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1460                 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
1461         },
1462         worktree_head => {
1463                 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1464                 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1465                 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1466         },
1467         worktree_nothead => {
1468                 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1469                 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1470                 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1471         },
1472 );
1473
1474 sub patch_update_file {
1475         my $quit = 0;
1476         my ($ix, $num);
1477         my $path = shift;
1478         my ($head, @hunk) = parse_diff($path);
1479         ($head, my $mode, my $deletion) = parse_diff_header($head);
1480         for (@{$head->{DISPLAY}}) {
1481                 print;
1482         }
1483
1484         if (@{$mode->{TEXT}}) {
1485                 unshift @hunk, $mode;
1486         }
1487         if (@{$deletion->{TEXT}}) {
1488                 foreach my $hunk (@hunk) {
1489                         push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1490                         push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1491                 }
1492                 @hunk = ($deletion);
1493         }
1494
1495         $num = scalar @hunk;
1496         $ix = 0;
1497
1498         while (1) {
1499                 my ($prev, $next, $other, $undecided, $i);
1500                 $other = '';
1501
1502                 if ($num <= $ix) {
1503                         $ix = 0;
1504                 }
1505                 for ($i = 0; $i < $ix; $i++) {
1506                         if (!defined $hunk[$i]{USE}) {
1507                                 $prev = 1;
1508                                 $other .= ',k';
1509                                 last;
1510                         }
1511                 }
1512                 if ($ix) {
1513                         $other .= ',K';
1514                 }
1515                 for ($i = $ix + 1; $i < $num; $i++) {
1516                         if (!defined $hunk[$i]{USE}) {
1517                                 $next = 1;
1518                                 $other .= ',j';
1519                                 last;
1520                         }
1521                 }
1522                 if ($ix < $num - 1) {
1523                         $other .= ',J';
1524                 }
1525                 if ($num > 1) {
1526                         $other .= ',g,/';
1527                 }
1528                 for ($i = 0; $i < $num; $i++) {
1529                         if (!defined $hunk[$i]{USE}) {
1530                                 $undecided = 1;
1531                                 last;
1532                         }
1533                 }
1534                 last if (!$undecided);
1535
1536                 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1537                     hunk_splittable($hunk[$ix]{TEXT})) {
1538                         $other .= ',s';
1539                 }
1540                 if ($hunk[$ix]{TYPE} eq 'hunk') {
1541                         $other .= ',e';
1542                 }
1543                 for (@{$hunk[$ix]{DISPLAY}}) {
1544                         print;
1545                 }
1546                 print colored $prompt_color, "(", ($ix+1), "/$num) ",
1547                         sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1548
1549                 my $line = prompt_single_character;
1550                 last unless defined $line;
1551                 if ($line) {
1552                         if ($line =~ /^y/i) {
1553                                 $hunk[$ix]{USE} = 1;
1554                         }
1555                         elsif ($line =~ /^n/i) {
1556                                 $hunk[$ix]{USE} = 0;
1557                         }
1558                         elsif ($line =~ /^a/i) {
1559                                 while ($ix < $num) {
1560                                         if (!defined $hunk[$ix]{USE}) {
1561                                                 $hunk[$ix]{USE} = 1;
1562                                         }
1563                                         $ix++;
1564                                 }
1565                                 next;
1566                         }
1567                         elsif ($line =~ /^g(.*)/) {
1568                                 my $response = $1;
1569                                 unless ($other =~ /g/) {
1570                                         error_msg __("No other hunks to goto\n");
1571                                         next;
1572                                 }
1573                                 my $no = $ix > 10 ? $ix - 10 : 0;
1574                                 while ($response eq '') {
1575                                         $no = display_hunks(\@hunk, $no);
1576                                         if ($no < $num) {
1577                                                 print __("go to which hunk (<ret> to see more)? ");
1578                                         } else {
1579                                                 print __("go to which hunk? ");
1580                                         }
1581                                         $response = <STDIN>;
1582                                         if (!defined $response) {
1583                                                 $response = '';
1584                                         }
1585                                         chomp $response;
1586                                 }
1587                                 if ($response !~ /^\s*\d+\s*$/) {
1588                                         error_msg sprintf(__("Invalid number: '%s'\n"),
1589                                                              $response);
1590                                 } elsif (0 < $response && $response <= $num) {
1591                                         $ix = $response - 1;
1592                                 } else {
1593                                         error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1594                                                               "Sorry, only %d hunks available.\n", $num), $num);
1595                                 }
1596                                 next;
1597                         }
1598                         elsif ($line =~ /^d/i) {
1599                                 while ($ix < $num) {
1600                                         if (!defined $hunk[$ix]{USE}) {
1601                                                 $hunk[$ix]{USE} = 0;
1602                                         }
1603                                         $ix++;
1604                                 }
1605                                 next;
1606                         }
1607                         elsif ($line =~ /^q/i) {
1608                                 for ($i = 0; $i < $num; $i++) {
1609                                         if (!defined $hunk[$i]{USE}) {
1610                                                 $hunk[$i]{USE} = 0;
1611                                         }
1612                                 }
1613                                 $quit = 1;
1614                                 last;
1615                         }
1616                         elsif ($line =~ m|^/(.*)|) {
1617                                 my $regex = $1;
1618                                 unless ($other =~ m|/|) {
1619                                         error_msg __("No other hunks to search\n");
1620                                         next;
1621                                 }
1622                                 if ($regex eq "") {
1623                                         print colored $prompt_color, __("search for regex? ");
1624                                         $regex = <STDIN>;
1625                                         if (defined $regex) {
1626                                                 chomp $regex;
1627                                         }
1628                                 }
1629                                 my $search_string;
1630                                 eval {
1631                                         $search_string = qr{$regex}m;
1632                                 };
1633                                 if ($@) {
1634                                         my ($err,$exp) = ($@, $1);
1635                                         $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1636                                         error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
1637                                         next;
1638                                 }
1639                                 my $iy = $ix;
1640                                 while (1) {
1641                                         my $text = join ("", @{$hunk[$iy]{TEXT}});
1642                                         last if ($text =~ $search_string);
1643                                         $iy++;
1644                                         $iy = 0 if ($iy >= $num);
1645                                         if ($ix == $iy) {
1646                                                 error_msg __("No hunk matches the given pattern\n");
1647                                                 last;
1648                                         }
1649                                 }
1650                                 $ix = $iy;
1651                                 next;
1652                         }
1653                         elsif ($line =~ /^K/) {
1654                                 if ($other =~ /K/) {
1655                                         $ix--;
1656                                 }
1657                                 else {
1658                                         error_msg __("No previous hunk\n");
1659                                 }
1660                                 next;
1661                         }
1662                         elsif ($line =~ /^J/) {
1663                                 if ($other =~ /J/) {
1664                                         $ix++;
1665                                 }
1666                                 else {
1667                                         error_msg __("No next hunk\n");
1668                                 }
1669                                 next;
1670                         }
1671                         elsif ($line =~ /^k/) {
1672                                 if ($other =~ /k/) {
1673                                         while (1) {
1674                                                 $ix--;
1675                                                 last if (!$ix ||
1676                                                          !defined $hunk[$ix]{USE});
1677                                         }
1678                                 }
1679                                 else {
1680                                         error_msg __("No previous hunk\n");
1681                                 }
1682                                 next;
1683                         }
1684                         elsif ($line =~ /^j/) {
1685                                 if ($other !~ /j/) {
1686                                         error_msg __("No next hunk\n");
1687                                         next;
1688                                 }
1689                         }
1690                         elsif ($line =~ /^s/) {
1691                                 unless ($other =~ /s/) {
1692                                         error_msg __("Sorry, cannot split this hunk\n");
1693                                         next;
1694                                 }
1695                                 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
1696                                 if (1 < @split) {
1697                                         print colored $header_color, sprintf(
1698                                                 __n("Split into %d hunk.\n",
1699                                                     "Split into %d hunks.\n",
1700                                                     scalar(@split)), scalar(@split));
1701                                 }
1702                                 splice (@hunk, $ix, 1, @split);
1703                                 $num = scalar @hunk;
1704                                 next;
1705                         }
1706                         elsif ($line =~ /^e/) {
1707                                 unless ($other =~ /e/) {
1708                                         error_msg __("Sorry, cannot edit this hunk\n");
1709                                         next;
1710                                 }
1711                                 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1712                                 if (defined $newhunk) {
1713                                         splice @hunk, $ix, 1, $newhunk;
1714                                 }
1715                         }
1716                         else {
1717                                 help_patch_cmd($other);
1718                                 next;
1719                         }
1720                         # soft increment
1721                         while (1) {
1722                                 $ix++;
1723                                 last if ($ix >= $num ||
1724                                          !defined $hunk[$ix]{USE});
1725                         }
1726                 }
1727         }
1728
1729         @hunk = coalesce_overlapping_hunks(@hunk);
1730
1731         my $n_lofs = 0;
1732         my @result = ();
1733         for (@hunk) {
1734                 if ($_->{USE}) {
1735                         push @result, @{$_->{TEXT}};
1736                 }
1737         }
1738
1739         if (@result) {
1740                 my @patch = reassemble_patch($head->{TEXT}, @result);
1741                 my $apply_routine = $patch_mode_flavour{APPLY};
1742                 &$apply_routine(@patch);
1743                 refresh();
1744         }
1745
1746         print "\n";
1747         return $quit;
1748 }
1749
1750 sub diff_cmd {
1751         my @mods = list_modified('index-only');
1752         @mods = grep { !($_->{BINARY}) } @mods;
1753         return if (!@mods);
1754         my (@them) = list_and_choose({ PROMPT => __('Review diff'),
1755                                      IMMEDIATE => 1,
1756                                      HEADER => $status_head, },
1757                                    @mods);
1758         return if (!@them);
1759         my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
1760         system(qw(git diff -p --cached), $reference, '--',
1761                 map { $_->{VALUE} } @them);
1762 }
1763
1764 sub quit_cmd {
1765         print __("Bye.\n");
1766         exit(0);
1767 }
1768
1769 sub help_cmd {
1770 # TRANSLATORS: please do not translate the command names
1771 # 'status', 'update', 'revert', etc.
1772         print colored $help_color, __ <<'EOF' ;
1773 status        - show paths with changes
1774 update        - add working tree state to the staged set of changes
1775 revert        - revert staged set of changes back to the HEAD version
1776 patch         - pick hunks and update selectively
1777 diff          - view diff between HEAD and index
1778 add untracked - add contents of untracked files to the staged set of changes
1779 EOF
1780 }
1781
1782 sub process_args {
1783         return unless @ARGV;
1784         my $arg = shift @ARGV;
1785         if ($arg =~ /--patch(?:=(.*))?/) {
1786                 if (defined $1) {
1787                         if ($1 eq 'reset') {
1788                                 $patch_mode = 'reset_head';
1789                                 $patch_mode_revision = 'HEAD';
1790                                 $arg = shift @ARGV or die __("missing --");
1791                                 if ($arg ne '--') {
1792                                         $patch_mode_revision = $arg;
1793                                         $patch_mode = ($arg eq 'HEAD' ?
1794                                                        'reset_head' : 'reset_nothead');
1795                                         $arg = shift @ARGV or die __("missing --");
1796                                 }
1797                         } elsif ($1 eq 'checkout') {
1798                                 $arg = shift @ARGV or die __("missing --");
1799                                 if ($arg eq '--') {
1800                                         $patch_mode = 'checkout_index';
1801                                 } else {
1802                                         $patch_mode_revision = $arg;
1803                                         $patch_mode = ($arg eq 'HEAD' ?
1804                                                        'checkout_head' : 'checkout_nothead');
1805                                         $arg = shift @ARGV or die __("missing --");
1806                                 }
1807                         } elsif ($1 eq 'worktree') {
1808                                 $arg = shift @ARGV or die __("missing --");
1809                                 if ($arg eq '--') {
1810                                         $patch_mode = 'checkout_index';
1811                                 } else {
1812                                         $patch_mode_revision = $arg;
1813                                         $patch_mode = ($arg eq 'HEAD' ?
1814                                                        'worktree_head' : 'worktree_nothead');
1815                                         $arg = shift @ARGV or die __("missing --");
1816                                 }
1817                         } elsif ($1 eq 'stage' or $1 eq 'stash') {
1818                                 $patch_mode = $1;
1819                                 $arg = shift @ARGV or die __("missing --");
1820                         } else {
1821                                 die sprintf(__("unknown --patch mode: %s"), $1);
1822                         }
1823                 } else {
1824                         $patch_mode = 'stage';
1825                         $arg = shift @ARGV or die __("missing --");
1826                 }
1827                 die sprintf(__("invalid argument %s, expecting --"),
1828                                $arg) unless $arg eq "--";
1829                 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1830                 $patch_mode_only = 1;
1831         }
1832         elsif ($arg ne "--") {
1833                 die sprintf(__("invalid argument %s, expecting --"), $arg);
1834         }
1835 }
1836
1837 sub main_loop {
1838         my @cmd = ([ 'status', \&status_cmd, ],
1839                    [ 'update', \&update_cmd, ],
1840                    [ 'revert', \&revert_cmd, ],
1841                    [ 'add untracked', \&add_untracked_cmd, ],
1842                    [ 'patch', \&patch_update_cmd, ],
1843                    [ 'diff', \&diff_cmd, ],
1844                    [ 'quit', \&quit_cmd, ],
1845                    [ 'help', \&help_cmd, ],
1846         );
1847         while (1) {
1848                 my ($it) = list_and_choose({ PROMPT => __('What now'),
1849                                              SINGLETON => 1,
1850                                              LIST_FLAT => 4,
1851                                              HEADER => __('*** Commands ***'),
1852                                              ON_EOF => \&quit_cmd,
1853                                              IMMEDIATE => 1 }, @cmd);
1854                 if ($it) {
1855                         eval {
1856                                 $it->[1]->();
1857                         };
1858                         if ($@) {
1859                                 print "$@";
1860                         }
1861                 }
1862         }
1863 }
1864
1865 process_args();
1866 refresh();
1867 if ($patch_mode_only) {
1868         patch_update_cmd();
1869 }
1870 else {
1871         status_cmd();
1872         main_loop();
1873 }