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