blob: f36c0078ac9a71758a7e1cab701d3a633b66c0d3 [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' };
Johannes Schindelin2c8bd842020-05-27 21:09:06 +0000757 my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' };
Jeff Kingb717a622008-03-27 03:30:43 -0400758
759 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
Jeff King24ab81a2009-10-27 20:52:57 -0400760 my $dest =
761 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
762 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
Johannes Schindelin2c8bd842020-05-27 21:09:06 +0000763 $src->{TEXT}->[$i] =~ /^new file/ ? $addition :
Jeff King24ab81a2009-10-27 20:52:57 -0400764 $head;
Jeff Kingb717a622008-03-27 03:30:43 -0400765 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
766 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
767 }
Johannes Schindelin2c8bd842020-05-27 21:09:06 +0000768 return ($head, $mode, $deletion, $addition);
Jeff Kingb717a622008-03-27 03:30:43 -0400769}
770
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800771sub 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) =
Jean-Luc Herren7288ed82007-10-09 21:29:26 +0200781 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
782 $o_cnt = 1 unless defined $o_cnt;
783 $n_cnt = 1 unless defined $n_cnt;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800784 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
785}
786
Phillip Wood492e60c2018-02-19 11:29:02 +0000787sub 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
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800796sub split_hunk {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100797 my ($text, $display) = @_;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800798 my @split = ();
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100799 if (!defined $display) {
800 $display = $text;
801 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800802 # 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
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200806 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800807 my $hunk_start = 1;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800808
809 OUTER:
810 while (1) {
811 my $next_hunk_start = undef;
812 my $i = $hunk_start - 1;
813 my $this = +{
814 TEXT => [],
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100815 DISPLAY => [],
Jeff King03925132009-04-16 03:14:15 -0400816 TYPE => 'hunk',
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800817 OLD => $o_ofs,
818 NEW => $n_ofs,
819 OCNT => 0,
820 NCNT => 0,
821 ADDDEL => 0,
822 POSTCTX => 0,
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100823 USE => undef,
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800824 };
825
826 while (++$i < @$text) {
827 my $line = $text->[$i];
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100828 my $display = $display->[$i];
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000829 if ($line =~ /^\\/) {
830 push @{$this->{TEXT}}, $line;
831 push @{$this->{DISPLAY}}, $display;
832 next;
833 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800834 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;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100845 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800846 $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;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100868 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800869 $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};
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200885 my $o_cnt = $hunk->{OCNT};
886 my $n_cnt = $hunk->{NCNT};
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800887
Phillip Wood492e60c2018-02-19 11:29:02 +0000888 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100889 my $display_head = $head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800890 unshift @{$hunk->{TEXT}}, $head;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100891 if ($diff_use_color) {
892 $display_head = colored($fraginfo_color, $head);
893 }
894 unshift @{$hunk->{DISPLAY}}, $display_head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800895 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100896 return @split;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800897}
898
Junio C Hamano7a26e652009-05-16 10:48:23 -0700899sub 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;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000932 } elsif ($line =~ /^\\/) {
933 push @line, $line;
934 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700935 }
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;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000953 } elsif ($line =~ /^\\/) {
954 push @line, $line;
955 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700956 }
957 $ofs++;
958 $o_cnt++;
959 if ($line =~ /^ /) {
960 $n_cnt++;
961 }
962 push @line, $line;
963 }
Phillip Wood492e60c2018-02-19 11:29:02 +0000964 my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
Junio C Hamano7a26e652009-05-16 10:48:23 -0700965 @{$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);
Phillip Woodfecc6f32018-03-01 10:51:00 +0000973 my $ofs_delta = 0;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700974
Phillip Woodfecc6f32018-03-01 10:51:00 +0000975 for (@in) {
Thomas Rast3d792162009-08-15 15:56:39 +0200976 if ($_->{TYPE} ne 'hunk') {
977 push @out, $_;
978 next;
979 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700980 my $text = $_->{TEXT};
Phillip Woodfecc6f32018-03-01 10:51:00 +0000981 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;
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000985 # 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 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000990 next;
991 }
992 if ($ofs_delta) {
Phillip Wood2bd69b92019-06-12 02:25:27 -0700993 if ($patch_mode_flavour{IS_REVERSE}) {
994 $o_ofs -= $ofs_delta;
995 } else {
996 $n_ofs += $ofs_delta;
997 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000998 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
999 $n_ofs, $n_cnt);
1000 }
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001001 # 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 }
Junio C Hamano7a26e652009-05-16 10:48:23 -07001006 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}
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001020
Jeff Kinge1327ed2010-02-22 20:05:44 -05001021sub 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
Thomas Rastac083c42008-07-03 00:00:00 +02001043sub 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
Vasco Almeidac9d96162016-12-14 11:54:32 -01001053my %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
Ralf Thielow0301f1f2017-04-13 18:41:12 +02001068marked for discarding."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001069 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
1074marked for applying."),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001075 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
1080marked for applying."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001081);
1082
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001083sub 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++;
Phillip Woodf4d35a62018-06-11 10:46:02 +01001093 } elsif ($mode eq ' ' or $mode eq "\n") {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001094 $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
Thomas Rastac083c42008-07-03 00:00:00 +02001107sub edit_hunk_manually {
1108 my ($oldtext) = @_;
1109
1110 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1111 my $fh;
1112 open $fh, '>', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001113 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
Vasco Almeidac9d96162016-12-14 11:54:32 -01001114 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
Thomas Rastac083c42008-07-03 00:00:00 +02001115 print $fh @$oldtext;
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -07001116 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1117 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
Vasco Almeidac9d96162016-12-14 11:54:32 -01001118 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.
Thomas Rastac083c42008-07-03 00:00:00 +02001124EOF
Vasco Almeidac9d96162016-12-14 11:54:32 -01001125__($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
Thomas Rastac083c42008-07-03 00:00:00 +02001132 close $fh;
1133
Johannes Schindelin89c85592019-12-06 13:08:24 +00001134 chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
Thomas Rastac083c42008-07-03 00:00:00 +02001135 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1136
Deskin Miller1d398a02009-02-12 00:19:41 -05001137 if ($? != 0) {
1138 return undef;
1139 }
1140
Thomas Rastac083c42008-07-03 00:00:00 +02001141 open $fh, '<', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001142 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
Jeff Kingd85d7ec2017-06-21 15:28:59 -04001143 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
Thomas Rastac083c42008-07-03 00:00:00 +02001144 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 {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001160 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
Thomas Rast8f0bef62009-08-13 14:29:39 +02001161 map { @{$_->{TEXT}} } @_);
Thomas Rastac083c42008-07-03 00:00:00 +02001162}
1163
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001164sub _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';
Thomas Rastb5cc0032011-05-17 17:19:08 +02001177 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 }
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001185 print "$key" if defined $key;
1186 print "\n";
1187 return $key;
1188 } else {
1189 return <STDIN>;
1190 }
1191}
1192
Thomas Rastac083c42008-07-03 00:00:00 +02001193sub prompt_yesno {
1194 my ($prompt) = @_;
1195 while (1) {
1196 print colored $prompt_color, $prompt;
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001197 my $line = prompt_single_character;
Jeff Kingd5addcf2017-06-21 15:26:36 -04001198 return undef unless defined $line;
Thomas Rastac083c42008-07-03 00:00:00 +02001199 return 0 if $line =~ /^n/i;
1200 return 1 if $line =~ /^y/i;
1201 }
1202}
1203
1204sub edit_hunk_loop {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001205 my ($head, $hunks, $ix) = @_;
1206 my $hunk = $hunks->[$ix];
1207 my $text = $hunk->{TEXT};
Thomas Rastac083c42008-07-03 00:00:00 +02001208
1209 while (1) {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001210 my $newtext = edit_hunk_manually($text);
1211 if (!defined $newtext) {
Thomas Rastac083c42008-07-03 00:00:00 +02001212 return undef;
1213 }
Jeff King03925132009-04-16 03:14:15 -04001214 my $newhunk = {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001215 TEXT => $newtext,
1216 TYPE => $hunk->{TYPE},
Junio C Hamano7a26e652009-05-16 10:48:23 -07001217 USE => 1,
1218 DIRTY => 1,
Jeff King03925132009-04-16 03:14:15 -04001219 };
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001220 $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};
Thomas Rastac083c42008-07-03 00:00:00 +02001226 if (diff_applies($head,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001227 @{$hunks}[0..$ix-1],
Thomas Rastac083c42008-07-03 00:00:00 +02001228 $newhunk,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001229 @{$hunks}[$ix+1..$#{$hunks}])) {
1230 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
Thomas Rastac083c42008-07-03 00:00:00 +02001231 return $newhunk;
1232 }
1233 else {
1234 prompt_yesno(
Vasco Almeida258e7792016-12-14 11:54:25 -01001235 # 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]? ')
Thomas Rastac083c42008-07-03 00:00:00 +02001243 ) or return undef;
1244 }
1245 }
1246}
1247
Vasco Almeida186b52c2016-12-14 11:54:31 -01001248my %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
1290d - 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 +07001291 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
1302d - do not apply this hunk or any of the later hunks in the file"),
Vasco Almeida186b52c2016-12-14 11:54:31 -01001303);
1304
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001305sub help_patch_cmd {
Phillip Wood01a69662018-02-13 10:32:39 +00001306 local $_;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001307 my $other = $_[0] . ",?";
Phillip Wood01a69662018-02-13 10:32:39 +00001308 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 ;
William Pursell070434d2008-12-04 10:22:40 +00001313g - select a hunk to go to
William Purselldd971cc2008-11-27 04:07:57 +00001314/ - search for a hunk matching the given regex
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001315j - 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
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001319s - split the current hunk into smaller hunks
Thomas Rastac083c42008-07-03 00:00:00 +02001320e - manually edit the current hunk
Ralf Wildenhues280e50c2007-11-28 19:21:42 +01001321? - print help
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001322EOF
1323}
1324
Thomas Rast8f0bef62009-08-13 14:29:39 +02001325sub apply_patch {
1326 my $cmd = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001327 my $ret = run_git_apply $cmd, @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +02001328 if (!$ret) {
1329 print STDERR @_;
1330 }
1331 return $ret;
1332}
1333
Thomas Rast4f353652009-08-15 13:48:30 +02001334sub apply_patch_for_checkout_commit {
1335 my $reverse = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001336 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1337 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001338
1339 if ($applies_worktree && $applies_index) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001340 run_git_apply 'apply '.$reverse.' --cached', @_;
1341 run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001342 return 1;
1343 } elsif (!$applies_index) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001344 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1345 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001346 return run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001347 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001348 print colored $error_color, __("Nothing was applied.\n");
Thomas Rast4f353652009-08-15 13:48:30 +02001349 return 0;
1350 }
1351 } else {
1352 print STDERR @_;
1353 return 0;
1354 }
1355}
1356
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001357sub patch_update_cmd {
Thomas Rast8f0bef62009-08-13 14:29:39 +02001358 my @all_mods = list_modified($patch_mode_flavour{FILTER});
Vasco Almeida13c58c12016-12-14 11:54:27 -01001359 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
Jeff King4066bd62012-04-05 08:30:08 -04001360 for grep { $_->{UNMERGED} } @all_mods;
1361 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1362
Thomas Rast9fe7a642008-10-26 20:37:06 +01001363 my @mods = grep { !($_->{BINARY}) } @all_mods;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001364 my @them;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001365
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001366 if (!@mods) {
Thomas Rast9fe7a642008-10-26 20:37:06 +01001367 if (@all_mods) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001368 print STDERR __("Only binary files changed.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001369 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001370 print STDERR __("No changes.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001371 }
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001372 return 0;
1373 }
Jeff Kingc852bd52017-03-02 04:48:22 -05001374 if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001375 @them = @mods;
1376 }
1377 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001378 @them = list_and_choose({ PROMPT => __('Patch update'),
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001379 HEADER => $status_head, },
1380 @mods);
1381 }
Junio C Hamano12db3342007-11-22 01:47:13 -08001382 for (@them) {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001383 return 0 if patch_update_file($_->{VALUE});
Junio C Hamano12db3342007-11-22 01:47:13 -08001384 }
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001385}
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001386
William Pursell3f6aff62008-12-04 10:00:24 +00001387# 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
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001410# the first argument, starting with the index in the 2nd.
William Pursell3f6aff62008-12-04 10:00:24 +00001411sub 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
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001428my %patch_update_prompt_modes = (
1429 stage => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001430 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1431 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001432 addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001433 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001434 },
1435 stash => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001436 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1437 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001438 addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001439 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001440 },
1441 reset_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001442 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1443 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001444 addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001445 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001446 },
1447 reset_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001448 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,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001450 addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001451 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001452 },
1453 checkout_index => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001454 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,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001456 addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001457 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001458 },
1459 checkout_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001460 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,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001462 addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001463 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001464 },
1465 checkout_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001466 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,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001468 addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001469 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001470 },
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001471 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,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001474 addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001475 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,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001480 addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001481 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1482 },
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001483);
1484
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001485sub patch_update_file {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001486 my $quit = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001487 my ($ix, $num);
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001488 my $path = shift;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001489 my ($head, @hunk) = parse_diff($path);
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001490 ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001491 for (@{$head->{DISPLAY}}) {
1492 print;
1493 }
Jeff Kingca724682008-03-27 03:32:25 -04001494
1495 if (@{$mode->{TEXT}}) {
Jeff King03925132009-04-16 03:14:15 -04001496 unshift @hunk, $mode;
Jeff Kingca724682008-03-27 03:32:25 -04001497 }
Jeff King8947fdd2009-12-08 02:49:35 -05001498 if (@{$deletion->{TEXT}}) {
1499 foreach my $hunk (@hunk) {
1500 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1501 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1502 }
Jeff King24ab81a2009-10-27 20:52:57 -04001503 @hunk = ($deletion);
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001504 } elsif (@{$addition->{TEXT}}) {
1505 foreach my $hunk (@hunk) {
1506 push @{$addition->{TEXT}}, @{$hunk->{TEXT}};
1507 push @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}};
1508 }
1509 @hunk = ($addition);
Jeff King24ab81a2009-10-27 20:52:57 -04001510 }
Jeff Kingca724682008-03-27 03:32:25 -04001511
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001512 $num = scalar @hunk;
1513 $ix = 0;
1514
1515 while (1) {
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001516 my ($prev, $next, $other, $undecided, $i);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001517 $other = '';
1518
1519 if ($num <= $ix) {
1520 $ix = 0;
1521 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001522 for ($i = 0; $i < $ix; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001523 if (!defined $hunk[$i]{USE}) {
1524 $prev = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001525 $other .= ',k';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001526 last;
1527 }
1528 }
1529 if ($ix) {
William Pursell57886bc2008-11-27 04:07:52 +00001530 $other .= ',K';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001531 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001532 for ($i = $ix + 1; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001533 if (!defined $hunk[$i]{USE}) {
1534 $next = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001535 $other .= ',j';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001536 last;
1537 }
1538 }
1539 if ($ix < $num - 1) {
William Pursell57886bc2008-11-27 04:07:52 +00001540 $other .= ',J';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001541 }
William Pursell070434d2008-12-04 10:22:40 +00001542 if ($num > 1) {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001543 $other .= ',g,/';
William Pursell070434d2008-12-04 10:22:40 +00001544 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001545 for ($i = 0; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001546 if (!defined $hunk[$i]{USE}) {
1547 $undecided = 1;
1548 last;
1549 }
1550 }
1551 last if (!$undecided);
1552
Jeff King03925132009-04-16 03:14:15 -04001553 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1554 hunk_splittable($hunk[$ix]{TEXT})) {
William Pursell57886bc2008-11-27 04:07:52 +00001555 $other .= ',s';
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001556 }
Jeff King03925132009-04-16 03:14:15 -04001557 if ($hunk[$ix]{TYPE} eq 'hunk') {
1558 $other .= ',e';
1559 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001560 for (@{$hunk[$ix]{DISPLAY}}) {
1561 print;
1562 }
Kunal Tyagi80850502019-09-29 22:22:59 -07001563 print colored $prompt_color, "(", ($ix+1), "/$num) ",
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001564 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1565
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001566 my $line = prompt_single_character;
Jeff Kinga8bec7a2014-12-15 11:35:27 -05001567 last unless defined $line;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001568 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 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001584 elsif ($line =~ /^g(.*)/) {
William Pursell070434d2008-12-04 10:22:40 +00001585 my $response = $1;
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001586 unless ($other =~ /g/) {
1587 error_msg __("No other hunks to goto\n");
1588 next;
1589 }
William Pursell070434d2008-12-04 10:22:40 +00001590 my $no = $ix > 10 ? $ix - 10 : 0;
1591 while ($response eq '') {
William Pursell070434d2008-12-04 10:22:40 +00001592 $no = display_hunks(\@hunk, $no);
1593 if ($no < $num) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001594 print __("go to which hunk (<ret> to see more)? ");
1595 } else {
1596 print __("go to which hunk? ");
William Pursell070434d2008-12-04 10:22:40 +00001597 }
William Pursell070434d2008-12-04 10:22:40 +00001598 $response = <STDIN>;
Thomas Rast68c02d72009-02-02 22:46:29 +01001599 if (!defined $response) {
1600 $response = '';
1601 }
William Pursell070434d2008-12-04 10:22:40 +00001602 chomp $response;
1603 }
1604 if ($response !~ /^\s*\d+\s*$/) {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001605 error_msg sprintf(__("Invalid number: '%s'\n"),
1606 $response);
William Pursell070434d2008-12-04 10:22:40 +00001607 } elsif (0 < $response && $response <= $num) {
1608 $ix = $response - 1;
1609 } else {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001610 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1611 "Sorry, only %d hunks available.\n", $num), $num);
William Pursell070434d2008-12-04 10:22:40 +00001612 }
1613 next;
1614 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001615 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 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001624 elsif ($line =~ /^q/i) {
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001625 for ($i = 0; $i < $num; $i++) {
1626 if (!defined $hunk[$i]{USE}) {
1627 $hunk[$i]{USE} = 0;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001628 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001629 }
1630 $quit = 1;
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001631 last;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001632 }
William Purselldd971cc2008-11-27 04:07:57 +00001633 elsif ($line =~ m|^/(.*)|) {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001634 my $regex = $1;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001635 unless ($other =~ m|/|) {
1636 error_msg __("No other hunks to search\n");
1637 next;
1638 }
Ævar Arnfjörð Bjarmasonfd2fb4a2018-03-31 12:50:58 +00001639 if ($regex eq "") {
Vasco Almeida258e7792016-12-14 11:54:25 -01001640 print colored $prompt_color, __("search for regex? ");
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001641 $regex = <STDIN>;
1642 if (defined $regex) {
1643 chomp $regex;
1644 }
1645 }
William Purselldd971cc2008-11-27 04:07:57 +00001646 my $search_string;
1647 eval {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001648 $search_string = qr{$regex}m;
William Purselldd971cc2008-11-27 04:07:57 +00001649 };
1650 if ($@) {
1651 my ($err,$exp) = ($@, $1);
1652 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
Vasco Almeida13c58c12016-12-14 11:54:27 -01001653 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
William Purselldd971cc2008-11-27 04:07:57 +00001654 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) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001663 error_msg __("No hunk matches the given pattern\n");
William Purselldd971cc2008-11-27 04:07:57 +00001664 last;
1665 }
1666 }
1667 $ix = $iy;
1668 next;
1669 }
William Pursellace30ba2008-11-27 04:08:03 +00001670 elsif ($line =~ /^K/) {
1671 if ($other =~ /K/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001672 $ix--;
William Pursellace30ba2008-11-27 04:08:03 +00001673 }
1674 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001675 error_msg __("No previous hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001676 }
1677 next;
1678 }
William Pursellace30ba2008-11-27 04:08:03 +00001679 elsif ($line =~ /^J/) {
1680 if ($other =~ /J/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001681 $ix++;
William Pursellace30ba2008-11-27 04:08:03 +00001682 }
1683 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001684 error_msg __("No next hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001685 }
1686 next;
1687 }
William Pursellace30ba2008-11-27 04:08:03 +00001688 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 {
Vasco Almeida258e7792016-12-14 11:54:25 -01001697 error_msg __("No previous hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001698 }
1699 next;
1700 }
1701 elsif ($line =~ /^j/) {
1702 if ($other !~ /j/) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001703 error_msg __("No next hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001704 next;
1705 }
1706 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001707 elsif ($line =~ /^s/) {
1708 unless ($other =~ /s/) {
1709 error_msg __("Sorry, cannot split this hunk\n");
1710 next;
1711 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001712 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001713 if (1 < @split) {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001714 print colored $header_color, sprintf(
1715 __n("Split into %d hunk.\n",
1716 "Split into %d hunks.\n",
1717 scalar(@split)), scalar(@split));
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001718 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001719 splice (@hunk, $ix, 1, @split);
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001720 $num = scalar @hunk;
1721 next;
1722 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001723 elsif ($line =~ /^e/) {
1724 unless ($other =~ /e/) {
1725 error_msg __("Sorry, cannot edit this hunk\n");
1726 next;
1727 }
Thomas Rastac083c42008-07-03 00:00:00 +02001728 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1729 if (defined $newhunk) {
1730 splice @hunk, $ix, 1, $newhunk;
1731 }
1732 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001733 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
Junio C Hamano7a26e652009-05-16 10:48:23 -07001746 @hunk = coalesce_overlapping_hunks(@hunk);
1747
Jean-Luc Herren7b40a452007-10-09 21:34:17 +02001748 my $n_lofs = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001749 my @result = ();
1750 for (@hunk) {
Thomas Rast8cbd4312008-07-02 23:59:16 +02001751 if ($_->{USE}) {
1752 push @result, @{$_->{TEXT}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001753 }
1754 }
1755
1756 if (@result) {
Jeff Kinge1327ed2010-02-22 20:05:44 -05001757 my @patch = reassemble_patch($head->{TEXT}, @result);
Thomas Rast8f0bef62009-08-13 14:29:39 +02001758 my $apply_routine = $patch_mode_flavour{APPLY};
1759 &$apply_routine(@patch);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001760 refresh();
1761 }
1762
1763 print "\n";
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001764 return $quit;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001765}
1766
1767sub diff_cmd {
1768 my @mods = list_modified('index-only');
1769 @mods = grep { !($_->{BINARY}) } @mods;
1770 return if (!@mods);
Vasco Almeida258e7792016-12-14 11:54:25 -01001771 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001772 IMMEDIATE => 1,
1773 HEADER => $status_head, },
1774 @mods);
1775 return if (!@them);
Vasco Almeida258e7792016-12-14 11:54:25 -01001776 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
Jeff King18bc7612008-02-13 05:50:51 -05001777 system(qw(git diff -p --cached), $reference, '--',
1778 map { $_->{VALUE} } @them);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001779}
1780
1781sub quit_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -01001782 print __("Bye.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001783 exit(0);
1784}
1785
1786sub help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -01001787# TRANSLATORS: please do not translate the command names
1788# 'status', 'update', 'revert', etc.
1789 print colored $help_color, __ <<'EOF' ;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001790status - 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
Ralf Thielowe519ecc2017-02-22 19:46:27 +01001794diff - view diff between HEAD and index
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001795add untracked - add contents of untracked files to the staged set of changes
1796EOF
1797}
1798
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001799sub process_args {
1800 return unless @ARGV;
1801 my $arg = shift @ARGV;
Thomas Rastd002ef42009-08-15 13:48:31 +02001802 if ($arg =~ /--patch(?:=(.*))?/) {
1803 if (defined $1) {
1804 if ($1 eq 'reset') {
1805 $patch_mode = 'reset_head';
1806 $patch_mode_revision = 'HEAD';
Vasco Almeida258e7792016-12-14 11:54:25 -01001807 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001808 if ($arg ne '--') {
1809 $patch_mode_revision = $arg;
1810 $patch_mode = ($arg eq 'HEAD' ?
1811 'reset_head' : 'reset_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001812 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001813 }
Thomas Rast4f353652009-08-15 13:48:30 +02001814 } elsif ($1 eq 'checkout') {
Vasco Almeida258e7792016-12-14 11:54:25 -01001815 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001816 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');
Vasco Almeida258e7792016-12-14 11:54:25 -01001822 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001823 }
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001824 } 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 }
Thomas Rastdda1f2a2009-08-13 14:29:44 +02001834 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1835 $patch_mode = $1;
Vasco Almeida258e7792016-12-14 11:54:25 -01001836 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001837 } else {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001838 die sprintf(__("unknown --patch mode: %s"), $1);
Thomas Rastd002ef42009-08-15 13:48:31 +02001839 }
1840 } else {
1841 $patch_mode = 'stage';
Vasco Almeida258e7792016-12-14 11:54:25 -01001842 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001843 }
Vasco Almeida13c58c12016-12-14 11:54:27 -01001844 die sprintf(__("invalid argument %s, expecting --"),
1845 $arg) unless $arg eq "--";
Thomas Rastd002ef42009-08-15 13:48:31 +02001846 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Jeff Kingc852bd52017-03-02 04:48:22 -05001847 $patch_mode_only = 1;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001848 }
1849 elsif ($arg ne "--") {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001850 die sprintf(__("invalid argument %s, expecting --"), $arg);
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001851 }
1852}
1853
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001854sub 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) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001865 my ($it) = list_and_choose({ PROMPT => __('What now'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001866 SINGLETON => 1,
1867 LIST_FLAT => 4,
Vasco Almeida258e7792016-12-14 11:54:25 -01001868 HEADER => __('*** Commands ***'),
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +02001869 ON_EOF => \&quit_cmd,
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001870 IMMEDIATE => 1 }, @cmd);
1871 if ($it) {
1872 eval {
1873 $it->[1]->();
1874 };
1875 if ($@) {
1876 print "$@";
1877 }
1878 }
1879 }
1880}
1881
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001882process_args();
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001883refresh();
Jeff Kingc852bd52017-03-02 04:48:22 -05001884if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001885 patch_update_cmd();
1886}
1887else {
1888 status_cmd();
1889 main_loop();
1890}