blob: 52659bb74c9b8fdd5645403afbe4e715b1e089cb [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;
180 return <$fh>;
181 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800182}
183
184my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
185
186if (!defined $GIT_DIR) {
187 exit(1); # rev-parse would have already said "not a git repo"
188}
189chomp($GIT_DIR);
190
191sub refresh {
192 my $fh;
Alex Riesen21e97572007-08-01 14:57:43 +0200193 open $fh, 'git update-index --refresh |'
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800194 or die;
195 while (<$fh>) {
196 ;# ignore 'needs update'
197 }
198 close $fh;
199}
200
201sub list_untracked {
202 map {
203 chomp $_;
Junio C Hamano8851f482009-02-16 22:43:43 -0800204 unquote_path($_);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800205 }
Wincent Colaiuta4c841682007-11-22 02:36:24 +0100206 run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800207}
208
Vasco Almeida258e7792016-12-14 11:54:25 -0100209# TRANSLATORS: you can adjust this to align "git add -i" status menu
210my $status_fmt = __('%12s %12s %s');
211my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800212
Jeff King18bc7612008-02-13 05:50:51 -0500213{
214 my $initial;
215 sub is_initial_commit {
216 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
217 unless defined $initial;
218 return $initial;
219 }
220}
221
brian m. carlson23ec4c52018-05-02 00:26:09 +0000222{
223 my $empty_tree;
224 sub get_empty_tree {
225 return $empty_tree if defined $empty_tree;
226
227 $empty_tree = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
228 chomp $empty_tree;
229 return $empty_tree;
230 }
Jeff King18bc7612008-02-13 05:50:51 -0500231}
232
Jeff King954312a2013-10-25 02:52:30 -0400233sub get_diff_reference {
234 my $ref = shift;
235 if (defined $ref and $ref ne 'HEAD') {
236 return $ref;
237 } elsif (is_initial_commit()) {
238 return get_empty_tree();
239 } else {
240 return 'HEAD';
241 }
242}
243
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800244# Returns list of hashes, contents of each of which are:
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800245# VALUE: pathname
246# BINARY: is a binary path
247# INDEX: is index different from HEAD?
248# FILE: is file different from index?
249# INDEX_ADDDEL: is it add/delete between HEAD and index?
250# FILE_ADDDEL: is it add/delete between index and file?
Jeff King4066bd62012-04-05 08:30:08 -0400251# UNMERGED: is the path unmerged
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800252
253sub list_modified {
254 my ($only) = @_;
255 my (%data, @return);
256 my ($add, $del, $adddel, $file);
257
Jeff King954312a2013-10-25 02:52:30 -0400258 my $reference = get_diff_reference($patch_mode_revision);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800259 for (run_cmd_pipe(qw(git diff-index --cached
Jeff King18bc7612008-02-13 05:50:51 -0500260 --numstat --summary), $reference,
Jeff King7288e122017-03-14 12:30:24 -0400261 '--', @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800262 if (($add, $del, $file) =
263 /^([-\d]+) ([-\d]+) (.*)/) {
264 my ($change, $bin);
Junio C Hamano8851f482009-02-16 22:43:43 -0800265 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800266 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100267 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800268 $bin = 1;
269 }
270 else {
271 $change = "+$add/-$del";
272 }
273 $data{$file} = {
274 INDEX => $change,
275 BINARY => $bin,
Vasco Almeida55aa0442016-12-14 11:54:34 -0100276 FILE => __('nothing'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800277 }
278 }
279 elsif (($adddel, $file) =
280 /^ (create|delete) mode [0-7]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800281 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800282 $data{$file}{INDEX_ADDDEL} = $adddel;
283 }
284 }
285
Nguyễn Thái Ngọc Duy12434ef2018-01-13 19:10:38 +0700286 for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800287 if (($add, $del, $file) =
288 /^([-\d]+) ([-\d]+) (.*)/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800289 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800290 my ($change, $bin);
291 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100292 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800293 $bin = 1;
294 }
295 else {
296 $change = "+$add/-$del";
297 }
298 $data{$file}{FILE} = $change;
299 if ($bin) {
300 $data{$file}{BINARY} = 1;
301 }
302 }
303 elsif (($adddel, $file) =
304 /^ (create|delete) mode [0-7]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800305 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800306 $data{$file}{FILE_ADDDEL} = $adddel;
307 }
Jeff King4066bd62012-04-05 08:30:08 -0400308 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
309 $file = unquote_path($2);
310 if (!exists $data{$file}) {
311 $data{$file} = +{
Vasco Almeida55aa0442016-12-14 11:54:34 -0100312 INDEX => __('unchanged'),
Jeff King4066bd62012-04-05 08:30:08 -0400313 BINARY => 0,
314 };
315 }
316 if ($1 eq 'U') {
317 $data{$file}{UNMERGED} = 1;
318 }
319 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800320 }
321
322 for (sort keys %data) {
323 my $it = $data{$_};
324
325 if ($only) {
326 if ($only eq 'index-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100327 next if ($it->{INDEX} eq __('unchanged'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800328 }
329 if ($only eq 'file-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100330 next if ($it->{FILE} eq __('nothing'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800331 }
332 }
333 push @return, +{
334 VALUE => $_,
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800335 %$it,
336 };
337 }
338 return @return;
339}
340
341sub find_unique {
342 my ($string, @stuff) = @_;
343 my $found = undef;
344 for (my $i = 0; $i < @stuff; $i++) {
345 my $it = $stuff[$i];
346 my $hit = undef;
347 if (ref $it) {
348 if ((ref $it) eq 'ARRAY') {
349 $it = $it->[0];
350 }
351 else {
352 $it = $it->{VALUE};
353 }
354 }
355 eval {
356 if ($it =~ /^$string/) {
357 $hit = 1;
358 };
359 };
360 if (defined $hit && defined $found) {
361 return undef;
362 }
363 if ($hit) {
364 $found = $i + 1;
365 }
366 }
367 return $found;
368}
369
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100370# inserts string into trie and updates count for each character
371sub update_trie {
372 my ($trie, $string) = @_;
373 foreach (split //, $string) {
374 $trie = $trie->{$_} ||= {COUNT => 0};
375 $trie->{COUNT}++;
376 }
377}
378
379# returns an array of tuples (prefix, remainder)
380sub find_unique_prefixes {
381 my @stuff = @_;
382 my @return = ();
383
384 # any single prefix exceeding the soft limit is omitted
385 # if any prefix exceeds the hard limit all are omitted
386 # 0 indicates no limit
387 my $soft_limit = 0;
388 my $hard_limit = 3;
389
390 # build a trie modelling all possible options
391 my %trie;
392 foreach my $print (@stuff) {
393 if ((ref $print) eq 'ARRAY') {
394 $print = $print->[0];
395 }
Wincent Colaiuta63320982007-12-02 14:44:11 +0100396 elsif ((ref $print) eq 'HASH') {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100397 $print = $print->{VALUE};
398 }
399 update_trie(\%trie, $print);
400 push @return, $print;
401 }
402
403 # use the trie to find the unique prefixes
404 for (my $i = 0; $i < @return; $i++) {
405 my $ret = $return[$i];
406 my @letters = split //, $ret;
407 my %search = %trie;
408 my ($prefix, $remainder);
409 my $j;
410 for ($j = 0; $j < @letters; $j++) {
411 my $letter = $letters[$j];
412 if ($search{$letter}{COUNT} == 1) {
413 $prefix = substr $ret, 0, $j + 1;
414 $remainder = substr $ret, $j + 1;
415 last;
416 }
417 else {
418 my $prefix = substr $ret, 0, $j;
419 return ()
420 if ($hard_limit && $j + 1 > $hard_limit);
421 }
422 %search = %{$search{$letter}};
423 }
Junio C Hamano8851f482009-02-16 22:43:43 -0800424 if (ord($letters[0]) > 127 ||
425 ($soft_limit && $j + 1 > $soft_limit)) {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100426 $prefix = undef;
427 $remainder = $ret;
428 }
429 $return[$i] = [$prefix, $remainder];
430 }
431 return @return;
432}
433
Wincent Colaiuta63320982007-12-02 14:44:11 +0100434# filters out prefixes which have special meaning to list_and_choose()
435sub is_valid_prefix {
436 my $prefix = shift;
437 return (defined $prefix) &&
438 !($prefix =~ /[\s,]/) && # separators
439 !($prefix =~ /^-/) && # deselection
440 !($prefix =~ /^\d+/) && # selection
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100441 ($prefix ne '*') && # "all" wildcard
442 ($prefix ne '?'); # prompt help
Wincent Colaiuta63320982007-12-02 14:44:11 +0100443}
444
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100445# given a prefix/remainder tuple return a string with the prefix highlighted
446# for now use square brackets; later might use ANSI colors (underline, bold)
447sub highlight_prefix {
448 my $prefix = shift;
449 my $remainder = shift;
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800450
451 if (!defined $prefix) {
452 return $remainder;
453 }
454
455 if (!is_valid_prefix($prefix)) {
456 return "$prefix$remainder";
457 }
458
Jeff Kingf87e3102008-01-04 03:35:21 -0500459 if (!$menu_use_color) {
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800460 return "[$prefix]$remainder";
461 }
462
463 return "$prompt_color$prefix$normal_color$remainder";
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100464}
465
Thomas Rasta3019732009-02-05 09:28:27 +0100466sub error_msg {
467 print STDERR colored $error_color, @_;
468}
469
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800470sub list_and_choose {
471 my ($opts, @stuff) = @_;
472 my (@chosen, @return);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600473 if (!@stuff) {
474 return @return;
475 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800476 my $i;
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100477 my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800478
479 TOPLOOP:
480 while (1) {
481 my $last_lf = 0;
482
483 if ($opts->{HEADER}) {
484 if (!$opts->{LIST_FLAT}) {
485 print " ";
486 }
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800487 print colored $header_color, "$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 }
Thomas Rast8f0bef62009-08-13 14:29:39 +0200715 my @diff = run_cmd_pipe("git", @diff_cmd, "--", $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' };
Jeff Kingb717a622008-03-27 03:30:43 -0400755
756 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
Jeff King24ab81a2009-10-27 20:52:57 -0400757 my $dest =
758 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
759 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
760 $head;
Jeff Kingb717a622008-03-27 03:30:43 -0400761 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
762 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
763 }
Jeff King24ab81a2009-10-27 20:52:57 -0400764 return ($head, $mode, $deletion);
Jeff Kingb717a622008-03-27 03:30:43 -0400765}
766
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800767sub hunk_splittable {
768 my ($text) = @_;
769
770 my @s = split_hunk($text);
771 return (1 < @s);
772}
773
774sub parse_hunk_header {
775 my ($line) = @_;
776 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
Jean-Luc Herren7288ed82007-10-09 21:29:26 +0200777 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
778 $o_cnt = 1 unless defined $o_cnt;
779 $n_cnt = 1 unless defined $n_cnt;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800780 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
781}
782
Phillip Wood492e60c2018-02-19 11:29:02 +0000783sub format_hunk_header {
784 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
785 return ("@@ -$o_ofs" .
786 (($o_cnt != 1) ? ",$o_cnt" : '') .
787 " +$n_ofs" .
788 (($n_cnt != 1) ? ",$n_cnt" : '') .
789 " @@\n");
790}
791
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800792sub split_hunk {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100793 my ($text, $display) = @_;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800794 my @split = ();
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100795 if (!defined $display) {
796 $display = $text;
797 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800798 # If there are context lines in the middle of a hunk,
799 # it can be split, but we would need to take care of
800 # overlaps later.
801
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200802 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800803 my $hunk_start = 1;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800804
805 OUTER:
806 while (1) {
807 my $next_hunk_start = undef;
808 my $i = $hunk_start - 1;
809 my $this = +{
810 TEXT => [],
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100811 DISPLAY => [],
Jeff King03925132009-04-16 03:14:15 -0400812 TYPE => 'hunk',
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800813 OLD => $o_ofs,
814 NEW => $n_ofs,
815 OCNT => 0,
816 NCNT => 0,
817 ADDDEL => 0,
818 POSTCTX => 0,
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100819 USE => undef,
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800820 };
821
822 while (++$i < @$text) {
823 my $line = $text->[$i];
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100824 my $display = $display->[$i];
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000825 if ($line =~ /^\\/) {
826 push @{$this->{TEXT}}, $line;
827 push @{$this->{DISPLAY}}, $display;
828 next;
829 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800830 if ($line =~ /^ /) {
831 if ($this->{ADDDEL} &&
832 !defined $next_hunk_start) {
833 # We have seen leading context and
834 # adds/dels and then here is another
835 # context, which is trailing for this
836 # split hunk and leading for the next
837 # one.
838 $next_hunk_start = $i;
839 }
840 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100841 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800842 $this->{OCNT}++;
843 $this->{NCNT}++;
844 if (defined $next_hunk_start) {
845 $this->{POSTCTX}++;
846 }
847 next;
848 }
849
850 # add/del
851 if (defined $next_hunk_start) {
852 # We are done with the current hunk and
853 # this is the first real change for the
854 # next split one.
855 $hunk_start = $next_hunk_start;
856 $o_ofs = $this->{OLD} + $this->{OCNT};
857 $n_ofs = $this->{NEW} + $this->{NCNT};
858 $o_ofs -= $this->{POSTCTX};
859 $n_ofs -= $this->{POSTCTX};
860 push @split, $this;
861 redo OUTER;
862 }
863 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100864 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800865 $this->{ADDDEL}++;
866 if ($line =~ /^-/) {
867 $this->{OCNT}++;
868 }
869 else {
870 $this->{NCNT}++;
871 }
872 }
873
874 push @split, $this;
875 last;
876 }
877
878 for my $hunk (@split) {
879 $o_ofs = $hunk->{OLD};
880 $n_ofs = $hunk->{NEW};
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200881 my $o_cnt = $hunk->{OCNT};
882 my $n_cnt = $hunk->{NCNT};
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800883
Phillip Wood492e60c2018-02-19 11:29:02 +0000884 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100885 my $display_head = $head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800886 unshift @{$hunk->{TEXT}}, $head;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100887 if ($diff_use_color) {
888 $display_head = colored($fraginfo_color, $head);
889 }
890 unshift @{$hunk->{DISPLAY}}, $display_head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800891 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100892 return @split;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800893}
894
Junio C Hamano7a26e652009-05-16 10:48:23 -0700895sub find_last_o_ctx {
896 my ($it) = @_;
897 my $text = $it->{TEXT};
898 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
899 my $i = @{$text};
900 my $last_o_ctx = $o_ofs + $o_cnt;
901 while (0 < --$i) {
902 my $line = $text->[$i];
903 if ($line =~ /^ /) {
904 $last_o_ctx--;
905 next;
906 }
907 last;
908 }
909 return $last_o_ctx;
910}
911
912sub merge_hunk {
913 my ($prev, $this) = @_;
914 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
915 parse_hunk_header($prev->{TEXT}[0]);
916 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
917 parse_hunk_header($this->{TEXT}[0]);
918
919 my (@line, $i, $ofs, $o_cnt, $n_cnt);
920 $ofs = $o0_ofs;
921 $o_cnt = $n_cnt = 0;
922 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
923 my $line = $prev->{TEXT}[$i];
924 if ($line =~ /^\+/) {
925 $n_cnt++;
926 push @line, $line;
927 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000928 } elsif ($line =~ /^\\/) {
929 push @line, $line;
930 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700931 }
932
933 last if ($o1_ofs <= $ofs);
934
935 $o_cnt++;
936 $ofs++;
937 if ($line =~ /^ /) {
938 $n_cnt++;
939 }
940 push @line, $line;
941 }
942
943 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
944 my $line = $this->{TEXT}[$i];
945 if ($line =~ /^\+/) {
946 $n_cnt++;
947 push @line, $line;
948 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000949 } elsif ($line =~ /^\\/) {
950 push @line, $line;
951 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700952 }
953 $ofs++;
954 $o_cnt++;
955 if ($line =~ /^ /) {
956 $n_cnt++;
957 }
958 push @line, $line;
959 }
Phillip Wood492e60c2018-02-19 11:29:02 +0000960 my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
Junio C Hamano7a26e652009-05-16 10:48:23 -0700961 @{$prev->{TEXT}} = ($head, @line);
962}
963
964sub coalesce_overlapping_hunks {
965 my (@in) = @_;
966 my @out = ();
967
968 my ($last_o_ctx, $last_was_dirty);
Phillip Woodfecc6f32018-03-01 10:51:00 +0000969 my $ofs_delta = 0;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700970
Phillip Woodfecc6f32018-03-01 10:51:00 +0000971 for (@in) {
Thomas Rast3d792162009-08-15 15:56:39 +0200972 if ($_->{TYPE} ne 'hunk') {
973 push @out, $_;
974 next;
975 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700976 my $text = $_->{TEXT};
Phillip Woodfecc6f32018-03-01 10:51:00 +0000977 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
978 parse_hunk_header($text->[0]);
979 unless ($_->{USE}) {
980 $ofs_delta += $o_cnt - $n_cnt;
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000981 # If this hunk has been edited then subtract
982 # the delta that is due to the edit.
983 if ($_->{OFS_DELTA}) {
984 $ofs_delta -= $_->{OFS_DELTA};
985 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000986 next;
987 }
988 if ($ofs_delta) {
Phillip Wood2bd69b92019-06-12 02:25:27 -0700989 if ($patch_mode_flavour{IS_REVERSE}) {
990 $o_ofs -= $ofs_delta;
991 } else {
992 $n_ofs += $ofs_delta;
993 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000994 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
995 $n_ofs, $n_cnt);
996 }
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000997 # If this hunk was edited then adjust the offset delta
998 # to reflect the edit.
999 if ($_->{OFS_DELTA}) {
1000 $ofs_delta += $_->{OFS_DELTA};
1001 }
Junio C Hamano7a26e652009-05-16 10:48:23 -07001002 if (defined $last_o_ctx &&
1003 $o_ofs <= $last_o_ctx &&
1004 !$_->{DIRTY} &&
1005 !$last_was_dirty) {
1006 merge_hunk($out[-1], $_);
1007 }
1008 else {
1009 push @out, $_;
1010 }
1011 $last_o_ctx = find_last_o_ctx($out[-1]);
1012 $last_was_dirty = $_->{DIRTY};
1013 }
1014 return @out;
1015}
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001016
Jeff Kinge1327ed2010-02-22 20:05:44 -05001017sub reassemble_patch {
1018 my $head = shift;
1019 my @patch;
1020
1021 # Include everything in the header except the beginning of the diff.
1022 push @patch, (grep { !/^[-+]{3}/ } @$head);
1023
1024 # Then include any headers from the hunk lines, which must
1025 # come before any actual hunk.
1026 while (@_ && $_[0] !~ /^@/) {
1027 push @patch, shift;
1028 }
1029
1030 # Then begin the diff.
1031 push @patch, grep { /^[-+]{3}/ } @$head;
1032
1033 # And then the actual hunks.
1034 push @patch, @_;
1035
1036 return @patch;
1037}
1038
Thomas Rastac083c42008-07-03 00:00:00 +02001039sub color_diff {
1040 return map {
1041 colored((/^@/ ? $fraginfo_color :
1042 /^\+/ ? $diff_new_color :
1043 /^-/ ? $diff_old_color :
1044 $diff_plain_color),
1045 $_);
1046 } @_;
1047}
1048
Vasco Almeidac9d96162016-12-14 11:54:32 -01001049my %edit_hunk_manually_modes = (
1050 stage => N__(
1051"If the patch applies cleanly, the edited hunk will immediately be
1052marked for staging."),
1053 stash => N__(
1054"If the patch applies cleanly, the edited hunk will immediately be
1055marked for stashing."),
1056 reset_head => N__(
1057"If the patch applies cleanly, the edited hunk will immediately be
1058marked for unstaging."),
1059 reset_nothead => N__(
1060"If the patch applies cleanly, the edited hunk will immediately be
1061marked for applying."),
1062 checkout_index => N__(
1063"If the patch applies cleanly, the edited hunk will immediately be
Ralf Thielow0301f1f2017-04-13 18:41:12 +02001064marked for discarding."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001065 checkout_head => N__(
1066"If the patch applies cleanly, the edited hunk will immediately be
1067marked for discarding."),
1068 checkout_nothead => N__(
1069"If the patch applies cleanly, the edited hunk will immediately be
1070marked for applying."),
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001071 worktree_head => N__(
1072"If the patch applies cleanly, the edited hunk will immediately be
1073marked for discarding."),
1074 worktree_nothead => N__(
1075"If the patch applies cleanly, the edited hunk will immediately be
1076marked for applying."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001077);
1078
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001079sub recount_edited_hunk {
1080 local $_;
1081 my ($oldtext, $newtext) = @_;
1082 my ($o_cnt, $n_cnt) = (0, 0);
1083 for (@{$newtext}[1..$#{$newtext}]) {
1084 my $mode = substr($_, 0, 1);
1085 if ($mode eq '-') {
1086 $o_cnt++;
1087 } elsif ($mode eq '+') {
1088 $n_cnt++;
Phillip Woodf4d35a62018-06-11 10:46:02 +01001089 } elsif ($mode eq ' ' or $mode eq "\n") {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001090 $o_cnt++;
1091 $n_cnt++;
1092 }
1093 }
1094 my ($o_ofs, undef, $n_ofs, undef) =
1095 parse_hunk_header($newtext->[0]);
1096 $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1097 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1098 parse_hunk_header($oldtext->[0]);
1099 # Return the change in the number of lines inserted by this hunk
1100 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1101}
1102
Thomas Rastac083c42008-07-03 00:00:00 +02001103sub edit_hunk_manually {
1104 my ($oldtext) = @_;
1105
1106 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1107 my $fh;
1108 open $fh, '>', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001109 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
Vasco Almeidac9d96162016-12-14 11:54:32 -01001110 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
Thomas Rastac083c42008-07-03 00:00:00 +02001111 print $fh @$oldtext;
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -07001112 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1113 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
Vasco Almeidac9d96162016-12-14 11:54:32 -01001114 my $comment_line_char = Git::get_comment_line_char;
1115 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1116---
1117To remove '%s' lines, make them ' ' lines (context).
1118To remove '%s' lines, delete them.
1119Lines starting with %s will be removed.
Thomas Rastac083c42008-07-03 00:00:00 +02001120EOF
Vasco Almeidac9d96162016-12-14 11:54:32 -01001121__($edit_hunk_manually_modes{$patch_mode}),
1122# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1123__ <<EOF2 ;
1124If it does not apply cleanly, you will be given an opportunity to
1125edit again. If all lines of the hunk are removed, then the edit is
1126aborted and the hunk is left unchanged.
1127EOF2
Thomas Rastac083c42008-07-03 00:00:00 +02001128 close $fh;
1129
Jonathan Niederb4479f02009-10-30 20:42:34 -05001130 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
Thomas Rastac083c42008-07-03 00:00:00 +02001131 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1132
Deskin Miller1d398a02009-02-12 00:19:41 -05001133 if ($? != 0) {
1134 return undef;
1135 }
1136
Thomas Rastac083c42008-07-03 00:00:00 +02001137 open $fh, '<', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001138 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
Jeff Kingd85d7ec2017-06-21 15:28:59 -04001139 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
Thomas Rastac083c42008-07-03 00:00:00 +02001140 close $fh;
1141 unlink $hunkfile;
1142
1143 # Abort if nothing remains
1144 if (!grep { /\S/ } @newtext) {
1145 return undef;
1146 }
1147
1148 # Reinsert the first hunk header if the user accidentally deleted it
1149 if ($newtext[0] !~ /^@/) {
1150 unshift @newtext, $oldtext->[0];
1151 }
1152 return \@newtext;
1153}
1154
1155sub diff_applies {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001156 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
Thomas Rast8f0bef62009-08-13 14:29:39 +02001157 map { @{$_->{TEXT}} } @_);
Thomas Rastac083c42008-07-03 00:00:00 +02001158}
1159
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001160sub _restore_terminal_and_die {
1161 ReadMode 'restore';
1162 print "\n";
1163 exit 1;
1164}
1165
1166sub prompt_single_character {
1167 if ($use_readkey) {
1168 local $SIG{TERM} = \&_restore_terminal_and_die;
1169 local $SIG{INT} = \&_restore_terminal_and_die;
1170 ReadMode 'cbreak';
1171 my $key = ReadKey 0;
1172 ReadMode 'restore';
Thomas Rastb5cc0032011-05-17 17:19:08 +02001173 if ($use_termcap and $key eq "\e") {
1174 while (!defined $term_escapes{$key}) {
1175 my $next = ReadKey 0.5;
1176 last if (!defined $next);
1177 $key .= $next;
1178 }
1179 $key =~ s/\e/^[/;
1180 }
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001181 print "$key" if defined $key;
1182 print "\n";
1183 return $key;
1184 } else {
1185 return <STDIN>;
1186 }
1187}
1188
Thomas Rastac083c42008-07-03 00:00:00 +02001189sub prompt_yesno {
1190 my ($prompt) = @_;
1191 while (1) {
1192 print colored $prompt_color, $prompt;
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001193 my $line = prompt_single_character;
Jeff Kingd5addcf2017-06-21 15:26:36 -04001194 return undef unless defined $line;
Thomas Rastac083c42008-07-03 00:00:00 +02001195 return 0 if $line =~ /^n/i;
1196 return 1 if $line =~ /^y/i;
1197 }
1198}
1199
1200sub edit_hunk_loop {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001201 my ($head, $hunks, $ix) = @_;
1202 my $hunk = $hunks->[$ix];
1203 my $text = $hunk->{TEXT};
Thomas Rastac083c42008-07-03 00:00:00 +02001204
1205 while (1) {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001206 my $newtext = edit_hunk_manually($text);
1207 if (!defined $newtext) {
Thomas Rastac083c42008-07-03 00:00:00 +02001208 return undef;
1209 }
Jeff King03925132009-04-16 03:14:15 -04001210 my $newhunk = {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001211 TEXT => $newtext,
1212 TYPE => $hunk->{TYPE},
Junio C Hamano7a26e652009-05-16 10:48:23 -07001213 USE => 1,
1214 DIRTY => 1,
Jeff King03925132009-04-16 03:14:15 -04001215 };
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001216 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1217 # If this hunk has already been edited then add the
1218 # offset delta of the previous edit to get the real
1219 # delta from the original unedited hunk.
1220 $hunk->{OFS_DELTA} and
1221 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
Thomas Rastac083c42008-07-03 00:00:00 +02001222 if (diff_applies($head,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001223 @{$hunks}[0..$ix-1],
Thomas Rastac083c42008-07-03 00:00:00 +02001224 $newhunk,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001225 @{$hunks}[$ix+1..$#{$hunks}])) {
1226 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
Thomas Rastac083c42008-07-03 00:00:00 +02001227 return $newhunk;
1228 }
1229 else {
1230 prompt_yesno(
Vasco Almeida258e7792016-12-14 11:54:25 -01001231 # TRANSLATORS: do not translate [y/n]
1232 # The program will only accept that input
1233 # at this point.
1234 # Consider translating (saying "no" discards!) as
1235 # (saying "n" for "no" discards!) if the translation
1236 # of the word "no" does not start with n.
1237 __('Your edited hunk does not apply. Edit again '
1238 . '(saying "no" discards!) [y/n]? ')
Thomas Rastac083c42008-07-03 00:00:00 +02001239 ) or return undef;
1240 }
1241 }
1242}
1243
Vasco Almeida186b52c2016-12-14 11:54:31 -01001244my %help_patch_modes = (
1245 stage => N__(
1246"y - stage this hunk
1247n - do not stage this hunk
1248q - quit; do not stage this hunk or any of the remaining ones
1249a - stage this hunk and all later hunks in the file
1250d - do not stage this hunk or any of the later hunks in the file"),
1251 stash => N__(
1252"y - stash this hunk
1253n - do not stash this hunk
1254q - quit; do not stash this hunk or any of the remaining ones
1255a - stash this hunk and all later hunks in the file
1256d - do not stash this hunk or any of the later hunks in the file"),
1257 reset_head => N__(
1258"y - unstage this hunk
1259n - do not unstage this hunk
1260q - quit; do not unstage this hunk or any of the remaining ones
1261a - unstage this hunk and all later hunks in the file
1262d - do not unstage this hunk or any of the later hunks in the file"),
1263 reset_nothead => N__(
1264"y - apply this hunk to index
1265n - do not apply this hunk to index
1266q - quit; do not apply this hunk or any of the remaining ones
1267a - apply this hunk and all later hunks in the file
1268d - do not apply this hunk or any of the later hunks in the file"),
1269 checkout_index => N__(
1270"y - discard this hunk from worktree
1271n - do not discard this hunk from worktree
1272q - quit; do not discard this hunk or any of the remaining ones
1273a - discard this hunk and all later hunks in the file
1274d - do not discard this hunk or any of the later hunks in the file"),
1275 checkout_head => N__(
1276"y - discard this hunk from index and worktree
1277n - do not discard this hunk from index and worktree
1278q - quit; do not discard this hunk or any of the remaining ones
1279a - discard this hunk and all later hunks in the file
1280d - do not discard this hunk or any of the later hunks in the file"),
1281 checkout_nothead => N__(
1282"y - apply this hunk to index and worktree
1283n - do not apply this hunk to index and worktree
1284q - quit; do not apply this hunk or any of the remaining ones
1285a - apply this hunk and all later hunks in the file
1286d - 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 +07001287 worktree_head => N__(
1288"y - discard this hunk from worktree
1289n - do not discard this hunk from worktree
1290q - quit; do not discard this hunk or any of the remaining ones
1291a - discard this hunk and all later hunks in the file
1292d - do not discard this hunk or any of the later hunks in the file"),
1293 worktree_nothead => N__(
1294"y - apply this hunk to worktree
1295n - do not apply this hunk to worktree
1296q - quit; do not apply this hunk or any of the remaining ones
1297a - apply this hunk and all later hunks in the file
1298d - do not apply this hunk or any of the later hunks in the file"),
Vasco Almeida186b52c2016-12-14 11:54:31 -01001299);
1300
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001301sub help_patch_cmd {
Phillip Wood01a69662018-02-13 10:32:39 +00001302 local $_;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001303 my $other = $_[0] . ",?";
Phillip Wood01a69662018-02-13 10:32:39 +00001304 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1305 map { "$_\n" } grep {
1306 my $c = quotemeta(substr($_, 0, 1));
1307 $other =~ /,$c/
1308 } split "\n", __ <<EOF ;
William Pursell070434d2008-12-04 10:22:40 +00001309g - select a hunk to go to
William Purselldd971cc2008-11-27 04:07:57 +00001310/ - search for a hunk matching the given regex
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001311j - leave this hunk undecided, see next undecided hunk
1312J - leave this hunk undecided, see next hunk
1313k - leave this hunk undecided, see previous undecided hunk
1314K - leave this hunk undecided, see previous hunk
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001315s - split the current hunk into smaller hunks
Thomas Rastac083c42008-07-03 00:00:00 +02001316e - manually edit the current hunk
Ralf Wildenhues280e50c2007-11-28 19:21:42 +01001317? - print help
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001318EOF
1319}
1320
Thomas Rast8f0bef62009-08-13 14:29:39 +02001321sub apply_patch {
1322 my $cmd = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001323 my $ret = run_git_apply $cmd, @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +02001324 if (!$ret) {
1325 print STDERR @_;
1326 }
1327 return $ret;
1328}
1329
Thomas Rast4f353652009-08-15 13:48:30 +02001330sub apply_patch_for_checkout_commit {
1331 my $reverse = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001332 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1333 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001334
1335 if ($applies_worktree && $applies_index) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001336 run_git_apply 'apply '.$reverse.' --cached', @_;
1337 run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001338 return 1;
1339 } elsif (!$applies_index) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001340 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1341 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001342 return run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001343 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001344 print colored $error_color, __("Nothing was applied.\n");
Thomas Rast4f353652009-08-15 13:48:30 +02001345 return 0;
1346 }
1347 } else {
1348 print STDERR @_;
1349 return 0;
1350 }
1351}
1352
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001353sub patch_update_cmd {
Thomas Rast8f0bef62009-08-13 14:29:39 +02001354 my @all_mods = list_modified($patch_mode_flavour{FILTER});
Vasco Almeida13c58c12016-12-14 11:54:27 -01001355 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
Jeff King4066bd62012-04-05 08:30:08 -04001356 for grep { $_->{UNMERGED} } @all_mods;
1357 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1358
Thomas Rast9fe7a642008-10-26 20:37:06 +01001359 my @mods = grep { !($_->{BINARY}) } @all_mods;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001360 my @them;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001361
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001362 if (!@mods) {
Thomas Rast9fe7a642008-10-26 20:37:06 +01001363 if (@all_mods) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001364 print STDERR __("Only binary files changed.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001365 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001366 print STDERR __("No changes.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001367 }
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001368 return 0;
1369 }
Jeff Kingc852bd52017-03-02 04:48:22 -05001370 if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001371 @them = @mods;
1372 }
1373 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001374 @them = list_and_choose({ PROMPT => __('Patch update'),
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001375 HEADER => $status_head, },
1376 @mods);
1377 }
Junio C Hamano12db3342007-11-22 01:47:13 -08001378 for (@them) {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001379 return 0 if patch_update_file($_->{VALUE});
Junio C Hamano12db3342007-11-22 01:47:13 -08001380 }
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001381}
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001382
William Pursell3f6aff62008-12-04 10:00:24 +00001383# Generate a one line summary of a hunk.
1384sub summarize_hunk {
1385 my $rhunk = shift;
1386 my $summary = $rhunk->{TEXT}[0];
1387
1388 # Keep the line numbers, discard extra context.
1389 $summary =~ s/@@(.*?)@@.*/$1 /s;
1390 $summary .= " " x (20 - length $summary);
1391
1392 # Add some user context.
1393 for my $line (@{$rhunk->{TEXT}}) {
1394 if ($line =~ m/^[+-].*\w/) {
1395 $summary .= $line;
1396 last;
1397 }
1398 }
1399
1400 chomp $summary;
1401 return substr($summary, 0, 80) . "\n";
1402}
1403
1404
1405# Print a one-line summary of each hunk in the array ref in
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001406# the first argument, starting with the index in the 2nd.
William Pursell3f6aff62008-12-04 10:00:24 +00001407sub display_hunks {
1408 my ($hunks, $i) = @_;
1409 my $ctr = 0;
1410 $i ||= 0;
1411 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1412 my $status = " ";
1413 if (defined $hunks->[$i]{USE}) {
1414 $status = $hunks->[$i]{USE} ? "+" : "-";
1415 }
1416 printf "%s%2d: %s",
1417 $status,
1418 $i + 1,
1419 summarize_hunk($hunks->[$i]);
1420 }
1421 return $i;
1422}
1423
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001424my %patch_update_prompt_modes = (
1425 stage => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001426 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1427 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1428 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001429 },
1430 stash => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001431 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1432 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1433 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001434 },
1435 reset_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001436 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1437 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1438 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001439 },
1440 reset_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001441 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1442 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1443 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001444 },
1445 checkout_index => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001446 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1447 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1448 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001449 },
1450 checkout_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001451 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1452 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1453 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001454 },
1455 checkout_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001456 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1457 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1458 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001459 },
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001460 worktree_head => {
1461 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1462 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1463 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1464 },
1465 worktree_nothead => {
1466 mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1467 deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1468 hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1469 },
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001470);
1471
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001472sub patch_update_file {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001473 my $quit = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001474 my ($ix, $num);
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001475 my $path = shift;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001476 my ($head, @hunk) = parse_diff($path);
Jeff King24ab81a2009-10-27 20:52:57 -04001477 ($head, my $mode, my $deletion) = parse_diff_header($head);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001478 for (@{$head->{DISPLAY}}) {
1479 print;
1480 }
Jeff Kingca724682008-03-27 03:32:25 -04001481
1482 if (@{$mode->{TEXT}}) {
Jeff King03925132009-04-16 03:14:15 -04001483 unshift @hunk, $mode;
Jeff Kingca724682008-03-27 03:32:25 -04001484 }
Jeff King8947fdd2009-12-08 02:49:35 -05001485 if (@{$deletion->{TEXT}}) {
1486 foreach my $hunk (@hunk) {
1487 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1488 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1489 }
Jeff King24ab81a2009-10-27 20:52:57 -04001490 @hunk = ($deletion);
1491 }
Jeff Kingca724682008-03-27 03:32:25 -04001492
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001493 $num = scalar @hunk;
1494 $ix = 0;
1495
1496 while (1) {
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001497 my ($prev, $next, $other, $undecided, $i);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001498 $other = '';
1499
1500 if ($num <= $ix) {
1501 $ix = 0;
1502 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001503 for ($i = 0; $i < $ix; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001504 if (!defined $hunk[$i]{USE}) {
1505 $prev = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001506 $other .= ',k';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001507 last;
1508 }
1509 }
1510 if ($ix) {
William Pursell57886bc2008-11-27 04:07:52 +00001511 $other .= ',K';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001512 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001513 for ($i = $ix + 1; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001514 if (!defined $hunk[$i]{USE}) {
1515 $next = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001516 $other .= ',j';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001517 last;
1518 }
1519 }
1520 if ($ix < $num - 1) {
William Pursell57886bc2008-11-27 04:07:52 +00001521 $other .= ',J';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001522 }
William Pursell070434d2008-12-04 10:22:40 +00001523 if ($num > 1) {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001524 $other .= ',g,/';
William Pursell070434d2008-12-04 10:22:40 +00001525 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001526 for ($i = 0; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001527 if (!defined $hunk[$i]{USE}) {
1528 $undecided = 1;
1529 last;
1530 }
1531 }
1532 last if (!$undecided);
1533
Jeff King03925132009-04-16 03:14:15 -04001534 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1535 hunk_splittable($hunk[$ix]{TEXT})) {
William Pursell57886bc2008-11-27 04:07:52 +00001536 $other .= ',s';
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001537 }
Jeff King03925132009-04-16 03:14:15 -04001538 if ($hunk[$ix]{TYPE} eq 'hunk') {
1539 $other .= ',e';
1540 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001541 for (@{$hunk[$ix]{DISPLAY}}) {
1542 print;
1543 }
Kunal Tyagi80850502019-09-29 22:22:59 -07001544 print colored $prompt_color, "(", ($ix+1), "/$num) ",
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001545 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1546
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001547 my $line = prompt_single_character;
Jeff Kinga8bec7a2014-12-15 11:35:27 -05001548 last unless defined $line;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001549 if ($line) {
1550 if ($line =~ /^y/i) {
1551 $hunk[$ix]{USE} = 1;
1552 }
1553 elsif ($line =~ /^n/i) {
1554 $hunk[$ix]{USE} = 0;
1555 }
1556 elsif ($line =~ /^a/i) {
1557 while ($ix < $num) {
1558 if (!defined $hunk[$ix]{USE}) {
1559 $hunk[$ix]{USE} = 1;
1560 }
1561 $ix++;
1562 }
1563 next;
1564 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001565 elsif ($line =~ /^g(.*)/) {
William Pursell070434d2008-12-04 10:22:40 +00001566 my $response = $1;
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001567 unless ($other =~ /g/) {
1568 error_msg __("No other hunks to goto\n");
1569 next;
1570 }
William Pursell070434d2008-12-04 10:22:40 +00001571 my $no = $ix > 10 ? $ix - 10 : 0;
1572 while ($response eq '') {
William Pursell070434d2008-12-04 10:22:40 +00001573 $no = display_hunks(\@hunk, $no);
1574 if ($no < $num) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001575 print __("go to which hunk (<ret> to see more)? ");
1576 } else {
1577 print __("go to which hunk? ");
William Pursell070434d2008-12-04 10:22:40 +00001578 }
William Pursell070434d2008-12-04 10:22:40 +00001579 $response = <STDIN>;
Thomas Rast68c02d72009-02-02 22:46:29 +01001580 if (!defined $response) {
1581 $response = '';
1582 }
William Pursell070434d2008-12-04 10:22:40 +00001583 chomp $response;
1584 }
1585 if ($response !~ /^\s*\d+\s*$/) {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001586 error_msg sprintf(__("Invalid number: '%s'\n"),
1587 $response);
William Pursell070434d2008-12-04 10:22:40 +00001588 } elsif (0 < $response && $response <= $num) {
1589 $ix = $response - 1;
1590 } else {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001591 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1592 "Sorry, only %d hunks available.\n", $num), $num);
William Pursell070434d2008-12-04 10:22:40 +00001593 }
1594 next;
1595 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001596 elsif ($line =~ /^d/i) {
1597 while ($ix < $num) {
1598 if (!defined $hunk[$ix]{USE}) {
1599 $hunk[$ix]{USE} = 0;
1600 }
1601 $ix++;
1602 }
1603 next;
1604 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001605 elsif ($line =~ /^q/i) {
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001606 for ($i = 0; $i < $num; $i++) {
1607 if (!defined $hunk[$i]{USE}) {
1608 $hunk[$i]{USE} = 0;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001609 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001610 }
1611 $quit = 1;
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001612 last;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001613 }
William Purselldd971cc2008-11-27 04:07:57 +00001614 elsif ($line =~ m|^/(.*)|) {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001615 my $regex = $1;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001616 unless ($other =~ m|/|) {
1617 error_msg __("No other hunks to search\n");
1618 next;
1619 }
Ævar Arnfjörð Bjarmasonfd2fb4a2018-03-31 12:50:58 +00001620 if ($regex eq "") {
Vasco Almeida258e7792016-12-14 11:54:25 -01001621 print colored $prompt_color, __("search for regex? ");
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001622 $regex = <STDIN>;
1623 if (defined $regex) {
1624 chomp $regex;
1625 }
1626 }
William Purselldd971cc2008-11-27 04:07:57 +00001627 my $search_string;
1628 eval {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001629 $search_string = qr{$regex}m;
William Purselldd971cc2008-11-27 04:07:57 +00001630 };
1631 if ($@) {
1632 my ($err,$exp) = ($@, $1);
1633 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
Vasco Almeida13c58c12016-12-14 11:54:27 -01001634 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
William Purselldd971cc2008-11-27 04:07:57 +00001635 next;
1636 }
1637 my $iy = $ix;
1638 while (1) {
1639 my $text = join ("", @{$hunk[$iy]{TEXT}});
1640 last if ($text =~ $search_string);
1641 $iy++;
1642 $iy = 0 if ($iy >= $num);
1643 if ($ix == $iy) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001644 error_msg __("No hunk matches the given pattern\n");
William Purselldd971cc2008-11-27 04:07:57 +00001645 last;
1646 }
1647 }
1648 $ix = $iy;
1649 next;
1650 }
William Pursellace30ba2008-11-27 04:08:03 +00001651 elsif ($line =~ /^K/) {
1652 if ($other =~ /K/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001653 $ix--;
William Pursellace30ba2008-11-27 04:08:03 +00001654 }
1655 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001656 error_msg __("No previous hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001657 }
1658 next;
1659 }
William Pursellace30ba2008-11-27 04:08:03 +00001660 elsif ($line =~ /^J/) {
1661 if ($other =~ /J/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001662 $ix++;
William Pursellace30ba2008-11-27 04:08:03 +00001663 }
1664 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001665 error_msg __("No next hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001666 }
1667 next;
1668 }
William Pursellace30ba2008-11-27 04:08:03 +00001669 elsif ($line =~ /^k/) {
1670 if ($other =~ /k/) {
1671 while (1) {
1672 $ix--;
1673 last if (!$ix ||
1674 !defined $hunk[$ix]{USE});
1675 }
1676 }
1677 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001678 error_msg __("No previous hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001679 }
1680 next;
1681 }
1682 elsif ($line =~ /^j/) {
1683 if ($other !~ /j/) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001684 error_msg __("No next hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001685 next;
1686 }
1687 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001688 elsif ($line =~ /^s/) {
1689 unless ($other =~ /s/) {
1690 error_msg __("Sorry, cannot split this hunk\n");
1691 next;
1692 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001693 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001694 if (1 < @split) {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001695 print colored $header_color, sprintf(
1696 __n("Split into %d hunk.\n",
1697 "Split into %d hunks.\n",
1698 scalar(@split)), scalar(@split));
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001699 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001700 splice (@hunk, $ix, 1, @split);
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001701 $num = scalar @hunk;
1702 next;
1703 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001704 elsif ($line =~ /^e/) {
1705 unless ($other =~ /e/) {
1706 error_msg __("Sorry, cannot edit this hunk\n");
1707 next;
1708 }
Thomas Rastac083c42008-07-03 00:00:00 +02001709 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1710 if (defined $newhunk) {
1711 splice @hunk, $ix, 1, $newhunk;
1712 }
1713 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001714 else {
1715 help_patch_cmd($other);
1716 next;
1717 }
1718 # soft increment
1719 while (1) {
1720 $ix++;
1721 last if ($ix >= $num ||
1722 !defined $hunk[$ix]{USE});
1723 }
1724 }
1725 }
1726
Junio C Hamano7a26e652009-05-16 10:48:23 -07001727 @hunk = coalesce_overlapping_hunks(@hunk);
1728
Jean-Luc Herren7b40a452007-10-09 21:34:17 +02001729 my $n_lofs = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001730 my @result = ();
1731 for (@hunk) {
Thomas Rast8cbd4312008-07-02 23:59:16 +02001732 if ($_->{USE}) {
1733 push @result, @{$_->{TEXT}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001734 }
1735 }
1736
1737 if (@result) {
Jeff Kinge1327ed2010-02-22 20:05:44 -05001738 my @patch = reassemble_patch($head->{TEXT}, @result);
Thomas Rast8f0bef62009-08-13 14:29:39 +02001739 my $apply_routine = $patch_mode_flavour{APPLY};
1740 &$apply_routine(@patch);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001741 refresh();
1742 }
1743
1744 print "\n";
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001745 return $quit;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001746}
1747
1748sub diff_cmd {
1749 my @mods = list_modified('index-only');
1750 @mods = grep { !($_->{BINARY}) } @mods;
1751 return if (!@mods);
Vasco Almeida258e7792016-12-14 11:54:25 -01001752 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001753 IMMEDIATE => 1,
1754 HEADER => $status_head, },
1755 @mods);
1756 return if (!@them);
Vasco Almeida258e7792016-12-14 11:54:25 -01001757 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
Jeff King18bc7612008-02-13 05:50:51 -05001758 system(qw(git diff -p --cached), $reference, '--',
1759 map { $_->{VALUE} } @them);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001760}
1761
1762sub quit_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -01001763 print __("Bye.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001764 exit(0);
1765}
1766
1767sub help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -01001768# TRANSLATORS: please do not translate the command names
1769# 'status', 'update', 'revert', etc.
1770 print colored $help_color, __ <<'EOF' ;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001771status - show paths with changes
1772update - add working tree state to the staged set of changes
1773revert - revert staged set of changes back to the HEAD version
1774patch - pick hunks and update selectively
Ralf Thielowe519ecc2017-02-22 19:46:27 +01001775diff - view diff between HEAD and index
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001776add untracked - add contents of untracked files to the staged set of changes
1777EOF
1778}
1779
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001780sub process_args {
1781 return unless @ARGV;
1782 my $arg = shift @ARGV;
Thomas Rastd002ef42009-08-15 13:48:31 +02001783 if ($arg =~ /--patch(?:=(.*))?/) {
1784 if (defined $1) {
1785 if ($1 eq 'reset') {
1786 $patch_mode = 'reset_head';
1787 $patch_mode_revision = 'HEAD';
Vasco Almeida258e7792016-12-14 11:54:25 -01001788 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001789 if ($arg ne '--') {
1790 $patch_mode_revision = $arg;
1791 $patch_mode = ($arg eq 'HEAD' ?
1792 'reset_head' : 'reset_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001793 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001794 }
Thomas Rast4f353652009-08-15 13:48:30 +02001795 } elsif ($1 eq 'checkout') {
Vasco Almeida258e7792016-12-14 11:54:25 -01001796 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001797 if ($arg eq '--') {
1798 $patch_mode = 'checkout_index';
1799 } else {
1800 $patch_mode_revision = $arg;
1801 $patch_mode = ($arg eq 'HEAD' ?
1802 'checkout_head' : 'checkout_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001803 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001804 }
Nguyễn Thái Ngọc Duy2f0896e2019-04-25 16:45:54 +07001805 } elsif ($1 eq 'worktree') {
1806 $arg = shift @ARGV or die __("missing --");
1807 if ($arg eq '--') {
1808 $patch_mode = 'checkout_index';
1809 } else {
1810 $patch_mode_revision = $arg;
1811 $patch_mode = ($arg eq 'HEAD' ?
1812 'worktree_head' : 'worktree_nothead');
1813 $arg = shift @ARGV or die __("missing --");
1814 }
Thomas Rastdda1f2a2009-08-13 14:29:44 +02001815 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1816 $patch_mode = $1;
Vasco Almeida258e7792016-12-14 11:54:25 -01001817 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001818 } else {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001819 die sprintf(__("unknown --patch mode: %s"), $1);
Thomas Rastd002ef42009-08-15 13:48:31 +02001820 }
1821 } else {
1822 $patch_mode = 'stage';
Vasco Almeida258e7792016-12-14 11:54:25 -01001823 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001824 }
Vasco Almeida13c58c12016-12-14 11:54:27 -01001825 die sprintf(__("invalid argument %s, expecting --"),
1826 $arg) unless $arg eq "--";
Thomas Rastd002ef42009-08-15 13:48:31 +02001827 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Jeff Kingc852bd52017-03-02 04:48:22 -05001828 $patch_mode_only = 1;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001829 }
1830 elsif ($arg ne "--") {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001831 die sprintf(__("invalid argument %s, expecting --"), $arg);
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001832 }
1833}
1834
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001835sub main_loop {
1836 my @cmd = ([ 'status', \&status_cmd, ],
1837 [ 'update', \&update_cmd, ],
1838 [ 'revert', \&revert_cmd, ],
1839 [ 'add untracked', \&add_untracked_cmd, ],
1840 [ 'patch', \&patch_update_cmd, ],
1841 [ 'diff', \&diff_cmd, ],
1842 [ 'quit', \&quit_cmd, ],
1843 [ 'help', \&help_cmd, ],
1844 );
1845 while (1) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001846 my ($it) = list_and_choose({ PROMPT => __('What now'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001847 SINGLETON => 1,
1848 LIST_FLAT => 4,
Vasco Almeida258e7792016-12-14 11:54:25 -01001849 HEADER => __('*** Commands ***'),
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +02001850 ON_EOF => \&quit_cmd,
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001851 IMMEDIATE => 1 }, @cmd);
1852 if ($it) {
1853 eval {
1854 $it->[1]->();
1855 };
1856 if ($@) {
1857 print "$@";
1858 }
1859 }
1860 }
1861}
1862
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001863process_args();
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001864refresh();
Jeff Kingc852bd52017-03-02 04:48:22 -05001865if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001866 patch_update_cmd();
1867}
1868else {
1869 status_cmd();
1870 main_loop();
1871}