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