6 use Git
qw(unquote_path);
9 binmode(STDOUT
, ":raw");
11 my $repo = Git
->repository();
13 my $menu_use_color = $repo->get_colorbool('color.interactive');
14 my ($prompt_color, $header_color, $help_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'),
21 if ($menu_use_color) {
22 my $help_color_spec = ($repo->config('color.interactive.help') or
24 $error_color = $repo->get_color('color.interactive.error',
28 my $diff_use_color = $repo->get_colorbool('color.diff');
29 my ($fraginfo_color) =
31 $repo->get_color('color.diff.frag', 'cyan'),
33 my ($diff_plain_color) =
35 $repo->get_color('color.diff.plain', ''),
37 my ($diff_old_color) =
39 $repo->get_color('color.diff.old', 'red'),
41 my ($diff_new_color) =
43 $repo->get_color('color.diff.new', 'green'),
46 my $normal_color = $repo->get_color("", "reset");
48 my $diff_algorithm = $repo->config('diff.algorithm');
49 my $diff_filter = $repo->config('interactive.difffilter');
57 if ($repo->config_bool("interactive.singlekey")) {
59 require Term
::ReadKey
;
60 Term
::ReadKey
->import;
64 print STDERR
"missing Term::ReadKey, disabling interactive.singlekey\n";
68 my $termcap = Term
::Cap
->Tgetent;
69 foreach (values %$termcap) {
70 $term_escapes{$_} = 1 if /^\e/;
78 my $string = join("", @_);
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$/;
93 # command line options
96 my $patch_mode_revision;
99 sub apply_patch_for_checkout_commit
;
100 sub apply_patch_for_stash
;
104 DIFF
=> 'diff-files -p',
105 APPLY
=> sub { apply_patch
'apply --cached', @_; },
106 APPLY_CHECK
=> 'apply --cached',
107 FILTER
=> 'file-only',
111 DIFF
=> 'diff-index -p HEAD',
112 APPLY
=> sub { apply_patch
'apply --cached', @_; },
113 APPLY_CHECK
=> 'apply --cached',
118 DIFF
=> 'diff-index -p --cached',
119 APPLY
=> sub { apply_patch
'apply -R --cached', @_; },
120 APPLY_CHECK
=> 'apply -R --cached',
121 FILTER
=> 'index-only',
125 DIFF
=> 'diff-index -R -p --cached',
126 APPLY
=> sub { apply_patch
'apply --cached', @_; },
127 APPLY_CHECK
=> 'apply --cached',
128 FILTER
=> 'index-only',
131 'checkout_index' => {
132 DIFF
=> 'diff-files -p',
133 APPLY
=> sub { apply_patch
'apply -R', @_; },
134 APPLY_CHECK
=> 'apply -R',
135 FILTER
=> 'file-only',
139 DIFF
=> 'diff-index -p',
140 APPLY
=> sub { apply_patch_for_checkout_commit
'-R', @_ },
141 APPLY_CHECK
=> 'apply -R',
145 'checkout_nothead' => {
146 DIFF
=> 'diff-index -R -p',
147 APPLY
=> sub { apply_patch_for_checkout_commit
'', @_ },
148 APPLY_CHECK
=> 'apply',
154 $patch_mode = 'stage';
155 my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
158 if ($^O
eq 'MSWin32') {
159 my @invalid = grep {m/[":*]/} @_;
160 die "$^O does not support: @invalid\n" if @invalid;
161 my @args = map { m/ /o ?
"\"$_\"": $_ } @_;
165 open($fh, '-|', @_) or die;
170 my ($GIT_DIR) = run_cmd_pipe
(qw(git rev-parse --git-dir));
172 if (!defined $GIT_DIR) {
173 exit(1); # rev-parse would have already said "not a git repo"
179 open $fh, 'git update-index --refresh |'
182 ;# ignore 'needs update'
192 run_cmd_pipe
(qw(git ls-files --others --exclude-standard --), @ARGV);
195 # TRANSLATORS: you can adjust this to align "git add -i" status menu
196 my $status_fmt = __
('%12s %12s %s');
197 my $status_head = sprintf($status_fmt, __
('staged'), __
('unstaged'), __
('path'));
201 sub is_initial_commit
{
202 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
203 unless defined $initial;
209 return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
212 sub get_diff_reference
{
214 if (defined $ref and $ref ne 'HEAD') {
216 } elsif (is_initial_commit
()) {
217 return get_empty_tree
();
223 # Returns list of hashes, contents of each of which are:
225 # BINARY: is a binary path
226 # INDEX: is index different from HEAD?
227 # FILE: is file different from index?
228 # INDEX_ADDDEL: is it add/delete between HEAD and index?
229 # FILE_ADDDEL: is it add/delete between index and file?
230 # UNMERGED: is the path unmerged
235 my ($add, $del, $adddel, $file);
237 my $reference = get_diff_reference
($patch_mode_revision);
238 for (run_cmd_pipe
(qw(git diff-index --cached
239 --numstat --summary), $reference,
241 if (($add, $del, $file) =
242 /^([-\d]+) ([-\d]+) (.*)/) {
244 $file = unquote_path
($file);
245 if ($add eq '-' && $del eq '-') {
246 $change = __
('binary');
250 $change = "+$add/-$del";
255 FILE
=> __
('nothing'),
258 elsif (($adddel, $file) =
259 /^ (create|delete) mode [0-7]+ (.*)$/) {
260 $file = unquote_path
($file);
261 $data{$file}{INDEX_ADDDEL
} = $adddel;
265 for (run_cmd_pipe
(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
266 if (($add, $del, $file) =
267 /^([-\d]+) ([-\d]+) (.*)/) {
268 $file = unquote_path
($file);
270 if ($add eq '-' && $del eq '-') {
271 $change = __
('binary');
275 $change = "+$add/-$del";
277 $data{$file}{FILE
} = $change;
279 $data{$file}{BINARY
} = 1;
282 elsif (($adddel, $file) =
283 /^ (create|delete) mode [0-7]+ (.*)$/) {
284 $file = unquote_path
($file);
285 $data{$file}{FILE_ADDDEL
} = $adddel;
287 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
288 $file = unquote_path
($2);
289 if (!exists $data{$file}) {
291 INDEX
=> __
('unchanged'),
296 $data{$file}{UNMERGED
} = 1;
301 for (sort keys %data) {
305 if ($only eq 'index-only') {
306 next if ($it->{INDEX
} eq __
('unchanged'));
308 if ($only eq 'file-only') {
309 next if ($it->{FILE
} eq __
('nothing'));
321 my ($string, @stuff) = @_;
323 for (my $i = 0; $i < @stuff; $i++) {
327 if ((ref $it) eq 'ARRAY') {
335 if ($it =~ /^$string/) {
339 if (defined $hit && defined $found) {
349 # inserts string into trie and updates count for each character
351 my ($trie, $string) = @_;
352 foreach (split //, $string) {
353 $trie = $trie->{$_} ||= {COUNT
=> 0};
358 # returns an array of tuples (prefix, remainder)
359 sub find_unique_prefixes
{
363 # any single prefix exceeding the soft limit is omitted
364 # if any prefix exceeds the hard limit all are omitted
365 # 0 indicates no limit
369 # build a trie modelling all possible options
371 foreach my $print (@stuff) {
372 if ((ref $print) eq 'ARRAY') {
373 $print = $print->[0];
375 elsif ((ref $print) eq 'HASH') {
376 $print = $print->{VALUE
};
378 update_trie
(\
%trie, $print);
379 push @return, $print;
382 # use the trie to find the unique prefixes
383 for (my $i = 0; $i < @return; $i++) {
384 my $ret = $return[$i];
385 my @letters = split //, $ret;
387 my ($prefix, $remainder);
389 for ($j = 0; $j < @letters; $j++) {
390 my $letter = $letters[$j];
391 if ($search{$letter}{COUNT
} == 1) {
392 $prefix = substr $ret, 0, $j + 1;
393 $remainder = substr $ret, $j + 1;
397 my $prefix = substr $ret, 0, $j;
399 if ($hard_limit && $j + 1 > $hard_limit);
401 %search = %{$search{$letter}};
403 if (ord($letters[0]) > 127 ||
404 ($soft_limit && $j + 1 > $soft_limit)) {
408 $return[$i] = [$prefix, $remainder];
413 # filters out prefixes which have special meaning to list_and_choose()
414 sub is_valid_prefix
{
416 return (defined $prefix) &&
417 !($prefix =~ /[\s,]/) && # separators
418 !($prefix =~ /^-/) && # deselection
419 !($prefix =~ /^\d+/) && # selection
420 ($prefix ne '*') && # "all" wildcard
421 ($prefix ne '?'); # prompt help
424 # given a prefix/remainder tuple return a string with the prefix highlighted
425 # for now use square brackets; later might use ANSI colors (underline, bold)
426 sub highlight_prefix
{
428 my $remainder = shift;
430 if (!defined $prefix) {
434 if (!is_valid_prefix
($prefix)) {
435 return "$prefix$remainder";
438 if (!$menu_use_color) {
439 return "[$prefix]$remainder";
442 return "$prompt_color$prefix$normal_color$remainder";
446 print STDERR colored
$error_color, @_;
449 sub list_and_choose
{
450 my ($opts, @stuff) = @_;
451 my (@chosen, @return);
456 my @prefixes = find_unique_prefixes
(@stuff) unless $opts->{LIST_ONLY
};
462 if ($opts->{HEADER
}) {
463 if (!$opts->{LIST_FLAT
}) {
466 print colored
$header_color, "$opts->{HEADER}\n";
468 for ($i = 0; $i < @stuff; $i++) {
469 my $chosen = $chosen[$i] ?
'*' : ' ';
470 my $print = $stuff[$i];
471 my $ref = ref $print;
472 my $highlighted = highlight_prefix
(@
{$prefixes[$i]})
474 if ($ref eq 'ARRAY') {
475 $print = $highlighted || $print->[0];
477 elsif ($ref eq 'HASH') {
478 my $value = $highlighted || $print->{VALUE
};
479 $print = sprintf($status_fmt,
485 $print = $highlighted || $print;
487 printf("%s%2d: %s", $chosen, $i+1, $print);
488 if (($opts->{LIST_FLAT
}) &&
489 (($i + 1) % ($opts->{LIST_FLAT
}))) {
502 return if ($opts->{LIST_ONLY
});
504 print colored
$prompt_color, $opts->{PROMPT
};
505 if ($opts->{SINGLETON
}) {
514 $opts->{ON_EOF
}->() if $opts->{ON_EOF
};
521 singleton_prompt_help_cmd
() :
525 for my $choice (split(/[\s,]+/, $line)) {
529 # Input that begins with '-'; unchoose
530 if ($choice =~ s/^-//) {
533 # A range can be specified like 5-7 or 5-.
534 if ($choice =~ /^(\d+)-(\d*)$/) {
535 ($bottom, $top) = ($1, length($2) ?
$2 : 1 + @stuff);
537 elsif ($choice =~ /^\d+$/) {
538 $bottom = $top = $choice;
540 elsif ($choice eq '*') {
545 $bottom = $top = find_unique
($choice, @stuff);
546 if (!defined $bottom) {
547 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
551 if ($opts->{SINGLETON
} && $bottom != $top) {
552 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
555 for ($i = $bottom-1; $i <= $top-1; $i++) {
556 next if (@stuff <= $i || $i < 0);
557 $chosen[$i] = $choose;
560 last if ($opts->{IMMEDIATE
} || $line eq '*');
562 for ($i = 0; $i < @stuff; $i++) {
564 push @return, $stuff[$i];
570 sub singleton_prompt_help_cmd
{
571 print colored
$help_color, __
<<'EOF' ;
573 1 - select a numbered item
574 foo - select item based on unique prefix
575 - (empty) select nothing
579 sub prompt_help_cmd
{
580 print colored
$help_color, __
<<'EOF' ;
582 1 - select a single item
583 3-5 - select a range of items
584 2-3,6-9 - select multiple ranges
585 foo - select item based on unique prefix
586 -... - unselect specified items
588 - (empty) finish selecting
593 list_and_choose
({ LIST_ONLY
=> 1, HEADER
=> $status_head },
601 if ($did eq 'added') {
602 printf(__n
("added %d path\n", "added %d paths\n",
604 } elsif ($did eq 'updated') {
605 printf(__n
("updated %d path\n", "updated %d paths\n",
607 } elsif ($did eq 'reverted') {
608 printf(__n
("reverted %d path\n", "reverted %d paths\n",
611 printf(__n
("touched %d path\n", "touched %d paths\n",
617 my @mods = list_modified
('file-only');
620 my @update = list_and_choose
({ PROMPT
=> __
('Update'),
621 HEADER
=> $status_head, },
624 system(qw(git update-index --add --remove --),
625 map { $_->{VALUE
} } @update);
626 say_n_paths
('updated', @update);
632 my @update = list_and_choose
({ PROMPT
=> __
('Revert'),
633 HEADER
=> $status_head, },
636 if (is_initial_commit
()) {
637 system(qw(git rm --cached),
638 map { $_->{VALUE
} } @update);
641 my @lines = run_cmd_pipe
(qw(git ls-tree HEAD --),
642 map { $_->{VALUE
} } @update);
644 open $fh, '| git update-index --index-info'
651 if ($_->{INDEX_ADDDEL
} &&
652 $_->{INDEX_ADDDEL
} eq 'create') {
653 system(qw(git update-index --force-remove --),
655 printf(__
("note: %s is untracked now.\n"), $_->{VALUE
});
660 say_n_paths
('reverted', @update);
665 sub add_untracked_cmd
{
666 my @add = list_and_choose
({ PROMPT
=> __
('Add untracked') },
669 system(qw(git update-index --add --), @add);
670 say_n_paths
('added', @add);
672 print __
("No untracked files.\n");
680 open $fh, '| git ' . $cmd . " --recount --allow-overlap";
687 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF
});
688 if (defined $diff_algorithm) {
689 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
691 if (defined $patch_mode_revision) {
692 push @diff_cmd, get_diff_reference
($patch_mode_revision);
694 my @diff = run_cmd_pipe
("git", @diff_cmd, "--", $path);
696 if ($diff_use_color) {
697 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
698 if (defined $diff_filter) {
699 # quotemeta is overkill, but sufficient for shell-quoting
700 my $diff = join(' ', map { quotemeta } @display_cmd);
701 @display_cmd = ("$diff | $diff_filter");
704 @colored = run_cmd_pipe
(@display_cmd);
706 my (@hunk) = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
708 if (@colored && @colored != @diff) {
710 "fatal: mismatched output from interactive.diffFilter\n",
711 "hint: Your filter must maintain a one-to-one correspondence\n",
712 "hint: between its input and output lines.\n";
716 for (my $i = 0; $i < @diff; $i++) {
717 if ($diff[$i] =~ /^@@ /) {
718 push @hunk, { TEXT
=> [], DISPLAY
=> [],
721 push @
{$hunk[-1]{TEXT
}}, $diff[$i];
722 push @
{$hunk[-1]{DISPLAY
}},
723 (@colored ?
$colored[$i] : $diff[$i]);
728 sub parse_diff_header
{
731 my $head = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
732 my $mode = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'mode' };
733 my $deletion = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'deletion' };
735 for (my $i = 0; $i < @
{$src->{TEXT
}}; $i++) {
737 $src->{TEXT
}->[$i] =~ /^(old|new) mode (\d+)$/ ?
$mode :
738 $src->{TEXT
}->[$i] =~ /^deleted file/ ?
$deletion :
740 push @
{$dest->{TEXT
}}, $src->{TEXT
}->[$i];
741 push @
{$dest->{DISPLAY
}}, $src->{DISPLAY
}->[$i];
743 return ($head, $mode, $deletion);
746 sub hunk_splittable
{
749 my @s = split_hunk
($text);
753 sub parse_hunk_header
{
755 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
756 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
757 $o_cnt = 1 unless defined $o_cnt;
758 $n_cnt = 1 unless defined $n_cnt;
759 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
763 my ($text, $display) = @_;
765 if (!defined $display) {
768 # If there are context lines in the middle of a hunk,
769 # it can be split, but we would need to take care of
772 my ($o_ofs, undef, $n_ofs) = parse_hunk_header
($text->[0]);
777 my $next_hunk_start = undef;
778 my $i = $hunk_start - 1;
792 while (++$i < @
$text) {
793 my $line = $text->[$i];
794 my $display = $display->[$i];
796 if ($this->{ADDDEL
} &&
797 !defined $next_hunk_start) {
798 # We have seen leading context and
799 # adds/dels and then here is another
800 # context, which is trailing for this
801 # split hunk and leading for the next
803 $next_hunk_start = $i;
805 push @
{$this->{TEXT
}}, $line;
806 push @
{$this->{DISPLAY
}}, $display;
809 if (defined $next_hunk_start) {
816 if (defined $next_hunk_start) {
817 # We are done with the current hunk and
818 # this is the first real change for the
820 $hunk_start = $next_hunk_start;
821 $o_ofs = $this->{OLD
} + $this->{OCNT
};
822 $n_ofs = $this->{NEW
} + $this->{NCNT
};
823 $o_ofs -= $this->{POSTCTX
};
824 $n_ofs -= $this->{POSTCTX
};
828 push @
{$this->{TEXT
}}, $line;
829 push @
{$this->{DISPLAY
}}, $display;
843 for my $hunk (@split) {
844 $o_ofs = $hunk->{OLD
};
845 $n_ofs = $hunk->{NEW
};
846 my $o_cnt = $hunk->{OCNT
};
847 my $n_cnt = $hunk->{NCNT
};
849 my $head = ("@@ -$o_ofs" .
850 (($o_cnt != 1) ?
",$o_cnt" : '') .
852 (($n_cnt != 1) ?
",$n_cnt" : '') .
854 my $display_head = $head;
855 unshift @
{$hunk->{TEXT
}}, $head;
856 if ($diff_use_color) {
857 $display_head = colored
($fraginfo_color, $head);
859 unshift @
{$hunk->{DISPLAY
}}, $display_head;
864 sub find_last_o_ctx
{
866 my $text = $it->{TEXT
};
867 my ($o_ofs, $o_cnt) = parse_hunk_header
($text->[0]);
869 my $last_o_ctx = $o_ofs + $o_cnt;
871 my $line = $text->[$i];
882 my ($prev, $this) = @_;
883 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
884 parse_hunk_header
($prev->{TEXT
}[0]);
885 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
886 parse_hunk_header
($this->{TEXT
}[0]);
888 my (@line, $i, $ofs, $o_cnt, $n_cnt);
891 for ($i = 1; $i < @
{$prev->{TEXT
}}; $i++) {
892 my $line = $prev->{TEXT
}[$i];
893 if ($line =~ /^\+/) {
899 last if ($o1_ofs <= $ofs);
909 for ($i = 1; $i < @
{$this->{TEXT
}}; $i++) {
910 my $line = $this->{TEXT
}[$i];
911 if ($line =~ /^\+/) {
923 my $head = ("@@ -$o0_ofs" .
924 (($o_cnt != 1) ?
",$o_cnt" : '') .
926 (($n_cnt != 1) ?
",$n_cnt" : '') .
928 @
{$prev->{TEXT
}} = ($head, @line);
931 sub coalesce_overlapping_hunks
{
935 my ($last_o_ctx, $last_was_dirty);
937 for (grep { $_->{USE
} } @in) {
938 if ($_->{TYPE
} ne 'hunk') {
942 my $text = $_->{TEXT
};
943 my ($o_ofs) = parse_hunk_header
($text->[0]);
944 if (defined $last_o_ctx &&
945 $o_ofs <= $last_o_ctx &&
948 merge_hunk
($out[-1], $_);
953 $last_o_ctx = find_last_o_ctx
($out[-1]);
954 $last_was_dirty = $_->{DIRTY
};
959 sub reassemble_patch
{
963 # Include everything in the header except the beginning of the diff.
964 push @patch, (grep { !/^[-+]{3}/ } @
$head);
966 # Then include any headers from the hunk lines, which must
967 # come before any actual hunk.
968 while (@_ && $_[0] !~ /^@/) {
972 # Then begin the diff.
973 push @patch, grep { /^[-+]{3}/ } @
$head;
975 # And then the actual hunks.
983 colored
((/^@/ ?
$fraginfo_color :
984 /^\+/ ?
$diff_new_color :
985 /^-/ ?
$diff_old_color :
991 my %edit_hunk_manually_modes = (
993 "If the patch applies cleanly, the edited hunk will immediately be
994 marked for staging."),
996 "If the patch applies cleanly, the edited hunk will immediately be
997 marked for stashing."),
999 "If the patch applies cleanly, the edited hunk will immediately be
1000 marked for unstaging."),
1001 reset_nothead
=> N__
(
1002 "If the patch applies cleanly, the edited hunk will immediately be
1003 marked for applying."),
1004 checkout_index
=> N__
(
1005 "If the patch applies cleanly, the edited hunk will immediately be
1006 marked for discarding."),
1007 checkout_head
=> N__
(
1008 "If the patch applies cleanly, the edited hunk will immediately be
1009 marked for discarding."),
1010 checkout_nothead
=> N__
(
1011 "If the patch applies cleanly, the edited hunk will immediately be
1012 marked for applying."),
1015 sub edit_hunk_manually
{
1018 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1020 open $fh, '>', $hunkfile
1021 or die sprintf(__
("failed to open hunk edit file for writing: %s"), $!);
1022 print $fh Git
::comment_lines __
("Manual hunk edit mode -- see bottom for a quick guide.\n");
1023 print $fh @
$oldtext;
1024 my $is_reverse = $patch_mode_flavour{IS_REVERSE
};
1025 my ($remove_plus, $remove_minus) = $is_reverse ?
('-', '+') : ('+', '-');
1026 my $comment_line_char = Git
::get_comment_line_char
;
1027 print $fh Git
::comment_lines
sprintf(__
<<EOF, $remove_minus, $remove_plus, $comment_line_char),
1029 To remove '%s' lines, make them ' ' lines (context).
1030 To remove '%s' lines, delete them.
1031 Lines starting with %s will be removed.
1033 __
($edit_hunk_manually_modes{$patch_mode}),
1034 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1036 If it does not apply cleanly, you will be given an opportunity to
1037 edit again. If all lines of the hunk are removed, then the edit is
1038 aborted and the hunk is left unchanged.
1042 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
1043 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1049 open $fh, '<', $hunkfile
1050 or die sprintf(__
("failed to open hunk edit file for reading: %s"), $!);
1051 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1055 # Abort if nothing remains
1056 if (!grep { /\S/ } @newtext) {
1060 # Reinsert the first hunk header if the user accidentally deleted it
1061 if ($newtext[0] !~ /^@/) {
1062 unshift @newtext, $oldtext->[0];
1068 return run_git_apply
($patch_mode_flavour{APPLY_CHECK
} . ' --check',
1069 map { @
{$_->{TEXT
}} } @_);
1072 sub _restore_terminal_and_die
{
1078 sub prompt_single_character
{
1080 local $SIG{TERM
} = \
&_restore_terminal_and_die
;
1081 local $SIG{INT
} = \
&_restore_terminal_and_die
;
1083 my $key = ReadKey
0;
1085 if ($use_termcap and $key eq "\e") {
1086 while (!defined $term_escapes{$key}) {
1087 my $next = ReadKey
0.5;
1088 last if (!defined $next);
1093 print "$key" if defined $key;
1104 print colored
$prompt_color, $prompt;
1105 my $line = prompt_single_character
;
1106 return undef unless defined $line;
1107 return 0 if $line =~ /^n/i;
1108 return 1 if $line =~ /^y/i;
1112 sub edit_hunk_loop
{
1113 my ($head, $hunk, $ix) = @_;
1114 my $text = $hunk->[$ix]->{TEXT
};
1117 $text = edit_hunk_manually
($text);
1118 if (!defined $text) {
1123 TYPE
=> $hunk->[$ix]->{TYPE
},
1127 if (diff_applies
($head,
1130 @
{$hunk}[$ix+1..$#{$hunk}])) {
1131 $newhunk->{DISPLAY
} = [color_diff
(@
{$text})];
1136 # TRANSLATORS: do not translate [y/n]
1137 # The program will only accept that input
1139 # Consider translating (saying "no" discards!) as
1140 # (saying "n" for "no" discards!) if the translation
1141 # of the word "no" does not start with n.
1142 __
('Your edited hunk does not apply. Edit again '
1143 . '(saying "no" discards!) [y/n]? ')
1149 my %help_patch_modes = (
1151 "y - stage this hunk
1152 n - do not stage this hunk
1153 q - quit; do not stage this hunk or any of the remaining ones
1154 a - stage this hunk and all later hunks in the file
1155 d - do not stage this hunk or any of the later hunks in the file"),
1157 "y - stash this hunk
1158 n - do not stash this hunk
1159 q - quit; do not stash this hunk or any of the remaining ones
1160 a - stash this hunk and all later hunks in the file
1161 d - do not stash this hunk or any of the later hunks in the file"),
1163 "y - unstage this hunk
1164 n - do not unstage this hunk
1165 q - quit; do not unstage this hunk or any of the remaining ones
1166 a - unstage this hunk and all later hunks in the file
1167 d - do not unstage this hunk or any of the later hunks in the file"),
1168 reset_nothead
=> N__
(
1169 "y - apply this hunk to index
1170 n - do not apply this hunk to index
1171 q - quit; do not apply this hunk or any of the remaining ones
1172 a - apply this hunk and all later hunks in the file
1173 d - do not apply this hunk or any of the later hunks in the file"),
1174 checkout_index
=> N__
(
1175 "y - discard this hunk from worktree
1176 n - do not discard this hunk from worktree
1177 q - quit; do not discard this hunk or any of the remaining ones
1178 a - discard this hunk and all later hunks in the file
1179 d - do not discard this hunk or any of the later hunks in the file"),
1180 checkout_head
=> N__
(
1181 "y - discard this hunk from index and worktree
1182 n - do not discard this hunk from index and worktree
1183 q - quit; do not discard this hunk or any of the remaining ones
1184 a - discard this hunk and all later hunks in the file
1185 d - do not discard this hunk or any of the later hunks in the file"),
1186 checkout_nothead
=> N__
(
1187 "y - apply this hunk to index and worktree
1188 n - do not apply this hunk to index and worktree
1189 q - quit; do not apply this hunk or any of the remaining ones
1190 a - apply this hunk and all later hunks in the file
1191 d - do not apply this hunk or any of the later hunks in the file"),
1194 sub help_patch_cmd
{
1195 print colored
$help_color, __
($help_patch_modes{$patch_mode}), "\n", __
<<EOF ;
1196 g - select a hunk to go to
1197 / - search for a hunk matching the given regex
1198 j - leave this hunk undecided, see next undecided hunk
1199 J - leave this hunk undecided, see next hunk
1200 k - leave this hunk undecided, see previous undecided hunk
1201 K - leave this hunk undecided, see previous hunk
1202 s - split the current hunk into smaller hunks
1203 e - manually edit the current hunk
1210 my $ret = run_git_apply
$cmd, @_;
1217 sub apply_patch_for_checkout_commit
{
1218 my $reverse = shift;
1219 my $applies_index = run_git_apply
'apply '.$reverse.' --cached --check', @_;
1220 my $applies_worktree = run_git_apply
'apply '.$reverse.' --check', @_;
1222 if ($applies_worktree && $applies_index) {
1223 run_git_apply
'apply '.$reverse.' --cached', @_;
1224 run_git_apply
'apply '.$reverse, @_;
1226 } elsif (!$applies_index) {
1227 print colored
$error_color, __
("The selected hunks do not apply to the index!\n");
1228 if (prompt_yesno __
("Apply them to the worktree anyway? ")) {
1229 return run_git_apply
'apply '.$reverse, @_;
1231 print colored
$error_color, __
("Nothing was applied.\n");
1240 sub patch_update_cmd
{
1241 my @all_mods = list_modified
($patch_mode_flavour{FILTER
});
1242 error_msg
sprintf(__
("ignoring unmerged: %s\n"), $_->{VALUE
})
1243 for grep { $_->{UNMERGED
} } @all_mods;
1244 @all_mods = grep { !$_->{UNMERGED
} } @all_mods;
1246 my @mods = grep { !($_->{BINARY
}) } @all_mods;
1251 print STDERR __
("Only binary files changed.\n");
1253 print STDERR __
("No changes.\n");
1257 if ($patch_mode_only) {
1261 @them = list_and_choose
({ PROMPT
=> __
('Patch update'),
1262 HEADER
=> $status_head, },
1266 return 0 if patch_update_file
($_->{VALUE
});
1270 # Generate a one line summary of a hunk.
1271 sub summarize_hunk
{
1273 my $summary = $rhunk->{TEXT
}[0];
1275 # Keep the line numbers, discard extra context.
1276 $summary =~ s/@@(.*?)@@.*/$1 /s;
1277 $summary .= " " x
(20 - length $summary);
1279 # Add some user context.
1280 for my $line (@
{$rhunk->{TEXT
}}) {
1281 if ($line =~ m/^[+-].*\w/) {
1288 return substr($summary, 0, 80) . "\n";
1292 # Print a one-line summary of each hunk in the array ref in
1293 # the first argument, starting with the index in the 2nd.
1295 my ($hunks, $i) = @_;
1298 for (; $i < @
$hunks && $ctr < 20; $i++, $ctr++) {
1300 if (defined $hunks->[$i]{USE
}) {
1301 $status = $hunks->[$i]{USE
} ?
"+" : "-";
1306 summarize_hunk
($hunks->[$i]);
1311 my %patch_update_prompt_modes = (
1313 mode
=> N__
("Stage mode change [y,n,q,a,d,/%s,?]? "),
1314 deletion
=> N__
("Stage deletion [y,n,q,a,d,/%s,?]? "),
1315 hunk
=> N__
("Stage this hunk [y,n,q,a,d,/%s,?]? "),
1318 mode
=> N__
("Stash mode change [y,n,q,a,d,/%s,?]? "),
1319 deletion
=> N__
("Stash deletion [y,n,q,a,d,/%s,?]? "),
1320 hunk
=> N__
("Stash this hunk [y,n,q,a,d,/%s,?]? "),
1323 mode
=> N__
("Unstage mode change [y,n,q,a,d,/%s,?]? "),
1324 deletion
=> N__
("Unstage deletion [y,n,q,a,d,/%s,?]? "),
1325 hunk
=> N__
("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
1328 mode
=> N__
("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
1329 deletion
=> N__
("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
1330 hunk
=> N__
("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
1333 mode
=> N__
("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
1334 deletion
=> N__
("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
1335 hunk
=> N__
("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
1338 mode
=> N__
("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
1339 deletion
=> N__
("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
1340 hunk
=> N__
("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
1342 checkout_nothead
=> {
1343 mode
=> N__
("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
1344 deletion
=> N__
("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
1345 hunk
=> N__
("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
1349 sub patch_update_file
{
1353 my ($head, @hunk) = parse_diff
($path);
1354 ($head, my $mode, my $deletion) = parse_diff_header
($head);
1355 for (@
{$head->{DISPLAY
}}) {
1359 if (@
{$mode->{TEXT
}}) {
1360 unshift @hunk, $mode;
1362 if (@
{$deletion->{TEXT
}}) {
1363 foreach my $hunk (@hunk) {
1364 push @
{$deletion->{TEXT
}}, @
{$hunk->{TEXT
}};
1365 push @
{$deletion->{DISPLAY
}}, @
{$hunk->{DISPLAY
}};
1367 @hunk = ($deletion);
1370 $num = scalar @hunk;
1374 my ($prev, $next, $other, $undecided, $i);
1380 for ($i = 0; $i < $ix; $i++) {
1381 if (!defined $hunk[$i]{USE
}) {
1390 for ($i = $ix + 1; $i < $num; $i++) {
1391 if (!defined $hunk[$i]{USE
}) {
1397 if ($ix < $num - 1) {
1403 for ($i = 0; $i < $num; $i++) {
1404 if (!defined $hunk[$i]{USE
}) {
1409 last if (!$undecided);
1411 if ($hunk[$ix]{TYPE
} eq 'hunk' &&
1412 hunk_splittable
($hunk[$ix]{TEXT
})) {
1415 if ($hunk[$ix]{TYPE
} eq 'hunk') {
1418 for (@
{$hunk[$ix]{DISPLAY
}}) {
1421 print colored
$prompt_color,
1422 sprintf(__
($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE
}}), $other);
1424 my $line = prompt_single_character
;
1425 last unless defined $line;
1427 if ($line =~ /^y/i) {
1428 $hunk[$ix]{USE
} = 1;
1430 elsif ($line =~ /^n/i) {
1431 $hunk[$ix]{USE
} = 0;
1433 elsif ($line =~ /^a/i) {
1434 while ($ix < $num) {
1435 if (!defined $hunk[$ix]{USE
}) {
1436 $hunk[$ix]{USE
} = 1;
1442 elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
1444 my $no = $ix > 10 ?
$ix - 10 : 0;
1445 while ($response eq '') {
1446 $no = display_hunks
(\
@hunk, $no);
1448 print __
("go to which hunk (<ret> to see more)? ");
1450 print __
("go to which hunk? ");
1452 $response = <STDIN
>;
1453 if (!defined $response) {
1458 if ($response !~ /^\s*\d+\s*$/) {
1459 error_msg
sprintf(__
("Invalid number: '%s'\n"),
1461 } elsif (0 < $response && $response <= $num) {
1462 $ix = $response - 1;
1464 error_msg
sprintf(__n
("Sorry, only %d hunk available.\n",
1465 "Sorry, only %d hunks available.\n", $num), $num);
1469 elsif ($line =~ /^d/i) {
1470 while ($ix < $num) {
1471 if (!defined $hunk[$ix]{USE
}) {
1472 $hunk[$ix]{USE
} = 0;
1478 elsif ($line =~ /^q/i) {
1479 for ($i = 0; $i < $num; $i++) {
1480 if (!defined $hunk[$i]{USE
}) {
1487 elsif ($line =~ m
|^/(.*)|) {
1490 print colored
$prompt_color, __
("search for regex? ");
1492 if (defined $regex) {
1498 $search_string = qr{$regex}m;
1501 my ($err,$exp) = ($@
, $1);
1502 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1503 error_msg
sprintf(__
("Malformed search regexp %s: %s\n"), $exp, $err);
1508 my $text = join ("", @
{$hunk[$iy]{TEXT
}});
1509 last if ($text =~ $search_string);
1511 $iy = 0 if ($iy >= $num);
1513 error_msg __
("No hunk matches the given pattern\n");
1520 elsif ($line =~ /^K/) {
1521 if ($other =~ /K/) {
1525 error_msg __
("No previous hunk\n");
1529 elsif ($line =~ /^J/) {
1530 if ($other =~ /J/) {
1534 error_msg __
("No next hunk\n");
1538 elsif ($line =~ /^k/) {
1539 if ($other =~ /k/) {
1543 !defined $hunk[$ix]{USE
});
1547 error_msg __
("No previous hunk\n");
1551 elsif ($line =~ /^j/) {
1552 if ($other !~ /j/) {
1553 error_msg __
("No next hunk\n");
1557 elsif ($other =~ /s/ && $line =~ /^s/) {
1558 my @split = split_hunk
($hunk[$ix]{TEXT
}, $hunk[$ix]{DISPLAY
});
1560 print colored
$header_color, sprintf(
1561 __n
("Split into %d hunk.\n",
1562 "Split into %d hunks.\n",
1563 scalar(@split)), scalar(@split));
1565 splice (@hunk, $ix, 1, @split);
1566 $num = scalar @hunk;
1569 elsif ($other =~ /e/ && $line =~ /^e/) {
1570 my $newhunk = edit_hunk_loop
($head, \
@hunk, $ix);
1571 if (defined $newhunk) {
1572 splice @hunk, $ix, 1, $newhunk;
1576 help_patch_cmd
($other);
1582 last if ($ix >= $num ||
1583 !defined $hunk[$ix]{USE
});
1588 @hunk = coalesce_overlapping_hunks
(@hunk);
1594 push @result, @
{$_->{TEXT
}};
1599 my @patch = reassemble_patch
($head->{TEXT
}, @result);
1600 my $apply_routine = $patch_mode_flavour{APPLY
};
1601 &$apply_routine(@patch);
1610 my @mods = list_modified
('index-only');
1611 @mods = grep { !($_->{BINARY
}) } @mods;
1613 my (@them) = list_and_choose
({ PROMPT
=> __
('Review diff'),
1615 HEADER
=> $status_head, },
1618 my $reference = (is_initial_commit
()) ? get_empty_tree
() : 'HEAD';
1619 system(qw(git diff -p --cached), $reference, '--',
1620 map { $_->{VALUE
} } @them);
1629 # TRANSLATORS: please do not translate the command names
1630 # 'status', 'update', 'revert', etc.
1631 print colored
$help_color, __
<<'EOF' ;
1632 status - show paths with changes
1633 update - add working tree state to the staged set of changes
1634 revert - revert staged set of changes back to the HEAD version
1635 patch - pick hunks and update selectively
1636 diff - view diff between HEAD and index
1637 add untracked - add contents of untracked files to the staged set of changes
1642 return unless @ARGV;
1643 my $arg = shift @ARGV;
1644 if ($arg =~ /--patch(?:=(.*))?/) {
1646 if ($1 eq 'reset') {
1647 $patch_mode = 'reset_head';
1648 $patch_mode_revision = 'HEAD';
1649 $arg = shift @ARGV or die __
("missing --");
1651 $patch_mode_revision = $arg;
1652 $patch_mode = ($arg eq 'HEAD' ?
1653 'reset_head' : 'reset_nothead');
1654 $arg = shift @ARGV or die __
("missing --");
1656 } elsif ($1 eq 'checkout') {
1657 $arg = shift @ARGV or die __
("missing --");
1659 $patch_mode = 'checkout_index';
1661 $patch_mode_revision = $arg;
1662 $patch_mode = ($arg eq 'HEAD' ?
1663 'checkout_head' : 'checkout_nothead');
1664 $arg = shift @ARGV or die __
("missing --");
1666 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1668 $arg = shift @ARGV or die __
("missing --");
1670 die sprintf(__
("unknown --patch mode: %s"), $1);
1673 $patch_mode = 'stage';
1674 $arg = shift @ARGV or die __
("missing --");
1676 die sprintf(__
("invalid argument %s, expecting --"),
1677 $arg) unless $arg eq "--";
1678 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1679 $patch_mode_only = 1;
1681 elsif ($arg ne "--") {
1682 die sprintf(__
("invalid argument %s, expecting --"), $arg);
1687 my @cmd = ([ 'status', \
&status_cmd
, ],
1688 [ 'update', \
&update_cmd
, ],
1689 [ 'revert', \
&revert_cmd
, ],
1690 [ 'add untracked', \
&add_untracked_cmd
, ],
1691 [ 'patch', \
&patch_update_cmd
, ],
1692 [ 'diff', \
&diff_cmd
, ],
1693 [ 'quit', \
&quit_cmd
, ],
1694 [ 'help', \
&help_cmd
, ],
1697 my ($it) = list_and_choose
({ PROMPT
=> __
('What now'),
1700 HEADER
=> __
('*** Commands ***'),
1701 ON_EOF
=> \
&quit_cmd
,
1702 IMMEDIATE
=> 1 }, @cmd);
1716 if ($patch_mode_only) {