add: remove "add.interactive.useBuiltin" & Perl "git add--interactive"

Since [1] first released with Git v2.37.0 the built-in version of "add -i" has been the default. That built-in implementation was added in [2], first released with Git v2.25.0. At this point enough time has passed to allow for finding any remaining bugs in this new implementation, so let's remove the fallback code. As with similar migrations for "stash"[3] and "rebase"[4] we're keeping a mention of "add.interactive.useBuiltin" in the documentation, but adding a warning() to notify any outstanding users that the built-in is now the default. As with [5] and [6] we should follow-up in the future and eventually remove that warning. 1. 0527ccb1b55 (add -i: default to the built-in implementation, 2021-11-30) 2. f83dff60a78 (Start to implement a built-in version of `git add --interactive`, 2019-11-13) 3. 8a2cd3f5123 (stash: remove the stash.useBuiltin setting, 2020-03-03) 4. d03ebd411c6 (rebase: remove the rebase.useBuiltin setting, 2019-03-18) 5. deeaf5ee077 (stash: remove documentation for `stash.useBuiltin`, 2022-01-27) 6. 9bcde4d5314 (rebase: remove transitory rebase.useBuiltin setting & env, 2021-03-23) Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Feb 6, 2023 at 23:58 UTC 20b813d7d3dc576aa58de22da2450b49116945bf
12 files changed +47 -2000
.gitignore
-1
@@ -14,7 +14,6 @@
14 /bin-wrappers/
15 /git
16 /git-add
17 -/git-add--interactive
17 /git-am
18 /git-annotate
19 /git-apply
Documentation/config/add.txt
+4 -3
@@ -7,6 +7,7 @@ add.ignore-errors (deprecated)::
7 variables.
8
9 add.interactive.useBuiltin::
10 - Set to `false` to fall back to the original Perl implementation of
11 - the interactive version of linkgit:git-add[1] instead of the built-in
12 - version. Is `true` by default.
10 + Unused configuration variable. Used in Git versions v2.25.0 to
11 + v2.36.0 to enable the built-in version of linkgit:git-add[1]'s
12 + interactive mode, which then became the default in Git
13 + versions v2.37.0 to v2.39.0.
INSTALL
+1 -1
@@ -120,7 +120,7 @@ Issues of note:
120 for everyday use (e.g. "bisect", "request-pull").
121
122 - "Perl" version 5.8 or later is needed to use some of the
123 - features (e.g. preparing a partial commit using "git add -i/-p",
123 + features (e.g. sending patches using "git send-email",
124 interacting with svn repositories with "git svn"). If you can
125 live without these, use NO_PERL. Note that recent releases of
126 Redhat/Fedora are reported to ship Perl binary package with some
Makefile
-1
@@ -708,7 +708,6 @@ SCRIPT_LIB += git-mergetool--lib
708 SCRIPT_LIB += git-sh-i18n
709 SCRIPT_LIB += git-sh-setup
710
711 -SCRIPT_PERL += git-add--interactive.perl
711 SCRIPT_PERL += git-archimport.perl
712 SCRIPT_PERL += git-cvsexportcommit.perl
713 SCRIPT_PERL += git-cvsimport.perl
builtin/add.c
+23 -43
@@ -241,55 +241,35 @@ static int refresh(int verbose, const struct pathspec *pathspec)
241 int run_add_interactive(const char *revision, const char *patch_mode,
242 const struct pathspec *pathspec)
243 {
244 - int i;
245 - struct child_process cmd = CHILD_PROCESS_INIT;
246 - int use_builtin_add_i =
247 - git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
248 -
249 - if (use_builtin_add_i < 0 &&
250 - git_config_get_bool("add.interactive.usebuiltin",
251 - &use_builtin_add_i))
252 - use_builtin_add_i = 1;
253 -
254 - if (use_builtin_add_i != 0) {
255 - enum add_p_mode mode;
256 -
257 - if (!patch_mode)
258 - return !!run_add_i(the_repository, pathspec);
259 -
260 - if (!strcmp(patch_mode, "--patch"))
261 - mode = ADD_P_ADD;
262 - else if (!strcmp(patch_mode, "--patch=stash"))
263 - mode = ADD_P_STASH;
264 - else if (!strcmp(patch_mode, "--patch=reset"))
265 - mode = ADD_P_RESET;
266 - else if (!strcmp(patch_mode, "--patch=checkout"))
267 - mode = ADD_P_CHECKOUT;
268 - else if (!strcmp(patch_mode, "--patch=worktree"))
269 - mode = ADD_P_WORKTREE;
270 - else
271 - die("'%s' not supported", patch_mode);
272 -
273 - return !!run_add_p(the_repository, mode, revision, pathspec);
274 - }
244 + enum add_p_mode mode;
245 +
246 + if (!patch_mode)
247 + return !!run_add_i(the_repository, pathspec);
248 +
249 + if (!strcmp(patch_mode, "--patch"))
250 + mode = ADD_P_ADD;
251 + else if (!strcmp(patch_mode, "--patch=stash"))
252 + mode = ADD_P_STASH;
253 + else if (!strcmp(patch_mode, "--patch=reset"))
254 + mode = ADD_P_RESET;
255 + else if (!strcmp(patch_mode, "--patch=checkout"))
256 + mode = ADD_P_CHECKOUT;
257 + else if (!strcmp(patch_mode, "--patch=worktree"))
258 + mode = ADD_P_WORKTREE;
259 + else
260 + die("'%s' not supported", patch_mode);
261
276 - strvec_push(&cmd.args, "add--interactive");
277 - if (patch_mode)
278 - strvec_push(&cmd.args, patch_mode);
279 - if (revision)
280 - strvec_push(&cmd.args, revision);
281 - strvec_push(&cmd.args, "--");
282 - for (i = 0; i < pathspec->nr; i++)
283 - /* pass original pathspec, to be re-parsed */
284 - strvec_push(&cmd.args, pathspec->items[i].original);
285 -
286 - cmd.git_cmd = 1;
287 - return run_command(&cmd);
262 + return !!run_add_p(the_repository, mode, revision, pathspec);
263 }
264
265 int interactive_add(const char **argv, const char *prefix, int patch)
266 {
267 struct pathspec pathspec;
268 + int unused;
269 +
270 + if (!git_config_get_bool("add.interactive.usebuiltin", &unused))
271 + warning(_("the add.interactive.useBuiltin setting has been removed!\n"
272 + "See its entry in 'git help config' for details."));
273
274 parse_pathspec(&pathspec, 0,
275 PATHSPEC_PREFER_FULL |
ci/run-build-and-tests.sh
-1
@@ -26,7 +26,6 @@ linux-TEST-vars)
26 export GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1
27 export GIT_TEST_MULTI_PACK_INDEX=1
28 export GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=1
29 - export GIT_TEST_ADD_I_USE_BUILTIN=0
29 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
30 export GIT_TEST_WRITE_REV_INDEX=1
31 export GIT_TEST_CHECKOUT_WORKERS=2
git-add--interactive.perl deleted
-1920
@@ -1,1920 +0,0 @@
1 -#!/usr/bin/perl
2 -
3 -use 5.008;
4 -use strict;
5 -use warnings;
6 -use Git qw(unquote_path);
7 -use Git::I18N;
8 -
9 -binmode(STDOUT, ":raw");
10 -
11 -my $repo = Git->repository();
12 -
13 -my $menu_use_color = $repo->get_colorbool('color.interactive');
14 -my ($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 - ) : ();
20 -my $error_color = ();
21 -if ($menu_use_color) {
22 - my $help_color_spec = ($repo->config('color.interactive.help') or
23 - 'red bold');
24 - $error_color = $repo->get_color('color.interactive.error',
25 - $help_color_spec);
26 -}
27 -
28 -my $diff_use_color = $repo->get_colorbool('color.diff');
29 -my ($fraginfo_color) =
30 - $diff_use_color ? (
31 - $repo->get_color('color.diff.frag', 'cyan'),
32 - ) : ();
33 -my ($diff_context_color) =
34 - $diff_use_color ? (
35 - $repo->get_color($repo->config('color.diff.context') ? 'color.diff.context' : 'color.diff.plain', ''),
36 - ) : ();
37 -my ($diff_old_color) =
38 - $diff_use_color ? (
39 - $repo->get_color('color.diff.old', 'red'),
40 - ) : ();
41 -my ($diff_new_color) =
42 - $diff_use_color ? (
43 - $repo->get_color('color.diff.new', 'green'),
44 - ) : ();
45 -
46 -my $normal_color = $repo->get_color("", "reset");
47 -
48 -my $diff_algorithm = $repo->config('diff.algorithm');
49 -my $diff_filter = $repo->config('interactive.difffilter');
50 -
51 -my $use_readkey = 0;
52 -my $use_termcap = 0;
53 -my %term_escapes;
54 -
55 -sub ReadMode;
56 -sub ReadKey;
57 -if ($repo->config_bool("interactive.singlekey")) {
58 - eval {
59 - require Term::ReadKey;
60 - Term::ReadKey->import;
61 - $use_readkey = 1;
62 - };
63 - if (!$use_readkey) {
64 - print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n";
65 - }
66 - 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 - };
74 -}
75 -
76 -sub colored {
77 - my $color = shift;
78 - my $string = join("", @_);
79 -
80 - if (defined $color) {
81 - # 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 -}
92 -
93 -# command line options
94 -my $patch_mode_only;
95 -my $patch_mode;
96 -my $patch_mode_revision;
97 -
98 -sub apply_patch;
99 -sub apply_patch_for_checkout_commit;
100 -sub apply_patch_for_stash;
101 -
102 -my %patch_modes = (
103 - 'stage' => {
104 - DIFF => 'diff-files -p',
105 - APPLY => sub { apply_patch 'apply --cached', @_; },
106 - APPLY_CHECK => 'apply --cached',
107 - FILTER => 'file-only',
108 - IS_REVERSE => 0,
109 - },
110 - 'stash' => {
111 - DIFF => 'diff-index -p HEAD',
112 - APPLY => sub { apply_patch 'apply --cached', @_; },
113 - APPLY_CHECK => 'apply --cached',
114 - FILTER => undef,
115 - IS_REVERSE => 0,
116 - },
117 - 'reset_head' => {
118 - DIFF => 'diff-index -p --cached',
119 - APPLY => sub { apply_patch 'apply -R --cached', @_; },
120 - APPLY_CHECK => 'apply -R --cached',
121 - FILTER => 'index-only',
122 - IS_REVERSE => 1,
123 - },
124 - 'reset_nothead' => {
125 - DIFF => 'diff-index -R -p --cached',
126 - APPLY => sub { apply_patch 'apply --cached', @_; },
127 - APPLY_CHECK => 'apply --cached',
128 - FILTER => 'index-only',
129 - IS_REVERSE => 0,
130 - },
131 - 'checkout_index' => {
132 - DIFF => 'diff-files -p',
133 - APPLY => sub { apply_patch 'apply -R', @_; },
134 - APPLY_CHECK => 'apply -R',
135 - FILTER => 'file-only',
136 - IS_REVERSE => 1,
137 - },
138 - 'checkout_head' => {
139 - DIFF => 'diff-index -p',
140 - APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
141 - APPLY_CHECK => 'apply -R',
142 - FILTER => undef,
143 - IS_REVERSE => 1,
144 - },
145 - 'checkout_nothead' => {
146 - DIFF => 'diff-index -R -p',
147 - APPLY => sub { apply_patch_for_checkout_commit '', @_ },
148 - APPLY_CHECK => 'apply',
149 - FILTER => undef,
150 - IS_REVERSE => 0,
151 - },
152 - 'worktree_head' => {
153 - DIFF => 'diff-index -p',
154 - APPLY => sub { apply_patch 'apply -R', @_ },
155 - APPLY_CHECK => 'apply -R',
156 - FILTER => undef,
157 - IS_REVERSE => 1,
158 - },
159 - 'worktree_nothead' => {
160 - DIFF => 'diff-index -R -p',
161 - APPLY => sub { apply_patch 'apply', @_ },
162 - APPLY_CHECK => 'apply',
163 - FILTER => undef,
164 - IS_REVERSE => 0,
165 - },
166 -);
167 -
168 -$patch_mode = 'stage';
169 -my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
170 -
171 -sub run_cmd_pipe {
172 - if ($^O eq 'MSWin32') {
173 - my @invalid = grep {m/[":*]/} @_;
174 - die "$^O does not support: @invalid\n" if @invalid;
175 - my @args = map { m/ /o ? "\"$_\"": $_ } @_;
176 - return qx{@args};
177 - } else {
178 - my $fh = undef;
179 - open($fh, '-|', @_) or die;
180 - my @out = <$fh>;
181 - close $fh || die "Cannot close @_ ($!)";
182 - return @out;
183 - }
184 -}
185 -
186 -my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
187 -
188 -if (!defined $GIT_DIR) {
189 - exit(1); # rev-parse would have already said "not a git repo"
190 -}
191 -chomp($GIT_DIR);
192 -
193 -sub refresh {
194 - my $fh;
195 - open $fh, 'git update-index --refresh |'
196 - or die;
197 - while (<$fh>) {
198 - ;# ignore 'needs update'
199 - }
200 - close $fh;
201 -}
202 -
203 -sub list_untracked {
204 - map {
205 - chomp $_;
206 - unquote_path($_);
207 - }
208 - run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
209 -}
210 -
211 -# TRANSLATORS: you can adjust this to align "git add -i" status menu
212 -my $status_fmt = __('%12s %12s %s');
213 -my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
214 -
215 -{
216 - my $initial;
217 - sub is_initial_commit {
218 - $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
219 - unless defined $initial;
220 - return $initial;
221 - }
222 -}
223 -
224 -{
225 - my $empty_tree;
226 - sub get_empty_tree {
227 - return $empty_tree if defined $empty_tree;
228 -
229 - ($empty_tree) = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
230 - chomp $empty_tree;
231 - return $empty_tree;
232 - }
233 -}
234 -
235 -sub get_diff_reference {
236 - my $ref = shift;
237 - if (defined $ref and $ref ne 'HEAD') {
238 - return $ref;
239 - } elsif (is_initial_commit()) {
240 - return get_empty_tree();
241 - } else {
242 - return 'HEAD';
243 - }
244 -}
245 -
246 -# Returns list of hashes, contents of each of which are:
247 -# VALUE: pathname
248 -# BINARY: is a binary path
249 -# INDEX: is index different from HEAD?
250 -# FILE: is file different from index?
251 -# INDEX_ADDDEL: is it add/delete between HEAD and index?
252 -# FILE_ADDDEL: is it add/delete between index and file?
253 -# UNMERGED: is the path unmerged
254 -
255 -sub list_modified {
256 - my ($only) = @_;
257 - my (%data, @return);
258 - my ($add, $del, $adddel, $file);
259 -
260 - my $reference = get_diff_reference($patch_mode_revision);
261 - for (run_cmd_pipe(qw(git diff-index --cached
262 - --numstat --summary), $reference,
263 - '--', @ARGV)) {
264 - if (($add, $del, $file) =
265 - /^([-\d]+) ([-\d]+) (.*)/) {
266 - my ($change, $bin);
267 - $file = unquote_path($file);
268 - if ($add eq '-' && $del eq '-') {
269 - $change = __('binary');
270 - $bin = 1;
271 - }
272 - else {
273 - $change = "+$add/-$del";
274 - }
275 - $data{$file} = {
276 - INDEX => $change,
277 - BINARY => $bin,
278 - FILE => __('nothing'),
279 - }
280 - }
281 - elsif (($adddel, $file) =
282 - /^ (create|delete) mode [0-7]+ (.*)$/) {
283 - $file = unquote_path($file);
284 - $data{$file}{INDEX_ADDDEL} = $adddel;
285 - }
286 - }
287 -
288 - for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
289 - if (($add, $del, $file) =
290 - /^([-\d]+) ([-\d]+) (.*)/) {
291 - $file = unquote_path($file);
292 - my ($change, $bin);
293 - if ($add eq '-' && $del eq '-') {
294 - $change = __('binary');
295 - $bin = 1;
296 - }
297 - else {
298 - $change = "+$add/-$del";
299 - }
300 - $data{$file}{FILE} = $change;
301 - if ($bin) {
302 - $data{$file}{BINARY} = 1;
303 - }
304 - }
305 - elsif (($adddel, $file) =
306 - /^ (create|delete) mode [0-7]+ (.*)$/) {
307 - $file = unquote_path($file);
308 - $data{$file}{FILE_ADDDEL} = $adddel;
309 - }
310 - elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
311 - $file = unquote_path($2);
312 - if (!exists $data{$file}) {
313 - $data{$file} = +{
314 - INDEX => __('unchanged'),
315 - BINARY => 0,
316 - };
317 - }
318 - if ($1 eq 'U') {
319 - $data{$file}{UNMERGED} = 1;
320 - }
321 - }
322 - }
323 -
324 - for (sort keys %data) {
325 - my $it = $data{$_};
326 -
327 - if ($only) {
328 - if ($only eq 'index-only') {
329 - next if ($it->{INDEX} eq __('unchanged'));
330 - }
331 - if ($only eq 'file-only') {
332 - next if ($it->{FILE} eq __('nothing'));
333 - }
334 - }
335 - push @return, +{
336 - VALUE => $_,
337 - %$it,
338 - };
339 - }
340 - return @return;
341 -}
342 -
343 -sub find_unique {
344 - my ($string, @stuff) = @_;
345 - my $found = undef;
346 - for (my $i = 0; $i < @stuff; $i++) {
347 - my $it = $stuff[$i];
348 - my $hit = undef;
349 - if (ref $it) {
350 - if ((ref $it) eq 'ARRAY') {
351 - $it = $it->[0];
352 - }
353 - else {
354 - $it = $it->{VALUE};
355 - }
356 - }
357 - eval {
358 - if ($it =~ /^$string/) {
359 - $hit = 1;
360 - };
361 - };
362 - if (defined $hit && defined $found) {
363 - return undef;
364 - }
365 - if ($hit) {
366 - $found = $i + 1;
367 - }
368 - }
369 - return $found;
370 -}
371 -
372 -# inserts string into trie and updates count for each character
373 -sub update_trie {
374 - my ($trie, $string) = @_;
375 - foreach (split //, $string) {
376 - $trie = $trie->{$_} ||= {COUNT => 0};
377 - $trie->{COUNT}++;
378 - }
379 -}
380 -
381 -# returns an array of tuples (prefix, remainder)
382 -sub find_unique_prefixes {
383 - my @stuff = @_;
384 - my @return = ();
385 -
386 - # any single prefix exceeding the soft limit is omitted
387 - # if any prefix exceeds the hard limit all are omitted
388 - # 0 indicates no limit
389 - my $soft_limit = 0;
390 - my $hard_limit = 3;
391 -
392 - # build a trie modelling all possible options
393 - my %trie;
394 - foreach my $print (@stuff) {
395 - if ((ref $print) eq 'ARRAY') {
396 - $print = $print->[0];
397 - }
398 - elsif ((ref $print) eq 'HASH') {
399 - $print = $print->{VALUE};
400 - }
401 - update_trie(\%trie, $print);
402 - push @return, $print;
403 - }
404 -
405 - # use the trie to find the unique prefixes
406 - for (my $i = 0; $i < @return; $i++) {
407 - my $ret = $return[$i];
408 - my @letters = split //, $ret;
409 - my %search = %trie;
410 - my ($prefix, $remainder);
411 - my $j;
412 - for ($j = 0; $j < @letters; $j++) {
413 - my $letter = $letters[$j];
414 - if ($search{$letter}{COUNT} == 1) {
415 - $prefix = substr $ret, 0, $j + 1;
416 - $remainder = substr $ret, $j + 1;
417 - last;
418 - }
419 - else {
420 - my $prefix = substr $ret, 0, $j;
421 - return ()
422 - if ($hard_limit && $j + 1 > $hard_limit);
423 - }
424 - %search = %{$search{$letter}};
425 - }
426 - if (ord($letters[0]) > 127 ||
427 - ($soft_limit && $j + 1 > $soft_limit)) {
428 - $prefix = undef;
429 - $remainder = $ret;
430 - }
431 - $return[$i] = [$prefix, $remainder];
432 - }
433 - return @return;
434 -}
435 -
436 -# filters out prefixes which have special meaning to list_and_choose()
437 -sub is_valid_prefix {
438 - my $prefix = shift;
439 - return (defined $prefix) &&
440 - !($prefix =~ /[\s,]/) && # separators
441 - !($prefix =~ /^-/) && # deselection
442 - !($prefix =~ /^\d+/) && # selection
443 - ($prefix ne '*') && # "all" wildcard
444 - ($prefix ne '?'); # prompt help
445 -}
446 -
447 -# given a prefix/remainder tuple return a string with the prefix highlighted
448 -# for now use square brackets; later might use ANSI colors (underline, bold)
449 -sub highlight_prefix {
450 - my $prefix = shift;
451 - my $remainder = shift;
452 -
453 - if (!defined $prefix) {
454 - return $remainder;
455 - }
456 -
457 - if (!is_valid_prefix($prefix)) {
458 - return "$prefix$remainder";
459 - }
460 -
461 - if (!$menu_use_color) {
462 - return "[$prefix]$remainder";
463 - }
464 -
465 - return "$prompt_color$prefix$normal_color$remainder";
466 -}
467 -
468 -sub error_msg {
469 - print STDERR colored $error_color, @_;
470 -}
471 -
472 -sub list_and_choose {
473 - my ($opts, @stuff) = @_;
474 - my (@chosen, @return);
475 - if (!@stuff) {
476 - return @return;
477 - }
478 - my $i;
479 - my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
480 -
481 - TOPLOOP:
482 - while (1) {
483 - my $last_lf = 0;
484 -
485 - if ($opts->{HEADER}) {
486 - my $indent = $opts->{LIST_FLAT} ? "" : " ";
487 - print colored $header_color, "$indent$opts->{HEADER}\n";
488 - }
489 - for ($i = 0; $i < @stuff; $i++) {
490 - my $chosen = $chosen[$i] ? '*' : ' ';
491 - my $print = $stuff[$i];
492 - my $ref = ref $print;
493 - my $highlighted = highlight_prefix(@{$prefixes[$i]})
494 - if @prefixes;
495 - if ($ref eq 'ARRAY') {
496 - $print = $highlighted || $print->[0];
497 - }
498 - elsif ($ref eq 'HASH') {
499 - my $value = $highlighted || $print->{VALUE};
500 - $print = sprintf($status_fmt,
501 - $print->{INDEX},
502 - $print->{FILE},
503 - $value);
504 - }
505 - else {
506 - $print = $highlighted || $print;
507 - }
508 - printf("%s%2d: %s", $chosen, $i+1, $print);
509 - if (($opts->{LIST_FLAT}) &&
510 - (($i + 1) % ($opts->{LIST_FLAT}))) {
511 - print "\t";
512 - $last_lf = 0;
513 - }
514 - else {
515 - print "\n";
516 - $last_lf = 1;
517 - }
518 - }
519 - if (!$last_lf) {
520 - print "\n";
521 - }
522 -
523 - return if ($opts->{LIST_ONLY});
524 -
525 - print colored $prompt_color, $opts->{PROMPT};
526 - if ($opts->{SINGLETON}) {
527 - print "> ";
528 - }
529 - else {
530 - print ">> ";
531 - }
532 - my $line = <STDIN>;
533 - if (!$line) {
534 - print "\n";
535 - $opts->{ON_EOF}->() if $opts->{ON_EOF};
536 - last;
537 - }
538 - chomp $line;
539 - last if $line eq '';
540 - if ($line eq '?') {
541 - $opts->{SINGLETON} ?
542 - singleton_prompt_help_cmd() :
543 - prompt_help_cmd();
544 - next TOPLOOP;
545 - }
546 - for my $choice (split(/[\s,]+/, $line)) {
547 - my $choose = 1;
548 - my ($bottom, $top);
549 -
550 - # Input that begins with '-'; unchoose
551 - if ($choice =~ s/^-//) {
552 - $choose = 0;
553 - }
554 - # A range can be specified like 5-7 or 5-.
555 - if ($choice =~ /^(\d+)-(\d*)$/) {
556 - ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
557 - }
558 - elsif ($choice =~ /^\d+$/) {
559 - $bottom = $top = $choice;
560 - }
561 - elsif ($choice eq '*') {
562 - $bottom = 1;
563 - $top = 1 + @stuff;
564 - }
565 - else {
566 - $bottom = $top = find_unique($choice, @stuff);
567 - if (!defined $bottom) {
568 - error_msg sprintf(__("Huh (%s)?\n"), $choice);
569 - next TOPLOOP;
570 - }
571 - }
572 - if ($opts->{SINGLETON} && $bottom != $top) {
573 - error_msg sprintf(__("Huh (%s)?\n"), $choice);
574 - next TOPLOOP;
575 - }
576 - for ($i = $bottom-1; $i <= $top-1; $i++) {
577 - next if (@stuff <= $i || $i < 0);
578 - $chosen[$i] = $choose;
579 - }
580 - }
581 - last if ($opts->{IMMEDIATE} || $line eq '*');
582 - }
583 - for ($i = 0; $i < @stuff; $i++) {
584 - if ($chosen[$i]) {
585 - push @return, $stuff[$i];
586 - }
587 - }
588 - return @return;
589 -}
590 -
591 -sub singleton_prompt_help_cmd {
592 - print colored $help_color, __ <<'EOF' ;
593 -Prompt help:
594 -1 - select a numbered item
595 -foo - select item based on unique prefix
596 - - (empty) select nothing
597 -EOF
598 -}
599 -
600 -sub prompt_help_cmd {
601 - print colored $help_color, __ <<'EOF' ;
602 -Prompt help:
603 -1 - select a single item
604 -3-5 - select a range of items
605 -2-3,6-9 - select multiple ranges
606 -foo - select item based on unique prefix
607 --... - unselect specified items
608 -* - choose all items
609 - - (empty) finish selecting
610 -EOF
611 -}
612 -
613 -sub status_cmd {
614 - list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
615 - list_modified());
616 - print "\n";
617 -}
618 -
619 -sub say_n_paths {
620 - my $did = shift @_;
621 - my $cnt = scalar @_;
622 - if ($did eq 'added') {
623 - printf(__n("added %d path\n", "added %d paths\n",
624 - $cnt), $cnt);
625 - } elsif ($did eq 'updated') {
626 - printf(__n("updated %d path\n", "updated %d paths\n",
627 - $cnt), $cnt);
628 - } elsif ($did eq 'reverted') {
629 - printf(__n("reverted %d path\n", "reverted %d paths\n",
630 - $cnt), $cnt);
631 - } else {
632 - printf(__n("touched %d path\n", "touched %d paths\n",
633 - $cnt), $cnt);
634 - }
635 -}
636 -
637 -sub update_cmd {
638 - my @mods = list_modified('file-only');
639 - return if (!@mods);
640 -
641 - my @update = list_and_choose({ PROMPT => __('Update'),
642 - HEADER => $status_head, },
643 - @mods);
644 - if (@update) {
645 - system(qw(git update-index --add --remove --),
646 - map { $_->{VALUE} } @update);
647 - say_n_paths('updated', @update);
648 - }
649 - print "\n";
650 -}
651 -
652 -sub revert_cmd {
653 - my @update = list_and_choose({ PROMPT => __('Revert'),
654 - HEADER => $status_head, },
655 - list_modified());
656 - if (@update) {
657 - if (is_initial_commit()) {
658 - system(qw(git rm --cached),
659 - map { $_->{VALUE} } @update);
660 - }
661 - else {
662 - my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
663 - map { $_->{VALUE} } @update);
664 - my $fh;
665 - open $fh, '| git update-index --index-info'
666 - or die;
667 - for (@lines) {
668 - print $fh $_;
669 - }
670 - close($fh);
671 - for (@update) {
672 - if ($_->{INDEX_ADDDEL} &&
673 - $_->{INDEX_ADDDEL} eq 'create') {
674 - system(qw(git update-index --force-remove --),
675 - $_->{VALUE});
676 - printf(__("note: %s is untracked now.\n"), $_->{VALUE});
677 - }
678 - }
679 - }
680 - refresh();
681 - say_n_paths('reverted', @update);
682 - }
683 - print "\n";
684 -}
685 -
686 -sub add_untracked_cmd {
687 - my @add = list_and_choose({ PROMPT => __('Add untracked') },
688 - list_untracked());
689 - if (@add) {
690 - system(qw(git update-index --add --), @add);
691 - say_n_paths('added', @add);
692 - } else {
693 - print __("No untracked files.\n");
694 - }
695 - print "\n";
696 -}
697 -
698 -sub run_git_apply {
699 - my $cmd = shift;
700 - my $fh;
701 - open $fh, '| git ' . $cmd . " --allow-overlap";
702 - print $fh @_;
703 - return close $fh;
704 -}
705 -
706 -sub parse_diff {
707 - my ($path) = @_;
708 - my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
709 - if (defined $diff_algorithm) {
710 - splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
711 - }
712 - if (defined $patch_mode_revision) {
713 - push @diff_cmd, get_diff_reference($patch_mode_revision);
714 - }
715 - my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path);
716 - my @colored = ();
717 - if ($diff_use_color) {
718 - my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
719 - if (defined $diff_filter) {
720 - # quotemeta is overkill, but sufficient for shell-quoting
721 - my $diff = join(' ', map { quotemeta } @display_cmd);
722 - @display_cmd = ("$diff | $diff_filter");
723 - }
724 -
725 - @colored = run_cmd_pipe(@display_cmd);
726 - }
727 - my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
728 -
729 - if (@colored && @colored != @diff) {
730 - print STDERR
731 - "fatal: mismatched output from interactive.diffFilter\n",
732 - "hint: Your filter must maintain a one-to-one correspondence\n",
733 - "hint: between its input and output lines.\n";
734 - exit 1;
735 - }
736 -
737 - for (my $i = 0; $i < @diff; $i++) {
738 - if ($diff[$i] =~ /^@@ /) {
739 - push @hunk, { TEXT => [], DISPLAY => [],
740 - TYPE => 'hunk' };
741 - }
742 - push @{$hunk[-1]{TEXT}}, $diff[$i];
743 - push @{$hunk[-1]{DISPLAY}},
744 - (@colored ? $colored[$i] : $diff[$i]);
745 - }
746 - return @hunk;
747 -}
748 -
749 -sub parse_diff_header {
750 - my $src = shift;
751 -
752 - my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
753 - my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
754 - my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
755 - my $addition;
756 -
757 - for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
758 - if ($src->{TEXT}->[$i] =~ /^new file/) {
759 - $addition = 1;
760 - $head->{TYPE} = 'addition';
761 - }
762 - my $dest =
763 - $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
764 - $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
765 - $head;
766 - push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
767 - push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
768 - }
769 - return ($head, $mode, $deletion, $addition);
770 -}
771 -
772 -sub hunk_splittable {
773 - my ($text) = @_;
774 -
775 - my @s = split_hunk($text);
776 - return (1 < @s);
777 -}
778 -
779 -sub parse_hunk_header {
780 - my ($line) = @_;
781 - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
782 - $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
783 - $o_cnt = 1 unless defined $o_cnt;
784 - $n_cnt = 1 unless defined $n_cnt;
785 - return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
786 -}
787 -
788 -sub format_hunk_header {
789 - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
790 - return ("@@ -$o_ofs" .
791 - (($o_cnt != 1) ? ",$o_cnt" : '') .
792 - " +$n_ofs" .
793 - (($n_cnt != 1) ? ",$n_cnt" : '') .
794 - " @@\n");
795 -}
796 -
797 -sub split_hunk {
798 - my ($text, $display) = @_;
799 - my @split = ();
800 - if (!defined $display) {
801 - $display = $text;
802 - }
803 - # If there are context lines in the middle of a hunk,
804 - # it can be split, but we would need to take care of
805 - # overlaps later.
806 -
807 - my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
808 - my $hunk_start = 1;
809 -
810 - OUTER:
811 - while (1) {
812 - my $next_hunk_start = undef;
813 - my $i = $hunk_start - 1;
814 - my $this = +{
815 - TEXT => [],
816 - DISPLAY => [],
817 - TYPE => 'hunk',
818 - OLD => $o_ofs,
819 - NEW => $n_ofs,
820 - OCNT => 0,
821 - NCNT => 0,
822 - ADDDEL => 0,
823 - POSTCTX => 0,
824 - USE => undef,
825 - };
826 -
827 - while (++$i < @$text) {
828 - my $line = $text->[$i];
829 - my $display = $display->[$i];
830 - if ($line =~ /^\\/) {
831 - push @{$this->{TEXT}}, $line;
832 - push @{$this->{DISPLAY}}, $display;
833 - next;
834 - }
835 - if ($line =~ /^ /) {
836 - if ($this->{ADDDEL} &&
837 - !defined $next_hunk_start) {
838 - # We have seen leading context and
839 - # adds/dels and then here is another
840 - # context, which is trailing for this
841 - # split hunk and leading for the next
842 - # one.
843 - $next_hunk_start = $i;
844 - }
845 - push @{$this->{TEXT}}, $line;
846 - push @{$this->{DISPLAY}}, $display;
847 - $this->{OCNT}++;
848 - $this->{NCNT}++;
849 - if (defined $next_hunk_start) {
850 - $this->{POSTCTX}++;
851 - }
852 - next;
853 - }
854 -
855 - # add/del
856 - if (defined $next_hunk_start) {
857 - # We are done with the current hunk and
858 - # this is the first real change for the
859 - # next split one.
860 - $hunk_start = $next_hunk_start;
861 - $o_ofs = $this->{OLD} + $this->{OCNT};
862 - $n_ofs = $this->{NEW} + $this->{NCNT};
863 - $o_ofs -= $this->{POSTCTX};
864 - $n_ofs -= $this->{POSTCTX};
865 - push @split, $this;
866 - redo OUTER;
867 - }
868 - push @{$this->{TEXT}}, $line;
869 - push @{$this->{DISPLAY}}, $display;
870 - $this->{ADDDEL}++;
871 - if ($line =~ /^-/) {
872 - $this->{OCNT}++;
873 - }
874 - else {
875 - $this->{NCNT}++;
876 - }
877 - }
878 -
879 - push @split, $this;
880 - last;
881 - }
882 -
883 - for my $hunk (@split) {
884 - $o_ofs = $hunk->{OLD};
885 - $n_ofs = $hunk->{NEW};
886 - my $o_cnt = $hunk->{OCNT};
887 - my $n_cnt = $hunk->{NCNT};
888 -
889 - my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
890 - my $display_head = $head;
891 - unshift @{$hunk->{TEXT}}, $head;
892 - if ($diff_use_color) {
893 - $display_head = colored($fraginfo_color, $head);
894 - }
895 - unshift @{$hunk->{DISPLAY}}, $display_head;
896 - }
897 - return @split;
898 -}
899 -
900 -sub find_last_o_ctx {
901 - my ($it) = @_;
902 - my $text = $it->{TEXT};
903 - my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
904 - my $i = @{$text};
905 - my $last_o_ctx = $o_ofs + $o_cnt;
906 - while (0 < --$i) {
907 - my $line = $text->[$i];
908 - if ($line =~ /^ /) {
909 - $last_o_ctx--;
910 - next;
911 - }
912 - last;
913 - }
914 - return $last_o_ctx;
915 -}
916 -
917 -sub merge_hunk {
918 - my ($prev, $this) = @_;
919 - my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
920 - parse_hunk_header($prev->{TEXT}[0]);
921 - my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
922 - parse_hunk_header($this->{TEXT}[0]);
923 -
924 - my (@line, $i, $ofs, $o_cnt, $n_cnt);
925 - $ofs = $o0_ofs;
926 - $o_cnt = $n_cnt = 0;
927 - for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
928 - my $line = $prev->{TEXT}[$i];
929 - if ($line =~ /^\+/) {
930 - $n_cnt++;
931 - push @line, $line;
932 - next;
933 - } elsif ($line =~ /^\\/) {
934 - push @line, $line;
935 - next;
936 - }
937 -
938 - last if ($o1_ofs <= $ofs);
939 -
940 - $o_cnt++;
941 - $ofs++;
942 - if ($line =~ /^ /) {
943 - $n_cnt++;
944 - }
945 - push @line, $line;
946 - }
947 -
948 - for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
949 - my $line = $this->{TEXT}[$i];
950 - if ($line =~ /^\+/) {
951 - $n_cnt++;
952 - push @line, $line;
953 - next;
954 - } elsif ($line =~ /^\\/) {
955 - push @line, $line;
956 - next;
957 - }
958 - $ofs++;
959 - $o_cnt++;
960 - if ($line =~ /^ /) {
961 - $n_cnt++;
962 - }
963 - push @line, $line;
964 - }
965 - my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
966 - @{$prev->{TEXT}} = ($head, @line);
967 -}
968 -
969 -sub coalesce_overlapping_hunks {
970 - my (@in) = @_;
971 - my @out = ();
972 -
973 - my ($last_o_ctx, $last_was_dirty);
974 - my $ofs_delta = 0;
975 -
976 - for (@in) {
977 - if ($_->{TYPE} ne 'hunk') {
978 - push @out, $_;
979 - next;
980 - }
981 - my $text = $_->{TEXT};
982 - my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
983 - parse_hunk_header($text->[0]);
984 - unless ($_->{USE}) {
985 - $ofs_delta += $o_cnt - $n_cnt;
986 - # If this hunk has been edited then subtract
987 - # the delta that is due to the edit.
988 - if ($_->{OFS_DELTA}) {
989 - $ofs_delta -= $_->{OFS_DELTA};
990 - }
991 - next;
992 - }
993 - if ($ofs_delta) {
994 - if ($patch_mode_flavour{IS_REVERSE}) {
995 - $o_ofs -= $ofs_delta;
996 - } else {
997 - $n_ofs += $ofs_delta;
998 - }
999 - $_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
1000 - $n_ofs, $n_cnt);
1001 - }
1002 - # If this hunk was edited then adjust the offset delta
1003 - # to reflect the edit.
1004 - if ($_->{OFS_DELTA}) {
1005 - $ofs_delta += $_->{OFS_DELTA};
1006 - }
1007 - if (defined $last_o_ctx &&
1008 - $o_ofs <= $last_o_ctx &&
1009 - !$_->{DIRTY} &&
1010 - !$last_was_dirty) {
1011 - merge_hunk($out[-1], $_);
1012 - }
1013 - else {
1014 - push @out, $_;
1015 - }
1016 - $last_o_ctx = find_last_o_ctx($out[-1]);
1017 - $last_was_dirty = $_->{DIRTY};
1018 - }
1019 - return @out;
1020 -}
1021 -
1022 -sub reassemble_patch {
1023 - my $head = shift;
1024 - my @patch;
1025 -
1026 - # Include everything in the header except the beginning of the diff.
1027 - push @patch, (grep { !/^[-+]{3}/ } @$head);
1028 -
1029 - # Then include any headers from the hunk lines, which must
1030 - # come before any actual hunk.
1031 - while (@_ && $_[0] !~ /^@/) {
1032 - push @patch, shift;
1033 - }
1034 -
1035 - # Then begin the diff.
1036 - push @patch, grep { /^[-+]{3}/ } @$head;
1037 -
1038 - # And then the actual hunks.
1039 - push @patch, @_;
1040 -
1041 - return @patch;
1042 -}
1043 -
1044 -sub color_diff {
1045 - return map {
1046 - colored((/^@/ ? $fraginfo_color :
1047 - /^\+/ ? $diff_new_color :
1048 - /^-/ ? $diff_old_color :
1049 - $diff_context_color),
1050 - $_);
1051 - } @_;
1052 -}
1053 -
1054 -my %edit_hunk_manually_modes = (
1055 - stage => N__(
1056 -"If the patch applies cleanly, the edited hunk will immediately be
1057 -marked for staging."),
1058 - stash => N__(
1059 -"If the patch applies cleanly, the edited hunk will immediately be
1060 -marked for stashing."),
1061 - reset_head => N__(
1062 -"If the patch applies cleanly, the edited hunk will immediately be
1063 -marked for unstaging."),
1064 - reset_nothead => N__(
1065 -"If the patch applies cleanly, the edited hunk will immediately be
1066 -marked for applying."),
1067 - checkout_index => N__(
1068 -"If the patch applies cleanly, the edited hunk will immediately be
1069 -marked for discarding."),
1070 - checkout_head => N__(
1071 -"If the patch applies cleanly, the edited hunk will immediately be
1072 -marked for discarding."),
1073 - checkout_nothead => N__(
1074 -"If the patch applies cleanly, the edited hunk will immediately be
1075 -marked for applying."),
1076 - worktree_head => N__(
1077 -"If the patch applies cleanly, the edited hunk will immediately be
1078 -marked for discarding."),
1079 - worktree_nothead => N__(
1080 -"If the patch applies cleanly, the edited hunk will immediately be
1081 -marked for applying."),
1082 -);
1083 -
1084 -sub recount_edited_hunk {
1085 - local $_;
1086 - my ($oldtext, $newtext) = @_;
1087 - my ($o_cnt, $n_cnt) = (0, 0);
1088 - for (@{$newtext}[1..$#{$newtext}]) {
1089 - my $mode = substr($_, 0, 1);
1090 - if ($mode eq '-') {
1091 - $o_cnt++;
1092 - } elsif ($mode eq '+') {
1093 - $n_cnt++;
1094 - } elsif ($mode eq ' ' or $mode eq "\n") {
1095 - $o_cnt++;
1096 - $n_cnt++;
1097 - }
1098 - }
1099 - my ($o_ofs, undef, $n_ofs, undef) =
1100 - parse_hunk_header($newtext->[0]);
1101 - $newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1102 - my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1103 - parse_hunk_header($oldtext->[0]);
1104 - # Return the change in the number of lines inserted by this hunk
1105 - return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1106 -}
1107 -
1108 -sub edit_hunk_manually {
1109 - my ($oldtext) = @_;
1110 -
1111 - my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1112 - my $fh;
1113 - open $fh, '>', $hunkfile
1114 - or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
1115 - print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
1116 - print $fh @$oldtext;
1117 - my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1118 - my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
1119 - my $comment_line_char = Git::get_comment_line_char;
1120 - print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1121 ----
1122 -To remove '%s' lines, make them ' ' lines (context).
1123 -To remove '%s' lines, delete them.
1124 -Lines starting with %s will be removed.
1125 -EOF
1126 -__($edit_hunk_manually_modes{$patch_mode}),
1127 -# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1128 -__ <<EOF2 ;
1129 -If it does not apply cleanly, you will be given an opportunity to
1130 -edit again. If all lines of the hunk are removed, then the edit is
1131 -aborted and the hunk is left unchanged.
1132 -EOF2
1133 - close $fh;
1134 -
1135 - chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
1136 - system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1137 -
1138 - if ($? != 0) {
1139 - return undef;
1140 - }
1141 -
1142 - open $fh, '<', $hunkfile
1143 - or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
1144 - my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1145 - close $fh;
1146 - unlink $hunkfile;
1147 -
1148 - # Abort if nothing remains
1149 - if (!grep { /\S/ } @newtext) {
1150 - return undef;
1151 - }
1152 -
1153 - # Reinsert the first hunk header if the user accidentally deleted it
1154 - if ($newtext[0] !~ /^@/) {
1155 - unshift @newtext, $oldtext->[0];
1156 - }
1157 - return \@newtext;
1158 -}
1159 -
1160 -sub diff_applies {
1161 - return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
1162 - map { @{$_->{TEXT}} } @_);
1163 -}
1164 -
1165 -sub _restore_terminal_and_die {
1166 - ReadMode 'restore';
1167 - print "\n";
1168 - exit 1;
1169 -}
1170 -
1171 -sub prompt_single_character {
1172 - if ($use_readkey) {
1173 - local $SIG{TERM} = \&_restore_terminal_and_die;
1174 - local $SIG{INT} = \&_restore_terminal_and_die;
1175 - ReadMode 'cbreak';
1176 - my $key = ReadKey 0;
1177 - ReadMode 'restore';
1178 - if (defined $key) {
1179 - if ($use_termcap and $key eq "\e") {
1180 - while (!defined $term_escapes{$key}) {
1181 - my $next = ReadKey 0.5;
1182 - last if (!defined $next);
1183 - $key .= $next;
1184 - }
1185 - $key =~ s/\e/^[/;
1186 - }
1187 - print "$key";
1188 - }
1189 - print "\n";
1190 - return $key;
1191 - } else {
1192 - return <STDIN>;
1193 - }
1194 -}
1195 -
1196 -sub prompt_yesno {
1197 - my ($prompt) = @_;
1198 - while (1) {
1199 - print colored $prompt_color, $prompt;
1200 - my $line = prompt_single_character;
1201 - return undef unless defined $line;
1202 - return 0 if $line =~ /^n/i;
1203 - return 1 if $line =~ /^y/i;
1204 - }
1205 -}
1206 -
1207 -sub edit_hunk_loop {
1208 - my ($head, $hunks, $ix) = @_;
1209 - my $hunk = $hunks->[$ix];
1210 - my $text = $hunk->{TEXT};
1211 -
1212 - while (1) {
1213 - my $newtext = edit_hunk_manually($text);
1214 - if (!defined $newtext) {
1215 - return undef;
1216 - }
1217 - my $newhunk = {
1218 - TEXT => $newtext,
1219 - TYPE => $hunk->{TYPE},
1220 - USE => 1,
1221 - DIRTY => 1,
1222 - };
1223 - $newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1224 - # If this hunk has already been edited then add the
1225 - # offset delta of the previous edit to get the real
1226 - # delta from the original unedited hunk.
1227 - $hunk->{OFS_DELTA} and
1228 - $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
1229 - if (diff_applies($head,
1230 - @{$hunks}[0..$ix-1],
1231 - $newhunk,
1232 - @{$hunks}[$ix+1..$#{$hunks}])) {
1233 - $newhunk->{DISPLAY} = [color_diff(@{$newtext})];
1234 - return $newhunk;
1235 - }
1236 - else {
1237 - prompt_yesno(
1238 - # TRANSLATORS: do not translate [y/n]
1239 - # The program will only accept that input
1240 - # at this point.
1241 - # Consider translating (saying "no" discards!) as
1242 - # (saying "n" for "no" discards!) if the translation
1243 - # of the word "no" does not start with n.
1244 - __('Your edited hunk does not apply. Edit again '
1245 - . '(saying "no" discards!) [y/n]? ')
1246 - ) or return undef;
1247 - }
1248 - }
1249 -}
1250 -
1251 -my %help_patch_modes = (
1252 - stage => N__(
1253 -"y - stage this hunk
1254 -n - do not stage this hunk
1255 -q - quit; do not stage this hunk or any of the remaining ones
1256 -a - stage this hunk and all later hunks in the file
1257 -d - do not stage this hunk or any of the later hunks in the file"),
1258 - stash => N__(
1259 -"y - stash this hunk
1260 -n - do not stash this hunk
1261 -q - quit; do not stash this hunk or any of the remaining ones
1262 -a - stash this hunk and all later hunks in the file
1263 -d - do not stash this hunk or any of the later hunks in the file"),
1264 - reset_head => N__(
1265 -"y - unstage this hunk
1266 -n - do not unstage this hunk
1267 -q - quit; do not unstage this hunk or any of the remaining ones
1268 -a - unstage this hunk and all later hunks in the file
1269 -d - do not unstage this hunk or any of the later hunks in the file"),
1270 - reset_nothead => N__(
1271 -"y - apply this hunk to index
1272 -n - do not apply this hunk to index
1273 -q - quit; do not apply this hunk or any of the remaining ones
1274 -a - apply this hunk and all later hunks in the file
1275 -d - do not apply this hunk or any of the later hunks in the file"),
1276 - checkout_index => N__(
1277 -"y - discard this hunk from worktree
1278 -n - do not discard this hunk from worktree
1279 -q - quit; do not discard this hunk or any of the remaining ones
1280 -a - discard this hunk and all later hunks in the file
1281 -d - do not discard this hunk or any of the later hunks in the file"),
1282 - checkout_head => N__(
1283 -"y - discard this hunk from index and worktree
1284 -n - do not discard this hunk from index and worktree
1285 -q - quit; do not discard this hunk or any of the remaining ones
1286 -a - discard this hunk and all later hunks in the file
1287 -d - do not discard this hunk or any of the later hunks in the file"),
1288 - checkout_nothead => N__(
1289 -"y - apply this hunk to index and worktree
1290 -n - do not apply this hunk to index and worktree
1291 -q - quit; do not apply this hunk or any of the remaining ones
1292 -a - apply this hunk and all later hunks in the file
1293 -d - do not apply this hunk or any of the later hunks in the file"),
1294 - worktree_head => N__(
1295 -"y - discard this hunk from worktree
1296 -n - do not discard this hunk from worktree
1297 -q - quit; do not discard this hunk or any of the remaining ones
1298 -a - discard this hunk and all later hunks in the file
1299 -d - do not discard this hunk or any of the later hunks in the file"),
1300 - worktree_nothead => N__(
1301 -"y - apply this hunk to worktree
1302 -n - do not apply this hunk to worktree
1303 -q - quit; do not apply this hunk or any of the remaining ones
1304 -a - apply this hunk and all later hunks in the file
1305 -d - do not apply this hunk or any of the later hunks in the file"),
1306 -);
1307 -
1308 -sub help_patch_cmd {
1309 - local $_;
1310 - my $other = $_[0] . ",?";
1311 - print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1312 - map { "$_\n" } grep {
1313 - my $c = quotemeta(substr($_, 0, 1));
1314 - $other =~ /,$c/
1315 - } split "\n", __ <<EOF ;
1316 -g - select a hunk to go to
1317 -/ - search for a hunk matching the given regex
1318 -j - leave this hunk undecided, see next undecided hunk
1319 -J - leave this hunk undecided, see next hunk
1320 -k - leave this hunk undecided, see previous undecided hunk
1321 -K - leave this hunk undecided, see previous hunk
1322 -s - split the current hunk into smaller hunks
1323 -e - manually edit the current hunk
1324 -? - print help
1325 -EOF
1326 -}
1327 -
1328 -sub apply_patch {
1329 - my $cmd = shift;
1330 - my $ret = run_git_apply $cmd, @_;
1331 - if (!$ret) {
1332 - print STDERR @_;
1333 - }
1334 - return $ret;
1335 -}
1336 -
1337 -sub apply_patch_for_checkout_commit {
1338 - my $reverse = shift;
1339 - my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1340 - my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
1341 -
1342 - if ($applies_worktree && $applies_index) {
1343 - run_git_apply 'apply '.$reverse.' --cached', @_;
1344 - run_git_apply 'apply '.$reverse, @_;
1345 - return 1;
1346 - } elsif (!$applies_index) {
1347 - print colored $error_color, __("The selected hunks do not apply to the index!\n");
1348 - if (prompt_yesno __("Apply them to the worktree anyway? ")) {
1349 - return run_git_apply 'apply '.$reverse, @_;
1350 - } else {
1351 - print colored $error_color, __("Nothing was applied.\n");
1352 - return 0;
1353 - }
1354 - } else {
1355 - print STDERR @_;
1356 - return 0;
1357 - }
1358 -}
1359 -
1360 -sub patch_update_cmd {
1361 - my @all_mods = list_modified($patch_mode_flavour{FILTER});
1362 - error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
1363 - for grep { $_->{UNMERGED} } @all_mods;
1364 - @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1365 -
1366 - my @mods = grep { !($_->{BINARY}) } @all_mods;
1367 - my @them;
1368 -
1369 - if (!@mods) {
1370 - if (@all_mods) {
1371 - print STDERR __("Only binary files changed.\n");
1372 - } else {
1373 - print STDERR __("No changes.\n");
1374 - }
1375 - return 0;
1376 - }
1377 - if ($patch_mode_only) {
1378 - @them = @mods;
1379 - }
1380 - else {
1381 - @them = list_and_choose({ PROMPT => __('Patch update'),
1382 - HEADER => $status_head, },
1383 - @mods);
1384 - }
1385 - for (@them) {
1386 - return 0 if patch_update_file($_->{VALUE});
1387 - }
1388 -}
1389 -
1390 -# Generate a one line summary of a hunk.
1391 -sub summarize_hunk {
1392 - my $rhunk = shift;
1393 - my $summary = $rhunk->{TEXT}[0];
1394 -
1395 - # Keep the line numbers, discard extra context.
1396 - $summary =~ s/@@(.*?)@@.*/$1 /s;
1397 - $summary .= " " x (20 - length $summary);
1398 -
1399 - # Add some user context.
1400 - for my $line (@{$rhunk->{TEXT}}) {
1401 - if ($line =~ m/^[+-].*\w/) {
1402 - $summary .= $line;
1403 - last;
1404 - }
1405 - }
1406 -
1407 - chomp $summary;
1408 - return substr($summary, 0, 80) . "\n";
1409 -}
1410 -
1411 -
1412 -# Print a one-line summary of each hunk in the array ref in
1413 -# the first argument, starting with the index in the 2nd.
1414 -sub display_hunks {
1415 - my ($hunks, $i) = @_;
1416 - my $ctr = 0;
1417 - $i ||= 0;
1418 - for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1419 - my $status = " ";
1420 - if (defined $hunks->[$i]{USE}) {
1421 - $status = $hunks->[$i]{USE} ? "+" : "-";
1422 - }
1423 - printf "%s%2d: %s",
1424 - $status,
1425 - $i + 1,
1426 - summarize_hunk($hunks->[$i]);
1427 - }
1428 - return $i;
1429 -}
1430 -
1431 -my %patch_update_prompt_modes = (
1432 - stage => {
1433 - mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1434 - deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1435 - addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
1436 - hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
1437 - },
1438 - stash => {
1439 - mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1440 - deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1441 - addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
1442 - hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
1443 - },
1444 - reset_head => {
1445 - mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1446 - deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1447 - addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
1448 - hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
1449 - },
1450 - reset_nothead => {
1451 - mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1452 - deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1453 - addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
1454 - hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
1455 - },
1456 - checkout_index => {
1457 - mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1458 - deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1459 - addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1460 - hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1461 - },
1462 - checkout_head => {
1463 - mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1464 - deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1465 - addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
1466 - hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
1467 - },
1468 - checkout_nothead => {
1469 - mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1470 - deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1471 - addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
1472 - hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
1473 - },
1474 - worktree_head => {
1475 - mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1476 - deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1477 - addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1478 - hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1479 - },
1480 - worktree_nothead => {
1481 - mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1482 - deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1483 - addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
1484 - hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1485 - },
1486 -);
1487 -
1488 -sub patch_update_file {
1489 - my $quit = 0;
1490 - my ($ix, $num);
1491 - my $path = shift;
1492 - my ($head, @hunk) = parse_diff($path);
1493 - ($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
1494 - for (@{$head->{DISPLAY}}) {
1495 - print;
1496 - }
1497 -
1498 - if (@{$mode->{TEXT}}) {
1499 - unshift @hunk, $mode;
1500 - }
1501 - if (@{$deletion->{TEXT}}) {
1502 - foreach my $hunk (@hunk) {
1503 - push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1504 - push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1505 - }
1506 - @hunk = ($deletion);
1507 - }
1508 -
1509 - $num = scalar @hunk;
1510 - $ix = 0;
1511 -
1512 - while (1) {
1513 - my ($prev, $next, $other, $undecided, $i);
1514 - $other = '';
1515 -
1516 - last if ($ix and !$num);
1517 - if ($num <= $ix) {
1518 - $ix = 0;
1519 - }
1520 - for ($i = 0; $i < $ix; $i++) {
1521 - if (!defined $hunk[$i]{USE}) {
1522 - $prev = 1;
1523 - $other .= ',k';
1524 - last;
1525 - }
1526 - }
1527 - if ($ix) {
1528 - $other .= ',K';
1529 - }
1530 - for ($i = $ix + 1; $i < $num; $i++) {
1531 - if (!defined $hunk[$i]{USE}) {
1532 - $next = 1;
1533 - $other .= ',j';
1534 - last;
1535 - }
1536 - }
1537 - if ($ix < $num - 1) {
1538 - $other .= ',J';
1539 - }
1540 - if ($num > 1) {
1541 - $other .= ',g,/';
1542 - }
1543 - for ($i = 0; $i < $num; $i++) {
1544 - if (!defined $hunk[$i]{USE}) {
1545 - $undecided = 1;
1546 - last;
1547 - }
1548 - }
1549 - last if (!$undecided && ($num || !$addition));
1550 -
1551 - if ($num) {
1552 - if ($hunk[$ix]{TYPE} eq 'hunk' &&
1553 - hunk_splittable($hunk[$ix]{TEXT})) {
1554 - $other .= ',s';
1555 - }
1556 - if ($hunk[$ix]{TYPE} eq 'hunk') {
1557 - $other .= ',e';
1558 - }
1559 - for (@{$hunk[$ix]{DISPLAY}}) {
1560 - print;
1561 - }
1562 - }
1563 - my $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE};
1564 - print colored $prompt_color, "(", ($ix+1), "/", ($num ? $num : 1), ") ",
1565 - sprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other);
1566 -
1567 - my $line = prompt_single_character;
1568 - last unless defined $line;
1569 - if ($line) {
1570 - if ($line =~ /^y/i) {
1571 - if ($num) {
1572 - $hunk[$ix]{USE} = 1;
1573 - } else {
1574 - $head->{USE} = 1;
1575 - }
1576 - }
1577 - elsif ($line =~ /^n/i) {
1578 - if ($num) {
1579 - $hunk[$ix]{USE} = 0;
1580 - } else {
1581 - $head->{USE} = 0;
1582 - }
1583 - }
1584 - elsif ($line =~ /^a/i) {
1585 - if ($num) {
1586 - while ($ix < $num) {
1587 - if (!defined $hunk[$ix]{USE}) {
1588 - $hunk[$ix]{USE} = 1;
1589 - }
1590 - $ix++;
1591 - }
1592 - } else {
1593 - $head->{USE} = 1;
1594 - $ix++;
1595 - }
1596 - next;
1597 - }
1598 - elsif ($line =~ /^g(.*)/) {
1599 - my $response = $1;
1600 - unless ($other =~ /g/) {
1601 - error_msg __("No other hunks to goto\n");
1602 - next;
1603 - }
1604 - my $no = $ix > 10 ? $ix - 10 : 0;
1605 - while ($response eq '') {
1606 - $no = display_hunks(\@hunk, $no);
1607 - if ($no < $num) {
1608 - print __("go to which hunk (<ret> to see more)? ");
1609 - } else {
1610 - print __("go to which hunk? ");
1611 - }
1612 - $response = <STDIN>;
1613 - if (!defined $response) {
1614 - $response = '';
1615 - }
1616 - chomp $response;
1617 - }
1618 - if ($response !~ /^\s*\d+\s*$/) {
1619 - error_msg sprintf(__("Invalid number: '%s'\n"),
1620 - $response);
1621 - } elsif (0 < $response && $response <= $num) {
1622 - $ix = $response - 1;
1623 - } else {
1624 - error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1625 - "Sorry, only %d hunks available.\n", $num), $num);
1626 - }
1627 - next;
1628 - }
1629 - elsif ($line =~ /^d/i) {
1630 - if ($num) {
1631 - while ($ix < $num) {
1632 - if (!defined $hunk[$ix]{USE}) {
1633 - $hunk[$ix]{USE} = 0;
1634 - }
1635 - $ix++;
1636 - }
1637 - } else {
1638 - $head->{USE} = 0;
1639 - $ix++;
1640 - }
1641 - next;
1642 - }
1643 - elsif ($line =~ /^q/i) {
1644 - if ($num) {
1645 - for ($i = 0; $i < $num; $i++) {
1646 - if (!defined $hunk[$i]{USE}) {
1647 - $hunk[$i]{USE} = 0;
1648 - }
1649 - }
1650 - } elsif (!defined $head->{USE}) {
1651 - $head->{USE} = 0;
1652 - }
1653 - $quit = 1;
1654 - last;
1655 - }
1656 - elsif ($line =~ m|^/(.*)|) {
1657 - my $regex = $1;
1658 - unless ($other =~ m|/|) {
1659 - error_msg __("No other hunks to search\n");
1660 - next;
1661 - }
1662 - if ($regex eq "") {
1663 - print colored $prompt_color, __("search for regex? ");
1664 - $regex = <STDIN>;
1665 - if (defined $regex) {
1666 - chomp $regex;
1667 - }
1668 - }
1669 - my $search_string;
1670 - eval {
1671 - $search_string = qr{$regex}m;
1672 - };
1673 - if ($@) {
1674 - my ($err,$exp) = ($@, $1);
1675 - $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1676 - error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
1677 - next;
1678 - }
1679 - my $iy = $ix;
1680 - while (1) {
1681 - my $text = join ("", @{$hunk[$iy]{TEXT}});
1682 - last if ($text =~ $search_string);
1683 - $iy++;
1684 - $iy = 0 if ($iy >= $num);
1685 - if ($ix == $iy) {
1686 - error_msg __("No hunk matches the given pattern\n");
1687 - last;
1688 - }
1689 - }
1690 - $ix = $iy;
1691 - next;
1692 - }
1693 - elsif ($line =~ /^K/) {
1694 - if ($other =~ /K/) {
1695 - $ix--;
1696 - }
1697 - else {
1698 - error_msg __("No previous hunk\n");
1699 - }
1700 - next;
1701 - }
1702 - elsif ($line =~ /^J/) {
1703 - if ($other =~ /J/) {
1704 - $ix++;
1705 - }
1706 - else {
1707 - error_msg __("No next hunk\n");
1708 - }
1709 - next;
1710 - }
1711 - elsif ($line =~ /^k/) {
1712 - if ($other =~ /k/) {
1713 - while (1) {
1714 - $ix--;
1715 - last if (!$ix ||
1716 - !defined $hunk[$ix]{USE});
1717 - }
1718 - }
1719 - else {
1720 - error_msg __("No previous hunk\n");
1721 - }
1722 - next;
1723 - }
1724 - elsif ($line =~ /^j/) {
1725 - if ($other !~ /j/) {
1726 - error_msg __("No next hunk\n");
1727 - next;
1728 - }
1729 - }
1730 - elsif ($line =~ /^s/) {
1731 - unless ($other =~ /s/) {
1732 - error_msg __("Sorry, cannot split this hunk\n");
1733 - next;
1734 - }
1735 - my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
1736 - if (1 < @split) {
1737 - print colored $header_color, sprintf(
1738 - __n("Split into %d hunk.\n",
1739 - "Split into %d hunks.\n",
1740 - scalar(@split)), scalar(@split));
1741 - }
1742 - splice (@hunk, $ix, 1, @split);
1743 - $num = scalar @hunk;
1744 - next;
1745 - }
1746 - elsif ($line =~ /^e/) {
1747 - unless ($other =~ /e/) {
1748 - error_msg __("Sorry, cannot edit this hunk\n");
1749 - next;
1750 - }
1751 - my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1752 - if (defined $newhunk) {
1753 - splice @hunk, $ix, 1, $newhunk;
1754 - }
1755 - }
1756 - else {
1757 - help_patch_cmd($other);
1758 - next;
1759 - }
1760 - # soft increment
1761 - while (1) {
1762 - $ix++;
1763 - last if ($ix >= $num ||
1764 - !defined $hunk[$ix]{USE});
1765 - }
1766 - }
1767 - }
1768 -
1769 - @hunk = coalesce_overlapping_hunks(@hunk) if ($num);
1770 -
1771 - my $n_lofs = 0;
1772 - my @result = ();
1773 - for (@hunk) {
1774 - if ($_->{USE}) {
1775 - push @result, @{$_->{TEXT}};
1776 - }
1777 - }
1778 -
1779 - if (@result or $head->{USE}) {
1780 - my @patch = reassemble_patch($head->{TEXT}, @result);
1781 - my $apply_routine = $patch_mode_flavour{APPLY};
1782 - &$apply_routine(@patch);
1783 - refresh();
1784 - }
1785 -
1786 - print "\n";
1787 - return $quit;
1788 -}
1789 -
1790 -sub diff_cmd {
1791 - my @mods = list_modified('index-only');
1792 - @mods = grep { !($_->{BINARY}) } @mods;
1793 - return if (!@mods);
1794 - my (@them) = list_and_choose({ PROMPT => __('Review diff'),
1795 - IMMEDIATE => 1,
1796 - HEADER => $status_head, },
1797 - @mods);
1798 - return if (!@them);
1799 - my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
1800 - system(qw(git diff -p --cached), $reference, '--',
1801 - map { $_->{VALUE} } @them);
1802 -}
1803 -
1804 -sub quit_cmd {
1805 - print __("Bye.\n");
1806 - exit(0);
1807 -}
1808 -
1809 -sub help_cmd {
1810 -# TRANSLATORS: please do not translate the command names
1811 -# 'status', 'update', 'revert', etc.
1812 - print colored $help_color, __ <<'EOF' ;
1813 -status - show paths with changes
1814 -update - add working tree state to the staged set of changes
1815 -revert - revert staged set of changes back to the HEAD version
1816 -patch - pick hunks and update selectively
1817 -diff - view diff between HEAD and index
1818 -add untracked - add contents of untracked files to the staged set of changes
1819 -EOF
1820 -}
1821 -
1822 -sub process_args {
1823 - return unless @ARGV;
1824 - my $arg = shift @ARGV;
1825 - if ($arg =~ /--patch(?:=(.*))?/) {
1826 - if (defined $1) {
1827 - if ($1 eq 'reset') {
1828 - $patch_mode = 'reset_head';
1829 - $patch_mode_revision = 'HEAD';
1830 - $arg = shift @ARGV or die __("missing --");
1831 - if ($arg ne '--') {
1832 - $patch_mode_revision = $arg;
1833 -
1834 - # NEEDSWORK: Instead of comparing to the literal "HEAD",
1835 - # compare the commit objects instead so that other ways of
1836 - # saying the same thing (such as "@") are also handled
1837 - # appropriately.
1838 - #
1839 - # This applies to the cases below too.
1840 - $patch_mode = ($arg eq 'HEAD' ?
1841 - 'reset_head' : 'reset_nothead');
1842 - $arg = shift @ARGV or die __("missing --");
1843 - }
1844 - } elsif ($1 eq 'checkout') {
1845 - $arg = shift @ARGV or die __("missing --");
1846 - if ($arg eq '--') {
1847 - $patch_mode = 'checkout_index';
1848 - } else {
1849 - $patch_mode_revision = $arg;
1850 - $patch_mode = ($arg eq 'HEAD' ?
1851 - 'checkout_head' : 'checkout_nothead');
1852 - $arg = shift @ARGV or die __("missing --");
1853 - }
1854 - } elsif ($1 eq 'worktree') {
1855 - $arg = shift @ARGV or die __("missing --");
1856 - if ($arg eq '--') {
1857 - $patch_mode = 'checkout_index';
1858 - } else {
1859 - $patch_mode_revision = $arg;
1860 - $patch_mode = ($arg eq 'HEAD' ?
1861 - 'worktree_head' : 'worktree_nothead');
1862 - $arg = shift @ARGV or die __("missing --");
1863 - }
1864 - } elsif ($1 eq 'stage' or $1 eq 'stash') {
1865 - $patch_mode = $1;
1866 - $arg = shift @ARGV or die __("missing --");
1867 - } else {
1868 - die sprintf(__("unknown --patch mode: %s"), $1);
1869 - }
1870 - } else {
1871 - $patch_mode = 'stage';
1872 - $arg = shift @ARGV or die __("missing --");
1873 - }
1874 - die sprintf(__("invalid argument %s, expecting --"),
1875 - $arg) unless $arg eq "--";
1876 - %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1877 - $patch_mode_only = 1;
1878 - }
1879 - elsif ($arg ne "--") {
1880 - die sprintf(__("invalid argument %s, expecting --"), $arg);
1881 - }
1882 -}
1883 -
1884 -sub main_loop {
1885 - my @cmd = ([ 'status', \&status_cmd, ],
1886 - [ 'update', \&update_cmd, ],
1887 - [ 'revert', \&revert_cmd, ],
1888 - [ 'add untracked', \&add_untracked_cmd, ],
1889 - [ 'patch', \&patch_update_cmd, ],
1890 - [ 'diff', \&diff_cmd, ],
1891 - [ 'quit', \&quit_cmd, ],
1892 - [ 'help', \&help_cmd, ],
1893 - );
1894 - while (1) {
1895 - my ($it) = list_and_choose({ PROMPT => __('What now'),
1896 - SINGLETON => 1,
1897 - LIST_FLAT => 4,
1898 - HEADER => __('*** Commands ***'),
1899 - ON_EOF => \&quit_cmd,
1900 - IMMEDIATE => 1 }, @cmd);
1901 - if ($it) {
1902 - eval {
1903 - $it->[1]->();
1904 - };
1905 - if ($@) {
1906 - print "$@";
1907 - }
1908 - }
1909 - }
1910 -}
1911 -
1912 -process_args();
1913 -refresh();
1914 -if ($patch_mode_only) {
1915 - patch_update_cmd();
1916 -}
1917 -else {
1918 - status_cmd();
1919 - main_loop();
1920 -}
t/README
-4
@@ -449,10 +449,6 @@ the --sparse command-line argument.
449 GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path
450 by overriding the minimum number of cache entries required per thread.
451
452 -GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when false, disables the
453 -built-in version of git add -i. See 'add.interactive.useBuiltin' in
454 -git-config(1).
455 -
452 GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading
453 of the index for the whole test suite by bypassing the default number of
454 cache entries and thread minimums. Setting this to 1 will make the
t/t2016-checkout-patch.sh
-6
@@ -4,12 +4,6 @@ test_description='git checkout --patch'
4
5 . ./lib-patch-mode.sh
6
7 -if ! test_have_prereq ADD_I_USE_BUILTIN && ! test_have_prereq PERL
8 -then
9 - skip_all='skipping interactive add tests, PERL not set'
10 - test_done
11 -fi
12 -
7 test_expect_success 'setup' '
8 mkdir dir &&
9 echo parent > dir/foo &&
t/t3701-add-interactive.sh
+18 -11
@@ -7,7 +7,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
7 . ./test-lib.sh
8 . "$TEST_DIRECTORY"/lib-terminal.sh
9
10 -if test_have_prereq !ADD_I_USE_BUILTIN,!PERL
10 +if test_have_prereq !PERL
11 then
12 skip_all='skipping add -i (scripted) tests, perl not available'
13 test_done
@@ -46,6 +46,21 @@ force_color () {
46 )
47 }
48
49 +test_expect_success 'warn about add.interactive.useBuiltin' '
50 + cat >expect <<-\EOF &&
51 + warning: the add.interactive.useBuiltin setting has been removed!
52 + See its entry in '\''git help config'\'' for details.
53 + No changes.
54 + EOF
55 +
56 + for v in = =true =false
57 + do
58 + git -c "add.interactive.useBuiltin$v" add -p >out 2>actual &&
59 + test_must_be_empty out &&
60 + test_cmp expect actual || return 1
61 + done
62 +'
63 +
64 test_expect_success 'setup (initial)' '
65 echo content >file &&
66 git add file &&
@@ -547,15 +562,7 @@ test_expect_success 'split hunk "add -p (edit)"' '
562 ! grep "^+15" actual
563 '
564
550 -test_expect_success 'setup ADD_I_USE_BUILTIN check' '
551 - result=success &&
552 - if ! test_have_prereq ADD_I_USE_BUILTIN
553 - then
554 - result=failure
555 - fi
556 -'
557 -
558 -test_expect_$result 'split hunk "add -p (no, yes, edit)"' '
565 +test_expect_success 'split hunk "add -p (no, yes, edit)"' '
566 test_write_lines 5 10 20 21 30 31 40 50 60 >test &&
567 git reset &&
568 # test sequence is s(plit), n(o), y(es), e(dit)
@@ -579,7 +586,7 @@ test_expect_success 'split hunk with incomplete line at end' '
586 test_must_fail git grep --cached before
587 '
588
582 -test_expect_$result 'edit, adding lines to the first hunk' '
589 +test_expect_success 'edit, adding lines to the first hunk' '
590 test_write_lines 10 11 20 30 40 50 51 60 >test &&
591 git reset &&
592 tr _ " " >patch <<-EOF &&
t/t6132-pathspec-exclude.sh
+1 -5
@@ -293,11 +293,7 @@ test_expect_success 'add with all negative' '
293 test_cmp expect actual
294 '
295
296 -test_lazy_prereq ADD_I_USE_BUILTIN_OR_PERL '
297 - test_have_prereq ADD_I_USE_BUILTIN || test_have_prereq PERL
298 -'
299 -
300 -test_expect_success ADD_I_USE_BUILTIN_OR_PERL 'add -p with all negative' '
296 +test_expect_success 'add -p with all negative' '
297 H=$(git rev-parse HEAD) &&
298 git reset --hard $H &&
299 git clean -f &&
t/test-lib.sh
-4
@@ -1937,10 +1937,6 @@ test_lazy_prereq SHA1 '
1937 esac
1938 '
1939
1940 -test_lazy_prereq ADD_I_USE_BUILTIN '
1941 - test_bool_env GIT_TEST_ADD_I_USE_BUILTIN true
1942 -'
1943 -
1940 # Ensure that no test accidentally triggers a Git command
1941 # that runs the actual maintenance scheduler, affecting a user's
1942 # system permanently.