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