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