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