]> git.ipfire.org Git - thirdparty/git.git/blame - git-add--interactive.perl
Sync with maint
[thirdparty/git.git] / git-add--interactive.perl
CommitLineData
3328aced 1#!/usr/bin/perl
5cde71d6 2
d48b2841 3use 5.008;
5cde71d6 4use strict;
3328aced 5use warnings;
1d542a54 6use Git qw(unquote_path);
258e7790 7use Git::I18N;
b4c61ed6 8
8851f480
JH
9binmode(STDOUT, ":raw");
10
b4c61ed6
JH
11my $repo = Git->repository();
12
f87e310d
JK
13my $menu_use_color = $repo->get_colorbool('color.interactive');
14my ($prompt_color, $header_color, $help_color) =
15 $menu_use_color ? (
16 $repo->get_color('color.interactive.prompt', 'bold blue'),
17 $repo->get_color('color.interactive.header', 'bold'),
18 $repo->get_color('color.interactive.help', 'red bold'),
19 ) : ();
a3019736
TR
20my $error_color = ();
21if ($menu_use_color) {
9aad6cba
SB
22 my $help_color_spec = ($repo->config('color.interactive.help') or
23 'red bold');
a3019736
TR
24 $error_color = $repo->get_color('color.interactive.error',
25 $help_color_spec);
26}
b4c61ed6 27
f87e310d
JK
28my $diff_use_color = $repo->get_colorbool('color.diff');
29my ($fraginfo_color) =
30 $diff_use_color ? (
31 $repo->get_color('color.diff.frag', 'cyan'),
32 ) : ();
ac083c47
TR
33my ($diff_plain_color) =
34 $diff_use_color ? (
35 $repo->get_color('color.diff.plain', ''),
36 ) : ();
37my ($diff_old_color) =
38 $diff_use_color ? (
39 $repo->get_color('color.diff.old', 'red'),
40 ) : ();
41my ($diff_new_color) =
42 $diff_use_color ? (
43 $repo->get_color('color.diff.new', 'green'),
44 ) : ();
b4c61ed6 45
f87e310d 46my $normal_color = $repo->get_color("", "reset");
b4c61ed6 47
2cc0f53b 48my $diff_algorithm = $repo->config('diff.algorithm');
01143847 49my $diff_filter = $repo->config('interactive.difffilter');
2cc0f53b 50
ca6ac7f1 51my $use_readkey = 0;
b5cc0032
TR
52my $use_termcap = 0;
53my %term_escapes;
54
748aa689
TR
55sub ReadMode;
56sub ReadKey;
ca6ac7f1
TR
57if ($repo->config_bool("interactive.singlekey")) {
58 eval {
748aa689
TR
59 require Term::ReadKey;
60 Term::ReadKey->import;
ca6ac7f1
TR
61 $use_readkey = 1;
62 };
b294097b
SR
63 if (!$use_readkey) {
64 print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n";
65 }
b5cc0032
TR
66 eval {
67 require Term::Cap;
68 my $termcap = Term::Cap->Tgetent;
69 foreach (values %$termcap) {
70 $term_escapes{$_} = 1 if /^\e/;
71 }
72 $use_termcap = 1;
73 };
ca6ac7f1
TR
74}
75
b4c61ed6
JH
76sub colored {
77 my $color = shift;
78 my $string = join("", @_);
79
f87e310d 80 if (defined $color) {
b4c61ed6
JH
81 # Put a color code at the beginning of each line, a reset at the end
82 # color after newlines that are not at the end of the string
83 $string =~ s/(\n+)(.)/$1$color$2/g;
84 # reset before newlines
85 $string =~ s/(\n+)/$normal_color$1/g;
86 # codes at beginning and end (if necessary):
87 $string =~ s/^/$color/;
88 $string =~ s/$/$normal_color/ unless $string =~ /\n$/;
89 }
90 return $string;
91}
5cde71d6 92
b63e9950 93# command line options
c852bd54 94my $patch_mode_only;
b63e9950 95my $patch_mode;
d002ef4d 96my $patch_mode_revision;
b63e9950 97
8f0bef6d 98sub apply_patch;
4f353658 99sub apply_patch_for_checkout_commit;
dda1f2a5 100sub apply_patch_for_stash;
8f0bef6d
TR
101
102my %patch_modes = (
103 'stage' => {
104 DIFF => 'diff-files -p',
105 APPLY => sub { apply_patch 'apply --cached', @_; },
106 APPLY_CHECK => 'apply --cached',
8f0bef6d 107 FILTER => 'file-only',
7b8c7051 108 IS_REVERSE => 0,
8f0bef6d 109 },
dda1f2a5
TR
110 'stash' => {
111 DIFF => 'diff-index -p HEAD',
112 APPLY => sub { apply_patch 'apply --cached', @_; },
113 APPLY_CHECK => 'apply --cached',
dda1f2a5 114 FILTER => undef,
7b8c7051 115 IS_REVERSE => 0,
dda1f2a5 116 },
d002ef4d
TR
117 'reset_head' => {
118 DIFF => 'diff-index -p --cached',
119 APPLY => sub { apply_patch 'apply -R --cached', @_; },
120 APPLY_CHECK => 'apply -R --cached',
d002ef4d 121 FILTER => 'index-only',
7b8c7051 122 IS_REVERSE => 1,
d002ef4d
TR
123 },
124 'reset_nothead' => {
125 DIFF => 'diff-index -R -p --cached',
126 APPLY => sub { apply_patch 'apply --cached', @_; },
127 APPLY_CHECK => 'apply --cached',
d002ef4d 128 FILTER => 'index-only',
7b8c7051 129 IS_REVERSE => 0,
d002ef4d 130 },
4f353658
TR
131 'checkout_index' => {
132 DIFF => 'diff-files -p',
133 APPLY => sub { apply_patch 'apply -R', @_; },
134 APPLY_CHECK => 'apply -R',
4f353658 135 FILTER => 'file-only',
7b8c7051 136 IS_REVERSE => 1,
4f353658
TR
137 },
138 'checkout_head' => {
139 DIFF => 'diff-index -p',
140 APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
141 APPLY_CHECK => 'apply -R',
4f353658 142 FILTER => undef,
7b8c7051 143 IS_REVERSE => 1,
4f353658
TR
144 },
145 'checkout_nothead' => {
146 DIFF => 'diff-index -R -p',
147 APPLY => sub { apply_patch_for_checkout_commit '', @_ },
148 APPLY_CHECK => 'apply',
4f353658 149 FILTER => undef,
7b8c7051 150 IS_REVERSE => 0,
4f353658 151 },
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 }
8f0bef6d 717 my @diff = run_cmd_pipe("git", @diff_cmd, "--", $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' };
b717a627
JK
757
758 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
24ab81ae
JK
759 my $dest =
760 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
761 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
762 $head;
b717a627
JK
763 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
764 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
765 }
24ab81ae 766 return ($head, $mode, $deletion);
b717a627
JK
767}
768
835b2aeb
JH
769sub hunk_splittable {
770 my ($text) = @_;
771
772 my @s = split_hunk($text);
773 return (1 < @s);
774}
775
776sub parse_hunk_header {
777 my ($line) = @_;
778 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
7288ed8e
JLH
779 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
780 $o_cnt = 1 unless defined $o_cnt;
781 $n_cnt = 1 unless defined $n_cnt;
835b2aeb
JH
782 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
783}
784
492e60c8
PW
785sub format_hunk_header {
786 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
787 return ("@@ -$o_ofs" .
788 (($o_cnt != 1) ? ",$o_cnt" : '') .
789 " +$n_ofs" .
790 (($n_cnt != 1) ? ",$n_cnt" : '') .
791 " @@\n");
792}
793
835b2aeb 794sub split_hunk {
4af756f3 795 my ($text, $display) = @_;
835b2aeb 796 my @split = ();
4af756f3
WC
797 if (!defined $display) {
798 $display = $text;
799 }
835b2aeb
JH
800 # If there are context lines in the middle of a hunk,
801 # it can be split, but we would need to take care of
802 # overlaps later.
803
7b40a455 804 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
835b2aeb 805 my $hunk_start = 1;
835b2aeb
JH
806
807 OUTER:
808 while (1) {
809 my $next_hunk_start = undef;
810 my $i = $hunk_start - 1;
811 my $this = +{
812 TEXT => [],
4af756f3 813 DISPLAY => [],
0392513f 814 TYPE => 'hunk',
835b2aeb
JH
815 OLD => $o_ofs,
816 NEW => $n_ofs,
817 OCNT => 0,
818 NCNT => 0,
819 ADDDEL => 0,
820 POSTCTX => 0,
4af756f3 821 USE => undef,
835b2aeb
JH
822 };
823
824 while (++$i < @$text) {
825 my $line = $text->[$i];
4af756f3 826 my $display = $display->[$i];
b3e0fcfe
PW
827 if ($line =~ /^\\/) {
828 push @{$this->{TEXT}}, $line;
829 push @{$this->{DISPLAY}}, $display;
830 next;
831 }
835b2aeb
JH
832 if ($line =~ /^ /) {
833 if ($this->{ADDDEL} &&
834 !defined $next_hunk_start) {
835 # We have seen leading context and
836 # adds/dels and then here is another
837 # context, which is trailing for this
838 # split hunk and leading for the next
839 # one.
840 $next_hunk_start = $i;
841 }
842 push @{$this->{TEXT}}, $line;
4af756f3 843 push @{$this->{DISPLAY}}, $display;
835b2aeb
JH
844 $this->{OCNT}++;
845 $this->{NCNT}++;
846 if (defined $next_hunk_start) {
847 $this->{POSTCTX}++;
848 }
849 next;
850 }
851
852 # add/del
853 if (defined $next_hunk_start) {
854 # We are done with the current hunk and
855 # this is the first real change for the
856 # next split one.
857 $hunk_start = $next_hunk_start;
858 $o_ofs = $this->{OLD} + $this->{OCNT};
859 $n_ofs = $this->{NEW} + $this->{NCNT};
860 $o_ofs -= $this->{POSTCTX};
861 $n_ofs -= $this->{POSTCTX};
862 push @split, $this;
863 redo OUTER;
864 }
865 push @{$this->{TEXT}}, $line;
4af756f3 866 push @{$this->{DISPLAY}}, $display;
835b2aeb
JH
867 $this->{ADDDEL}++;
868 if ($line =~ /^-/) {
869 $this->{OCNT}++;
870 }
871 else {
872 $this->{NCNT}++;
873 }
874 }
875
876 push @split, $this;
877 last;
878 }
879
880 for my $hunk (@split) {
881 $o_ofs = $hunk->{OLD};
882 $n_ofs = $hunk->{NEW};
7b40a455
JLH
883 my $o_cnt = $hunk->{OCNT};
884 my $n_cnt = $hunk->{NCNT};
835b2aeb 885
492e60c8 886 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
4af756f3 887 my $display_head = $head;
835b2aeb 888 unshift @{$hunk->{TEXT}}, $head;
4af756f3
WC
889 if ($diff_use_color) {
890 $display_head = colored($fraginfo_color, $head);
891 }
892 unshift @{$hunk->{DISPLAY}}, $display_head;
835b2aeb 893 }
4af756f3 894 return @split;
835b2aeb
JH
895}
896
7a26e653
JH
897sub find_last_o_ctx {
898 my ($it) = @_;
899 my $text = $it->{TEXT};
900 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
901 my $i = @{$text};
902 my $last_o_ctx = $o_ofs + $o_cnt;
903 while (0 < --$i) {
904 my $line = $text->[$i];
905 if ($line =~ /^ /) {
906 $last_o_ctx--;
907 next;
908 }
909 last;
910 }
911 return $last_o_ctx;
912}
913
914sub merge_hunk {
915 my ($prev, $this) = @_;
916 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
917 parse_hunk_header($prev->{TEXT}[0]);
918 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
919 parse_hunk_header($this->{TEXT}[0]);
920
921 my (@line, $i, $ofs, $o_cnt, $n_cnt);
922 $ofs = $o0_ofs;
923 $o_cnt = $n_cnt = 0;
924 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
925 my $line = $prev->{TEXT}[$i];
926 if ($line =~ /^\+/) {
927 $n_cnt++;
928 push @line, $line;
929 next;
b3e0fcfe
PW
930 } elsif ($line =~ /^\\/) {
931 push @line, $line;
932 next;
7a26e653
JH
933 }
934
935 last if ($o1_ofs <= $ofs);
936
937 $o_cnt++;
938 $ofs++;
939 if ($line =~ /^ /) {
940 $n_cnt++;
941 }
942 push @line, $line;
943 }
944
945 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
946 my $line = $this->{TEXT}[$i];
947 if ($line =~ /^\+/) {
948 $n_cnt++;
949 push @line, $line;
950 next;
b3e0fcfe
PW
951 } elsif ($line =~ /^\\/) {
952 push @line, $line;
953 next;
7a26e653
JH
954 }
955 $ofs++;
956 $o_cnt++;
957 if ($line =~ /^ /) {
958 $n_cnt++;
959 }
960 push @line, $line;
961 }
492e60c8 962 my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
7a26e653
JH
963 @{$prev->{TEXT}} = ($head, @line);
964}
965
966sub coalesce_overlapping_hunks {
967 my (@in) = @_;
968 my @out = ();
969
970 my ($last_o_ctx, $last_was_dirty);
fecc6f3a 971 my $ofs_delta = 0;
7a26e653 972
fecc6f3a 973 for (@in) {
3d792161
TR
974 if ($_->{TYPE} ne 'hunk') {
975 push @out, $_;
976 next;
977 }
7a26e653 978 my $text = $_->{TEXT};
fecc6f3a
PW
979 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
980 parse_hunk_header($text->[0]);
981 unless ($_->{USE}) {
982 $ofs_delta += $o_cnt - $n_cnt;
2b8ea7f3
PW
983 # If this hunk has been edited then subtract
984 # the delta that is due to the edit.
985 if ($_->{OFS_DELTA}) {
986 $ofs_delta -= $_->{OFS_DELTA};
987 }
fecc6f3a
PW
988 next;
989 }
990 if ($ofs_delta) {
2bd69b90
PW
991 if ($patch_mode_flavour{IS_REVERSE}) {
992 $o_ofs -= $ofs_delta;
993 } else {
994 $n_ofs += $ofs_delta;
995 }
fecc6f3a
PW
996 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
997 $n_ofs, $n_cnt);
998 }
2b8ea7f3
PW
999 # If this hunk was edited then adjust the offset delta
1000 # to reflect the edit.
1001 if ($_->{OFS_DELTA}) {
1002 $ofs_delta += $_->{OFS_DELTA};
1003 }
7a26e653
JH
1004 if (defined $last_o_ctx &&
1005 $o_ofs <= $last_o_ctx &&
1006 !$_->{DIRTY} &&
1007 !$last_was_dirty) {
1008 merge_hunk($out[-1], $_);
1009 }
1010 else {
1011 push @out, $_;
1012 }
1013 $last_o_ctx = find_last_o_ctx($out[-1]);
1014 $last_was_dirty = $_->{DIRTY};
1015 }
1016 return @out;
1017}
835b2aeb 1018
e1327ed5
JK
1019sub reassemble_patch {
1020 my $head = shift;
1021 my @patch;
1022
1023 # Include everything in the header except the beginning of the diff.
1024 push @patch, (grep { !/^[-+]{3}/ } @$head);
1025
1026 # Then include any headers from the hunk lines, which must
1027 # come before any actual hunk.
1028 while (@_ && $_[0] !~ /^@/) {
1029 push @patch, shift;
1030 }
1031
1032 # Then begin the diff.
1033 push @patch, grep { /^[-+]{3}/ } @$head;
1034
1035 # And then the actual hunks.
1036 push @patch, @_;
1037
1038 return @patch;
1039}
1040
ac083c47
TR
1041sub color_diff {
1042 return map {
1043 colored((/^@/ ? $fraginfo_color :
1044 /^\+/ ? $diff_new_color :
1045 /^-/ ? $diff_old_color :
1046 $diff_plain_color),
1047 $_);
1048 } @_;
1049}
1050
c9d96164
VA
1051my %edit_hunk_manually_modes = (
1052 stage => N__(
1053"If the patch applies cleanly, the edited hunk will immediately be
1054marked for staging."),
1055 stash => N__(
1056"If the patch applies cleanly, the edited hunk will immediately be
1057marked for stashing."),
1058 reset_head => N__(
1059"If the patch applies cleanly, the edited hunk will immediately be
1060marked for unstaging."),
1061 reset_nothead => N__(
1062"If the patch applies cleanly, the edited hunk will immediately be
1063marked for applying."),
1064 checkout_index => N__(
1065"If the patch applies cleanly, the edited hunk will immediately be
0301f1fd 1066marked for discarding."),
c9d96164
VA
1067 checkout_head => N__(
1068"If the patch applies cleanly, the edited hunk will immediately be
1069marked for discarding."),
1070 checkout_nothead => N__(
1071"If the patch applies cleanly, the edited hunk will immediately be
2f0896ec
NTND
1072marked for applying."),
1073 worktree_head => N__(
1074"If the patch applies cleanly, the edited hunk will immediately be
1075marked for discarding."),
1076 worktree_nothead => N__(
1077"If the patch applies cleanly, the edited hunk will immediately be
c9d96164
VA
1078marked for applying."),
1079);
1080
2b8ea7f3
PW
1081sub recount_edited_hunk {
1082 local $_;
1083 my ($oldtext, $newtext) = @_;
1084 my ($o_cnt, $n_cnt) = (0, 0);
1085 for (@{$newtext}[1..$#{$newtext}]) {
1086 my $mode = substr($_, 0, 1);
1087 if ($mode eq '-') {
1088 $o_cnt++;
1089 } elsif ($mode eq '+') {
1090 $n_cnt++;
f4d35a6b 1091 } elsif ($mode eq ' ' or $mode eq "\n") {
2b8ea7f3
PW
1092 $o_cnt++;
1093 $n_cnt++;
1094 }
1095 }
1096 my ($o_ofs, undef, $n_ofs, undef) =
1097 parse_hunk_header($newtext->[0]);
1098 $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1099 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1100 parse_hunk_header($oldtext->[0]);
1101 # Return the change in the number of lines inserted by this hunk
1102 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1103}
1104
ac083c47
TR
1105sub edit_hunk_manually {
1106 my ($oldtext) = @_;
1107
1108 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1109 my $fh;
1110 open $fh, '>', $hunkfile
13c58c17 1111 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
c9d96164 1112 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
ac083c47 1113 print $fh @$oldtext;
7b8c7051
JDL
1114 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1115 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
c9d96164
VA
1116 my $comment_line_char = Git::get_comment_line_char;
1117 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1118---
1119To remove '%s' lines, make them ' ' lines (context).
1120To remove '%s' lines, delete them.
1121Lines starting with %s will be removed.
ac083c47 1122EOF
c9d96164
VA
1123__($edit_hunk_manually_modes{$patch_mode}),
1124# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1125__ <<EOF2 ;
1126If it does not apply cleanly, you will be given an opportunity to
1127edit again. If all lines of the hunk are removed, then the edit is
1128aborted and the hunk is left unchanged.
1129EOF2
ac083c47
TR
1130 close $fh;
1131
89c85593 1132 chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
ac083c47
TR
1133 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1134
1d398a03
DM
1135 if ($? != 0) {
1136 return undef;
1137 }
1138
ac083c47 1139 open $fh, '<', $hunkfile
13c58c17 1140 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
d85d7ecb 1141 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
ac083c47
TR
1142 close $fh;
1143 unlink $hunkfile;
1144
1145 # Abort if nothing remains
1146 if (!grep { /\S/ } @newtext) {
1147 return undef;
1148 }
1149
1150 # Reinsert the first hunk header if the user accidentally deleted it
1151 if ($newtext[0] !~ /^@/) {
1152 unshift @newtext, $oldtext->[0];
1153 }
1154 return \@newtext;
1155}
1156
1157sub diff_applies {
9dce8323 1158 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
8f0bef6d 1159 map { @{$_->{TEXT}} } @_);
ac083c47
TR
1160}
1161
ca6ac7f1
TR
1162sub _restore_terminal_and_die {
1163 ReadMode 'restore';
1164 print "\n";
1165 exit 1;
1166}
1167
1168sub prompt_single_character {
1169 if ($use_readkey) {
1170 local $SIG{TERM} = \&_restore_terminal_and_die;
1171 local $SIG{INT} = \&_restore_terminal_and_die;
1172 ReadMode 'cbreak';
1173 my $key = ReadKey 0;
1174 ReadMode 'restore';
b5cc0032
TR
1175 if ($use_termcap and $key eq "\e") {
1176 while (!defined $term_escapes{$key}) {
1177 my $next = ReadKey 0.5;
1178 last if (!defined $next);
1179 $key .= $next;
1180 }
1181 $key =~ s/\e/^[/;
1182 }
ca6ac7f1
TR
1183 print "$key" if defined $key;
1184 print "\n";
1185 return $key;
1186 } else {
1187 return <STDIN>;
1188 }
1189}
1190
ac083c47
TR
1191sub prompt_yesno {
1192 my ($prompt) = @_;
1193 while (1) {
1194 print colored $prompt_color, $prompt;
ca6ac7f1 1195 my $line = prompt_single_character;
d5addcf5 1196 return undef unless defined $line;
ac083c47
TR
1197 return 0 if $line =~ /^n/i;
1198 return 1 if $line =~ /^y/i;
1199 }
1200}
1201
1202sub edit_hunk_loop {
2b8ea7f3
PW
1203 my ($head, $hunks, $ix) = @_;
1204 my $hunk = $hunks->[$ix];
1205 my $text = $hunk->{TEXT};
ac083c47
TR
1206
1207 while (1) {
2b8ea7f3
PW
1208 my $newtext = edit_hunk_manually($text);
1209 if (!defined $newtext) {
ac083c47
TR
1210 return undef;
1211 }
0392513f 1212 my $newhunk = {
2b8ea7f3
PW
1213 TEXT => $newtext,
1214 TYPE => $hunk->{TYPE},
7a26e653
JH
1215 USE => 1,
1216 DIRTY => 1,
0392513f 1217 };
2b8ea7f3
PW
1218 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1219 # If this hunk has already been edited then add the
1220 # offset delta of the previous edit to get the real
1221 # delta from the original unedited hunk.
1222 $hunk->{OFS_DELTA} and
1223 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
ac083c47 1224 if (diff_applies($head,
2b8ea7f3 1225 @{$hunks}[0..$ix-1],
ac083c47 1226 $newhunk,
2b8ea7f3
PW
1227 @{$hunks}[$ix+1..$#{$hunks}])) {
1228 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
ac083c47
TR
1229 return $newhunk;
1230 }
1231 else {
1232 prompt_yesno(
258e7790
VA
1233 # TRANSLATORS: do not translate [y/n]
1234 # The program will only accept that input
1235 # at this point.
1236 # Consider translating (saying "no" discards!) as
1237 # (saying "n" for "no" discards!) if the translation
1238 # of the word "no" does not start with n.
1239 __('Your edited hunk does not apply. Edit again '
1240 . '(saying "no" discards!) [y/n]? ')
ac083c47
TR
1241 ) or return undef;
1242 }
1243 }
1244}
1245
186b52c5
VA
1246my %help_patch_modes = (
1247 stage => N__(
1248"y - stage this hunk
1249n - do not stage this hunk
1250q - quit; do not stage this hunk or any of the remaining ones
1251a - stage this hunk and all later hunks in the file
1252d - do not stage this hunk or any of the later hunks in the file"),
1253 stash => N__(
1254"y - stash this hunk
1255n - do not stash this hunk
1256q - quit; do not stash this hunk or any of the remaining ones
1257a - stash this hunk and all later hunks in the file
1258d - do not stash this hunk or any of the later hunks in the file"),
1259 reset_head => N__(
1260"y - unstage this hunk
1261n - do not unstage this hunk
1262q - quit; do not unstage this hunk or any of the remaining ones
1263a - unstage this hunk and all later hunks in the file
1264d - do not unstage this hunk or any of the later hunks in the file"),
1265 reset_nothead => N__(
1266"y - apply this hunk to index
1267n - do not apply this hunk to index
1268q - quit; do not apply this hunk or any of the remaining ones
1269a - apply this hunk and all later hunks in the file
1270d - do not apply this hunk or any of the later hunks in the file"),
1271 checkout_index => N__(
1272"y - discard this hunk from worktree
1273n - do not discard this hunk from worktree
1274q - quit; do not discard this hunk or any of the remaining ones
1275a - discard this hunk and all later hunks in the file
1276d - do not discard this hunk or any of the later hunks in the file"),
1277 checkout_head => N__(
1278"y - discard this hunk from index and worktree
1279n - do not discard this hunk from index and worktree
1280q - quit; do not discard this hunk or any of the remaining ones
1281a - discard this hunk and all later hunks in the file
1282d - do not discard this hunk or any of the later hunks in the file"),
1283 checkout_nothead => N__(
1284"y - apply this hunk to index and worktree
1285n - do not apply this hunk to index and worktree
1286q - quit; do not apply this hunk or any of the remaining ones
1287a - apply this hunk and all later hunks in the file
2f0896ec
NTND
1288d - do not apply this hunk or any of the later hunks in the file"),
1289 worktree_head => N__(
1290"y - discard this hunk from worktree
1291n - do not discard this hunk from worktree
1292q - quit; do not discard this hunk or any of the remaining ones
1293a - discard this hunk and all later hunks in the file
1294d - do not discard this hunk or any of the later hunks in the file"),
1295 worktree_nothead => N__(
1296"y - apply this hunk to worktree
1297n - do not apply this hunk to worktree
1298q - quit; do not apply this hunk or any of the remaining ones
1299a - apply this hunk and all later hunks in the file
186b52c5
VA
1300d - do not apply this hunk or any of the later hunks in the file"),
1301);
1302
5cde71d6 1303sub help_patch_cmd {
01a69660 1304 local $_;
88f6ffc1 1305 my $other = $_[0] . ",?";
01a69660
PW
1306 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1307 map { "$_\n" } grep {
1308 my $c = quotemeta(substr($_, 0, 1));
1309 $other =~ /,$c/
1310 } split "\n", __ <<EOF ;
070434d0 1311g - select a hunk to go to
dd971cc9 1312/ - search for a hunk matching the given regex
5cde71d6
JH
1313j - leave this hunk undecided, see next undecided hunk
1314J - leave this hunk undecided, see next hunk
1315k - leave this hunk undecided, see previous undecided hunk
1316K - leave this hunk undecided, see previous hunk
835b2aeb 1317s - split the current hunk into smaller hunks
ac083c47 1318e - manually edit the current hunk
280e50c7 1319? - print help
5cde71d6
JH
1320EOF
1321}
1322
8f0bef6d
TR
1323sub apply_patch {
1324 my $cmd = shift;
9dce8323 1325 my $ret = run_git_apply $cmd, @_;
8f0bef6d
TR
1326 if (!$ret) {
1327 print STDERR @_;
1328 }
1329 return $ret;
1330}
1331
4f353658
TR
1332sub apply_patch_for_checkout_commit {
1333 my $reverse = shift;
9dce8323
JH
1334 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1335 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
4f353658
TR
1336
1337 if ($applies_worktree && $applies_index) {
9dce8323
JH
1338 run_git_apply 'apply '.$reverse.' --cached', @_;
1339 run_git_apply 'apply '.$reverse, @_;
4f353658
TR
1340 return 1;
1341 } elsif (!$applies_index) {
258e7790
VA
1342 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1343 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
9dce8323 1344 return run_git_apply 'apply '.$reverse, @_;
4f353658 1345 } else {
258e7790 1346 print colored $error_color, __("Nothing was applied.\n");
4f353658
TR
1347 return 0;
1348 }
1349 } else {
1350 print STDERR @_;
1351 return 0;
1352 }
1353}
1354
5cde71d6 1355sub patch_update_cmd {
8f0bef6d 1356 my @all_mods = list_modified($patch_mode_flavour{FILTER});
13c58c17 1357 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
4066bd67
JK
1358 for grep { $_->{UNMERGED} } @all_mods;
1359 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1360
9fe7a643 1361 my @mods = grep { !($_->{BINARY}) } @all_mods;
b63e9950 1362 my @them;
5cde71d6 1363
b63e9950 1364 if (!@mods) {
9fe7a643 1365 if (@all_mods) {
258e7790 1366 print STDERR __("Only binary files changed.\n");
9fe7a643 1367 } else {
258e7790 1368 print STDERR __("No changes.\n");
9fe7a643 1369 }
b63e9950
WC
1370 return 0;
1371 }
c852bd54 1372 if ($patch_mode_only) {
b63e9950
WC
1373 @them = @mods;
1374 }
1375 else {
258e7790 1376 @them = list_and_choose({ PROMPT => __('Patch update'),
b63e9950
WC
1377 HEADER => $status_head, },
1378 @mods);
1379 }
12db334e 1380 for (@them) {
9a7a1e03 1381 return 0 if patch_update_file($_->{VALUE});
12db334e 1382 }
a7d9da6c 1383}
5cde71d6 1384
3f6aff68
WP
1385# Generate a one line summary of a hunk.
1386sub summarize_hunk {
1387 my $rhunk = shift;
1388 my $summary = $rhunk->{TEXT}[0];
1389
1390 # Keep the line numbers, discard extra context.
1391 $summary =~ s/@@(.*?)@@.*/$1 /s;
1392 $summary .= " " x (20 - length $summary);
1393
1394 # Add some user context.
1395 for my $line (@{$rhunk->{TEXT}}) {
1396 if ($line =~ m/^[+-].*\w/) {
1397 $summary .= $line;
1398 last;
1399 }
1400 }
1401
1402 chomp $summary;
1403 return substr($summary, 0, 80) . "\n";
1404}
1405
1406
1407# Print a one-line summary of each hunk in the array ref in
41ccfdd9 1408# the first argument, starting with the index in the 2nd.
3f6aff68
WP
1409sub display_hunks {
1410 my ($hunks, $i) = @_;
1411 my $ctr = 0;
1412 $i ||= 0;
1413 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1414 my $status = " ";
1415 if (defined $hunks->[$i]{USE}) {
1416 $status = $hunks->[$i]{USE} ? "+" : "-";
1417 }
1418 printf "%s%2d: %s",
1419 $status,
1420 $i + 1,
1421 summarize_hunk($hunks->[$i]);
1422 }
1423 return $i;
1424}
1425
0539d5e6
VA
1426my %patch_update_prompt_modes = (
1427 stage => {
88f6ffc1
PW
1428 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1429 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1430 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1431 },
1432 stash => {
88f6ffc1
PW
1433 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1434 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1435 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1436 },
1437 reset_head => {
88f6ffc1
PW
1438 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1439 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1440 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1441 },
1442 reset_nothead => {
88f6ffc1
PW
1443 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1444 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1445 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1446 },
1447 checkout_index => {
88f6ffc1
PW
1448 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1449 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1450 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1451 },
1452 checkout_head => {
88f6ffc1
PW
1453 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1454 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1455 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
0539d5e6
VA
1456 },
1457 checkout_nothead => {
88f6ffc1
PW
1458 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1459 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1460 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
0539d5e6 1461 },
2f0896ec
NTND
1462 worktree_head => {
1463 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1464 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1465 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1466 },
1467 worktree_nothead => {
1468 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1469 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1470 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1471 },
0539d5e6
VA
1472);
1473
a7d9da6c 1474sub patch_update_file {
9a7a1e03 1475 my $quit = 0;
5cde71d6 1476 my ($ix, $num);
a7d9da6c 1477 my $path = shift;
5cde71d6 1478 my ($head, @hunk) = parse_diff($path);
24ab81ae 1479 ($head, my $mode, my $deletion) = parse_diff_header($head);
4af756f3
WC
1480 for (@{$head->{DISPLAY}}) {
1481 print;
1482 }
ca724686
JK
1483
1484 if (@{$mode->{TEXT}}) {
0392513f 1485 unshift @hunk, $mode;
ca724686 1486 }
8947fdd5
JK
1487 if (@{$deletion->{TEXT}}) {
1488 foreach my $hunk (@hunk) {
1489 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1490 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1491 }
24ab81ae
JK
1492 @hunk = ($deletion);
1493 }
ca724686 1494
5cde71d6
JH
1495 $num = scalar @hunk;
1496 $ix = 0;
1497
1498 while (1) {
835b2aeb 1499 my ($prev, $next, $other, $undecided, $i);
5cde71d6
JH
1500 $other = '';
1501
1502 if ($num <= $ix) {
1503 $ix = 0;
1504 }
835b2aeb 1505 for ($i = 0; $i < $ix; $i++) {
5cde71d6
JH
1506 if (!defined $hunk[$i]{USE}) {
1507 $prev = 1;
57886bc7 1508 $other .= ',k';
5cde71d6
JH
1509 last;
1510 }
1511 }
1512 if ($ix) {
57886bc7 1513 $other .= ',K';
5cde71d6 1514 }
835b2aeb 1515 for ($i = $ix + 1; $i < $num; $i++) {
5cde71d6
JH
1516 if (!defined $hunk[$i]{USE}) {
1517 $next = 1;
57886bc7 1518 $other .= ',j';
5cde71d6
JH
1519 last;
1520 }
1521 }
1522 if ($ix < $num - 1) {
57886bc7 1523 $other .= ',J';
5cde71d6 1524 }
070434d0 1525 if ($num > 1) {
88f6ffc1 1526 $other .= ',g,/';
070434d0 1527 }
835b2aeb 1528 for ($i = 0; $i < $num; $i++) {
5cde71d6
JH
1529 if (!defined $hunk[$i]{USE}) {
1530 $undecided = 1;
1531 last;
1532 }
1533 }
1534 last if (!$undecided);
1535
0392513f
JK
1536 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1537 hunk_splittable($hunk[$ix]{TEXT})) {
57886bc7 1538 $other .= ',s';
835b2aeb 1539 }
0392513f
JK
1540 if ($hunk[$ix]{TYPE} eq 'hunk') {
1541 $other .= ',e';
1542 }
4af756f3
WC
1543 for (@{$hunk[$ix]{DISPLAY}}) {
1544 print;
1545 }
8085050a 1546 print colored $prompt_color, "(", ($ix+1), "/$num) ",
0539d5e6
VA
1547 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1548
ca6ac7f1 1549 my $line = prompt_single_character;
a8bec7ab 1550 last unless defined $line;
5cde71d6
JH
1551 if ($line) {
1552 if ($line =~ /^y/i) {
1553 $hunk[$ix]{USE} = 1;
1554 }
1555 elsif ($line =~ /^n/i) {
1556 $hunk[$ix]{USE} = 0;
1557 }
1558 elsif ($line =~ /^a/i) {
1559 while ($ix < $num) {
1560 if (!defined $hunk[$ix]{USE}) {
1561 $hunk[$ix]{USE} = 1;
1562 }
1563 $ix++;
1564 }
1565 next;
1566 }
4bdd6e7c 1567 elsif ($line =~ /^g(.*)/) {
070434d0 1568 my $response = $1;
4bdd6e7c
PW
1569 unless ($other =~ /g/) {
1570 error_msg __("No other hunks to goto\n");
1571 next;
1572 }
070434d0
WP
1573 my $no = $ix > 10 ? $ix - 10 : 0;
1574 while ($response eq '') {
070434d0
WP
1575 $no = display_hunks(\@hunk, $no);
1576 if ($no < $num) {
258e7790
VA
1577 print __("go to which hunk (<ret> to see more)? ");
1578 } else {
1579 print __("go to which hunk? ");
070434d0 1580 }
070434d0 1581 $response = <STDIN>;
68c02d7c
TR
1582 if (!defined $response) {
1583 $response = '';
1584 }
070434d0
WP
1585 chomp $response;
1586 }
1587 if ($response !~ /^\s*\d+\s*$/) {
13c58c17
VA
1588 error_msg sprintf(__("Invalid number: '%s'\n"),
1589 $response);
070434d0
WP
1590 } elsif (0 < $response && $response <= $num) {
1591 $ix = $response - 1;
1592 } else {
c4a85c3b
VA
1593 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1594 "Sorry, only %d hunks available.\n", $num), $num);
070434d0
WP
1595 }
1596 next;
1597 }
5cde71d6
JH
1598 elsif ($line =~ /^d/i) {
1599 while ($ix < $num) {
1600 if (!defined $hunk[$ix]{USE}) {
1601 $hunk[$ix]{USE} = 0;
1602 }
1603 $ix++;
1604 }
1605 next;
1606 }
9a7a1e03 1607 elsif ($line =~ /^q/i) {
f5ea3f2b
JH
1608 for ($i = 0; $i < $num; $i++) {
1609 if (!defined $hunk[$i]{USE}) {
1610 $hunk[$i]{USE} = 0;
9a7a1e03 1611 }
9a7a1e03
MM
1612 }
1613 $quit = 1;
f5ea3f2b 1614 last;
9a7a1e03 1615 }
dd971cc9 1616 elsif ($line =~ m|^/(.*)|) {
ca6ac7f1 1617 my $regex = $1;
88f6ffc1
PW
1618 unless ($other =~ m|/|) {
1619 error_msg __("No other hunks to search\n");
1620 next;
1621 }
fd2fb4aa 1622 if ($regex eq "") {
258e7790 1623 print colored $prompt_color, __("search for regex? ");
ca6ac7f1
TR
1624 $regex = <STDIN>;
1625 if (defined $regex) {
1626 chomp $regex;
1627 }
1628 }
dd971cc9
WP
1629 my $search_string;
1630 eval {
ca6ac7f1 1631 $search_string = qr{$regex}m;
dd971cc9
WP
1632 };
1633 if ($@) {
1634 my ($err,$exp) = ($@, $1);
1635 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
13c58c17 1636 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
dd971cc9
WP
1637 next;
1638 }
1639 my $iy = $ix;
1640 while (1) {
1641 my $text = join ("", @{$hunk[$iy]{TEXT}});
1642 last if ($text =~ $search_string);
1643 $iy++;
1644 $iy = 0 if ($iy >= $num);
1645 if ($ix == $iy) {
258e7790 1646 error_msg __("No hunk matches the given pattern\n");
dd971cc9
WP
1647 last;
1648 }
1649 }
1650 $ix = $iy;
1651 next;
1652 }
ace30ba8
WP
1653 elsif ($line =~ /^K/) {
1654 if ($other =~ /K/) {
1655 $ix--;
1656 }
1657 else {
258e7790 1658 error_msg __("No previous hunk\n");
ace30ba8 1659 }
5cde71d6
JH
1660 next;
1661 }
ace30ba8
WP
1662 elsif ($line =~ /^J/) {
1663 if ($other =~ /J/) {
1664 $ix++;
1665 }
1666 else {
258e7790 1667 error_msg __("No next hunk\n");
ace30ba8 1668 }
5cde71d6
JH
1669 next;
1670 }
ace30ba8
WP
1671 elsif ($line =~ /^k/) {
1672 if ($other =~ /k/) {
1673 while (1) {
1674 $ix--;
1675 last if (!$ix ||
1676 !defined $hunk[$ix]{USE});
1677 }
1678 }
1679 else {
258e7790 1680 error_msg __("No previous hunk\n");
5cde71d6
JH
1681 }
1682 next;
1683 }
ace30ba8
WP
1684 elsif ($line =~ /^j/) {
1685 if ($other !~ /j/) {
258e7790 1686 error_msg __("No next hunk\n");
ace30ba8 1687 next;
5cde71d6 1688 }
5cde71d6 1689 }
4bdd6e7c
PW
1690 elsif ($line =~ /^s/) {
1691 unless ($other =~ /s/) {
1692 error_msg __("Sorry, cannot split this hunk\n");
1693 next;
1694 }
4af756f3 1695 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
835b2aeb 1696 if (1 < @split) {
c4a85c3b
VA
1697 print colored $header_color, sprintf(
1698 __n("Split into %d hunk.\n",
1699 "Split into %d hunks.\n",
1700 scalar(@split)), scalar(@split));
835b2aeb 1701 }
4af756f3 1702 splice (@hunk, $ix, 1, @split);
835b2aeb
JH
1703 $num = scalar @hunk;
1704 next;
1705 }
4bdd6e7c
PW
1706 elsif ($line =~ /^e/) {
1707 unless ($other =~ /e/) {
1708 error_msg __("Sorry, cannot edit this hunk\n");
1709 next;
1710 }
ac083c47
TR
1711 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1712 if (defined $newhunk) {
1713 splice @hunk, $ix, 1, $newhunk;
1714 }
1715 }
5cde71d6
JH
1716 else {
1717 help_patch_cmd($other);
1718 next;
1719 }
1720 # soft increment
1721 while (1) {
1722 $ix++;
1723 last if ($ix >= $num ||
1724 !defined $hunk[$ix]{USE});
1725 }
1726 }
1727 }
1728
7a26e653
JH
1729 @hunk = coalesce_overlapping_hunks(@hunk);
1730
7b40a455 1731 my $n_lofs = 0;
5cde71d6
JH
1732 my @result = ();
1733 for (@hunk) {
8cbd4310
TR
1734 if ($_->{USE}) {
1735 push @result, @{$_->{TEXT}};
5cde71d6
JH
1736 }
1737 }
1738
1739 if (@result) {
e1327ed5 1740 my @patch = reassemble_patch($head->{TEXT}, @result);
8f0bef6d
TR
1741 my $apply_routine = $patch_mode_flavour{APPLY};
1742 &$apply_routine(@patch);
5cde71d6
JH
1743 refresh();
1744 }
1745
1746 print "\n";
9a7a1e03 1747 return $quit;
5cde71d6
JH
1748}
1749
1750sub diff_cmd {
1751 my @mods = list_modified('index-only');
1752 @mods = grep { !($_->{BINARY}) } @mods;
1753 return if (!@mods);
258e7790 1754 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
5cde71d6
JH
1755 IMMEDIATE => 1,
1756 HEADER => $status_head, },
1757 @mods);
1758 return if (!@them);
258e7790 1759 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
18bc7616
JK
1760 system(qw(git diff -p --cached), $reference, '--',
1761 map { $_->{VALUE} } @them);
5cde71d6
JH
1762}
1763
1764sub quit_cmd {
258e7790 1765 print __("Bye.\n");
5cde71d6
JH
1766 exit(0);
1767}
1768
1769sub help_cmd {
5fa83264
VA
1770# TRANSLATORS: please do not translate the command names
1771# 'status', 'update', 'revert', etc.
1772 print colored $help_color, __ <<'EOF' ;
5cde71d6
JH
1773status - show paths with changes
1774update - add working tree state to the staged set of changes
1775revert - revert staged set of changes back to the HEAD version
1776patch - pick hunks and update selectively
e519eccd 1777diff - view diff between HEAD and index
5cde71d6
JH
1778add untracked - add contents of untracked files to the staged set of changes
1779EOF
1780}
1781
b63e9950
WC
1782sub process_args {
1783 return unless @ARGV;
1784 my $arg = shift @ARGV;
d002ef4d
TR
1785 if ($arg =~ /--patch(?:=(.*))?/) {
1786 if (defined $1) {
1787 if ($1 eq 'reset') {
1788 $patch_mode = 'reset_head';
1789 $patch_mode_revision = 'HEAD';
258e7790 1790 $arg = shift @ARGV or die __("missing --");
d002ef4d
TR
1791 if ($arg ne '--') {
1792 $patch_mode_revision = $arg;
1793 $patch_mode = ($arg eq 'HEAD' ?
1794 'reset_head' : 'reset_nothead');
258e7790 1795 $arg = shift @ARGV or die __("missing --");
d002ef4d 1796 }
4f353658 1797 } elsif ($1 eq 'checkout') {
258e7790 1798 $arg = shift @ARGV or die __("missing --");
4f353658
TR
1799 if ($arg eq '--') {
1800 $patch_mode = 'checkout_index';
1801 } else {
1802 $patch_mode_revision = $arg;
1803 $patch_mode = ($arg eq 'HEAD' ?
1804 'checkout_head' : 'checkout_nothead');
258e7790 1805 $arg = shift @ARGV or die __("missing --");
4f353658 1806 }
2f0896ec
NTND
1807 } elsif ($1 eq 'worktree') {
1808 $arg = shift @ARGV or die __("missing --");
1809 if ($arg eq '--') {
1810 $patch_mode = 'checkout_index';
1811 } else {
1812 $patch_mode_revision = $arg;
1813 $patch_mode = ($arg eq 'HEAD' ?
1814 'worktree_head' : 'worktree_nothead');
1815 $arg = shift @ARGV or die __("missing --");
1816 }
dda1f2a5
TR
1817 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1818 $patch_mode = $1;
258e7790 1819 $arg = shift @ARGV or die __("missing --");
d002ef4d 1820 } else {
13c58c17 1821 die sprintf(__("unknown --patch mode: %s"), $1);
d002ef4d
TR
1822 }
1823 } else {
1824 $patch_mode = 'stage';
258e7790 1825 $arg = shift @ARGV or die __("missing --");
d002ef4d 1826 }
13c58c17
VA
1827 die sprintf(__("invalid argument %s, expecting --"),
1828 $arg) unless $arg eq "--";
d002ef4d 1829 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
c852bd54 1830 $patch_mode_only = 1;
b63e9950
WC
1831 }
1832 elsif ($arg ne "--") {
13c58c17 1833 die sprintf(__("invalid argument %s, expecting --"), $arg);
b63e9950
WC
1834 }
1835}
1836
5cde71d6
JH
1837sub main_loop {
1838 my @cmd = ([ 'status', \&status_cmd, ],
1839 [ 'update', \&update_cmd, ],
1840 [ 'revert', \&revert_cmd, ],
1841 [ 'add untracked', \&add_untracked_cmd, ],
1842 [ 'patch', \&patch_update_cmd, ],
1843 [ 'diff', \&diff_cmd, ],
1844 [ 'quit', \&quit_cmd, ],
1845 [ 'help', \&help_cmd, ],
1846 );
1847 while (1) {
258e7790 1848 my ($it) = list_and_choose({ PROMPT => __('What now'),
5cde71d6
JH
1849 SINGLETON => 1,
1850 LIST_FLAT => 4,
258e7790 1851 HEADER => __('*** Commands ***'),
c95c0248 1852 ON_EOF => \&quit_cmd,
5cde71d6
JH
1853 IMMEDIATE => 1 }, @cmd);
1854 if ($it) {
1855 eval {
1856 $it->[1]->();
1857 };
1858 if ($@) {
1859 print "$@";
1860 }
1861 }
1862 }
1863}
1864
b63e9950 1865process_args();
5cde71d6 1866refresh();
c852bd54 1867if ($patch_mode_only) {
b63e9950
WC
1868 patch_update_cmd();
1869}
1870else {
1871 status_cmd();
1872 main_loop();
1873}