blob: 10fd30ae16a3bdf943732d07bc33c0a723b5e344 [file] [log] [blame]
Ævar Arnfjörð Bjarmason3328ace2010-09-24 20:00:53 +00001#!/usr/bin/perl
Junio C Hamano5cde71d2006-12-10 20:55:50 -08002
Ævar Arnfjörð Bjarmasond48b2842010-09-24 20:00:52 +00003use 5.008;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08004use strict;
Ævar Arnfjörð Bjarmason3328ace2010-09-24 20:00:53 +00005use warnings;
Phillip Wood1d542a52017-06-30 10:49:09 +01006use Git qw(unquote_path);
Vasco Almeida258e7792016-12-14 11:54:25 -01007use Git::I18N;
Junio C Hamanob4c61ed2007-12-05 00:50:23 -08008
Junio C Hamano8851f482009-02-16 22:43:43 -08009binmode(STDOUT, ":raw");
10
Junio C Hamanob4c61ed2007-12-05 00:50:23 -080011my $repo = Git->repository();
12
Jeff Kingf87e3102008-01-04 03:35:21 -050013my $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 ) : ();
Thomas Rasta3019732009-02-05 09:28:27 +010020my $error_color = ();
21if ($menu_use_color) {
Stephan Beyer9aad6cb2009-02-08 18:40:39 +010022 my $help_color_spec = ($repo->config('color.interactive.help') or
23 'red bold');
Thomas Rasta3019732009-02-05 09:28:27 +010024 $error_color = $repo->get_color('color.interactive.error',
25 $help_color_spec);
26}
Junio C Hamanob4c61ed2007-12-05 00:50:23 -080027
Jeff Kingf87e3102008-01-04 03:35:21 -050028my $diff_use_color = $repo->get_colorbool('color.diff');
29my ($fraginfo_color) =
30 $diff_use_color ? (
31 $repo->get_color('color.diff.frag', 'cyan'),
32 ) : ();
Thomas Rastac083c42008-07-03 00:00:00 +020033my ($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 ) : ();
Junio C Hamanob4c61ed2007-12-05 00:50:23 -080045
Jeff Kingf87e3102008-01-04 03:35:21 -050046my $normal_color = $repo->get_color("", "reset");
Junio C Hamanob4c61ed2007-12-05 00:50:23 -080047
John Keeping2cc0f532013-06-12 19:44:10 +010048my $diff_algorithm = $repo->config('diff.algorithm');
Jeff King01143842016-02-27 00:37:06 -050049my $diff_filter = $repo->config('interactive.difffilter');
John Keeping2cc0f532013-06-12 19:44:10 +010050
Thomas Rastca6ac7f2009-02-05 09:28:26 +010051my $use_readkey = 0;
Thomas Rastb5cc0032011-05-17 17:19:08 +020052my $use_termcap = 0;
53my %term_escapes;
54
Thomas Rast748aa682009-02-06 20:30:01 +010055sub ReadMode;
56sub ReadKey;
Thomas Rastca6ac7f2009-02-05 09:28:26 +010057if ($repo->config_bool("interactive.singlekey")) {
58 eval {
Thomas Rast748aa682009-02-06 20:30:01 +010059 require Term::ReadKey;
60 Term::ReadKey->import;
Thomas Rastca6ac7f2009-02-05 09:28:26 +010061 $use_readkey = 1;
62 };
Simon Ruderichb2940972014-03-03 22:16:12 +010063 if (!$use_readkey) {
64 print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n";
65 }
Thomas Rastb5cc0032011-05-17 17:19:08 +020066 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 };
Thomas Rastca6ac7f2009-02-05 09:28:26 +010074}
75
Junio C Hamanob4c61ed2007-12-05 00:50:23 -080076sub colored {
77 my $color = shift;
78 my $string = join("", @_);
79
Jeff Kingf87e3102008-01-04 03:35:21 -050080 if (defined $color) {
Junio C Hamanob4c61ed2007-12-05 00:50:23 -080081 # 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}
Junio C Hamano5cde71d2006-12-10 20:55:50 -080092
Wincent Colaiutab63e9952007-11-25 14:15:42 +010093# command line options
Jeff Kingc852bd52017-03-02 04:48:22 -050094my $patch_mode_only;
Wincent Colaiutab63e9952007-11-25 14:15:42 +010095my $patch_mode;
Thomas Rastd002ef42009-08-15 13:48:31 +020096my $patch_mode_revision;
Wincent Colaiutab63e9952007-11-25 14:15:42 +010097
Thomas Rast8f0bef62009-08-13 14:29:39 +020098sub apply_patch;
Thomas Rast4f353652009-08-15 13:48:30 +020099sub apply_patch_for_checkout_commit;
Thomas Rastdda1f2a2009-08-13 14:29:44 +0200100sub apply_patch_for_stash;
Thomas Rast8f0bef62009-08-13 14:29:39 +0200101
102my %patch_modes = (
103 'stage' => {
104 DIFF => 'diff-files -p',
105 APPLY => sub { apply_patch 'apply --cached', @_; },
106 APPLY_CHECK => 'apply --cached',
Thomas Rast8f0bef62009-08-13 14:29:39 +0200107 FILTER => 'file-only',
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -0700108 IS_REVERSE => 0,
Thomas Rast8f0bef62009-08-13 14:29:39 +0200109 },
Thomas Rastdda1f2a2009-08-13 14:29:44 +0200110 'stash' => {
111 DIFF => 'diff-index -p HEAD',
112 APPLY => sub { apply_patch 'apply --cached', @_; },
113 APPLY_CHECK => 'apply --cached',
Thomas Rastdda1f2a2009-08-13 14:29:44 +0200114 FILTER => undef,
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -0700115 IS_REVERSE => 0,
Thomas Rastdda1f2a2009-08-13 14:29:44 +0200116 },
Thomas Rastd002ef42009-08-15 13:48:31 +0200117 'reset_head' => {
118 DIFF => 'diff-index -p --cached',
119 APPLY => sub { apply_patch 'apply -R --cached', @_; },
120 APPLY_CHECK => 'apply -R --cached',
Thomas Rastd002ef42009-08-15 13:48:31 +0200121 FILTER => 'index-only',
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -0700122 IS_REVERSE => 1,
Thomas Rastd002ef42009-08-15 13:48:31 +0200123 },
124 'reset_nothead' => {
125 DIFF => 'diff-index -R -p --cached',
126 APPLY => sub { apply_patch 'apply --cached', @_; },
127 APPLY_CHECK => 'apply --cached',
Thomas Rastd002ef42009-08-15 13:48:31 +0200128 FILTER => 'index-only',
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -0700129 IS_REVERSE => 0,
Thomas Rastd002ef42009-08-15 13:48:31 +0200130 },
Thomas Rast4f353652009-08-15 13:48:30 +0200131 'checkout_index' => {
132 DIFF => 'diff-files -p',
133 APPLY => sub { apply_patch 'apply -R', @_; },
134 APPLY_CHECK => 'apply -R',
Thomas Rast4f353652009-08-15 13:48:30 +0200135 FILTER => 'file-only',
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -0700136 IS_REVERSE => 1,
Thomas Rast4f353652009-08-15 13:48:30 +0200137 },
138 'checkout_head' => {
139 DIFF => 'diff-index -p',
140 APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
141 APPLY_CHECK => 'apply -R',
Thomas Rast4f353652009-08-15 13:48:30 +0200142 FILTER => undef,
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -0700143 IS_REVERSE => 1,
Thomas Rast4f353652009-08-15 13:48:30 +0200144 },
145 'checkout_nothead' => {
146 DIFF => 'diff-index -R -p',
147 APPLY => sub { apply_patch_for_checkout_commit '', @_ },
148 APPLY_CHECK => 'apply',
Thomas Rast4f353652009-08-15 13:48:30 +0200149 FILTER => undef,
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -0700150 IS_REVERSE => 0,
Thomas Rast4f353652009-08-15 13:48:30 +0200151 },
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +0700152 '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 },
Thomas Rast8f0bef62009-08-13 14:29:39 +0200166);
167
Vasco Almeida0539d5e2016-12-14 11:54:30 -0100168$patch_mode = 'stage';
169my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800170
171sub run_cmd_pipe {
Johannes Sixtdf17e772013-09-04 09:24:47 +0200172 if ($^O eq 'MSWin32') {
Alex Riesen21e97572007-08-01 14:57:43 +0200173 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;
Johannes Schindelin89c85592019-12-06 13:08:24 +0000180 my @out = <$fh>;
181 close $fh || die "Cannot close @_ ($!)";
182 return @out;
Alex Riesen21e97572007-08-01 14:57:43 +0200183 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800184}
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;
Alex Riesen21e97572007-08-01 14:57:43 +0200195 open $fh, 'git update-index --refresh |'
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800196 or die;
197 while (<$fh>) {
198 ;# ignore 'needs update'
199 }
200 close $fh;
201}
202
203sub list_untracked {
204 map {
205 chomp $_;
Junio C Hamano8851f482009-02-16 22:43:43 -0800206 unquote_path($_);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800207 }
Wincent Colaiuta4c841682007-11-22 02:36:24 +0100208 run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800209}
210
Vasco Almeida258e7792016-12-14 11:54:25 -0100211# 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'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800214
Jeff King18bc7612008-02-13 05:50:51 -0500215{
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
brian m. carlson23ec4c52018-05-02 00:26:09 +0000224{
225 my $empty_tree;
226 sub get_empty_tree {
227 return $empty_tree if defined $empty_tree;
228
Johannes Schindelin89c85592019-12-06 13:08:24 +0000229 ($empty_tree) = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
brian m. carlson23ec4c52018-05-02 00:26:09 +0000230 chomp $empty_tree;
231 return $empty_tree;
232 }
Jeff King18bc7612008-02-13 05:50:51 -0500233}
234
Jeff King954312a2013-10-25 02:52:30 -0400235sub 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
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800246# Returns list of hashes, contents of each of which are:
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800247# 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?
Jeff King4066bd62012-04-05 08:30:08 -0400253# UNMERGED: is the path unmerged
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800254
255sub list_modified {
256 my ($only) = @_;
257 my (%data, @return);
258 my ($add, $del, $adddel, $file);
259
Jeff King954312a2013-10-25 02:52:30 -0400260 my $reference = get_diff_reference($patch_mode_revision);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800261 for (run_cmd_pipe(qw(git diff-index --cached
Jeff King18bc7612008-02-13 05:50:51 -0500262 --numstat --summary), $reference,
Jeff King7288e122017-03-14 12:30:24 -0400263 '--', @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800264 if (($add, $del, $file) =
265 /^([-\d]+) ([-\d]+) (.*)/) {
266 my ($change, $bin);
Junio C Hamano8851f482009-02-16 22:43:43 -0800267 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800268 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100269 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800270 $bin = 1;
271 }
272 else {
273 $change = "+$add/-$del";
274 }
275 $data{$file} = {
276 INDEX => $change,
277 BINARY => $bin,
Vasco Almeida55aa0442016-12-14 11:54:34 -0100278 FILE => __('nothing'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800279 }
280 }
281 elsif (($adddel, $file) =
282 /^ (create|delete) mode [0-7]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800283 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800284 $data{$file}{INDEX_ADDDEL} = $adddel;
285 }
286 }
287
Nguyễn Thái Ngọc Duy12434ef2018-01-13 19:10:38 +0700288 for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800289 if (($add, $del, $file) =
290 /^([-\d]+) ([-\d]+) (.*)/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800291 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800292 my ($change, $bin);
293 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100294 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800295 $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]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800307 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800308 $data{$file}{FILE_ADDDEL} = $adddel;
309 }
Jeff King4066bd62012-04-05 08:30:08 -0400310 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
311 $file = unquote_path($2);
312 if (!exists $data{$file}) {
313 $data{$file} = +{
Vasco Almeida55aa0442016-12-14 11:54:34 -0100314 INDEX => __('unchanged'),
Jeff King4066bd62012-04-05 08:30:08 -0400315 BINARY => 0,
316 };
317 }
318 if ($1 eq 'U') {
319 $data{$file}{UNMERGED} = 1;
320 }
321 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800322 }
323
324 for (sort keys %data) {
325 my $it = $data{$_};
326
327 if ($only) {
328 if ($only eq 'index-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100329 next if ($it->{INDEX} eq __('unchanged'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800330 }
331 if ($only eq 'file-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100332 next if ($it->{FILE} eq __('nothing'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800333 }
334 }
335 push @return, +{
336 VALUE => $_,
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800337 %$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
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100372# 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 }
Wincent Colaiuta63320982007-12-02 14:44:11 +0100398 elsif ((ref $print) eq 'HASH') {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100399 $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 }
Junio C Hamano8851f482009-02-16 22:43:43 -0800426 if (ord($letters[0]) > 127 ||
427 ($soft_limit && $j + 1 > $soft_limit)) {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100428 $prefix = undef;
429 $remainder = $ret;
430 }
431 $return[$i] = [$prefix, $remainder];
432 }
433 return @return;
434}
435
Wincent Colaiuta63320982007-12-02 14:44:11 +0100436# 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
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100443 ($prefix ne '*') && # "all" wildcard
444 ($prefix ne '?'); # prompt help
Wincent Colaiuta63320982007-12-02 14:44:11 +0100445}
446
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100447# 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;
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800452
453 if (!defined $prefix) {
454 return $remainder;
455 }
456
457 if (!is_valid_prefix($prefix)) {
458 return "$prefix$remainder";
459 }
460
Jeff Kingf87e3102008-01-04 03:35:21 -0500461 if (!$menu_use_color) {
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800462 return "[$prefix]$remainder";
463 }
464
465 return "$prompt_color$prefix$normal_color$remainder";
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100466}
467
Thomas Rasta3019732009-02-05 09:28:27 +0100468sub error_msg {
469 print STDERR colored $error_color, @_;
470}
471
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800472sub list_and_choose {
473 my ($opts, @stuff) = @_;
474 my (@chosen, @return);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600475 if (!@stuff) {
476 return @return;
477 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800478 my $i;
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100479 my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800480
481 TOPLOOP:
482 while (1) {
483 my $last_lf = 0;
484
485 if ($opts->{HEADER}) {
486 if (!$opts->{LIST_FLAT}) {
487 print " ";
488 }
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800489 print colored $header_color, "$opts->{HEADER}\n";
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800490 }
491 for ($i = 0; $i < @stuff; $i++) {
492 my $chosen = $chosen[$i] ? '*' : ' ';
493 my $print = $stuff[$i];
Wincent Colaiuta63320982007-12-02 14:44:11 +0100494 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;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800509 }
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
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800527 print colored $prompt_color, $opts->{PROMPT};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800528 if ($opts->{SINGLETON}) {
529 print "> ";
530 }
531 else {
532 print ">> ";
533 }
534 my $line = <STDIN>;
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +0200535 if (!$line) {
536 print "\n";
537 $opts->{ON_EOF}->() if $opts->{ON_EOF};
538 last;
539 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800540 chomp $line;
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200541 last if $line eq '';
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100542 if ($line eq '?') {
543 $opts->{SINGLETON} ?
544 singleton_prompt_help_cmd() :
545 prompt_help_cmd();
546 next TOPLOOP;
547 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800548 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 }
Ciaran McCreesh1e5aaa62008-07-14 19:29:37 +0100556 # A range can be specified like 5-7 or 5-.
557 if ($choice =~ /^(\d+)-(\d*)$/) {
558 ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800559 }
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) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100570 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800571 next TOPLOOP;
572 }
573 }
574 if ($opts->{SINGLETON} && $bottom != $top) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100575 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800576 next TOPLOOP;
577 }
578 for ($i = $bottom-1; $i <= $top-1; $i++) {
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200579 next if (@stuff <= $i || $i < 0);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800580 $chosen[$i] = $choose;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800581 }
582 }
Junio C Hamano12db3342007-11-22 01:47:13 -0800583 last if ($opts->{IMMEDIATE} || $line eq '*');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800584 }
585 for ($i = 0; $i < @stuff; $i++) {
586 if ($chosen[$i]) {
587 push @return, $stuff[$i];
588 }
589 }
590 return @return;
591}
592
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100593sub singleton_prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100594 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100595Prompt help:
5961 - select a numbered item
597foo - select item based on unique prefix
598 - (empty) select nothing
599EOF
600}
601
602sub prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100603 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100604Prompt 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
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800615sub 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 @_;
Vasco Almeidac4a85c32016-12-14 11:54:29 -0100624 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);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800636 }
637}
638
639sub update_cmd {
640 my @mods = list_modified('file-only');
641 return if (!@mods);
642
Vasco Almeida258e7792016-12-14 11:54:25 -0100643 my @update = list_and_choose({ PROMPT => __('Update'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800644 HEADER => $status_head, },
645 @mods);
646 if (@update) {
Junio C Hamanoa4f71122007-02-07 10:56:38 -0800647 system(qw(git update-index --add --remove --),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800648 map { $_->{VALUE} } @update);
649 say_n_paths('updated', @update);
650 }
651 print "\n";
652}
653
654sub revert_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100655 my @update = list_and_choose({ PROMPT => __('Revert'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800656 HEADER => $status_head, },
657 list_modified());
658 if (@update) {
Jeff King18bc7612008-02-13 05:50:51 -0500659 if (is_initial_commit()) {
660 system(qw(git rm --cached),
661 map { $_->{VALUE} } @update);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800662 }
Jeff King18bc7612008-02-13 05:50:51 -0500663 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});
Vasco Almeida13c58c12016-12-14 11:54:27 -0100678 printf(__("note: %s is untracked now.\n"), $_->{VALUE});
Jeff King18bc7612008-02-13 05:50:51 -0500679 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800680 }
681 }
682 refresh();
683 say_n_paths('reverted', @update);
684 }
685 print "\n";
686}
687
688sub add_untracked_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100689 my @add = list_and_choose({ PROMPT => __('Add untracked') },
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800690 list_untracked());
691 if (@add) {
692 system(qw(git update-index --add --), @add);
693 say_n_paths('added', @add);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600694 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -0100695 print __("No untracked files.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800696 }
697 print "\n";
698}
699
Thomas Rast8f0bef62009-08-13 14:29:39 +0200700sub run_git_apply {
701 my $cmd = shift;
702 my $fh;
Phillip Wood3a8522f2018-03-05 10:56:30 +0000703 open $fh, '| git ' . $cmd . " --allow-overlap";
Thomas Rast8f0bef62009-08-13 14:29:39 +0200704 print $fh @_;
705 return close $fh;
706}
707
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800708sub parse_diff {
709 my ($path) = @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +0200710 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
John Keeping2cc0f532013-06-12 19:44:10 +0100711 if (defined $diff_algorithm) {
Junio C Hamanoe5c29092013-06-23 12:19:05 -0700712 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
John Keeping2cc0f532013-06-12 19:44:10 +0100713 }
Thomas Rastd002ef42009-08-15 13:48:31 +0200714 if (defined $patch_mode_revision) {
Jeff King954312a2013-10-25 02:52:30 -0400715 push @diff_cmd, get_diff_reference($patch_mode_revision);
Thomas Rastd002ef42009-08-15 13:48:31 +0200716 }
Thomas Rast8f0bef62009-08-13 14:29:39 +0200717 my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100718 my @colored = ();
719 if ($diff_use_color) {
Jeff King01143842016-02-27 00:37:06 -0500720 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);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100728 }
Jeff King03925132009-04-16 03:14:15 -0400729 my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800730
Jeff King42f7d452018-03-03 00:58:49 -0500731 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
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100739 for (my $i = 0; $i < @diff; $i++) {
740 if ($diff[$i] =~ /^@@ /) {
Jeff King03925132009-04-16 03:14:15 -0400741 push @hunk, { TEXT => [], DISPLAY => [],
742 TYPE => 'hunk' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800743 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100744 push @{$hunk[-1]{TEXT}}, $diff[$i];
745 push @{$hunk[-1]{DISPLAY}},
Jeff King01143842016-02-27 00:37:06 -0500746 (@colored ? $colored[$i] : $diff[$i]);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800747 }
748 return @hunk;
749}
750
Jeff Kingb717a622008-03-27 03:30:43 -0400751sub parse_diff_header {
752 my $src = shift;
753
Jeff King03925132009-04-16 03:14:15 -0400754 my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
755 my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
Jeff King24ab81a2009-10-27 20:52:57 -0400756 my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
Jeff Kingb717a622008-03-27 03:30:43 -0400757
758 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
Jeff King24ab81a2009-10-27 20:52:57 -0400759 my $dest =
760 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
761 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
762 $head;
Jeff Kingb717a622008-03-27 03:30:43 -0400763 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
764 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
765 }
Jeff King24ab81a2009-10-27 20:52:57 -0400766 return ($head, $mode, $deletion);
Jeff Kingb717a622008-03-27 03:30:43 -0400767}
768
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800769sub hunk_splittable {
770 my ($text) = @_;
771
772 my @s = split_hunk($text);
773 return (1 < @s);
774}
775
776sub parse_hunk_header {
777 my ($line) = @_;
778 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
Jean-Luc Herren7288ed82007-10-09 21:29:26 +0200779 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
780 $o_cnt = 1 unless defined $o_cnt;
781 $n_cnt = 1 unless defined $n_cnt;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800782 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
783}
784
Phillip Wood492e60c2018-02-19 11:29:02 +0000785sub format_hunk_header {
786 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
787 return ("@@ -$o_ofs" .
788 (($o_cnt != 1) ? ",$o_cnt" : '') .
789 " +$n_ofs" .
790 (($n_cnt != 1) ? ",$n_cnt" : '') .
791 " @@\n");
792}
793
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800794sub split_hunk {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100795 my ($text, $display) = @_;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800796 my @split = ();
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100797 if (!defined $display) {
798 $display = $text;
799 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800800 # If there are context lines in the middle of a hunk,
801 # it can be split, but we would need to take care of
802 # overlaps later.
803
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200804 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800805 my $hunk_start = 1;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800806
807 OUTER:
808 while (1) {
809 my $next_hunk_start = undef;
810 my $i = $hunk_start - 1;
811 my $this = +{
812 TEXT => [],
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100813 DISPLAY => [],
Jeff King03925132009-04-16 03:14:15 -0400814 TYPE => 'hunk',
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800815 OLD => $o_ofs,
816 NEW => $n_ofs,
817 OCNT => 0,
818 NCNT => 0,
819 ADDDEL => 0,
820 POSTCTX => 0,
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100821 USE => undef,
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800822 };
823
824 while (++$i < @$text) {
825 my $line = $text->[$i];
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100826 my $display = $display->[$i];
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000827 if ($line =~ /^\\/) {
828 push @{$this->{TEXT}}, $line;
829 push @{$this->{DISPLAY}}, $display;
830 next;
831 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800832 if ($line =~ /^ /) {
833 if ($this->{ADDDEL} &&
834 !defined $next_hunk_start) {
835 # We have seen leading context and
836 # adds/dels and then here is another
837 # context, which is trailing for this
838 # split hunk and leading for the next
839 # one.
840 $next_hunk_start = $i;
841 }
842 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100843 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800844 $this->{OCNT}++;
845 $this->{NCNT}++;
846 if (defined $next_hunk_start) {
847 $this->{POSTCTX}++;
848 }
849 next;
850 }
851
852 # add/del
853 if (defined $next_hunk_start) {
854 # We are done with the current hunk and
855 # this is the first real change for the
856 # next split one.
857 $hunk_start = $next_hunk_start;
858 $o_ofs = $this->{OLD} + $this->{OCNT};
859 $n_ofs = $this->{NEW} + $this->{NCNT};
860 $o_ofs -= $this->{POSTCTX};
861 $n_ofs -= $this->{POSTCTX};
862 push @split, $this;
863 redo OUTER;
864 }
865 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100866 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800867 $this->{ADDDEL}++;
868 if ($line =~ /^-/) {
869 $this->{OCNT}++;
870 }
871 else {
872 $this->{NCNT}++;
873 }
874 }
875
876 push @split, $this;
877 last;
878 }
879
880 for my $hunk (@split) {
881 $o_ofs = $hunk->{OLD};
882 $n_ofs = $hunk->{NEW};
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200883 my $o_cnt = $hunk->{OCNT};
884 my $n_cnt = $hunk->{NCNT};
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800885
Phillip Wood492e60c2018-02-19 11:29:02 +0000886 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100887 my $display_head = $head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800888 unshift @{$hunk->{TEXT}}, $head;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100889 if ($diff_use_color) {
890 $display_head = colored($fraginfo_color, $head);
891 }
892 unshift @{$hunk->{DISPLAY}}, $display_head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800893 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100894 return @split;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800895}
896
Junio C Hamano7a26e652009-05-16 10:48:23 -0700897sub find_last_o_ctx {
898 my ($it) = @_;
899 my $text = $it->{TEXT};
900 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
901 my $i = @{$text};
902 my $last_o_ctx = $o_ofs + $o_cnt;
903 while (0 < --$i) {
904 my $line = $text->[$i];
905 if ($line =~ /^ /) {
906 $last_o_ctx--;
907 next;
908 }
909 last;
910 }
911 return $last_o_ctx;
912}
913
914sub merge_hunk {
915 my ($prev, $this) = @_;
916 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
917 parse_hunk_header($prev->{TEXT}[0]);
918 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
919 parse_hunk_header($this->{TEXT}[0]);
920
921 my (@line, $i, $ofs, $o_cnt, $n_cnt);
922 $ofs = $o0_ofs;
923 $o_cnt = $n_cnt = 0;
924 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
925 my $line = $prev->{TEXT}[$i];
926 if ($line =~ /^\+/) {
927 $n_cnt++;
928 push @line, $line;
929 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000930 } elsif ($line =~ /^\\/) {
931 push @line, $line;
932 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700933 }
934
935 last if ($o1_ofs <= $ofs);
936
937 $o_cnt++;
938 $ofs++;
939 if ($line =~ /^ /) {
940 $n_cnt++;
941 }
942 push @line, $line;
943 }
944
945 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
946 my $line = $this->{TEXT}[$i];
947 if ($line =~ /^\+/) {
948 $n_cnt++;
949 push @line, $line;
950 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000951 } elsif ($line =~ /^\\/) {
952 push @line, $line;
953 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700954 }
955 $ofs++;
956 $o_cnt++;
957 if ($line =~ /^ /) {
958 $n_cnt++;
959 }
960 push @line, $line;
961 }
Phillip Wood492e60c2018-02-19 11:29:02 +0000962 my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
Junio C Hamano7a26e652009-05-16 10:48:23 -0700963 @{$prev->{TEXT}} = ($head, @line);
964}
965
966sub coalesce_overlapping_hunks {
967 my (@in) = @_;
968 my @out = ();
969
970 my ($last_o_ctx, $last_was_dirty);
Phillip Woodfecc6f32018-03-01 10:51:00 +0000971 my $ofs_delta = 0;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700972
Phillip Woodfecc6f32018-03-01 10:51:00 +0000973 for (@in) {
Thomas Rast3d792162009-08-15 15:56:39 +0200974 if ($_->{TYPE} ne 'hunk') {
975 push @out, $_;
976 next;
977 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700978 my $text = $_->{TEXT};
Phillip Woodfecc6f32018-03-01 10:51:00 +0000979 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
980 parse_hunk_header($text->[0]);
981 unless ($_->{USE}) {
982 $ofs_delta += $o_cnt - $n_cnt;
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000983 # If this hunk has been edited then subtract
984 # the delta that is due to the edit.
985 if ($_->{OFS_DELTA}) {
986 $ofs_delta -= $_->{OFS_DELTA};
987 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000988 next;
989 }
990 if ($ofs_delta) {
Phillip Wood2bd69b92019-06-12 02:25:27 -0700991 if ($patch_mode_flavour{IS_REVERSE}) {
992 $o_ofs -= $ofs_delta;
993 } else {
994 $n_ofs += $ofs_delta;
995 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000996 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
997 $n_ofs, $n_cnt);
998 }
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000999 # If this hunk was edited then adjust the offset delta
1000 # to reflect the edit.
1001 if ($_->{OFS_DELTA}) {
1002 $ofs_delta += $_->{OFS_DELTA};
1003 }
Junio C Hamano7a26e652009-05-16 10:48:23 -07001004 if (defined $last_o_ctx &&
1005 $o_ofs <= $last_o_ctx &&
1006 !$_->{DIRTY} &&
1007 !$last_was_dirty) {
1008 merge_hunk($out[-1], $_);
1009 }
1010 else {
1011 push @out, $_;
1012 }
1013 $last_o_ctx = find_last_o_ctx($out[-1]);
1014 $last_was_dirty = $_->{DIRTY};
1015 }
1016 return @out;
1017}
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001018
Jeff Kinge1327ed2010-02-22 20:05:44 -05001019sub reassemble_patch {
1020 my $head = shift;
1021 my @patch;
1022
1023 # Include everything in the header except the beginning of the diff.
1024 push @patch, (grep { !/^[-+]{3}/ } @$head);
1025
1026 # Then include any headers from the hunk lines, which must
1027 # come before any actual hunk.
1028 while (@_ && $_[0] !~ /^@/) {
1029 push @patch, shift;
1030 }
1031
1032 # Then begin the diff.
1033 push @patch, grep { /^[-+]{3}/ } @$head;
1034
1035 # And then the actual hunks.
1036 push @patch, @_;
1037
1038 return @patch;
1039}
1040
Thomas Rastac083c42008-07-03 00:00:00 +02001041sub color_diff {
1042 return map {
1043 colored((/^@/ ? $fraginfo_color :
1044 /^\+/ ? $diff_new_color :
1045 /^-/ ? $diff_old_color :
1046 $diff_plain_color),
1047 $_);
1048 } @_;
1049}
1050
Vasco Almeidac9d96162016-12-14 11:54:32 -01001051my %edit_hunk_manually_modes = (
1052 stage => N__(
1053"If the patch applies cleanly, the edited hunk will immediately be
1054marked for staging."),
1055 stash => N__(
1056"If the patch applies cleanly, the edited hunk will immediately be
1057marked for stashing."),
1058 reset_head => N__(
1059"If the patch applies cleanly, the edited hunk will immediately be
1060marked for unstaging."),
1061 reset_nothead => N__(
1062"If the patch applies cleanly, the edited hunk will immediately be
1063marked for applying."),
1064 checkout_index => N__(
1065"If the patch applies cleanly, the edited hunk will immediately be
Ralf Thielow0301f1f2017-04-13 18:41:12 +02001066marked for discarding."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001067 checkout_head => N__(
1068"If the patch applies cleanly, the edited hunk will immediately be
1069marked for discarding."),
1070 checkout_nothead => N__(
1071"If the patch applies cleanly, the edited hunk will immediately be
1072marked for applying."),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001073 worktree_head => N__(
1074"If the patch applies cleanly, the edited hunk will immediately be
1075marked for discarding."),
1076 worktree_nothead => N__(
1077"If the patch applies cleanly, the edited hunk will immediately be
1078marked for applying."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001079);
1080
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001081sub recount_edited_hunk {
1082 local $_;
1083 my ($oldtext, $newtext) = @_;
1084 my ($o_cnt, $n_cnt) = (0, 0);
1085 for (@{$newtext}[1..$#{$newtext}]) {
1086 my $mode = substr($_, 0, 1);
1087 if ($mode eq '-') {
1088 $o_cnt++;
1089 } elsif ($mode eq '+') {
1090 $n_cnt++;
Phillip Woodf4d35a62018-06-11 10:46:02 +01001091 } elsif ($mode eq ' ' or $mode eq "\n") {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001092 $o_cnt++;
1093 $n_cnt++;
1094 }
1095 }
1096 my ($o_ofs, undef, $n_ofs, undef) =
1097 parse_hunk_header($newtext->[0]);
1098 $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1099 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1100 parse_hunk_header($oldtext->[0]);
1101 # Return the change in the number of lines inserted by this hunk
1102 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1103}
1104
Thomas Rastac083c42008-07-03 00:00:00 +02001105sub edit_hunk_manually {
1106 my ($oldtext) = @_;
1107
1108 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1109 my $fh;
1110 open $fh, '>', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001111 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
Vasco Almeidac9d96162016-12-14 11:54:32 -01001112 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
Thomas Rastac083c42008-07-03 00:00:00 +02001113 print $fh @$oldtext;
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -07001114 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1115 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
Vasco Almeidac9d96162016-12-14 11:54:32 -01001116 my $comment_line_char = Git::get_comment_line_char;
1117 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1118---
1119To remove '%s' lines, make them ' ' lines (context).
1120To remove '%s' lines, delete them.
1121Lines starting with %s will be removed.
Thomas Rastac083c42008-07-03 00:00:00 +02001122EOF
Vasco Almeidac9d96162016-12-14 11:54:32 -01001123__($edit_hunk_manually_modes{$patch_mode}),
1124# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1125__ <<EOF2 ;
1126If it does not apply cleanly, you will be given an opportunity to
1127edit again. If all lines of the hunk are removed, then the edit is
1128aborted and the hunk is left unchanged.
1129EOF2
Thomas Rastac083c42008-07-03 00:00:00 +02001130 close $fh;
1131
Johannes Schindelin89c85592019-12-06 13:08:24 +00001132 chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
Thomas Rastac083c42008-07-03 00:00:00 +02001133 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1134
Deskin Miller1d398a02009-02-12 00:19:41 -05001135 if ($? != 0) {
1136 return undef;
1137 }
1138
Thomas Rastac083c42008-07-03 00:00:00 +02001139 open $fh, '<', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001140 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
Jeff Kingd85d7ec2017-06-21 15:28:59 -04001141 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
Thomas Rastac083c42008-07-03 00:00:00 +02001142 close $fh;
1143 unlink $hunkfile;
1144
1145 # Abort if nothing remains
1146 if (!grep { /\S/ } @newtext) {
1147 return undef;
1148 }
1149
1150 # Reinsert the first hunk header if the user accidentally deleted it
1151 if ($newtext[0] !~ /^@/) {
1152 unshift @newtext, $oldtext->[0];
1153 }
1154 return \@newtext;
1155}
1156
1157sub diff_applies {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001158 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
Thomas Rast8f0bef62009-08-13 14:29:39 +02001159 map { @{$_->{TEXT}} } @_);
Thomas Rastac083c42008-07-03 00:00:00 +02001160}
1161
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001162sub _restore_terminal_and_die {
1163 ReadMode 'restore';
1164 print "\n";
1165 exit 1;
1166}
1167
1168sub prompt_single_character {
1169 if ($use_readkey) {
1170 local $SIG{TERM} = \&_restore_terminal_and_die;
1171 local $SIG{INT} = \&_restore_terminal_and_die;
1172 ReadMode 'cbreak';
1173 my $key = ReadKey 0;
1174 ReadMode 'restore';
Thomas Rastb5cc0032011-05-17 17:19:08 +02001175 if ($use_termcap and $key eq "\e") {
1176 while (!defined $term_escapes{$key}) {
1177 my $next = ReadKey 0.5;
1178 last if (!defined $next);
1179 $key .= $next;
1180 }
1181 $key =~ s/\e/^[/;
1182 }
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001183 print "$key" if defined $key;
1184 print "\n";
1185 return $key;
1186 } else {
1187 return <STDIN>;
1188 }
1189}
1190
Thomas Rastac083c42008-07-03 00:00:00 +02001191sub prompt_yesno {
1192 my ($prompt) = @_;
1193 while (1) {
1194 print colored $prompt_color, $prompt;
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001195 my $line = prompt_single_character;
Jeff Kingd5addcf2017-06-21 15:26:36 -04001196 return undef unless defined $line;
Thomas Rastac083c42008-07-03 00:00:00 +02001197 return 0 if $line =~ /^n/i;
1198 return 1 if $line =~ /^y/i;
1199 }
1200}
1201
1202sub edit_hunk_loop {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001203 my ($head, $hunks, $ix) = @_;
1204 my $hunk = $hunks->[$ix];
1205 my $text = $hunk->{TEXT};
Thomas Rastac083c42008-07-03 00:00:00 +02001206
1207 while (1) {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001208 my $newtext = edit_hunk_manually($text);
1209 if (!defined $newtext) {
Thomas Rastac083c42008-07-03 00:00:00 +02001210 return undef;
1211 }
Jeff King03925132009-04-16 03:14:15 -04001212 my $newhunk = {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001213 TEXT => $newtext,
1214 TYPE => $hunk->{TYPE},
Junio C Hamano7a26e652009-05-16 10:48:23 -07001215 USE => 1,
1216 DIRTY => 1,
Jeff King03925132009-04-16 03:14:15 -04001217 };
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001218 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1219 # If this hunk has already been edited then add the
1220 # offset delta of the previous edit to get the real
1221 # delta from the original unedited hunk.
1222 $hunk->{OFS_DELTA} and
1223 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
Thomas Rastac083c42008-07-03 00:00:00 +02001224 if (diff_applies($head,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001225 @{$hunks}[0..$ix-1],
Thomas Rastac083c42008-07-03 00:00:00 +02001226 $newhunk,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001227 @{$hunks}[$ix+1..$#{$hunks}])) {
1228 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
Thomas Rastac083c42008-07-03 00:00:00 +02001229 return $newhunk;
1230 }
1231 else {
1232 prompt_yesno(
Vasco Almeida258e7792016-12-14 11:54:25 -01001233 # TRANSLATORS: do not translate [y/n]
1234 # The program will only accept that input
1235 # at this point.
1236 # Consider translating (saying "no" discards!) as
1237 # (saying "n" for "no" discards!) if the translation
1238 # of the word "no" does not start with n.
1239 __('Your edited hunk does not apply. Edit again '
1240 . '(saying "no" discards!) [y/n]? ')
Thomas Rastac083c42008-07-03 00:00:00 +02001241 ) or return undef;
1242 }
1243 }
1244}
1245
Vasco Almeida186b52c2016-12-14 11:54:31 -01001246my %help_patch_modes = (
1247 stage => N__(
1248"y - stage this hunk
1249n - do not stage this hunk
1250q - quit; do not stage this hunk or any of the remaining ones
1251a - stage this hunk and all later hunks in the file
1252d - do not stage this hunk or any of the later hunks in the file"),
1253 stash => N__(
1254"y - stash this hunk
1255n - do not stash this hunk
1256q - quit; do not stash this hunk or any of the remaining ones
1257a - stash this hunk and all later hunks in the file
1258d - do not stash this hunk or any of the later hunks in the file"),
1259 reset_head => N__(
1260"y - unstage this hunk
1261n - do not unstage this hunk
1262q - quit; do not unstage this hunk or any of the remaining ones
1263a - unstage this hunk and all later hunks in the file
1264d - do not unstage this hunk or any of the later hunks in the file"),
1265 reset_nothead => N__(
1266"y - apply this hunk to index
1267n - do not apply this hunk to index
1268q - quit; do not apply this hunk or any of the remaining ones
1269a - apply this hunk and all later hunks in the file
1270d - do not apply this hunk or any of the later hunks in the file"),
1271 checkout_index => N__(
1272"y - discard this hunk from worktree
1273n - do not discard this hunk from worktree
1274q - quit; do not discard this hunk or any of the remaining ones
1275a - discard this hunk and all later hunks in the file
1276d - do not discard this hunk or any of the later hunks in the file"),
1277 checkout_head => N__(
1278"y - discard this hunk from index and worktree
1279n - do not discard this hunk from index and worktree
1280q - quit; do not discard this hunk or any of the remaining ones
1281a - discard this hunk and all later hunks in the file
1282d - do not discard this hunk or any of the later hunks in the file"),
1283 checkout_nothead => N__(
1284"y - apply this hunk to index and worktree
1285n - do not apply this hunk to index and worktree
1286q - quit; do not apply this hunk or any of the remaining ones
1287a - apply this hunk and all later hunks in the file
1288d - do not apply this hunk or any of the later hunks in the file"),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001289 worktree_head => N__(
1290"y - discard this hunk from worktree
1291n - do not discard this hunk from worktree
1292q - quit; do not discard this hunk or any of the remaining ones
1293a - discard this hunk and all later hunks in the file
1294d - do not discard this hunk or any of the later hunks in the file"),
1295 worktree_nothead => N__(
1296"y - apply this hunk to worktree
1297n - do not apply this hunk to worktree
1298q - quit; do not apply this hunk or any of the remaining ones
1299a - apply this hunk and all later hunks in the file
1300d - do not apply this hunk or any of the later hunks in the file"),
Vasco Almeida186b52c2016-12-14 11:54:31 -01001301);
1302
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001303sub help_patch_cmd {
Phillip Wood01a69662018-02-13 10:32:39 +00001304 local $_;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001305 my $other = $_[0] . ",?";
Phillip Wood01a69662018-02-13 10:32:39 +00001306 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1307 map { "$_\n" } grep {
1308 my $c = quotemeta(substr($_, 0, 1));
1309 $other =~ /,$c/
1310 } split "\n", __ <<EOF ;
William Pursell070434d2008-12-04 10:22:40 +00001311g - select a hunk to go to
William Purselldd971cc2008-11-27 04:07:57 +00001312/ - search for a hunk matching the given regex
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001313j - leave this hunk undecided, see next undecided hunk
1314J - leave this hunk undecided, see next hunk
1315k - leave this hunk undecided, see previous undecided hunk
1316K - leave this hunk undecided, see previous hunk
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001317s - split the current hunk into smaller hunks
Thomas Rastac083c42008-07-03 00:00:00 +02001318e - manually edit the current hunk
Ralf Wildenhues280e50c2007-11-28 19:21:42 +01001319? - print help
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001320EOF
1321}
1322
Thomas Rast8f0bef62009-08-13 14:29:39 +02001323sub apply_patch {
1324 my $cmd = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001325 my $ret = run_git_apply $cmd, @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +02001326 if (!$ret) {
1327 print STDERR @_;
1328 }
1329 return $ret;
1330}
1331
Thomas Rast4f353652009-08-15 13:48:30 +02001332sub apply_patch_for_checkout_commit {
1333 my $reverse = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001334 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1335 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001336
1337 if ($applies_worktree && $applies_index) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001338 run_git_apply 'apply '.$reverse.' --cached', @_;
1339 run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001340 return 1;
1341 } elsif (!$applies_index) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001342 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1343 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001344 return run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001345 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001346 print colored $error_color, __("Nothing was applied.\n");
Thomas Rast4f353652009-08-15 13:48:30 +02001347 return 0;
1348 }
1349 } else {
1350 print STDERR @_;
1351 return 0;
1352 }
1353}
1354
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001355sub patch_update_cmd {
Thomas Rast8f0bef62009-08-13 14:29:39 +02001356 my @all_mods = list_modified($patch_mode_flavour{FILTER});
Vasco Almeida13c58c12016-12-14 11:54:27 -01001357 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
Jeff King4066bd62012-04-05 08:30:08 -04001358 for grep { $_->{UNMERGED} } @all_mods;
1359 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1360
Thomas Rast9fe7a642008-10-26 20:37:06 +01001361 my @mods = grep { !($_->{BINARY}) } @all_mods;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001362 my @them;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001363
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001364 if (!@mods) {
Thomas Rast9fe7a642008-10-26 20:37:06 +01001365 if (@all_mods) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001366 print STDERR __("Only binary files changed.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001367 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001368 print STDERR __("No changes.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001369 }
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001370 return 0;
1371 }
Jeff Kingc852bd52017-03-02 04:48:22 -05001372 if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001373 @them = @mods;
1374 }
1375 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001376 @them = list_and_choose({ PROMPT => __('Patch update'),
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001377 HEADER => $status_head, },
1378 @mods);
1379 }
Junio C Hamano12db3342007-11-22 01:47:13 -08001380 for (@them) {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001381 return 0 if patch_update_file($_->{VALUE});
Junio C Hamano12db3342007-11-22 01:47:13 -08001382 }
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001383}
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001384
William Pursell3f6aff62008-12-04 10:00:24 +00001385# Generate a one line summary of a hunk.
1386sub summarize_hunk {
1387 my $rhunk = shift;
1388 my $summary = $rhunk->{TEXT}[0];
1389
1390 # Keep the line numbers, discard extra context.
1391 $summary =~ s/@@(.*?)@@.*/$1 /s;
1392 $summary .= " " x (20 - length $summary);
1393
1394 # Add some user context.
1395 for my $line (@{$rhunk->{TEXT}}) {
1396 if ($line =~ m/^[+-].*\w/) {
1397 $summary .= $line;
1398 last;
1399 }
1400 }
1401
1402 chomp $summary;
1403 return substr($summary, 0, 80) . "\n";
1404}
1405
1406
1407# Print a one-line summary of each hunk in the array ref in
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001408# the first argument, starting with the index in the 2nd.
William Pursell3f6aff62008-12-04 10:00:24 +00001409sub display_hunks {
1410 my ($hunks, $i) = @_;
1411 my $ctr = 0;
1412 $i ||= 0;
1413 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1414 my $status = " ";
1415 if (defined $hunks->[$i]{USE}) {
1416 $status = $hunks->[$i]{USE} ? "+" : "-";
1417 }
1418 printf "%s%2d: %s",
1419 $status,
1420 $i + 1,
1421 summarize_hunk($hunks->[$i]);
1422 }
1423 return $i;
1424}
1425
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001426my %patch_update_prompt_modes = (
1427 stage => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001428 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1429 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1430 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001431 },
1432 stash => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001433 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1434 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1435 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001436 },
1437 reset_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001438 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1439 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1440 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001441 },
1442 reset_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001443 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1444 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1445 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001446 },
1447 checkout_index => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001448 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1449 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1450 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001451 },
1452 checkout_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001453 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1454 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1455 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001456 },
1457 checkout_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001458 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1459 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1460 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001461 },
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001462 worktree_head => {
1463 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1464 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1465 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1466 },
1467 worktree_nothead => {
1468 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1469 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1470 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1471 },
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001472);
1473
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001474sub patch_update_file {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001475 my $quit = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001476 my ($ix, $num);
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001477 my $path = shift;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001478 my ($head, @hunk) = parse_diff($path);
Jeff King24ab81a2009-10-27 20:52:57 -04001479 ($head, my $mode, my $deletion) = parse_diff_header($head);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001480 for (@{$head->{DISPLAY}}) {
1481 print;
1482 }
Jeff Kingca724682008-03-27 03:32:25 -04001483
1484 if (@{$mode->{TEXT}}) {
Jeff King03925132009-04-16 03:14:15 -04001485 unshift @hunk, $mode;
Jeff Kingca724682008-03-27 03:32:25 -04001486 }
Jeff King8947fdd2009-12-08 02:49:35 -05001487 if (@{$deletion->{TEXT}}) {
1488 foreach my $hunk (@hunk) {
1489 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1490 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1491 }
Jeff King24ab81a2009-10-27 20:52:57 -04001492 @hunk = ($deletion);
1493 }
Jeff Kingca724682008-03-27 03:32:25 -04001494
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001495 $num = scalar @hunk;
1496 $ix = 0;
1497
1498 while (1) {
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001499 my ($prev, $next, $other, $undecided, $i);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001500 $other = '';
1501
1502 if ($num <= $ix) {
1503 $ix = 0;
1504 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001505 for ($i = 0; $i < $ix; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001506 if (!defined $hunk[$i]{USE}) {
1507 $prev = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001508 $other .= ',k';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001509 last;
1510 }
1511 }
1512 if ($ix) {
William Pursell57886bc2008-11-27 04:07:52 +00001513 $other .= ',K';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001514 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001515 for ($i = $ix + 1; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001516 if (!defined $hunk[$i]{USE}) {
1517 $next = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001518 $other .= ',j';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001519 last;
1520 }
1521 }
1522 if ($ix < $num - 1) {
William Pursell57886bc2008-11-27 04:07:52 +00001523 $other .= ',J';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001524 }
William Pursell070434d2008-12-04 10:22:40 +00001525 if ($num > 1) {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001526 $other .= ',g,/';
William Pursell070434d2008-12-04 10:22:40 +00001527 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001528 for ($i = 0; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001529 if (!defined $hunk[$i]{USE}) {
1530 $undecided = 1;
1531 last;
1532 }
1533 }
1534 last if (!$undecided);
1535
Jeff King03925132009-04-16 03:14:15 -04001536 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1537 hunk_splittable($hunk[$ix]{TEXT})) {
William Pursell57886bc2008-11-27 04:07:52 +00001538 $other .= ',s';
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001539 }
Jeff King03925132009-04-16 03:14:15 -04001540 if ($hunk[$ix]{TYPE} eq 'hunk') {
1541 $other .= ',e';
1542 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001543 for (@{$hunk[$ix]{DISPLAY}}) {
1544 print;
1545 }
Kunal Tyagi80850502019-09-29 22:22:59 -07001546 print colored $prompt_color, "(", ($ix+1), "/$num) ",
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001547 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1548
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001549 my $line = prompt_single_character;
Jeff Kinga8bec7a2014-12-15 11:35:27 -05001550 last unless defined $line;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001551 if ($line) {
1552 if ($line =~ /^y/i) {
1553 $hunk[$ix]{USE} = 1;
1554 }
1555 elsif ($line =~ /^n/i) {
1556 $hunk[$ix]{USE} = 0;
1557 }
1558 elsif ($line =~ /^a/i) {
1559 while ($ix < $num) {
1560 if (!defined $hunk[$ix]{USE}) {
1561 $hunk[$ix]{USE} = 1;
1562 }
1563 $ix++;
1564 }
1565 next;
1566 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001567 elsif ($line =~ /^g(.*)/) {
William Pursell070434d2008-12-04 10:22:40 +00001568 my $response = $1;
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001569 unless ($other =~ /g/) {
1570 error_msg __("No other hunks to goto\n");
1571 next;
1572 }
William Pursell070434d2008-12-04 10:22:40 +00001573 my $no = $ix > 10 ? $ix - 10 : 0;
1574 while ($response eq '') {
William Pursell070434d2008-12-04 10:22:40 +00001575 $no = display_hunks(\@hunk, $no);
1576 if ($no < $num) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001577 print __("go to which hunk (<ret> to see more)? ");
1578 } else {
1579 print __("go to which hunk? ");
William Pursell070434d2008-12-04 10:22:40 +00001580 }
William Pursell070434d2008-12-04 10:22:40 +00001581 $response = <STDIN>;
Thomas Rast68c02d72009-02-02 22:46:29 +01001582 if (!defined $response) {
1583 $response = '';
1584 }
William Pursell070434d2008-12-04 10:22:40 +00001585 chomp $response;
1586 }
1587 if ($response !~ /^\s*\d+\s*$/) {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001588 error_msg sprintf(__("Invalid number: '%s'\n"),
1589 $response);
William Pursell070434d2008-12-04 10:22:40 +00001590 } elsif (0 < $response && $response <= $num) {
1591 $ix = $response - 1;
1592 } else {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001593 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1594 "Sorry, only %d hunks available.\n", $num), $num);
William Pursell070434d2008-12-04 10:22:40 +00001595 }
1596 next;
1597 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001598 elsif ($line =~ /^d/i) {
1599 while ($ix < $num) {
1600 if (!defined $hunk[$ix]{USE}) {
1601 $hunk[$ix]{USE} = 0;
1602 }
1603 $ix++;
1604 }
1605 next;
1606 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001607 elsif ($line =~ /^q/i) {
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001608 for ($i = 0; $i < $num; $i++) {
1609 if (!defined $hunk[$i]{USE}) {
1610 $hunk[$i]{USE} = 0;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001611 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001612 }
1613 $quit = 1;
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001614 last;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001615 }
William Purselldd971cc2008-11-27 04:07:57 +00001616 elsif ($line =~ m|^/(.*)|) {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001617 my $regex = $1;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001618 unless ($other =~ m|/|) {
1619 error_msg __("No other hunks to search\n");
1620 next;
1621 }
Ævar Arnfjörð Bjarmasonfd2fb4a2018-03-31 12:50:58 +00001622 if ($regex eq "") {
Vasco Almeida258e7792016-12-14 11:54:25 -01001623 print colored $prompt_color, __("search for regex? ");
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001624 $regex = <STDIN>;
1625 if (defined $regex) {
1626 chomp $regex;
1627 }
1628 }
William Purselldd971cc2008-11-27 04:07:57 +00001629 my $search_string;
1630 eval {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001631 $search_string = qr{$regex}m;
William Purselldd971cc2008-11-27 04:07:57 +00001632 };
1633 if ($@) {
1634 my ($err,$exp) = ($@, $1);
1635 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
Vasco Almeida13c58c12016-12-14 11:54:27 -01001636 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
William Purselldd971cc2008-11-27 04:07:57 +00001637 next;
1638 }
1639 my $iy = $ix;
1640 while (1) {
1641 my $text = join ("", @{$hunk[$iy]{TEXT}});
1642 last if ($text =~ $search_string);
1643 $iy++;
1644 $iy = 0 if ($iy >= $num);
1645 if ($ix == $iy) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001646 error_msg __("No hunk matches the given pattern\n");
William Purselldd971cc2008-11-27 04:07:57 +00001647 last;
1648 }
1649 }
1650 $ix = $iy;
1651 next;
1652 }
William Pursellace30ba2008-11-27 04:08:03 +00001653 elsif ($line =~ /^K/) {
1654 if ($other =~ /K/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001655 $ix--;
William Pursellace30ba2008-11-27 04:08:03 +00001656 }
1657 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001658 error_msg __("No previous hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001659 }
1660 next;
1661 }
William Pursellace30ba2008-11-27 04:08:03 +00001662 elsif ($line =~ /^J/) {
1663 if ($other =~ /J/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001664 $ix++;
William Pursellace30ba2008-11-27 04:08:03 +00001665 }
1666 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001667 error_msg __("No next hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001668 }
1669 next;
1670 }
William Pursellace30ba2008-11-27 04:08:03 +00001671 elsif ($line =~ /^k/) {
1672 if ($other =~ /k/) {
1673 while (1) {
1674 $ix--;
1675 last if (!$ix ||
1676 !defined $hunk[$ix]{USE});
1677 }
1678 }
1679 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001680 error_msg __("No previous hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001681 }
1682 next;
1683 }
1684 elsif ($line =~ /^j/) {
1685 if ($other !~ /j/) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001686 error_msg __("No next hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001687 next;
1688 }
1689 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001690 elsif ($line =~ /^s/) {
1691 unless ($other =~ /s/) {
1692 error_msg __("Sorry, cannot split this hunk\n");
1693 next;
1694 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001695 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001696 if (1 < @split) {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001697 print colored $header_color, sprintf(
1698 __n("Split into %d hunk.\n",
1699 "Split into %d hunks.\n",
1700 scalar(@split)), scalar(@split));
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001701 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001702 splice (@hunk, $ix, 1, @split);
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001703 $num = scalar @hunk;
1704 next;
1705 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001706 elsif ($line =~ /^e/) {
1707 unless ($other =~ /e/) {
1708 error_msg __("Sorry, cannot edit this hunk\n");
1709 next;
1710 }
Thomas Rastac083c42008-07-03 00:00:00 +02001711 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1712 if (defined $newhunk) {
1713 splice @hunk, $ix, 1, $newhunk;
1714 }
1715 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001716 else {
1717 help_patch_cmd($other);
1718 next;
1719 }
1720 # soft increment
1721 while (1) {
1722 $ix++;
1723 last if ($ix >= $num ||
1724 !defined $hunk[$ix]{USE});
1725 }
1726 }
1727 }
1728
Junio C Hamano7a26e652009-05-16 10:48:23 -07001729 @hunk = coalesce_overlapping_hunks(@hunk);
1730
Jean-Luc Herren7b40a452007-10-09 21:34:17 +02001731 my $n_lofs = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001732 my @result = ();
1733 for (@hunk) {
Thomas Rast8cbd4312008-07-02 23:59:16 +02001734 if ($_->{USE}) {
1735 push @result, @{$_->{TEXT}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001736 }
1737 }
1738
1739 if (@result) {
Jeff Kinge1327ed2010-02-22 20:05:44 -05001740 my @patch = reassemble_patch($head->{TEXT}, @result);
Thomas Rast8f0bef62009-08-13 14:29:39 +02001741 my $apply_routine = $patch_mode_flavour{APPLY};
1742 &$apply_routine(@patch);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001743 refresh();
1744 }
1745
1746 print "\n";
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001747 return $quit;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001748}
1749
1750sub diff_cmd {
1751 my @mods = list_modified('index-only');
1752 @mods = grep { !($_->{BINARY}) } @mods;
1753 return if (!@mods);
Vasco Almeida258e7792016-12-14 11:54:25 -01001754 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001755 IMMEDIATE => 1,
1756 HEADER => $status_head, },
1757 @mods);
1758 return if (!@them);
Vasco Almeida258e7792016-12-14 11:54:25 -01001759 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
Jeff King18bc7612008-02-13 05:50:51 -05001760 system(qw(git diff -p --cached), $reference, '--',
1761 map { $_->{VALUE} } @them);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001762}
1763
1764sub quit_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -01001765 print __("Bye.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001766 exit(0);
1767}
1768
1769sub help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -01001770# TRANSLATORS: please do not translate the command names
1771# 'status', 'update', 'revert', etc.
1772 print colored $help_color, __ <<'EOF' ;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001773status - show paths with changes
1774update - add working tree state to the staged set of changes
1775revert - revert staged set of changes back to the HEAD version
1776patch - pick hunks and update selectively
Ralf Thielowe519ecc2017-02-22 19:46:27 +01001777diff - view diff between HEAD and index
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001778add untracked - add contents of untracked files to the staged set of changes
1779EOF
1780}
1781
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001782sub process_args {
1783 return unless @ARGV;
1784 my $arg = shift @ARGV;
Thomas Rastd002ef42009-08-15 13:48:31 +02001785 if ($arg =~ /--patch(?:=(.*))?/) {
1786 if (defined $1) {
1787 if ($1 eq 'reset') {
1788 $patch_mode = 'reset_head';
1789 $patch_mode_revision = 'HEAD';
Vasco Almeida258e7792016-12-14 11:54:25 -01001790 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001791 if ($arg ne '--') {
1792 $patch_mode_revision = $arg;
1793 $patch_mode = ($arg eq 'HEAD' ?
1794 'reset_head' : 'reset_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001795 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001796 }
Thomas Rast4f353652009-08-15 13:48:30 +02001797 } elsif ($1 eq 'checkout') {
Vasco Almeida258e7792016-12-14 11:54:25 -01001798 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001799 if ($arg eq '--') {
1800 $patch_mode = 'checkout_index';
1801 } else {
1802 $patch_mode_revision = $arg;
1803 $patch_mode = ($arg eq 'HEAD' ?
1804 'checkout_head' : 'checkout_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001805 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001806 }
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001807 } elsif ($1 eq 'worktree') {
1808 $arg = shift @ARGV or die __("missing --");
1809 if ($arg eq '--') {
1810 $patch_mode = 'checkout_index';
1811 } else {
1812 $patch_mode_revision = $arg;
1813 $patch_mode = ($arg eq 'HEAD' ?
1814 'worktree_head' : 'worktree_nothead');
1815 $arg = shift @ARGV or die __("missing --");
1816 }
Thomas Rastdda1f2a2009-08-13 14:29:44 +02001817 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1818 $patch_mode = $1;
Vasco Almeida258e7792016-12-14 11:54:25 -01001819 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001820 } else {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001821 die sprintf(__("unknown --patch mode: %s"), $1);
Thomas Rastd002ef42009-08-15 13:48:31 +02001822 }
1823 } else {
1824 $patch_mode = 'stage';
Vasco Almeida258e7792016-12-14 11:54:25 -01001825 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001826 }
Vasco Almeida13c58c12016-12-14 11:54:27 -01001827 die sprintf(__("invalid argument %s, expecting --"),
1828 $arg) unless $arg eq "--";
Thomas Rastd002ef42009-08-15 13:48:31 +02001829 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Jeff Kingc852bd52017-03-02 04:48:22 -05001830 $patch_mode_only = 1;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001831 }
1832 elsif ($arg ne "--") {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001833 die sprintf(__("invalid argument %s, expecting --"), $arg);
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001834 }
1835}
1836
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001837sub main_loop {
1838 my @cmd = ([ 'status', \&status_cmd, ],
1839 [ 'update', \&update_cmd, ],
1840 [ 'revert', \&revert_cmd, ],
1841 [ 'add untracked', \&add_untracked_cmd, ],
1842 [ 'patch', \&patch_update_cmd, ],
1843 [ 'diff', \&diff_cmd, ],
1844 [ 'quit', \&quit_cmd, ],
1845 [ 'help', \&help_cmd, ],
1846 );
1847 while (1) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001848 my ($it) = list_and_choose({ PROMPT => __('What now'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001849 SINGLETON => 1,
1850 LIST_FLAT => 4,
Vasco Almeida258e7792016-12-14 11:54:25 -01001851 HEADER => __('*** Commands ***'),
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +02001852 ON_EOF => \&quit_cmd,
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001853 IMMEDIATE => 1 }, @cmd);
1854 if ($it) {
1855 eval {
1856 $it->[1]->();
1857 };
1858 if ($@) {
1859 print "$@";
1860 }
1861 }
1862 }
1863}
1864
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001865process_args();
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001866refresh();
Jeff Kingc852bd52017-03-02 04:48:22 -05001867if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001868 patch_update_cmd();
1869}
1870else {
1871 status_cmd();
1872 main_loop();
1873}