blob: 28b325d75481b74e8f7b16485e1fc18ad854161c [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 },
Thomas Rast8f0bef62009-08-13 14:29:39 +0200152);
153
Vasco Almeida0539d5e2016-12-14 11:54:30 -0100154$patch_mode = 'stage';
155my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800156
157sub run_cmd_pipe {
Johannes Sixtdf17e772013-09-04 09:24:47 +0200158 if ($^O eq 'MSWin32') {
Alex Riesen21e97572007-08-01 14:57:43 +0200159 my @invalid = grep {m/[":*]/} @_;
160 die "$^O does not support: @invalid\n" if @invalid;
161 my @args = map { m/ /o ? "\"$_\"": $_ } @_;
162 return qx{@args};
163 } else {
164 my $fh = undef;
165 open($fh, '-|', @_) or die;
166 return <$fh>;
167 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800168}
169
170my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
171
172if (!defined $GIT_DIR) {
173 exit(1); # rev-parse would have already said "not a git repo"
174}
175chomp($GIT_DIR);
176
177sub refresh {
178 my $fh;
Alex Riesen21e97572007-08-01 14:57:43 +0200179 open $fh, 'git update-index --refresh |'
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800180 or die;
181 while (<$fh>) {
182 ;# ignore 'needs update'
183 }
184 close $fh;
185}
186
187sub list_untracked {
188 map {
189 chomp $_;
Junio C Hamano8851f482009-02-16 22:43:43 -0800190 unquote_path($_);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800191 }
Wincent Colaiuta4c841682007-11-22 02:36:24 +0100192 run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800193}
194
Vasco Almeida258e7792016-12-14 11:54:25 -0100195# TRANSLATORS: you can adjust this to align "git add -i" status menu
196my $status_fmt = __('%12s %12s %s');
197my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800198
Jeff King18bc7612008-02-13 05:50:51 -0500199{
200 my $initial;
201 sub is_initial_commit {
202 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
203 unless defined $initial;
204 return $initial;
205 }
206}
207
208sub get_empty_tree {
209 return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
210}
211
Jeff King954312a2013-10-25 02:52:30 -0400212sub get_diff_reference {
213 my $ref = shift;
214 if (defined $ref and $ref ne 'HEAD') {
215 return $ref;
216 } elsif (is_initial_commit()) {
217 return get_empty_tree();
218 } else {
219 return 'HEAD';
220 }
221}
222
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800223# Returns list of hashes, contents of each of which are:
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800224# VALUE: pathname
225# BINARY: is a binary path
226# INDEX: is index different from HEAD?
227# FILE: is file different from index?
228# INDEX_ADDDEL: is it add/delete between HEAD and index?
229# FILE_ADDDEL: is it add/delete between index and file?
Jeff King4066bd62012-04-05 08:30:08 -0400230# UNMERGED: is the path unmerged
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800231
232sub list_modified {
233 my ($only) = @_;
234 my (%data, @return);
235 my ($add, $del, $adddel, $file);
236
Jeff King954312a2013-10-25 02:52:30 -0400237 my $reference = get_diff_reference($patch_mode_revision);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800238 for (run_cmd_pipe(qw(git diff-index --cached
Jeff King18bc7612008-02-13 05:50:51 -0500239 --numstat --summary), $reference,
Jeff King7288e122017-03-14 12:30:24 -0400240 '--', @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800241 if (($add, $del, $file) =
242 /^([-\d]+) ([-\d]+) (.*)/) {
243 my ($change, $bin);
Junio C Hamano8851f482009-02-16 22:43:43 -0800244 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800245 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100246 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800247 $bin = 1;
248 }
249 else {
250 $change = "+$add/-$del";
251 }
252 $data{$file} = {
253 INDEX => $change,
254 BINARY => $bin,
Vasco Almeida55aa0442016-12-14 11:54:34 -0100255 FILE => __('nothing'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800256 }
257 }
258 elsif (($adddel, $file) =
259 /^ (create|delete) mode [0-7]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800260 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800261 $data{$file}{INDEX_ADDDEL} = $adddel;
262 }
263 }
264
Jeff King7288e122017-03-14 12:30:24 -0400265 for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800266 if (($add, $del, $file) =
267 /^([-\d]+) ([-\d]+) (.*)/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800268 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800269 my ($change, $bin);
270 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100271 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800272 $bin = 1;
273 }
274 else {
275 $change = "+$add/-$del";
276 }
277 $data{$file}{FILE} = $change;
278 if ($bin) {
279 $data{$file}{BINARY} = 1;
280 }
281 }
282 elsif (($adddel, $file) =
283 /^ (create|delete) mode [0-7]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800284 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800285 $data{$file}{FILE_ADDDEL} = $adddel;
286 }
Jeff King4066bd62012-04-05 08:30:08 -0400287 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
288 $file = unquote_path($2);
289 if (!exists $data{$file}) {
290 $data{$file} = +{
Vasco Almeida55aa0442016-12-14 11:54:34 -0100291 INDEX => __('unchanged'),
Jeff King4066bd62012-04-05 08:30:08 -0400292 BINARY => 0,
293 };
294 }
295 if ($1 eq 'U') {
296 $data{$file}{UNMERGED} = 1;
297 }
298 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800299 }
300
301 for (sort keys %data) {
302 my $it = $data{$_};
303
304 if ($only) {
305 if ($only eq 'index-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100306 next if ($it->{INDEX} eq __('unchanged'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800307 }
308 if ($only eq 'file-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100309 next if ($it->{FILE} eq __('nothing'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800310 }
311 }
312 push @return, +{
313 VALUE => $_,
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800314 %$it,
315 };
316 }
317 return @return;
318}
319
320sub find_unique {
321 my ($string, @stuff) = @_;
322 my $found = undef;
323 for (my $i = 0; $i < @stuff; $i++) {
324 my $it = $stuff[$i];
325 my $hit = undef;
326 if (ref $it) {
327 if ((ref $it) eq 'ARRAY') {
328 $it = $it->[0];
329 }
330 else {
331 $it = $it->{VALUE};
332 }
333 }
334 eval {
335 if ($it =~ /^$string/) {
336 $hit = 1;
337 };
338 };
339 if (defined $hit && defined $found) {
340 return undef;
341 }
342 if ($hit) {
343 $found = $i + 1;
344 }
345 }
346 return $found;
347}
348
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100349# inserts string into trie and updates count for each character
350sub update_trie {
351 my ($trie, $string) = @_;
352 foreach (split //, $string) {
353 $trie = $trie->{$_} ||= {COUNT => 0};
354 $trie->{COUNT}++;
355 }
356}
357
358# returns an array of tuples (prefix, remainder)
359sub find_unique_prefixes {
360 my @stuff = @_;
361 my @return = ();
362
363 # any single prefix exceeding the soft limit is omitted
364 # if any prefix exceeds the hard limit all are omitted
365 # 0 indicates no limit
366 my $soft_limit = 0;
367 my $hard_limit = 3;
368
369 # build a trie modelling all possible options
370 my %trie;
371 foreach my $print (@stuff) {
372 if ((ref $print) eq 'ARRAY') {
373 $print = $print->[0];
374 }
Wincent Colaiuta63320982007-12-02 14:44:11 +0100375 elsif ((ref $print) eq 'HASH') {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100376 $print = $print->{VALUE};
377 }
378 update_trie(\%trie, $print);
379 push @return, $print;
380 }
381
382 # use the trie to find the unique prefixes
383 for (my $i = 0; $i < @return; $i++) {
384 my $ret = $return[$i];
385 my @letters = split //, $ret;
386 my %search = %trie;
387 my ($prefix, $remainder);
388 my $j;
389 for ($j = 0; $j < @letters; $j++) {
390 my $letter = $letters[$j];
391 if ($search{$letter}{COUNT} == 1) {
392 $prefix = substr $ret, 0, $j + 1;
393 $remainder = substr $ret, $j + 1;
394 last;
395 }
396 else {
397 my $prefix = substr $ret, 0, $j;
398 return ()
399 if ($hard_limit && $j + 1 > $hard_limit);
400 }
401 %search = %{$search{$letter}};
402 }
Junio C Hamano8851f482009-02-16 22:43:43 -0800403 if (ord($letters[0]) > 127 ||
404 ($soft_limit && $j + 1 > $soft_limit)) {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100405 $prefix = undef;
406 $remainder = $ret;
407 }
408 $return[$i] = [$prefix, $remainder];
409 }
410 return @return;
411}
412
Wincent Colaiuta63320982007-12-02 14:44:11 +0100413# filters out prefixes which have special meaning to list_and_choose()
414sub is_valid_prefix {
415 my $prefix = shift;
416 return (defined $prefix) &&
417 !($prefix =~ /[\s,]/) && # separators
418 !($prefix =~ /^-/) && # deselection
419 !($prefix =~ /^\d+/) && # selection
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100420 ($prefix ne '*') && # "all" wildcard
421 ($prefix ne '?'); # prompt help
Wincent Colaiuta63320982007-12-02 14:44:11 +0100422}
423
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100424# given a prefix/remainder tuple return a string with the prefix highlighted
425# for now use square brackets; later might use ANSI colors (underline, bold)
426sub highlight_prefix {
427 my $prefix = shift;
428 my $remainder = shift;
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800429
430 if (!defined $prefix) {
431 return $remainder;
432 }
433
434 if (!is_valid_prefix($prefix)) {
435 return "$prefix$remainder";
436 }
437
Jeff Kingf87e3102008-01-04 03:35:21 -0500438 if (!$menu_use_color) {
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800439 return "[$prefix]$remainder";
440 }
441
442 return "$prompt_color$prefix$normal_color$remainder";
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100443}
444
Thomas Rasta3019732009-02-05 09:28:27 +0100445sub error_msg {
446 print STDERR colored $error_color, @_;
447}
448
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800449sub list_and_choose {
450 my ($opts, @stuff) = @_;
451 my (@chosen, @return);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600452 if (!@stuff) {
453 return @return;
454 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800455 my $i;
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100456 my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800457
458 TOPLOOP:
459 while (1) {
460 my $last_lf = 0;
461
462 if ($opts->{HEADER}) {
463 if (!$opts->{LIST_FLAT}) {
464 print " ";
465 }
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800466 print colored $header_color, "$opts->{HEADER}\n";
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800467 }
468 for ($i = 0; $i < @stuff; $i++) {
469 my $chosen = $chosen[$i] ? '*' : ' ';
470 my $print = $stuff[$i];
Wincent Colaiuta63320982007-12-02 14:44:11 +0100471 my $ref = ref $print;
472 my $highlighted = highlight_prefix(@{$prefixes[$i]})
473 if @prefixes;
474 if ($ref eq 'ARRAY') {
475 $print = $highlighted || $print->[0];
476 }
477 elsif ($ref eq 'HASH') {
478 my $value = $highlighted || $print->{VALUE};
479 $print = sprintf($status_fmt,
480 $print->{INDEX},
481 $print->{FILE},
482 $value);
483 }
484 else {
485 $print = $highlighted || $print;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800486 }
487 printf("%s%2d: %s", $chosen, $i+1, $print);
488 if (($opts->{LIST_FLAT}) &&
489 (($i + 1) % ($opts->{LIST_FLAT}))) {
490 print "\t";
491 $last_lf = 0;
492 }
493 else {
494 print "\n";
495 $last_lf = 1;
496 }
497 }
498 if (!$last_lf) {
499 print "\n";
500 }
501
502 return if ($opts->{LIST_ONLY});
503
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800504 print colored $prompt_color, $opts->{PROMPT};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800505 if ($opts->{SINGLETON}) {
506 print "> ";
507 }
508 else {
509 print ">> ";
510 }
511 my $line = <STDIN>;
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +0200512 if (!$line) {
513 print "\n";
514 $opts->{ON_EOF}->() if $opts->{ON_EOF};
515 last;
516 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800517 chomp $line;
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200518 last if $line eq '';
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100519 if ($line eq '?') {
520 $opts->{SINGLETON} ?
521 singleton_prompt_help_cmd() :
522 prompt_help_cmd();
523 next TOPLOOP;
524 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800525 for my $choice (split(/[\s,]+/, $line)) {
526 my $choose = 1;
527 my ($bottom, $top);
528
529 # Input that begins with '-'; unchoose
530 if ($choice =~ s/^-//) {
531 $choose = 0;
532 }
Ciaran McCreesh1e5aaa62008-07-14 19:29:37 +0100533 # A range can be specified like 5-7 or 5-.
534 if ($choice =~ /^(\d+)-(\d*)$/) {
535 ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800536 }
537 elsif ($choice =~ /^\d+$/) {
538 $bottom = $top = $choice;
539 }
540 elsif ($choice eq '*') {
541 $bottom = 1;
542 $top = 1 + @stuff;
543 }
544 else {
545 $bottom = $top = find_unique($choice, @stuff);
546 if (!defined $bottom) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100547 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800548 next TOPLOOP;
549 }
550 }
551 if ($opts->{SINGLETON} && $bottom != $top) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100552 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800553 next TOPLOOP;
554 }
555 for ($i = $bottom-1; $i <= $top-1; $i++) {
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200556 next if (@stuff <= $i || $i < 0);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800557 $chosen[$i] = $choose;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800558 }
559 }
Junio C Hamano12db3342007-11-22 01:47:13 -0800560 last if ($opts->{IMMEDIATE} || $line eq '*');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800561 }
562 for ($i = 0; $i < @stuff; $i++) {
563 if ($chosen[$i]) {
564 push @return, $stuff[$i];
565 }
566 }
567 return @return;
568}
569
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100570sub singleton_prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100571 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100572Prompt help:
5731 - select a numbered item
574foo - select item based on unique prefix
575 - (empty) select nothing
576EOF
577}
578
579sub prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100580 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100581Prompt help:
5821 - select a single item
5833-5 - select a range of items
5842-3,6-9 - select multiple ranges
585foo - select item based on unique prefix
586-... - unselect specified items
587* - choose all items
588 - (empty) finish selecting
589EOF
590}
591
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800592sub status_cmd {
593 list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
594 list_modified());
595 print "\n";
596}
597
598sub say_n_paths {
599 my $did = shift @_;
600 my $cnt = scalar @_;
Vasco Almeidac4a85c32016-12-14 11:54:29 -0100601 if ($did eq 'added') {
602 printf(__n("added %d path\n", "added %d paths\n",
603 $cnt), $cnt);
604 } elsif ($did eq 'updated') {
605 printf(__n("updated %d path\n", "updated %d paths\n",
606 $cnt), $cnt);
607 } elsif ($did eq 'reverted') {
608 printf(__n("reverted %d path\n", "reverted %d paths\n",
609 $cnt), $cnt);
610 } else {
611 printf(__n("touched %d path\n", "touched %d paths\n",
612 $cnt), $cnt);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800613 }
614}
615
616sub update_cmd {
617 my @mods = list_modified('file-only');
618 return if (!@mods);
619
Vasco Almeida258e7792016-12-14 11:54:25 -0100620 my @update = list_and_choose({ PROMPT => __('Update'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800621 HEADER => $status_head, },
622 @mods);
623 if (@update) {
Junio C Hamanoa4f71122007-02-07 10:56:38 -0800624 system(qw(git update-index --add --remove --),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800625 map { $_->{VALUE} } @update);
626 say_n_paths('updated', @update);
627 }
628 print "\n";
629}
630
631sub revert_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100632 my @update = list_and_choose({ PROMPT => __('Revert'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800633 HEADER => $status_head, },
634 list_modified());
635 if (@update) {
Jeff King18bc7612008-02-13 05:50:51 -0500636 if (is_initial_commit()) {
637 system(qw(git rm --cached),
638 map { $_->{VALUE} } @update);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800639 }
Jeff King18bc7612008-02-13 05:50:51 -0500640 else {
641 my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
642 map { $_->{VALUE} } @update);
643 my $fh;
644 open $fh, '| git update-index --index-info'
645 or die;
646 for (@lines) {
647 print $fh $_;
648 }
649 close($fh);
650 for (@update) {
651 if ($_->{INDEX_ADDDEL} &&
652 $_->{INDEX_ADDDEL} eq 'create') {
653 system(qw(git update-index --force-remove --),
654 $_->{VALUE});
Vasco Almeida13c58c12016-12-14 11:54:27 -0100655 printf(__("note: %s is untracked now.\n"), $_->{VALUE});
Jeff King18bc7612008-02-13 05:50:51 -0500656 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800657 }
658 }
659 refresh();
660 say_n_paths('reverted', @update);
661 }
662 print "\n";
663}
664
665sub add_untracked_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100666 my @add = list_and_choose({ PROMPT => __('Add untracked') },
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800667 list_untracked());
668 if (@add) {
669 system(qw(git update-index --add --), @add);
670 say_n_paths('added', @add);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600671 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -0100672 print __("No untracked files.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800673 }
674 print "\n";
675}
676
Thomas Rast8f0bef62009-08-13 14:29:39 +0200677sub run_git_apply {
678 my $cmd = shift;
679 my $fh;
Junio C Hamano933e44d2011-04-06 14:20:57 -0700680 open $fh, '| git ' . $cmd . " --recount --allow-overlap";
Thomas Rast8f0bef62009-08-13 14:29:39 +0200681 print $fh @_;
682 return close $fh;
683}
684
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800685sub parse_diff {
686 my ($path) = @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +0200687 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
John Keeping2cc0f532013-06-12 19:44:10 +0100688 if (defined $diff_algorithm) {
Junio C Hamanoe5c29092013-06-23 12:19:05 -0700689 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
John Keeping2cc0f532013-06-12 19:44:10 +0100690 }
Thomas Rastd002ef42009-08-15 13:48:31 +0200691 if (defined $patch_mode_revision) {
Jeff King954312a2013-10-25 02:52:30 -0400692 push @diff_cmd, get_diff_reference($patch_mode_revision);
Thomas Rastd002ef42009-08-15 13:48:31 +0200693 }
Thomas Rast8f0bef62009-08-13 14:29:39 +0200694 my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100695 my @colored = ();
696 if ($diff_use_color) {
Jeff King01143842016-02-27 00:37:06 -0500697 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
698 if (defined $diff_filter) {
699 # quotemeta is overkill, but sufficient for shell-quoting
700 my $diff = join(' ', map { quotemeta } @display_cmd);
701 @display_cmd = ("$diff | $diff_filter");
702 }
703
704 @colored = run_cmd_pipe(@display_cmd);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100705 }
Jeff King03925132009-04-16 03:14:15 -0400706 my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800707
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100708 for (my $i = 0; $i < @diff; $i++) {
709 if ($diff[$i] =~ /^@@ /) {
Jeff King03925132009-04-16 03:14:15 -0400710 push @hunk, { TEXT => [], DISPLAY => [],
711 TYPE => 'hunk' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800712 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100713 push @{$hunk[-1]{TEXT}}, $diff[$i];
714 push @{$hunk[-1]{DISPLAY}},
Jeff King01143842016-02-27 00:37:06 -0500715 (@colored ? $colored[$i] : $diff[$i]);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800716 }
717 return @hunk;
718}
719
Jeff Kingb717a622008-03-27 03:30:43 -0400720sub parse_diff_header {
721 my $src = shift;
722
Jeff King03925132009-04-16 03:14:15 -0400723 my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
724 my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
Jeff King24ab81a2009-10-27 20:52:57 -0400725 my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
Jeff Kingb717a622008-03-27 03:30:43 -0400726
727 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
Jeff King24ab81a2009-10-27 20:52:57 -0400728 my $dest =
729 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
730 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
731 $head;
Jeff Kingb717a622008-03-27 03:30:43 -0400732 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
733 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
734 }
Jeff King24ab81a2009-10-27 20:52:57 -0400735 return ($head, $mode, $deletion);
Jeff Kingb717a622008-03-27 03:30:43 -0400736}
737
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800738sub hunk_splittable {
739 my ($text) = @_;
740
741 my @s = split_hunk($text);
742 return (1 < @s);
743}
744
745sub parse_hunk_header {
746 my ($line) = @_;
747 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
Jean-Luc Herren7288ed82007-10-09 21:29:26 +0200748 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
749 $o_cnt = 1 unless defined $o_cnt;
750 $n_cnt = 1 unless defined $n_cnt;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800751 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
752}
753
754sub split_hunk {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100755 my ($text, $display) = @_;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800756 my @split = ();
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100757 if (!defined $display) {
758 $display = $text;
759 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800760 # If there are context lines in the middle of a hunk,
761 # it can be split, but we would need to take care of
762 # overlaps later.
763
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200764 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800765 my $hunk_start = 1;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800766
767 OUTER:
768 while (1) {
769 my $next_hunk_start = undef;
770 my $i = $hunk_start - 1;
771 my $this = +{
772 TEXT => [],
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100773 DISPLAY => [],
Jeff King03925132009-04-16 03:14:15 -0400774 TYPE => 'hunk',
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800775 OLD => $o_ofs,
776 NEW => $n_ofs,
777 OCNT => 0,
778 NCNT => 0,
779 ADDDEL => 0,
780 POSTCTX => 0,
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100781 USE => undef,
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800782 };
783
784 while (++$i < @$text) {
785 my $line = $text->[$i];
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100786 my $display = $display->[$i];
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800787 if ($line =~ /^ /) {
788 if ($this->{ADDDEL} &&
789 !defined $next_hunk_start) {
790 # We have seen leading context and
791 # adds/dels and then here is another
792 # context, which is trailing for this
793 # split hunk and leading for the next
794 # one.
795 $next_hunk_start = $i;
796 }
797 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100798 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800799 $this->{OCNT}++;
800 $this->{NCNT}++;
801 if (defined $next_hunk_start) {
802 $this->{POSTCTX}++;
803 }
804 next;
805 }
806
807 # add/del
808 if (defined $next_hunk_start) {
809 # We are done with the current hunk and
810 # this is the first real change for the
811 # next split one.
812 $hunk_start = $next_hunk_start;
813 $o_ofs = $this->{OLD} + $this->{OCNT};
814 $n_ofs = $this->{NEW} + $this->{NCNT};
815 $o_ofs -= $this->{POSTCTX};
816 $n_ofs -= $this->{POSTCTX};
817 push @split, $this;
818 redo OUTER;
819 }
820 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100821 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800822 $this->{ADDDEL}++;
823 if ($line =~ /^-/) {
824 $this->{OCNT}++;
825 }
826 else {
827 $this->{NCNT}++;
828 }
829 }
830
831 push @split, $this;
832 last;
833 }
834
835 for my $hunk (@split) {
836 $o_ofs = $hunk->{OLD};
837 $n_ofs = $hunk->{NEW};
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200838 my $o_cnt = $hunk->{OCNT};
839 my $n_cnt = $hunk->{NCNT};
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800840
841 my $head = ("@@ -$o_ofs" .
842 (($o_cnt != 1) ? ",$o_cnt" : '') .
843 " +$n_ofs" .
844 (($n_cnt != 1) ? ",$n_cnt" : '') .
845 " @@\n");
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100846 my $display_head = $head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800847 unshift @{$hunk->{TEXT}}, $head;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100848 if ($diff_use_color) {
849 $display_head = colored($fraginfo_color, $head);
850 }
851 unshift @{$hunk->{DISPLAY}}, $display_head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800852 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100853 return @split;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800854}
855
Junio C Hamano7a26e652009-05-16 10:48:23 -0700856sub find_last_o_ctx {
857 my ($it) = @_;
858 my $text = $it->{TEXT};
859 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
860 my $i = @{$text};
861 my $last_o_ctx = $o_ofs + $o_cnt;
862 while (0 < --$i) {
863 my $line = $text->[$i];
864 if ($line =~ /^ /) {
865 $last_o_ctx--;
866 next;
867 }
868 last;
869 }
870 return $last_o_ctx;
871}
872
873sub merge_hunk {
874 my ($prev, $this) = @_;
875 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
876 parse_hunk_header($prev->{TEXT}[0]);
877 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
878 parse_hunk_header($this->{TEXT}[0]);
879
880 my (@line, $i, $ofs, $o_cnt, $n_cnt);
881 $ofs = $o0_ofs;
882 $o_cnt = $n_cnt = 0;
883 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
884 my $line = $prev->{TEXT}[$i];
885 if ($line =~ /^\+/) {
886 $n_cnt++;
887 push @line, $line;
888 next;
889 }
890
891 last if ($o1_ofs <= $ofs);
892
893 $o_cnt++;
894 $ofs++;
895 if ($line =~ /^ /) {
896 $n_cnt++;
897 }
898 push @line, $line;
899 }
900
901 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
902 my $line = $this->{TEXT}[$i];
903 if ($line =~ /^\+/) {
904 $n_cnt++;
905 push @line, $line;
906 next;
907 }
908 $ofs++;
909 $o_cnt++;
910 if ($line =~ /^ /) {
911 $n_cnt++;
912 }
913 push @line, $line;
914 }
915 my $head = ("@@ -$o0_ofs" .
916 (($o_cnt != 1) ? ",$o_cnt" : '') .
917 " +$n0_ofs" .
918 (($n_cnt != 1) ? ",$n_cnt" : '') .
919 " @@\n");
920 @{$prev->{TEXT}} = ($head, @line);
921}
922
923sub coalesce_overlapping_hunks {
924 my (@in) = @_;
925 my @out = ();
926
927 my ($last_o_ctx, $last_was_dirty);
928
929 for (grep { $_->{USE} } @in) {
Thomas Rast3d792162009-08-15 15:56:39 +0200930 if ($_->{TYPE} ne 'hunk') {
931 push @out, $_;
932 next;
933 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700934 my $text = $_->{TEXT};
935 my ($o_ofs) = parse_hunk_header($text->[0]);
936 if (defined $last_o_ctx &&
937 $o_ofs <= $last_o_ctx &&
938 !$_->{DIRTY} &&
939 !$last_was_dirty) {
940 merge_hunk($out[-1], $_);
941 }
942 else {
943 push @out, $_;
944 }
945 $last_o_ctx = find_last_o_ctx($out[-1]);
946 $last_was_dirty = $_->{DIRTY};
947 }
948 return @out;
949}
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800950
Jeff Kinge1327ed2010-02-22 20:05:44 -0500951sub reassemble_patch {
952 my $head = shift;
953 my @patch;
954
955 # Include everything in the header except the beginning of the diff.
956 push @patch, (grep { !/^[-+]{3}/ } @$head);
957
958 # Then include any headers from the hunk lines, which must
959 # come before any actual hunk.
960 while (@_ && $_[0] !~ /^@/) {
961 push @patch, shift;
962 }
963
964 # Then begin the diff.
965 push @patch, grep { /^[-+]{3}/ } @$head;
966
967 # And then the actual hunks.
968 push @patch, @_;
969
970 return @patch;
971}
972
Thomas Rastac083c42008-07-03 00:00:00 +0200973sub color_diff {
974 return map {
975 colored((/^@/ ? $fraginfo_color :
976 /^\+/ ? $diff_new_color :
977 /^-/ ? $diff_old_color :
978 $diff_plain_color),
979 $_);
980 } @_;
981}
982
Vasco Almeidac9d96162016-12-14 11:54:32 -0100983my %edit_hunk_manually_modes = (
984 stage => N__(
985"If the patch applies cleanly, the edited hunk will immediately be
986marked for staging."),
987 stash => N__(
988"If the patch applies cleanly, the edited hunk will immediately be
989marked for stashing."),
990 reset_head => N__(
991"If the patch applies cleanly, the edited hunk will immediately be
992marked for unstaging."),
993 reset_nothead => N__(
994"If the patch applies cleanly, the edited hunk will immediately be
995marked for applying."),
996 checkout_index => N__(
997"If the patch applies cleanly, the edited hunk will immediately be
Ralf Thielow0301f1f2017-04-13 18:41:12 +0200998marked for discarding."),
Vasco Almeidac9d96162016-12-14 11:54:32 -0100999 checkout_head => N__(
1000"If the patch applies cleanly, the edited hunk will immediately be
1001marked for discarding."),
1002 checkout_nothead => N__(
1003"If the patch applies cleanly, the edited hunk will immediately be
1004marked for applying."),
1005);
1006
Thomas Rastac083c42008-07-03 00:00:00 +02001007sub edit_hunk_manually {
1008 my ($oldtext) = @_;
1009
1010 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1011 my $fh;
1012 open $fh, '>', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001013 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
Vasco Almeidac9d96162016-12-14 11:54:32 -01001014 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
Thomas Rastac083c42008-07-03 00:00:00 +02001015 print $fh @$oldtext;
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -07001016 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1017 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
Vasco Almeidac9d96162016-12-14 11:54:32 -01001018 my $comment_line_char = Git::get_comment_line_char;
1019 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1020---
1021To remove '%s' lines, make them ' ' lines (context).
1022To remove '%s' lines, delete them.
1023Lines starting with %s will be removed.
Thomas Rastac083c42008-07-03 00:00:00 +02001024EOF
Vasco Almeidac9d96162016-12-14 11:54:32 -01001025__($edit_hunk_manually_modes{$patch_mode}),
1026# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1027__ <<EOF2 ;
1028If it does not apply cleanly, you will be given an opportunity to
1029edit again. If all lines of the hunk are removed, then the edit is
1030aborted and the hunk is left unchanged.
1031EOF2
Thomas Rastac083c42008-07-03 00:00:00 +02001032 close $fh;
1033
Jonathan Niederb4479f02009-10-30 20:42:34 -05001034 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
Thomas Rastac083c42008-07-03 00:00:00 +02001035 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1036
Deskin Miller1d398a02009-02-12 00:19:41 -05001037 if ($? != 0) {
1038 return undef;
1039 }
1040
Thomas Rastac083c42008-07-03 00:00:00 +02001041 open $fh, '<', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001042 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
Jeff Kingd85d7ec2017-06-21 15:28:59 -04001043 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
Thomas Rastac083c42008-07-03 00:00:00 +02001044 close $fh;
1045 unlink $hunkfile;
1046
1047 # Abort if nothing remains
1048 if (!grep { /\S/ } @newtext) {
1049 return undef;
1050 }
1051
1052 # Reinsert the first hunk header if the user accidentally deleted it
1053 if ($newtext[0] !~ /^@/) {
1054 unshift @newtext, $oldtext->[0];
1055 }
1056 return \@newtext;
1057}
1058
1059sub diff_applies {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001060 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
Thomas Rast8f0bef62009-08-13 14:29:39 +02001061 map { @{$_->{TEXT}} } @_);
Thomas Rastac083c42008-07-03 00:00:00 +02001062}
1063
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001064sub _restore_terminal_and_die {
1065 ReadMode 'restore';
1066 print "\n";
1067 exit 1;
1068}
1069
1070sub prompt_single_character {
1071 if ($use_readkey) {
1072 local $SIG{TERM} = \&_restore_terminal_and_die;
1073 local $SIG{INT} = \&_restore_terminal_and_die;
1074 ReadMode 'cbreak';
1075 my $key = ReadKey 0;
1076 ReadMode 'restore';
Thomas Rastb5cc0032011-05-17 17:19:08 +02001077 if ($use_termcap and $key eq "\e") {
1078 while (!defined $term_escapes{$key}) {
1079 my $next = ReadKey 0.5;
1080 last if (!defined $next);
1081 $key .= $next;
1082 }
1083 $key =~ s/\e/^[/;
1084 }
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001085 print "$key" if defined $key;
1086 print "\n";
1087 return $key;
1088 } else {
1089 return <STDIN>;
1090 }
1091}
1092
Thomas Rastac083c42008-07-03 00:00:00 +02001093sub prompt_yesno {
1094 my ($prompt) = @_;
1095 while (1) {
1096 print colored $prompt_color, $prompt;
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001097 my $line = prompt_single_character;
Jeff Kingd5addcf2017-06-21 15:26:36 -04001098 return undef unless defined $line;
Thomas Rastac083c42008-07-03 00:00:00 +02001099 return 0 if $line =~ /^n/i;
1100 return 1 if $line =~ /^y/i;
1101 }
1102}
1103
1104sub edit_hunk_loop {
1105 my ($head, $hunk, $ix) = @_;
1106 my $text = $hunk->[$ix]->{TEXT};
1107
1108 while (1) {
1109 $text = edit_hunk_manually($text);
1110 if (!defined $text) {
1111 return undef;
1112 }
Jeff King03925132009-04-16 03:14:15 -04001113 my $newhunk = {
1114 TEXT => $text,
1115 TYPE => $hunk->[$ix]->{TYPE},
Junio C Hamano7a26e652009-05-16 10:48:23 -07001116 USE => 1,
1117 DIRTY => 1,
Jeff King03925132009-04-16 03:14:15 -04001118 };
Thomas Rastac083c42008-07-03 00:00:00 +02001119 if (diff_applies($head,
1120 @{$hunk}[0..$ix-1],
1121 $newhunk,
1122 @{$hunk}[$ix+1..$#{$hunk}])) {
1123 $newhunk->{DISPLAY} = [color_diff(@{$text})];
1124 return $newhunk;
1125 }
1126 else {
1127 prompt_yesno(
Vasco Almeida258e7792016-12-14 11:54:25 -01001128 # TRANSLATORS: do not translate [y/n]
1129 # The program will only accept that input
1130 # at this point.
1131 # Consider translating (saying "no" discards!) as
1132 # (saying "n" for "no" discards!) if the translation
1133 # of the word "no" does not start with n.
1134 __('Your edited hunk does not apply. Edit again '
1135 . '(saying "no" discards!) [y/n]? ')
Thomas Rastac083c42008-07-03 00:00:00 +02001136 ) or return undef;
1137 }
1138 }
1139}
1140
Vasco Almeida186b52c2016-12-14 11:54:31 -01001141my %help_patch_modes = (
1142 stage => N__(
1143"y - stage this hunk
1144n - do not stage this hunk
1145q - quit; do not stage this hunk or any of the remaining ones
1146a - stage this hunk and all later hunks in the file
1147d - do not stage this hunk or any of the later hunks in the file"),
1148 stash => N__(
1149"y - stash this hunk
1150n - do not stash this hunk
1151q - quit; do not stash this hunk or any of the remaining ones
1152a - stash this hunk and all later hunks in the file
1153d - do not stash this hunk or any of the later hunks in the file"),
1154 reset_head => N__(
1155"y - unstage this hunk
1156n - do not unstage this hunk
1157q - quit; do not unstage this hunk or any of the remaining ones
1158a - unstage this hunk and all later hunks in the file
1159d - do not unstage this hunk or any of the later hunks in the file"),
1160 reset_nothead => N__(
1161"y - apply this hunk to index
1162n - do not apply this hunk to index
1163q - quit; do not apply this hunk or any of the remaining ones
1164a - apply this hunk and all later hunks in the file
1165d - do not apply this hunk or any of the later hunks in the file"),
1166 checkout_index => N__(
1167"y - discard this hunk from worktree
1168n - do not discard this hunk from worktree
1169q - quit; do not discard this hunk or any of the remaining ones
1170a - discard this hunk and all later hunks in the file
1171d - do not discard this hunk or any of the later hunks in the file"),
1172 checkout_head => N__(
1173"y - discard this hunk from index and worktree
1174n - do not discard this hunk from index and worktree
1175q - quit; do not discard this hunk or any of the remaining ones
1176a - discard this hunk and all later hunks in the file
1177d - do not discard this hunk or any of the later hunks in the file"),
1178 checkout_nothead => N__(
1179"y - apply this hunk to index and worktree
1180n - do not apply this hunk to index and worktree
1181q - quit; do not apply this hunk or any of the remaining ones
1182a - apply this hunk and all later hunks in the file
1183d - do not apply this hunk or any of the later hunks in the file"),
1184);
1185
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001186sub help_patch_cmd {
Vasco Almeida186b52c2016-12-14 11:54:31 -01001187 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", __ <<EOF ;
William Pursell070434d2008-12-04 10:22:40 +00001188g - select a hunk to go to
William Purselldd971cc2008-11-27 04:07:57 +00001189/ - search for a hunk matching the given regex
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001190j - leave this hunk undecided, see next undecided hunk
1191J - leave this hunk undecided, see next hunk
1192k - leave this hunk undecided, see previous undecided hunk
1193K - leave this hunk undecided, see previous hunk
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001194s - split the current hunk into smaller hunks
Thomas Rastac083c42008-07-03 00:00:00 +02001195e - manually edit the current hunk
Ralf Wildenhues280e50c2007-11-28 19:21:42 +01001196? - print help
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001197EOF
1198}
1199
Thomas Rast8f0bef62009-08-13 14:29:39 +02001200sub apply_patch {
1201 my $cmd = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001202 my $ret = run_git_apply $cmd, @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +02001203 if (!$ret) {
1204 print STDERR @_;
1205 }
1206 return $ret;
1207}
1208
Thomas Rast4f353652009-08-15 13:48:30 +02001209sub apply_patch_for_checkout_commit {
1210 my $reverse = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001211 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1212 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001213
1214 if ($applies_worktree && $applies_index) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001215 run_git_apply 'apply '.$reverse.' --cached', @_;
1216 run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001217 return 1;
1218 } elsif (!$applies_index) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001219 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1220 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001221 return run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001222 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001223 print colored $error_color, __("Nothing was applied.\n");
Thomas Rast4f353652009-08-15 13:48:30 +02001224 return 0;
1225 }
1226 } else {
1227 print STDERR @_;
1228 return 0;
1229 }
1230}
1231
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001232sub patch_update_cmd {
Thomas Rast8f0bef62009-08-13 14:29:39 +02001233 my @all_mods = list_modified($patch_mode_flavour{FILTER});
Vasco Almeida13c58c12016-12-14 11:54:27 -01001234 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
Jeff King4066bd62012-04-05 08:30:08 -04001235 for grep { $_->{UNMERGED} } @all_mods;
1236 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1237
Thomas Rast9fe7a642008-10-26 20:37:06 +01001238 my @mods = grep { !($_->{BINARY}) } @all_mods;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001239 my @them;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001240
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001241 if (!@mods) {
Thomas Rast9fe7a642008-10-26 20:37:06 +01001242 if (@all_mods) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001243 print STDERR __("Only binary files changed.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001244 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001245 print STDERR __("No changes.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001246 }
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001247 return 0;
1248 }
Jeff Kingc852bd52017-03-02 04:48:22 -05001249 if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001250 @them = @mods;
1251 }
1252 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001253 @them = list_and_choose({ PROMPT => __('Patch update'),
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001254 HEADER => $status_head, },
1255 @mods);
1256 }
Junio C Hamano12db3342007-11-22 01:47:13 -08001257 for (@them) {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001258 return 0 if patch_update_file($_->{VALUE});
Junio C Hamano12db3342007-11-22 01:47:13 -08001259 }
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001260}
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001261
William Pursell3f6aff62008-12-04 10:00:24 +00001262# Generate a one line summary of a hunk.
1263sub summarize_hunk {
1264 my $rhunk = shift;
1265 my $summary = $rhunk->{TEXT}[0];
1266
1267 # Keep the line numbers, discard extra context.
1268 $summary =~ s/@@(.*?)@@.*/$1 /s;
1269 $summary .= " " x (20 - length $summary);
1270
1271 # Add some user context.
1272 for my $line (@{$rhunk->{TEXT}}) {
1273 if ($line =~ m/^[+-].*\w/) {
1274 $summary .= $line;
1275 last;
1276 }
1277 }
1278
1279 chomp $summary;
1280 return substr($summary, 0, 80) . "\n";
1281}
1282
1283
1284# Print a one-line summary of each hunk in the array ref in
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001285# the first argument, starting with the index in the 2nd.
William Pursell3f6aff62008-12-04 10:00:24 +00001286sub display_hunks {
1287 my ($hunks, $i) = @_;
1288 my $ctr = 0;
1289 $i ||= 0;
1290 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1291 my $status = " ";
1292 if (defined $hunks->[$i]{USE}) {
1293 $status = $hunks->[$i]{USE} ? "+" : "-";
1294 }
1295 printf "%s%2d: %s",
1296 $status,
1297 $i + 1,
1298 summarize_hunk($hunks->[$i]);
1299 }
1300 return $i;
1301}
1302
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001303my %patch_update_prompt_modes = (
1304 stage => {
1305 mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "),
1306 deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "),
1307 hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "),
1308 },
1309 stash => {
1310 mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "),
1311 deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "),
1312 hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "),
1313 },
1314 reset_head => {
1315 mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "),
1316 deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "),
1317 hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
1318 },
1319 reset_nothead => {
1320 mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
1321 deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
1322 hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
1323 },
1324 checkout_index => {
1325 mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
1326 deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
1327 hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
1328 },
1329 checkout_head => {
1330 mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
1331 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
1332 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
1333 },
1334 checkout_nothead => {
1335 mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
1336 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
1337 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
1338 },
1339);
1340
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001341sub patch_update_file {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001342 my $quit = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001343 my ($ix, $num);
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001344 my $path = shift;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001345 my ($head, @hunk) = parse_diff($path);
Jeff King24ab81a2009-10-27 20:52:57 -04001346 ($head, my $mode, my $deletion) = parse_diff_header($head);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001347 for (@{$head->{DISPLAY}}) {
1348 print;
1349 }
Jeff Kingca724682008-03-27 03:32:25 -04001350
1351 if (@{$mode->{TEXT}}) {
Jeff King03925132009-04-16 03:14:15 -04001352 unshift @hunk, $mode;
Jeff Kingca724682008-03-27 03:32:25 -04001353 }
Jeff King8947fdd2009-12-08 02:49:35 -05001354 if (@{$deletion->{TEXT}}) {
1355 foreach my $hunk (@hunk) {
1356 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1357 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1358 }
Jeff King24ab81a2009-10-27 20:52:57 -04001359 @hunk = ($deletion);
1360 }
Jeff Kingca724682008-03-27 03:32:25 -04001361
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001362 $num = scalar @hunk;
1363 $ix = 0;
1364
1365 while (1) {
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001366 my ($prev, $next, $other, $undecided, $i);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001367 $other = '';
1368
1369 if ($num <= $ix) {
1370 $ix = 0;
1371 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001372 for ($i = 0; $i < $ix; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001373 if (!defined $hunk[$i]{USE}) {
1374 $prev = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001375 $other .= ',k';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001376 last;
1377 }
1378 }
1379 if ($ix) {
William Pursell57886bc2008-11-27 04:07:52 +00001380 $other .= ',K';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001381 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001382 for ($i = $ix + 1; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001383 if (!defined $hunk[$i]{USE}) {
1384 $next = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001385 $other .= ',j';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001386 last;
1387 }
1388 }
1389 if ($ix < $num - 1) {
William Pursell57886bc2008-11-27 04:07:52 +00001390 $other .= ',J';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001391 }
William Pursell070434d2008-12-04 10:22:40 +00001392 if ($num > 1) {
Thomas Rast4404b2e2009-02-02 22:46:28 +01001393 $other .= ',g';
William Pursell070434d2008-12-04 10:22:40 +00001394 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001395 for ($i = 0; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001396 if (!defined $hunk[$i]{USE}) {
1397 $undecided = 1;
1398 last;
1399 }
1400 }
1401 last if (!$undecided);
1402
Jeff King03925132009-04-16 03:14:15 -04001403 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1404 hunk_splittable($hunk[$ix]{TEXT})) {
William Pursell57886bc2008-11-27 04:07:52 +00001405 $other .= ',s';
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001406 }
Jeff King03925132009-04-16 03:14:15 -04001407 if ($hunk[$ix]{TYPE} eq 'hunk') {
1408 $other .= ',e';
1409 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001410 for (@{$hunk[$ix]{DISPLAY}}) {
1411 print;
1412 }
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001413 print colored $prompt_color,
1414 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1415
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001416 my $line = prompt_single_character;
Jeff Kinga8bec7a2014-12-15 11:35:27 -05001417 last unless defined $line;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001418 if ($line) {
1419 if ($line =~ /^y/i) {
1420 $hunk[$ix]{USE} = 1;
1421 }
1422 elsif ($line =~ /^n/i) {
1423 $hunk[$ix]{USE} = 0;
1424 }
1425 elsif ($line =~ /^a/i) {
1426 while ($ix < $num) {
1427 if (!defined $hunk[$ix]{USE}) {
1428 $hunk[$ix]{USE} = 1;
1429 }
1430 $ix++;
1431 }
1432 next;
1433 }
William Pursell070434d2008-12-04 10:22:40 +00001434 elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
1435 my $response = $1;
1436 my $no = $ix > 10 ? $ix - 10 : 0;
1437 while ($response eq '') {
William Pursell070434d2008-12-04 10:22:40 +00001438 $no = display_hunks(\@hunk, $no);
1439 if ($no < $num) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001440 print __("go to which hunk (<ret> to see more)? ");
1441 } else {
1442 print __("go to which hunk? ");
William Pursell070434d2008-12-04 10:22:40 +00001443 }
William Pursell070434d2008-12-04 10:22:40 +00001444 $response = <STDIN>;
Thomas Rast68c02d72009-02-02 22:46:29 +01001445 if (!defined $response) {
1446 $response = '';
1447 }
William Pursell070434d2008-12-04 10:22:40 +00001448 chomp $response;
1449 }
1450 if ($response !~ /^\s*\d+\s*$/) {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001451 error_msg sprintf(__("Invalid number: '%s'\n"),
1452 $response);
William Pursell070434d2008-12-04 10:22:40 +00001453 } elsif (0 < $response && $response <= $num) {
1454 $ix = $response - 1;
1455 } else {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001456 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1457 "Sorry, only %d hunks available.\n", $num), $num);
William Pursell070434d2008-12-04 10:22:40 +00001458 }
1459 next;
1460 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001461 elsif ($line =~ /^d/i) {
1462 while ($ix < $num) {
1463 if (!defined $hunk[$ix]{USE}) {
1464 $hunk[$ix]{USE} = 0;
1465 }
1466 $ix++;
1467 }
1468 next;
1469 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001470 elsif ($line =~ /^q/i) {
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001471 for ($i = 0; $i < $num; $i++) {
1472 if (!defined $hunk[$i]{USE}) {
1473 $hunk[$i]{USE} = 0;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001474 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001475 }
1476 $quit = 1;
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001477 last;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001478 }
William Purselldd971cc2008-11-27 04:07:57 +00001479 elsif ($line =~ m|^/(.*)|) {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001480 my $regex = $1;
1481 if ($1 eq "") {
Vasco Almeida258e7792016-12-14 11:54:25 -01001482 print colored $prompt_color, __("search for regex? ");
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001483 $regex = <STDIN>;
1484 if (defined $regex) {
1485 chomp $regex;
1486 }
1487 }
William Purselldd971cc2008-11-27 04:07:57 +00001488 my $search_string;
1489 eval {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001490 $search_string = qr{$regex}m;
William Purselldd971cc2008-11-27 04:07:57 +00001491 };
1492 if ($@) {
1493 my ($err,$exp) = ($@, $1);
1494 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
Vasco Almeida13c58c12016-12-14 11:54:27 -01001495 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
William Purselldd971cc2008-11-27 04:07:57 +00001496 next;
1497 }
1498 my $iy = $ix;
1499 while (1) {
1500 my $text = join ("", @{$hunk[$iy]{TEXT}});
1501 last if ($text =~ $search_string);
1502 $iy++;
1503 $iy = 0 if ($iy >= $num);
1504 if ($ix == $iy) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001505 error_msg __("No hunk matches the given pattern\n");
William Purselldd971cc2008-11-27 04:07:57 +00001506 last;
1507 }
1508 }
1509 $ix = $iy;
1510 next;
1511 }
William Pursellace30ba2008-11-27 04:08:03 +00001512 elsif ($line =~ /^K/) {
1513 if ($other =~ /K/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001514 $ix--;
William Pursellace30ba2008-11-27 04:08:03 +00001515 }
1516 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001517 error_msg __("No previous hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001518 }
1519 next;
1520 }
William Pursellace30ba2008-11-27 04:08:03 +00001521 elsif ($line =~ /^J/) {
1522 if ($other =~ /J/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001523 $ix++;
William Pursellace30ba2008-11-27 04:08:03 +00001524 }
1525 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001526 error_msg __("No next hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001527 }
1528 next;
1529 }
William Pursellace30ba2008-11-27 04:08:03 +00001530 elsif ($line =~ /^k/) {
1531 if ($other =~ /k/) {
1532 while (1) {
1533 $ix--;
1534 last if (!$ix ||
1535 !defined $hunk[$ix]{USE});
1536 }
1537 }
1538 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001539 error_msg __("No previous hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001540 }
1541 next;
1542 }
1543 elsif ($line =~ /^j/) {
1544 if ($other !~ /j/) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001545 error_msg __("No next hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001546 next;
1547 }
1548 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001549 elsif ($other =~ /s/ && $line =~ /^s/) {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001550 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001551 if (1 < @split) {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001552 print colored $header_color, sprintf(
1553 __n("Split into %d hunk.\n",
1554 "Split into %d hunks.\n",
1555 scalar(@split)), scalar(@split));
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001556 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001557 splice (@hunk, $ix, 1, @split);
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001558 $num = scalar @hunk;
1559 next;
1560 }
Jeff King03925132009-04-16 03:14:15 -04001561 elsif ($other =~ /e/ && $line =~ /^e/) {
Thomas Rastac083c42008-07-03 00:00:00 +02001562 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1563 if (defined $newhunk) {
1564 splice @hunk, $ix, 1, $newhunk;
1565 }
1566 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001567 else {
1568 help_patch_cmd($other);
1569 next;
1570 }
1571 # soft increment
1572 while (1) {
1573 $ix++;
1574 last if ($ix >= $num ||
1575 !defined $hunk[$ix]{USE});
1576 }
1577 }
1578 }
1579
Junio C Hamano7a26e652009-05-16 10:48:23 -07001580 @hunk = coalesce_overlapping_hunks(@hunk);
1581
Jean-Luc Herren7b40a452007-10-09 21:34:17 +02001582 my $n_lofs = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001583 my @result = ();
1584 for (@hunk) {
Thomas Rast8cbd4312008-07-02 23:59:16 +02001585 if ($_->{USE}) {
1586 push @result, @{$_->{TEXT}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001587 }
1588 }
1589
1590 if (@result) {
Jeff Kinge1327ed2010-02-22 20:05:44 -05001591 my @patch = reassemble_patch($head->{TEXT}, @result);
Thomas Rast8f0bef62009-08-13 14:29:39 +02001592 my $apply_routine = $patch_mode_flavour{APPLY};
1593 &$apply_routine(@patch);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001594 refresh();
1595 }
1596
1597 print "\n";
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001598 return $quit;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001599}
1600
1601sub diff_cmd {
1602 my @mods = list_modified('index-only');
1603 @mods = grep { !($_->{BINARY}) } @mods;
1604 return if (!@mods);
Vasco Almeida258e7792016-12-14 11:54:25 -01001605 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001606 IMMEDIATE => 1,
1607 HEADER => $status_head, },
1608 @mods);
1609 return if (!@them);
Vasco Almeida258e7792016-12-14 11:54:25 -01001610 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
Jeff King18bc7612008-02-13 05:50:51 -05001611 system(qw(git diff -p --cached), $reference, '--',
1612 map { $_->{VALUE} } @them);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001613}
1614
1615sub quit_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -01001616 print __("Bye.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001617 exit(0);
1618}
1619
1620sub help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -01001621# TRANSLATORS: please do not translate the command names
1622# 'status', 'update', 'revert', etc.
1623 print colored $help_color, __ <<'EOF' ;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001624status - show paths with changes
1625update - add working tree state to the staged set of changes
1626revert - revert staged set of changes back to the HEAD version
1627patch - pick hunks and update selectively
Ralf Thielowe519ecc2017-02-22 19:46:27 +01001628diff - view diff between HEAD and index
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001629add untracked - add contents of untracked files to the staged set of changes
1630EOF
1631}
1632
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001633sub process_args {
1634 return unless @ARGV;
1635 my $arg = shift @ARGV;
Thomas Rastd002ef42009-08-15 13:48:31 +02001636 if ($arg =~ /--patch(?:=(.*))?/) {
1637 if (defined $1) {
1638 if ($1 eq 'reset') {
1639 $patch_mode = 'reset_head';
1640 $patch_mode_revision = 'HEAD';
Vasco Almeida258e7792016-12-14 11:54:25 -01001641 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001642 if ($arg ne '--') {
1643 $patch_mode_revision = $arg;
1644 $patch_mode = ($arg eq 'HEAD' ?
1645 'reset_head' : 'reset_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001646 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001647 }
Thomas Rast4f353652009-08-15 13:48:30 +02001648 } elsif ($1 eq 'checkout') {
Vasco Almeida258e7792016-12-14 11:54:25 -01001649 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001650 if ($arg eq '--') {
1651 $patch_mode = 'checkout_index';
1652 } else {
1653 $patch_mode_revision = $arg;
1654 $patch_mode = ($arg eq 'HEAD' ?
1655 'checkout_head' : 'checkout_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001656 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001657 }
Thomas Rastdda1f2a2009-08-13 14:29:44 +02001658 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1659 $patch_mode = $1;
Vasco Almeida258e7792016-12-14 11:54:25 -01001660 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001661 } else {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001662 die sprintf(__("unknown --patch mode: %s"), $1);
Thomas Rastd002ef42009-08-15 13:48:31 +02001663 }
1664 } else {
1665 $patch_mode = 'stage';
Vasco Almeida258e7792016-12-14 11:54:25 -01001666 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001667 }
Vasco Almeida13c58c12016-12-14 11:54:27 -01001668 die sprintf(__("invalid argument %s, expecting --"),
1669 $arg) unless $arg eq "--";
Thomas Rastd002ef42009-08-15 13:48:31 +02001670 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Jeff Kingc852bd52017-03-02 04:48:22 -05001671 $patch_mode_only = 1;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001672 }
1673 elsif ($arg ne "--") {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001674 die sprintf(__("invalid argument %s, expecting --"), $arg);
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001675 }
1676}
1677
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001678sub main_loop {
1679 my @cmd = ([ 'status', \&status_cmd, ],
1680 [ 'update', \&update_cmd, ],
1681 [ 'revert', \&revert_cmd, ],
1682 [ 'add untracked', \&add_untracked_cmd, ],
1683 [ 'patch', \&patch_update_cmd, ],
1684 [ 'diff', \&diff_cmd, ],
1685 [ 'quit', \&quit_cmd, ],
1686 [ 'help', \&help_cmd, ],
1687 );
1688 while (1) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001689 my ($it) = list_and_choose({ PROMPT => __('What now'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001690 SINGLETON => 1,
1691 LIST_FLAT => 4,
Vasco Almeida258e7792016-12-14 11:54:25 -01001692 HEADER => __('*** Commands ***'),
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +02001693 ON_EOF => \&quit_cmd,
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001694 IMMEDIATE => 1 }, @cmd);
1695 if ($it) {
1696 eval {
1697 $it->[1]->();
1698 };
1699 if ($@) {
1700 print "$@";
1701 }
1702 }
1703 }
1704}
1705
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001706process_args();
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001707refresh();
Jeff Kingc852bd52017-03-02 04:48:22 -05001708if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001709 patch_update_cmd();
1710}
1711else {
1712 status_cmd();
1713 main_loop();
1714}