blob: c1f52e457f6c5b03947766555e10fba33e7e1b38 [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
Nguyễn Thái Ngọc Duy12434ef2018-01-13 19:10:38 +0700265 for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --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;
Phillip Wood3a8522f2018-03-05 10:56:30 +0000680 open $fh, '| git ' . $cmd . " --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
Jeff King42f7d452018-03-03 00:58:49 -0500708 if (@colored && @colored != @diff) {
709 print STDERR
710 "fatal: mismatched output from interactive.diffFilter\n",
711 "hint: Your filter must maintain a one-to-one correspondence\n",
712 "hint: between its input and output lines.\n";
713 exit 1;
714 }
715
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100716 for (my $i = 0; $i < @diff; $i++) {
717 if ($diff[$i] =~ /^@@ /) {
Jeff King03925132009-04-16 03:14:15 -0400718 push @hunk, { TEXT => [], DISPLAY => [],
719 TYPE => 'hunk' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800720 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100721 push @{$hunk[-1]{TEXT}}, $diff[$i];
722 push @{$hunk[-1]{DISPLAY}},
Jeff King01143842016-02-27 00:37:06 -0500723 (@colored ? $colored[$i] : $diff[$i]);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800724 }
725 return @hunk;
726}
727
Jeff Kingb717a622008-03-27 03:30:43 -0400728sub parse_diff_header {
729 my $src = shift;
730
Jeff King03925132009-04-16 03:14:15 -0400731 my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
732 my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
Jeff King24ab81a2009-10-27 20:52:57 -0400733 my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
Jeff Kingb717a622008-03-27 03:30:43 -0400734
735 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
Jeff King24ab81a2009-10-27 20:52:57 -0400736 my $dest =
737 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
738 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
739 $head;
Jeff Kingb717a622008-03-27 03:30:43 -0400740 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
741 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
742 }
Jeff King24ab81a2009-10-27 20:52:57 -0400743 return ($head, $mode, $deletion);
Jeff Kingb717a622008-03-27 03:30:43 -0400744}
745
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800746sub hunk_splittable {
747 my ($text) = @_;
748
749 my @s = split_hunk($text);
750 return (1 < @s);
751}
752
753sub parse_hunk_header {
754 my ($line) = @_;
755 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
Jean-Luc Herren7288ed82007-10-09 21:29:26 +0200756 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
757 $o_cnt = 1 unless defined $o_cnt;
758 $n_cnt = 1 unless defined $n_cnt;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800759 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
760}
761
Phillip Wood492e60c2018-02-19 11:29:02 +0000762sub format_hunk_header {
763 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
764 return ("@@ -$o_ofs" .
765 (($o_cnt != 1) ? ",$o_cnt" : '') .
766 " +$n_ofs" .
767 (($n_cnt != 1) ? ",$n_cnt" : '') .
768 " @@\n");
769}
770
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800771sub split_hunk {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100772 my ($text, $display) = @_;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800773 my @split = ();
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100774 if (!defined $display) {
775 $display = $text;
776 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800777 # If there are context lines in the middle of a hunk,
778 # it can be split, but we would need to take care of
779 # overlaps later.
780
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200781 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800782 my $hunk_start = 1;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800783
784 OUTER:
785 while (1) {
786 my $next_hunk_start = undef;
787 my $i = $hunk_start - 1;
788 my $this = +{
789 TEXT => [],
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100790 DISPLAY => [],
Jeff King03925132009-04-16 03:14:15 -0400791 TYPE => 'hunk',
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800792 OLD => $o_ofs,
793 NEW => $n_ofs,
794 OCNT => 0,
795 NCNT => 0,
796 ADDDEL => 0,
797 POSTCTX => 0,
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100798 USE => undef,
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800799 };
800
801 while (++$i < @$text) {
802 my $line = $text->[$i];
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100803 my $display = $display->[$i];
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000804 if ($line =~ /^\\/) {
805 push @{$this->{TEXT}}, $line;
806 push @{$this->{DISPLAY}}, $display;
807 next;
808 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800809 if ($line =~ /^ /) {
810 if ($this->{ADDDEL} &&
811 !defined $next_hunk_start) {
812 # We have seen leading context and
813 # adds/dels and then here is another
814 # context, which is trailing for this
815 # split hunk and leading for the next
816 # one.
817 $next_hunk_start = $i;
818 }
819 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100820 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800821 $this->{OCNT}++;
822 $this->{NCNT}++;
823 if (defined $next_hunk_start) {
824 $this->{POSTCTX}++;
825 }
826 next;
827 }
828
829 # add/del
830 if (defined $next_hunk_start) {
831 # We are done with the current hunk and
832 # this is the first real change for the
833 # next split one.
834 $hunk_start = $next_hunk_start;
835 $o_ofs = $this->{OLD} + $this->{OCNT};
836 $n_ofs = $this->{NEW} + $this->{NCNT};
837 $o_ofs -= $this->{POSTCTX};
838 $n_ofs -= $this->{POSTCTX};
839 push @split, $this;
840 redo OUTER;
841 }
842 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100843 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800844 $this->{ADDDEL}++;
845 if ($line =~ /^-/) {
846 $this->{OCNT}++;
847 }
848 else {
849 $this->{NCNT}++;
850 }
851 }
852
853 push @split, $this;
854 last;
855 }
856
857 for my $hunk (@split) {
858 $o_ofs = $hunk->{OLD};
859 $n_ofs = $hunk->{NEW};
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200860 my $o_cnt = $hunk->{OCNT};
861 my $n_cnt = $hunk->{NCNT};
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800862
Phillip Wood492e60c2018-02-19 11:29:02 +0000863 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100864 my $display_head = $head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800865 unshift @{$hunk->{TEXT}}, $head;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100866 if ($diff_use_color) {
867 $display_head = colored($fraginfo_color, $head);
868 }
869 unshift @{$hunk->{DISPLAY}}, $display_head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800870 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100871 return @split;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800872}
873
Junio C Hamano7a26e652009-05-16 10:48:23 -0700874sub find_last_o_ctx {
875 my ($it) = @_;
876 my $text = $it->{TEXT};
877 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
878 my $i = @{$text};
879 my $last_o_ctx = $o_ofs + $o_cnt;
880 while (0 < --$i) {
881 my $line = $text->[$i];
882 if ($line =~ /^ /) {
883 $last_o_ctx--;
884 next;
885 }
886 last;
887 }
888 return $last_o_ctx;
889}
890
891sub merge_hunk {
892 my ($prev, $this) = @_;
893 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
894 parse_hunk_header($prev->{TEXT}[0]);
895 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
896 parse_hunk_header($this->{TEXT}[0]);
897
898 my (@line, $i, $ofs, $o_cnt, $n_cnt);
899 $ofs = $o0_ofs;
900 $o_cnt = $n_cnt = 0;
901 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
902 my $line = $prev->{TEXT}[$i];
903 if ($line =~ /^\+/) {
904 $n_cnt++;
905 push @line, $line;
906 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000907 } elsif ($line =~ /^\\/) {
908 push @line, $line;
909 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700910 }
911
912 last if ($o1_ofs <= $ofs);
913
914 $o_cnt++;
915 $ofs++;
916 if ($line =~ /^ /) {
917 $n_cnt++;
918 }
919 push @line, $line;
920 }
921
922 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
923 my $line = $this->{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 $ofs++;
933 $o_cnt++;
934 if ($line =~ /^ /) {
935 $n_cnt++;
936 }
937 push @line, $line;
938 }
Phillip Wood492e60c2018-02-19 11:29:02 +0000939 my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
Junio C Hamano7a26e652009-05-16 10:48:23 -0700940 @{$prev->{TEXT}} = ($head, @line);
941}
942
943sub coalesce_overlapping_hunks {
944 my (@in) = @_;
945 my @out = ();
946
947 my ($last_o_ctx, $last_was_dirty);
Phillip Woodfecc6f32018-03-01 10:51:00 +0000948 my $ofs_delta = 0;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700949
Phillip Woodfecc6f32018-03-01 10:51:00 +0000950 for (@in) {
Thomas Rast3d792162009-08-15 15:56:39 +0200951 if ($_->{TYPE} ne 'hunk') {
952 push @out, $_;
953 next;
954 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700955 my $text = $_->{TEXT};
Phillip Woodfecc6f32018-03-01 10:51:00 +0000956 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
957 parse_hunk_header($text->[0]);
958 unless ($_->{USE}) {
959 $ofs_delta += $o_cnt - $n_cnt;
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000960 # If this hunk has been edited then subtract
961 # the delta that is due to the edit.
962 if ($_->{OFS_DELTA}) {
963 $ofs_delta -= $_->{OFS_DELTA};
964 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000965 next;
966 }
967 if ($ofs_delta) {
968 $n_ofs += $ofs_delta;
969 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
970 $n_ofs, $n_cnt);
971 }
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000972 # If this hunk was edited then adjust the offset delta
973 # to reflect the edit.
974 if ($_->{OFS_DELTA}) {
975 $ofs_delta += $_->{OFS_DELTA};
976 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700977 if (defined $last_o_ctx &&
978 $o_ofs <= $last_o_ctx &&
979 !$_->{DIRTY} &&
980 !$last_was_dirty) {
981 merge_hunk($out[-1], $_);
982 }
983 else {
984 push @out, $_;
985 }
986 $last_o_ctx = find_last_o_ctx($out[-1]);
987 $last_was_dirty = $_->{DIRTY};
988 }
989 return @out;
990}
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800991
Jeff Kinge1327ed2010-02-22 20:05:44 -0500992sub reassemble_patch {
993 my $head = shift;
994 my @patch;
995
996 # Include everything in the header except the beginning of the diff.
997 push @patch, (grep { !/^[-+]{3}/ } @$head);
998
999 # Then include any headers from the hunk lines, which must
1000 # come before any actual hunk.
1001 while (@_ && $_[0] !~ /^@/) {
1002 push @patch, shift;
1003 }
1004
1005 # Then begin the diff.
1006 push @patch, grep { /^[-+]{3}/ } @$head;
1007
1008 # And then the actual hunks.
1009 push @patch, @_;
1010
1011 return @patch;
1012}
1013
Thomas Rastac083c42008-07-03 00:00:00 +02001014sub color_diff {
1015 return map {
1016 colored((/^@/ ? $fraginfo_color :
1017 /^\+/ ? $diff_new_color :
1018 /^-/ ? $diff_old_color :
1019 $diff_plain_color),
1020 $_);
1021 } @_;
1022}
1023
Vasco Almeidac9d96162016-12-14 11:54:32 -01001024my %edit_hunk_manually_modes = (
1025 stage => N__(
1026"If the patch applies cleanly, the edited hunk will immediately be
1027marked for staging."),
1028 stash => N__(
1029"If the patch applies cleanly, the edited hunk will immediately be
1030marked for stashing."),
1031 reset_head => N__(
1032"If the patch applies cleanly, the edited hunk will immediately be
1033marked for unstaging."),
1034 reset_nothead => N__(
1035"If the patch applies cleanly, the edited hunk will immediately be
1036marked for applying."),
1037 checkout_index => N__(
1038"If the patch applies cleanly, the edited hunk will immediately be
Ralf Thielow0301f1f2017-04-13 18:41:12 +02001039marked for discarding."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001040 checkout_head => N__(
1041"If the patch applies cleanly, the edited hunk will immediately be
1042marked for discarding."),
1043 checkout_nothead => N__(
1044"If the patch applies cleanly, the edited hunk will immediately be
1045marked for applying."),
1046);
1047
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001048sub recount_edited_hunk {
1049 local $_;
1050 my ($oldtext, $newtext) = @_;
1051 my ($o_cnt, $n_cnt) = (0, 0);
1052 for (@{$newtext}[1..$#{$newtext}]) {
1053 my $mode = substr($_, 0, 1);
1054 if ($mode eq '-') {
1055 $o_cnt++;
1056 } elsif ($mode eq '+') {
1057 $n_cnt++;
1058 } elsif ($mode eq ' ') {
1059 $o_cnt++;
1060 $n_cnt++;
1061 }
1062 }
1063 my ($o_ofs, undef, $n_ofs, undef) =
1064 parse_hunk_header($newtext->[0]);
1065 $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1066 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1067 parse_hunk_header($oldtext->[0]);
1068 # Return the change in the number of lines inserted by this hunk
1069 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1070}
1071
Thomas Rastac083c42008-07-03 00:00:00 +02001072sub edit_hunk_manually {
1073 my ($oldtext) = @_;
1074
1075 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1076 my $fh;
1077 open $fh, '>', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001078 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
Vasco Almeidac9d96162016-12-14 11:54:32 -01001079 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
Thomas Rastac083c42008-07-03 00:00:00 +02001080 print $fh @$oldtext;
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -07001081 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1082 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
Vasco Almeidac9d96162016-12-14 11:54:32 -01001083 my $comment_line_char = Git::get_comment_line_char;
1084 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1085---
1086To remove '%s' lines, make them ' ' lines (context).
1087To remove '%s' lines, delete them.
1088Lines starting with %s will be removed.
Thomas Rastac083c42008-07-03 00:00:00 +02001089EOF
Vasco Almeidac9d96162016-12-14 11:54:32 -01001090__($edit_hunk_manually_modes{$patch_mode}),
1091# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1092__ <<EOF2 ;
1093If it does not apply cleanly, you will be given an opportunity to
1094edit again. If all lines of the hunk are removed, then the edit is
1095aborted and the hunk is left unchanged.
1096EOF2
Thomas Rastac083c42008-07-03 00:00:00 +02001097 close $fh;
1098
Jonathan Niederb4479f02009-10-30 20:42:34 -05001099 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
Thomas Rastac083c42008-07-03 00:00:00 +02001100 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1101
Deskin Miller1d398a02009-02-12 00:19:41 -05001102 if ($? != 0) {
1103 return undef;
1104 }
1105
Thomas Rastac083c42008-07-03 00:00:00 +02001106 open $fh, '<', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001107 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
Jeff Kingd85d7ec2017-06-21 15:28:59 -04001108 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
Thomas Rastac083c42008-07-03 00:00:00 +02001109 close $fh;
1110 unlink $hunkfile;
1111
1112 # Abort if nothing remains
1113 if (!grep { /\S/ } @newtext) {
1114 return undef;
1115 }
1116
1117 # Reinsert the first hunk header if the user accidentally deleted it
1118 if ($newtext[0] !~ /^@/) {
1119 unshift @newtext, $oldtext->[0];
1120 }
1121 return \@newtext;
1122}
1123
1124sub diff_applies {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001125 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
Thomas Rast8f0bef62009-08-13 14:29:39 +02001126 map { @{$_->{TEXT}} } @_);
Thomas Rastac083c42008-07-03 00:00:00 +02001127}
1128
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001129sub _restore_terminal_and_die {
1130 ReadMode 'restore';
1131 print "\n";
1132 exit 1;
1133}
1134
1135sub prompt_single_character {
1136 if ($use_readkey) {
1137 local $SIG{TERM} = \&_restore_terminal_and_die;
1138 local $SIG{INT} = \&_restore_terminal_and_die;
1139 ReadMode 'cbreak';
1140 my $key = ReadKey 0;
1141 ReadMode 'restore';
Thomas Rastb5cc0032011-05-17 17:19:08 +02001142 if ($use_termcap and $key eq "\e") {
1143 while (!defined $term_escapes{$key}) {
1144 my $next = ReadKey 0.5;
1145 last if (!defined $next);
1146 $key .= $next;
1147 }
1148 $key =~ s/\e/^[/;
1149 }
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001150 print "$key" if defined $key;
1151 print "\n";
1152 return $key;
1153 } else {
1154 return <STDIN>;
1155 }
1156}
1157
Thomas Rastac083c42008-07-03 00:00:00 +02001158sub prompt_yesno {
1159 my ($prompt) = @_;
1160 while (1) {
1161 print colored $prompt_color, $prompt;
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001162 my $line = prompt_single_character;
Jeff Kingd5addcf2017-06-21 15:26:36 -04001163 return undef unless defined $line;
Thomas Rastac083c42008-07-03 00:00:00 +02001164 return 0 if $line =~ /^n/i;
1165 return 1 if $line =~ /^y/i;
1166 }
1167}
1168
1169sub edit_hunk_loop {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001170 my ($head, $hunks, $ix) = @_;
1171 my $hunk = $hunks->[$ix];
1172 my $text = $hunk->{TEXT};
Thomas Rastac083c42008-07-03 00:00:00 +02001173
1174 while (1) {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001175 my $newtext = edit_hunk_manually($text);
1176 if (!defined $newtext) {
Thomas Rastac083c42008-07-03 00:00:00 +02001177 return undef;
1178 }
Jeff King03925132009-04-16 03:14:15 -04001179 my $newhunk = {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001180 TEXT => $newtext,
1181 TYPE => $hunk->{TYPE},
Junio C Hamano7a26e652009-05-16 10:48:23 -07001182 USE => 1,
1183 DIRTY => 1,
Jeff King03925132009-04-16 03:14:15 -04001184 };
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001185 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1186 # If this hunk has already been edited then add the
1187 # offset delta of the previous edit to get the real
1188 # delta from the original unedited hunk.
1189 $hunk->{OFS_DELTA} and
1190 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
Thomas Rastac083c42008-07-03 00:00:00 +02001191 if (diff_applies($head,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001192 @{$hunks}[0..$ix-1],
Thomas Rastac083c42008-07-03 00:00:00 +02001193 $newhunk,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001194 @{$hunks}[$ix+1..$#{$hunks}])) {
1195 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
Thomas Rastac083c42008-07-03 00:00:00 +02001196 return $newhunk;
1197 }
1198 else {
1199 prompt_yesno(
Vasco Almeida258e7792016-12-14 11:54:25 -01001200 # TRANSLATORS: do not translate [y/n]
1201 # The program will only accept that input
1202 # at this point.
1203 # Consider translating (saying "no" discards!) as
1204 # (saying "n" for "no" discards!) if the translation
1205 # of the word "no" does not start with n.
1206 __('Your edited hunk does not apply. Edit again '
1207 . '(saying "no" discards!) [y/n]? ')
Thomas Rastac083c42008-07-03 00:00:00 +02001208 ) or return undef;
1209 }
1210 }
1211}
1212
Vasco Almeida186b52c2016-12-14 11:54:31 -01001213my %help_patch_modes = (
1214 stage => N__(
1215"y - stage this hunk
1216n - do not stage this hunk
1217q - quit; do not stage this hunk or any of the remaining ones
1218a - stage this hunk and all later hunks in the file
1219d - do not stage this hunk or any of the later hunks in the file"),
1220 stash => N__(
1221"y - stash this hunk
1222n - do not stash this hunk
1223q - quit; do not stash this hunk or any of the remaining ones
1224a - stash this hunk and all later hunks in the file
1225d - do not stash this hunk or any of the later hunks in the file"),
1226 reset_head => N__(
1227"y - unstage this hunk
1228n - do not unstage this hunk
1229q - quit; do not unstage this hunk or any of the remaining ones
1230a - unstage this hunk and all later hunks in the file
1231d - do not unstage this hunk or any of the later hunks in the file"),
1232 reset_nothead => N__(
1233"y - apply this hunk to index
1234n - do not apply this hunk to index
1235q - quit; do not apply this hunk or any of the remaining ones
1236a - apply this hunk and all later hunks in the file
1237d - do not apply this hunk or any of the later hunks in the file"),
1238 checkout_index => N__(
1239"y - discard this hunk from worktree
1240n - do not discard this hunk from worktree
1241q - quit; do not discard this hunk or any of the remaining ones
1242a - discard this hunk and all later hunks in the file
1243d - do not discard this hunk or any of the later hunks in the file"),
1244 checkout_head => N__(
1245"y - discard this hunk from index and worktree
1246n - do not discard this hunk from index and worktree
1247q - quit; do not discard this hunk or any of the remaining ones
1248a - discard this hunk and all later hunks in the file
1249d - do not discard this hunk or any of the later hunks in the file"),
1250 checkout_nothead => N__(
1251"y - apply this hunk to index and worktree
1252n - do not apply this hunk to index and worktree
1253q - quit; do not apply this hunk or any of the remaining ones
1254a - apply this hunk and all later hunks in the file
1255d - do not apply this hunk or any of the later hunks in the file"),
1256);
1257
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001258sub help_patch_cmd {
Phillip Wood01a69662018-02-13 10:32:39 +00001259 local $_;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001260 my $other = $_[0] . ",?";
Phillip Wood01a69662018-02-13 10:32:39 +00001261 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1262 map { "$_\n" } grep {
1263 my $c = quotemeta(substr($_, 0, 1));
1264 $other =~ /,$c/
1265 } split "\n", __ <<EOF ;
William Pursell070434d2008-12-04 10:22:40 +00001266g - select a hunk to go to
William Purselldd971cc2008-11-27 04:07:57 +00001267/ - search for a hunk matching the given regex
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001268j - leave this hunk undecided, see next undecided hunk
1269J - leave this hunk undecided, see next hunk
1270k - leave this hunk undecided, see previous undecided hunk
1271K - leave this hunk undecided, see previous hunk
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001272s - split the current hunk into smaller hunks
Thomas Rastac083c42008-07-03 00:00:00 +02001273e - manually edit the current hunk
Ralf Wildenhues280e50c2007-11-28 19:21:42 +01001274? - print help
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001275EOF
1276}
1277
Thomas Rast8f0bef62009-08-13 14:29:39 +02001278sub apply_patch {
1279 my $cmd = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001280 my $ret = run_git_apply $cmd, @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +02001281 if (!$ret) {
1282 print STDERR @_;
1283 }
1284 return $ret;
1285}
1286
Thomas Rast4f353652009-08-15 13:48:30 +02001287sub apply_patch_for_checkout_commit {
1288 my $reverse = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001289 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1290 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001291
1292 if ($applies_worktree && $applies_index) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001293 run_git_apply 'apply '.$reverse.' --cached', @_;
1294 run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001295 return 1;
1296 } elsif (!$applies_index) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001297 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1298 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001299 return run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001300 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001301 print colored $error_color, __("Nothing was applied.\n");
Thomas Rast4f353652009-08-15 13:48:30 +02001302 return 0;
1303 }
1304 } else {
1305 print STDERR @_;
1306 return 0;
1307 }
1308}
1309
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001310sub patch_update_cmd {
Thomas Rast8f0bef62009-08-13 14:29:39 +02001311 my @all_mods = list_modified($patch_mode_flavour{FILTER});
Vasco Almeida13c58c12016-12-14 11:54:27 -01001312 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
Jeff King4066bd62012-04-05 08:30:08 -04001313 for grep { $_->{UNMERGED} } @all_mods;
1314 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1315
Thomas Rast9fe7a642008-10-26 20:37:06 +01001316 my @mods = grep { !($_->{BINARY}) } @all_mods;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001317 my @them;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001318
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001319 if (!@mods) {
Thomas Rast9fe7a642008-10-26 20:37:06 +01001320 if (@all_mods) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001321 print STDERR __("Only binary files changed.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001322 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001323 print STDERR __("No changes.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001324 }
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001325 return 0;
1326 }
Jeff Kingc852bd52017-03-02 04:48:22 -05001327 if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001328 @them = @mods;
1329 }
1330 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001331 @them = list_and_choose({ PROMPT => __('Patch update'),
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001332 HEADER => $status_head, },
1333 @mods);
1334 }
Junio C Hamano12db3342007-11-22 01:47:13 -08001335 for (@them) {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001336 return 0 if patch_update_file($_->{VALUE});
Junio C Hamano12db3342007-11-22 01:47:13 -08001337 }
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001338}
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001339
William Pursell3f6aff62008-12-04 10:00:24 +00001340# Generate a one line summary of a hunk.
1341sub summarize_hunk {
1342 my $rhunk = shift;
1343 my $summary = $rhunk->{TEXT}[0];
1344
1345 # Keep the line numbers, discard extra context.
1346 $summary =~ s/@@(.*?)@@.*/$1 /s;
1347 $summary .= " " x (20 - length $summary);
1348
1349 # Add some user context.
1350 for my $line (@{$rhunk->{TEXT}}) {
1351 if ($line =~ m/^[+-].*\w/) {
1352 $summary .= $line;
1353 last;
1354 }
1355 }
1356
1357 chomp $summary;
1358 return substr($summary, 0, 80) . "\n";
1359}
1360
1361
1362# Print a one-line summary of each hunk in the array ref in
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001363# the first argument, starting with the index in the 2nd.
William Pursell3f6aff62008-12-04 10:00:24 +00001364sub display_hunks {
1365 my ($hunks, $i) = @_;
1366 my $ctr = 0;
1367 $i ||= 0;
1368 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1369 my $status = " ";
1370 if (defined $hunks->[$i]{USE}) {
1371 $status = $hunks->[$i]{USE} ? "+" : "-";
1372 }
1373 printf "%s%2d: %s",
1374 $status,
1375 $i + 1,
1376 summarize_hunk($hunks->[$i]);
1377 }
1378 return $i;
1379}
1380
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001381my %patch_update_prompt_modes = (
1382 stage => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001383 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1384 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1385 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001386 },
1387 stash => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001388 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1389 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1390 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001391 },
1392 reset_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001393 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1394 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1395 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001396 },
1397 reset_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001398 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1399 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1400 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001401 },
1402 checkout_index => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001403 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1404 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1405 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001406 },
1407 checkout_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001408 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1409 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1410 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001411 },
1412 checkout_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001413 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1414 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1415 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001416 },
1417);
1418
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001419sub patch_update_file {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001420 my $quit = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001421 my ($ix, $num);
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001422 my $path = shift;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001423 my ($head, @hunk) = parse_diff($path);
Jeff King24ab81a2009-10-27 20:52:57 -04001424 ($head, my $mode, my $deletion) = parse_diff_header($head);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001425 for (@{$head->{DISPLAY}}) {
1426 print;
1427 }
Jeff Kingca724682008-03-27 03:32:25 -04001428
1429 if (@{$mode->{TEXT}}) {
Jeff King03925132009-04-16 03:14:15 -04001430 unshift @hunk, $mode;
Jeff Kingca724682008-03-27 03:32:25 -04001431 }
Jeff King8947fdd2009-12-08 02:49:35 -05001432 if (@{$deletion->{TEXT}}) {
1433 foreach my $hunk (@hunk) {
1434 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1435 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1436 }
Jeff King24ab81a2009-10-27 20:52:57 -04001437 @hunk = ($deletion);
1438 }
Jeff Kingca724682008-03-27 03:32:25 -04001439
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001440 $num = scalar @hunk;
1441 $ix = 0;
1442
1443 while (1) {
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001444 my ($prev, $next, $other, $undecided, $i);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001445 $other = '';
1446
1447 if ($num <= $ix) {
1448 $ix = 0;
1449 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001450 for ($i = 0; $i < $ix; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001451 if (!defined $hunk[$i]{USE}) {
1452 $prev = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001453 $other .= ',k';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001454 last;
1455 }
1456 }
1457 if ($ix) {
William Pursell57886bc2008-11-27 04:07:52 +00001458 $other .= ',K';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001459 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001460 for ($i = $ix + 1; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001461 if (!defined $hunk[$i]{USE}) {
1462 $next = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001463 $other .= ',j';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001464 last;
1465 }
1466 }
1467 if ($ix < $num - 1) {
William Pursell57886bc2008-11-27 04:07:52 +00001468 $other .= ',J';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001469 }
William Pursell070434d2008-12-04 10:22:40 +00001470 if ($num > 1) {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001471 $other .= ',g,/';
William Pursell070434d2008-12-04 10:22:40 +00001472 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001473 for ($i = 0; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001474 if (!defined $hunk[$i]{USE}) {
1475 $undecided = 1;
1476 last;
1477 }
1478 }
1479 last if (!$undecided);
1480
Jeff King03925132009-04-16 03:14:15 -04001481 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1482 hunk_splittable($hunk[$ix]{TEXT})) {
William Pursell57886bc2008-11-27 04:07:52 +00001483 $other .= ',s';
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001484 }
Jeff King03925132009-04-16 03:14:15 -04001485 if ($hunk[$ix]{TYPE} eq 'hunk') {
1486 $other .= ',e';
1487 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001488 for (@{$hunk[$ix]{DISPLAY}}) {
1489 print;
1490 }
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001491 print colored $prompt_color,
1492 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1493
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001494 my $line = prompt_single_character;
Jeff Kinga8bec7a2014-12-15 11:35:27 -05001495 last unless defined $line;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001496 if ($line) {
1497 if ($line =~ /^y/i) {
1498 $hunk[$ix]{USE} = 1;
1499 }
1500 elsif ($line =~ /^n/i) {
1501 $hunk[$ix]{USE} = 0;
1502 }
1503 elsif ($line =~ /^a/i) {
1504 while ($ix < $num) {
1505 if (!defined $hunk[$ix]{USE}) {
1506 $hunk[$ix]{USE} = 1;
1507 }
1508 $ix++;
1509 }
1510 next;
1511 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001512 elsif ($line =~ /^g(.*)/) {
William Pursell070434d2008-12-04 10:22:40 +00001513 my $response = $1;
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001514 unless ($other =~ /g/) {
1515 error_msg __("No other hunks to goto\n");
1516 next;
1517 }
William Pursell070434d2008-12-04 10:22:40 +00001518 my $no = $ix > 10 ? $ix - 10 : 0;
1519 while ($response eq '') {
William Pursell070434d2008-12-04 10:22:40 +00001520 $no = display_hunks(\@hunk, $no);
1521 if ($no < $num) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001522 print __("go to which hunk (<ret> to see more)? ");
1523 } else {
1524 print __("go to which hunk? ");
William Pursell070434d2008-12-04 10:22:40 +00001525 }
William Pursell070434d2008-12-04 10:22:40 +00001526 $response = <STDIN>;
Thomas Rast68c02d72009-02-02 22:46:29 +01001527 if (!defined $response) {
1528 $response = '';
1529 }
William Pursell070434d2008-12-04 10:22:40 +00001530 chomp $response;
1531 }
1532 if ($response !~ /^\s*\d+\s*$/) {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001533 error_msg sprintf(__("Invalid number: '%s'\n"),
1534 $response);
William Pursell070434d2008-12-04 10:22:40 +00001535 } elsif (0 < $response && $response <= $num) {
1536 $ix = $response - 1;
1537 } else {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001538 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1539 "Sorry, only %d hunks available.\n", $num), $num);
William Pursell070434d2008-12-04 10:22:40 +00001540 }
1541 next;
1542 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001543 elsif ($line =~ /^d/i) {
1544 while ($ix < $num) {
1545 if (!defined $hunk[$ix]{USE}) {
1546 $hunk[$ix]{USE} = 0;
1547 }
1548 $ix++;
1549 }
1550 next;
1551 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001552 elsif ($line =~ /^q/i) {
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001553 for ($i = 0; $i < $num; $i++) {
1554 if (!defined $hunk[$i]{USE}) {
1555 $hunk[$i]{USE} = 0;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001556 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001557 }
1558 $quit = 1;
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001559 last;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001560 }
William Purselldd971cc2008-11-27 04:07:57 +00001561 elsif ($line =~ m|^/(.*)|) {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001562 my $regex = $1;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001563 unless ($other =~ m|/|) {
1564 error_msg __("No other hunks to search\n");
1565 next;
1566 }
Ævar Arnfjörð Bjarmasonfd2fb4a2018-03-31 12:50:58 +00001567 if ($regex eq "") {
Vasco Almeida258e7792016-12-14 11:54:25 -01001568 print colored $prompt_color, __("search for regex? ");
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001569 $regex = <STDIN>;
1570 if (defined $regex) {
1571 chomp $regex;
1572 }
1573 }
William Purselldd971cc2008-11-27 04:07:57 +00001574 my $search_string;
1575 eval {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001576 $search_string = qr{$regex}m;
William Purselldd971cc2008-11-27 04:07:57 +00001577 };
1578 if ($@) {
1579 my ($err,$exp) = ($@, $1);
1580 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
Vasco Almeida13c58c12016-12-14 11:54:27 -01001581 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
William Purselldd971cc2008-11-27 04:07:57 +00001582 next;
1583 }
1584 my $iy = $ix;
1585 while (1) {
1586 my $text = join ("", @{$hunk[$iy]{TEXT}});
1587 last if ($text =~ $search_string);
1588 $iy++;
1589 $iy = 0 if ($iy >= $num);
1590 if ($ix == $iy) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001591 error_msg __("No hunk matches the given pattern\n");
William Purselldd971cc2008-11-27 04:07:57 +00001592 last;
1593 }
1594 }
1595 $ix = $iy;
1596 next;
1597 }
William Pursellace30ba2008-11-27 04:08:03 +00001598 elsif ($line =~ /^K/) {
1599 if ($other =~ /K/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001600 $ix--;
William Pursellace30ba2008-11-27 04:08:03 +00001601 }
1602 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001603 error_msg __("No previous hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001604 }
1605 next;
1606 }
William Pursellace30ba2008-11-27 04:08:03 +00001607 elsif ($line =~ /^J/) {
1608 if ($other =~ /J/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001609 $ix++;
William Pursellace30ba2008-11-27 04:08:03 +00001610 }
1611 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001612 error_msg __("No next hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001613 }
1614 next;
1615 }
William Pursellace30ba2008-11-27 04:08:03 +00001616 elsif ($line =~ /^k/) {
1617 if ($other =~ /k/) {
1618 while (1) {
1619 $ix--;
1620 last if (!$ix ||
1621 !defined $hunk[$ix]{USE});
1622 }
1623 }
1624 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001625 error_msg __("No previous hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001626 }
1627 next;
1628 }
1629 elsif ($line =~ /^j/) {
1630 if ($other !~ /j/) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001631 error_msg __("No next hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001632 next;
1633 }
1634 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001635 elsif ($line =~ /^s/) {
1636 unless ($other =~ /s/) {
1637 error_msg __("Sorry, cannot split this hunk\n");
1638 next;
1639 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001640 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001641 if (1 < @split) {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001642 print colored $header_color, sprintf(
1643 __n("Split into %d hunk.\n",
1644 "Split into %d hunks.\n",
1645 scalar(@split)), scalar(@split));
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001646 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001647 splice (@hunk, $ix, 1, @split);
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001648 $num = scalar @hunk;
1649 next;
1650 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001651 elsif ($line =~ /^e/) {
1652 unless ($other =~ /e/) {
1653 error_msg __("Sorry, cannot edit this hunk\n");
1654 next;
1655 }
Thomas Rastac083c42008-07-03 00:00:00 +02001656 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1657 if (defined $newhunk) {
1658 splice @hunk, $ix, 1, $newhunk;
1659 }
1660 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001661 else {
1662 help_patch_cmd($other);
1663 next;
1664 }
1665 # soft increment
1666 while (1) {
1667 $ix++;
1668 last if ($ix >= $num ||
1669 !defined $hunk[$ix]{USE});
1670 }
1671 }
1672 }
1673
Junio C Hamano7a26e652009-05-16 10:48:23 -07001674 @hunk = coalesce_overlapping_hunks(@hunk);
1675
Jean-Luc Herren7b40a452007-10-09 21:34:17 +02001676 my $n_lofs = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001677 my @result = ();
1678 for (@hunk) {
Thomas Rast8cbd4312008-07-02 23:59:16 +02001679 if ($_->{USE}) {
1680 push @result, @{$_->{TEXT}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001681 }
1682 }
1683
1684 if (@result) {
Jeff Kinge1327ed2010-02-22 20:05:44 -05001685 my @patch = reassemble_patch($head->{TEXT}, @result);
Thomas Rast8f0bef62009-08-13 14:29:39 +02001686 my $apply_routine = $patch_mode_flavour{APPLY};
1687 &$apply_routine(@patch);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001688 refresh();
1689 }
1690
1691 print "\n";
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001692 return $quit;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001693}
1694
1695sub diff_cmd {
1696 my @mods = list_modified('index-only');
1697 @mods = grep { !($_->{BINARY}) } @mods;
1698 return if (!@mods);
Vasco Almeida258e7792016-12-14 11:54:25 -01001699 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001700 IMMEDIATE => 1,
1701 HEADER => $status_head, },
1702 @mods);
1703 return if (!@them);
Vasco Almeida258e7792016-12-14 11:54:25 -01001704 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
Jeff King18bc7612008-02-13 05:50:51 -05001705 system(qw(git diff -p --cached), $reference, '--',
1706 map { $_->{VALUE} } @them);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001707}
1708
1709sub quit_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -01001710 print __("Bye.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001711 exit(0);
1712}
1713
1714sub help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -01001715# TRANSLATORS: please do not translate the command names
1716# 'status', 'update', 'revert', etc.
1717 print colored $help_color, __ <<'EOF' ;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001718status - show paths with changes
1719update - add working tree state to the staged set of changes
1720revert - revert staged set of changes back to the HEAD version
1721patch - pick hunks and update selectively
Ralf Thielowe519ecc2017-02-22 19:46:27 +01001722diff - view diff between HEAD and index
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001723add untracked - add contents of untracked files to the staged set of changes
1724EOF
1725}
1726
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001727sub process_args {
1728 return unless @ARGV;
1729 my $arg = shift @ARGV;
Thomas Rastd002ef42009-08-15 13:48:31 +02001730 if ($arg =~ /--patch(?:=(.*))?/) {
1731 if (defined $1) {
1732 if ($1 eq 'reset') {
1733 $patch_mode = 'reset_head';
1734 $patch_mode_revision = 'HEAD';
Vasco Almeida258e7792016-12-14 11:54:25 -01001735 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001736 if ($arg ne '--') {
1737 $patch_mode_revision = $arg;
1738 $patch_mode = ($arg eq 'HEAD' ?
1739 'reset_head' : 'reset_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001740 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001741 }
Thomas Rast4f353652009-08-15 13:48:30 +02001742 } elsif ($1 eq 'checkout') {
Vasco Almeida258e7792016-12-14 11:54:25 -01001743 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001744 if ($arg eq '--') {
1745 $patch_mode = 'checkout_index';
1746 } else {
1747 $patch_mode_revision = $arg;
1748 $patch_mode = ($arg eq 'HEAD' ?
1749 'checkout_head' : 'checkout_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001750 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001751 }
Thomas Rastdda1f2a2009-08-13 14:29:44 +02001752 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1753 $patch_mode = $1;
Vasco Almeida258e7792016-12-14 11:54:25 -01001754 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001755 } else {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001756 die sprintf(__("unknown --patch mode: %s"), $1);
Thomas Rastd002ef42009-08-15 13:48:31 +02001757 }
1758 } else {
1759 $patch_mode = 'stage';
Vasco Almeida258e7792016-12-14 11:54:25 -01001760 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001761 }
Vasco Almeida13c58c12016-12-14 11:54:27 -01001762 die sprintf(__("invalid argument %s, expecting --"),
1763 $arg) unless $arg eq "--";
Thomas Rastd002ef42009-08-15 13:48:31 +02001764 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Jeff Kingc852bd52017-03-02 04:48:22 -05001765 $patch_mode_only = 1;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001766 }
1767 elsif ($arg ne "--") {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001768 die sprintf(__("invalid argument %s, expecting --"), $arg);
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001769 }
1770}
1771
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001772sub main_loop {
1773 my @cmd = ([ 'status', \&status_cmd, ],
1774 [ 'update', \&update_cmd, ],
1775 [ 'revert', \&revert_cmd, ],
1776 [ 'add untracked', \&add_untracked_cmd, ],
1777 [ 'patch', \&patch_update_cmd, ],
1778 [ 'diff', \&diff_cmd, ],
1779 [ 'quit', \&quit_cmd, ],
1780 [ 'help', \&help_cmd, ],
1781 );
1782 while (1) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001783 my ($it) = list_and_choose({ PROMPT => __('What now'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001784 SINGLETON => 1,
1785 LIST_FLAT => 4,
Vasco Almeida258e7792016-12-14 11:54:25 -01001786 HEADER => __('*** Commands ***'),
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +02001787 ON_EOF => \&quit_cmd,
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001788 IMMEDIATE => 1 }, @cmd);
1789 if ($it) {
1790 eval {
1791 $it->[1]->();
1792 };
1793 if ($@) {
1794 print "$@";
1795 }
1796 }
1797 }
1798}
1799
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001800process_args();
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001801refresh();
Jeff Kingc852bd52017-03-02 04:48:22 -05001802if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001803 patch_update_cmd();
1804}
1805else {
1806 status_cmd();
1807 main_loop();
1808}