6 use Git
qw(unquote_path);
9 binmode(STDOUT
, ":raw");
11 my $repo = Git
->repository();
13 my $menu_use_color = $repo->get_colorbool('color.interactive');
14 my ($prompt_color, $header_color, $help_color) =
16 $repo->get_color('color.interactive.prompt', 'bold blue'),
17 $repo->get_color('color.interactive.header', 'bold'),
18 $repo->get_color('color.interactive.help', 'red bold'),
21 if ($menu_use_color) {
22 my $help_color_spec = ($repo->config('color.interactive.help') or
24 $error_color = $repo->get_color('color.interactive.error',
28 my $diff_use_color = $repo->get_colorbool('color.diff');
29 my ($fraginfo_color) =
31 $repo->get_color('color.diff.frag', 'cyan'),
33 my ($diff_plain_color) =
35 $repo->get_color('color.diff.plain', ''),
37 my ($diff_old_color) =
39 $repo->get_color('color.diff.old', 'red'),
41 my ($diff_new_color) =
43 $repo->get_color('color.diff.new', 'green'),
46 my $normal_color = $repo->get_color("", "reset");
48 my $diff_algorithm = $repo->config('diff.algorithm');
49 my $diff_filter = $repo->config('interactive.difffilter');
57 if ($repo->config_bool("interactive.singlekey")) {
59 require Term
::ReadKey
;
60 Term
::ReadKey
->import;
64 print STDERR
"missing Term::ReadKey, disabling interactive.singlekey\n";
68 my $termcap = Term
::Cap
->Tgetent;
69 foreach (values %$termcap) {
70 $term_escapes{$_} = 1 if /^\e/;
78 my $string = join("", @_);
81 # Put a color code at the beginning of each line, a reset at the end
82 # color after newlines that are not at the end of the string
83 $string =~ s/(\n+)(.)/$1$color$2/g;
84 # reset before newlines
85 $string =~ s/(\n+)/$normal_color$1/g;
86 # codes at beginning and end (if necessary):
87 $string =~ s/^/$color/;
88 $string =~ s/$/$normal_color/ unless $string =~ /\n$/;
93 # command line options
96 my $patch_mode_revision;
99 sub apply_patch_for_checkout_commit
;
100 sub apply_patch_for_stash
;
104 DIFF
=> 'diff-files -p',
105 APPLY
=> sub { apply_patch
'apply --cached', @_; },
106 APPLY_CHECK
=> 'apply --cached',
107 FILTER
=> 'file-only',
111 DIFF
=> 'diff-index -p HEAD',
112 APPLY
=> sub { apply_patch
'apply --cached', @_; },
113 APPLY_CHECK
=> 'apply --cached',
118 DIFF
=> 'diff-index -p --cached',
119 APPLY
=> sub { apply_patch
'apply -R --cached', @_; },
120 APPLY_CHECK
=> 'apply -R --cached',
121 FILTER
=> 'index-only',
125 DIFF
=> 'diff-index -R -p --cached',
126 APPLY
=> sub { apply_patch
'apply --cached', @_; },
127 APPLY_CHECK
=> 'apply --cached',
128 FILTER
=> 'index-only',
131 'checkout_index' => {
132 DIFF
=> 'diff-files -p',
133 APPLY
=> sub { apply_patch
'apply -R', @_; },
134 APPLY_CHECK
=> 'apply -R',
135 FILTER
=> 'file-only',
139 DIFF
=> 'diff-index -p',
140 APPLY
=> sub { apply_patch_for_checkout_commit
'-R', @_ },
141 APPLY_CHECK
=> 'apply -R',
145 'checkout_nothead' => {
146 DIFF
=> 'diff-index -R -p',
147 APPLY
=> sub { apply_patch_for_checkout_commit
'', @_ },
148 APPLY_CHECK
=> 'apply',
153 DIFF
=> 'diff-index -p',
154 APPLY
=> sub { apply_patch
'apply -R', @_ },
155 APPLY_CHECK
=> 'apply -R',
159 'worktree_nothead' => {
160 DIFF
=> 'diff-index -R -p',
161 APPLY
=> sub { apply_patch
'apply', @_ },
162 APPLY_CHECK
=> 'apply',
168 $patch_mode = 'stage';
169 my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
172 if ($^O
eq 'MSWin32') {
173 my @invalid = grep {m/[":*]/} @_;
174 die "$^O does not support: @invalid\n" if @invalid;
175 my @args = map { m/ /o ?
"\"$_\"": $_ } @_;
179 open($fh, '-|', @_) or die;
184 my ($GIT_DIR) = run_cmd_pipe
(qw(git rev-parse --git-dir));
186 if (!defined $GIT_DIR) {
187 exit(1); # rev-parse would have already said "not a git repo"
193 open $fh, 'git update-index --refresh |'
196 ;# ignore 'needs update'
206 run_cmd_pipe
(qw(git ls-files --others --exclude-standard --), @ARGV);
209 # TRANSLATORS: you can adjust this to align "git add -i" status menu
210 my $status_fmt = __
('%12s %12s %s');
211 my $status_head = sprintf($status_fmt, __
('staged'), __
('unstaged'), __
('path'));
215 sub is_initial_commit
{
216 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
217 unless defined $initial;
225 return $empty_tree if defined $empty_tree;
227 $empty_tree = run_cmd_pipe
(qw(git hash-object -t tree /dev/null));
233 sub get_diff_reference
{
235 if (defined $ref and $ref ne 'HEAD') {
237 } elsif (is_initial_commit
()) {
238 return get_empty_tree
();
244 # Returns list of hashes, contents of each of which are:
246 # BINARY: is a binary path
247 # INDEX: is index different from HEAD?
248 # FILE: is file different from index?
249 # INDEX_ADDDEL: is it add/delete between HEAD and index?
250 # FILE_ADDDEL: is it add/delete between index and file?
251 # UNMERGED: is the path unmerged
256 my ($add, $del, $adddel, $file);
258 my $reference = get_diff_reference
($patch_mode_revision);
259 for (run_cmd_pipe
(qw(git diff-index --cached
260 --numstat --summary), $reference,
262 if (($add, $del, $file) =
263 /^([-\d]+) ([-\d]+) (.*)/) {
265 $file = unquote_path
($file);
266 if ($add eq '-' && $del eq '-') {
267 $change = __
('binary');
271 $change = "+$add/-$del";
276 FILE
=> __
('nothing'),
279 elsif (($adddel, $file) =
280 /^ (create|delete) mode [0-7]+ (.*)$/) {
281 $file = unquote_path
($file);
282 $data{$file}{INDEX_ADDDEL
} = $adddel;
286 for (run_cmd_pipe
(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
287 if (($add, $del, $file) =
288 /^([-\d]+) ([-\d]+) (.*)/) {
289 $file = unquote_path
($file);
291 if ($add eq '-' && $del eq '-') {
292 $change = __
('binary');
296 $change = "+$add/-$del";
298 $data{$file}{FILE
} = $change;
300 $data{$file}{BINARY
} = 1;
303 elsif (($adddel, $file) =
304 /^ (create|delete) mode [0-7]+ (.*)$/) {
305 $file = unquote_path
($file);
306 $data{$file}{FILE_ADDDEL
} = $adddel;
308 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
309 $file = unquote_path
($2);
310 if (!exists $data{$file}) {
312 INDEX
=> __
('unchanged'),
317 $data{$file}{UNMERGED
} = 1;
322 for (sort keys %data) {
326 if ($only eq 'index-only') {
327 next if ($it->{INDEX
} eq __
('unchanged'));
329 if ($only eq 'file-only') {
330 next if ($it->{FILE
} eq __
('nothing'));
342 my ($string, @stuff) = @_;
344 for (my $i = 0; $i < @stuff; $i++) {
348 if ((ref $it) eq 'ARRAY') {
356 if ($it =~ /^$string/) {
360 if (defined $hit && defined $found) {
370 # inserts string into trie and updates count for each character
372 my ($trie, $string) = @_;
373 foreach (split //, $string) {
374 $trie = $trie->{$_} ||= {COUNT
=> 0};
379 # returns an array of tuples (prefix, remainder)
380 sub find_unique_prefixes
{
384 # any single prefix exceeding the soft limit is omitted
385 # if any prefix exceeds the hard limit all are omitted
386 # 0 indicates no limit
390 # build a trie modelling all possible options
392 foreach my $print (@stuff) {
393 if ((ref $print) eq 'ARRAY') {
394 $print = $print->[0];
396 elsif ((ref $print) eq 'HASH') {
397 $print = $print->{VALUE
};
399 update_trie
(\
%trie, $print);
400 push @return, $print;
403 # use the trie to find the unique prefixes
404 for (my $i = 0; $i < @return; $i++) {
405 my $ret = $return[$i];
406 my @letters = split //, $ret;
408 my ($prefix, $remainder);
410 for ($j = 0; $j < @letters; $j++) {
411 my $letter = $letters[$j];
412 if ($search{$letter}{COUNT
} == 1) {
413 $prefix = substr $ret, 0, $j + 1;
414 $remainder = substr $ret, $j + 1;
418 my $prefix = substr $ret, 0, $j;
420 if ($hard_limit && $j + 1 > $hard_limit);
422 %search = %{$search{$letter}};
424 if (ord($letters[0]) > 127 ||
425 ($soft_limit && $j + 1 > $soft_limit)) {
429 $return[$i] = [$prefix, $remainder];
434 # filters out prefixes which have special meaning to list_and_choose()
435 sub is_valid_prefix
{
437 return (defined $prefix) &&
438 !($prefix =~ /[\s,]/) && # separators
439 !($prefix =~ /^-/) && # deselection
440 !($prefix =~ /^\d+/) && # selection
441 ($prefix ne '*') && # "all" wildcard
442 ($prefix ne '?'); # prompt help
445 # given a prefix/remainder tuple return a string with the prefix highlighted
446 # for now use square brackets; later might use ANSI colors (underline, bold)
447 sub highlight_prefix
{
449 my $remainder = shift;
451 if (!defined $prefix) {
455 if (!is_valid_prefix
($prefix)) {
456 return "$prefix$remainder";
459 if (!$menu_use_color) {
460 return "[$prefix]$remainder";
463 return "$prompt_color$prefix$normal_color$remainder";
467 print STDERR colored
$error_color, @_;
470 sub list_and_choose
{
471 my ($opts, @stuff) = @_;
472 my (@chosen, @return);
477 my @prefixes = find_unique_prefixes
(@stuff) unless $opts->{LIST_ONLY
};
483 if ($opts->{HEADER
}) {
484 if (!$opts->{LIST_FLAT
}) {
487 print colored
$header_color, "$opts->{HEADER}\n";
489 for ($i = 0; $i < @stuff; $i++) {
490 my $chosen = $chosen[$i] ?
'*' : ' ';
491 my $print = $stuff[$i];
492 my $ref = ref $print;
493 my $highlighted = highlight_prefix
(@
{$prefixes[$i]})
495 if ($ref eq 'ARRAY') {
496 $print = $highlighted || $print->[0];
498 elsif ($ref eq 'HASH') {
499 my $value = $highlighted || $print->{VALUE
};
500 $print = sprintf($status_fmt,
506 $print = $highlighted || $print;
508 printf("%s%2d: %s", $chosen, $i+1, $print);
509 if (($opts->{LIST_FLAT
}) &&
510 (($i + 1) % ($opts->{LIST_FLAT
}))) {
523 return if ($opts->{LIST_ONLY
});
525 print colored
$prompt_color, $opts->{PROMPT
};
526 if ($opts->{SINGLETON
}) {
535 $opts->{ON_EOF
}->() if $opts->{ON_EOF
};
542 singleton_prompt_help_cmd
() :
546 for my $choice (split(/[\s,]+/, $line)) {
550 # Input that begins with '-'; unchoose
551 if ($choice =~ s/^-//) {
554 # A range can be specified like 5-7 or 5-.
555 if ($choice =~ /^(\d+)-(\d*)$/) {
556 ($bottom, $top) = ($1, length($2) ?
$2 : 1 + @stuff);
558 elsif ($choice =~ /^\d+$/) {
559 $bottom = $top = $choice;
561 elsif ($choice eq '*') {
566 $bottom = $top = find_unique
($choice, @stuff);
567 if (!defined $bottom) {
568 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
572 if ($opts->{SINGLETON
} && $bottom != $top) {
573 error_msg
sprintf(__
("Huh (%s)?\n"), $choice);
576 for ($i = $bottom-1; $i <= $top-1; $i++) {
577 next if (@stuff <= $i || $i < 0);
578 $chosen[$i] = $choose;
581 last if ($opts->{IMMEDIATE
} || $line eq '*');
583 for ($i = 0; $i < @stuff; $i++) {
585 push @return, $stuff[$i];
591 sub singleton_prompt_help_cmd
{
592 print colored
$help_color, __
<<'EOF' ;
594 1 - select a numbered item
595 foo - select item based on unique prefix
596 - (empty) select nothing
600 sub prompt_help_cmd
{
601 print colored
$help_color, __
<<'EOF' ;
603 1 - select a single item
604 3-5 - select a range of items
605 2-3,6-9 - select multiple ranges
606 foo - select item based on unique prefix
607 -... - unselect specified items
609 - (empty) finish selecting
614 list_and_choose
({ LIST_ONLY
=> 1, HEADER
=> $status_head },
622 if ($did eq 'added') {
623 printf(__n
("added %d path\n", "added %d paths\n",
625 } elsif ($did eq 'updated') {
626 printf(__n
("updated %d path\n", "updated %d paths\n",
628 } elsif ($did eq 'reverted') {
629 printf(__n
("reverted %d path\n", "reverted %d paths\n",
632 printf(__n
("touched %d path\n", "touched %d paths\n",
638 my @mods = list_modified
('file-only');
641 my @update = list_and_choose
({ PROMPT
=> __
('Update'),
642 HEADER
=> $status_head, },
645 system(qw(git update-index --add --remove --),
646 map { $_->{VALUE
} } @update);
647 say_n_paths
('updated', @update);
653 my @update = list_and_choose
({ PROMPT
=> __
('Revert'),
654 HEADER
=> $status_head, },
657 if (is_initial_commit
()) {
658 system(qw(git rm --cached),
659 map { $_->{VALUE
} } @update);
662 my @lines = run_cmd_pipe
(qw(git ls-tree HEAD --),
663 map { $_->{VALUE
} } @update);
665 open $fh, '| git update-index --index-info'
672 if ($_->{INDEX_ADDDEL
} &&
673 $_->{INDEX_ADDDEL
} eq 'create') {
674 system(qw(git update-index --force-remove --),
676 printf(__
("note: %s is untracked now.\n"), $_->{VALUE
});
681 say_n_paths
('reverted', @update);
686 sub add_untracked_cmd
{
687 my @add = list_and_choose
({ PROMPT
=> __
('Add untracked') },
690 system(qw(git update-index --add --), @add);
691 say_n_paths
('added', @add);
693 print __
("No untracked files.\n");
701 open $fh, '| git ' . $cmd . " --allow-overlap";
708 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF
});
709 if (defined $diff_algorithm) {
710 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
712 if (defined $patch_mode_revision) {
713 push @diff_cmd, get_diff_reference
($patch_mode_revision);
715 my @diff = run_cmd_pipe
("git", @diff_cmd, "--", $path);
717 if ($diff_use_color) {
718 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
719 if (defined $diff_filter) {
720 # quotemeta is overkill, but sufficient for shell-quoting
721 my $diff = join(' ', map { quotemeta } @display_cmd);
722 @display_cmd = ("$diff | $diff_filter");
725 @colored = run_cmd_pipe
(@display_cmd);
727 my (@hunk) = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
729 if (@colored && @colored != @diff) {
731 "fatal: mismatched output from interactive.diffFilter\n",
732 "hint: Your filter must maintain a one-to-one correspondence\n",
733 "hint: between its input and output lines.\n";
737 for (my $i = 0; $i < @diff; $i++) {
738 if ($diff[$i] =~ /^@@ /) {
739 push @hunk, { TEXT
=> [], DISPLAY
=> [],
742 push @
{$hunk[-1]{TEXT
}}, $diff[$i];
743 push @
{$hunk[-1]{DISPLAY
}},
744 (@colored ?
$colored[$i] : $diff[$i]);
749 sub parse_diff_header
{
752 my $head = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'header' };
753 my $mode = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'mode' };
754 my $deletion = { TEXT
=> [], DISPLAY
=> [], TYPE
=> 'deletion' };
756 for (my $i = 0; $i < @
{$src->{TEXT
}}; $i++) {
758 $src->{TEXT
}->[$i] =~ /^(old|new) mode (\d+)$/ ?
$mode :
759 $src->{TEXT
}->[$i] =~ /^deleted file/ ?
$deletion :
761 push @
{$dest->{TEXT
}}, $src->{TEXT
}->[$i];
762 push @
{$dest->{DISPLAY
}}, $src->{DISPLAY
}->[$i];
764 return ($head, $mode, $deletion);
767 sub hunk_splittable
{
770 my @s = split_hunk
($text);
774 sub parse_hunk_header
{
776 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
777 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
778 $o_cnt = 1 unless defined $o_cnt;
779 $n_cnt = 1 unless defined $n_cnt;
780 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
783 sub format_hunk_header
{
784 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
785 return ("@@ -$o_ofs" .
786 (($o_cnt != 1) ?
",$o_cnt" : '') .
788 (($n_cnt != 1) ?
",$n_cnt" : '') .
793 my ($text, $display) = @_;
795 if (!defined $display) {
798 # If there are context lines in the middle of a hunk,
799 # it can be split, but we would need to take care of
802 my ($o_ofs, undef, $n_ofs) = parse_hunk_header
($text->[0]);
807 my $next_hunk_start = undef;
808 my $i = $hunk_start - 1;
822 while (++$i < @
$text) {
823 my $line = $text->[$i];
824 my $display = $display->[$i];
825 if ($line =~ /^\\/) {
826 push @
{$this->{TEXT
}}, $line;
827 push @
{$this->{DISPLAY
}}, $display;
831 if ($this->{ADDDEL
} &&
832 !defined $next_hunk_start) {
833 # We have seen leading context and
834 # adds/dels and then here is another
835 # context, which is trailing for this
836 # split hunk and leading for the next
838 $next_hunk_start = $i;
840 push @
{$this->{TEXT
}}, $line;
841 push @
{$this->{DISPLAY
}}, $display;
844 if (defined $next_hunk_start) {
851 if (defined $next_hunk_start) {
852 # We are done with the current hunk and
853 # this is the first real change for the
855 $hunk_start = $next_hunk_start;
856 $o_ofs = $this->{OLD
} + $this->{OCNT
};
857 $n_ofs = $this->{NEW
} + $this->{NCNT
};
858 $o_ofs -= $this->{POSTCTX
};
859 $n_ofs -= $this->{POSTCTX
};
863 push @
{$this->{TEXT
}}, $line;
864 push @
{$this->{DISPLAY
}}, $display;
878 for my $hunk (@split) {
879 $o_ofs = $hunk->{OLD
};
880 $n_ofs = $hunk->{NEW
};
881 my $o_cnt = $hunk->{OCNT
};
882 my $n_cnt = $hunk->{NCNT
};
884 my $head = format_hunk_header
($o_ofs, $o_cnt, $n_ofs, $n_cnt);
885 my $display_head = $head;
886 unshift @
{$hunk->{TEXT
}}, $head;
887 if ($diff_use_color) {
888 $display_head = colored
($fraginfo_color, $head);
890 unshift @
{$hunk->{DISPLAY
}}, $display_head;
895 sub find_last_o_ctx
{
897 my $text = $it->{TEXT
};
898 my ($o_ofs, $o_cnt) = parse_hunk_header
($text->[0]);
900 my $last_o_ctx = $o_ofs + $o_cnt;
902 my $line = $text->[$i];
913 my ($prev, $this) = @_;
914 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
915 parse_hunk_header
($prev->{TEXT
}[0]);
916 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
917 parse_hunk_header
($this->{TEXT
}[0]);
919 my (@line, $i, $ofs, $o_cnt, $n_cnt);
922 for ($i = 1; $i < @
{$prev->{TEXT
}}; $i++) {
923 my $line = $prev->{TEXT
}[$i];
924 if ($line =~ /^\+/) {
928 } elsif ($line =~ /^\\/) {
933 last if ($o1_ofs <= $ofs);
943 for ($i = 1; $i < @
{$this->{TEXT
}}; $i++) {
944 my $line = $this->{TEXT
}[$i];
945 if ($line =~ /^\+/) {
949 } elsif ($line =~ /^\\/) {
960 my $head = format_hunk_header
($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
961 @
{$prev->{TEXT
}} = ($head, @line);
964 sub coalesce_overlapping_hunks
{
968 my ($last_o_ctx, $last_was_dirty);
972 if ($_->{TYPE
} ne 'hunk') {
976 my $text = $_->{TEXT
};
977 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
978 parse_hunk_header
($text->[0]);
980 $ofs_delta += $o_cnt - $n_cnt;
981 # If this hunk has been edited then subtract
982 # the delta that is due to the edit.
983 if ($_->{OFS_DELTA
}) {
984 $ofs_delta -= $_->{OFS_DELTA
};
989 $n_ofs += $ofs_delta;
990 $_->{TEXT
}->[0] = format_hunk_header
($o_ofs, $o_cnt,
993 # If this hunk was edited then adjust the offset delta
994 # to reflect the edit.
995 if ($_->{OFS_DELTA
}) {
996 $ofs_delta += $_->{OFS_DELTA
};
998 if (defined $last_o_ctx &&
999 $o_ofs <= $last_o_ctx &&
1002 merge_hunk
($out[-1], $_);
1007 $last_o_ctx = find_last_o_ctx
($out[-1]);
1008 $last_was_dirty = $_->{DIRTY
};
1013 sub reassemble_patch
{
1017 # Include everything in the header except the beginning of the diff.
1018 push @patch, (grep { !/^[-+]{3}/ } @
$head);
1020 # Then include any headers from the hunk lines, which must
1021 # come before any actual hunk.
1022 while (@_ && $_[0] !~ /^@/) {
1026 # Then begin the diff.
1027 push @patch, grep { /^[-+]{3}/ } @
$head;
1029 # And then the actual hunks.
1037 colored
((/^@/ ?
$fraginfo_color :
1038 /^\+/ ?
$diff_new_color :
1039 /^-/ ?
$diff_old_color :
1045 my %edit_hunk_manually_modes = (
1047 "If the patch applies cleanly, the edited hunk will immediately be
1048 marked for staging."),
1050 "If the patch applies cleanly, the edited hunk will immediately be
1051 marked for stashing."),
1053 "If the patch applies cleanly, the edited hunk will immediately be
1054 marked for unstaging."),
1055 reset_nothead
=> N__
(
1056 "If the patch applies cleanly, the edited hunk will immediately be
1057 marked for applying."),
1058 checkout_index
=> N__
(
1059 "If the patch applies cleanly, the edited hunk will immediately be
1060 marked for discarding."),
1061 checkout_head
=> N__
(
1062 "If the patch applies cleanly, the edited hunk will immediately be
1063 marked for discarding."),
1064 checkout_nothead
=> N__
(
1065 "If the patch applies cleanly, the edited hunk will immediately be
1066 marked for applying."),
1067 worktree_head
=> N__
(
1068 "If the patch applies cleanly, the edited hunk will immediately be
1069 marked for discarding."),
1070 worktree_nothead
=> N__
(
1071 "If the patch applies cleanly, the edited hunk will immediately be
1072 marked for applying."),
1075 sub recount_edited_hunk
{
1077 my ($oldtext, $newtext) = @_;
1078 my ($o_cnt, $n_cnt) = (0, 0);
1079 for (@
{$newtext}[1..$#{$newtext}]) {
1080 my $mode = substr($_, 0, 1);
1083 } elsif ($mode eq '+') {
1085 } elsif ($mode eq ' ' or $mode eq "\n") {
1090 my ($o_ofs, undef, $n_ofs, undef) =
1091 parse_hunk_header
($newtext->[0]);
1092 $newtext->[0] = format_hunk_header
($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1093 my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1094 parse_hunk_header
($oldtext->[0]);
1095 # Return the change in the number of lines inserted by this hunk
1096 return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1099 sub edit_hunk_manually
{
1102 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1104 open $fh, '>', $hunkfile
1105 or die sprintf(__
("failed to open hunk edit file for writing: %s"), $!);
1106 print $fh Git
::comment_lines __
("Manual hunk edit mode -- see bottom for a quick guide.\n");
1107 print $fh @
$oldtext;
1108 my $is_reverse = $patch_mode_flavour{IS_REVERSE
};
1109 my ($remove_plus, $remove_minus) = $is_reverse ?
('-', '+') : ('+', '-');
1110 my $comment_line_char = Git
::get_comment_line_char
;
1111 print $fh Git
::comment_lines
sprintf(__
<<EOF, $remove_minus, $remove_plus, $comment_line_char),
1113 To remove '%s' lines, make them ' ' lines (context).
1114 To remove '%s' lines, delete them.
1115 Lines starting with %s will be removed.
1117 __
($edit_hunk_manually_modes{$patch_mode}),
1118 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1120 If it does not apply cleanly, you will be given an opportunity to
1121 edit again. If all lines of the hunk are removed, then the edit is
1122 aborted and the hunk is left unchanged.
1126 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
1127 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1133 open $fh, '<', $hunkfile
1134 or die sprintf(__
("failed to open hunk edit file for reading: %s"), $!);
1135 my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1139 # Abort if nothing remains
1140 if (!grep { /\S/ } @newtext) {
1144 # Reinsert the first hunk header if the user accidentally deleted it
1145 if ($newtext[0] !~ /^@/) {
1146 unshift @newtext, $oldtext->[0];
1152 return run_git_apply
($patch_mode_flavour{APPLY_CHECK
} . ' --check',
1153 map { @
{$_->{TEXT
}} } @_);
1156 sub _restore_terminal_and_die
{
1162 sub prompt_single_character
{
1164 local $SIG{TERM
} = \
&_restore_terminal_and_die
;
1165 local $SIG{INT
} = \
&_restore_terminal_and_die
;
1167 my $key = ReadKey
0;
1169 if ($use_termcap and $key eq "\e") {
1170 while (!defined $term_escapes{$key}) {
1171 my $next = ReadKey
0.5;
1172 last if (!defined $next);
1177 print "$key" if defined $key;
1188 print colored
$prompt_color, $prompt;
1189 my $line = prompt_single_character
;
1190 return undef unless defined $line;
1191 return 0 if $line =~ /^n/i;
1192 return 1 if $line =~ /^y/i;
1196 sub edit_hunk_loop
{
1197 my ($head, $hunks, $ix) = @_;
1198 my $hunk = $hunks->[$ix];
1199 my $text = $hunk->{TEXT
};
1202 my $newtext = edit_hunk_manually
($text);
1203 if (!defined $newtext) {
1208 TYPE
=> $hunk->{TYPE
},
1212 $newhunk->{OFS_DELTA
} = recount_edited_hunk
($text, $newtext);
1213 # If this hunk has already been edited then add the
1214 # offset delta of the previous edit to get the real
1215 # delta from the original unedited hunk.
1216 $hunk->{OFS_DELTA
} and
1217 $newhunk->{OFS_DELTA
} += $hunk->{OFS_DELTA
};
1218 if (diff_applies
($head,
1219 @
{$hunks}[0..$ix-1],
1221 @
{$hunks}[$ix+1..$#{$hunks}])) {
1222 $newhunk->{DISPLAY
} = [color_diff
(@
{$newtext})];
1227 # TRANSLATORS: do not translate [y/n]
1228 # The program will only accept that input
1230 # Consider translating (saying "no" discards!) as
1231 # (saying "n" for "no" discards!) if the translation
1232 # of the word "no" does not start with n.
1233 __
('Your edited hunk does not apply. Edit again '
1234 . '(saying "no" discards!) [y/n]? ')
1240 my %help_patch_modes = (
1242 "y - stage this hunk
1243 n - do not stage this hunk
1244 q - quit; do not stage this hunk or any of the remaining ones
1245 a - stage this hunk and all later hunks in the file
1246 d - do not stage this hunk or any of the later hunks in the file"),
1248 "y - stash this hunk
1249 n - do not stash this hunk
1250 q - quit; do not stash this hunk or any of the remaining ones
1251 a - stash this hunk and all later hunks in the file
1252 d - do not stash this hunk or any of the later hunks in the file"),
1254 "y - unstage this hunk
1255 n - do not unstage this hunk
1256 q - quit; do not unstage this hunk or any of the remaining ones
1257 a - unstage this hunk and all later hunks in the file
1258 d - do not unstage this hunk or any of the later hunks in the file"),
1259 reset_nothead
=> N__
(
1260 "y - apply this hunk to index
1261 n - do not apply this hunk to index
1262 q - quit; do not apply this hunk or any of the remaining ones
1263 a - apply this hunk and all later hunks in the file
1264 d - do not apply this hunk or any of the later hunks in the file"),
1265 checkout_index
=> N__
(
1266 "y - discard this hunk from worktree
1267 n - do not discard this hunk from worktree
1268 q - quit; do not discard this hunk or any of the remaining ones
1269 a - discard this hunk and all later hunks in the file
1270 d - do not discard this hunk or any of the later hunks in the file"),
1271 checkout_head
=> N__
(
1272 "y - discard this hunk from index and worktree
1273 n - do not discard this hunk from index and worktree
1274 q - quit; do not discard this hunk or any of the remaining ones
1275 a - discard this hunk and all later hunks in the file
1276 d - do not discard this hunk or any of the later hunks in the file"),
1277 checkout_nothead
=> N__
(
1278 "y - apply this hunk to index and worktree
1279 n - do not apply this hunk to index and worktree
1280 q - quit; do not apply this hunk or any of the remaining ones
1281 a - apply this hunk and all later hunks in the file
1282 d - do not apply this hunk or any of the later hunks in the file"),
1283 worktree_head
=> N__
(
1284 "y - discard this hunk from worktree
1285 n - do not discard this hunk from worktree
1286 q - quit; do not discard this hunk or any of the remaining ones
1287 a - discard this hunk and all later hunks in the file
1288 d - do not discard this hunk or any of the later hunks in the file"),
1289 worktree_nothead
=> N__
(
1290 "y - apply this hunk to worktree
1291 n - do not apply this hunk to worktree
1292 q - quit; do not apply this hunk or any of the remaining ones
1293 a - apply this hunk and all later hunks in the file
1294 d - do not apply this hunk or any of the later hunks in the file"),
1297 sub help_patch_cmd
{
1299 my $other = $_[0] . ",?";
1300 print colored
$help_color, __
($help_patch_modes{$patch_mode}), "\n",
1301 map { "$_\n" } grep {
1302 my $c = quotemeta(substr($_, 0, 1));
1304 } split "\n", __
<<EOF ;
1305 g - select a hunk to go to
1306 / - search for a hunk matching the given regex
1307 j - leave this hunk undecided, see next undecided hunk
1308 J - leave this hunk undecided, see next hunk
1309 k - leave this hunk undecided, see previous undecided hunk
1310 K - leave this hunk undecided, see previous hunk
1311 s - split the current hunk into smaller hunks
1312 e - manually edit the current hunk
1319 my $ret = run_git_apply
$cmd, @_;
1326 sub apply_patch_for_checkout_commit
{
1327 my $reverse = shift;
1328 my $applies_index = run_git_apply
'apply '.$reverse.' --cached --check', @_;
1329 my $applies_worktree = run_git_apply
'apply '.$reverse.' --check', @_;
1331 if ($applies_worktree && $applies_index) {
1332 run_git_apply
'apply '.$reverse.' --cached', @_;
1333 run_git_apply
'apply '.$reverse, @_;
1335 } elsif (!$applies_index) {
1336 print colored
$error_color, __
("The selected hunks do not apply to the index!\n");
1337 if (prompt_yesno __
("Apply them to the worktree anyway? ")) {
1338 return run_git_apply
'apply '.$reverse, @_;
1340 print colored
$error_color, __
("Nothing was applied.\n");
1349 sub patch_update_cmd
{
1350 my @all_mods = list_modified
($patch_mode_flavour{FILTER
});
1351 error_msg
sprintf(__
("ignoring unmerged: %s\n"), $_->{VALUE
})
1352 for grep { $_->{UNMERGED
} } @all_mods;
1353 @all_mods = grep { !$_->{UNMERGED
} } @all_mods;
1355 my @mods = grep { !($_->{BINARY
}) } @all_mods;
1360 print STDERR __
("Only binary files changed.\n");
1362 print STDERR __
("No changes.\n");
1366 if ($patch_mode_only) {
1370 @them = list_and_choose
({ PROMPT
=> __
('Patch update'),
1371 HEADER
=> $status_head, },
1375 return 0 if patch_update_file
($_->{VALUE
});
1379 # Generate a one line summary of a hunk.
1380 sub summarize_hunk
{
1382 my $summary = $rhunk->{TEXT
}[0];
1384 # Keep the line numbers, discard extra context.
1385 $summary =~ s/@@(.*?)@@.*/$1 /s;
1386 $summary .= " " x
(20 - length $summary);
1388 # Add some user context.
1389 for my $line (@
{$rhunk->{TEXT
}}) {
1390 if ($line =~ m/^[+-].*\w/) {
1397 return substr($summary, 0, 80) . "\n";
1401 # Print a one-line summary of each hunk in the array ref in
1402 # the first argument, starting with the index in the 2nd.
1404 my ($hunks, $i) = @_;
1407 for (; $i < @
$hunks && $ctr < 20; $i++, $ctr++) {
1409 if (defined $hunks->[$i]{USE
}) {
1410 $status = $hunks->[$i]{USE
} ?
"+" : "-";
1415 summarize_hunk
($hunks->[$i]);
1420 my %patch_update_prompt_modes = (
1422 mode
=> N__
("Stage mode change [y,n,q,a,d%s,?]? "),
1423 deletion
=> N__
("Stage deletion [y,n,q,a,d%s,?]? "),
1424 hunk
=> N__
("Stage this hunk [y,n,q,a,d%s,?]? "),
1427 mode
=> N__
("Stash mode change [y,n,q,a,d%s,?]? "),
1428 deletion
=> N__
("Stash deletion [y,n,q,a,d%s,?]? "),
1429 hunk
=> N__
("Stash this hunk [y,n,q,a,d%s,?]? "),
1432 mode
=> N__
("Unstage mode change [y,n,q,a,d%s,?]? "),
1433 deletion
=> N__
("Unstage deletion [y,n,q,a,d%s,?]? "),
1434 hunk
=> N__
("Unstage this hunk [y,n,q,a,d%s,?]? "),
1437 mode
=> N__
("Apply mode change to index [y,n,q,a,d%s,?]? "),
1438 deletion
=> N__
("Apply deletion to index [y,n,q,a,d%s,?]? "),
1439 hunk
=> N__
("Apply this hunk to index [y,n,q,a,d%s,?]? "),
1442 mode
=> N__
("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1443 deletion
=> N__
("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1444 hunk
=> N__
("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1447 mode
=> N__
("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1448 deletion
=> N__
("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1449 hunk
=> N__
("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
1451 checkout_nothead
=> {
1452 mode
=> N__
("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1453 deletion
=> N__
("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1454 hunk
=> N__
("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
1457 mode
=> N__
("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1458 deletion
=> N__
("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1459 hunk
=> N__
("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1461 worktree_nothead
=> {
1462 mode
=> N__
("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1463 deletion
=> N__
("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1464 hunk
=> N__
("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1468 sub patch_update_file
{
1472 my ($head, @hunk) = parse_diff
($path);
1473 ($head, my $mode, my $deletion) = parse_diff_header
($head);
1474 for (@
{$head->{DISPLAY
}}) {
1478 if (@
{$mode->{TEXT
}}) {
1479 unshift @hunk, $mode;
1481 if (@
{$deletion->{TEXT
}}) {
1482 foreach my $hunk (@hunk) {
1483 push @
{$deletion->{TEXT
}}, @
{$hunk->{TEXT
}};
1484 push @
{$deletion->{DISPLAY
}}, @
{$hunk->{DISPLAY
}};
1486 @hunk = ($deletion);
1489 $num = scalar @hunk;
1493 my ($prev, $next, $other, $undecided, $i);
1499 for ($i = 0; $i < $ix; $i++) {
1500 if (!defined $hunk[$i]{USE
}) {
1509 for ($i = $ix + 1; $i < $num; $i++) {
1510 if (!defined $hunk[$i]{USE
}) {
1516 if ($ix < $num - 1) {
1522 for ($i = 0; $i < $num; $i++) {
1523 if (!defined $hunk[$i]{USE
}) {
1528 last if (!$undecided);
1530 if ($hunk[$ix]{TYPE
} eq 'hunk' &&
1531 hunk_splittable
($hunk[$ix]{TEXT
})) {
1534 if ($hunk[$ix]{TYPE
} eq 'hunk') {
1537 for (@
{$hunk[$ix]{DISPLAY
}}) {
1540 print colored
$prompt_color,
1541 sprintf(__
($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE
}}), $other);
1543 my $line = prompt_single_character
;
1544 last unless defined $line;
1546 if ($line =~ /^y/i) {
1547 $hunk[$ix]{USE
} = 1;
1549 elsif ($line =~ /^n/i) {
1550 $hunk[$ix]{USE
} = 0;
1552 elsif ($line =~ /^a/i) {
1553 while ($ix < $num) {
1554 if (!defined $hunk[$ix]{USE
}) {
1555 $hunk[$ix]{USE
} = 1;
1561 elsif ($line =~ /^g(.*)/) {
1563 unless ($other =~ /g/) {
1564 error_msg __
("No other hunks to goto\n");
1567 my $no = $ix > 10 ?
$ix - 10 : 0;
1568 while ($response eq '') {
1569 $no = display_hunks
(\
@hunk, $no);
1571 print __
("go to which hunk (<ret> to see more)? ");
1573 print __
("go to which hunk? ");
1575 $response = <STDIN
>;
1576 if (!defined $response) {
1581 if ($response !~ /^\s*\d+\s*$/) {
1582 error_msg
sprintf(__
("Invalid number: '%s'\n"),
1584 } elsif (0 < $response && $response <= $num) {
1585 $ix = $response - 1;
1587 error_msg
sprintf(__n
("Sorry, only %d hunk available.\n",
1588 "Sorry, only %d hunks available.\n", $num), $num);
1592 elsif ($line =~ /^d/i) {
1593 while ($ix < $num) {
1594 if (!defined $hunk[$ix]{USE
}) {
1595 $hunk[$ix]{USE
} = 0;
1601 elsif ($line =~ /^q/i) {
1602 for ($i = 0; $i < $num; $i++) {
1603 if (!defined $hunk[$i]{USE
}) {
1610 elsif ($line =~ m
|^/(.*)|) {
1612 unless ($other =~ m
|/|) {
1613 error_msg __
("No other hunks to search\n");
1617 print colored
$prompt_color, __
("search for regex? ");
1619 if (defined $regex) {
1625 $search_string = qr{$regex}m;
1628 my ($err,$exp) = ($@
, $1);
1629 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1630 error_msg
sprintf(__
("Malformed search regexp %s: %s\n"), $exp, $err);
1635 my $text = join ("", @
{$hunk[$iy]{TEXT
}});
1636 last if ($text =~ $search_string);
1638 $iy = 0 if ($iy >= $num);
1640 error_msg __
("No hunk matches the given pattern\n");
1647 elsif ($line =~ /^K/) {
1648 if ($other =~ /K/) {
1652 error_msg __
("No previous hunk\n");
1656 elsif ($line =~ /^J/) {
1657 if ($other =~ /J/) {
1661 error_msg __
("No next hunk\n");
1665 elsif ($line =~ /^k/) {
1666 if ($other =~ /k/) {
1670 !defined $hunk[$ix]{USE
});
1674 error_msg __
("No previous hunk\n");
1678 elsif ($line =~ /^j/) {
1679 if ($other !~ /j/) {
1680 error_msg __
("No next hunk\n");
1684 elsif ($line =~ /^s/) {
1685 unless ($other =~ /s/) {
1686 error_msg __
("Sorry, cannot split this hunk\n");
1689 my @split = split_hunk
($hunk[$ix]{TEXT
}, $hunk[$ix]{DISPLAY
});
1691 print colored
$header_color, sprintf(
1692 __n
("Split into %d hunk.\n",
1693 "Split into %d hunks.\n",
1694 scalar(@split)), scalar(@split));
1696 splice (@hunk, $ix, 1, @split);
1697 $num = scalar @hunk;
1700 elsif ($line =~ /^e/) {
1701 unless ($other =~ /e/) {
1702 error_msg __
("Sorry, cannot edit this hunk\n");
1705 my $newhunk = edit_hunk_loop
($head, \
@hunk, $ix);
1706 if (defined $newhunk) {
1707 splice @hunk, $ix, 1, $newhunk;
1711 help_patch_cmd
($other);
1717 last if ($ix >= $num ||
1718 !defined $hunk[$ix]{USE
});
1723 @hunk = coalesce_overlapping_hunks
(@hunk);
1729 push @result, @
{$_->{TEXT
}};
1734 my @patch = reassemble_patch
($head->{TEXT
}, @result);
1735 my $apply_routine = $patch_mode_flavour{APPLY
};
1736 &$apply_routine(@patch);
1745 my @mods = list_modified
('index-only');
1746 @mods = grep { !($_->{BINARY
}) } @mods;
1748 my (@them) = list_and_choose
({ PROMPT
=> __
('Review diff'),
1750 HEADER
=> $status_head, },
1753 my $reference = (is_initial_commit
()) ? get_empty_tree
() : 'HEAD';
1754 system(qw(git diff -p --cached), $reference, '--',
1755 map { $_->{VALUE
} } @them);
1764 # TRANSLATORS: please do not translate the command names
1765 # 'status', 'update', 'revert', etc.
1766 print colored
$help_color, __
<<'EOF' ;
1767 status - show paths with changes
1768 update - add working tree state to the staged set of changes
1769 revert - revert staged set of changes back to the HEAD version
1770 patch - pick hunks and update selectively
1771 diff - view diff between HEAD and index
1772 add untracked - add contents of untracked files to the staged set of changes
1777 return unless @ARGV;
1778 my $arg = shift @ARGV;
1779 if ($arg =~ /--patch(?:=(.*))?/) {
1781 if ($1 eq 'reset') {
1782 $patch_mode = 'reset_head';
1783 $patch_mode_revision = 'HEAD';
1784 $arg = shift @ARGV or die __
("missing --");
1786 $patch_mode_revision = $arg;
1787 $patch_mode = ($arg eq 'HEAD' ?
1788 'reset_head' : 'reset_nothead');
1789 $arg = shift @ARGV or die __
("missing --");
1791 } elsif ($1 eq 'checkout') {
1792 $arg = shift @ARGV or die __
("missing --");
1794 $patch_mode = 'checkout_index';
1796 $patch_mode_revision = $arg;
1797 $patch_mode = ($arg eq 'HEAD' ?
1798 'checkout_head' : 'checkout_nothead');
1799 $arg = shift @ARGV or die __
("missing --");
1801 } elsif ($1 eq 'worktree') {
1802 $arg = shift @ARGV or die __
("missing --");
1804 $patch_mode = 'checkout_index';
1806 $patch_mode_revision = $arg;
1807 $patch_mode = ($arg eq 'HEAD' ?
1808 'worktree_head' : 'worktree_nothead');
1809 $arg = shift @ARGV or die __
("missing --");
1811 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1813 $arg = shift @ARGV or die __
("missing --");
1815 die sprintf(__
("unknown --patch mode: %s"), $1);
1818 $patch_mode = 'stage';
1819 $arg = shift @ARGV or die __
("missing --");
1821 die sprintf(__
("invalid argument %s, expecting --"),
1822 $arg) unless $arg eq "--";
1823 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1824 $patch_mode_only = 1;
1826 elsif ($arg ne "--") {
1827 die sprintf(__
("invalid argument %s, expecting --"), $arg);
1832 my @cmd = ([ 'status', \
&status_cmd
, ],
1833 [ 'update', \
&update_cmd
, ],
1834 [ 'revert', \
&revert_cmd
, ],
1835 [ 'add untracked', \
&add_untracked_cmd
, ],
1836 [ 'patch', \
&patch_update_cmd
, ],
1837 [ 'diff', \
&diff_cmd
, ],
1838 [ 'quit', \
&quit_cmd
, ],
1839 [ 'help', \
&help_cmd
, ],
1842 my ($it) = list_and_choose
({ PROMPT
=> __
('What now'),
1845 HEADER
=> __
('*** Commands ***'),
1846 ON_EOF
=> \
&quit_cmd
,
1847 IMMEDIATE
=> 1 }, @cmd);
1861 if ($patch_mode_only) {