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