blob: 20eb81cc92f947d872b31a179d43d97772ff25e4 [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
brian m. carlson23ec4c52018-05-02 00:26:09 +0000208{
209 my $empty_tree;
210 sub get_empty_tree {
211 return $empty_tree if defined $empty_tree;
212
213 $empty_tree = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
214 chomp $empty_tree;
215 return $empty_tree;
216 }
Jeff King18bc7612008-02-13 05:50:51 -0500217}
218
Jeff King954312a2013-10-25 02:52:30 -0400219sub get_diff_reference {
220 my $ref = shift;
221 if (defined $ref and $ref ne 'HEAD') {
222 return $ref;
223 } elsif (is_initial_commit()) {
224 return get_empty_tree();
225 } else {
226 return 'HEAD';
227 }
228}
229
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800230# Returns list of hashes, contents of each of which are:
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800231# VALUE: pathname
232# BINARY: is a binary path
233# INDEX: is index different from HEAD?
234# FILE: is file different from index?
235# INDEX_ADDDEL: is it add/delete between HEAD and index?
236# FILE_ADDDEL: is it add/delete between index and file?
Jeff King4066bd62012-04-05 08:30:08 -0400237# UNMERGED: is the path unmerged
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800238
239sub list_modified {
240 my ($only) = @_;
241 my (%data, @return);
242 my ($add, $del, $adddel, $file);
243
Jeff King954312a2013-10-25 02:52:30 -0400244 my $reference = get_diff_reference($patch_mode_revision);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800245 for (run_cmd_pipe(qw(git diff-index --cached
Jeff King18bc7612008-02-13 05:50:51 -0500246 --numstat --summary), $reference,
Jeff King7288e122017-03-14 12:30:24 -0400247 '--', @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800248 if (($add, $del, $file) =
249 /^([-\d]+) ([-\d]+) (.*)/) {
250 my ($change, $bin);
Junio C Hamano8851f482009-02-16 22:43:43 -0800251 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800252 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100253 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800254 $bin = 1;
255 }
256 else {
257 $change = "+$add/-$del";
258 }
259 $data{$file} = {
260 INDEX => $change,
261 BINARY => $bin,
Vasco Almeida55aa0442016-12-14 11:54:34 -0100262 FILE => __('nothing'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800263 }
264 }
265 elsif (($adddel, $file) =
266 /^ (create|delete) mode [0-7]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800267 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800268 $data{$file}{INDEX_ADDDEL} = $adddel;
269 }
270 }
271
Nguyễn Thái Ngọc Duy12434ef2018-01-13 19:10:38 +0700272 for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800273 if (($add, $del, $file) =
274 /^([-\d]+) ([-\d]+) (.*)/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800275 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800276 my ($change, $bin);
277 if ($add eq '-' && $del eq '-') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100278 $change = __('binary');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800279 $bin = 1;
280 }
281 else {
282 $change = "+$add/-$del";
283 }
284 $data{$file}{FILE} = $change;
285 if ($bin) {
286 $data{$file}{BINARY} = 1;
287 }
288 }
289 elsif (($adddel, $file) =
290 /^ (create|delete) mode [0-7]+ (.*)$/) {
Junio C Hamano8851f482009-02-16 22:43:43 -0800291 $file = unquote_path($file);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800292 $data{$file}{FILE_ADDDEL} = $adddel;
293 }
Jeff King4066bd62012-04-05 08:30:08 -0400294 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
295 $file = unquote_path($2);
296 if (!exists $data{$file}) {
297 $data{$file} = +{
Vasco Almeida55aa0442016-12-14 11:54:34 -0100298 INDEX => __('unchanged'),
Jeff King4066bd62012-04-05 08:30:08 -0400299 BINARY => 0,
300 };
301 }
302 if ($1 eq 'U') {
303 $data{$file}{UNMERGED} = 1;
304 }
305 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800306 }
307
308 for (sort keys %data) {
309 my $it = $data{$_};
310
311 if ($only) {
312 if ($only eq 'index-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100313 next if ($it->{INDEX} eq __('unchanged'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800314 }
315 if ($only eq 'file-only') {
Vasco Almeida55aa0442016-12-14 11:54:34 -0100316 next if ($it->{FILE} eq __('nothing'));
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800317 }
318 }
319 push @return, +{
320 VALUE => $_,
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800321 %$it,
322 };
323 }
324 return @return;
325}
326
327sub find_unique {
328 my ($string, @stuff) = @_;
329 my $found = undef;
330 for (my $i = 0; $i < @stuff; $i++) {
331 my $it = $stuff[$i];
332 my $hit = undef;
333 if (ref $it) {
334 if ((ref $it) eq 'ARRAY') {
335 $it = $it->[0];
336 }
337 else {
338 $it = $it->{VALUE};
339 }
340 }
341 eval {
342 if ($it =~ /^$string/) {
343 $hit = 1;
344 };
345 };
346 if (defined $hit && defined $found) {
347 return undef;
348 }
349 if ($hit) {
350 $found = $i + 1;
351 }
352 }
353 return $found;
354}
355
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100356# inserts string into trie and updates count for each character
357sub update_trie {
358 my ($trie, $string) = @_;
359 foreach (split //, $string) {
360 $trie = $trie->{$_} ||= {COUNT => 0};
361 $trie->{COUNT}++;
362 }
363}
364
365# returns an array of tuples (prefix, remainder)
366sub find_unique_prefixes {
367 my @stuff = @_;
368 my @return = ();
369
370 # any single prefix exceeding the soft limit is omitted
371 # if any prefix exceeds the hard limit all are omitted
372 # 0 indicates no limit
373 my $soft_limit = 0;
374 my $hard_limit = 3;
375
376 # build a trie modelling all possible options
377 my %trie;
378 foreach my $print (@stuff) {
379 if ((ref $print) eq 'ARRAY') {
380 $print = $print->[0];
381 }
Wincent Colaiuta63320982007-12-02 14:44:11 +0100382 elsif ((ref $print) eq 'HASH') {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100383 $print = $print->{VALUE};
384 }
385 update_trie(\%trie, $print);
386 push @return, $print;
387 }
388
389 # use the trie to find the unique prefixes
390 for (my $i = 0; $i < @return; $i++) {
391 my $ret = $return[$i];
392 my @letters = split //, $ret;
393 my %search = %trie;
394 my ($prefix, $remainder);
395 my $j;
396 for ($j = 0; $j < @letters; $j++) {
397 my $letter = $letters[$j];
398 if ($search{$letter}{COUNT} == 1) {
399 $prefix = substr $ret, 0, $j + 1;
400 $remainder = substr $ret, $j + 1;
401 last;
402 }
403 else {
404 my $prefix = substr $ret, 0, $j;
405 return ()
406 if ($hard_limit && $j + 1 > $hard_limit);
407 }
408 %search = %{$search{$letter}};
409 }
Junio C Hamano8851f482009-02-16 22:43:43 -0800410 if (ord($letters[0]) > 127 ||
411 ($soft_limit && $j + 1 > $soft_limit)) {
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100412 $prefix = undef;
413 $remainder = $ret;
414 }
415 $return[$i] = [$prefix, $remainder];
416 }
417 return @return;
418}
419
Wincent Colaiuta63320982007-12-02 14:44:11 +0100420# filters out prefixes which have special meaning to list_and_choose()
421sub is_valid_prefix {
422 my $prefix = shift;
423 return (defined $prefix) &&
424 !($prefix =~ /[\s,]/) && # separators
425 !($prefix =~ /^-/) && # deselection
426 !($prefix =~ /^\d+/) && # selection
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100427 ($prefix ne '*') && # "all" wildcard
428 ($prefix ne '?'); # prompt help
Wincent Colaiuta63320982007-12-02 14:44:11 +0100429}
430
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100431# given a prefix/remainder tuple return a string with the prefix highlighted
432# for now use square brackets; later might use ANSI colors (underline, bold)
433sub highlight_prefix {
434 my $prefix = shift;
435 my $remainder = shift;
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800436
437 if (!defined $prefix) {
438 return $remainder;
439 }
440
441 if (!is_valid_prefix($prefix)) {
442 return "$prefix$remainder";
443 }
444
Jeff Kingf87e3102008-01-04 03:35:21 -0500445 if (!$menu_use_color) {
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800446 return "[$prefix]$remainder";
447 }
448
449 return "$prompt_color$prefix$normal_color$remainder";
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100450}
451
Thomas Rasta3019732009-02-05 09:28:27 +0100452sub error_msg {
453 print STDERR colored $error_color, @_;
454}
455
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800456sub list_and_choose {
457 my ($opts, @stuff) = @_;
458 my (@chosen, @return);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600459 if (!@stuff) {
460 return @return;
461 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800462 my $i;
Wincent Colaiuta14cb5032007-11-29 13:00:38 +0100463 my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800464
465 TOPLOOP:
466 while (1) {
467 my $last_lf = 0;
468
469 if ($opts->{HEADER}) {
470 if (!$opts->{LIST_FLAT}) {
471 print " ";
472 }
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800473 print colored $header_color, "$opts->{HEADER}\n";
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800474 }
475 for ($i = 0; $i < @stuff; $i++) {
476 my $chosen = $chosen[$i] ? '*' : ' ';
477 my $print = $stuff[$i];
Wincent Colaiuta63320982007-12-02 14:44:11 +0100478 my $ref = ref $print;
479 my $highlighted = highlight_prefix(@{$prefixes[$i]})
480 if @prefixes;
481 if ($ref eq 'ARRAY') {
482 $print = $highlighted || $print->[0];
483 }
484 elsif ($ref eq 'HASH') {
485 my $value = $highlighted || $print->{VALUE};
486 $print = sprintf($status_fmt,
487 $print->{INDEX},
488 $print->{FILE},
489 $value);
490 }
491 else {
492 $print = $highlighted || $print;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800493 }
494 printf("%s%2d: %s", $chosen, $i+1, $print);
495 if (($opts->{LIST_FLAT}) &&
496 (($i + 1) % ($opts->{LIST_FLAT}))) {
497 print "\t";
498 $last_lf = 0;
499 }
500 else {
501 print "\n";
502 $last_lf = 1;
503 }
504 }
505 if (!$last_lf) {
506 print "\n";
507 }
508
509 return if ($opts->{LIST_ONLY});
510
Junio C Hamanob4c61ed2007-12-05 00:50:23 -0800511 print colored $prompt_color, $opts->{PROMPT};
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800512 if ($opts->{SINGLETON}) {
513 print "> ";
514 }
515 else {
516 print ">> ";
517 }
518 my $line = <STDIN>;
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +0200519 if (!$line) {
520 print "\n";
521 $opts->{ON_EOF}->() if $opts->{ON_EOF};
522 last;
523 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800524 chomp $line;
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200525 last if $line eq '';
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100526 if ($line eq '?') {
527 $opts->{SINGLETON} ?
528 singleton_prompt_help_cmd() :
529 prompt_help_cmd();
530 next TOPLOOP;
531 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800532 for my $choice (split(/[\s,]+/, $line)) {
533 my $choose = 1;
534 my ($bottom, $top);
535
536 # Input that begins with '-'; unchoose
537 if ($choice =~ s/^-//) {
538 $choose = 0;
539 }
Ciaran McCreesh1e5aaa62008-07-14 19:29:37 +0100540 # A range can be specified like 5-7 or 5-.
541 if ($choice =~ /^(\d+)-(\d*)$/) {
542 ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800543 }
544 elsif ($choice =~ /^\d+$/) {
545 $bottom = $top = $choice;
546 }
547 elsif ($choice eq '*') {
548 $bottom = 1;
549 $top = 1 + @stuff;
550 }
551 else {
552 $bottom = $top = find_unique($choice, @stuff);
553 if (!defined $bottom) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100554 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800555 next TOPLOOP;
556 }
557 }
558 if ($opts->{SINGLETON} && $bottom != $top) {
Vasco Almeida13c58c12016-12-14 11:54:27 -0100559 error_msg sprintf(__("Huh (%s)?\n"), $choice);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800560 next TOPLOOP;
561 }
562 for ($i = $bottom-1; $i <= $top-1; $i++) {
Jean-Luc Herren6a6eb3d2007-09-26 16:05:01 +0200563 next if (@stuff <= $i || $i < 0);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800564 $chosen[$i] = $choose;
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800565 }
566 }
Junio C Hamano12db3342007-11-22 01:47:13 -0800567 last if ($opts->{IMMEDIATE} || $line eq '*');
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800568 }
569 for ($i = 0; $i < @stuff; $i++) {
570 if ($chosen[$i]) {
571 push @return, $stuff[$i];
572 }
573 }
574 return @return;
575}
576
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100577sub singleton_prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100578 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100579Prompt help:
5801 - select a numbered item
581foo - select item based on unique prefix
582 - (empty) select nothing
583EOF
584}
585
586sub prompt_help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -0100587 print colored $help_color, __ <<'EOF' ;
Wincent Colaiuta7e018be2007-12-03 09:09:43 +0100588Prompt help:
5891 - select a single item
5903-5 - select a range of items
5912-3,6-9 - select multiple ranges
592foo - select item based on unique prefix
593-... - unselect specified items
594* - choose all items
595 - (empty) finish selecting
596EOF
597}
598
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800599sub status_cmd {
600 list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
601 list_modified());
602 print "\n";
603}
604
605sub say_n_paths {
606 my $did = shift @_;
607 my $cnt = scalar @_;
Vasco Almeidac4a85c32016-12-14 11:54:29 -0100608 if ($did eq 'added') {
609 printf(__n("added %d path\n", "added %d paths\n",
610 $cnt), $cnt);
611 } elsif ($did eq 'updated') {
612 printf(__n("updated %d path\n", "updated %d paths\n",
613 $cnt), $cnt);
614 } elsif ($did eq 'reverted') {
615 printf(__n("reverted %d path\n", "reverted %d paths\n",
616 $cnt), $cnt);
617 } else {
618 printf(__n("touched %d path\n", "touched %d paths\n",
619 $cnt), $cnt);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800620 }
621}
622
623sub update_cmd {
624 my @mods = list_modified('file-only');
625 return if (!@mods);
626
Vasco Almeida258e7792016-12-14 11:54:25 -0100627 my @update = list_and_choose({ PROMPT => __('Update'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800628 HEADER => $status_head, },
629 @mods);
630 if (@update) {
Junio C Hamanoa4f71122007-02-07 10:56:38 -0800631 system(qw(git update-index --add --remove --),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800632 map { $_->{VALUE} } @update);
633 say_n_paths('updated', @update);
634 }
635 print "\n";
636}
637
638sub revert_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100639 my @update = list_and_choose({ PROMPT => __('Revert'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800640 HEADER => $status_head, },
641 list_modified());
642 if (@update) {
Jeff King18bc7612008-02-13 05:50:51 -0500643 if (is_initial_commit()) {
644 system(qw(git rm --cached),
645 map { $_->{VALUE} } @update);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800646 }
Jeff King18bc7612008-02-13 05:50:51 -0500647 else {
648 my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
649 map { $_->{VALUE} } @update);
650 my $fh;
651 open $fh, '| git update-index --index-info'
652 or die;
653 for (@lines) {
654 print $fh $_;
655 }
656 close($fh);
657 for (@update) {
658 if ($_->{INDEX_ADDDEL} &&
659 $_->{INDEX_ADDDEL} eq 'create') {
660 system(qw(git update-index --force-remove --),
661 $_->{VALUE});
Vasco Almeida13c58c12016-12-14 11:54:27 -0100662 printf(__("note: %s is untracked now.\n"), $_->{VALUE});
Jeff King18bc7612008-02-13 05:50:51 -0500663 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800664 }
665 }
666 refresh();
667 say_n_paths('reverted', @update);
668 }
669 print "\n";
670}
671
672sub add_untracked_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -0100673 my @add = list_and_choose({ PROMPT => __('Add untracked') },
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800674 list_untracked());
675 if (@add) {
676 system(qw(git update-index --add --), @add);
677 say_n_paths('added', @add);
Alexander Kuleshova9c46412015-01-22 14:39:44 +0600678 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -0100679 print __("No untracked files.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800680 }
681 print "\n";
682}
683
Thomas Rast8f0bef62009-08-13 14:29:39 +0200684sub run_git_apply {
685 my $cmd = shift;
686 my $fh;
Phillip Wood3a8522f2018-03-05 10:56:30 +0000687 open $fh, '| git ' . $cmd . " --allow-overlap";
Thomas Rast8f0bef62009-08-13 14:29:39 +0200688 print $fh @_;
689 return close $fh;
690}
691
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800692sub parse_diff {
693 my ($path) = @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +0200694 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
John Keeping2cc0f532013-06-12 19:44:10 +0100695 if (defined $diff_algorithm) {
Junio C Hamanoe5c29092013-06-23 12:19:05 -0700696 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
John Keeping2cc0f532013-06-12 19:44:10 +0100697 }
Thomas Rastd002ef42009-08-15 13:48:31 +0200698 if (defined $patch_mode_revision) {
Jeff King954312a2013-10-25 02:52:30 -0400699 push @diff_cmd, get_diff_reference($patch_mode_revision);
Thomas Rastd002ef42009-08-15 13:48:31 +0200700 }
Thomas Rast8f0bef62009-08-13 14:29:39 +0200701 my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100702 my @colored = ();
703 if ($diff_use_color) {
Jeff King01143842016-02-27 00:37:06 -0500704 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
705 if (defined $diff_filter) {
706 # quotemeta is overkill, but sufficient for shell-quoting
707 my $diff = join(' ', map { quotemeta } @display_cmd);
708 @display_cmd = ("$diff | $diff_filter");
709 }
710
711 @colored = run_cmd_pipe(@display_cmd);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100712 }
Jeff King03925132009-04-16 03:14:15 -0400713 my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800714
Jeff King42f7d452018-03-03 00:58:49 -0500715 if (@colored && @colored != @diff) {
716 print STDERR
717 "fatal: mismatched output from interactive.diffFilter\n",
718 "hint: Your filter must maintain a one-to-one correspondence\n",
719 "hint: between its input and output lines.\n";
720 exit 1;
721 }
722
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100723 for (my $i = 0; $i < @diff; $i++) {
724 if ($diff[$i] =~ /^@@ /) {
Jeff King03925132009-04-16 03:14:15 -0400725 push @hunk, { TEXT => [], DISPLAY => [],
726 TYPE => 'hunk' };
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800727 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100728 push @{$hunk[-1]{TEXT}}, $diff[$i];
729 push @{$hunk[-1]{DISPLAY}},
Jeff King01143842016-02-27 00:37:06 -0500730 (@colored ? $colored[$i] : $diff[$i]);
Junio C Hamano5cde71d2006-12-10 20:55:50 -0800731 }
732 return @hunk;
733}
734
Jeff Kingb717a622008-03-27 03:30:43 -0400735sub parse_diff_header {
736 my $src = shift;
737
Jeff King03925132009-04-16 03:14:15 -0400738 my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
739 my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
Jeff King24ab81a2009-10-27 20:52:57 -0400740 my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
Jeff Kingb717a622008-03-27 03:30:43 -0400741
742 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
Jeff King24ab81a2009-10-27 20:52:57 -0400743 my $dest =
744 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
745 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
746 $head;
Jeff Kingb717a622008-03-27 03:30:43 -0400747 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
748 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
749 }
Jeff King24ab81a2009-10-27 20:52:57 -0400750 return ($head, $mode, $deletion);
Jeff Kingb717a622008-03-27 03:30:43 -0400751}
752
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800753sub hunk_splittable {
754 my ($text) = @_;
755
756 my @s = split_hunk($text);
757 return (1 < @s);
758}
759
760sub parse_hunk_header {
761 my ($line) = @_;
762 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
Jean-Luc Herren7288ed82007-10-09 21:29:26 +0200763 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
764 $o_cnt = 1 unless defined $o_cnt;
765 $n_cnt = 1 unless defined $n_cnt;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800766 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
767}
768
Phillip Wood492e60c2018-02-19 11:29:02 +0000769sub format_hunk_header {
770 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
771 return ("@@ -$o_ofs" .
772 (($o_cnt != 1) ? ",$o_cnt" : '') .
773 " +$n_ofs" .
774 (($n_cnt != 1) ? ",$n_cnt" : '') .
775 " @@\n");
776}
777
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800778sub split_hunk {
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100779 my ($text, $display) = @_;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800780 my @split = ();
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100781 if (!defined $display) {
782 $display = $text;
783 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800784 # If there are context lines in the middle of a hunk,
785 # it can be split, but we would need to take care of
786 # overlaps later.
787
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200788 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800789 my $hunk_start = 1;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800790
791 OUTER:
792 while (1) {
793 my $next_hunk_start = undef;
794 my $i = $hunk_start - 1;
795 my $this = +{
796 TEXT => [],
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100797 DISPLAY => [],
Jeff King03925132009-04-16 03:14:15 -0400798 TYPE => 'hunk',
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800799 OLD => $o_ofs,
800 NEW => $n_ofs,
801 OCNT => 0,
802 NCNT => 0,
803 ADDDEL => 0,
804 POSTCTX => 0,
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100805 USE => undef,
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800806 };
807
808 while (++$i < @$text) {
809 my $line = $text->[$i];
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100810 my $display = $display->[$i];
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000811 if ($line =~ /^\\/) {
812 push @{$this->{TEXT}}, $line;
813 push @{$this->{DISPLAY}}, $display;
814 next;
815 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800816 if ($line =~ /^ /) {
817 if ($this->{ADDDEL} &&
818 !defined $next_hunk_start) {
819 # We have seen leading context and
820 # adds/dels and then here is another
821 # context, which is trailing for this
822 # split hunk and leading for the next
823 # one.
824 $next_hunk_start = $i;
825 }
826 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100827 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800828 $this->{OCNT}++;
829 $this->{NCNT}++;
830 if (defined $next_hunk_start) {
831 $this->{POSTCTX}++;
832 }
833 next;
834 }
835
836 # add/del
837 if (defined $next_hunk_start) {
838 # We are done with the current hunk and
839 # this is the first real change for the
840 # next split one.
841 $hunk_start = $next_hunk_start;
842 $o_ofs = $this->{OLD} + $this->{OCNT};
843 $n_ofs = $this->{NEW} + $this->{NCNT};
844 $o_ofs -= $this->{POSTCTX};
845 $n_ofs -= $this->{POSTCTX};
846 push @split, $this;
847 redo OUTER;
848 }
849 push @{$this->{TEXT}}, $line;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100850 push @{$this->{DISPLAY}}, $display;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800851 $this->{ADDDEL}++;
852 if ($line =~ /^-/) {
853 $this->{OCNT}++;
854 }
855 else {
856 $this->{NCNT}++;
857 }
858 }
859
860 push @split, $this;
861 last;
862 }
863
864 for my $hunk (@split) {
865 $o_ofs = $hunk->{OLD};
866 $n_ofs = $hunk->{NEW};
Jean-Luc Herren7b40a452007-10-09 21:34:17 +0200867 my $o_cnt = $hunk->{OCNT};
868 my $n_cnt = $hunk->{NCNT};
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800869
Phillip Wood492e60c2018-02-19 11:29:02 +0000870 my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100871 my $display_head = $head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800872 unshift @{$hunk->{TEXT}}, $head;
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100873 if ($diff_use_color) {
874 $display_head = colored($fraginfo_color, $head);
875 }
876 unshift @{$hunk->{DISPLAY}}, $display_head;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800877 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +0100878 return @split;
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800879}
880
Junio C Hamano7a26e652009-05-16 10:48:23 -0700881sub find_last_o_ctx {
882 my ($it) = @_;
883 my $text = $it->{TEXT};
884 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
885 my $i = @{$text};
886 my $last_o_ctx = $o_ofs + $o_cnt;
887 while (0 < --$i) {
888 my $line = $text->[$i];
889 if ($line =~ /^ /) {
890 $last_o_ctx--;
891 next;
892 }
893 last;
894 }
895 return $last_o_ctx;
896}
897
898sub merge_hunk {
899 my ($prev, $this) = @_;
900 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
901 parse_hunk_header($prev->{TEXT}[0]);
902 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
903 parse_hunk_header($this->{TEXT}[0]);
904
905 my (@line, $i, $ofs, $o_cnt, $n_cnt);
906 $ofs = $o0_ofs;
907 $o_cnt = $n_cnt = 0;
908 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
909 my $line = $prev->{TEXT}[$i];
910 if ($line =~ /^\+/) {
911 $n_cnt++;
912 push @line, $line;
913 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000914 } elsif ($line =~ /^\\/) {
915 push @line, $line;
916 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700917 }
918
919 last if ($o1_ofs <= $ofs);
920
921 $o_cnt++;
922 $ofs++;
923 if ($line =~ /^ /) {
924 $n_cnt++;
925 }
926 push @line, $line;
927 }
928
929 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
930 my $line = $this->{TEXT}[$i];
931 if ($line =~ /^\+/) {
932 $n_cnt++;
933 push @line, $line;
934 next;
Phillip Woodb3e0fcf2018-03-05 10:56:29 +0000935 } elsif ($line =~ /^\\/) {
936 push @line, $line;
937 next;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700938 }
939 $ofs++;
940 $o_cnt++;
941 if ($line =~ /^ /) {
942 $n_cnt++;
943 }
944 push @line, $line;
945 }
Phillip Wood492e60c2018-02-19 11:29:02 +0000946 my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
Junio C Hamano7a26e652009-05-16 10:48:23 -0700947 @{$prev->{TEXT}} = ($head, @line);
948}
949
950sub coalesce_overlapping_hunks {
951 my (@in) = @_;
952 my @out = ();
953
954 my ($last_o_ctx, $last_was_dirty);
Phillip Woodfecc6f32018-03-01 10:51:00 +0000955 my $ofs_delta = 0;
Junio C Hamano7a26e652009-05-16 10:48:23 -0700956
Phillip Woodfecc6f32018-03-01 10:51:00 +0000957 for (@in) {
Thomas Rast3d792162009-08-15 15:56:39 +0200958 if ($_->{TYPE} ne 'hunk') {
959 push @out, $_;
960 next;
961 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700962 my $text = $_->{TEXT};
Phillip Woodfecc6f32018-03-01 10:51:00 +0000963 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
964 parse_hunk_header($text->[0]);
965 unless ($_->{USE}) {
966 $ofs_delta += $o_cnt - $n_cnt;
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000967 # If this hunk has been edited then subtract
968 # the delta that is due to the edit.
969 if ($_->{OFS_DELTA}) {
970 $ofs_delta -= $_->{OFS_DELTA};
971 }
Phillip Woodfecc6f32018-03-01 10:51:00 +0000972 next;
973 }
974 if ($ofs_delta) {
975 $n_ofs += $ofs_delta;
976 $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
977 $n_ofs, $n_cnt);
978 }
Phillip Wood2b8ea7f2018-03-05 10:56:28 +0000979 # If this hunk was edited then adjust the offset delta
980 # to reflect the edit.
981 if ($_->{OFS_DELTA}) {
982 $ofs_delta += $_->{OFS_DELTA};
983 }
Junio C Hamano7a26e652009-05-16 10:48:23 -0700984 if (defined $last_o_ctx &&
985 $o_ofs <= $last_o_ctx &&
986 !$_->{DIRTY} &&
987 !$last_was_dirty) {
988 merge_hunk($out[-1], $_);
989 }
990 else {
991 push @out, $_;
992 }
993 $last_o_ctx = find_last_o_ctx($out[-1]);
994 $last_was_dirty = $_->{DIRTY};
995 }
996 return @out;
997}
Junio C Hamano835b2ae2006-12-11 17:09:26 -0800998
Jeff Kinge1327ed2010-02-22 20:05:44 -0500999sub reassemble_patch {
1000 my $head = shift;
1001 my @patch;
1002
1003 # Include everything in the header except the beginning of the diff.
1004 push @patch, (grep { !/^[-+]{3}/ } @$head);
1005
1006 # Then include any headers from the hunk lines, which must
1007 # come before any actual hunk.
1008 while (@_ && $_[0] !~ /^@/) {
1009 push @patch, shift;
1010 }
1011
1012 # Then begin the diff.
1013 push @patch, grep { /^[-+]{3}/ } @$head;
1014
1015 # And then the actual hunks.
1016 push @patch, @_;
1017
1018 return @patch;
1019}
1020
Thomas Rastac083c42008-07-03 00:00:00 +02001021sub color_diff {
1022 return map {
1023 colored((/^@/ ? $fraginfo_color :
1024 /^\+/ ? $diff_new_color :
1025 /^-/ ? $diff_old_color :
1026 $diff_plain_color),
1027 $_);
1028 } @_;
1029}
1030
Vasco Almeidac9d96162016-12-14 11:54:32 -01001031my %edit_hunk_manually_modes = (
1032 stage => N__(
1033"If the patch applies cleanly, the edited hunk will immediately be
1034marked for staging."),
1035 stash => N__(
1036"If the patch applies cleanly, the edited hunk will immediately be
1037marked for stashing."),
1038 reset_head => N__(
1039"If the patch applies cleanly, the edited hunk will immediately be
1040marked for unstaging."),
1041 reset_nothead => N__(
1042"If the patch applies cleanly, the edited hunk will immediately be
1043marked for applying."),
1044 checkout_index => N__(
1045"If the patch applies cleanly, the edited hunk will immediately be
Ralf Thielow0301f1f2017-04-13 18:41:12 +02001046marked for discarding."),
Vasco Almeidac9d96162016-12-14 11:54:32 -01001047 checkout_head => N__(
1048"If the patch applies cleanly, the edited hunk will immediately be
1049marked for discarding."),
1050 checkout_nothead => N__(
1051"If the patch applies cleanly, the edited hunk will immediately be
1052marked for applying."),
1053);
1054
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001055sub recount_edited_hunk {
1056 local $_;
1057 my ($oldtext, $newtext) = @_;
1058 my ($o_cnt, $n_cnt) = (0, 0);
1059 for (@{$newtext}[1..$#{$newtext}]) {
1060 my $mode = substr($_, 0, 1);
1061 if ($mode eq '-') {
1062 $o_cnt++;
1063 } elsif ($mode eq '+') {
1064 $n_cnt++;
Phillip Woodf4d35a62018-06-11 10:46:02 +01001065 } elsif ($mode eq ' ' or $mode eq "\n") {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001066 $o_cnt++;
1067 $n_cnt++;
1068 }
1069 }
1070 my ($o_ofs, undef, $n_ofs, undef) =
1071 parse_hunk_header($newtext->[0]);
1072 $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1073 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1074 parse_hunk_header($oldtext->[0]);
1075 # Return the change in the number of lines inserted by this hunk
1076 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1077}
1078
Thomas Rastac083c42008-07-03 00:00:00 +02001079sub edit_hunk_manually {
1080 my ($oldtext) = @_;
1081
1082 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1083 my $fh;
1084 open $fh, '>', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001085 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
Vasco Almeidac9d96162016-12-14 11:54:32 -01001086 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
Thomas Rastac083c42008-07-03 00:00:00 +02001087 print $fh @$oldtext;
Jonathan "Duke" Leto7b8c7052010-10-27 17:49:20 -07001088 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1089 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
Vasco Almeidac9d96162016-12-14 11:54:32 -01001090 my $comment_line_char = Git::get_comment_line_char;
1091 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1092---
1093To remove '%s' lines, make them ' ' lines (context).
1094To remove '%s' lines, delete them.
1095Lines starting with %s will be removed.
Thomas Rastac083c42008-07-03 00:00:00 +02001096EOF
Vasco Almeidac9d96162016-12-14 11:54:32 -01001097__($edit_hunk_manually_modes{$patch_mode}),
1098# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1099__ <<EOF2 ;
1100If it does not apply cleanly, you will be given an opportunity to
1101edit again. If all lines of the hunk are removed, then the edit is
1102aborted and the hunk is left unchanged.
1103EOF2
Thomas Rastac083c42008-07-03 00:00:00 +02001104 close $fh;
1105
Jonathan Niederb4479f02009-10-30 20:42:34 -05001106 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
Thomas Rastac083c42008-07-03 00:00:00 +02001107 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1108
Deskin Miller1d398a02009-02-12 00:19:41 -05001109 if ($? != 0) {
1110 return undef;
1111 }
1112
Thomas Rastac083c42008-07-03 00:00:00 +02001113 open $fh, '<', $hunkfile
Vasco Almeida13c58c12016-12-14 11:54:27 -01001114 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
Jeff Kingd85d7ec2017-06-21 15:28:59 -04001115 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
Thomas Rastac083c42008-07-03 00:00:00 +02001116 close $fh;
1117 unlink $hunkfile;
1118
1119 # Abort if nothing remains
1120 if (!grep { /\S/ } @newtext) {
1121 return undef;
1122 }
1123
1124 # Reinsert the first hunk header if the user accidentally deleted it
1125 if ($newtext[0] !~ /^@/) {
1126 unshift @newtext, $oldtext->[0];
1127 }
1128 return \@newtext;
1129}
1130
1131sub diff_applies {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001132 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
Thomas Rast8f0bef62009-08-13 14:29:39 +02001133 map { @{$_->{TEXT}} } @_);
Thomas Rastac083c42008-07-03 00:00:00 +02001134}
1135
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001136sub _restore_terminal_and_die {
1137 ReadMode 'restore';
1138 print "\n";
1139 exit 1;
1140}
1141
1142sub prompt_single_character {
1143 if ($use_readkey) {
1144 local $SIG{TERM} = \&_restore_terminal_and_die;
1145 local $SIG{INT} = \&_restore_terminal_and_die;
1146 ReadMode 'cbreak';
1147 my $key = ReadKey 0;
1148 ReadMode 'restore';
Thomas Rastb5cc0032011-05-17 17:19:08 +02001149 if ($use_termcap and $key eq "\e") {
1150 while (!defined $term_escapes{$key}) {
1151 my $next = ReadKey 0.5;
1152 last if (!defined $next);
1153 $key .= $next;
1154 }
1155 $key =~ s/\e/^[/;
1156 }
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001157 print "$key" if defined $key;
1158 print "\n";
1159 return $key;
1160 } else {
1161 return <STDIN>;
1162 }
1163}
1164
Thomas Rastac083c42008-07-03 00:00:00 +02001165sub prompt_yesno {
1166 my ($prompt) = @_;
1167 while (1) {
1168 print colored $prompt_color, $prompt;
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001169 my $line = prompt_single_character;
Jeff Kingd5addcf2017-06-21 15:26:36 -04001170 return undef unless defined $line;
Thomas Rastac083c42008-07-03 00:00:00 +02001171 return 0 if $line =~ /^n/i;
1172 return 1 if $line =~ /^y/i;
1173 }
1174}
1175
1176sub edit_hunk_loop {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001177 my ($head, $hunks, $ix) = @_;
1178 my $hunk = $hunks->[$ix];
1179 my $text = $hunk->{TEXT};
Thomas Rastac083c42008-07-03 00:00:00 +02001180
1181 while (1) {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001182 my $newtext = edit_hunk_manually($text);
1183 if (!defined $newtext) {
Thomas Rastac083c42008-07-03 00:00:00 +02001184 return undef;
1185 }
Jeff King03925132009-04-16 03:14:15 -04001186 my $newhunk = {
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001187 TEXT => $newtext,
1188 TYPE => $hunk->{TYPE},
Junio C Hamano7a26e652009-05-16 10:48:23 -07001189 USE => 1,
1190 DIRTY => 1,
Jeff King03925132009-04-16 03:14:15 -04001191 };
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001192 $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1193 # If this hunk has already been edited then add the
1194 # offset delta of the previous edit to get the real
1195 # delta from the original unedited hunk.
1196 $hunk->{OFS_DELTA} and
1197 $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
Thomas Rastac083c42008-07-03 00:00:00 +02001198 if (diff_applies($head,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001199 @{$hunks}[0..$ix-1],
Thomas Rastac083c42008-07-03 00:00:00 +02001200 $newhunk,
Phillip Wood2b8ea7f2018-03-05 10:56:28 +00001201 @{$hunks}[$ix+1..$#{$hunks}])) {
1202 $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
Thomas Rastac083c42008-07-03 00:00:00 +02001203 return $newhunk;
1204 }
1205 else {
1206 prompt_yesno(
Vasco Almeida258e7792016-12-14 11:54:25 -01001207 # TRANSLATORS: do not translate [y/n]
1208 # The program will only accept that input
1209 # at this point.
1210 # Consider translating (saying "no" discards!) as
1211 # (saying "n" for "no" discards!) if the translation
1212 # of the word "no" does not start with n.
1213 __('Your edited hunk does not apply. Edit again '
1214 . '(saying "no" discards!) [y/n]? ')
Thomas Rastac083c42008-07-03 00:00:00 +02001215 ) or return undef;
1216 }
1217 }
1218}
1219
Vasco Almeida186b52c2016-12-14 11:54:31 -01001220my %help_patch_modes = (
1221 stage => N__(
1222"y - stage this hunk
1223n - do not stage this hunk
1224q - quit; do not stage this hunk or any of the remaining ones
1225a - stage this hunk and all later hunks in the file
1226d - do not stage this hunk or any of the later hunks in the file"),
1227 stash => N__(
1228"y - stash this hunk
1229n - do not stash this hunk
1230q - quit; do not stash this hunk or any of the remaining ones
1231a - stash this hunk and all later hunks in the file
1232d - do not stash this hunk or any of the later hunks in the file"),
1233 reset_head => N__(
1234"y - unstage this hunk
1235n - do not unstage this hunk
1236q - quit; do not unstage this hunk or any of the remaining ones
1237a - unstage this hunk and all later hunks in the file
1238d - do not unstage this hunk or any of the later hunks in the file"),
1239 reset_nothead => N__(
1240"y - apply this hunk to index
1241n - do not apply this hunk to index
1242q - quit; do not apply this hunk or any of the remaining ones
1243a - apply this hunk and all later hunks in the file
1244d - do not apply this hunk or any of the later hunks in the file"),
1245 checkout_index => N__(
1246"y - discard this hunk from worktree
1247n - do not discard this hunk from worktree
1248q - quit; do not discard this hunk or any of the remaining ones
1249a - discard this hunk and all later hunks in the file
1250d - do not discard this hunk or any of the later hunks in the file"),
1251 checkout_head => N__(
1252"y - discard this hunk from index and worktree
1253n - do not discard this hunk from index and worktree
1254q - quit; do not discard this hunk or any of the remaining ones
1255a - discard this hunk and all later hunks in the file
1256d - do not discard this hunk or any of the later hunks in the file"),
1257 checkout_nothead => N__(
1258"y - apply this hunk to index and worktree
1259n - do not apply this hunk to index and worktree
1260q - quit; do not apply this hunk or any of the remaining ones
1261a - apply this hunk and all later hunks in the file
1262d - do not apply this hunk or any of the later hunks in the file"),
1263);
1264
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001265sub help_patch_cmd {
Phillip Wood01a69662018-02-13 10:32:39 +00001266 local $_;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001267 my $other = $_[0] . ",?";
Phillip Wood01a69662018-02-13 10:32:39 +00001268 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1269 map { "$_\n" } grep {
1270 my $c = quotemeta(substr($_, 0, 1));
1271 $other =~ /,$c/
1272 } split "\n", __ <<EOF ;
William Pursell070434d2008-12-04 10:22:40 +00001273g - select a hunk to go to
William Purselldd971cc2008-11-27 04:07:57 +00001274/ - search for a hunk matching the given regex
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001275j - leave this hunk undecided, see next undecided hunk
1276J - leave this hunk undecided, see next hunk
1277k - leave this hunk undecided, see previous undecided hunk
1278K - leave this hunk undecided, see previous hunk
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001279s - split the current hunk into smaller hunks
Thomas Rastac083c42008-07-03 00:00:00 +02001280e - manually edit the current hunk
Ralf Wildenhues280e50c2007-11-28 19:21:42 +01001281? - print help
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001282EOF
1283}
1284
Thomas Rast8f0bef62009-08-13 14:29:39 +02001285sub apply_patch {
1286 my $cmd = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001287 my $ret = run_git_apply $cmd, @_;
Thomas Rast8f0bef62009-08-13 14:29:39 +02001288 if (!$ret) {
1289 print STDERR @_;
1290 }
1291 return $ret;
1292}
1293
Thomas Rast4f353652009-08-15 13:48:30 +02001294sub apply_patch_for_checkout_commit {
1295 my $reverse = shift;
Junio C Hamano9dce8322011-04-06 14:12:34 -07001296 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1297 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001298
1299 if ($applies_worktree && $applies_index) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001300 run_git_apply 'apply '.$reverse.' --cached', @_;
1301 run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001302 return 1;
1303 } elsif (!$applies_index) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001304 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1305 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
Junio C Hamano9dce8322011-04-06 14:12:34 -07001306 return run_git_apply 'apply '.$reverse, @_;
Thomas Rast4f353652009-08-15 13:48:30 +02001307 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001308 print colored $error_color, __("Nothing was applied.\n");
Thomas Rast4f353652009-08-15 13:48:30 +02001309 return 0;
1310 }
1311 } else {
1312 print STDERR @_;
1313 return 0;
1314 }
1315}
1316
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001317sub patch_update_cmd {
Thomas Rast8f0bef62009-08-13 14:29:39 +02001318 my @all_mods = list_modified($patch_mode_flavour{FILTER});
Vasco Almeida13c58c12016-12-14 11:54:27 -01001319 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
Jeff King4066bd62012-04-05 08:30:08 -04001320 for grep { $_->{UNMERGED} } @all_mods;
1321 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1322
Thomas Rast9fe7a642008-10-26 20:37:06 +01001323 my @mods = grep { !($_->{BINARY}) } @all_mods;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001324 my @them;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001325
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001326 if (!@mods) {
Thomas Rast9fe7a642008-10-26 20:37:06 +01001327 if (@all_mods) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001328 print STDERR __("Only binary files changed.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001329 } else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001330 print STDERR __("No changes.\n");
Thomas Rast9fe7a642008-10-26 20:37:06 +01001331 }
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001332 return 0;
1333 }
Jeff Kingc852bd52017-03-02 04:48:22 -05001334 if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001335 @them = @mods;
1336 }
1337 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001338 @them = list_and_choose({ PROMPT => __('Patch update'),
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001339 HEADER => $status_head, },
1340 @mods);
1341 }
Junio C Hamano12db3342007-11-22 01:47:13 -08001342 for (@them) {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001343 return 0 if patch_update_file($_->{VALUE});
Junio C Hamano12db3342007-11-22 01:47:13 -08001344 }
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001345}
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001346
William Pursell3f6aff62008-12-04 10:00:24 +00001347# Generate a one line summary of a hunk.
1348sub summarize_hunk {
1349 my $rhunk = shift;
1350 my $summary = $rhunk->{TEXT}[0];
1351
1352 # Keep the line numbers, discard extra context.
1353 $summary =~ s/@@(.*?)@@.*/$1 /s;
1354 $summary .= " " x (20 - length $summary);
1355
1356 # Add some user context.
1357 for my $line (@{$rhunk->{TEXT}}) {
1358 if ($line =~ m/^[+-].*\w/) {
1359 $summary .= $line;
1360 last;
1361 }
1362 }
1363
1364 chomp $summary;
1365 return substr($summary, 0, 80) . "\n";
1366}
1367
1368
1369# Print a one-line summary of each hunk in the array ref in
Stefano Lattarini41ccfdd2013-04-12 00:36:10 +02001370# the first argument, starting with the index in the 2nd.
William Pursell3f6aff62008-12-04 10:00:24 +00001371sub display_hunks {
1372 my ($hunks, $i) = @_;
1373 my $ctr = 0;
1374 $i ||= 0;
1375 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1376 my $status = " ";
1377 if (defined $hunks->[$i]{USE}) {
1378 $status = $hunks->[$i]{USE} ? "+" : "-";
1379 }
1380 printf "%s%2d: %s",
1381 $status,
1382 $i + 1,
1383 summarize_hunk($hunks->[$i]);
1384 }
1385 return $i;
1386}
1387
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001388my %patch_update_prompt_modes = (
1389 stage => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001390 mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1391 deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1392 hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001393 },
1394 stash => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001395 mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1396 deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1397 hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001398 },
1399 reset_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001400 mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1401 deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1402 hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001403 },
1404 reset_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001405 mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1406 deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1407 hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001408 },
1409 checkout_index => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001410 mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1411 deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1412 hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001413 },
1414 checkout_head => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001415 mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1416 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1417 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001418 },
1419 checkout_nothead => {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001420 mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1421 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1422 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001423 },
1424);
1425
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001426sub patch_update_file {
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001427 my $quit = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001428 my ($ix, $num);
Wincent Colaiutaa7d9da62007-11-21 13:36:38 +01001429 my $path = shift;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001430 my ($head, @hunk) = parse_diff($path);
Jeff King24ab81a2009-10-27 20:52:57 -04001431 ($head, my $mode, my $deletion) = parse_diff_header($head);
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001432 for (@{$head->{DISPLAY}}) {
1433 print;
1434 }
Jeff Kingca724682008-03-27 03:32:25 -04001435
1436 if (@{$mode->{TEXT}}) {
Jeff King03925132009-04-16 03:14:15 -04001437 unshift @hunk, $mode;
Jeff Kingca724682008-03-27 03:32:25 -04001438 }
Jeff King8947fdd2009-12-08 02:49:35 -05001439 if (@{$deletion->{TEXT}}) {
1440 foreach my $hunk (@hunk) {
1441 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1442 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1443 }
Jeff King24ab81a2009-10-27 20:52:57 -04001444 @hunk = ($deletion);
1445 }
Jeff Kingca724682008-03-27 03:32:25 -04001446
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001447 $num = scalar @hunk;
1448 $ix = 0;
1449
1450 while (1) {
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001451 my ($prev, $next, $other, $undecided, $i);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001452 $other = '';
1453
1454 if ($num <= $ix) {
1455 $ix = 0;
1456 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001457 for ($i = 0; $i < $ix; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001458 if (!defined $hunk[$i]{USE}) {
1459 $prev = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001460 $other .= ',k';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001461 last;
1462 }
1463 }
1464 if ($ix) {
William Pursell57886bc2008-11-27 04:07:52 +00001465 $other .= ',K';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001466 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001467 for ($i = $ix + 1; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001468 if (!defined $hunk[$i]{USE}) {
1469 $next = 1;
William Pursell57886bc2008-11-27 04:07:52 +00001470 $other .= ',j';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001471 last;
1472 }
1473 }
1474 if ($ix < $num - 1) {
William Pursell57886bc2008-11-27 04:07:52 +00001475 $other .= ',J';
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001476 }
William Pursell070434d2008-12-04 10:22:40 +00001477 if ($num > 1) {
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001478 $other .= ',g,/';
William Pursell070434d2008-12-04 10:22:40 +00001479 }
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001480 for ($i = 0; $i < $num; $i++) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001481 if (!defined $hunk[$i]{USE}) {
1482 $undecided = 1;
1483 last;
1484 }
1485 }
1486 last if (!$undecided);
1487
Jeff King03925132009-04-16 03:14:15 -04001488 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1489 hunk_splittable($hunk[$ix]{TEXT})) {
William Pursell57886bc2008-11-27 04:07:52 +00001490 $other .= ',s';
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001491 }
Jeff King03925132009-04-16 03:14:15 -04001492 if ($hunk[$ix]{TYPE} eq 'hunk') {
1493 $other .= ',e';
1494 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001495 for (@{$hunk[$ix]{DISPLAY}}) {
1496 print;
1497 }
Vasco Almeida0539d5e2016-12-14 11:54:30 -01001498 print colored $prompt_color,
1499 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1500
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001501 my $line = prompt_single_character;
Jeff Kinga8bec7a2014-12-15 11:35:27 -05001502 last unless defined $line;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001503 if ($line) {
1504 if ($line =~ /^y/i) {
1505 $hunk[$ix]{USE} = 1;
1506 }
1507 elsif ($line =~ /^n/i) {
1508 $hunk[$ix]{USE} = 0;
1509 }
1510 elsif ($line =~ /^a/i) {
1511 while ($ix < $num) {
1512 if (!defined $hunk[$ix]{USE}) {
1513 $hunk[$ix]{USE} = 1;
1514 }
1515 $ix++;
1516 }
1517 next;
1518 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001519 elsif ($line =~ /^g(.*)/) {
William Pursell070434d2008-12-04 10:22:40 +00001520 my $response = $1;
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001521 unless ($other =~ /g/) {
1522 error_msg __("No other hunks to goto\n");
1523 next;
1524 }
William Pursell070434d2008-12-04 10:22:40 +00001525 my $no = $ix > 10 ? $ix - 10 : 0;
1526 while ($response eq '') {
William Pursell070434d2008-12-04 10:22:40 +00001527 $no = display_hunks(\@hunk, $no);
1528 if ($no < $num) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001529 print __("go to which hunk (<ret> to see more)? ");
1530 } else {
1531 print __("go to which hunk? ");
William Pursell070434d2008-12-04 10:22:40 +00001532 }
William Pursell070434d2008-12-04 10:22:40 +00001533 $response = <STDIN>;
Thomas Rast68c02d72009-02-02 22:46:29 +01001534 if (!defined $response) {
1535 $response = '';
1536 }
William Pursell070434d2008-12-04 10:22:40 +00001537 chomp $response;
1538 }
1539 if ($response !~ /^\s*\d+\s*$/) {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001540 error_msg sprintf(__("Invalid number: '%s'\n"),
1541 $response);
William Pursell070434d2008-12-04 10:22:40 +00001542 } elsif (0 < $response && $response <= $num) {
1543 $ix = $response - 1;
1544 } else {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001545 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1546 "Sorry, only %d hunks available.\n", $num), $num);
William Pursell070434d2008-12-04 10:22:40 +00001547 }
1548 next;
1549 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001550 elsif ($line =~ /^d/i) {
1551 while ($ix < $num) {
1552 if (!defined $hunk[$ix]{USE}) {
1553 $hunk[$ix]{USE} = 0;
1554 }
1555 $ix++;
1556 }
1557 next;
1558 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001559 elsif ($line =~ /^q/i) {
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001560 for ($i = 0; $i < $num; $i++) {
1561 if (!defined $hunk[$i]{USE}) {
1562 $hunk[$i]{USE} = 0;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001563 }
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001564 }
1565 $quit = 1;
Junio C Hamanof5ea3f22011-04-29 15:12:32 -07001566 last;
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001567 }
William Purselldd971cc2008-11-27 04:07:57 +00001568 elsif ($line =~ m|^/(.*)|) {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001569 my $regex = $1;
Phillip Wood88f6ffc2018-02-13 10:32:40 +00001570 unless ($other =~ m|/|) {
1571 error_msg __("No other hunks to search\n");
1572 next;
1573 }
Ævar Arnfjörð Bjarmasonfd2fb4a2018-03-31 12:50:58 +00001574 if ($regex eq "") {
Vasco Almeida258e7792016-12-14 11:54:25 -01001575 print colored $prompt_color, __("search for regex? ");
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001576 $regex = <STDIN>;
1577 if (defined $regex) {
1578 chomp $regex;
1579 }
1580 }
William Purselldd971cc2008-11-27 04:07:57 +00001581 my $search_string;
1582 eval {
Thomas Rastca6ac7f2009-02-05 09:28:26 +01001583 $search_string = qr{$regex}m;
William Purselldd971cc2008-11-27 04:07:57 +00001584 };
1585 if ($@) {
1586 my ($err,$exp) = ($@, $1);
1587 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
Vasco Almeida13c58c12016-12-14 11:54:27 -01001588 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
William Purselldd971cc2008-11-27 04:07:57 +00001589 next;
1590 }
1591 my $iy = $ix;
1592 while (1) {
1593 my $text = join ("", @{$hunk[$iy]{TEXT}});
1594 last if ($text =~ $search_string);
1595 $iy++;
1596 $iy = 0 if ($iy >= $num);
1597 if ($ix == $iy) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001598 error_msg __("No hunk matches the given pattern\n");
William Purselldd971cc2008-11-27 04:07:57 +00001599 last;
1600 }
1601 }
1602 $ix = $iy;
1603 next;
1604 }
William Pursellace30ba2008-11-27 04:08:03 +00001605 elsif ($line =~ /^K/) {
1606 if ($other =~ /K/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001607 $ix--;
William Pursellace30ba2008-11-27 04:08:03 +00001608 }
1609 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001610 error_msg __("No previous hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001611 }
1612 next;
1613 }
William Pursellace30ba2008-11-27 04:08:03 +00001614 elsif ($line =~ /^J/) {
1615 if ($other =~ /J/) {
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001616 $ix++;
William Pursellace30ba2008-11-27 04:08:03 +00001617 }
1618 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001619 error_msg __("No next hunk\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001620 }
1621 next;
1622 }
William Pursellace30ba2008-11-27 04:08:03 +00001623 elsif ($line =~ /^k/) {
1624 if ($other =~ /k/) {
1625 while (1) {
1626 $ix--;
1627 last if (!$ix ||
1628 !defined $hunk[$ix]{USE});
1629 }
1630 }
1631 else {
Vasco Almeida258e7792016-12-14 11:54:25 -01001632 error_msg __("No previous hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001633 }
1634 next;
1635 }
1636 elsif ($line =~ /^j/) {
1637 if ($other !~ /j/) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001638 error_msg __("No next hunk\n");
William Pursellace30ba2008-11-27 04:08:03 +00001639 next;
1640 }
1641 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001642 elsif ($line =~ /^s/) {
1643 unless ($other =~ /s/) {
1644 error_msg __("Sorry, cannot split this hunk\n");
1645 next;
1646 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001647 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001648 if (1 < @split) {
Vasco Almeidac4a85c32016-12-14 11:54:29 -01001649 print colored $header_color, sprintf(
1650 __n("Split into %d hunk.\n",
1651 "Split into %d hunks.\n",
1652 scalar(@split)), scalar(@split));
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001653 }
Wincent Colaiuta4af756f2007-12-07 13:35:10 +01001654 splice (@hunk, $ix, 1, @split);
Junio C Hamano835b2ae2006-12-11 17:09:26 -08001655 $num = scalar @hunk;
1656 next;
1657 }
Phillip Wood4bdd6e72018-02-13 10:32:41 +00001658 elsif ($line =~ /^e/) {
1659 unless ($other =~ /e/) {
1660 error_msg __("Sorry, cannot edit this hunk\n");
1661 next;
1662 }
Thomas Rastac083c42008-07-03 00:00:00 +02001663 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1664 if (defined $newhunk) {
1665 splice @hunk, $ix, 1, $newhunk;
1666 }
1667 }
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001668 else {
1669 help_patch_cmd($other);
1670 next;
1671 }
1672 # soft increment
1673 while (1) {
1674 $ix++;
1675 last if ($ix >= $num ||
1676 !defined $hunk[$ix]{USE});
1677 }
1678 }
1679 }
1680
Junio C Hamano7a26e652009-05-16 10:48:23 -07001681 @hunk = coalesce_overlapping_hunks(@hunk);
1682
Jean-Luc Herren7b40a452007-10-09 21:34:17 +02001683 my $n_lofs = 0;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001684 my @result = ();
1685 for (@hunk) {
Thomas Rast8cbd4312008-07-02 23:59:16 +02001686 if ($_->{USE}) {
1687 push @result, @{$_->{TEXT}};
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001688 }
1689 }
1690
1691 if (@result) {
Jeff Kinge1327ed2010-02-22 20:05:44 -05001692 my @patch = reassemble_patch($head->{TEXT}, @result);
Thomas Rast8f0bef62009-08-13 14:29:39 +02001693 my $apply_routine = $patch_mode_flavour{APPLY};
1694 &$apply_routine(@patch);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001695 refresh();
1696 }
1697
1698 print "\n";
Matthieu Moy9a7a1e02009-04-10 16:57:01 +02001699 return $quit;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001700}
1701
1702sub diff_cmd {
1703 my @mods = list_modified('index-only');
1704 @mods = grep { !($_->{BINARY}) } @mods;
1705 return if (!@mods);
Vasco Almeida258e7792016-12-14 11:54:25 -01001706 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001707 IMMEDIATE => 1,
1708 HEADER => $status_head, },
1709 @mods);
1710 return if (!@them);
Vasco Almeida258e7792016-12-14 11:54:25 -01001711 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
Jeff King18bc7612008-02-13 05:50:51 -05001712 system(qw(git diff -p --cached), $reference, '--',
1713 map { $_->{VALUE} } @them);
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001714}
1715
1716sub quit_cmd {
Vasco Almeida258e7792016-12-14 11:54:25 -01001717 print __("Bye.\n");
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001718 exit(0);
1719}
1720
1721sub help_cmd {
Vasco Almeida5fa83262016-12-14 11:54:26 -01001722# TRANSLATORS: please do not translate the command names
1723# 'status', 'update', 'revert', etc.
1724 print colored $help_color, __ <<'EOF' ;
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001725status - show paths with changes
1726update - add working tree state to the staged set of changes
1727revert - revert staged set of changes back to the HEAD version
1728patch - pick hunks and update selectively
Ralf Thielowe519ecc2017-02-22 19:46:27 +01001729diff - view diff between HEAD and index
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001730add untracked - add contents of untracked files to the staged set of changes
1731EOF
1732}
1733
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001734sub process_args {
1735 return unless @ARGV;
1736 my $arg = shift @ARGV;
Thomas Rastd002ef42009-08-15 13:48:31 +02001737 if ($arg =~ /--patch(?:=(.*))?/) {
1738 if (defined $1) {
1739 if ($1 eq 'reset') {
1740 $patch_mode = 'reset_head';
1741 $patch_mode_revision = 'HEAD';
Vasco Almeida258e7792016-12-14 11:54:25 -01001742 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001743 if ($arg ne '--') {
1744 $patch_mode_revision = $arg;
1745 $patch_mode = ($arg eq 'HEAD' ?
1746 'reset_head' : 'reset_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001747 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001748 }
Thomas Rast4f353652009-08-15 13:48:30 +02001749 } elsif ($1 eq 'checkout') {
Vasco Almeida258e7792016-12-14 11:54:25 -01001750 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001751 if ($arg eq '--') {
1752 $patch_mode = 'checkout_index';
1753 } else {
1754 $patch_mode_revision = $arg;
1755 $patch_mode = ($arg eq 'HEAD' ?
1756 'checkout_head' : 'checkout_nothead');
Vasco Almeida258e7792016-12-14 11:54:25 -01001757 $arg = shift @ARGV or die __("missing --");
Thomas Rast4f353652009-08-15 13:48:30 +02001758 }
Thomas Rastdda1f2a2009-08-13 14:29:44 +02001759 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1760 $patch_mode = $1;
Vasco Almeida258e7792016-12-14 11:54:25 -01001761 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001762 } else {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001763 die sprintf(__("unknown --patch mode: %s"), $1);
Thomas Rastd002ef42009-08-15 13:48:31 +02001764 }
1765 } else {
1766 $patch_mode = 'stage';
Vasco Almeida258e7792016-12-14 11:54:25 -01001767 $arg = shift @ARGV or die __("missing --");
Thomas Rastd002ef42009-08-15 13:48:31 +02001768 }
Vasco Almeida13c58c12016-12-14 11:54:27 -01001769 die sprintf(__("invalid argument %s, expecting --"),
1770 $arg) unless $arg eq "--";
Thomas Rastd002ef42009-08-15 13:48:31 +02001771 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
Jeff Kingc852bd52017-03-02 04:48:22 -05001772 $patch_mode_only = 1;
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001773 }
1774 elsif ($arg ne "--") {
Vasco Almeida13c58c12016-12-14 11:54:27 -01001775 die sprintf(__("invalid argument %s, expecting --"), $arg);
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001776 }
1777}
1778
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001779sub main_loop {
1780 my @cmd = ([ 'status', \&status_cmd, ],
1781 [ 'update', \&update_cmd, ],
1782 [ 'revert', \&revert_cmd, ],
1783 [ 'add untracked', \&add_untracked_cmd, ],
1784 [ 'patch', \&patch_update_cmd, ],
1785 [ 'diff', \&diff_cmd, ],
1786 [ 'quit', \&quit_cmd, ],
1787 [ 'help', \&help_cmd, ],
1788 );
1789 while (1) {
Vasco Almeida258e7792016-12-14 11:54:25 -01001790 my ($it) = list_and_choose({ PROMPT => __('What now'),
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001791 SINGLETON => 1,
1792 LIST_FLAT => 4,
Vasco Almeida258e7792016-12-14 11:54:25 -01001793 HEADER => __('*** Commands ***'),
Jean-Luc Herrenc95c0242007-09-26 15:56:19 +02001794 ON_EOF => \&quit_cmd,
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001795 IMMEDIATE => 1 }, @cmd);
1796 if ($it) {
1797 eval {
1798 $it->[1]->();
1799 };
1800 if ($@) {
1801 print "$@";
1802 }
1803 }
1804 }
1805}
1806
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001807process_args();
Junio C Hamano5cde71d2006-12-10 20:55:50 -08001808refresh();
Jeff Kingc852bd52017-03-02 04:48:22 -05001809if ($patch_mode_only) {
Wincent Colaiutab63e9952007-11-25 14:15:42 +01001810 patch_update_cmd();
1811}
1812else {
1813 status_cmd();
1814 main_loop();
1815}