restore: fix typo in docs
[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                 return <$fh>;
181         }
182 }
183
184 my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
185
186 if (!defined $GIT_DIR) {
187         exit(1); # rev-parse would have already said "not a git repo"
188 }
189 chomp($GIT_DIR);
190
191 sub refresh {
192         my $fh;
193         open $fh, 'git update-index --refresh |'
194             or die;
195         while (<$fh>) {
196                 ;# ignore 'needs update'
197         }
198         close $fh;
199 }
200
201 sub list_untracked {
202         map {
203                 chomp $_;
204                 unquote_path($_);
205         }
206         run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
207 }
208
209 # TRANSLATORS: you can adjust this to align "git add -i" status menu
210 my $status_fmt = __('%12s %12s %s');
211 my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
212
213 {
214         my $initial;
215         sub is_initial_commit {
216                 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
217                         unless defined $initial;
218                 return $initial;
219         }
220 }
221
222 {
223         my $empty_tree;
224         sub get_empty_tree {
225                 return $empty_tree if defined $empty_tree;
226
227                 $empty_tree = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
228                 chomp $empty_tree;
229                 return $empty_tree;
230         }
231 }
232
233 sub get_diff_reference {
234         my $ref = shift;
235         if (defined $ref and $ref ne 'HEAD') {
236                 return $ref;
237         } elsif (is_initial_commit()) {
238                 return get_empty_tree();
239         } else {
240                 return 'HEAD';
241         }
242 }
243
244 # Returns list of hashes, contents of each of which are:
245 # VALUE:        pathname
246 # BINARY:       is a binary path
247 # INDEX:        is index different from HEAD?
248 # FILE:         is file different from index?
249 # INDEX_ADDDEL: is it add/delete between HEAD and index?
250 # FILE_ADDDEL:  is it add/delete between index and file?
251 # UNMERGED:     is the path unmerged
252
253 sub list_modified {
254         my ($only) = @_;
255         my (%data, @return);
256         my ($add, $del, $adddel, $file);
257
258         my $reference = get_diff_reference($patch_mode_revision);
259         for (run_cmd_pipe(qw(git diff-index --cached
260                              --numstat --summary), $reference,
261                              '--', @ARGV)) {
262                 if (($add, $del, $file) =
263                     /^([-\d]+)  ([-\d]+)        (.*)/) {
264                         my ($change, $bin);
265                         $file = unquote_path($file);
266                         if ($add eq '-' && $del eq '-') {
267                                 $change = __('binary');
268                                 $bin = 1;
269                         }
270                         else {
271                                 $change = "+$add/-$del";
272                         }
273                         $data{$file} = {
274                                 INDEX => $change,
275                                 BINARY => $bin,
276                                 FILE => __('nothing'),
277                         }
278                 }
279                 elsif (($adddel, $file) =
280                        /^ (create|delete) mode [0-7]+ (.*)$/) {
281                         $file = unquote_path($file);
282                         $data{$file}{INDEX_ADDDEL} = $adddel;
283                 }
284         }
285
286         for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
287                 if (($add, $del, $file) =
288                     /^([-\d]+)  ([-\d]+)        (.*)/) {
289                         $file = unquote_path($file);
290                         my ($change, $bin);
291                         if ($add eq '-' && $del eq '-') {
292                                 $change = __('binary');
293                                 $bin = 1;
294                         }
295                         else {
296                                 $change = "+$add/-$del";
297                         }
298                         $data{$file}{FILE} = $change;
299                         if ($bin) {
300                                 $data{$file}{BINARY} = 1;
301                         }
302                 }
303                 elsif (($adddel, $file) =
304                        /^ (create|delete) mode [0-7]+ (.*)$/) {
305                         $file = unquote_path($file);
306                         $data{$file}{FILE_ADDDEL} = $adddel;
307                 }
308                 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
309                         $file = unquote_path($2);
310                         if (!exists $data{$file}) {
311                                 $data{$file} = +{
312                                         INDEX => __('unchanged'),
313                                         BINARY => 0,
314                                 };
315                         }
316                         if ($1 eq 'U') {
317                                 $data{$file}{UNMERGED} = 1;
318                         }
319                 }
320         }
321
322         for (sort keys %data) {
323                 my $it = $data{$_};
324
325                 if ($only) {
326                         if ($only eq 'index-only') {
327                                 next if ($it->{INDEX} eq __('unchanged'));
328                         }
329                         if ($only eq 'file-only') {
330                                 next if ($it->{FILE} eq __('nothing'));
331                         }
332                 }
333                 push @return, +{
334                         VALUE => $_,
335                         %$it,
336                 };
337         }
338         return @return;
339 }
340
341 sub find_unique {
342         my ($string, @stuff) = @_;
343         my $found = undef;
344         for (my $i = 0; $i < @stuff; $i++) {
345                 my $it = $stuff[$i];
346                 my $hit = undef;
347                 if (ref $it) {
348                         if ((ref $it) eq 'ARRAY') {
349                                 $it = $it->[0];
350                         }
351                         else {
352                                 $it = $it->{VALUE};
353                         }
354                 }
355                 eval {
356                         if ($it =~ /^$string/) {
357                                 $hit = 1;
358                         };
359                 };
360                 if (defined $hit && defined $found) {
361                         return undef;
362                 }
363                 if ($hit) {
364                         $found = $i + 1;
365                 }
366         }
367         return $found;
368 }
369
370 # inserts string into trie and updates count for each character
371 sub update_trie {
372         my ($trie, $string) = @_;
373         foreach (split //, $string) {
374                 $trie = $trie->{$_} ||= {COUNT => 0};
375                 $trie->{COUNT}++;
376         }
377 }
378
379 # returns an array of tuples (prefix, remainder)
380 sub find_unique_prefixes {
381         my @stuff = @_;
382         my @return = ();
383
384         # any single prefix exceeding the soft limit is omitted
385         # if any prefix exceeds the hard limit all are omitted
386         # 0 indicates no limit
387         my $soft_limit = 0;
388         my $hard_limit = 3;
389
390         # build a trie modelling all possible options
391         my %trie;
392         foreach my $print (@stuff) {
393                 if ((ref $print) eq 'ARRAY') {
394                         $print = $print->[0];
395                 }
396                 elsif ((ref $print) eq 'HASH') {
397                         $print = $print->{VALUE};
398                 }
399                 update_trie(\%trie, $print);
400                 push @return, $print;
401         }
402
403         # use the trie to find the unique prefixes
404         for (my $i = 0; $i < @return; $i++) {
405                 my $ret = $return[$i];
406                 my @letters = split //, $ret;
407                 my %search = %trie;
408                 my ($prefix, $remainder);
409                 my $j;
410                 for ($j = 0; $j < @letters; $j++) {
411                         my $letter = $letters[$j];
412                         if ($search{$letter}{COUNT} == 1) {
413                                 $prefix = substr $ret, 0, $j + 1;
414                                 $remainder = substr $ret, $j + 1;
415                                 last;
416                         }
417                         else {
418                                 my $prefix = substr $ret, 0, $j;
419                                 return ()
420                                     if ($hard_limit && $j + 1 > $hard_limit);
421                         }
422                         %search = %{$search{$letter}};
423                 }
424                 if (ord($letters[0]) > 127 ||
425                     ($soft_limit && $j + 1 > $soft_limit)) {
426                         $prefix = undef;
427                         $remainder = $ret;
428                 }
429                 $return[$i] = [$prefix, $remainder];
430         }
431         return @return;
432 }
433
434 # filters out prefixes which have special meaning to list_and_choose()
435 sub is_valid_prefix {
436         my $prefix = shift;
437         return (defined $prefix) &&
438             !($prefix =~ /[\s,]/) && # separators
439             !($prefix =~ /^-/) &&    # deselection
440             !($prefix =~ /^\d+/) &&  # selection
441             ($prefix ne '*') &&      # "all" wildcard
442             ($prefix ne '?');        # prompt help
443 }
444
445 # given a prefix/remainder tuple return a string with the prefix highlighted
446 # for now use square brackets; later might use ANSI colors (underline, bold)
447 sub highlight_prefix {
448         my $prefix = shift;
449         my $remainder = shift;
450
451         if (!defined $prefix) {
452                 return $remainder;
453         }
454
455         if (!is_valid_prefix($prefix)) {
456                 return "$prefix$remainder";
457         }
458
459         if (!$menu_use_color) {
460                 return "[$prefix]$remainder";
461         }
462
463         return "$prompt_color$prefix$normal_color$remainder";
464 }
465
466 sub error_msg {
467         print STDERR colored $error_color, @_;
468 }
469
470 sub list_and_choose {
471         my ($opts, @stuff) = @_;
472         my (@chosen, @return);
473         if (!@stuff) {
474             return @return;
475         }
476         my $i;
477         my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
478
479       TOPLOOP:
480         while (1) {
481                 my $last_lf = 0;
482
483                 if ($opts->{HEADER}) {
484                         if (!$opts->{LIST_FLAT}) {
485                                 print "     ";
486                         }
487                         print colored $header_color, "$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, "--", $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
756         for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
757                 my $dest =
758                    $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
759                    $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
760                    $head;
761                 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
762                 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
763         }
764         return ($head, $mode, $deletion);
765 }
766
767 sub hunk_splittable {
768         my ($text) = @_;
769
770         my @s = split_hunk($text);
771         return (1 < @s);
772 }
773
774 sub parse_hunk_header {
775         my ($line) = @_;
776         my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
777             $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
778         $o_cnt = 1 unless defined $o_cnt;
779         $n_cnt = 1 unless defined $n_cnt;
780         return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
781 }
782
783 sub format_hunk_header {
784         my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
785         return ("@@ -$o_ofs" .
786                 (($o_cnt != 1) ? ",$o_cnt" : '') .
787                 " +$n_ofs" .
788                 (($n_cnt != 1) ? ",$n_cnt" : '') .
789                 " @@\n");
790 }
791
792 sub split_hunk {
793         my ($text, $display) = @_;
794         my @split = ();
795         if (!defined $display) {
796                 $display = $text;
797         }
798         # If there are context lines in the middle of a hunk,
799         # it can be split, but we would need to take care of
800         # overlaps later.
801
802         my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
803         my $hunk_start = 1;
804
805       OUTER:
806         while (1) {
807                 my $next_hunk_start = undef;
808                 my $i = $hunk_start - 1;
809                 my $this = +{
810                         TEXT => [],
811                         DISPLAY => [],
812                         TYPE => 'hunk',
813                         OLD => $o_ofs,
814                         NEW => $n_ofs,
815                         OCNT => 0,
816                         NCNT => 0,
817                         ADDDEL => 0,
818                         POSTCTX => 0,
819                         USE => undef,
820                 };
821
822                 while (++$i < @$text) {
823                         my $line = $text->[$i];
824                         my $display = $display->[$i];
825                         if ($line =~ /^\\/) {
826                                 push @{$this->{TEXT}}, $line;
827                                 push @{$this->{DISPLAY}}, $display;
828                                 next;
829                         }
830                         if ($line =~ /^ /) {
831                                 if ($this->{ADDDEL} &&
832                                     !defined $next_hunk_start) {
833                                         # We have seen leading context and
834                                         # adds/dels and then here is another
835                                         # context, which is trailing for this
836                                         # split hunk and leading for the next
837                                         # one.
838                                         $next_hunk_start = $i;
839                                 }
840                                 push @{$this->{TEXT}}, $line;
841                                 push @{$this->{DISPLAY}}, $display;
842                                 $this->{OCNT}++;
843                                 $this->{NCNT}++;
844                                 if (defined $next_hunk_start) {
845                                         $this->{POSTCTX}++;
846                                 }
847                                 next;
848                         }
849
850                         # add/del
851                         if (defined $next_hunk_start) {
852                                 # We are done with the current hunk and
853                                 # this is the first real change for the
854                                 # next split one.
855                                 $hunk_start = $next_hunk_start;
856                                 $o_ofs = $this->{OLD} + $this->{OCNT};
857                                 $n_ofs = $this->{NEW} + $this->{NCNT};
858                                 $o_ofs -= $this->{POSTCTX};
859                                 $n_ofs -= $this->{POSTCTX};
860                                 push @split, $this;
861                                 redo OUTER;
862                         }
863                         push @{$this->{TEXT}}, $line;
864                         push @{$this->{DISPLAY}}, $display;
865                         $this->{ADDDEL}++;
866                         if ($line =~ /^-/) {
867                                 $this->{OCNT}++;
868                         }
869                         else {
870                                 $this->{NCNT}++;
871                         }
872                 }
873
874                 push @split, $this;
875                 last;
876         }
877
878         for my $hunk (@split) {
879                 $o_ofs = $hunk->{OLD};
880                 $n_ofs = $hunk->{NEW};
881                 my $o_cnt = $hunk->{OCNT};
882                 my $n_cnt = $hunk->{NCNT};
883
884                 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
885                 my $display_head = $head;
886                 unshift @{$hunk->{TEXT}}, $head;
887                 if ($diff_use_color) {
888                         $display_head = colored($fraginfo_color, $head);
889                 }
890                 unshift @{$hunk->{DISPLAY}}, $display_head;
891         }
892         return @split;
893 }
894
895 sub find_last_o_ctx {
896         my ($it) = @_;
897         my $text = $it->{TEXT};
898         my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
899         my $i = @{$text};
900         my $last_o_ctx = $o_ofs + $o_cnt;
901         while (0 < --$i) {
902                 my $line = $text->[$i];
903                 if ($line =~ /^ /) {
904                         $last_o_ctx--;
905                         next;
906                 }
907                 last;
908         }
909         return $last_o_ctx;
910 }
911
912 sub merge_hunk {
913         my ($prev, $this) = @_;
914         my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
915             parse_hunk_header($prev->{TEXT}[0]);
916         my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
917             parse_hunk_header($this->{TEXT}[0]);
918
919         my (@line, $i, $ofs, $o_cnt, $n_cnt);
920         $ofs = $o0_ofs;
921         $o_cnt = $n_cnt = 0;
922         for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
923                 my $line = $prev->{TEXT}[$i];
924                 if ($line =~ /^\+/) {
925                         $n_cnt++;
926                         push @line, $line;
927                         next;
928                 } elsif ($line =~ /^\\/) {
929                         push @line, $line;
930                         next;
931                 }
932
933                 last if ($o1_ofs <= $ofs);
934
935                 $o_cnt++;
936                 $ofs++;
937                 if ($line =~ /^ /) {
938                         $n_cnt++;
939                 }
940                 push @line, $line;
941         }
942
943         for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
944                 my $line = $this->{TEXT}[$i];
945                 if ($line =~ /^\+/) {
946                         $n_cnt++;
947                         push @line, $line;
948                         next;
949                 } elsif ($line =~ /^\\/) {
950                         push @line, $line;
951                         next;
952                 }
953                 $ofs++;
954                 $o_cnt++;
955                 if ($line =~ /^ /) {
956                         $n_cnt++;
957                 }
958                 push @line, $line;
959         }
960         my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
961         @{$prev->{TEXT}} = ($head, @line);
962 }
963
964 sub coalesce_overlapping_hunks {
965         my (@in) = @_;
966         my @out = ();
967
968         my ($last_o_ctx, $last_was_dirty);
969         my $ofs_delta = 0;
970
971         for (@in) {
972                 if ($_->{TYPE} ne 'hunk') {
973                         push @out, $_;
974                         next;
975                 }
976                 my $text = $_->{TEXT};
977                 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
978                                                 parse_hunk_header($text->[0]);
979                 unless ($_->{USE}) {
980                         $ofs_delta += $o_cnt - $n_cnt;
981                         # If this hunk has been edited then subtract
982                         # the delta that is due to the edit.
983                         if ($_->{OFS_DELTA}) {
984                                 $ofs_delta -= $_->{OFS_DELTA};
985                         }
986                         next;
987                 }
988                 if ($ofs_delta) {
989                         $n_ofs += $ofs_delta;
990                         $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
991                                                              $n_ofs, $n_cnt);
992                 }
993                 # If this hunk was edited then adjust the offset delta
994                 # to reflect the edit.
995                 if ($_->{OFS_DELTA}) {
996                         $ofs_delta += $_->{OFS_DELTA};
997                 }
998                 if (defined $last_o_ctx &&
999                     $o_ofs <= $last_o_ctx &&
1000                     !$_->{DIRTY} &&
1001                     !$last_was_dirty) {
1002                         merge_hunk($out[-1], $_);
1003                 }
1004                 else {
1005                         push @out, $_;
1006                 }
1007                 $last_o_ctx = find_last_o_ctx($out[-1]);
1008                 $last_was_dirty = $_->{DIRTY};
1009         }
1010         return @out;
1011 }
1012
1013 sub reassemble_patch {
1014         my $head = shift;
1015         my @patch;
1016
1017         # Include everything in the header except the beginning of the diff.
1018         push @patch, (grep { !/^[-+]{3}/ } @$head);
1019
1020         # Then include any headers from the hunk lines, which must
1021         # come before any actual hunk.
1022         while (@_ && $_[0] !~ /^@/) {
1023                 push @patch, shift;
1024         }
1025
1026         # Then begin the diff.
1027         push @patch, grep { /^[-+]{3}/ } @$head;
1028
1029         # And then the actual hunks.
1030         push @patch, @_;
1031
1032         return @patch;
1033 }
1034
1035 sub color_diff {
1036         return map {
1037                 colored((/^@/  ? $fraginfo_color :
1038                          /^\+/ ? $diff_new_color :
1039                          /^-/  ? $diff_old_color :
1040                          $diff_plain_color),
1041                         $_);
1042         } @_;
1043 }
1044
1045 my %edit_hunk_manually_modes = (
1046         stage => N__(
1047 "If the patch applies cleanly, the edited hunk will immediately be
1048 marked for staging."),
1049         stash => N__(
1050 "If the patch applies cleanly, the edited hunk will immediately be
1051 marked for stashing."),
1052         reset_head => N__(
1053 "If the patch applies cleanly, the edited hunk will immediately be
1054 marked for unstaging."),
1055         reset_nothead => N__(
1056 "If the patch applies cleanly, the edited hunk will immediately be
1057 marked for applying."),
1058         checkout_index => N__(
1059 "If the patch applies cleanly, the edited hunk will immediately be
1060 marked for discarding."),
1061         checkout_head => N__(
1062 "If the patch applies cleanly, the edited hunk will immediately be
1063 marked for discarding."),
1064         checkout_nothead => N__(
1065 "If the patch applies cleanly, the edited hunk will immediately be
1066 marked for applying."),
1067         worktree_head => N__(
1068 "If the patch applies cleanly, the edited hunk will immediately be
1069 marked for discarding."),
1070         worktree_nothead => N__(
1071 "If the patch applies cleanly, the edited hunk will immediately be
1072 marked for applying."),
1073 );
1074
1075 sub recount_edited_hunk {
1076         local $_;
1077         my ($oldtext, $newtext) = @_;
1078         my ($o_cnt, $n_cnt) = (0, 0);
1079         for (@{$newtext}[1..$#{$newtext}]) {
1080                 my $mode = substr($_, 0, 1);
1081                 if ($mode eq '-') {
1082                         $o_cnt++;
1083                 } elsif ($mode eq '+') {
1084                         $n_cnt++;
1085                 } elsif ($mode eq ' ' or $mode eq "\n") {
1086                         $o_cnt++;
1087                         $n_cnt++;
1088                 }
1089         }
1090         my ($o_ofs, undef, $n_ofs, undef) =
1091                                         parse_hunk_header($newtext->[0]);
1092         $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1093         my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1094                                         parse_hunk_header($oldtext->[0]);
1095         # Return the change in the number of lines inserted by this hunk
1096         return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1097 }
1098
1099 sub edit_hunk_manually {
1100         my ($oldtext) = @_;
1101
1102         my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1103         my $fh;
1104         open $fh, '>', $hunkfile
1105                 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
1106         print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
1107         print $fh @$oldtext;
1108         my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1109         my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
1110         my $comment_line_char = Git::get_comment_line_char;
1111         print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1112 ---
1113 To remove '%s' lines, make them ' ' lines (context).
1114 To remove '%s' lines, delete them.
1115 Lines starting with %s will be removed.
1116 EOF
1117 __($edit_hunk_manually_modes{$patch_mode}),
1118 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1119 __ <<EOF2 ;
1120 If it does not apply cleanly, you will be given an opportunity to
1121 edit again.  If all lines of the hunk are removed, then the edit is
1122 aborted and the hunk is left unchanged.
1123 EOF2
1124         close $fh;
1125
1126         chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
1127         system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1128
1129         if ($? != 0) {
1130                 return undef;
1131         }
1132
1133         open $fh, '<', $hunkfile
1134                 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
1135         my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1136         close $fh;
1137         unlink $hunkfile;
1138
1139         # Abort if nothing remains
1140         if (!grep { /\S/ } @newtext) {
1141                 return undef;
1142         }
1143
1144         # Reinsert the first hunk header if the user accidentally deleted it
1145         if ($newtext[0] !~ /^@/) {
1146                 unshift @newtext, $oldtext->[0];
1147         }
1148         return \@newtext;
1149 }
1150
1151 sub diff_applies {
1152         return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
1153                              map { @{$_->{TEXT}} } @_);
1154 }
1155
1156 sub _restore_terminal_and_die {
1157         ReadMode 'restore';
1158         print "\n";
1159         exit 1;
1160 }
1161
1162 sub prompt_single_character {
1163         if ($use_readkey) {
1164                 local $SIG{TERM} = \&_restore_terminal_and_die;
1165                 local $SIG{INT} = \&_restore_terminal_and_die;
1166                 ReadMode 'cbreak';
1167                 my $key = ReadKey 0;
1168                 ReadMode 'restore';
1169                 if ($use_termcap and $key eq "\e") {
1170                         while (!defined $term_escapes{$key}) {
1171                                 my $next = ReadKey 0.5;
1172                                 last if (!defined $next);
1173                                 $key .= $next;
1174                         }
1175                         $key =~ s/\e/^[/;
1176                 }
1177                 print "$key" if defined $key;
1178                 print "\n";
1179                 return $key;
1180         } else {
1181                 return <STDIN>;
1182         }
1183 }
1184
1185 sub prompt_yesno {
1186         my ($prompt) = @_;
1187         while (1) {
1188                 print colored $prompt_color, $prompt;
1189                 my $line = prompt_single_character;
1190                 return undef unless defined $line;
1191                 return 0 if $line =~ /^n/i;
1192                 return 1 if $line =~ /^y/i;
1193         }
1194 }
1195
1196 sub edit_hunk_loop {
1197         my ($head, $hunks, $ix) = @_;
1198         my $hunk = $hunks->[$ix];
1199         my $text = $hunk->{TEXT};
1200
1201         while (1) {
1202                 my $newtext = edit_hunk_manually($text);
1203                 if (!defined $newtext) {
1204                         return undef;
1205                 }
1206                 my $newhunk = {
1207                         TEXT => $newtext,
1208                         TYPE => $hunk->{TYPE},
1209                         USE => 1,
1210                         DIRTY => 1,
1211                 };
1212                 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1213                 # If this hunk has already been edited then add the
1214                 # offset delta of the previous edit to get the real
1215                 # delta from the original unedited hunk.
1216                 $hunk->{OFS_DELTA} and
1217                                 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
1218                 if (diff_applies($head,
1219                                  @{$hunks}[0..$ix-1],
1220                                  $newhunk,
1221                                  @{$hunks}[$ix+1..$#{$hunks}])) {
1222                         $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
1223                         return $newhunk;
1224                 }
1225                 else {
1226                         prompt_yesno(
1227                                 # TRANSLATORS: do not translate [y/n]
1228                                 # The program will only accept that input
1229                                 # at this point.
1230                                 # Consider translating (saying "no" discards!) as
1231                                 # (saying "n" for "no" discards!) if the translation
1232                                 # of the word "no" does not start with n.
1233                                 __('Your edited hunk does not apply. Edit again '
1234                                    . '(saying "no" discards!) [y/n]? ')
1235                                 ) or return undef;
1236                 }
1237         }
1238 }
1239
1240 my %help_patch_modes = (
1241         stage => N__(
1242 "y - stage this hunk
1243 n - do not stage this hunk
1244 q - quit; do not stage this hunk or any of the remaining ones
1245 a - stage this hunk and all later hunks in the file
1246 d - do not stage this hunk or any of the later hunks in the file"),
1247         stash => N__(
1248 "y - stash this hunk
1249 n - do not stash this hunk
1250 q - quit; do not stash this hunk or any of the remaining ones
1251 a - stash this hunk and all later hunks in the file
1252 d - do not stash this hunk or any of the later hunks in the file"),
1253         reset_head => N__(
1254 "y - unstage this hunk
1255 n - do not unstage this hunk
1256 q - quit; do not unstage this hunk or any of the remaining ones
1257 a - unstage this hunk and all later hunks in the file
1258 d - do not unstage this hunk or any of the later hunks in the file"),
1259         reset_nothead => N__(
1260 "y - apply this hunk to index
1261 n - do not apply this hunk to index
1262 q - quit; do not apply this hunk or any of the remaining ones
1263 a - apply this hunk and all later hunks in the file
1264 d - do not apply this hunk or any of the later hunks in the file"),
1265         checkout_index => N__(
1266 "y - discard this hunk from worktree
1267 n - do not discard this hunk from worktree
1268 q - quit; do not discard this hunk or any of the remaining ones
1269 a - discard this hunk and all later hunks in the file
1270 d - do not discard this hunk or any of the later hunks in the file"),
1271         checkout_head => N__(
1272 "y - discard this hunk from index and worktree
1273 n - do not discard this hunk from index and worktree
1274 q - quit; do not discard this hunk or any of the remaining ones
1275 a - discard this hunk and all later hunks in the file
1276 d - do not discard this hunk or any of the later hunks in the file"),
1277         checkout_nothead => N__(
1278 "y - apply this hunk to index and worktree
1279 n - do not apply this hunk to index and worktree
1280 q - quit; do not apply this hunk or any of the remaining ones
1281 a - apply this hunk and all later hunks in the file
1282 d - do not apply this hunk or any of the later hunks in the file"),
1283         worktree_head => N__(
1284 "y - discard this hunk from worktree
1285 n - do not discard this hunk from worktree
1286 q - quit; do not discard this hunk or any of the remaining ones
1287 a - discard this hunk and all later hunks in the file
1288 d - do not discard this hunk or any of the later hunks in the file"),
1289         worktree_nothead => N__(
1290 "y - apply this hunk to worktree
1291 n - do not apply this hunk to worktree
1292 q - quit; do not apply this hunk or any of the remaining ones
1293 a - apply this hunk and all later hunks in the file
1294 d - do not apply this hunk or any of the later hunks in the file"),
1295 );
1296
1297 sub help_patch_cmd {
1298         local $_;
1299         my $other = $_[0] . ",?";
1300         print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1301                 map { "$_\n" } grep {
1302                         my $c = quotemeta(substr($_, 0, 1));
1303                         $other =~ /,$c/
1304                 } split "\n", __ <<EOF ;
1305 g - select a hunk to go to
1306 / - search for a hunk matching the given regex
1307 j - leave this hunk undecided, see next undecided hunk
1308 J - leave this hunk undecided, see next hunk
1309 k - leave this hunk undecided, see previous undecided hunk
1310 K - leave this hunk undecided, see previous hunk
1311 s - split the current hunk into smaller hunks
1312 e - manually edit the current hunk
1313 ? - print help
1314 EOF
1315 }
1316
1317 sub apply_patch {
1318         my $cmd = shift;
1319         my $ret = run_git_apply $cmd, @_;
1320         if (!$ret) {
1321                 print STDERR @_;
1322         }
1323         return $ret;
1324 }
1325
1326 sub apply_patch_for_checkout_commit {
1327         my $reverse = shift;
1328         my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1329         my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
1330
1331         if ($applies_worktree && $applies_index) {
1332                 run_git_apply 'apply '.$reverse.' --cached', @_;
1333                 run_git_apply 'apply '.$reverse, @_;
1334                 return 1;
1335         } elsif (!$applies_index) {
1336                 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1337                 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
1338                         return run_git_apply 'apply '.$reverse, @_;
1339                 } else {
1340                         print colored $error_color, __("Nothing was applied.\n");
1341                         return 0;
1342                 }
1343         } else {
1344                 print STDERR @_;
1345                 return 0;
1346         }
1347 }
1348
1349 sub patch_update_cmd {
1350         my @all_mods = list_modified($patch_mode_flavour{FILTER});
1351         error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
1352                 for grep { $_->{UNMERGED} } @all_mods;
1353         @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1354
1355         my @mods = grep { !($_->{BINARY}) } @all_mods;
1356         my @them;
1357
1358         if (!@mods) {
1359                 if (@all_mods) {
1360                         print STDERR __("Only binary files changed.\n");
1361                 } else {
1362                         print STDERR __("No changes.\n");
1363                 }
1364                 return 0;
1365         }
1366         if ($patch_mode_only) {
1367                 @them = @mods;
1368         }
1369         else {
1370                 @them = list_and_choose({ PROMPT => __('Patch update'),
1371                                           HEADER => $status_head, },
1372                                         @mods);
1373         }
1374         for (@them) {
1375                 return 0 if patch_update_file($_->{VALUE});
1376         }
1377 }
1378
1379 # Generate a one line summary of a hunk.
1380 sub summarize_hunk {
1381         my $rhunk = shift;
1382         my $summary = $rhunk->{TEXT}[0];
1383
1384         # Keep the line numbers, discard extra context.
1385         $summary =~ s/@@(.*?)@@.*/$1 /s;
1386         $summary .= " " x (20 - length $summary);
1387
1388         # Add some user context.
1389         for my $line (@{$rhunk->{TEXT}}) {
1390                 if ($line =~ m/^[+-].*\w/) {
1391                         $summary .= $line;
1392                         last;
1393                 }
1394         }
1395
1396         chomp $summary;
1397         return substr($summary, 0, 80) . "\n";
1398 }
1399
1400
1401 # Print a one-line summary of each hunk in the array ref in
1402 # the first argument, starting with the index in the 2nd.
1403 sub display_hunks {
1404         my ($hunks, $i) = @_;
1405         my $ctr = 0;
1406         $i ||= 0;
1407         for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1408                 my $status = " ";
1409                 if (defined $hunks->[$i]{USE}) {
1410                         $status = $hunks->[$i]{USE} ? "+" : "-";
1411                 }
1412                 printf "%s%2d: %s",
1413                         $status,
1414                         $i + 1,
1415                         summarize_hunk($hunks->[$i]);
1416         }
1417         return $i;
1418 }
1419
1420 my %patch_update_prompt_modes = (
1421         stage => {
1422                 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1423                 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1424                 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
1425         },
1426         stash => {
1427                 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1428                 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1429                 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
1430         },
1431         reset_head => {
1432                 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1433                 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1434                 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
1435         },
1436         reset_nothead => {
1437                 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1438                 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1439                 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
1440         },
1441         checkout_index => {
1442                 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1443                 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1444                 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1445         },
1446         checkout_head => {
1447                 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1448                 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1449                 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
1450         },
1451         checkout_nothead => {
1452                 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1453                 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1454                 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
1455         },
1456         worktree_head => {
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                 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1460         },
1461         worktree_nothead => {
1462                 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1463                 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1464                 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1465         },
1466 );
1467
1468 sub patch_update_file {
1469         my $quit = 0;
1470         my ($ix, $num);
1471         my $path = shift;
1472         my ($head, @hunk) = parse_diff($path);
1473         ($head, my $mode, my $deletion) = parse_diff_header($head);
1474         for (@{$head->{DISPLAY}}) {
1475                 print;
1476         }
1477
1478         if (@{$mode->{TEXT}}) {
1479                 unshift @hunk, $mode;
1480         }
1481         if (@{$deletion->{TEXT}}) {
1482                 foreach my $hunk (@hunk) {
1483                         push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1484                         push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1485                 }
1486                 @hunk = ($deletion);
1487         }
1488
1489         $num = scalar @hunk;
1490         $ix = 0;
1491
1492         while (1) {
1493                 my ($prev, $next, $other, $undecided, $i);
1494                 $other = '';
1495
1496                 if ($num <= $ix) {
1497                         $ix = 0;
1498                 }
1499                 for ($i = 0; $i < $ix; $i++) {
1500                         if (!defined $hunk[$i]{USE}) {
1501                                 $prev = 1;
1502                                 $other .= ',k';
1503                                 last;
1504                         }
1505                 }
1506                 if ($ix) {
1507                         $other .= ',K';
1508                 }
1509                 for ($i = $ix + 1; $i < $num; $i++) {
1510                         if (!defined $hunk[$i]{USE}) {
1511                                 $next = 1;
1512                                 $other .= ',j';
1513                                 last;
1514                         }
1515                 }
1516                 if ($ix < $num - 1) {
1517                         $other .= ',J';
1518                 }
1519                 if ($num > 1) {
1520                         $other .= ',g,/';
1521                 }
1522                 for ($i = 0; $i < $num; $i++) {
1523                         if (!defined $hunk[$i]{USE}) {
1524                                 $undecided = 1;
1525                                 last;
1526                         }
1527                 }
1528                 last if (!$undecided);
1529
1530                 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1531                     hunk_splittable($hunk[$ix]{TEXT})) {
1532                         $other .= ',s';
1533                 }
1534                 if ($hunk[$ix]{TYPE} eq 'hunk') {
1535                         $other .= ',e';
1536                 }
1537                 for (@{$hunk[$ix]{DISPLAY}}) {
1538                         print;
1539                 }
1540                 print colored $prompt_color,
1541                         sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1542
1543                 my $line = prompt_single_character;
1544                 last unless defined $line;
1545                 if ($line) {
1546                         if ($line =~ /^y/i) {
1547                                 $hunk[$ix]{USE} = 1;
1548                         }
1549                         elsif ($line =~ /^n/i) {
1550                                 $hunk[$ix]{USE} = 0;
1551                         }
1552                         elsif ($line =~ /^a/i) {
1553                                 while ($ix < $num) {
1554                                         if (!defined $hunk[$ix]{USE}) {
1555                                                 $hunk[$ix]{USE} = 1;
1556                                         }
1557                                         $ix++;
1558                                 }
1559                                 next;
1560                         }
1561                         elsif ($line =~ /^g(.*)/) {
1562                                 my $response = $1;
1563                                 unless ($other =~ /g/) {
1564                                         error_msg __("No other hunks to goto\n");
1565                                         next;
1566                                 }
1567                                 my $no = $ix > 10 ? $ix - 10 : 0;
1568                                 while ($response eq '') {
1569                                         $no = display_hunks(\@hunk, $no);
1570                                         if ($no < $num) {
1571                                                 print __("go to which hunk (<ret> to see more)? ");
1572                                         } else {
1573                                                 print __("go to which hunk? ");
1574                                         }
1575                                         $response = <STDIN>;
1576                                         if (!defined $response) {
1577                                                 $response = '';
1578                                         }
1579                                         chomp $response;
1580                                 }
1581                                 if ($response !~ /^\s*\d+\s*$/) {
1582                                         error_msg sprintf(__("Invalid number: '%s'\n"),
1583                                                              $response);
1584                                 } elsif (0 < $response && $response <= $num) {
1585                                         $ix = $response - 1;
1586                                 } else {
1587                                         error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1588                                                               "Sorry, only %d hunks available.\n", $num), $num);
1589                                 }
1590                                 next;
1591                         }
1592                         elsif ($line =~ /^d/i) {
1593                                 while ($ix < $num) {
1594                                         if (!defined $hunk[$ix]{USE}) {
1595                                                 $hunk[$ix]{USE} = 0;
1596                                         }
1597                                         $ix++;
1598                                 }
1599                                 next;
1600                         }
1601                         elsif ($line =~ /^q/i) {
1602                                 for ($i = 0; $i < $num; $i++) {
1603                                         if (!defined $hunk[$i]{USE}) {
1604                                                 $hunk[$i]{USE} = 0;
1605                                         }
1606                                 }
1607                                 $quit = 1;
1608                                 last;
1609                         }
1610                         elsif ($line =~ m|^/(.*)|) {
1611                                 my $regex = $1;
1612                                 unless ($other =~ m|/|) {
1613                                         error_msg __("No other hunks to search\n");
1614                                         next;
1615                                 }
1616                                 if ($regex eq "") {
1617                                         print colored $prompt_color, __("search for regex? ");
1618                                         $regex = <STDIN>;
1619                                         if (defined $regex) {
1620                                                 chomp $regex;
1621                                         }
1622                                 }
1623                                 my $search_string;
1624                                 eval {
1625                                         $search_string = qr{$regex}m;
1626                                 };
1627                                 if ($@) {
1628                                         my ($err,$exp) = ($@, $1);
1629                                         $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1630                                         error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
1631                                         next;
1632                                 }
1633                                 my $iy = $ix;
1634                                 while (1) {
1635                                         my $text = join ("", @{$hunk[$iy]{TEXT}});
1636                                         last if ($text =~ $search_string);
1637                                         $iy++;
1638                                         $iy = 0 if ($iy >= $num);
1639                                         if ($ix == $iy) {
1640                                                 error_msg __("No hunk matches the given pattern\n");
1641                                                 last;
1642                                         }
1643                                 }
1644                                 $ix = $iy;
1645                                 next;
1646                         }
1647                         elsif ($line =~ /^K/) {
1648                                 if ($other =~ /K/) {
1649                                         $ix--;
1650                                 }
1651                                 else {
1652                                         error_msg __("No previous hunk\n");
1653                                 }
1654                                 next;
1655                         }
1656                         elsif ($line =~ /^J/) {
1657                                 if ($other =~ /J/) {
1658                                         $ix++;
1659                                 }
1660                                 else {
1661                                         error_msg __("No next hunk\n");
1662                                 }
1663                                 next;
1664                         }
1665                         elsif ($line =~ /^k/) {
1666                                 if ($other =~ /k/) {
1667                                         while (1) {
1668                                                 $ix--;
1669                                                 last if (!$ix ||
1670                                                          !defined $hunk[$ix]{USE});
1671                                         }
1672                                 }
1673                                 else {
1674                                         error_msg __("No previous hunk\n");
1675                                 }
1676                                 next;
1677                         }
1678                         elsif ($line =~ /^j/) {
1679                                 if ($other !~ /j/) {
1680                                         error_msg __("No next hunk\n");
1681                                         next;
1682                                 }
1683                         }
1684                         elsif ($line =~ /^s/) {
1685                                 unless ($other =~ /s/) {
1686                                         error_msg __("Sorry, cannot split this hunk\n");
1687                                         next;
1688                                 }
1689                                 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
1690                                 if (1 < @split) {
1691                                         print colored $header_color, sprintf(
1692                                                 __n("Split into %d hunk.\n",
1693                                                     "Split into %d hunks.\n",
1694                                                     scalar(@split)), scalar(@split));
1695                                 }
1696                                 splice (@hunk, $ix, 1, @split);
1697                                 $num = scalar @hunk;
1698                                 next;
1699                         }
1700                         elsif ($line =~ /^e/) {
1701                                 unless ($other =~ /e/) {
1702                                         error_msg __("Sorry, cannot edit this hunk\n");
1703                                         next;
1704                                 }
1705                                 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1706                                 if (defined $newhunk) {
1707                                         splice @hunk, $ix, 1, $newhunk;
1708                                 }
1709                         }
1710                         else {
1711                                 help_patch_cmd($other);
1712                                 next;
1713                         }
1714                         # soft increment
1715                         while (1) {
1716                                 $ix++;
1717                                 last if ($ix >= $num ||
1718                                          !defined $hunk[$ix]{USE});
1719                         }
1720                 }
1721         }
1722
1723         @hunk = coalesce_overlapping_hunks(@hunk);
1724
1725         my $n_lofs = 0;
1726         my @result = ();
1727         for (@hunk) {
1728                 if ($_->{USE}) {
1729                         push @result, @{$_->{TEXT}};
1730                 }
1731         }
1732
1733         if (@result) {
1734                 my @patch = reassemble_patch($head->{TEXT}, @result);
1735                 my $apply_routine = $patch_mode_flavour{APPLY};
1736                 &$apply_routine(@patch);
1737                 refresh();
1738         }
1739
1740         print "\n";
1741         return $quit;
1742 }
1743
1744 sub diff_cmd {
1745         my @mods = list_modified('index-only');
1746         @mods = grep { !($_->{BINARY}) } @mods;
1747         return if (!@mods);
1748         my (@them) = list_and_choose({ PROMPT => __('Review diff'),
1749                                      IMMEDIATE => 1,
1750                                      HEADER => $status_head, },
1751                                    @mods);
1752         return if (!@them);
1753         my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
1754         system(qw(git diff -p --cached), $reference, '--',
1755                 map { $_->{VALUE} } @them);
1756 }
1757
1758 sub quit_cmd {
1759         print __("Bye.\n");
1760         exit(0);
1761 }
1762
1763 sub help_cmd {
1764 # TRANSLATORS: please do not translate the command names
1765 # 'status', 'update', 'revert', etc.
1766         print colored $help_color, __ <<'EOF' ;
1767 status        - show paths with changes
1768 update        - add working tree state to the staged set of changes
1769 revert        - revert staged set of changes back to the HEAD version
1770 patch         - pick hunks and update selectively
1771 diff          - view diff between HEAD and index
1772 add untracked - add contents of untracked files to the staged set of changes
1773 EOF
1774 }
1775
1776 sub process_args {
1777         return unless @ARGV;
1778         my $arg = shift @ARGV;
1779         if ($arg =~ /--patch(?:=(.*))?/) {
1780                 if (defined $1) {
1781                         if ($1 eq 'reset') {
1782                                 $patch_mode = 'reset_head';
1783                                 $patch_mode_revision = 'HEAD';
1784                                 $arg = shift @ARGV or die __("missing --");
1785                                 if ($arg ne '--') {
1786                                         $patch_mode_revision = $arg;
1787                                         $patch_mode = ($arg eq 'HEAD' ?
1788                                                        'reset_head' : 'reset_nothead');
1789                                         $arg = shift @ARGV or die __("missing --");
1790                                 }
1791                         } elsif ($1 eq 'checkout') {
1792                                 $arg = shift @ARGV or die __("missing --");
1793                                 if ($arg eq '--') {
1794                                         $patch_mode = 'checkout_index';
1795                                 } else {
1796                                         $patch_mode_revision = $arg;
1797                                         $patch_mode = ($arg eq 'HEAD' ?
1798                                                        'checkout_head' : 'checkout_nothead');
1799                                         $arg = shift @ARGV or die __("missing --");
1800                                 }
1801                         } elsif ($1 eq 'worktree') {
1802                                 $arg = shift @ARGV or die __("missing --");
1803                                 if ($arg eq '--') {
1804                                         $patch_mode = 'checkout_index';
1805                                 } else {
1806                                         $patch_mode_revision = $arg;
1807                                         $patch_mode = ($arg eq 'HEAD' ?
1808                                                        'worktree_head' : 'worktree_nothead');
1809                                         $arg = shift @ARGV or die __("missing --");
1810                                 }
1811                         } elsif ($1 eq 'stage' or $1 eq 'stash') {
1812                                 $patch_mode = $1;
1813                                 $arg = shift @ARGV or die __("missing --");
1814                         } else {
1815                                 die sprintf(__("unknown --patch mode: %s"), $1);
1816                         }
1817                 } else {
1818                         $patch_mode = 'stage';
1819                         $arg = shift @ARGV or die __("missing --");
1820                 }
1821                 die sprintf(__("invalid argument %s, expecting --"),
1822                                $arg) unless $arg eq "--";
1823                 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1824                 $patch_mode_only = 1;
1825         }
1826         elsif ($arg ne "--") {
1827                 die sprintf(__("invalid argument %s, expecting --"), $arg);
1828         }
1829 }
1830
1831 sub main_loop {
1832         my @cmd = ([ 'status', \&status_cmd, ],
1833                    [ 'update', \&update_cmd, ],
1834                    [ 'revert', \&revert_cmd, ],
1835                    [ 'add untracked', \&add_untracked_cmd, ],
1836                    [ 'patch', \&patch_update_cmd, ],
1837                    [ 'diff', \&diff_cmd, ],
1838                    [ 'quit', \&quit_cmd, ],
1839                    [ 'help', \&help_cmd, ],
1840         );
1841         while (1) {
1842                 my ($it) = list_and_choose({ PROMPT => __('What now'),
1843                                              SINGLETON => 1,
1844                                              LIST_FLAT => 4,
1845                                              HEADER => __('*** Commands ***'),
1846                                              ON_EOF => \&quit_cmd,
1847                                              IMMEDIATE => 1 }, @cmd);
1848                 if ($it) {
1849                         eval {
1850                                 $it->[1]->();
1851                         };
1852                         if ($@) {
1853                                 print "$@";
1854                         }
1855                 }
1856         }
1857 }
1858
1859 process_args();
1860 refresh();
1861 if ($patch_mode_only) {
1862         patch_update_cmd();
1863 }
1864 else {
1865         status_cmd();
1866         main_loop();
1867 }