]> git.ipfire.org Git - thirdparty/git.git/blame - git-add--interactive.perl
Merge branch 'jk/no-system-includes-in-dot-c' 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) {
2bd69b90
PW
975 if ($patch_mode_flavour{IS_REVERSE}) {
976 $o_ofs -= $ofs_delta;
977 } else {
978 $n_ofs += $ofs_delta;
979 }
fecc6f3a
PW
980 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
981 $n_ofs, $n_cnt);
982 }
2b8ea7f3
PW
983 # If this hunk was edited then adjust the offset delta
984 # to reflect the edit.
985 if ($_->{OFS_DELTA}) {
986 $ofs_delta += $_->{OFS_DELTA};
987 }
7a26e653
JH
988 if (defined $last_o_ctx &&
989 $o_ofs <= $last_o_ctx &&
990 !$_->{DIRTY} &&
991 !$last_was_dirty) {
992 merge_hunk($out[-1], $_);
993 }
994 else {
995 push @out, $_;
996 }
997 $last_o_ctx = find_last_o_ctx($out[-1]);
998 $last_was_dirty = $_->{DIRTY};
999 }
1000 return @out;
1001}
835b2aeb 1002
e1327ed5
JK
1003sub reassemble_patch {
1004 my $head = shift;
1005 my @patch;
1006
1007 # Include everything in the header except the beginning of the diff.
1008 push @patch, (grep { !/^[-+]{3}/ } @$head);
1009
1010 # Then include any headers from the hunk lines, which must
1011 # come before any actual hunk.
1012 while (@_ && $_[0] !~ /^@/) {
1013 push @patch, shift;
1014 }
1015
1016 # Then begin the diff.
1017 push @patch, grep { /^[-+]{3}/ } @$head;
1018
1019 # And then the actual hunks.
1020 push @patch, @_;
1021
1022 return @patch;
1023}
1024
ac083c47
TR
1025sub color_diff {
1026 return map {
1027 colored((/^@/ ? $fraginfo_color :
1028 /^\+/ ? $diff_new_color :
1029 /^-/ ? $diff_old_color :
1030 $diff_plain_color),
1031 $_);
1032 } @_;
1033}
1034
c9d96164
VA
1035my %edit_hunk_manually_modes = (
1036 stage => N__(
1037"If the patch applies cleanly, the edited hunk will immediately be
1038marked for staging."),
1039 stash => N__(
1040"If the patch applies cleanly, the edited hunk will immediately be
1041marked for stashing."),
1042 reset_head => N__(
1043"If the patch applies cleanly, the edited hunk will immediately be
1044marked for unstaging."),
1045 reset_nothead => N__(
1046"If the patch applies cleanly, the edited hunk will immediately be
1047marked for applying."),
1048 checkout_index => N__(
1049"If the patch applies cleanly, the edited hunk will immediately be
0301f1fd 1050marked for discarding."),
c9d96164
VA
1051 checkout_head => N__(
1052"If the patch applies cleanly, the edited hunk will immediately be
1053marked for discarding."),
1054 checkout_nothead => N__(
1055"If the patch applies cleanly, the edited hunk will immediately be
1056marked for applying."),
1057);
1058
2b8ea7f3
PW
1059sub recount_edited_hunk {
1060 local $_;
1061 my ($oldtext, $newtext) = @_;
1062 my ($o_cnt, $n_cnt) = (0, 0);
1063 for (@{$newtext}[1..$#{$newtext}]) {
1064 my $mode = substr($_, 0, 1);
1065 if ($mode eq '-') {
1066 $o_cnt++;
1067 } elsif ($mode eq '+') {
1068 $n_cnt++;
f4d35a6b 1069 } elsif ($mode eq ' ' or $mode eq "\n") {
2b8ea7f3
PW
1070 $o_cnt++;
1071 $n_cnt++;
1072 }
1073 }
1074 my ($o_ofs, undef, $n_ofs, undef) =
1075 parse_hunk_header($newtext->[0]);
1076 $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1077 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1078 parse_hunk_header($oldtext->[0]);
1079 # Return the change in the number of lines inserted by this hunk
1080 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1081}
1082
ac083c47
TR
1083sub edit_hunk_manually {
1084 my ($oldtext) = @_;
1085
1086 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1087 my $fh;
1088 open $fh, '>', $hunkfile
13c58c17 1089 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
c9d96164 1090 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
ac083c47 1091 print $fh @$oldtext;
7b8c7051
JDL
1092 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1093 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
c9d96164
VA
1094 my $comment_line_char = Git::get_comment_line_char;
1095 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1096---
1097To remove '%s' lines, make them ' ' lines (context).
1098To remove '%s' lines, delete them.
1099Lines starting with %s will be removed.
ac083c47 1100EOF
c9d96164
VA
1101__($edit_hunk_manually_modes{$patch_mode}),
1102# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1103__ <<EOF2 ;
1104If it does not apply cleanly, you will be given an opportunity to
1105edit again. If all lines of the hunk are removed, then the edit is
1106aborted and the hunk is left unchanged.
1107EOF2
ac083c47
TR
1108 close $fh;
1109
b4479f07 1110 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
ac083c47
TR
1111 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1112
1d398a03
DM
1113 if ($? != 0) {
1114 return undef;
1115 }
1116
ac083c47 1117 open $fh, '<', $hunkfile
13c58c17 1118 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
d85d7ecb 1119 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
ac083c47
TR
1120 close $fh;
1121 unlink $hunkfile;
1122
1123 # Abort if nothing remains
1124 if (!grep { /\S/ } @newtext) {
1125 return undef;
1126 }
1127
1128 # Reinsert the first hunk header if the user accidentally deleted it
1129 if ($newtext[0] !~ /^@/) {
1130 unshift @newtext, $oldtext->[0];
1131 }
1132 return \@newtext;
1133}
1134
1135sub diff_applies {
9dce8323 1136 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
8f0bef6d 1137 map { @{$_->{TEXT}} } @_);
ac083c47
TR
1138}
1139
ca6ac7f1
TR
1140sub _restore_terminal_and_die {
1141 ReadMode 'restore';
1142 print "\n";
1143 exit 1;
1144}
1145
1146sub prompt_single_character {
1147 if ($use_readkey) {
1148 local $SIG{TERM} = \&_restore_terminal_and_die;
1149 local $SIG{INT} = \&_restore_terminal_and_die;
1150 ReadMode 'cbreak';
1151 my $key = ReadKey 0;
1152 ReadMode 'restore';
b5cc0032
TR
1153 if ($use_termcap and $key eq "\e") {
1154 while (!defined $term_escapes{$key}) {
1155 my $next = ReadKey 0.5;
1156 last if (!defined $next);
1157 $key .= $next;
1158 }
1159 $key =~ s/\e/^[/;
1160 }
ca6ac7f1
TR
1161 print "$key" if defined $key;
1162 print "\n";
1163 return $key;
1164 } else {
1165 return <STDIN>;
1166 }
1167}
1168
ac083c47
TR
1169sub prompt_yesno {
1170 my ($prompt) = @_;
1171 while (1) {
1172 print colored $prompt_color, $prompt;
ca6ac7f1 1173 my $line = prompt_single_character;
d5addcf5 1174 return undef unless defined $line;
ac083c47
TR
1175 return 0 if $line =~ /^n/i;
1176 return 1 if $line =~ /^y/i;
1177 }
1178}
1179
1180sub edit_hunk_loop {
2b8ea7f3
PW
1181 my ($head, $hunks, $ix) = @_;
1182 my $hunk = $hunks->[$ix];
1183 my $text = $hunk->{TEXT};
ac083c47
TR
1184
1185 while (1) {
2b8ea7f3
PW
1186 my $newtext = edit_hunk_manually($text);
1187 if (!defined $newtext) {
ac083c47
TR
1188 return undef;
1189 }
0392513f 1190 my $newhunk = {
2b8ea7f3
PW
1191 TEXT => $newtext,
1192 TYPE => $hunk->{TYPE},
7a26e653
JH
1193 USE => 1,
1194 DIRTY => 1,
0392513f 1195 };
2b8ea7f3
PW
1196 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1197 # If this hunk has already been edited then add the
1198 # offset delta of the previous edit to get the real
1199 # delta from the original unedited hunk.
1200 $hunk->{OFS_DELTA} and
1201 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
ac083c47 1202 if (diff_applies($head,
2b8ea7f3 1203 @{$hunks}[0..$ix-1],
ac083c47 1204 $newhunk,
2b8ea7f3
PW
1205 @{$hunks}[$ix+1..$#{$hunks}])) {
1206 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
ac083c47
TR
1207 return $newhunk;
1208 }
1209 else {
1210 prompt_yesno(
258e7790
VA
1211 # TRANSLATORS: do not translate [y/n]
1212 # The program will only accept that input
1213 # at this point.
1214 # Consider translating (saying "no" discards!) as
1215 # (saying "n" for "no" discards!) if the translation
1216 # of the word "no" does not start with n.
1217 __('Your edited hunk does not apply. Edit again '
1218 . '(saying "no" discards!) [y/n]? ')
ac083c47
TR
1219 ) or return undef;
1220 }
1221 }
1222}
1223
186b52c5
VA
1224my %help_patch_modes = (
1225 stage => N__(
1226"y - stage this hunk
1227n - do not stage this hunk
1228q - quit; do not stage this hunk or any of the remaining ones
1229a - stage this hunk and all later hunks in the file
1230d - do not stage this hunk or any of the later hunks in the file"),
1231 stash => N__(
1232"y - stash this hunk
1233n - do not stash this hunk
1234q - quit; do not stash this hunk or any of the remaining ones
1235a - stash this hunk and all later hunks in the file
1236d - do not stash this hunk or any of the later hunks in the file"),
1237 reset_head => N__(
1238"y - unstage this hunk
1239n - do not unstage this hunk
1240q - quit; do not unstage this hunk or any of the remaining ones
1241a - unstage this hunk and all later hunks in the file
1242d - do not unstage this hunk or any of the later hunks in the file"),
1243 reset_nothead => N__(
1244"y - apply this hunk to index
1245n - do not apply this hunk to index
1246q - quit; do not apply this hunk or any of the remaining ones
1247a - apply this hunk and all later hunks in the file
1248d - do not apply this hunk or any of the later hunks in the file"),
1249 checkout_index => N__(
1250"y - discard this hunk from worktree
1251n - do not discard this hunk from worktree
1252q - quit; do not discard this hunk or any of the remaining ones
1253a - discard this hunk and all later hunks in the file
1254d - do not discard this hunk or any of the later hunks in the file"),
1255 checkout_head => N__(
1256"y - discard this hunk from index and worktree
1257n - do not discard this hunk from index and worktree
1258q - quit; do not discard this hunk or any of the remaining ones
1259a - discard this hunk and all later hunks in the file
1260d - do not discard this hunk or any of the later hunks in the file"),
1261 checkout_nothead => N__(
1262"y - apply this hunk to index and worktree
1263n - do not apply this hunk to index and worktree
1264q - quit; do not apply this hunk or any of the remaining ones
1265a - apply this hunk and all later hunks in the file
1266d - do not apply this hunk or any of the later hunks in the file"),
1267);
1268
5cde71d6 1269sub help_patch_cmd {
01a69660 1270 local $_;
88f6ffc1 1271 my $other = $_[0] . ",?";
01a69660
PW
1272 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1273 map { "$_\n" } grep {
1274 my $c = quotemeta(substr($_, 0, 1));
1275 $other =~ /,$c/
1276 } split "\n", __ <<EOF ;
070434d0 1277g - select a hunk to go to
dd971cc9 1278/ - search for a hunk matching the given regex
5cde71d6
JH
1279j - leave this hunk undecided, see next undecided hunk
1280J - leave this hunk undecided, see next hunk
1281k - leave this hunk undecided, see previous undecided hunk
1282K - leave this hunk undecided, see previous hunk
835b2aeb 1283s - split the current hunk into smaller hunks
ac083c47 1284e - manually edit the current hunk
280e50c7 1285? - print help
5cde71d6
JH
1286EOF
1287}
1288
8f0bef6d
TR
1289sub apply_patch {
1290 my $cmd = shift;
9dce8323 1291 my $ret = run_git_apply $cmd, @_;
8f0bef6d
TR
1292 if (!$ret) {
1293 print STDERR @_;
1294 }
1295 return $ret;
1296}
1297
4f353658
TR
1298sub apply_patch_for_checkout_commit {
1299 my $reverse = shift;
9dce8323
JH
1300 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1301 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
4f353658
TR
1302
1303 if ($applies_worktree && $applies_index) {
9dce8323
JH
1304 run_git_apply 'apply '.$reverse.' --cached', @_;
1305 run_git_apply 'apply '.$reverse, @_;
4f353658
TR
1306 return 1;
1307 } elsif (!$applies_index) {
258e7790
VA
1308 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1309 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
9dce8323 1310 return run_git_apply 'apply '.$reverse, @_;
4f353658 1311 } else {
258e7790 1312 print colored $error_color, __("Nothing was applied.\n");
4f353658
TR
1313 return 0;
1314 }
1315 } else {
1316 print STDERR @_;
1317 return 0;
1318 }
1319}
1320
5cde71d6 1321sub patch_update_cmd {
8f0bef6d 1322 my @all_mods = list_modified($patch_mode_flavour{FILTER});
13c58c17 1323 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
4066bd67
JK
1324 for grep { $_->{UNMERGED} } @all_mods;
1325 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1326
9fe7a643 1327 my @mods = grep { !($_->{BINARY}) } @all_mods;
b63e9950 1328 my @them;
5cde71d6 1329
b63e9950 1330 if (!@mods) {
9fe7a643 1331 if (@all_mods) {
258e7790 1332 print STDERR __("Only binary files changed.\n");
9fe7a643 1333 } else {
258e7790 1334 print STDERR __("No changes.\n");
9fe7a643 1335 }
b63e9950
WC
1336 return 0;
1337 }
c852bd54 1338 if ($patch_mode_only) {
b63e9950
WC
1339 @them = @mods;
1340 }
1341 else {
258e7790 1342 @them = list_and_choose({ PROMPT => __('Patch update'),
b63e9950
WC
1343 HEADER => $status_head, },
1344 @mods);
1345 }
12db334e 1346 for (@them) {
9a7a1e03 1347 return 0 if patch_update_file($_->{VALUE});
12db334e 1348 }
a7d9da6c 1349}
5cde71d6 1350
3f6aff68
WP
1351# Generate a one line summary of a hunk.
1352sub summarize_hunk {
1353 my $rhunk = shift;
1354 my $summary = $rhunk->{TEXT}[0];
1355
1356 # Keep the line numbers, discard extra context.
1357 $summary =~ s/@@(.*?)@@.*/$1 /s;
1358 $summary .= " " x (20 - length $summary);
1359
1360 # Add some user context.
1361 for my $line (@{$rhunk->{TEXT}}) {
1362 if ($line =~ m/^[+-].*\w/) {
1363 $summary .= $line;
1364 last;
1365 }
1366 }
1367
1368 chomp $summary;
1369 return substr($summary, 0, 80) . "\n";
1370}
1371
1372
1373# Print a one-line summary of each hunk in the array ref in
41ccfdd9 1374# the first argument, starting with the index in the 2nd.
3f6aff68
WP
1375sub display_hunks {
1376 my ($hunks, $i) = @_;
1377 my $ctr = 0;
1378 $i ||= 0;
1379 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1380 my $status = " ";
1381 if (defined $hunks->[$i]{USE}) {
1382 $status = $hunks->[$i]{USE} ? "+" : "-";
1383 }
1384 printf "%s%2d: %s",
1385 $status,
1386 $i + 1,
1387 summarize_hunk($hunks->[$i]);
1388 }
1389 return $i;
1390}
1391
0539d5e6
VA
1392my %patch_update_prompt_modes = (
1393 stage => {
88f6ffc1
PW
1394 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1395 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1396 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1397 },
1398 stash => {
88f6ffc1
PW
1399 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1400 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1401 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1402 },
1403 reset_head => {
88f6ffc1
PW
1404 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1405 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1406 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1407 },
1408 reset_nothead => {
88f6ffc1
PW
1409 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1410 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1411 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1412 },
1413 checkout_index => {
88f6ffc1
PW
1414 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1415 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1416 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1417 },
1418 checkout_head => {
88f6ffc1
PW
1419 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1420 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1421 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1422 },
1423 checkout_nothead => {
88f6ffc1
PW
1424 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1425 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1426 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1427 },
1428);
1429
a7d9da6c 1430sub patch_update_file {
9a7a1e03 1431 my $quit = 0;
5cde71d6 1432 my ($ix, $num);
a7d9da6c 1433 my $path = shift;
5cde71d6 1434 my ($head, @hunk) = parse_diff($path);
24ab81ae 1435 ($head, my $mode, my $deletion) = parse_diff_header($head);
4af756f3
WC
1436 for (@{$head->{DISPLAY}}) {
1437 print;
1438 }
ca724686
JK
1439
1440 if (@{$mode->{TEXT}}) {
0392513f 1441 unshift @hunk, $mode;
ca724686 1442 }
8947fdd5
JK
1443 if (@{$deletion->{TEXT}}) {
1444 foreach my $hunk (@hunk) {
1445 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1446 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1447 }
24ab81ae
JK
1448 @hunk = ($deletion);
1449 }
ca724686 1450
5cde71d6
JH
1451 $num = scalar @hunk;
1452 $ix = 0;
1453
1454 while (1) {
835b2aeb 1455 my ($prev, $next, $other, $undecided, $i);
5cde71d6
JH
1456 $other = '';
1457
1458 if ($num <= $ix) {
1459 $ix = 0;
1460 }
835b2aeb 1461 for ($i = 0; $i < $ix; $i++) {
5cde71d6
JH
1462 if (!defined $hunk[$i]{USE}) {
1463 $prev = 1;
57886bc7 1464 $other .= ',k';
5cde71d6
JH
1465 last;
1466 }
1467 }
1468 if ($ix) {
57886bc7 1469 $other .= ',K';
5cde71d6 1470 }
835b2aeb 1471 for ($i = $ix + 1; $i < $num; $i++) {
5cde71d6
JH
1472 if (!defined $hunk[$i]{USE}) {
1473 $next = 1;
57886bc7 1474 $other .= ',j';
5cde71d6
JH
1475 last;
1476 }
1477 }
1478 if ($ix < $num - 1) {
57886bc7 1479 $other .= ',J';
5cde71d6 1480 }
070434d0 1481 if ($num > 1) {
88f6ffc1 1482 $other .= ',g,/';
070434d0 1483 }
835b2aeb 1484 for ($i = 0; $i < $num; $i++) {
5cde71d6
JH
1485 if (!defined $hunk[$i]{USE}) {
1486 $undecided = 1;
1487 last;
1488 }
1489 }
1490 last if (!$undecided);
1491
0392513f
JK
1492 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1493 hunk_splittable($hunk[$ix]{TEXT})) {
57886bc7 1494 $other .= ',s';
835b2aeb 1495 }
0392513f
JK
1496 if ($hunk[$ix]{TYPE} eq 'hunk') {
1497 $other .= ',e';
1498 }
4af756f3
WC
1499 for (@{$hunk[$ix]{DISPLAY}}) {
1500 print;
1501 }
0539d5e6
VA
1502 print colored $prompt_color,
1503 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1504
ca6ac7f1 1505 my $line = prompt_single_character;
a8bec7ab 1506 last unless defined $line;
5cde71d6
JH
1507 if ($line) {
1508 if ($line =~ /^y/i) {
1509 $hunk[$ix]{USE} = 1;
1510 }
1511 elsif ($line =~ /^n/i) {
1512 $hunk[$ix]{USE} = 0;
1513 }
1514 elsif ($line =~ /^a/i) {
1515 while ($ix < $num) {
1516 if (!defined $hunk[$ix]{USE}) {
1517 $hunk[$ix]{USE} = 1;
1518 }
1519 $ix++;
1520 }
1521 next;
1522 }
4bdd6e7c 1523 elsif ($line =~ /^g(.*)/) {
070434d0 1524 my $response = $1;
4bdd6e7c
PW
1525 unless ($other =~ /g/) {
1526 error_msg __("No other hunks to goto\n");
1527 next;
1528 }
070434d0
WP
1529 my $no = $ix > 10 ? $ix - 10 : 0;
1530 while ($response eq '') {
070434d0
WP
1531 $no = display_hunks(\@hunk, $no);
1532 if ($no < $num) {
258e7790
VA
1533 print __("go to which hunk (<ret> to see more)? ");
1534 } else {
1535 print __("go to which hunk? ");
070434d0 1536 }
070434d0 1537 $response = <STDIN>;
68c02d7c
TR
1538 if (!defined $response) {
1539 $response = '';
1540 }
070434d0
WP
1541 chomp $response;
1542 }
1543 if ($response !~ /^\s*\d+\s*$/) {
13c58c17
VA
1544 error_msg sprintf(__("Invalid number: '%s'\n"),
1545 $response);
070434d0
WP
1546 } elsif (0 < $response && $response <= $num) {
1547 $ix = $response - 1;
1548 } else {
c4a85c3b
VA
1549 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1550 "Sorry, only %d hunks available.\n", $num), $num);
070434d0
WP
1551 }
1552 next;
1553 }
5cde71d6
JH
1554 elsif ($line =~ /^d/i) {
1555 while ($ix < $num) {
1556 if (!defined $hunk[$ix]{USE}) {
1557 $hunk[$ix]{USE} = 0;
1558 }
1559 $ix++;
1560 }
1561 next;
1562 }
9a7a1e03 1563 elsif ($line =~ /^q/i) {
f5ea3f2b
JH
1564 for ($i = 0; $i < $num; $i++) {
1565 if (!defined $hunk[$i]{USE}) {
1566 $hunk[$i]{USE} = 0;
9a7a1e03 1567 }
9a7a1e03
MM
1568 }
1569 $quit = 1;
f5ea3f2b 1570 last;
9a7a1e03 1571 }
dd971cc9 1572 elsif ($line =~ m|^/(.*)|) {
ca6ac7f1 1573 my $regex = $1;
88f6ffc1
PW
1574 unless ($other =~ m|/|) {
1575 error_msg __("No other hunks to search\n");
1576 next;
1577 }
fd2fb4aa 1578 if ($regex eq "") {
258e7790 1579 print colored $prompt_color, __("search for regex? ");
ca6ac7f1
TR
1580 $regex = <STDIN>;
1581 if (defined $regex) {
1582 chomp $regex;
1583 }
1584 }
dd971cc9
WP
1585 my $search_string;
1586 eval {
ca6ac7f1 1587 $search_string = qr{$regex}m;
dd971cc9
WP
1588 };
1589 if ($@) {
1590 my ($err,$exp) = ($@, $1);
1591 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
13c58c17 1592 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
dd971cc9
WP
1593 next;
1594 }
1595 my $iy = $ix;
1596 while (1) {
1597 my $text = join ("", @{$hunk[$iy]{TEXT}});
1598 last if ($text =~ $search_string);
1599 $iy++;
1600 $iy = 0 if ($iy >= $num);
1601 if ($ix == $iy) {
258e7790 1602 error_msg __("No hunk matches the given pattern\n");
dd971cc9
WP
1603 last;
1604 }
1605 }
1606 $ix = $iy;
1607 next;
1608 }
ace30ba8
WP
1609 elsif ($line =~ /^K/) {
1610 if ($other =~ /K/) {
1611 $ix--;
1612 }
1613 else {
258e7790 1614 error_msg __("No previous hunk\n");
ace30ba8 1615 }
5cde71d6
JH
1616 next;
1617 }
ace30ba8
WP
1618 elsif ($line =~ /^J/) {
1619 if ($other =~ /J/) {
1620 $ix++;
1621 }
1622 else {
258e7790 1623 error_msg __("No next hunk\n");
ace30ba8 1624 }
5cde71d6
JH
1625 next;
1626 }
ace30ba8
WP
1627 elsif ($line =~ /^k/) {
1628 if ($other =~ /k/) {
1629 while (1) {
1630 $ix--;
1631 last if (!$ix ||
1632 !defined $hunk[$ix]{USE});
1633 }
1634 }
1635 else {
258e7790 1636 error_msg __("No previous hunk\n");
5cde71d6
JH
1637 }
1638 next;
1639 }
ace30ba8
WP
1640 elsif ($line =~ /^j/) {
1641 if ($other !~ /j/) {
258e7790 1642 error_msg __("No next hunk\n");
ace30ba8 1643 next;
5cde71d6 1644 }
5cde71d6 1645 }
4bdd6e7c
PW
1646 elsif ($line =~ /^s/) {
1647 unless ($other =~ /s/) {
1648 error_msg __("Sorry, cannot split this hunk\n");
1649 next;
1650 }
4af756f3 1651 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
835b2aeb 1652 if (1 < @split) {
c4a85c3b
VA
1653 print colored $header_color, sprintf(
1654 __n("Split into %d hunk.\n",
1655 "Split into %d hunks.\n",
1656 scalar(@split)), scalar(@split));
835b2aeb 1657 }
4af756f3 1658 splice (@hunk, $ix, 1, @split);
835b2aeb
JH
1659 $num = scalar @hunk;
1660 next;
1661 }
4bdd6e7c
PW
1662 elsif ($line =~ /^e/) {
1663 unless ($other =~ /e/) {
1664 error_msg __("Sorry, cannot edit this hunk\n");
1665 next;
1666 }
ac083c47
TR
1667 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1668 if (defined $newhunk) {
1669 splice @hunk, $ix, 1, $newhunk;
1670 }
1671 }
5cde71d6
JH
1672 else {
1673 help_patch_cmd($other);
1674 next;
1675 }
1676 # soft increment
1677 while (1) {
1678 $ix++;
1679 last if ($ix >= $num ||
1680 !defined $hunk[$ix]{USE});
1681 }
1682 }
1683 }
1684
7a26e653
JH
1685 @hunk = coalesce_overlapping_hunks(@hunk);
1686
7b40a455 1687 my $n_lofs = 0;
5cde71d6
JH
1688 my @result = ();
1689 for (@hunk) {
8cbd4310
TR
1690 if ($_->{USE}) {
1691 push @result, @{$_->{TEXT}};
5cde71d6
JH
1692 }
1693 }
1694
1695 if (@result) {
e1327ed5 1696 my @patch = reassemble_patch($head->{TEXT}, @result);
8f0bef6d
TR
1697 my $apply_routine = $patch_mode_flavour{APPLY};
1698 &$apply_routine(@patch);
5cde71d6
JH
1699 refresh();
1700 }
1701
1702 print "\n";
9a7a1e03 1703 return $quit;
5cde71d6
JH
1704}
1705
1706sub diff_cmd {
1707 my @mods = list_modified('index-only');
1708 @mods = grep { !($_->{BINARY}) } @mods;
1709 return if (!@mods);
258e7790 1710 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
5cde71d6
JH
1711 IMMEDIATE => 1,
1712 HEADER => $status_head, },
1713 @mods);
1714 return if (!@them);
258e7790 1715 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
18bc7616
JK
1716 system(qw(git diff -p --cached), $reference, '--',
1717 map { $_->{VALUE} } @them);
5cde71d6
JH
1718}
1719
1720sub quit_cmd {
258e7790 1721 print __("Bye.\n");
5cde71d6
JH
1722 exit(0);
1723}
1724
1725sub help_cmd {
5fa83264
VA
1726# TRANSLATORS: please do not translate the command names
1727# 'status', 'update', 'revert', etc.
1728 print colored $help_color, __ <<'EOF' ;
5cde71d6
JH
1729status - show paths with changes
1730update - add working tree state to the staged set of changes
1731revert - revert staged set of changes back to the HEAD version
1732patch - pick hunks and update selectively
e519eccd 1733diff - view diff between HEAD and index
5cde71d6
JH
1734add untracked - add contents of untracked files to the staged set of changes
1735EOF
1736}
1737
b63e9950
WC
1738sub process_args {
1739 return unless @ARGV;
1740 my $arg = shift @ARGV;
d002ef4d
TR
1741 if ($arg =~ /--patch(?:=(.*))?/) {
1742 if (defined $1) {
1743 if ($1 eq 'reset') {
1744 $patch_mode = 'reset_head';
1745 $patch_mode_revision = 'HEAD';
258e7790 1746 $arg = shift @ARGV or die __("missing --");
d002ef4d
TR
1747 if ($arg ne '--') {
1748 $patch_mode_revision = $arg;
1749 $patch_mode = ($arg eq 'HEAD' ?
1750 'reset_head' : 'reset_nothead');
258e7790 1751 $arg = shift @ARGV or die __("missing --");
d002ef4d 1752 }
4f353658 1753 } elsif ($1 eq 'checkout') {
258e7790 1754 $arg = shift @ARGV or die __("missing --");
4f353658
TR
1755 if ($arg eq '--') {
1756 $patch_mode = 'checkout_index';
1757 } else {
1758 $patch_mode_revision = $arg;
1759 $patch_mode = ($arg eq 'HEAD' ?
1760 'checkout_head' : 'checkout_nothead');
258e7790 1761 $arg = shift @ARGV or die __("missing --");
4f353658 1762 }
dda1f2a5
TR
1763 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1764 $patch_mode = $1;
258e7790 1765 $arg = shift @ARGV or die __("missing --");
d002ef4d 1766 } else {
13c58c17 1767 die sprintf(__("unknown --patch mode: %s"), $1);
d002ef4d
TR
1768 }
1769 } else {
1770 $patch_mode = 'stage';
258e7790 1771 $arg = shift @ARGV or die __("missing --");
d002ef4d 1772 }
13c58c17
VA
1773 die sprintf(__("invalid argument %s, expecting --"),
1774 $arg) unless $arg eq "--";
d002ef4d 1775 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
c852bd54 1776 $patch_mode_only = 1;
b63e9950
WC
1777 }
1778 elsif ($arg ne "--") {
13c58c17 1779 die sprintf(__("invalid argument %s, expecting --"), $arg);
b63e9950
WC
1780 }
1781}
1782
5cde71d6
JH
1783sub main_loop {
1784 my @cmd = ([ 'status', \&status_cmd, ],
1785 [ 'update', \&update_cmd, ],
1786 [ 'revert', \&revert_cmd, ],
1787 [ 'add untracked', \&add_untracked_cmd, ],
1788 [ 'patch', \&patch_update_cmd, ],
1789 [ 'diff', \&diff_cmd, ],
1790 [ 'quit', \&quit_cmd, ],
1791 [ 'help', \&help_cmd, ],
1792 );
1793 while (1) {
258e7790 1794 my ($it) = list_and_choose({ PROMPT => __('What now'),
5cde71d6
JH
1795 SINGLETON => 1,
1796 LIST_FLAT => 4,
258e7790 1797 HEADER => __('*** Commands ***'),
c95c0248 1798 ON_EOF => \&quit_cmd,
5cde71d6
JH
1799 IMMEDIATE => 1 }, @cmd);
1800 if ($it) {
1801 eval {
1802 $it->[1]->();
1803 };
1804 if ($@) {
1805 print "$@";
1806 }
1807 }
1808 }
1809}
1810
b63e9950 1811process_args();
5cde71d6 1812refresh();
c852bd54 1813if ($patch_mode_only) {
b63e9950
WC
1814 patch_update_cmd();
1815}
1816else {
1817 status_cmd();
1818 main_loop();
1819}