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