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