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 { !/^$comment_line_char/ } <$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 0 if $line =~ /^n/i;
1103 return 1 if $line =~ /^y/i;
1107 sub edit_hunk_loop
{
1108 my ($head, $hunk, $ix) = @_;
1109 my $text = $hunk->[$ix]->{TEXT
};
1112 $text = edit_hunk_manually
($text);
1113 if (!defined $text) {
1118 TYPE
=> $hunk->[$ix]->{TYPE
},
1122 if (diff_applies
($head,
1125 @
{$hunk}[$ix+1..$#{$hunk}])) {
1126 $newhunk->{DISPLAY
} = [color_diff
(@
{$text})];
1131 # TRANSLATORS: do not translate [y/n]
1132 # The program will only accept that input
1134 # Consider translating (saying "no" discards!) as
1135 # (saying "n" for "no" discards!) if the translation
1136 # of the word "no" does not start with n.
1137 __
('Your edited hunk does not apply. Edit again '
1138 . '(saying "no" discards!) [y/n]? ')
1144 my %help_patch_modes = (
1146 "y - stage this hunk
1147 n - do not stage this hunk
1148 q - quit; do not stage this hunk or any of the remaining ones
1149 a - stage this hunk and all later hunks in the file
1150 d - do not stage this hunk or any of the later hunks in the file"),
1152 "y - stash this hunk
1153 n - do not stash this hunk
1154 q - quit; do not stash this hunk or any of the remaining ones
1155 a - stash this hunk and all later hunks in the file
1156 d - do not stash this hunk or any of the later hunks in the file"),
1158 "y - unstage this hunk
1159 n - do not unstage this hunk
1160 q - quit; do not unstage this hunk or any of the remaining ones
1161 a - unstage this hunk and all later hunks in the file
1162 d - do not unstage this hunk or any of the later hunks in the file"),
1163 reset_nothead
=> N__
(
1164 "y - apply this hunk to index
1165 n - do not apply this hunk to index
1166 q - quit; do not apply this hunk or any of the remaining ones
1167 a - apply this hunk and all later hunks in the file
1168 d - do not apply this hunk or any of the later hunks in the file"),
1169 checkout_index
=> N__
(
1170 "y - discard this hunk from worktree
1171 n - do not discard this hunk from worktree
1172 q - quit; do not discard this hunk or any of the remaining ones
1173 a - discard this hunk and all later hunks in the file
1174 d - do not discard this hunk or any of the later hunks in the file"),
1175 checkout_head
=> N__
(
1176 "y - discard this hunk from index and worktree
1177 n - do not discard this hunk from index and worktree
1178 q - quit; do not discard this hunk or any of the remaining ones
1179 a - discard this hunk and all later hunks in the file
1180 d - do not discard this hunk or any of the later hunks in the file"),
1181 checkout_nothead
=> N__
(
1182 "y - apply this hunk to index and worktree
1183 n - do not apply this hunk to index and worktree
1184 q - quit; do not apply this hunk or any of the remaining ones
1185 a - apply this hunk and all later hunks in the file
1186 d - do not apply this hunk or any of the later hunks in the file"),
1189 sub help_patch_cmd
{
1190 print colored
$help_color, __
($help_patch_modes{$patch_mode}), "\n", __
<<EOF ;
1191 g - select a hunk to go to
1192 / - search for a hunk matching the given regex
1193 j - leave this hunk undecided, see next undecided hunk
1194 J - leave this hunk undecided, see next hunk
1195 k - leave this hunk undecided, see previous undecided hunk
1196 K - leave this hunk undecided, see previous hunk
1197 s - split the current hunk into smaller hunks
1198 e - manually edit the current hunk
1205 my $ret = run_git_apply
$cmd, @_;
1212 sub apply_patch_for_checkout_commit
{
1213 my $reverse = shift;
1214 my $applies_index = run_git_apply
'apply '.$reverse.' --cached --check', @_;
1215 my $applies_worktree = run_git_apply
'apply '.$reverse.' --check', @_;
1217 if ($applies_worktree && $applies_index) {
1218 run_git_apply
'apply '.$reverse.' --cached', @_;
1219 run_git_apply
'apply '.$reverse, @_;
1221 } elsif (!$applies_index) {
1222 print colored
$error_color, __
("The selected hunks do not apply to the index!\n");
1223 if (prompt_yesno __
("Apply them to the worktree anyway? ")) {
1224 return run_git_apply
'apply '.$reverse, @_;
1226 print colored
$error_color, __
("Nothing was applied.\n");
1235 sub patch_update_cmd
{
1236 my @all_mods = list_modified
($patch_mode_flavour{FILTER
});
1237 error_msg
sprintf(__
("ignoring unmerged: %s\n"), $_->{VALUE
})
1238 for grep { $_->{UNMERGED
} } @all_mods;
1239 @all_mods = grep { !$_->{UNMERGED
} } @all_mods;
1241 my @mods = grep { !($_->{BINARY
}) } @all_mods;
1246 print STDERR __
("Only binary files changed.\n");
1248 print STDERR __
("No changes.\n");
1252 if ($patch_mode_only) {
1256 @them = list_and_choose
({ PROMPT
=> __
('Patch update'),
1257 HEADER
=> $status_head, },
1261 return 0 if patch_update_file
($_->{VALUE
});
1265 # Generate a one line summary of a hunk.
1266 sub summarize_hunk
{
1268 my $summary = $rhunk->{TEXT
}[0];
1270 # Keep the line numbers, discard extra context.
1271 $summary =~ s/@@(.*?)@@.*/$1 /s;
1272 $summary .= " " x
(20 - length $summary);
1274 # Add some user context.
1275 for my $line (@
{$rhunk->{TEXT
}}) {
1276 if ($line =~ m/^[+-].*\w/) {
1283 return substr($summary, 0, 80) . "\n";
1287 # Print a one-line summary of each hunk in the array ref in
1288 # the first argument, starting with the index in the 2nd.
1290 my ($hunks, $i) = @_;
1293 for (; $i < @
$hunks && $ctr < 20; $i++, $ctr++) {
1295 if (defined $hunks->[$i]{USE
}) {
1296 $status = $hunks->[$i]{USE
} ?
"+" : "-";
1301 summarize_hunk
($hunks->[$i]);
1306 my %patch_update_prompt_modes = (
1308 mode
=> N__
("Stage mode change [y,n,q,a,d,/%s,?]? "),
1309 deletion
=> N__
("Stage deletion [y,n,q,a,d,/%s,?]? "),
1310 hunk
=> N__
("Stage this hunk [y,n,q,a,d,/%s,?]? "),
1313 mode
=> N__
("Stash mode change [y,n,q,a,d,/%s,?]? "),
1314 deletion
=> N__
("Stash deletion [y,n,q,a,d,/%s,?]? "),
1315 hunk
=> N__
("Stash this hunk [y,n,q,a,d,/%s,?]? "),
1318 mode
=> N__
("Unstage mode change [y,n,q,a,d,/%s,?]? "),
1319 deletion
=> N__
("Unstage deletion [y,n,q,a,d,/%s,?]? "),
1320 hunk
=> N__
("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
1323 mode
=> N__
("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
1324 deletion
=> N__
("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
1325 hunk
=> N__
("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
1328 mode
=> N__
("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
1329 deletion
=> N__
("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
1330 hunk
=> N__
("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
1333 mode
=> N__
("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
1334 deletion
=> N__
("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
1335 hunk
=> N__
("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
1337 checkout_nothead
=> {
1338 mode
=> N__
("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
1339 deletion
=> N__
("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
1340 hunk
=> N__
("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
1344 sub patch_update_file
{
1348 my ($head, @hunk) = parse_diff
($path);
1349 ($head, my $mode, my $deletion) = parse_diff_header
($head);
1350 for (@
{$head->{DISPLAY
}}) {
1354 if (@
{$mode->{TEXT
}}) {
1355 unshift @hunk, $mode;
1357 if (@
{$deletion->{TEXT
}}) {
1358 foreach my $hunk (@hunk) {
1359 push @
{$deletion->{TEXT
}}, @
{$hunk->{TEXT
}};
1360 push @
{$deletion->{DISPLAY
}}, @
{$hunk->{DISPLAY
}};
1362 @hunk = ($deletion);
1365 $num = scalar @hunk;
1369 my ($prev, $next, $other, $undecided, $i);
1375 for ($i = 0; $i < $ix; $i++) {
1376 if (!defined $hunk[$i]{USE
}) {
1385 for ($i = $ix + 1; $i < $num; $i++) {
1386 if (!defined $hunk[$i]{USE
}) {
1392 if ($ix < $num - 1) {
1398 for ($i = 0; $i < $num; $i++) {
1399 if (!defined $hunk[$i]{USE
}) {
1404 last if (!$undecided);
1406 if ($hunk[$ix]{TYPE
} eq 'hunk' &&
1407 hunk_splittable
($hunk[$ix]{TEXT
})) {
1410 if ($hunk[$ix]{TYPE
} eq 'hunk') {
1413 for (@
{$hunk[$ix]{DISPLAY
}}) {
1416 print colored
$prompt_color,
1417 sprintf(__
($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE
}}), $other);
1419 my $line = prompt_single_character
;
1420 last unless defined $line;
1422 if ($line =~ /^y/i) {
1423 $hunk[$ix]{USE
} = 1;
1425 elsif ($line =~ /^n/i) {
1426 $hunk[$ix]{USE
} = 0;
1428 elsif ($line =~ /^a/i) {
1429 while ($ix < $num) {
1430 if (!defined $hunk[$ix]{USE
}) {
1431 $hunk[$ix]{USE
} = 1;
1437 elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
1439 my $no = $ix > 10 ?
$ix - 10 : 0;
1440 while ($response eq '') {
1441 $no = display_hunks
(\
@hunk, $no);
1443 print __
("go to which hunk (<ret> to see more)? ");
1445 print __
("go to which hunk? ");
1447 $response = <STDIN
>;
1448 if (!defined $response) {
1453 if ($response !~ /^\s*\d+\s*$/) {
1454 error_msg
sprintf(__
("Invalid number: '%s'\n"),
1456 } elsif (0 < $response && $response <= $num) {
1457 $ix = $response - 1;
1459 error_msg
sprintf(__n
("Sorry, only %d hunk available.\n",
1460 "Sorry, only %d hunks available.\n", $num), $num);
1464 elsif ($line =~ /^d/i) {
1465 while ($ix < $num) {
1466 if (!defined $hunk[$ix]{USE
}) {
1467 $hunk[$ix]{USE
} = 0;
1473 elsif ($line =~ /^q/i) {
1474 for ($i = 0; $i < $num; $i++) {
1475 if (!defined $hunk[$i]{USE
}) {
1482 elsif ($line =~ m
|^/(.*)|) {
1485 print colored
$prompt_color, __
("search for regex? ");
1487 if (defined $regex) {
1493 $search_string = qr{$regex}m;
1496 my ($err,$exp) = ($@
, $1);
1497 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1498 error_msg
sprintf(__
("Malformed search regexp %s: %s\n"), $exp, $err);
1503 my $text = join ("", @
{$hunk[$iy]{TEXT
}});
1504 last if ($text =~ $search_string);
1506 $iy = 0 if ($iy >= $num);
1508 error_msg __
("No hunk matches the given pattern\n");
1515 elsif ($line =~ /^K/) {
1516 if ($other =~ /K/) {
1520 error_msg __
("No previous hunk\n");
1524 elsif ($line =~ /^J/) {
1525 if ($other =~ /J/) {
1529 error_msg __
("No next hunk\n");
1533 elsif ($line =~ /^k/) {
1534 if ($other =~ /k/) {
1538 !defined $hunk[$ix]{USE
});
1542 error_msg __
("No previous hunk\n");
1546 elsif ($line =~ /^j/) {
1547 if ($other !~ /j/) {
1548 error_msg __
("No next hunk\n");
1552 elsif ($other =~ /s/ && $line =~ /^s/) {
1553 my @split = split_hunk
($hunk[$ix]{TEXT
}, $hunk[$ix]{DISPLAY
});
1555 print colored
$header_color, sprintf(
1556 __n
("Split into %d hunk.\n",
1557 "Split into %d hunks.\n",
1558 scalar(@split)), scalar(@split));
1560 splice (@hunk, $ix, 1, @split);
1561 $num = scalar @hunk;
1564 elsif ($other =~ /e/ && $line =~ /^e/) {
1565 my $newhunk = edit_hunk_loop
($head, \
@hunk, $ix);
1566 if (defined $newhunk) {
1567 splice @hunk, $ix, 1, $newhunk;
1571 help_patch_cmd
($other);
1577 last if ($ix >= $num ||
1578 !defined $hunk[$ix]{USE
});
1583 @hunk = coalesce_overlapping_hunks
(@hunk);
1589 push @result, @
{$_->{TEXT
}};
1594 my @patch = reassemble_patch
($head->{TEXT
}, @result);
1595 my $apply_routine = $patch_mode_flavour{APPLY
};
1596 &$apply_routine(@patch);
1605 my @mods = list_modified
('index-only');
1606 @mods = grep { !($_->{BINARY
}) } @mods;
1608 my (@them) = list_and_choose
({ PROMPT
=> __
('Review diff'),
1610 HEADER
=> $status_head, },
1613 my $reference = (is_initial_commit
()) ? get_empty_tree
() : 'HEAD';
1614 system(qw(git diff -p --cached), $reference, '--',
1615 map { $_->{VALUE
} } @them);
1624 # TRANSLATORS: please do not translate the command names
1625 # 'status', 'update', 'revert', etc.
1626 print colored
$help_color, __
<<'EOF' ;
1627 status - show paths with changes
1628 update - add working tree state to the staged set of changes
1629 revert - revert staged set of changes back to the HEAD version
1630 patch - pick hunks and update selectively
1631 diff - view diff between HEAD and index
1632 add untracked - add contents of untracked files to the staged set of changes
1637 return unless @ARGV;
1638 my $arg = shift @ARGV;
1639 if ($arg =~ /--patch(?:=(.*))?/) {
1641 if ($1 eq 'reset') {
1642 $patch_mode = 'reset_head';
1643 $patch_mode_revision = 'HEAD';
1644 $arg = shift @ARGV or die __
("missing --");
1646 $patch_mode_revision = $arg;
1647 $patch_mode = ($arg eq 'HEAD' ?
1648 'reset_head' : 'reset_nothead');
1649 $arg = shift @ARGV or die __
("missing --");
1651 } elsif ($1 eq 'checkout') {
1652 $arg = shift @ARGV or die __
("missing --");
1654 $patch_mode = 'checkout_index';
1656 $patch_mode_revision = $arg;
1657 $patch_mode = ($arg eq 'HEAD' ?
1658 'checkout_head' : 'checkout_nothead');
1659 $arg = shift @ARGV or die __
("missing --");
1661 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1663 $arg = shift @ARGV or die __
("missing --");
1665 die sprintf(__
("unknown --patch mode: %s"), $1);
1668 $patch_mode = 'stage';
1669 $arg = shift @ARGV or die __
("missing --");
1671 die sprintf(__
("invalid argument %s, expecting --"),
1672 $arg) unless $arg eq "--";
1673 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1674 $patch_mode_only = 1;
1676 elsif ($arg ne "--") {
1677 die sprintf(__
("invalid argument %s, expecting --"), $arg);
1682 my @cmd = ([ 'status', \
&status_cmd
, ],
1683 [ 'update', \
&update_cmd
, ],
1684 [ 'revert', \
&revert_cmd
, ],
1685 [ 'add untracked', \
&add_untracked_cmd
, ],
1686 [ 'patch', \
&patch_update_cmd
, ],
1687 [ 'diff', \
&diff_cmd
, ],
1688 [ 'quit', \
&quit_cmd
, ],
1689 [ 'help', \
&help_cmd
, ],
1692 my ($it) = list_and_choose
({ PROMPT
=> __
('What now'),
1695 HEADER
=> __
('*** Commands ***'),
1696 ON_EOF
=> \
&quit_cmd
,
1697 IMMEDIATE
=> 1 }, @cmd);
1711 if ($patch_mode_only) {