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"
190 my ($retval, $remainder);
191 if (!/^\042(.*)\042$/) {
194 ($_, $retval) = ($1, "");
195 while (/^([^\\]*)\\(.*)$/) {
199 if (/^([0-3][0-7][0-7])(.*)$/) {
200 $retval .= chr(oct($1));
204 if (/^([\\\042btnvfr])(.*)$/) {
205 $retval .= $cquote_map{$1};
209 # This is malformed -- just return it as-is for now.
220 open $fh, 'git update-index --refresh |'
223 ;# ignore 'needs update'
233 run_cmd_pipe
(qw(git ls-files --others --exclude-standard --), @ARGV);
236 # TRANSLATORS: you can adjust this to align "git add -i" status menu
237 my $status_fmt = __
('%12s %12s %s');
238 my $status_head = sprintf($status_fmt, __
('staged'), __
('unstaged'), __
('path'));
242 sub is_initial_commit
{
243 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
244 unless defined $initial;
250 return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
253 sub get_diff_reference
{
255 if (defined $ref and $ref ne 'HEAD') {
257 } elsif (is_initial_commit
()) {
258 return get_empty_tree
();
264 # Returns list of hashes, contents of each of which are:
266 # BINARY: is a binary path
267 # INDEX: is index different from HEAD?
268 # FILE: is file different from index?
269 # INDEX_ADDDEL: is it add/delete between HEAD and index?
270 # FILE_ADDDEL: is it add/delete between index and file?
271 # UNMERGED: is the path unmerged
276 my ($add, $del, $adddel, $file);
278 my $reference = get_diff_reference
($patch_mode_revision);
279 for (run_cmd_pipe
(qw(git diff-index --cached
280 --numstat --summary), $reference,
282 if (($add, $del, $file) =
283 /^([-\d]+) ([-\d]+) (.*)/) {
285 $file = unquote_path
($file);
286 if ($add eq '-' && $del eq '-') {
287 $change = __
('binary');
291 $change = "+$add/-$del";
296 FILE
=> __
('nothing'),
299 elsif (($adddel, $file) =
300 /^ (create|delete) mode [0-7]+ (.*)$/) {
301 $file = unquote_path
($file);
302 $data{$file}{INDEX_ADDDEL
} = $adddel;
306 for (run_cmd_pipe
(qw(git diff-files --numstat --summary --raw --), @ARGV)) {
307 if (($add, $del, $file) =
308 /^([-\d]+) ([-\d]+) (.*)/) {
309 $file = unquote_path
($file);
311 if ($add eq '-' && $del eq '-') {
312 $change = __
('binary');
316 $change = "+$add/-$del";
318 $data{$file}{FILE
} = $change;
320 $data{$file}{BINARY
} = 1;
323 elsif (($adddel, $file) =
324 /^ (create|delete) mode [0-7]+ (.*)$/) {
325 $file = unquote_path
($file);
326 $data{$file}{FILE_ADDDEL
} = $adddel;
328 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
329 $file = unquote_path
($2);
330 if (!exists $data{$file}) {
332 INDEX
=> __
('unchanged'),
337 $data{$file}{UNMERGED
} = 1;
342 for (sort keys %data) {
346 if ($only eq 'index-only') {
347 next if ($it->{INDEX
} eq __
('unchanged'));
349 if ($only eq 'file-only') {
350 next if ($it->{FILE
} eq __
('nothing'));
362 my ($string, @stuff) = @_;
364 for (my $i = 0; $i < @stuff; $i++) {
368 if ((ref $it) eq 'ARRAY') {
376 if ($it =~ /^$string/) {
380 if (defined $hit && defined $found) {
390 # inserts string into trie and updates count for each character
392 my ($trie, $string) = @_;
393 foreach (split //, $string) {
394 $trie = $trie->{$_} ||= {COUNT
=> 0};
399 # returns an array of tuples (prefix, remainder)
400 sub find_unique_prefixes
{
404 # any single prefix exceeding the soft limit is omitted
405 # if any prefix exceeds the hard limit all are omitted
406 # 0 indicates no limit
410 # build a trie modelling all possible options
412 foreach my $print (@stuff) {
413 if ((ref $print) eq 'ARRAY') {
414 $print = $print->[0];
416 elsif ((ref $print) eq 'HASH') {
417 $print = $print->{VALUE
};
419 update_trie
(\
%trie, $print);
420 push @return, $print;
423 # use the trie to find the unique prefixes
424 for (my $i = 0; $i < @return; $i++) {
425 my $ret = $return[$i];
426 my @letters = split //, $ret;
428 my ($prefix, $remainder);
430 for ($j = 0; $j < @letters; $j++) {
431 my $letter = $letters[$j];
432 if ($search{$letter}{COUNT
} == 1) {
433 $prefix = substr $ret, 0, $j + 1;
434 $remainder = substr $ret, $j + 1;
438 my $prefix = substr $ret, 0, $j;
440 if ($hard_limit && $j + 1 > $hard_limit);
442 %search = %{$search{$letter}};
444 if (ord($letters[0]) > 127 ||
445 ($soft_limit && $j + 1 > $soft_limit)) {
449 $return[$i] = [$prefix, $remainder];
454 # filters out prefixes which have special meaning to list_and_choose()
455 sub is_valid_prefix
{
457 return (defined $prefix) &&
458 !($prefix =~ /[\s,]/) && # separators
459 !($prefix =~ /^-/) && # deselection
460 !($prefix =~ /^\d+/) && # selection
461 ($prefix ne '*') && # "all" wildcard
462 ($prefix ne '?'); # prompt help
465 # given a prefix/remainder tuple return a string with the prefix highlighted
466 # for now use square brackets; later might use ANSI colors (underline, bold)
467 sub highlight_prefix
{
469 my $remainder = shift;
471 if (!defined $prefix) {
475 if (!is_valid_prefix
($prefix)) {
476 return "$prefix$remainder";
479 if (!$menu_use_color) {
480 return "[$prefix]$remainder";
483 return "$prompt_color$prefix$normal_color$remainder";
487 print STDERR colored
$error_color, @_;
490 sub list_and_choose
{
491 my ($opts, @stuff) = @_;
492 my (@chosen, @return);
497 my @prefixes = find_unique_prefixes
(@stuff) unless $opts->{LIST_ONLY
};
503 if ($opts->{HEADER
}) {
504 if (!$opts->{LIST_FLAT
}) {
507 print colored
$header_color, "$opts->{HEADER}\n";
509 for ($i = 0; $i < @stuff; $i++) {
510 my $chosen = $chosen[$i] ?
'*' : ' ';
511 my $print = $stuff[$i];
512 my $ref = ref $print;
513 my $highlighted = highlight_prefix
(@
{$prefixes[$i]})
515 if ($ref eq 'ARRAY') {
516 $print = $highlighted || $print->[0];
518 elsif ($ref eq 'HASH') {
519 my $value = $highlighted || $print->{VALUE
};
520 $print = sprintf($status_fmt,
526 $print = $highlighted || $print;
528 printf("%s%2d: %s", $chosen, $i+1, $print);
529 if (($opts->{LIST_FLAT
}) &&
530 (($i + 1) % ($opts->{LIST_FLAT
}))) {
543 return if ($opts->{LIST_ONLY
});
545 print colored
$prompt_color, $opts->{PROMPT
};
546 if ($opts->{SINGLETON
}) {
555 $opts->{ON_EOF
}->() if $opts->{ON_EOF
};
562 singleton_prompt_help_cmd
() :
566 for my $choice (split(/[\s,]+/, $line)) {
570 # Input that begins with '-'; unchoose
571 if ($choice =~ s/^-//) {
574 # A range can be specified like 5-7 or 5-.
575 if ($choice =~ /^(\d+)-(\d*)$/) {
576 ($bottom, $top) = ($1, length($2) ?
$2 : 1 + @stuff);
578 elsif ($choice =~ /^\d+$/) {
579 $bottom = $top = $choice;
581 elsif ($choice eq '*') {
586 $bottom = $top = find_unique
($choice, @stuff);
587 if (!defined $bottom) {
588 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
592 if ($opts->{SINGLETON
} && $bottom != $top) {
593 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
596 for ($i = $bottom-1; $i <= $top-1; $i++) {
597 next if (@stuff <= $i || $i < 0);
598 $chosen[$i] = $choose;
601 last if ($opts->{IMMEDIATE
} || $line eq '*');
603 for ($i = 0; $i < @stuff; $i++) {
605 push @return, $stuff[$i];
611 sub singleton_prompt_help_cmd
{
612 print colored
$help_color, __
<<'EOF' ;
614 1 - select a numbered item
615 foo - select item based on unique prefix
616 - (empty) select nothing
620 sub prompt_help_cmd
{
621 print colored
$help_color, __
<<'EOF' ;
623 1 - select a single item
624 3-5 - select a range of items
625 2-3,6-9 - select multiple ranges
626 foo - select item based on unique prefix
627 -... - unselect specified items
629 - (empty) finish selecting
634 list_and_choose
({ LIST_ONLY
=> 1, HEADER
=> $status_head },
642 if ($did eq 'added') {
643 printf(__n
("added %d path\n", "added %d paths\n",
645 } elsif ($did eq 'updated') {
646 printf(__n
("updated %d path\n", "updated %d paths\n",
648 } elsif ($did eq 'reverted') {
649 printf(__n
("reverted %d path\n", "reverted %d paths\n",
652 printf(__n
("touched %d path\n", "touched %d paths\n",
658 my @mods = list_modified
('file-only');
661 my @update = list_and_choose
({ PROMPT
=> __
('Update'),
662 HEADER
=> $status_head, },
665 system(qw(git update-index --add --remove --),
666 map { $_->{VALUE
} } @update);
667 say_n_paths
('updated', @update);
673 my @update = list_and_choose
({ PROMPT
=> __
('Revert'),
674 HEADER
=> $status_head, },
677 if (is_initial_commit
()) {
678 system(qw(git rm --cached),
679 map { $_->{VALUE
} } @update);
682 my @lines = run_cmd_pipe
(qw(git ls-tree HEAD --),
683 map { $_->{VALUE
} } @update);
685 open $fh, '| git update-index --index-info'
692 if ($_->{INDEX_ADDDEL
} &&
693 $_->{INDEX_ADDDEL
} eq 'create') {
694 system(qw(git update-index --force-remove --),
696 printf(__
("note: %s is untracked now.\n"), $_->{VALUE
});
701 say_n_paths
('reverted', @update);
706 sub add_untracked_cmd
{
707 my @add = list_and_choose
({ PROMPT
=> __
('Add untracked') },
710 system(qw(git update-index --add --), @add);
711 say_n_paths
('added', @add);
713 print __
("No untracked files.\n");
721 open $fh, '| git ' . $cmd . " --recount --allow-overlap";
728 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF
});
729 if (defined $diff_algorithm) {
730 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
732 if (defined $patch_mode_revision) {
733 push @diff_cmd, get_diff_reference
($patch_mode_revision);
735 my @diff = run_cmd_pipe
("git", @diff_cmd, "--", $path);
737 if ($diff_use_color) {
738 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
739 if (defined $diff_filter) {
740 # quotemeta is overkill, but sufficient for shell-quoting
741 my $diff = join(' ', map { quotemeta } @display_cmd);
742 @display_cmd = ("$diff | $diff_filter");
745 @colored = run_cmd_pipe
(@display_cmd);
747 my (@hunk) = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
749 for (my $i = 0; $i < @diff; $i++) {
750 if ($diff[$i] =~ /^@@ /) {
751 push @hunk, { TEXT
=> [], DISPLAY
=> [],
754 push @
{$hunk[-1]{TEXT
}}, $diff[$i];
755 push @
{$hunk[-1]{DISPLAY
}},
756 (@colored ?
$colored[$i] : $diff[$i]);
761 sub parse_diff_header
{
764 my $head = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
765 my $mode = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'mode' };
766 my $deletion = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'deletion' };
768 for (my $i = 0; $i < @
{$src->{TEXT
}}; $i++) {
770 $src->{TEXT
}->[$i] =~ /^(old|new) mode (\d+)$/ ?
$mode :
771 $src->{TEXT
}->[$i] =~ /^deleted file/ ?
$deletion :
773 push @
{$dest->{TEXT
}}, $src->{TEXT
}->[$i];
774 push @
{$dest->{DISPLAY
}}, $src->{DISPLAY
}->[$i];
776 return ($head, $mode, $deletion);
779 sub hunk_splittable
{
782 my @s = split_hunk
($text);
786 sub parse_hunk_header
{
788 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
789 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
790 $o_cnt = 1 unless defined $o_cnt;
791 $n_cnt = 1 unless defined $n_cnt;
792 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
796 my ($text, $display) = @_;
798 if (!defined $display) {
801 # If there are context lines in the middle of a hunk,
802 # it can be split, but we would need to take care of
805 my ($o_ofs, undef, $n_ofs) = parse_hunk_header
($text->[0]);
810 my $next_hunk_start = undef;
811 my $i = $hunk_start - 1;
825 while (++$i < @
$text) {
826 my $line = $text->[$i];
827 my $display = $display->[$i];
829 if ($this->{ADDDEL
} &&
830 !defined $next_hunk_start) {
831 # We have seen leading context and
832 # adds/dels and then here is another
833 # context, which is trailing for this
834 # split hunk and leading for the next
836 $next_hunk_start = $i;
838 push @
{$this->{TEXT
}}, $line;
839 push @
{$this->{DISPLAY
}}, $display;
842 if (defined $next_hunk_start) {
849 if (defined $next_hunk_start) {
850 # We are done with the current hunk and
851 # this is the first real change for the
853 $hunk_start = $next_hunk_start;
854 $o_ofs = $this->{OLD
} + $this->{OCNT
};
855 $n_ofs = $this->{NEW
} + $this->{NCNT
};
856 $o_ofs -= $this->{POSTCTX
};
857 $n_ofs -= $this->{POSTCTX
};
861 push @
{$this->{TEXT
}}, $line;
862 push @
{$this->{DISPLAY
}}, $display;
876 for my $hunk (@split) {
877 $o_ofs = $hunk->{OLD
};
878 $n_ofs = $hunk->{NEW
};
879 my $o_cnt = $hunk->{OCNT
};
880 my $n_cnt = $hunk->{NCNT
};
882 my $head = ("@@ -$o_ofs" .
883 (($o_cnt != 1) ?
",$o_cnt" : '') .
885 (($n_cnt != 1) ?
",$n_cnt" : '') .
887 my $display_head = $head;
888 unshift @
{$hunk->{TEXT
}}, $head;
889 if ($diff_use_color) {
890 $display_head = colored
($fraginfo_color, $head);
892 unshift @
{$hunk->{DISPLAY
}}, $display_head;
897 sub find_last_o_ctx
{
899 my $text = $it->{TEXT
};
900 my ($o_ofs, $o_cnt) = parse_hunk_header
($text->[0]);
902 my $last_o_ctx = $o_ofs + $o_cnt;
904 my $line = $text->[$i];
915 my ($prev, $this) = @_;
916 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
917 parse_hunk_header
($prev->{TEXT
}[0]);
918 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
919 parse_hunk_header
($this->{TEXT
}[0]);
921 my (@line, $i, $ofs, $o_cnt, $n_cnt);
924 for ($i = 1; $i < @
{$prev->{TEXT
}}; $i++) {
925 my $line = $prev->{TEXT
}[$i];
926 if ($line =~ /^\+/) {
932 last if ($o1_ofs <= $ofs);
942 for ($i = 1; $i < @
{$this->{TEXT
}}; $i++) {
943 my $line = $this->{TEXT
}[$i];
944 if ($line =~ /^\+/) {
956 my $head = ("@@ -$o0_ofs" .
957 (($o_cnt != 1) ?
",$o_cnt" : '') .
959 (($n_cnt != 1) ?
",$n_cnt" : '') .
961 @
{$prev->{TEXT
}} = ($head, @line);
964 sub coalesce_overlapping_hunks
{
968 my ($last_o_ctx, $last_was_dirty);
970 for (grep { $_->{USE
} } @in) {
971 if ($_->{TYPE
} ne 'hunk') {
975 my $text = $_->{TEXT
};
976 my ($o_ofs) = parse_hunk_header
($text->[0]);
977 if (defined $last_o_ctx &&
978 $o_ofs <= $last_o_ctx &&
981 merge_hunk
($out[-1], $_);
986 $last_o_ctx = find_last_o_ctx
($out[-1]);
987 $last_was_dirty = $_->{DIRTY
};
992 sub reassemble_patch
{
996 # Include everything in the header except the beginning of the diff.
997 push @patch, (grep { !/^[-+]{3}/ } @
$head);
999 # Then include any headers from the hunk lines, which must
1000 # come before any actual hunk.
1001 while (@_ && $_[0] !~ /^@/) {
1005 # Then begin the diff.
1006 push @patch, grep { /^[-+]{3}/ } @
$head;
1008 # And then the actual hunks.
1016 colored
((/^@/ ?
$fraginfo_color :
1017 /^\+/ ?
$diff_new_color :
1018 /^-/ ?
$diff_old_color :
1024 my %edit_hunk_manually_modes = (
1026 "If the patch applies cleanly, the edited hunk will immediately be
1027 marked for staging."),
1029 "If the patch applies cleanly, the edited hunk will immediately be
1030 marked for stashing."),
1032 "If the patch applies cleanly, the edited hunk will immediately be
1033 marked for unstaging."),
1034 reset_nothead
=> N__
(
1035 "If the patch applies cleanly, the edited hunk will immediately be
1036 marked for applying."),
1037 checkout_index
=> N__
(
1038 "If the patch applies cleanly, the edited hunk will immediately be
1039 marked for discarding."),
1040 checkout_head
=> N__
(
1041 "If the patch applies cleanly, the edited hunk will immediately be
1042 marked for discarding."),
1043 checkout_nothead
=> N__
(
1044 "If the patch applies cleanly, the edited hunk will immediately be
1045 marked for applying."),
1048 sub edit_hunk_manually
{
1051 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1053 open $fh, '>', $hunkfile
1054 or die sprintf(__
("failed to open hunk edit file for writing: %s"), $!);
1055 print $fh Git
::comment_lines __
("Manual hunk edit mode -- see bottom for a quick guide.\n");
1056 print $fh @
$oldtext;
1057 my $is_reverse = $patch_mode_flavour{IS_REVERSE
};
1058 my ($remove_plus, $remove_minus) = $is_reverse ?
('-', '+') : ('+', '-');
1059 my $comment_line_char = Git
::get_comment_line_char
;
1060 print $fh Git
::comment_lines
sprintf(__
<<EOF, $remove_minus, $remove_plus, $comment_line_char),
1062 To remove '%s' lines, make them ' ' lines (context).
1063 To remove '%s' lines, delete them.
1064 Lines starting with %s will be removed.
1066 __
($edit_hunk_manually_modes{$patch_mode}),
1067 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1069 If it does not apply cleanly, you will be given an opportunity to
1070 edit again. If all lines of the hunk are removed, then the edit is
1071 aborted and the hunk is left unchanged.
1075 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
1076 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1082 open $fh, '<', $hunkfile
1083 or die sprintf(__
("failed to open hunk edit file for reading: %s"), $!);
1084 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1088 # Abort if nothing remains
1089 if (!grep { /\S/ } @newtext) {
1093 # Reinsert the first hunk header if the user accidentally deleted it
1094 if ($newtext[0] !~ /^@/) {
1095 unshift @newtext, $oldtext->[0];
1101 return run_git_apply
($patch_mode_flavour{APPLY_CHECK
} . ' --check',
1102 map { @
{$_->{TEXT
}} } @_);
1105 sub _restore_terminal_and_die
{
1111 sub prompt_single_character
{
1113 local $SIG{TERM
} = \
&_restore_terminal_and_die
;
1114 local $SIG{INT
} = \
&_restore_terminal_and_die
;
1116 my $key = ReadKey
0;
1118 if ($use_termcap and $key eq "\e") {
1119 while (!defined $term_escapes{$key}) {
1120 my $next = ReadKey
0.5;
1121 last if (!defined $next);
1126 print "$key" if defined $key;
1137 print colored
$prompt_color, $prompt;
1138 my $line = prompt_single_character
;
1139 return undef unless defined $line;
1140 return 0 if $line =~ /^n/i;
1141 return 1 if $line =~ /^y/i;
1145 sub edit_hunk_loop
{
1146 my ($head, $hunk, $ix) = @_;
1147 my $text = $hunk->[$ix]->{TEXT
};
1150 $text = edit_hunk_manually
($text);
1151 if (!defined $text) {
1156 TYPE
=> $hunk->[$ix]->{TYPE
},
1160 if (diff_applies
($head,
1163 @
{$hunk}[$ix+1..$#{$hunk}])) {
1164 $newhunk->{DISPLAY
} = [color_diff
(@
{$text})];
1169 # TRANSLATORS: do not translate [y/n]
1170 # The program will only accept that input
1172 # Consider translating (saying "no" discards!) as
1173 # (saying "n" for "no" discards!) if the translation
1174 # of the word "no" does not start with n.
1175 __
('Your edited hunk does not apply. Edit again '
1176 . '(saying "no" discards!) [y/n]? ')
1182 my %help_patch_modes = (
1184 "y - stage this hunk
1185 n - do not stage this hunk
1186 q - quit; do not stage this hunk or any of the remaining ones
1187 a - stage this hunk and all later hunks in the file
1188 d - do not stage this hunk or any of the later hunks in the file"),
1190 "y - stash this hunk
1191 n - do not stash this hunk
1192 q - quit; do not stash this hunk or any of the remaining ones
1193 a - stash this hunk and all later hunks in the file
1194 d - do not stash this hunk or any of the later hunks in the file"),
1196 "y - unstage this hunk
1197 n - do not unstage this hunk
1198 q - quit; do not unstage this hunk or any of the remaining ones
1199 a - unstage this hunk and all later hunks in the file
1200 d - do not unstage this hunk or any of the later hunks in the file"),
1201 reset_nothead
=> N__
(
1202 "y - apply this hunk to index
1203 n - do not apply this hunk to index
1204 q - quit; do not apply this hunk or any of the remaining ones
1205 a - apply this hunk and all later hunks in the file
1206 d - do not apply this hunk or any of the later hunks in the file"),
1207 checkout_index
=> N__
(
1208 "y - discard this hunk from worktree
1209 n - do not discard this hunk from worktree
1210 q - quit; do not discard this hunk or any of the remaining ones
1211 a - discard this hunk and all later hunks in the file
1212 d - do not discard this hunk or any of the later hunks in the file"),
1213 checkout_head
=> N__
(
1214 "y - discard this hunk from index and worktree
1215 n - do not discard this hunk from index and worktree
1216 q - quit; do not discard this hunk or any of the remaining ones
1217 a - discard this hunk and all later hunks in the file
1218 d - do not discard this hunk or any of the later hunks in the file"),
1219 checkout_nothead
=> N__
(
1220 "y - apply this hunk to index and worktree
1221 n - do not apply this hunk to index and worktree
1222 q - quit; do not apply this hunk or any of the remaining ones
1223 a - apply this hunk and all later hunks in the file
1224 d - do not apply this hunk or any of the later hunks in the file"),
1227 sub help_patch_cmd
{
1228 print colored
$help_color, __
($help_patch_modes{$patch_mode}), "\n", __
<<EOF ;
1229 g - select a hunk to go to
1230 / - search for a hunk matching the given regex
1231 j - leave this hunk undecided, see next undecided hunk
1232 J - leave this hunk undecided, see next hunk
1233 k - leave this hunk undecided, see previous undecided hunk
1234 K - leave this hunk undecided, see previous hunk
1235 s - split the current hunk into smaller hunks
1236 e - manually edit the current hunk
1243 my $ret = run_git_apply
$cmd, @_;
1250 sub apply_patch_for_checkout_commit
{
1251 my $reverse = shift;
1252 my $applies_index = run_git_apply
'apply '.$reverse.' --cached --check', @_;
1253 my $applies_worktree = run_git_apply
'apply '.$reverse.' --check', @_;
1255 if ($applies_worktree && $applies_index) {
1256 run_git_apply
'apply '.$reverse.' --cached', @_;
1257 run_git_apply
'apply '.$reverse, @_;
1259 } elsif (!$applies_index) {
1260 print colored
$error_color, __
("The selected hunks do not apply to the index!\n");
1261 if (prompt_yesno __
("Apply them to the worktree anyway? ")) {
1262 return run_git_apply
'apply '.$reverse, @_;
1264 print colored
$error_color, __
("Nothing was applied.\n");
1273 sub patch_update_cmd
{
1274 my @all_mods = list_modified
($patch_mode_flavour{FILTER
});
1275 error_msg
sprintf(__
("ignoring unmerged: %s\n"), $_->{VALUE
})
1276 for grep { $_->{UNMERGED
} } @all_mods;
1277 @all_mods = grep { !$_->{UNMERGED
} } @all_mods;
1279 my @mods = grep { !($_->{BINARY
}) } @all_mods;
1284 print STDERR __
("Only binary files changed.\n");
1286 print STDERR __
("No changes.\n");
1290 if ($patch_mode_only) {
1294 @them = list_and_choose
({ PROMPT
=> __
('Patch update'),
1295 HEADER
=> $status_head, },
1299 return 0 if patch_update_file
($_->{VALUE
});
1303 # Generate a one line summary of a hunk.
1304 sub summarize_hunk
{
1306 my $summary = $rhunk->{TEXT
}[0];
1308 # Keep the line numbers, discard extra context.
1309 $summary =~ s/@@(.*?)@@.*/$1 /s;
1310 $summary .= " " x
(20 - length $summary);
1312 # Add some user context.
1313 for my $line (@
{$rhunk->{TEXT
}}) {
1314 if ($line =~ m/^[+-].*\w/) {
1321 return substr($summary, 0, 80) . "\n";
1325 # Print a one-line summary of each hunk in the array ref in
1326 # the first argument, starting with the index in the 2nd.
1328 my ($hunks, $i) = @_;
1331 for (; $i < @
$hunks && $ctr < 20; $i++, $ctr++) {
1333 if (defined $hunks->[$i]{USE
}) {
1334 $status = $hunks->[$i]{USE
} ?
"+" : "-";
1339 summarize_hunk
($hunks->[$i]);
1344 my %patch_update_prompt_modes = (
1346 mode
=> N__
("Stage mode change [y,n,q,a,d,/%s,?]? "),
1347 deletion
=> N__
("Stage deletion [y,n,q,a,d,/%s,?]? "),
1348 hunk
=> N__
("Stage this hunk [y,n,q,a,d,/%s,?]? "),
1351 mode
=> N__
("Stash mode change [y,n,q,a,d,/%s,?]? "),
1352 deletion
=> N__
("Stash deletion [y,n,q,a,d,/%s,?]? "),
1353 hunk
=> N__
("Stash this hunk [y,n,q,a,d,/%s,?]? "),
1356 mode
=> N__
("Unstage mode change [y,n,q,a,d,/%s,?]? "),
1357 deletion
=> N__
("Unstage deletion [y,n,q,a,d,/%s,?]? "),
1358 hunk
=> N__
("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
1361 mode
=> N__
("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
1362 deletion
=> N__
("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
1363 hunk
=> N__
("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
1366 mode
=> N__
("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
1367 deletion
=> N__
("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
1368 hunk
=> N__
("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
1371 mode
=> N__
("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
1372 deletion
=> N__
("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
1373 hunk
=> N__
("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
1375 checkout_nothead
=> {
1376 mode
=> N__
("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
1377 deletion
=> N__
("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
1378 hunk
=> N__
("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
1382 sub patch_update_file
{
1386 my ($head, @hunk) = parse_diff
($path);
1387 ($head, my $mode, my $deletion) = parse_diff_header
($head);
1388 for (@
{$head->{DISPLAY
}}) {
1392 if (@
{$mode->{TEXT
}}) {
1393 unshift @hunk, $mode;
1395 if (@
{$deletion->{TEXT
}}) {
1396 foreach my $hunk (@hunk) {
1397 push @
{$deletion->{TEXT
}}, @
{$hunk->{TEXT
}};
1398 push @
{$deletion->{DISPLAY
}}, @
{$hunk->{DISPLAY
}};
1400 @hunk = ($deletion);
1403 $num = scalar @hunk;
1407 my ($prev, $next, $other, $undecided, $i);
1413 for ($i = 0; $i < $ix; $i++) {
1414 if (!defined $hunk[$i]{USE
}) {
1423 for ($i = $ix + 1; $i < $num; $i++) {
1424 if (!defined $hunk[$i]{USE
}) {
1430 if ($ix < $num - 1) {
1436 for ($i = 0; $i < $num; $i++) {
1437 if (!defined $hunk[$i]{USE
}) {
1442 last if (!$undecided);
1444 if ($hunk[$ix]{TYPE
} eq 'hunk' &&
1445 hunk_splittable
($hunk[$ix]{TEXT
})) {
1448 if ($hunk[$ix]{TYPE
} eq 'hunk') {
1451 for (@
{$hunk[$ix]{DISPLAY
}}) {
1454 print colored
$prompt_color,
1455 sprintf(__
($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE
}}), $other);
1457 my $line = prompt_single_character
;
1458 last unless defined $line;
1460 if ($line =~ /^y/i) {
1461 $hunk[$ix]{USE
} = 1;
1463 elsif ($line =~ /^n/i) {
1464 $hunk[$ix]{USE
} = 0;
1466 elsif ($line =~ /^a/i) {
1467 while ($ix < $num) {
1468 if (!defined $hunk[$ix]{USE
}) {
1469 $hunk[$ix]{USE
} = 1;
1475 elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
1477 my $no = $ix > 10 ?
$ix - 10 : 0;
1478 while ($response eq '') {
1479 $no = display_hunks
(\
@hunk, $no);
1481 print __
("go to which hunk (<ret> to see more)? ");
1483 print __
("go to which hunk? ");
1485 $response = <STDIN
>;
1486 if (!defined $response) {
1491 if ($response !~ /^\s*\d+\s*$/) {
1492 error_msg
sprintf(__
("Invalid number: '%s'\n"),
1494 } elsif (0 < $response && $response <= $num) {
1495 $ix = $response - 1;
1497 error_msg
sprintf(__n
("Sorry, only %d hunk available.\n",
1498 "Sorry, only %d hunks available.\n", $num), $num);
1502 elsif ($line =~ /^d/i) {
1503 while ($ix < $num) {
1504 if (!defined $hunk[$ix]{USE
}) {
1505 $hunk[$ix]{USE
} = 0;
1511 elsif ($line =~ /^q/i) {
1512 for ($i = 0; $i < $num; $i++) {
1513 if (!defined $hunk[$i]{USE
}) {
1520 elsif ($line =~ m
|^/(.*)|) {
1523 print colored
$prompt_color, __
("search for regex? ");
1525 if (defined $regex) {
1531 $search_string = qr{$regex}m;
1534 my ($err,$exp) = ($@
, $1);
1535 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1536 error_msg
sprintf(__
("Malformed search regexp %s: %s\n"), $exp, $err);
1541 my $text = join ("", @
{$hunk[$iy]{TEXT
}});
1542 last if ($text =~ $search_string);
1544 $iy = 0 if ($iy >= $num);
1546 error_msg __
("No hunk matches the given pattern\n");
1553 elsif ($line =~ /^K/) {
1554 if ($other =~ /K/) {
1558 error_msg __
("No previous hunk\n");
1562 elsif ($line =~ /^J/) {
1563 if ($other =~ /J/) {
1567 error_msg __
("No next hunk\n");
1571 elsif ($line =~ /^k/) {
1572 if ($other =~ /k/) {
1576 !defined $hunk[$ix]{USE
});
1580 error_msg __
("No previous hunk\n");
1584 elsif ($line =~ /^j/) {
1585 if ($other !~ /j/) {
1586 error_msg __
("No next hunk\n");
1590 elsif ($other =~ /s/ && $line =~ /^s/) {
1591 my @split = split_hunk
($hunk[$ix]{TEXT
}, $hunk[$ix]{DISPLAY
});
1593 print colored
$header_color, sprintf(
1594 __n
("Split into %d hunk.\n",
1595 "Split into %d hunks.\n",
1596 scalar(@split)), scalar(@split));
1598 splice (@hunk, $ix, 1, @split);
1599 $num = scalar @hunk;
1602 elsif ($other =~ /e/ && $line =~ /^e/) {
1603 my $newhunk = edit_hunk_loop
($head, \
@hunk, $ix);
1604 if (defined $newhunk) {
1605 splice @hunk, $ix, 1, $newhunk;
1609 help_patch_cmd
($other);
1615 last if ($ix >= $num ||
1616 !defined $hunk[$ix]{USE
});
1621 @hunk = coalesce_overlapping_hunks
(@hunk);
1627 push @result, @
{$_->{TEXT
}};
1632 my @patch = reassemble_patch
($head->{TEXT
}, @result);
1633 my $apply_routine = $patch_mode_flavour{APPLY
};
1634 &$apply_routine(@patch);
1643 my @mods = list_modified
('index-only');
1644 @mods = grep { !($_->{BINARY
}) } @mods;
1646 my (@them) = list_and_choose
({ PROMPT
=> __
('Review diff'),
1648 HEADER
=> $status_head, },
1651 my $reference = (is_initial_commit
()) ? get_empty_tree
() : 'HEAD';
1652 system(qw(git diff -p --cached), $reference, '--',
1653 map { $_->{VALUE
} } @them);
1662 # TRANSLATORS: please do not translate the command names
1663 # 'status', 'update', 'revert', etc.
1664 print colored
$help_color, __
<<'EOF' ;
1665 status - show paths with changes
1666 update - add working tree state to the staged set of changes
1667 revert - revert staged set of changes back to the HEAD version
1668 patch - pick hunks and update selectively
1669 diff - view diff between HEAD and index
1670 add untracked - add contents of untracked files to the staged set of changes
1675 return unless @ARGV;
1676 my $arg = shift @ARGV;
1677 if ($arg =~ /--patch(?:=(.*))?/) {
1679 if ($1 eq 'reset') {
1680 $patch_mode = 'reset_head';
1681 $patch_mode_revision = 'HEAD';
1682 $arg = shift @ARGV or die __
("missing --");
1684 $patch_mode_revision = $arg;
1685 $patch_mode = ($arg eq 'HEAD' ?
1686 'reset_head' : 'reset_nothead');
1687 $arg = shift @ARGV or die __
("missing --");
1689 } elsif ($1 eq 'checkout') {
1690 $arg = shift @ARGV or die __
("missing --");
1692 $patch_mode = 'checkout_index';
1694 $patch_mode_revision = $arg;
1695 $patch_mode = ($arg eq 'HEAD' ?
1696 'checkout_head' : 'checkout_nothead');
1697 $arg = shift @ARGV or die __
("missing --");
1699 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1701 $arg = shift @ARGV or die __
("missing --");
1703 die sprintf(__
("unknown --patch mode: %s"), $1);
1706 $patch_mode = 'stage';
1707 $arg = shift @ARGV or die __
("missing --");
1709 die sprintf(__
("invalid argument %s, expecting --"),
1710 $arg) unless $arg eq "--";
1711 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1712 $patch_mode_only = 1;
1714 elsif ($arg ne "--") {
1715 die sprintf(__
("invalid argument %s, expecting --"), $arg);
1720 my @cmd = ([ 'status', \
&status_cmd
, ],
1721 [ 'update', \
&update_cmd
, ],
1722 [ 'revert', \
&revert_cmd
, ],
1723 [ 'add untracked', \
&add_untracked_cmd
, ],
1724 [ 'patch', \
&patch_update_cmd
, ],
1725 [ 'diff', \
&diff_cmd
, ],
1726 [ 'quit', \
&quit_cmd
, ],
1727 [ 'help', \
&help_cmd
, ],
1730 my ($it) = list_and_choose
({ PROMPT
=> __
('What now'),
1733 HEADER
=> __
('*** Commands ***'),
1734 ON_EOF
=> \
&quit_cmd
,
1735 IMMEDIATE
=> 1 }, @cmd);
1749 if ($patch_mode_only) {