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_indent_heuristic = $repo->config_bool('diff.indentheuristic');
50 my $diff_filter = $repo->config('interactive.difffilter');
58 if ($repo->config_bool("interactive.singlekey")) {
60 require Term
::ReadKey
;
61 Term
::ReadKey
->import;
65 print STDERR
"missing Term::ReadKey, disabling interactive.singlekey\n";
69 my $termcap = Term
::Cap
->Tgetent;
70 foreach (values %$termcap) {
71 $term_escapes{$_} = 1 if /^\e/;
79 my $string = join("", @_);
82 # Put a color code at the beginning of each line, a reset at the end
83 # color after newlines that are not at the end of the string
84 $string =~ s/(\n+)(.)/$1$color$2/g;
85 # reset before newlines
86 $string =~ s/(\n+)/$normal_color$1/g;
87 # codes at beginning and end (if necessary):
88 $string =~ s/^/$color/;
89 $string =~ s/$/$normal_color/ unless $string =~ /\n$/;
94 # command line options
97 my $patch_mode_revision;
100 sub apply_patch_for_checkout_commit
;
101 sub apply_patch_for_stash
;
105 DIFF
=> 'diff-files -p',
106 APPLY
=> sub { apply_patch
'apply --cached', @_; },
107 APPLY_CHECK
=> 'apply --cached',
108 FILTER
=> 'file-only',
112 DIFF
=> 'diff-index -p HEAD',
113 APPLY
=> sub { apply_patch
'apply --cached', @_; },
114 APPLY_CHECK
=> 'apply --cached',
119 DIFF
=> 'diff-index -p --cached',
120 APPLY
=> sub { apply_patch
'apply -R --cached', @_; },
121 APPLY_CHECK
=> 'apply -R --cached',
122 FILTER
=> 'index-only',
126 DIFF
=> 'diff-index -R -p --cached',
127 APPLY
=> sub { apply_patch
'apply --cached', @_; },
128 APPLY_CHECK
=> 'apply --cached',
129 FILTER
=> 'index-only',
132 'checkout_index' => {
133 DIFF
=> 'diff-files -p',
134 APPLY
=> sub { apply_patch
'apply -R', @_; },
135 APPLY_CHECK
=> 'apply -R',
136 FILTER
=> 'file-only',
140 DIFF
=> 'diff-index -p',
141 APPLY
=> sub { apply_patch_for_checkout_commit
'-R', @_ },
142 APPLY_CHECK
=> 'apply -R',
146 'checkout_nothead' => {
147 DIFF
=> 'diff-index -R -p',
148 APPLY
=> sub { apply_patch_for_checkout_commit
'', @_ },
149 APPLY_CHECK
=> 'apply',
155 $patch_mode = 'stage';
156 my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
159 if ($^O
eq 'MSWin32') {
160 my @invalid = grep {m/[":*]/} @_;
161 die "$^O does not support: @invalid\n" if @invalid;
162 my @args = map { m/ /o ?
"\"$_\"": $_ } @_;
166 open($fh, '-|', @_) or die;
171 my ($GIT_DIR) = run_cmd_pipe
(qw(git rev-parse --git-dir));
173 if (!defined $GIT_DIR) {
174 exit(1); # rev-parse would have already said "not a git repo"
180 open $fh, 'git update-index --refresh |'
183 ;# ignore 'needs update'
193 run_cmd_pipe
(qw(git ls-files --others --exclude-standard --), @ARGV);
196 # TRANSLATORS: you can adjust this to align "git add -i" status menu
197 my $status_fmt = __
('%12s %12s %s');
198 my $status_head = sprintf($status_fmt, __
('staged'), __
('unstaged'), __
('path'));
202 sub is_initial_commit
{
203 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
204 unless defined $initial;
210 return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
213 sub get_diff_reference
{
215 if (defined $ref and $ref ne 'HEAD') {
217 } elsif (is_initial_commit
()) {
218 return get_empty_tree
();
224 # Returns list of hashes, contents of each of which are:
226 # BINARY: is a binary path
227 # INDEX: is index different from HEAD?
228 # FILE: is file different from index?
229 # INDEX_ADDDEL: is it add/delete between HEAD and index?
230 # FILE_ADDDEL: is it add/delete between index and file?
231 # UNMERGED: is the path unmerged
236 my ($add, $del, $adddel, $file);
238 my $reference = get_diff_reference
($patch_mode_revision);
239 for (run_cmd_pipe
(qw(git diff-index --cached
240 --numstat --summary), $reference,
242 if (($add, $del, $file) =
243 /^([-\d]+) ([-\d]+) (.*)/) {
245 $file = unquote_path
($file);
246 if ($add eq '-' && $del eq '-') {
247 $change = __
('binary');
251 $change = "+$add/-$del";
256 FILE
=> __
('nothing'),
259 elsif (($adddel, $file) =
260 /^ (create|delete) mode [0-7]+ (.*)$/) {
261 $file = unquote_path
($file);
262 $data{$file}{INDEX_ADDDEL
} = $adddel;
266 for (run_cmd_pipe
(qw(git diff-files --numstat --summary --raw --), @ARGV)) {
267 if (($add, $del, $file) =
268 /^([-\d]+) ([-\d]+) (.*)/) {
269 $file = unquote_path
($file);
271 if ($add eq '-' && $del eq '-') {
272 $change = __
('binary');
276 $change = "+$add/-$del";
278 $data{$file}{FILE
} = $change;
280 $data{$file}{BINARY
} = 1;
283 elsif (($adddel, $file) =
284 /^ (create|delete) mode [0-7]+ (.*)$/) {
285 $file = unquote_path
($file);
286 $data{$file}{FILE_ADDDEL
} = $adddel;
288 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
289 $file = unquote_path
($2);
290 if (!exists $data{$file}) {
292 INDEX
=> __
('unchanged'),
297 $data{$file}{UNMERGED
} = 1;
302 for (sort keys %data) {
306 if ($only eq 'index-only') {
307 next if ($it->{INDEX
} eq __
('unchanged'));
309 if ($only eq 'file-only') {
310 next if ($it->{FILE
} eq __
('nothing'));
322 my ($string, @stuff) = @_;
324 for (my $i = 0; $i < @stuff; $i++) {
328 if ((ref $it) eq 'ARRAY') {
336 if ($it =~ /^$string/) {
340 if (defined $hit && defined $found) {
350 # inserts string into trie and updates count for each character
352 my ($trie, $string) = @_;
353 foreach (split //, $string) {
354 $trie = $trie->{$_} ||= {COUNT
=> 0};
359 # returns an array of tuples (prefix, remainder)
360 sub find_unique_prefixes
{
364 # any single prefix exceeding the soft limit is omitted
365 # if any prefix exceeds the hard limit all are omitted
366 # 0 indicates no limit
370 # build a trie modelling all possible options
372 foreach my $print (@stuff) {
373 if ((ref $print) eq 'ARRAY') {
374 $print = $print->[0];
376 elsif ((ref $print) eq 'HASH') {
377 $print = $print->{VALUE
};
379 update_trie
(\
%trie, $print);
380 push @return, $print;
383 # use the trie to find the unique prefixes
384 for (my $i = 0; $i < @return; $i++) {
385 my $ret = $return[$i];
386 my @letters = split //, $ret;
388 my ($prefix, $remainder);
390 for ($j = 0; $j < @letters; $j++) {
391 my $letter = $letters[$j];
392 if ($search{$letter}{COUNT
} == 1) {
393 $prefix = substr $ret, 0, $j + 1;
394 $remainder = substr $ret, $j + 1;
398 my $prefix = substr $ret, 0, $j;
400 if ($hard_limit && $j + 1 > $hard_limit);
402 %search = %{$search{$letter}};
404 if (ord($letters[0]) > 127 ||
405 ($soft_limit && $j + 1 > $soft_limit)) {
409 $return[$i] = [$prefix, $remainder];
414 # filters out prefixes which have special meaning to list_and_choose()
415 sub is_valid_prefix
{
417 return (defined $prefix) &&
418 !($prefix =~ /[\s,]/) && # separators
419 !($prefix =~ /^-/) && # deselection
420 !($prefix =~ /^\d+/) && # selection
421 ($prefix ne '*') && # "all" wildcard
422 ($prefix ne '?'); # prompt help
425 # given a prefix/remainder tuple return a string with the prefix highlighted
426 # for now use square brackets; later might use ANSI colors (underline, bold)
427 sub highlight_prefix
{
429 my $remainder = shift;
431 if (!defined $prefix) {
435 if (!is_valid_prefix
($prefix)) {
436 return "$prefix$remainder";
439 if (!$menu_use_color) {
440 return "[$prefix]$remainder";
443 return "$prompt_color$prefix$normal_color$remainder";
447 print STDERR colored
$error_color, @_;
450 sub list_and_choose
{
451 my ($opts, @stuff) = @_;
452 my (@chosen, @return);
457 my @prefixes = find_unique_prefixes
(@stuff) unless $opts->{LIST_ONLY
};
463 if ($opts->{HEADER
}) {
464 if (!$opts->{LIST_FLAT
}) {
467 print colored
$header_color, "$opts->{HEADER}\n";
469 for ($i = 0; $i < @stuff; $i++) {
470 my $chosen = $chosen[$i] ?
'*' : ' ';
471 my $print = $stuff[$i];
472 my $ref = ref $print;
473 my $highlighted = highlight_prefix
(@
{$prefixes[$i]})
475 if ($ref eq 'ARRAY') {
476 $print = $highlighted || $print->[0];
478 elsif ($ref eq 'HASH') {
479 my $value = $highlighted || $print->{VALUE
};
480 $print = sprintf($status_fmt,
486 $print = $highlighted || $print;
488 printf("%s%2d: %s", $chosen, $i+1, $print);
489 if (($opts->{LIST_FLAT
}) &&
490 (($i + 1) % ($opts->{LIST_FLAT
}))) {
503 return if ($opts->{LIST_ONLY
});
505 print colored
$prompt_color, $opts->{PROMPT
};
506 if ($opts->{SINGLETON
}) {
515 $opts->{ON_EOF
}->() if $opts->{ON_EOF
};
522 singleton_prompt_help_cmd
() :
526 for my $choice (split(/[\s,]+/, $line)) {
530 # Input that begins with '-'; unchoose
531 if ($choice =~ s/^-//) {
534 # A range can be specified like 5-7 or 5-.
535 if ($choice =~ /^(\d+)-(\d*)$/) {
536 ($bottom, $top) = ($1, length($2) ?
$2 : 1 + @stuff);
538 elsif ($choice =~ /^\d+$/) {
539 $bottom = $top = $choice;
541 elsif ($choice eq '*') {
546 $bottom = $top = find_unique
($choice, @stuff);
547 if (!defined $bottom) {
548 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
552 if ($opts->{SINGLETON
} && $bottom != $top) {
553 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
556 for ($i = $bottom-1; $i <= $top-1; $i++) {
557 next if (@stuff <= $i || $i < 0);
558 $chosen[$i] = $choose;
561 last if ($opts->{IMMEDIATE
} || $line eq '*');
563 for ($i = 0; $i < @stuff; $i++) {
565 push @return, $stuff[$i];
571 sub singleton_prompt_help_cmd
{
572 print colored
$help_color, __
<<'EOF' ;
574 1 - select a numbered item
575 foo - select item based on unique prefix
576 - (empty) select nothing
580 sub prompt_help_cmd
{
581 print colored
$help_color, __
<<'EOF' ;
583 1 - select a single item
584 3-5 - select a range of items
585 2-3,6-9 - select multiple ranges
586 foo - select item based on unique prefix
587 -... - unselect specified items
589 - (empty) finish selecting
594 list_and_choose
({ LIST_ONLY
=> 1, HEADER
=> $status_head },
602 if ($did eq 'added') {
603 printf(__n
("added %d path\n", "added %d paths\n",
605 } elsif ($did eq 'updated') {
606 printf(__n
("updated %d path\n", "updated %d paths\n",
608 } elsif ($did eq 'reverted') {
609 printf(__n
("reverted %d path\n", "reverted %d paths\n",
612 printf(__n
("touched %d path\n", "touched %d paths\n",
618 my @mods = list_modified
('file-only');
621 my @update = list_and_choose
({ PROMPT
=> __
('Update'),
622 HEADER
=> $status_head, },
625 system(qw(git update-index --add --remove --),
626 map { $_->{VALUE
} } @update);
627 say_n_paths
('updated', @update);
633 my @update = list_and_choose
({ PROMPT
=> __
('Revert'),
634 HEADER
=> $status_head, },
637 if (is_initial_commit
()) {
638 system(qw(git rm --cached),
639 map { $_->{VALUE
} } @update);
642 my @lines = run_cmd_pipe
(qw(git ls-tree HEAD --),
643 map { $_->{VALUE
} } @update);
645 open $fh, '| git update-index --index-info'
652 if ($_->{INDEX_ADDDEL
} &&
653 $_->{INDEX_ADDDEL
} eq 'create') {
654 system(qw(git update-index --force-remove --),
656 printf(__
("note: %s is untracked now.\n"), $_->{VALUE
});
661 say_n_paths
('reverted', @update);
666 sub add_untracked_cmd
{
667 my @add = list_and_choose
({ PROMPT
=> __
('Add untracked') },
670 system(qw(git update-index --add --), @add);
671 say_n_paths
('added', @add);
673 print __
("No untracked files.\n");
681 open $fh, '| git ' . $cmd . " --recount --allow-overlap";
688 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF
});
689 if (defined $diff_algorithm) {
690 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
692 if ($diff_indent_heuristic) {
693 splice @diff_cmd, 1, 0, "--indent-heuristic";
695 if (defined $patch_mode_revision) {
696 push @diff_cmd, get_diff_reference
($patch_mode_revision);
698 my @diff = run_cmd_pipe
("git", @diff_cmd, "--", $path);
700 if ($diff_use_color) {
701 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
702 if (defined $diff_filter) {
703 # quotemeta is overkill, but sufficient for shell-quoting
704 my $diff = join(' ', map { quotemeta } @display_cmd);
705 @display_cmd = ("$diff | $diff_filter");
708 @colored = run_cmd_pipe
(@display_cmd);
710 my (@hunk) = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
712 for (my $i = 0; $i < @diff; $i++) {
713 if ($diff[$i] =~ /^@@ /) {
714 push @hunk, { TEXT
=> [], DISPLAY
=> [],
717 push @
{$hunk[-1]{TEXT
}}, $diff[$i];
718 push @
{$hunk[-1]{DISPLAY
}},
719 (@colored ?
$colored[$i] : $diff[$i]);
724 sub parse_diff_header
{
727 my $head = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
728 my $mode = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'mode' };
729 my $deletion = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'deletion' };
731 for (my $i = 0; $i < @
{$src->{TEXT
}}; $i++) {
733 $src->{TEXT
}->[$i] =~ /^(old|new) mode (\d+)$/ ?
$mode :
734 $src->{TEXT
}->[$i] =~ /^deleted file/ ?
$deletion :
736 push @
{$dest->{TEXT
}}, $src->{TEXT
}->[$i];
737 push @
{$dest->{DISPLAY
}}, $src->{DISPLAY
}->[$i];
739 return ($head, $mode, $deletion);
742 sub hunk_splittable
{
745 my @s = split_hunk
($text);
749 sub parse_hunk_header
{
751 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
752 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
753 $o_cnt = 1 unless defined $o_cnt;
754 $n_cnt = 1 unless defined $n_cnt;
755 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
759 my ($text, $display) = @_;
761 if (!defined $display) {
764 # If there are context lines in the middle of a hunk,
765 # it can be split, but we would need to take care of
768 my ($o_ofs, undef, $n_ofs) = parse_hunk_header
($text->[0]);
773 my $next_hunk_start = undef;
774 my $i = $hunk_start - 1;
788 while (++$i < @
$text) {
789 my $line = $text->[$i];
790 my $display = $display->[$i];
792 if ($this->{ADDDEL
} &&
793 !defined $next_hunk_start) {
794 # We have seen leading context and
795 # adds/dels and then here is another
796 # context, which is trailing for this
797 # split hunk and leading for the next
799 $next_hunk_start = $i;
801 push @
{$this->{TEXT
}}, $line;
802 push @
{$this->{DISPLAY
}}, $display;
805 if (defined $next_hunk_start) {
812 if (defined $next_hunk_start) {
813 # We are done with the current hunk and
814 # this is the first real change for the
816 $hunk_start = $next_hunk_start;
817 $o_ofs = $this->{OLD
} + $this->{OCNT
};
818 $n_ofs = $this->{NEW
} + $this->{NCNT
};
819 $o_ofs -= $this->{POSTCTX
};
820 $n_ofs -= $this->{POSTCTX
};
824 push @
{$this->{TEXT
}}, $line;
825 push @
{$this->{DISPLAY
}}, $display;
839 for my $hunk (@split) {
840 $o_ofs = $hunk->{OLD
};
841 $n_ofs = $hunk->{NEW
};
842 my $o_cnt = $hunk->{OCNT
};
843 my $n_cnt = $hunk->{NCNT
};
845 my $head = ("@@ -$o_ofs" .
846 (($o_cnt != 1) ?
",$o_cnt" : '') .
848 (($n_cnt != 1) ?
",$n_cnt" : '') .
850 my $display_head = $head;
851 unshift @
{$hunk->{TEXT
}}, $head;
852 if ($diff_use_color) {
853 $display_head = colored
($fraginfo_color, $head);
855 unshift @
{$hunk->{DISPLAY
}}, $display_head;
860 sub find_last_o_ctx
{
862 my $text = $it->{TEXT
};
863 my ($o_ofs, $o_cnt) = parse_hunk_header
($text->[0]);
865 my $last_o_ctx = $o_ofs + $o_cnt;
867 my $line = $text->[$i];
878 my ($prev, $this) = @_;
879 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
880 parse_hunk_header
($prev->{TEXT
}[0]);
881 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
882 parse_hunk_header
($this->{TEXT
}[0]);
884 my (@line, $i, $ofs, $o_cnt, $n_cnt);
887 for ($i = 1; $i < @
{$prev->{TEXT
}}; $i++) {
888 my $line = $prev->{TEXT
}[$i];
889 if ($line =~ /^\+/) {
895 last if ($o1_ofs <= $ofs);
905 for ($i = 1; $i < @
{$this->{TEXT
}}; $i++) {
906 my $line = $this->{TEXT
}[$i];
907 if ($line =~ /^\+/) {
919 my $head = ("@@ -$o0_ofs" .
920 (($o_cnt != 1) ?
",$o_cnt" : '') .
922 (($n_cnt != 1) ?
",$n_cnt" : '') .
924 @
{$prev->{TEXT
}} = ($head, @line);
927 sub coalesce_overlapping_hunks
{
931 my ($last_o_ctx, $last_was_dirty);
933 for (grep { $_->{USE
} } @in) {
934 if ($_->{TYPE
} ne 'hunk') {
938 my $text = $_->{TEXT
};
939 my ($o_ofs) = parse_hunk_header
($text->[0]);
940 if (defined $last_o_ctx &&
941 $o_ofs <= $last_o_ctx &&
944 merge_hunk
($out[-1], $_);
949 $last_o_ctx = find_last_o_ctx
($out[-1]);
950 $last_was_dirty = $_->{DIRTY
};
955 sub reassemble_patch
{
959 # Include everything in the header except the beginning of the diff.
960 push @patch, (grep { !/^[-+]{3}/ } @
$head);
962 # Then include any headers from the hunk lines, which must
963 # come before any actual hunk.
964 while (@_ && $_[0] !~ /^@/) {
968 # Then begin the diff.
969 push @patch, grep { /^[-+]{3}/ } @
$head;
971 # And then the actual hunks.
979 colored
((/^@/ ?
$fraginfo_color :
980 /^\+/ ?
$diff_new_color :
981 /^-/ ?
$diff_old_color :
987 my %edit_hunk_manually_modes = (
989 "If the patch applies cleanly, the edited hunk will immediately be
990 marked for staging."),
992 "If the patch applies cleanly, the edited hunk will immediately be
993 marked for stashing."),
995 "If the patch applies cleanly, the edited hunk will immediately be
996 marked for unstaging."),
997 reset_nothead
=> N__
(
998 "If the patch applies cleanly, the edited hunk will immediately be
999 marked for applying."),
1000 checkout_index
=> N__
(
1001 "If the patch applies cleanly, the edited hunk will immediately be
1002 marked for discarding."),
1003 checkout_head
=> N__
(
1004 "If the patch applies cleanly, the edited hunk will immediately be
1005 marked for discarding."),
1006 checkout_nothead
=> N__
(
1007 "If the patch applies cleanly, the edited hunk will immediately be
1008 marked for applying."),
1011 sub edit_hunk_manually
{
1014 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1016 open $fh, '>', $hunkfile
1017 or die sprintf(__
("failed to open hunk edit file for writing: %s"), $!);
1018 print $fh Git
::comment_lines __
("Manual hunk edit mode -- see bottom for a quick guide.\n");
1019 print $fh @
$oldtext;
1020 my $is_reverse = $patch_mode_flavour{IS_REVERSE
};
1021 my ($remove_plus, $remove_minus) = $is_reverse ?
('-', '+') : ('+', '-');
1022 my $comment_line_char = Git
::get_comment_line_char
;
1023 print $fh Git
::comment_lines
sprintf(__
<<EOF, $remove_minus, $remove_plus, $comment_line_char),
1025 To remove '%s' lines, make them ' ' lines (context).
1026 To remove '%s' lines, delete them.
1027 Lines starting with %s will be removed.
1029 __
($edit_hunk_manually_modes{$patch_mode}),
1030 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1032 If it does not apply cleanly, you will be given an opportunity to
1033 edit again. If all lines of the hunk are removed, then the edit is
1034 aborted and the hunk is left unchanged.
1038 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
1039 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1045 open $fh, '<', $hunkfile
1046 or die sprintf(__
("failed to open hunk edit file for reading: %s"), $!);
1047 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1051 # Abort if nothing remains
1052 if (!grep { /\S/ } @newtext) {
1056 # Reinsert the first hunk header if the user accidentally deleted it
1057 if ($newtext[0] !~ /^@/) {
1058 unshift @newtext, $oldtext->[0];
1064 return run_git_apply
($patch_mode_flavour{APPLY_CHECK
} . ' --check',
1065 map { @
{$_->{TEXT
}} } @_);
1068 sub _restore_terminal_and_die
{
1074 sub prompt_single_character
{
1076 local $SIG{TERM
} = \
&_restore_terminal_and_die
;
1077 local $SIG{INT
} = \
&_restore_terminal_and_die
;
1079 my $key = ReadKey
0;
1081 if ($use_termcap and $key eq "\e") {
1082 while (!defined $term_escapes{$key}) {
1083 my $next = ReadKey
0.5;
1084 last if (!defined $next);
1089 print "$key" if defined $key;
1100 print colored
$prompt_color, $prompt;
1101 my $line = prompt_single_character
;
1102 return undef unless defined $line;
1103 return 0 if $line =~ /^n/i;
1104 return 1 if $line =~ /^y/i;
1108 sub edit_hunk_loop
{
1109 my ($head, $hunk, $ix) = @_;
1110 my $text = $hunk->[$ix]->{TEXT
};
1113 $text = edit_hunk_manually
($text);
1114 if (!defined $text) {
1119 TYPE
=> $hunk->[$ix]->{TYPE
},
1123 if (diff_applies
($head,
1126 @
{$hunk}[$ix+1..$#{$hunk}])) {
1127 $newhunk->{DISPLAY
} = [color_diff
(@
{$text})];
1132 # TRANSLATORS: do not translate [y/n]
1133 # The program will only accept that input
1135 # Consider translating (saying "no" discards!) as
1136 # (saying "n" for "no" discards!) if the translation
1137 # of the word "no" does not start with n.
1138 __
('Your edited hunk does not apply. Edit again '
1139 . '(saying "no" discards!) [y/n]? ')
1145 my %help_patch_modes = (
1147 "y - stage this hunk
1148 n - do not stage this hunk
1149 q - quit; do not stage this hunk or any of the remaining ones
1150 a - stage this hunk and all later hunks in the file
1151 d - do not stage this hunk or any of the later hunks in the file"),
1153 "y - stash this hunk
1154 n - do not stash this hunk
1155 q - quit; do not stash this hunk or any of the remaining ones
1156 a - stash this hunk and all later hunks in the file
1157 d - do not stash this hunk or any of the later hunks in the file"),
1159 "y - unstage this hunk
1160 n - do not unstage this hunk
1161 q - quit; do not unstage this hunk or any of the remaining ones
1162 a - unstage this hunk and all later hunks in the file
1163 d - do not unstage this hunk or any of the later hunks in the file"),
1164 reset_nothead
=> N__
(
1165 "y - apply this hunk to index
1166 n - do not apply this hunk to index
1167 q - quit; do not apply this hunk or any of the remaining ones
1168 a - apply this hunk and all later hunks in the file
1169 d - do not apply this hunk or any of the later hunks in the file"),
1170 checkout_index
=> N__
(
1171 "y - discard this hunk from worktree
1172 n - do not discard this hunk from worktree
1173 q - quit; do not discard this hunk or any of the remaining ones
1174 a - discard this hunk and all later hunks in the file
1175 d - do not discard this hunk or any of the later hunks in the file"),
1176 checkout_head
=> N__
(
1177 "y - discard this hunk from index and worktree
1178 n - do not discard this hunk from index and worktree
1179 q - quit; do not discard this hunk or any of the remaining ones
1180 a - discard this hunk and all later hunks in the file
1181 d - do not discard this hunk or any of the later hunks in the file"),
1182 checkout_nothead
=> N__
(
1183 "y - apply this hunk to index and worktree
1184 n - do not apply this hunk to index and worktree
1185 q - quit; do not apply this hunk or any of the remaining ones
1186 a - apply this hunk and all later hunks in the file
1187 d - do not apply this hunk or any of the later hunks in the file"),
1190 sub help_patch_cmd
{
1191 print colored
$help_color, __
($help_patch_modes{$patch_mode}), "\n", __
<<EOF ;
1192 g - select a hunk to go to
1193 / - search for a hunk matching the given regex
1194 j - leave this hunk undecided, see next undecided hunk
1195 J - leave this hunk undecided, see next hunk
1196 k - leave this hunk undecided, see previous undecided hunk
1197 K - leave this hunk undecided, see previous hunk
1198 s - split the current hunk into smaller hunks
1199 e - manually edit the current hunk
1206 my $ret = run_git_apply
$cmd, @_;
1213 sub apply_patch_for_checkout_commit
{
1214 my $reverse = shift;
1215 my $applies_index = run_git_apply
'apply '.$reverse.' --cached --check', @_;
1216 my $applies_worktree = run_git_apply
'apply '.$reverse.' --check', @_;
1218 if ($applies_worktree && $applies_index) {
1219 run_git_apply
'apply '.$reverse.' --cached', @_;
1220 run_git_apply
'apply '.$reverse, @_;
1222 } elsif (!$applies_index) {
1223 print colored
$error_color, __
("The selected hunks do not apply to the index!\n");
1224 if (prompt_yesno __
("Apply them to the worktree anyway? ")) {
1225 return run_git_apply
'apply '.$reverse, @_;
1227 print colored
$error_color, __
("Nothing was applied.\n");
1236 sub patch_update_cmd
{
1237 my @all_mods = list_modified
($patch_mode_flavour{FILTER
});
1238 error_msg
sprintf(__
("ignoring unmerged: %s\n"), $_->{VALUE
})
1239 for grep { $_->{UNMERGED
} } @all_mods;
1240 @all_mods = grep { !$_->{UNMERGED
} } @all_mods;
1242 my @mods = grep { !($_->{BINARY
}) } @all_mods;
1247 print STDERR __
("Only binary files changed.\n");
1249 print STDERR __
("No changes.\n");
1253 if ($patch_mode_only) {
1257 @them = list_and_choose
({ PROMPT
=> __
('Patch update'),
1258 HEADER
=> $status_head, },
1262 return 0 if patch_update_file
($_->{VALUE
});
1266 # Generate a one line summary of a hunk.
1267 sub summarize_hunk
{
1269 my $summary = $rhunk->{TEXT
}[0];
1271 # Keep the line numbers, discard extra context.
1272 $summary =~ s/@@(.*?)@@.*/$1 /s;
1273 $summary .= " " x
(20 - length $summary);
1275 # Add some user context.
1276 for my $line (@
{$rhunk->{TEXT
}}) {
1277 if ($line =~ m/^[+-].*\w/) {
1284 return substr($summary, 0, 80) . "\n";
1288 # Print a one-line summary of each hunk in the array ref in
1289 # the first argument, starting with the index in the 2nd.
1291 my ($hunks, $i) = @_;
1294 for (; $i < @
$hunks && $ctr < 20; $i++, $ctr++) {
1296 if (defined $hunks->[$i]{USE
}) {
1297 $status = $hunks->[$i]{USE
} ?
"+" : "-";
1302 summarize_hunk
($hunks->[$i]);
1307 my %patch_update_prompt_modes = (
1309 mode
=> N__
("Stage mode change [y,n,q,a,d,/%s,?]? "),
1310 deletion
=> N__
("Stage deletion [y,n,q,a,d,/%s,?]? "),
1311 hunk
=> N__
("Stage this hunk [y,n,q,a,d,/%s,?]? "),
1314 mode
=> N__
("Stash mode change [y,n,q,a,d,/%s,?]? "),
1315 deletion
=> N__
("Stash deletion [y,n,q,a,d,/%s,?]? "),
1316 hunk
=> N__
("Stash this hunk [y,n,q,a,d,/%s,?]? "),
1319 mode
=> N__
("Unstage mode change [y,n,q,a,d,/%s,?]? "),
1320 deletion
=> N__
("Unstage deletion [y,n,q,a,d,/%s,?]? "),
1321 hunk
=> N__
("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
1324 mode
=> N__
("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
1325 deletion
=> N__
("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
1326 hunk
=> N__
("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
1329 mode
=> N__
("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
1330 deletion
=> N__
("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
1331 hunk
=> N__
("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
1334 mode
=> N__
("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
1335 deletion
=> N__
("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
1336 hunk
=> N__
("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
1338 checkout_nothead
=> {
1339 mode
=> N__
("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
1340 deletion
=> N__
("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
1341 hunk
=> N__
("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
1345 sub patch_update_file
{
1349 my ($head, @hunk) = parse_diff
($path);
1350 ($head, my $mode, my $deletion) = parse_diff_header
($head);
1351 for (@
{$head->{DISPLAY
}}) {
1355 if (@
{$mode->{TEXT
}}) {
1356 unshift @hunk, $mode;
1358 if (@
{$deletion->{TEXT
}}) {
1359 foreach my $hunk (@hunk) {
1360 push @
{$deletion->{TEXT
}}, @
{$hunk->{TEXT
}};
1361 push @
{$deletion->{DISPLAY
}}, @
{$hunk->{DISPLAY
}};
1363 @hunk = ($deletion);
1366 $num = scalar @hunk;
1370 my ($prev, $next, $other, $undecided, $i);
1376 for ($i = 0; $i < $ix; $i++) {
1377 if (!defined $hunk[$i]{USE
}) {
1386 for ($i = $ix + 1; $i < $num; $i++) {
1387 if (!defined $hunk[$i]{USE
}) {
1393 if ($ix < $num - 1) {
1399 for ($i = 0; $i < $num; $i++) {
1400 if (!defined $hunk[$i]{USE
}) {
1405 last if (!$undecided);
1407 if ($hunk[$ix]{TYPE
} eq 'hunk' &&
1408 hunk_splittable
($hunk[$ix]{TEXT
})) {
1411 if ($hunk[$ix]{TYPE
} eq 'hunk') {
1414 for (@
{$hunk[$ix]{DISPLAY
}}) {
1417 print colored
$prompt_color,
1418 sprintf(__
($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE
}}), $other);
1420 my $line = prompt_single_character
;
1421 last unless defined $line;
1423 if ($line =~ /^y/i) {
1424 $hunk[$ix]{USE
} = 1;
1426 elsif ($line =~ /^n/i) {
1427 $hunk[$ix]{USE
} = 0;
1429 elsif ($line =~ /^a/i) {
1430 while ($ix < $num) {
1431 if (!defined $hunk[$ix]{USE
}) {
1432 $hunk[$ix]{USE
} = 1;
1438 elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
1440 my $no = $ix > 10 ?
$ix - 10 : 0;
1441 while ($response eq '') {
1442 $no = display_hunks
(\
@hunk, $no);
1444 print __
("go to which hunk (<ret> to see more)? ");
1446 print __
("go to which hunk? ");
1448 $response = <STDIN
>;
1449 if (!defined $response) {
1454 if ($response !~ /^\s*\d+\s*$/) {
1455 error_msg
sprintf(__
("Invalid number: '%s'\n"),
1457 } elsif (0 < $response && $response <= $num) {
1458 $ix = $response - 1;
1460 error_msg
sprintf(__n
("Sorry, only %d hunk available.\n",
1461 "Sorry, only %d hunks available.\n", $num), $num);
1465 elsif ($line =~ /^d/i) {
1466 while ($ix < $num) {
1467 if (!defined $hunk[$ix]{USE
}) {
1468 $hunk[$ix]{USE
} = 0;
1474 elsif ($line =~ /^q/i) {
1475 for ($i = 0; $i < $num; $i++) {
1476 if (!defined $hunk[$i]{USE
}) {
1483 elsif ($line =~ m
|^/(.*)|) {
1486 print colored
$prompt_color, __
("search for regex? ");
1488 if (defined $regex) {
1494 $search_string = qr{$regex}m;
1497 my ($err,$exp) = ($@
, $1);
1498 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1499 error_msg
sprintf(__
("Malformed search regexp %s: %s\n"), $exp, $err);
1504 my $text = join ("", @
{$hunk[$iy]{TEXT
}});
1505 last if ($text =~ $search_string);
1507 $iy = 0 if ($iy >= $num);
1509 error_msg __
("No hunk matches the given pattern\n");
1516 elsif ($line =~ /^K/) {
1517 if ($other =~ /K/) {
1521 error_msg __
("No previous hunk\n");
1525 elsif ($line =~ /^J/) {
1526 if ($other =~ /J/) {
1530 error_msg __
("No next hunk\n");
1534 elsif ($line =~ /^k/) {
1535 if ($other =~ /k/) {
1539 !defined $hunk[$ix]{USE
});
1543 error_msg __
("No previous hunk\n");
1547 elsif ($line =~ /^j/) {
1548 if ($other !~ /j/) {
1549 error_msg __
("No next hunk\n");
1553 elsif ($other =~ /s/ && $line =~ /^s/) {
1554 my @split = split_hunk
($hunk[$ix]{TEXT
}, $hunk[$ix]{DISPLAY
});
1556 print colored
$header_color, sprintf(
1557 __n
("Split into %d hunk.\n",
1558 "Split into %d hunks.\n",
1559 scalar(@split)), scalar(@split));
1561 splice (@hunk, $ix, 1, @split);
1562 $num = scalar @hunk;
1565 elsif ($other =~ /e/ && $line =~ /^e/) {
1566 my $newhunk = edit_hunk_loop
($head, \
@hunk, $ix);
1567 if (defined $newhunk) {
1568 splice @hunk, $ix, 1, $newhunk;
1572 help_patch_cmd
($other);
1578 last if ($ix >= $num ||
1579 !defined $hunk[$ix]{USE
});
1584 @hunk = coalesce_overlapping_hunks
(@hunk);
1590 push @result, @
{$_->{TEXT
}};
1595 my @patch = reassemble_patch
($head->{TEXT
}, @result);
1596 my $apply_routine = $patch_mode_flavour{APPLY
};
1597 &$apply_routine(@patch);
1606 my @mods = list_modified
('index-only');
1607 @mods = grep { !($_->{BINARY
}) } @mods;
1609 my (@them) = list_and_choose
({ PROMPT
=> __
('Review diff'),
1611 HEADER
=> $status_head, },
1614 my $reference = (is_initial_commit
()) ? get_empty_tree
() : 'HEAD';
1615 system(qw(git diff -p --cached), $reference, '--',
1616 map { $_->{VALUE
} } @them);
1625 # TRANSLATORS: please do not translate the command names
1626 # 'status', 'update', 'revert', etc.
1627 print colored
$help_color, __
<<'EOF' ;
1628 status - show paths with changes
1629 update - add working tree state to the staged set of changes
1630 revert - revert staged set of changes back to the HEAD version
1631 patch - pick hunks and update selectively
1632 diff - view diff between HEAD and index
1633 add untracked - add contents of untracked files to the staged set of changes
1638 return unless @ARGV;
1639 my $arg = shift @ARGV;
1640 if ($arg =~ /--patch(?:=(.*))?/) {
1642 if ($1 eq 'reset') {
1643 $patch_mode = 'reset_head';
1644 $patch_mode_revision = 'HEAD';
1645 $arg = shift @ARGV or die __
("missing --");
1647 $patch_mode_revision = $arg;
1648 $patch_mode = ($arg eq 'HEAD' ?
1649 'reset_head' : 'reset_nothead');
1650 $arg = shift @ARGV or die __
("missing --");
1652 } elsif ($1 eq 'checkout') {
1653 $arg = shift @ARGV or die __
("missing --");
1655 $patch_mode = 'checkout_index';
1657 $patch_mode_revision = $arg;
1658 $patch_mode = ($arg eq 'HEAD' ?
1659 'checkout_head' : 'checkout_nothead');
1660 $arg = shift @ARGV or die __
("missing --");
1662 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1664 $arg = shift @ARGV or die __
("missing --");
1666 die sprintf(__
("unknown --patch mode: %s"), $1);
1669 $patch_mode = 'stage';
1670 $arg = shift @ARGV or die __
("missing --");
1672 die sprintf(__
("invalid argument %s, expecting --"),
1673 $arg) unless $arg eq "--";
1674 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1675 $patch_mode_only = 1;
1677 elsif ($arg ne "--") {
1678 die sprintf(__
("invalid argument %s, expecting --"), $arg);
1683 my @cmd = ([ 'status', \
&status_cmd
, ],
1684 [ 'update', \
&update_cmd
, ],
1685 [ 'revert', \
&revert_cmd
, ],
1686 [ 'add untracked', \
&add_untracked_cmd
, ],
1687 [ 'patch', \
&patch_update_cmd
, ],
1688 [ 'diff', \
&diff_cmd
, ],
1689 [ 'quit', \
&quit_cmd
, ],
1690 [ 'help', \
&help_cmd
, ],
1693 my ($it) = list_and_choose
({ PROMPT
=> __
('What now'),
1696 HEADER
=> __
('*** Commands ***'),
1697 ON_EOF
=> \
&quit_cmd
,
1698 IMMEDIATE
=> 1 }, @cmd);
1712 if ($patch_mode_only) {