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