blob: bc3a1e8effa321bd40bb83170646b98ca7f2e968 [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 ) : ();
Johannes Schindelin890b68b2020-11-16 16:08:31 +000033my ($diff_context_color) =
Thomas Rastac083c42008-07-03 00:00:00 +020034 $diff_use_color ? (
Johannes Schindelin890b68b2020-11-16 16:08:31 +000035 $repo->get_color($repo->config('color.diff.context') ? 'color.diff.context' : 'color.diff.plain', ''),
Thomas Rastac083c42008-07-03 00:00:00 +020036 ) : ();
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}) {
Johannes Schindelin0cb89392020-11-16 16:08:30 +0000486 my $indent = $opts->{LIST_FLAT} ? "" : " ";
487 print colored $header_color, "$indent$opts->{HEADER}\n";
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800488 }
489 for ($i = 0; $i < @stuff; $i++) {
490 my $chosen = $chosen[$i] ? '*' : ' ';
491 my $print = $stuff[$i];
Wincent Colaiuta63320982007-12-02 14:44:11 +0100492 my $ref = ref $print;
493 my $highlighted = highlight_prefix(@{$prefixes[$i]})
494 if @prefixes;
495 if ($ref eq 'ARRAY') {
496 $print = $highlighted || $print->[0];
497 }
498 elsif ($ref eq 'HASH') {
499 my $value = $highlighted || $print->{VALUE};
500 $print = sprintf($status_fmt,
501 $print->{INDEX},
502 $print->{FILE},
503 $value);
504 }
505 else {
506 $print = $highlighted || $print;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800507 }
508 printf("%s%2d: %s", $chosen, $i+1, $print);
509 if (($opts->{LIST_FLAT}) &&
510 (($i + 1) % ($opts->{LIST_FLAT}))) {
511 print "\t";
512 $last_lf = 0;
513 }
514 else {
515 print "\n";
516 $last_lf = 1;
517 }
518 }
519 if (!$last_lf) {
520 print "\n";
521 }
522
523 return if ($opts->{LIST_ONLY});
524
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800525 print colored $prompt_color, $opts->{PROMPT};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800526 if ($opts->{SINGLETON}) {
527 print "> ";
528 }
529 else {
530 print ">> ";
531 }
532 my $line = <STDIN>;
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +0200533 if (!$line) {
534 print "\n";
535 $opts->{ON_EOF}->() if $opts->{ON_EOF};
536 last;
537 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800538 chomp $line;
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200539 last if $line eq '';
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100540 if ($line eq '?') {
541 $opts->{SINGLETON} ?
542 singleton_prompt_help_cmd() :
543 prompt_help_cmd();
544 next TOPLOOP;
545 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800546 for my $choice (split(/[\s,]+/, $line)) {
547 my $choose = 1;
548 my ($bottom, $top);
549
550 # Input that begins with '-'; unchoose
551 if ($choice =~ s/^-//) {
552 $choose = 0;
553 }
Ciaran McCreesh1e5aaa62008-07-14 19:29:37 +0100554 # A range can be specified like 5-7 or 5-.
555 if ($choice =~ /^(\d+)-(\d*)$/) {
556 ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800557 }
558 elsif ($choice =~ /^\d+$/) {
559 $bottom = $top = $choice;
560 }
561 elsif ($choice eq '*') {
562 $bottom = 1;
563 $top = 1 + @stuff;
564 }
565 else {
566 $bottom = $top = find_unique($choice, @stuff);
567 if (!defined $bottom) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100568 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800569 next TOPLOOP;
570 }
571 }
572 if ($opts->{SINGLETON} && $bottom != $top) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100573 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800574 next TOPLOOP;
575 }
576 for ($i = $bottom-1; $i <= $top-1; $i++) {
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200577 next if (@stuff <= $i || $i < 0);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800578 $chosen[$i] = $choose;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800579 }
580 }
Junio C Hamano12db3342007-11-22 01:47:13 -0800581 last if ($opts->{IMMEDIATE} || $line eq '*');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800582 }
583 for ($i = 0; $i < @stuff; $i++) {
584 if ($chosen[$i]) {
585 push @return, $stuff[$i];
586 }
587 }
588 return @return;
589}
590
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100591sub singleton_prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100592 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100593Prompt help:
5941 - select a numbered item
595foo - select item based on unique prefix
596 - (empty) select nothing
597EOF
598}
599
600sub prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100601 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100602Prompt help:
6031 - select a single item
6043-5 - select a range of items
6052-3,6-9 - select multiple ranges
606foo - select item based on unique prefix
607-... - unselect specified items
608* - choose all items
609 - (empty) finish selecting
610EOF
611}
612
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800613sub status_cmd {
614 list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
615 list_modified());
616 print "\n";
617}
618
619sub say_n_paths {
620 my $did = shift @_;
621 my $cnt = scalar @_;
Vasco Almeidac4a85c32016-12-14 11:54:29 -0100622 if ($did eq 'added') {
623 printf(__n("added %d path\n", "added %d paths\n",
624 $cnt), $cnt);
625 } elsif ($did eq 'updated') {
626 printf(__n("updated %d path\n", "updated %d paths\n",
627 $cnt), $cnt);
628 } elsif ($did eq 'reverted') {
629 printf(__n("reverted %d path\n", "reverted %d paths\n",
630 $cnt), $cnt);
631 } else {
632 printf(__n("touched %d path\n", "touched %d paths\n",
633 $cnt), $cnt);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800634 }
635}
636
637sub update_cmd {
638 my @mods = list_modified('file-only');
639 return if (!@mods);
640
Vasco Almeida258e7792016-12-14 11:54:25 -0100641 my @update = list_and_choose({ PROMPT => __('Update'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800642 HEADER => $status_head, },
643 @mods);
644 if (@update) {
Junio C Hamanoa4f71122007-02-07 10:56:38 -0800645 system(qw(git update-index --add --remove --),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800646 map { $_->{VALUE} } @update);
647 say_n_paths('updated', @update);
648 }
649 print "\n";
650}
651
652sub revert_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100653 my @update = list_and_choose({ PROMPT => __('Revert'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800654 HEADER => $status_head, },
655 list_modified());
656 if (@update) {
Jeff King18bc7612008-02-13 05:50:51 -0500657 if (is_initial_commit()) {
658 system(qw(git rm --cached),
659 map { $_->{VALUE} } @update);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800660 }
Jeff King18bc7612008-02-13 05:50:51 -0500661 else {
662 my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
663 map { $_->{VALUE} } @update);
664 my $fh;
665 open $fh, '| git update-index --index-info'
666 or die;
667 for (@lines) {
668 print $fh $_;
669 }
670 close($fh);
671 for (@update) {
672 if ($_->{INDEX_ADDDEL} &&
673 $_->{INDEX_ADDDEL} eq 'create') {
674 system(qw(git update-index --force-remove --),
675 $_->{VALUE});
Vasco Almeida13c58c12016-12-14 11:54:27 -0100676 printf(__("note: %s is untracked now.\n"), $_->{VALUE});
Jeff King18bc7612008-02-13 05:50:51 -0500677 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800678 }
679 }
680 refresh();
681 say_n_paths('reverted', @update);
682 }
683 print "\n";
684}
685
686sub add_untracked_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100687 my @add = list_and_choose({ PROMPT => __('Add untracked') },
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800688 list_untracked());
689 if (@add) {
690 system(qw(git update-index --add --), @add);
691 say_n_paths('added', @add);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600692 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -0100693 print __("No untracked files.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800694 }
695 print "\n";
696}
697
Thomas Rast8f0bef62009-08-13 14:29:39 +0200698sub run_git_apply {
699 my $cmd = shift;
700 my $fh;
Phillip Wood3a8522f2018-03-05 10:56:30 +0000701 open $fh, '| git ' . $cmd . " --allow-overlap";
Thomas Rast8f0bef62009-08-13 14:29:39 +0200702 print $fh @_;
703 return close $fh;
704}
705
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800706sub parse_diff {
707 my ($path) = @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +0200708 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
John Keeping2cc0f532013-06-12 19:44:10 +0100709 if (defined $diff_algorithm) {
Junio C Hamanoe5c29092013-06-23 12:19:05 -0700710 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
John Keeping2cc0f532013-06-12 19:44:10 +0100711 }
Thomas Rastd002ef42009-08-15 13:48:31 +0200712 if (defined $patch_mode_revision) {
Jeff King954312a2013-10-25 02:52:30 -0400713 push @diff_cmd, get_diff_reference($patch_mode_revision);
Thomas Rastd002ef42009-08-15 13:48:31 +0200714 }
Jeff King1c6ffb52020-09-07 04:17:39 -0400715 my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100716 my @colored = ();
717 if ($diff_use_color) {
Jeff King01143842016-02-27 00:37:06 -0500718 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
719 if (defined $diff_filter) {
720 # quotemeta is overkill, but sufficient for shell-quoting
721 my $diff = join(' ', map { quotemeta } @display_cmd);
722 @display_cmd = ("$diff | $diff_filter");
723 }
724
725 @colored = run_cmd_pipe(@display_cmd);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100726 }
Jeff King03925132009-04-16 03:14:15 -0400727 my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800728
Jeff King42f7d452018-03-03 00:58:49 -0500729 if (@colored && @colored != @diff) {
730 print STDERR
731 "fatal: mismatched output from interactive.diffFilter\n",
732 "hint: Your filter must maintain a one-to-one correspondence\n",
733 "hint: between its input and output lines.\n";
734 exit 1;
735 }
736
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100737 for (my $i = 0; $i < @diff; $i++) {
738 if ($diff[$i] =~ /^@@ /) {
Jeff King03925132009-04-16 03:14:15 -0400739 push @hunk, { TEXT => [], DISPLAY => [],
740 TYPE => 'hunk' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800741 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100742 push @{$hunk[-1]{TEXT}}, $diff[$i];
743 push @{$hunk[-1]{DISPLAY}},
Jeff King01143842016-02-27 00:37:06 -0500744 (@colored ? $colored[$i] : $diff[$i]);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800745 }
746 return @hunk;
747}
748
Jeff Kingb717a622008-03-27 03:30:43 -0400749sub parse_diff_header {
750 my $src = shift;
751
Jeff King03925132009-04-16 03:14:15 -0400752 my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
753 my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
Jeff King24ab81a2009-10-27 20:52:57 -0400754 my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
Phillip Wood75a009d2020-09-09 13:58:52 +0000755 my $addition;
Jeff Kingb717a622008-03-27 03:30:43 -0400756
757 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
Phillip Wood75a009d2020-09-09 13:58:52 +0000758 if ($src->{TEXT}->[$i] =~ /^new file/) {
759 $addition = 1;
760 $head->{TYPE} = 'addition';
761 }
Jeff King24ab81a2009-10-27 20:52:57 -0400762 my $dest =
763 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
764 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
765 $head;
Jeff Kingb717a622008-03-27 03:30:43 -0400766 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
767 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
768 }
Johannes Schindelin2c8bd842020-05-27 21:09:06 +0000769 return ($head, $mode, $deletion, $addition);
Jeff Kingb717a622008-03-27 03:30:43 -0400770}
771
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800772sub hunk_splittable {
773 my ($text) = @_;
774
775 my @s = split_hunk($text);
776 return (1 < @s);
777}
778
779sub parse_hunk_header {
780 my ($line) = @_;
781 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
Jean-Luc Herren7288ed82007-10-09 21:29:26 +0200782 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
783 $o_cnt = 1 unless defined $o_cnt;
784 $n_cnt = 1 unless defined $n_cnt;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800785 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
786}
787
Phillip Wood492e60c2018-02-19 11:29:02 +0000788sub format_hunk_header {
789 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
790 return ("@@ -$o_ofs" .
791 (($o_cnt != 1) ? ",$o_cnt" : '') .
792 " +$n_ofs" .
793 (($n_cnt != 1) ? ",$n_cnt" : '') .
794 " @@\n");
795}
796
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800797sub split_hunk {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100798 my ($text, $display) = @_;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800799 my @split = ();
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100800 if (!defined $display) {
801 $display = $text;
802 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800803 # If there are context lines in the middle of a hunk,
804 # it can be split, but we would need to take care of
805 # overlaps later.
806
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200807 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800808 my $hunk_start = 1;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800809
810 OUTER:
811 while (1) {
812 my $next_hunk_start = undef;
813 my $i = $hunk_start - 1;
814 my $this = +{
815 TEXT => [],
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100816 DISPLAY => [],
Jeff King03925132009-04-16 03:14:15 -0400817 TYPE => 'hunk',
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800818 OLD => $o_ofs,
819 NEW => $n_ofs,
820 OCNT => 0,
821 NCNT => 0,
822 ADDDEL => 0,
823 POSTCTX => 0,
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100824 USE => undef,
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800825 };
826
827 while (++$i < @$text) {
828 my $line = $text->[$i];
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100829 my $display = $display->[$i];
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000830 if ($line =~ /^\\/) {
831 push @{$this->{TEXT}}, $line;
832 push @{$this->{DISPLAY}}, $display;
833 next;
834 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800835 if ($line =~ /^ /) {
836 if ($this->{ADDDEL} &&
837 !defined $next_hunk_start) {
838 # We have seen leading context and
839 # adds/dels and then here is another
840 # context, which is trailing for this
841 # split hunk and leading for the next
842 # one.
843 $next_hunk_start = $i;
844 }
845 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100846 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800847 $this->{OCNT}++;
848 $this->{NCNT}++;
849 if (defined $next_hunk_start) {
850 $this->{POSTCTX}++;
851 }
852 next;
853 }
854
855 # add/del
856 if (defined $next_hunk_start) {
857 # We are done with the current hunk and
858 # this is the first real change for the
859 # next split one.
860 $hunk_start = $next_hunk_start;
861 $o_ofs = $this->{OLD} + $this->{OCNT};
862 $n_ofs = $this->{NEW} + $this->{NCNT};
863 $o_ofs -= $this->{POSTCTX};
864 $n_ofs -= $this->{POSTCTX};
865 push @split, $this;
866 redo OUTER;
867 }
868 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100869 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800870 $this->{ADDDEL}++;
871 if ($line =~ /^-/) {
872 $this->{OCNT}++;
873 }
874 else {
875 $this->{NCNT}++;
876 }
877 }
878
879 push @split, $this;
880 last;
881 }
882
883 for my $hunk (@split) {
884 $o_ofs = $hunk->{OLD};
885 $n_ofs = $hunk->{NEW};
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200886 my $o_cnt = $hunk->{OCNT};
887 my $n_cnt = $hunk->{NCNT};
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800888
Phillip Wood492e60c2018-02-19 11:29:02 +0000889 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100890 my $display_head = $head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800891 unshift @{$hunk->{TEXT}}, $head;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100892 if ($diff_use_color) {
893 $display_head = colored($fraginfo_color, $head);
894 }
895 unshift @{$hunk->{DISPLAY}}, $display_head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800896 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100897 return @split;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800898}
899
Junio C Hamano7a26e652009-05-16 10:48:23 -0700900sub find_last_o_ctx {
901 my ($it) = @_;
902 my $text = $it->{TEXT};
903 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
904 my $i = @{$text};
905 my $last_o_ctx = $o_ofs + $o_cnt;
906 while (0 < --$i) {
907 my $line = $text->[$i];
908 if ($line =~ /^ /) {
909 $last_o_ctx--;
910 next;
911 }
912 last;
913 }
914 return $last_o_ctx;
915}
916
917sub merge_hunk {
918 my ($prev, $this) = @_;
919 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
920 parse_hunk_header($prev->{TEXT}[0]);
921 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
922 parse_hunk_header($this->{TEXT}[0]);
923
924 my (@line, $i, $ofs, $o_cnt, $n_cnt);
925 $ofs = $o0_ofs;
926 $o_cnt = $n_cnt = 0;
927 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
928 my $line = $prev->{TEXT}[$i];
929 if ($line =~ /^\+/) {
930 $n_cnt++;
931 push @line, $line;
932 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000933 } elsif ($line =~ /^\\/) {
934 push @line, $line;
935 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700936 }
937
938 last if ($o1_ofs <= $ofs);
939
940 $o_cnt++;
941 $ofs++;
942 if ($line =~ /^ /) {
943 $n_cnt++;
944 }
945 push @line, $line;
946 }
947
948 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
949 my $line = $this->{TEXT}[$i];
950 if ($line =~ /^\+/) {
951 $n_cnt++;
952 push @line, $line;
953 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000954 } elsif ($line =~ /^\\/) {
955 push @line, $line;
956 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700957 }
958 $ofs++;
959 $o_cnt++;
960 if ($line =~ /^ /) {
961 $n_cnt++;
962 }
963 push @line, $line;
964 }
Phillip Wood492e60c2018-02-19 11:29:02 +0000965 my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
Junio C Hamano7a26e652009-05-16 10:48:23 -0700966 @{$prev->{TEXT}} = ($head, @line);
967}
968
969sub coalesce_overlapping_hunks {
970 my (@in) = @_;
971 my @out = ();
972
973 my ($last_o_ctx, $last_was_dirty);
Phillip Woodfecc6f32018-03-01 10:51:00 +0000974 my $ofs_delta = 0;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700975
Phillip Woodfecc6f32018-03-01 10:51:00 +0000976 for (@in) {
Thomas Rast3d792162009-08-15 15:56:39 +0200977 if ($_->{TYPE} ne 'hunk') {
978 push @out, $_;
979 next;
980 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700981 my $text = $_->{TEXT};
Phillip Woodfecc6f32018-03-01 10:51:00 +0000982 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
983 parse_hunk_header($text->[0]);
984 unless ($_->{USE}) {
985 $ofs_delta += $o_cnt - $n_cnt;
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000986 # If this hunk has been edited then subtract
987 # the delta that is due to the edit.
988 if ($_->{OFS_DELTA}) {
989 $ofs_delta -= $_->{OFS_DELTA};
990 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000991 next;
992 }
993 if ($ofs_delta) {
Phillip Wood2bd69b92019-06-12 02:25:27 -0700994 if ($patch_mode_flavour{IS_REVERSE}) {
995 $o_ofs -= $ofs_delta;
996 } else {
997 $n_ofs += $ofs_delta;
998 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000999 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
1000 $n_ofs, $n_cnt);
1001 }
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001002 # If this hunk was edited then adjust the offset delta
1003 # to reflect the edit.
1004 if ($_->{OFS_DELTA}) {
1005 $ofs_delta += $_->{OFS_DELTA};
1006 }
Junio C Hamano7a26e652009-05-16 10:48:23 -07001007 if (defined $last_o_ctx &&
1008 $o_ofs <= $last_o_ctx &&
1009 !$_->{DIRTY} &&
1010 !$last_was_dirty) {
1011 merge_hunk($out[-1], $_);
1012 }
1013 else {
1014 push @out, $_;
1015 }
1016 $last_o_ctx = find_last_o_ctx($out[-1]);
1017 $last_was_dirty = $_->{DIRTY};
1018 }
1019 return @out;
1020}
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001021
Jeff Kinge1327ed2010-02-22 20:05:44 -05001022sub reassemble_patch {
1023 my $head = shift;
1024 my @patch;
1025
1026 # Include everything in the header except the beginning of the diff.
1027 push @patch, (grep { !/^[-+]{3}/ } @$head);
1028
1029 # Then include any headers from the hunk lines, which must
1030 # come before any actual hunk.
1031 while (@_ && $_[0] !~ /^@/) {
1032 push @patch, shift;
1033 }
1034
1035 # Then begin the diff.
1036 push @patch, grep { /^[-+]{3}/ } @$head;
1037
1038 # And then the actual hunks.
1039 push @patch, @_;
1040
1041 return @patch;
1042}
1043
Thomas Rastac083c42008-07-03 00:00:00 +02001044sub color_diff {
1045 return map {
1046 colored((/^@/ ? $fraginfo_color :
1047 /^\+/ ? $diff_new_color :
1048 /^-/ ? $diff_old_color :
Johannes Schindelin890b68b2020-11-16 16:08:31 +00001049 $diff_context_color),
Thomas Rastac083c42008-07-03 00:00:00 +02001050 $_);
1051 } @_;
1052}
1053
Vasco Almeidac9d96162016-12-14 11:54:32 -01001054my %edit_hunk_manually_modes = (
1055 stage => N__(
1056"If the patch applies cleanly, the edited hunk will immediately be
1057marked for staging."),
1058 stash => N__(
1059"If the patch applies cleanly, the edited hunk will immediately be
1060marked for stashing."),
1061 reset_head => N__(
1062"If the patch applies cleanly, the edited hunk will immediately be
1063marked for unstaging."),
1064 reset_nothead => N__(
1065"If the patch applies cleanly, the edited hunk will immediately be
1066marked for applying."),
1067 checkout_index => N__(
1068"If the patch applies cleanly, the edited hunk will immediately be
Ralf Thielow0301f1f2017-04-13 18:41:12 +02001069marked for discarding."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001070 checkout_head => N__(
1071"If the patch applies cleanly, the edited hunk will immediately be
1072marked for discarding."),
1073 checkout_nothead => N__(
1074"If the patch applies cleanly, the edited hunk will immediately be
1075marked for applying."),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001076 worktree_head => N__(
1077"If the patch applies cleanly, the edited hunk will immediately be
1078marked for discarding."),
1079 worktree_nothead => N__(
1080"If the patch applies cleanly, the edited hunk will immediately be
1081marked for applying."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001082);
1083
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001084sub recount_edited_hunk {
1085 local $_;
1086 my ($oldtext, $newtext) = @_;
1087 my ($o_cnt, $n_cnt) = (0, 0);
1088 for (@{$newtext}[1..$#{$newtext}]) {
1089 my $mode = substr($_, 0, 1);
1090 if ($mode eq '-') {
1091 $o_cnt++;
1092 } elsif ($mode eq '+') {
1093 $n_cnt++;
Phillip Woodf4d35a62018-06-11 10:46:02 +01001094 } elsif ($mode eq ' ' or $mode eq "\n") {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001095 $o_cnt++;
1096 $n_cnt++;
1097 }
1098 }
1099 my ($o_ofs, undef, $n_ofs, undef) =
1100 parse_hunk_header($newtext->[0]);
1101 $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1102 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1103 parse_hunk_header($oldtext->[0]);
1104 # Return the change in the number of lines inserted by this hunk
1105 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1106}
1107
Thomas Rastac083c42008-07-03 00:00:00 +02001108sub edit_hunk_manually {
1109 my ($oldtext) = @_;
1110
1111 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1112 my $fh;
1113 open $fh, '>', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001114 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
Vasco Almeidac9d96162016-12-14 11:54:32 -01001115 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
Thomas Rastac083c42008-07-03 00:00:00 +02001116 print $fh @$oldtext;
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -07001117 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1118 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
Vasco Almeidac9d96162016-12-14 11:54:32 -01001119 my $comment_line_char = Git::get_comment_line_char;
1120 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1121---
1122To remove '%s' lines, make them ' ' lines (context).
1123To remove '%s' lines, delete them.
1124Lines starting with %s will be removed.
Thomas Rastac083c42008-07-03 00:00:00 +02001125EOF
Vasco Almeidac9d96162016-12-14 11:54:32 -01001126__($edit_hunk_manually_modes{$patch_mode}),
1127# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1128__ <<EOF2 ;
1129If it does not apply cleanly, you will be given an opportunity to
1130edit again. If all lines of the hunk are removed, then the edit is
1131aborted and the hunk is left unchanged.
1132EOF2
Thomas Rastac083c42008-07-03 00:00:00 +02001133 close $fh;
1134
Johannes Schindelin89c85592019-12-06 13:08:24 +00001135 chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
Thomas Rastac083c42008-07-03 00:00:00 +02001136 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1137
Deskin Miller1d398a02009-02-12 00:19:41 -05001138 if ($? != 0) {
1139 return undef;
1140 }
1141
Thomas Rastac083c42008-07-03 00:00:00 +02001142 open $fh, '<', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001143 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
Jeff Kingd85d7ec2017-06-21 15:28:59 -04001144 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
Thomas Rastac083c42008-07-03 00:00:00 +02001145 close $fh;
1146 unlink $hunkfile;
1147
1148 # Abort if nothing remains
1149 if (!grep { /\S/ } @newtext) {
1150 return undef;
1151 }
1152
1153 # Reinsert the first hunk header if the user accidentally deleted it
1154 if ($newtext[0] !~ /^@/) {
1155 unshift @newtext, $oldtext->[0];
1156 }
1157 return \@newtext;
1158}
1159
1160sub diff_applies {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001161 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
Thomas Rast8f0bef62009-08-13 14:29:39 +02001162 map { @{$_->{TEXT}} } @_);
Thomas Rastac083c42008-07-03 00:00:00 +02001163}
1164
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001165sub _restore_terminal_and_die {
1166 ReadMode 'restore';
1167 print "\n";
1168 exit 1;
1169}
1170
1171sub prompt_single_character {
1172 if ($use_readkey) {
1173 local $SIG{TERM} = \&_restore_terminal_and_die;
1174 local $SIG{INT} = \&_restore_terminal_and_die;
1175 ReadMode 'cbreak';
1176 my $key = ReadKey 0;
1177 ReadMode 'restore';
Thomas Rastb5cc0032011-05-17 17:19:08 +02001178 if ($use_termcap and $key eq "\e") {
1179 while (!defined $term_escapes{$key}) {
1180 my $next = ReadKey 0.5;
1181 last if (!defined $next);
1182 $key .= $next;
1183 }
1184 $key =~ s/\e/^[/;
1185 }
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001186 print "$key" if defined $key;
1187 print "\n";
1188 return $key;
1189 } else {
1190 return <STDIN>;
1191 }
1192}
1193
Thomas Rastac083c42008-07-03 00:00:00 +02001194sub prompt_yesno {
1195 my ($prompt) = @_;
1196 while (1) {
1197 print colored $prompt_color, $prompt;
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001198 my $line = prompt_single_character;
Jeff Kingd5addcf2017-06-21 15:26:36 -04001199 return undef unless defined $line;
Thomas Rastac083c42008-07-03 00:00:00 +02001200 return 0 if $line =~ /^n/i;
1201 return 1 if $line =~ /^y/i;
1202 }
1203}
1204
1205sub edit_hunk_loop {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001206 my ($head, $hunks, $ix) = @_;
1207 my $hunk = $hunks->[$ix];
1208 my $text = $hunk->{TEXT};
Thomas Rastac083c42008-07-03 00:00:00 +02001209
1210 while (1) {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001211 my $newtext = edit_hunk_manually($text);
1212 if (!defined $newtext) {
Thomas Rastac083c42008-07-03 00:00:00 +02001213 return undef;
1214 }
Jeff King03925132009-04-16 03:14:15 -04001215 my $newhunk = {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001216 TEXT => $newtext,
1217 TYPE => $hunk->{TYPE},
Junio C Hamano7a26e652009-05-16 10:48:23 -07001218 USE => 1,
1219 DIRTY => 1,
Jeff King03925132009-04-16 03:14:15 -04001220 };
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001221 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1222 # If this hunk has already been edited then add the
1223 # offset delta of the previous edit to get the real
1224 # delta from the original unedited hunk.
1225 $hunk->{OFS_DELTA} and
1226 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
Thomas Rastac083c42008-07-03 00:00:00 +02001227 if (diff_applies($head,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001228 @{$hunks}[0..$ix-1],
Thomas Rastac083c42008-07-03 00:00:00 +02001229 $newhunk,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001230 @{$hunks}[$ix+1..$#{$hunks}])) {
1231 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
Thomas Rastac083c42008-07-03 00:00:00 +02001232 return $newhunk;
1233 }
1234 else {
1235 prompt_yesno(
Vasco Almeida258e7792016-12-14 11:54:25 -01001236 # TRANSLATORS: do not translate [y/n]
1237 # The program will only accept that input
1238 # at this point.
1239 # Consider translating (saying "no" discards!) as
1240 # (saying "n" for "no" discards!) if the translation
1241 # of the word "no" does not start with n.
1242 __('Your edited hunk does not apply. Edit again '
1243 . '(saying "no" discards!) [y/n]? ')
Thomas Rastac083c42008-07-03 00:00:00 +02001244 ) or return undef;
1245 }
1246 }
1247}
1248
Vasco Almeida186b52c2016-12-14 11:54:31 -01001249my %help_patch_modes = (
1250 stage => N__(
1251"y - stage this hunk
1252n - do not stage this hunk
1253q - quit; do not stage this hunk or any of the remaining ones
1254a - stage this hunk and all later hunks in the file
1255d - do not stage this hunk or any of the later hunks in the file"),
1256 stash => N__(
1257"y - stash this hunk
1258n - do not stash this hunk
1259q - quit; do not stash this hunk or any of the remaining ones
1260a - stash this hunk and all later hunks in the file
1261d - do not stash this hunk or any of the later hunks in the file"),
1262 reset_head => N__(
1263"y - unstage this hunk
1264n - do not unstage this hunk
1265q - quit; do not unstage this hunk or any of the remaining ones
1266a - unstage this hunk and all later hunks in the file
1267d - do not unstage this hunk or any of the later hunks in the file"),
1268 reset_nothead => N__(
1269"y - apply this hunk to index
1270n - do not apply this hunk to index
1271q - quit; do not apply this hunk or any of the remaining ones
1272a - apply this hunk and all later hunks in the file
1273d - do not apply this hunk or any of the later hunks in the file"),
1274 checkout_index => N__(
1275"y - discard this hunk from worktree
1276n - do not discard this hunk from worktree
1277q - quit; do not discard this hunk or any of the remaining ones
1278a - discard this hunk and all later hunks in the file
1279d - do not discard this hunk or any of the later hunks in the file"),
1280 checkout_head => N__(
1281"y - discard this hunk from index and worktree
1282n - do not discard this hunk from index and worktree
1283q - quit; do not discard this hunk or any of the remaining ones
1284a - discard this hunk and all later hunks in the file
1285d - do not discard this hunk or any of the later hunks in the file"),
1286 checkout_nothead => N__(
1287"y - apply this hunk to index and worktree
1288n - do not apply this hunk to index and worktree
1289q - quit; do not apply this hunk or any of the remaining ones
1290a - apply this hunk and all later hunks in the file
1291d - 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 +07001292 worktree_head => N__(
1293"y - discard this hunk from worktree
1294n - do not discard this hunk from worktree
1295q - quit; do not discard this hunk or any of the remaining ones
1296a - discard this hunk and all later hunks in the file
1297d - do not discard this hunk or any of the later hunks in the file"),
1298 worktree_nothead => N__(
1299"y - apply this hunk to worktree
1300n - do not apply this hunk to worktree
1301q - quit; do not apply this hunk or any of the remaining ones
1302a - apply this hunk and all later hunks in the file
1303d - do not apply this hunk or any of the later hunks in the file"),
Vasco Almeida186b52c2016-12-14 11:54:31 -01001304);
1305
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001306sub help_patch_cmd {
Phillip Wood01a69662018-02-13 10:32:39 +00001307 local $_;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001308 my $other = $_[0] . ",?";
Phillip Wood01a69662018-02-13 10:32:39 +00001309 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1310 map { "$_\n" } grep {
1311 my $c = quotemeta(substr($_, 0, 1));
1312 $other =~ /,$c/
1313 } split "\n", __ <<EOF ;
William Pursell070434d2008-12-04 10:22:40 +00001314g - select a hunk to go to
William Purselldd971cc2008-11-27 04:07:57 +00001315/ - search for a hunk matching the given regex
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001316j - leave this hunk undecided, see next undecided hunk
1317J - leave this hunk undecided, see next hunk
1318k - leave this hunk undecided, see previous undecided hunk
1319K - leave this hunk undecided, see previous hunk
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001320s - split the current hunk into smaller hunks
Thomas Rastac083c42008-07-03 00:00:00 +02001321e - manually edit the current hunk
Ralf Wildenhues280e50c2007-11-28 19:21:42 +01001322? - print help
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001323EOF
1324}
1325
Thomas Rast8f0bef62009-08-13 14:29:39 +02001326sub apply_patch {
1327 my $cmd = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001328 my $ret = run_git_apply $cmd, @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +02001329 if (!$ret) {
1330 print STDERR @_;
1331 }
1332 return $ret;
1333}
1334
Thomas Rast4f353652009-08-15 13:48:30 +02001335sub apply_patch_for_checkout_commit {
1336 my $reverse = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001337 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1338 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001339
1340 if ($applies_worktree && $applies_index) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001341 run_git_apply 'apply '.$reverse.' --cached', @_;
1342 run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001343 return 1;
1344 } elsif (!$applies_index) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001345 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1346 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001347 return run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001348 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001349 print colored $error_color, __("Nothing was applied.\n");
Thomas Rast4f353652009-08-15 13:48:30 +02001350 return 0;
1351 }
1352 } else {
1353 print STDERR @_;
1354 return 0;
1355 }
1356}
1357
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001358sub patch_update_cmd {
Thomas Rast8f0bef62009-08-13 14:29:39 +02001359 my @all_mods = list_modified($patch_mode_flavour{FILTER});
Vasco Almeida13c58c12016-12-14 11:54:27 -01001360 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
Jeff King4066bd62012-04-05 08:30:08 -04001361 for grep { $_->{UNMERGED} } @all_mods;
1362 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1363
Thomas Rast9fe7a642008-10-26 20:37:06 +01001364 my @mods = grep { !($_->{BINARY}) } @all_mods;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001365 my @them;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001366
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001367 if (!@mods) {
Thomas Rast9fe7a642008-10-26 20:37:06 +01001368 if (@all_mods) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001369 print STDERR __("Only binary files changed.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001370 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001371 print STDERR __("No changes.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001372 }
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001373 return 0;
1374 }
Jeff Kingc852bd52017-03-02 04:48:22 -05001375 if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001376 @them = @mods;
1377 }
1378 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001379 @them = list_and_choose({ PROMPT => __('Patch update'),
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001380 HEADER => $status_head, },
1381 @mods);
1382 }
Junio C Hamano12db3342007-11-22 01:47:13 -08001383 for (@them) {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001384 return 0 if patch_update_file($_->{VALUE});
Junio C Hamano12db3342007-11-22 01:47:13 -08001385 }
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001386}
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001387
William Pursell3f6aff62008-12-04 10:00:24 +00001388# Generate a one line summary of a hunk.
1389sub summarize_hunk {
1390 my $rhunk = shift;
1391 my $summary = $rhunk->{TEXT}[0];
1392
1393 # Keep the line numbers, discard extra context.
1394 $summary =~ s/@@(.*?)@@.*/$1 /s;
1395 $summary .= " " x (20 - length $summary);
1396
1397 # Add some user context.
1398 for my $line (@{$rhunk->{TEXT}}) {
1399 if ($line =~ m/^[+-].*\w/) {
1400 $summary .= $line;
1401 last;
1402 }
1403 }
1404
1405 chomp $summary;
1406 return substr($summary, 0, 80) . "\n";
1407}
1408
1409
1410# Print a one-line summary of each hunk in the array ref in
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001411# the first argument, starting with the index in the 2nd.
William Pursell3f6aff62008-12-04 10:00:24 +00001412sub display_hunks {
1413 my ($hunks, $i) = @_;
1414 my $ctr = 0;
1415 $i ||= 0;
1416 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1417 my $status = " ";
1418 if (defined $hunks->[$i]{USE}) {
1419 $status = $hunks->[$i]{USE} ? "+" : "-";
1420 }
1421 printf "%s%2d: %s",
1422 $status,
1423 $i + 1,
1424 summarize_hunk($hunks->[$i]);
1425 }
1426 return $i;
1427}
1428
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001429my %patch_update_prompt_modes = (
1430 stage => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001431 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1432 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001433 addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001434 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001435 },
1436 stash => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001437 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1438 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001439 addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001440 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001441 },
1442 reset_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001443 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1444 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001445 addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001446 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001447 },
1448 reset_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001449 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1450 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001451 addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001452 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001453 },
1454 checkout_index => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001455 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1456 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001457 addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001458 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001459 },
1460 checkout_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001461 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1462 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001463 addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001464 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001465 },
1466 checkout_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001467 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1468 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001469 addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001470 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001471 },
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001472 worktree_head => {
1473 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1474 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001475 addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001476 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1477 },
1478 worktree_nothead => {
1479 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1480 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001481 addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001482 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1483 },
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001484);
1485
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001486sub patch_update_file {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001487 my $quit = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001488 my ($ix, $num);
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001489 my $path = shift;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001490 my ($head, @hunk) = parse_diff($path);
Johannes Schindelin2c8bd842020-05-27 21:09:06 +00001491 ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001492 for (@{$head->{DISPLAY}}) {
1493 print;
1494 }
Jeff Kingca724682008-03-27 03:32:25 -04001495
1496 if (@{$mode->{TEXT}}) {
Jeff King03925132009-04-16 03:14:15 -04001497 unshift @hunk, $mode;
Jeff Kingca724682008-03-27 03:32:25 -04001498 }
Jeff King8947fdd2009-12-08 02:49:35 -05001499 if (@{$deletion->{TEXT}}) {
1500 foreach my $hunk (@hunk) {
1501 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1502 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1503 }
Jeff King24ab81a2009-10-27 20:52:57 -04001504 @hunk = ($deletion);
1505 }
Jeff Kingca724682008-03-27 03:32:25 -04001506
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001507 $num = scalar @hunk;
1508 $ix = 0;
1509
1510 while (1) {
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001511 my ($prev, $next, $other, $undecided, $i);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001512 $other = '';
1513
Phillip Wood75a009d2020-09-09 13:58:52 +00001514 last if ($ix and !$num);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001515 if ($num <= $ix) {
1516 $ix = 0;
1517 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001518 for ($i = 0; $i < $ix; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001519 if (!defined $hunk[$i]{USE}) {
1520 $prev = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001521 $other .= ',k';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001522 last;
1523 }
1524 }
1525 if ($ix) {
William Pursell57886bc2008-11-27 04:07:52 +00001526 $other .= ',K';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001527 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001528 for ($i = $ix + 1; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001529 if (!defined $hunk[$i]{USE}) {
1530 $next = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001531 $other .= ',j';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001532 last;
1533 }
1534 }
1535 if ($ix < $num - 1) {
William Pursell57886bc2008-11-27 04:07:52 +00001536 $other .= ',J';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001537 }
William Pursell070434d2008-12-04 10:22:40 +00001538 if ($num > 1) {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001539 $other .= ',g,/';
William Pursell070434d2008-12-04 10:22:40 +00001540 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001541 for ($i = 0; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001542 if (!defined $hunk[$i]{USE}) {
1543 $undecided = 1;
1544 last;
1545 }
1546 }
Phillip Wood75a009d2020-09-09 13:58:52 +00001547 last if (!$undecided && ($num || !$addition));
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001548
Phillip Wood75a009d2020-09-09 13:58:52 +00001549 if ($num) {
1550 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1551 hunk_splittable($hunk[$ix]{TEXT})) {
1552 $other .= ',s';
1553 }
1554 if ($hunk[$ix]{TYPE} eq 'hunk') {
1555 $other .= ',e';
1556 }
1557 for (@{$hunk[$ix]{DISPLAY}}) {
1558 print;
1559 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001560 }
Phillip Wood75a009d2020-09-09 13:58:52 +00001561 my $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE};
1562 print colored $prompt_color, "(", ($ix+1), "/", ($num ? $num : 1), ") ",
1563 sprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other);
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001564
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001565 my $line = prompt_single_character;
Jeff Kinga8bec7a2014-12-15 11:35:27 -05001566 last unless defined $line;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001567 if ($line) {
1568 if ($line =~ /^y/i) {
Phillip Wood75a009d2020-09-09 13:58:52 +00001569 if ($num) {
1570 $hunk[$ix]{USE} = 1;
1571 } else {
1572 $head->{USE} = 1;
1573 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001574 }
1575 elsif ($line =~ /^n/i) {
Phillip Wood75a009d2020-09-09 13:58:52 +00001576 if ($num) {
1577 $hunk[$ix]{USE} = 0;
1578 } else {
1579 $head->{USE} = 0;
1580 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001581 }
1582 elsif ($line =~ /^a/i) {
Phillip Wood75a009d2020-09-09 13:58:52 +00001583 if ($num) {
1584 while ($ix < $num) {
1585 if (!defined $hunk[$ix]{USE}) {
1586 $hunk[$ix]{USE} = 1;
1587 }
1588 $ix++;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001589 }
Phillip Wood75a009d2020-09-09 13:58:52 +00001590 } else {
1591 $head->{USE} = 1;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001592 $ix++;
1593 }
1594 next;
1595 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001596 elsif ($line =~ /^g(.*)/) {
William Pursell070434d2008-12-04 10:22:40 +00001597 my $response = $1;
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001598 unless ($other =~ /g/) {
1599 error_msg __("No other hunks to goto\n");
1600 next;
1601 }
William Pursell070434d2008-12-04 10:22:40 +00001602 my $no = $ix > 10 ? $ix - 10 : 0;
1603 while ($response eq '') {
William Pursell070434d2008-12-04 10:22:40 +00001604 $no = display_hunks(\@hunk, $no);
1605 if ($no < $num) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001606 print __("go to which hunk (<ret> to see more)? ");
1607 } else {
1608 print __("go to which hunk? ");
William Pursell070434d2008-12-04 10:22:40 +00001609 }
William Pursell070434d2008-12-04 10:22:40 +00001610 $response = <STDIN>;
Thomas Rast68c02d72009-02-02 22:46:29 +01001611 if (!defined $response) {
1612 $response = '';
1613 }
William Pursell070434d2008-12-04 10:22:40 +00001614 chomp $response;
1615 }
1616 if ($response !~ /^\s*\d+\s*$/) {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001617 error_msg sprintf(__("Invalid number: '%s'\n"),
1618 $response);
William Pursell070434d2008-12-04 10:22:40 +00001619 } elsif (0 < $response && $response <= $num) {
1620 $ix = $response - 1;
1621 } else {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001622 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1623 "Sorry, only %d hunks available.\n", $num), $num);
William Pursell070434d2008-12-04 10:22:40 +00001624 }
1625 next;
1626 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001627 elsif ($line =~ /^d/i) {
Phillip Wood75a009d2020-09-09 13:58:52 +00001628 if ($num) {
1629 while ($ix < $num) {
1630 if (!defined $hunk[$ix]{USE}) {
1631 $hunk[$ix]{USE} = 0;
1632 }
1633 $ix++;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001634 }
Phillip Wood75a009d2020-09-09 13:58:52 +00001635 } else {
1636 $head->{USE} = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001637 $ix++;
1638 }
1639 next;
1640 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001641 elsif ($line =~ /^q/i) {
Phillip Wood75a009d2020-09-09 13:58:52 +00001642 if ($num) {
1643 for ($i = 0; $i < $num; $i++) {
1644 if (!defined $hunk[$i]{USE}) {
1645 $hunk[$i]{USE} = 0;
1646 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001647 }
Phillip Wood75a009d2020-09-09 13:58:52 +00001648 } elsif (!defined $head->{USE}) {
1649 $head->{USE} = 0;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001650 }
1651 $quit = 1;
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001652 last;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001653 }
William Purselldd971cc2008-11-27 04:07:57 +00001654 elsif ($line =~ m|^/(.*)|) {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001655 my $regex = $1;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001656 unless ($other =~ m|/|) {
1657 error_msg __("No other hunks to search\n");
1658 next;
1659 }
Ævar Arnfjörð Bjarmasonfd2fb4a2018-03-31 12:50:58 +00001660 if ($regex eq "") {
Vasco Almeida258e7792016-12-14 11:54:25 -01001661 print colored $prompt_color, __("search for regex? ");
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001662 $regex = <STDIN>;
1663 if (defined $regex) {
1664 chomp $regex;
1665 }
1666 }
William Purselldd971cc2008-11-27 04:07:57 +00001667 my $search_string;
1668 eval {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001669 $search_string = qr{$regex}m;
William Purselldd971cc2008-11-27 04:07:57 +00001670 };
1671 if ($@) {
1672 my ($err,$exp) = ($@, $1);
1673 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
Vasco Almeida13c58c12016-12-14 11:54:27 -01001674 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
William Purselldd971cc2008-11-27 04:07:57 +00001675 next;
1676 }
1677 my $iy = $ix;
1678 while (1) {
1679 my $text = join ("", @{$hunk[$iy]{TEXT}});
1680 last if ($text =~ $search_string);
1681 $iy++;
1682 $iy = 0 if ($iy >= $num);
1683 if ($ix == $iy) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001684 error_msg __("No hunk matches the given pattern\n");
William Purselldd971cc2008-11-27 04:07:57 +00001685 last;
1686 }
1687 }
1688 $ix = $iy;
1689 next;
1690 }
William Pursellace30ba2008-11-27 04:08:03 +00001691 elsif ($line =~ /^K/) {
1692 if ($other =~ /K/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001693 $ix--;
William Pursellace30ba2008-11-27 04:08:03 +00001694 }
1695 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001696 error_msg __("No previous hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001697 }
1698 next;
1699 }
William Pursellace30ba2008-11-27 04:08:03 +00001700 elsif ($line =~ /^J/) {
1701 if ($other =~ /J/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001702 $ix++;
William Pursellace30ba2008-11-27 04:08:03 +00001703 }
1704 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001705 error_msg __("No next hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001706 }
1707 next;
1708 }
William Pursellace30ba2008-11-27 04:08:03 +00001709 elsif ($line =~ /^k/) {
1710 if ($other =~ /k/) {
1711 while (1) {
1712 $ix--;
1713 last if (!$ix ||
1714 !defined $hunk[$ix]{USE});
1715 }
1716 }
1717 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001718 error_msg __("No previous hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001719 }
1720 next;
1721 }
1722 elsif ($line =~ /^j/) {
1723 if ($other !~ /j/) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001724 error_msg __("No next hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001725 next;
1726 }
1727 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001728 elsif ($line =~ /^s/) {
1729 unless ($other =~ /s/) {
1730 error_msg __("Sorry, cannot split this hunk\n");
1731 next;
1732 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001733 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001734 if (1 < @split) {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001735 print colored $header_color, sprintf(
1736 __n("Split into %d hunk.\n",
1737 "Split into %d hunks.\n",
1738 scalar(@split)), scalar(@split));
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001739 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001740 splice (@hunk, $ix, 1, @split);
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001741 $num = scalar @hunk;
1742 next;
1743 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001744 elsif ($line =~ /^e/) {
1745 unless ($other =~ /e/) {
1746 error_msg __("Sorry, cannot edit this hunk\n");
1747 next;
1748 }
Thomas Rastac083c42008-07-03 00:00:00 +02001749 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1750 if (defined $newhunk) {
1751 splice @hunk, $ix, 1, $newhunk;
1752 }
1753 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001754 else {
1755 help_patch_cmd($other);
1756 next;
1757 }
1758 # soft increment
1759 while (1) {
1760 $ix++;
1761 last if ($ix >= $num ||
1762 !defined $hunk[$ix]{USE});
1763 }
1764 }
1765 }
1766
Phillip Wood75a009d2020-09-09 13:58:52 +00001767 @hunk = coalesce_overlapping_hunks(@hunk) if ($num);
Junio C Hamano7a26e652009-05-16 10:48:23 -07001768
Jean-Luc Herren7b40a452007-10-09 21:34:17 +02001769 my $n_lofs = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001770 my @result = ();
1771 for (@hunk) {
Thomas Rast8cbd4312008-07-02 23:59:16 +02001772 if ($_->{USE}) {
1773 push @result, @{$_->{TEXT}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001774 }
1775 }
1776
Phillip Wood75a009d2020-09-09 13:58:52 +00001777 if (@result or $head->{USE}) {
Jeff Kinge1327ed2010-02-22 20:05:44 -05001778 my @patch = reassemble_patch($head->{TEXT}, @result);
Thomas Rast8f0bef62009-08-13 14:29:39 +02001779 my $apply_routine = $patch_mode_flavour{APPLY};
1780 &$apply_routine(@patch);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001781 refresh();
1782 }
1783
1784 print "\n";
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001785 return $quit;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001786}
1787
1788sub diff_cmd {
1789 my @mods = list_modified('index-only');
1790 @mods = grep { !($_->{BINARY}) } @mods;
1791 return if (!@mods);
Vasco Almeida258e7792016-12-14 11:54:25 -01001792 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001793 IMMEDIATE => 1,
1794 HEADER => $status_head, },
1795 @mods);
1796 return if (!@them);
Vasco Almeida258e7792016-12-14 11:54:25 -01001797 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
Jeff King18bc7612008-02-13 05:50:51 -05001798 system(qw(git diff -p --cached), $reference, '--',
1799 map { $_->{VALUE} } @them);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001800}
1801
1802sub quit_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -01001803 print __("Bye.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001804 exit(0);
1805}
1806
1807sub help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -01001808# TRANSLATORS: please do not translate the command names
1809# 'status', 'update', 'revert', etc.
1810 print colored $help_color, __ <<'EOF' ;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001811status - show paths with changes
1812update - add working tree state to the staged set of changes
1813revert - revert staged set of changes back to the HEAD version
1814patch - pick hunks and update selectively
Ralf Thielowe519ecc2017-02-22 19:46:27 +01001815diff - view diff between HEAD and index
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001816add untracked - add contents of untracked files to the staged set of changes
1817EOF
1818}
1819
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001820sub process_args {
1821 return unless @ARGV;
1822 my $arg = shift @ARGV;
Thomas Rastd002ef42009-08-15 13:48:31 +02001823 if ($arg =~ /--patch(?:=(.*))?/) {
1824 if (defined $1) {
1825 if ($1 eq 'reset') {
1826 $patch_mode = 'reset_head';
1827 $patch_mode_revision = 'HEAD';
Vasco Almeida258e7792016-12-14 11:54:25 -01001828 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001829 if ($arg ne '--') {
1830 $patch_mode_revision = $arg;
Denton Liuf82a9e52020-10-07 00:56:17 -07001831
1832 # NEEDSWORK: Instead of comparing to the literal "HEAD",
1833 # compare the commit objects instead so that other ways of
1834 # saying the same thing (such as "@") are also handled
1835 # appropriately.
1836 #
1837 # This applies to the cases below too.
Thomas Rastd002ef42009-08-15 13:48:31 +02001838 $patch_mode = ($arg eq 'HEAD' ?
1839 'reset_head' : 'reset_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001840 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001841 }
Thomas Rast4f353652009-08-15 13:48:30 +02001842 } elsif ($1 eq 'checkout') {
Vasco Almeida258e7792016-12-14 11:54:25 -01001843 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001844 if ($arg eq '--') {
1845 $patch_mode = 'checkout_index';
1846 } else {
1847 $patch_mode_revision = $arg;
1848 $patch_mode = ($arg eq 'HEAD' ?
1849 'checkout_head' : 'checkout_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001850 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001851 }
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001852 } elsif ($1 eq 'worktree') {
1853 $arg = shift @ARGV or die __("missing --");
1854 if ($arg eq '--') {
1855 $patch_mode = 'checkout_index';
1856 } else {
1857 $patch_mode_revision = $arg;
1858 $patch_mode = ($arg eq 'HEAD' ?
1859 'worktree_head' : 'worktree_nothead');
1860 $arg = shift @ARGV or die __("missing --");
1861 }
Thomas Rastdda1f2a2009-08-13 14:29:44 +02001862 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1863 $patch_mode = $1;
Vasco Almeida258e7792016-12-14 11:54:25 -01001864 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001865 } else {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001866 die sprintf(__("unknown --patch mode: %s"), $1);
Thomas Rastd002ef42009-08-15 13:48:31 +02001867 }
1868 } else {
1869 $patch_mode = 'stage';
Vasco Almeida258e7792016-12-14 11:54:25 -01001870 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001871 }
Vasco Almeida13c58c12016-12-14 11:54:27 -01001872 die sprintf(__("invalid argument %s, expecting --"),
1873 $arg) unless $arg eq "--";
Thomas Rastd002ef42009-08-15 13:48:31 +02001874 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Jeff Kingc852bd52017-03-02 04:48:22 -05001875 $patch_mode_only = 1;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001876 }
1877 elsif ($arg ne "--") {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001878 die sprintf(__("invalid argument %s, expecting --"), $arg);
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001879 }
1880}
1881
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001882sub main_loop {
1883 my @cmd = ([ 'status', \&status_cmd, ],
1884 [ 'update', \&update_cmd, ],
1885 [ 'revert', \&revert_cmd, ],
1886 [ 'add untracked', \&add_untracked_cmd, ],
1887 [ 'patch', \&patch_update_cmd, ],
1888 [ 'diff', \&diff_cmd, ],
1889 [ 'quit', \&quit_cmd, ],
1890 [ 'help', \&help_cmd, ],
1891 );
1892 while (1) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001893 my ($it) = list_and_choose({ PROMPT => __('What now'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001894 SINGLETON => 1,
1895 LIST_FLAT => 4,
Vasco Almeida258e7792016-12-14 11:54:25 -01001896 HEADER => __('*** Commands ***'),
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +02001897 ON_EOF => \&quit_cmd,
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001898 IMMEDIATE => 1 }, @cmd);
1899 if ($it) {
1900 eval {
1901 $it->[1]->();
1902 };
1903 if ($@) {
1904 print "$@";
1905 }
1906 }
1907 }
1908}
1909
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001910process_args();
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001911refresh();
Jeff Kingc852bd52017-03-02 04:48:22 -05001912if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001913 patch_update_cmd();
1914}
1915else {
1916 status_cmd();
1917 main_loop();
1918}