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_compaction_heuristic = $repo->config_bool('diff.compactionheuristic');
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',
110 PARTICIPLE => 'staging',
111 FILTER => 'file-only',
115 DIFF => 'diff-index -p HEAD',
116 APPLY => sub { apply_patch 'apply --cached', @_; },
117 APPLY_CHECK => 'apply --cached',
120 PARTICIPLE => 'stashing',
125 DIFF => 'diff-index -p --cached',
126 APPLY => sub { apply_patch 'apply -R --cached', @_; },
127 APPLY_CHECK => 'apply -R --cached',
130 PARTICIPLE => 'unstaging',
131 FILTER => 'index-only',
135 DIFF => 'diff-index -R -p --cached',
136 APPLY => sub { apply_patch 'apply --cached', @_; },
137 APPLY_CHECK => 'apply --cached',
139 TARGET => ' to index',
140 PARTICIPLE => 'applying',
141 FILTER => 'index-only',
144 'checkout_index' => {
145 DIFF => 'diff-files -p',
146 APPLY => sub { apply_patch 'apply -R', @_; },
147 APPLY_CHECK => 'apply -R',
149 TARGET => ' from worktree',
150 PARTICIPLE => 'discarding',
151 FILTER => 'file-only',
155 DIFF => 'diff-index -p',
156 APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
157 APPLY_CHECK => 'apply -R',
159 TARGET => ' from index and worktree',
160 PARTICIPLE => 'discarding',
164 'checkout_nothead' => {
165 DIFF => 'diff-index -R -p',
166 APPLY => sub { apply_patch_for_checkout_commit '', @_ },
167 APPLY_CHECK => 'apply',
169 TARGET => ' to index and worktree',
170 PARTICIPLE => 'applying',
176 $patch_mode = 'stage';
177 my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
180 if ($^O eq 'MSWin32') {
181 my @invalid = grep {m/[":*]/} @_;
182 die "$^O does not support: @invalid\n" if @invalid;
183 my @args = map { m/ /o ? "\"$_\"": $_ } @_;
187 open($fh, '-|', @_) or die;
192 my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
194 if (!defined $GIT_DIR) {
195 exit(1); # rev-parse would have already said "not a git repo"
212 my ($retval, $remainder);
213 if (!/^\042(.*)\042$/) {
216 ($_, $retval) = ($1, "");
217 while (/^([^\\]*)\\(.*)$/) {
221 if (/^([0-3][0-7][0-7])(.*)$/) {
222 $retval .= chr(oct($1));
226 if (/^([\\\042btnvfr])(.*)$/) {
227 $retval .= $cquote_map{$1};
231 # This is malformed -- just return it as-is for now.
242 open $fh, 'git update-index --refresh |'
245 ;# ignore 'needs update'
255 run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
258 # TRANSLATORS: you can adjust this to align "git add -i" status menu
259 my $status_fmt = __('%12s %12s %s');
260 my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
264 sub is_initial_commit {
265 $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
266 unless defined $initial;
272 return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
275 sub get_diff_reference {
277 if (defined $ref and $ref ne 'HEAD') {
279 } elsif (is_initial_commit()) {
280 return get_empty_tree();
286 # Returns list of hashes, contents of each of which are:
288 # BINARY: is a binary path
289 # INDEX: is index different from HEAD?
290 # FILE: is file different from index?
291 # INDEX_ADDDEL: is it add/delete between HEAD and index?
292 # FILE_ADDDEL: is it add/delete between index and file?
293 # UNMERGED: is the path unmerged
298 my ($add, $del, $adddel, $file);
305 } run_cmd_pipe(qw(git ls-files --), @ARGV);
306 return if (!@tracked);
309 my $reference = get_diff_reference($patch_mode_revision);
310 for (run_cmd_pipe(qw(git diff-index --cached
311 --numstat --summary), $reference,
313 if (($add, $del, $file) =
314 /^([-\d]+) ([-\d]+) (.*)/) {
316 $file = unquote_path($file);
317 if ($add eq '-' && $del eq '-') {
322 $change = "+$add/-$del";
330 elsif (($adddel, $file) =
331 /^ (create|delete) mode [0-7]+ (.*)$/) {
332 $file = unquote_path($file);
333 $data{$file}{INDEX_ADDDEL} = $adddel;
337 for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @tracked)) {
338 if (($add, $del, $file) =
339 /^([-\d]+) ([-\d]+) (.*)/) {
340 $file = unquote_path($file);
342 if ($add eq '-' && $del eq '-') {
347 $change = "+$add/-$del";
349 $data{$file}{FILE} = $change;
351 $data{$file}{BINARY} = 1;
354 elsif (($adddel, $file) =
355 /^ (create|delete) mode [0-7]+ (.*)$/) {
356 $file = unquote_path($file);
357 $data{$file}{FILE_ADDDEL} = $adddel;
359 elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
360 $file = unquote_path($2);
361 if (!exists $data{$file}) {
363 INDEX => 'unchanged',
368 $data{$file}{UNMERGED} = 1;
373 for (sort keys %data) {
377 if ($only eq 'index-only') {
378 next if ($it->{INDEX} eq 'unchanged');
380 if ($only eq 'file-only') {
381 next if ($it->{FILE} eq 'nothing');
393 my ($string, @stuff) = @_;
395 for (my $i = 0; $i < @stuff; $i++) {
399 if ((ref $it) eq 'ARRAY') {
407 if ($it =~ /^$string/) {
411 if (defined $hit && defined $found) {
421 # inserts string into trie and updates count for each character
423 my ($trie, $string) = @_;
424 foreach (split //, $string) {
425 $trie = $trie->{$_} ||= {COUNT => 0};
430 # returns an array of tuples (prefix, remainder)
431 sub find_unique_prefixes {
435 # any single prefix exceeding the soft limit is omitted
436 # if any prefix exceeds the hard limit all are omitted
437 # 0 indicates no limit
441 # build a trie modelling all possible options
443 foreach my $print (@stuff) {
444 if ((ref $print) eq 'ARRAY') {
445 $print = $print->[0];
447 elsif ((ref $print) eq 'HASH') {
448 $print = $print->{VALUE};
450 update_trie(\%trie, $print);
451 push @return, $print;
454 # use the trie to find the unique prefixes
455 for (my $i = 0; $i < @return; $i++) {
456 my $ret = $return[$i];
457 my @letters = split //, $ret;
459 my ($prefix, $remainder);
461 for ($j = 0; $j < @letters; $j++) {
462 my $letter = $letters[$j];
463 if ($search{$letter}{COUNT} == 1) {
464 $prefix = substr $ret, 0, $j + 1;
465 $remainder = substr $ret, $j + 1;
469 my $prefix = substr $ret, 0, $j;
471 if ($hard_limit && $j + 1 > $hard_limit);
473 %search = %{$search{$letter}};
475 if (ord($letters[0]) > 127 ||
476 ($soft_limit && $j + 1 > $soft_limit)) {
480 $return[$i] = [$prefix, $remainder];
485 # filters out prefixes which have special meaning to list_and_choose()
486 sub is_valid_prefix {
488 return (defined $prefix) &&
489 !($prefix =~ /[\s,]/) && # separators
490 !($prefix =~ /^-/) && # deselection
491 !($prefix =~ /^\d+/) && # selection
492 ($prefix ne '*') && # "all" wildcard
493 ($prefix ne '?'); # prompt help
496 # given a prefix/remainder tuple return a string with the prefix highlighted
497 # for now use square brackets; later might use ANSI colors (underline, bold)
498 sub highlight_prefix {
500 my $remainder = shift;
502 if (!defined $prefix) {
506 if (!is_valid_prefix($prefix)) {
507 return "$prefix$remainder";
510 if (!$menu_use_color) {
511 return "[$prefix]$remainder";
514 return "$prompt_color$prefix$normal_color$remainder";
518 print STDERR colored $error_color, @_;
521 sub list_and_choose {
522 my ($opts, @stuff) = @_;
523 my (@chosen, @return);
528 my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
534 if ($opts->{HEADER}) {
535 if (!$opts->{LIST_FLAT}) {
538 print colored $header_color, "$opts->{HEADER}\n";
540 for ($i = 0; $i < @stuff; $i++) {
541 my $chosen = $chosen[$i] ? '*' : ' ';
542 my $print = $stuff[$i];
543 my $ref = ref $print;
544 my $highlighted = highlight_prefix(@{$prefixes[$i]})
546 if ($ref eq 'ARRAY') {
547 $print = $highlighted || $print->[0];
549 elsif ($ref eq 'HASH') {
550 my $value = $highlighted || $print->{VALUE};
551 $print = sprintf($status_fmt,
557 $print = $highlighted || $print;
559 printf("%s%2d: %s", $chosen, $i+1, $print);
560 if (($opts->{LIST_FLAT}) &&
561 (($i + 1) % ($opts->{LIST_FLAT}))) {
574 return if ($opts->{LIST_ONLY});
576 print colored $prompt_color, $opts->{PROMPT};
577 if ($opts->{SINGLETON}) {
586 $opts->{ON_EOF}->() if $opts->{ON_EOF};
593 singleton_prompt_help_cmd() :
597 for my $choice (split(/[\s,]+/, $line)) {
601 # Input that begins with '-'; unchoose
602 if ($choice =~ s/^-//) {
605 # A range can be specified like 5-7 or 5-.
606 if ($choice =~ /^(\d+)-(\d*)$/) {
607 ($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
609 elsif ($choice =~ /^\d+$/) {
610 $bottom = $top = $choice;
612 elsif ($choice eq '*') {
617 $bottom = $top = find_unique($choice, @stuff);
618 if (!defined $bottom) {
619 error_msg sprintf(__("Huh (%s)?\n"), $choice);
623 if ($opts->{SINGLETON} && $bottom != $top) {
624 error_msg sprintf(__("Huh (%s)?\n"), $choice);
627 for ($i = $bottom-1; $i <= $top-1; $i++) {
628 next if (@stuff <= $i || $i < 0);
629 $chosen[$i] = $choose;
632 last if ($opts->{IMMEDIATE} || $line eq '*');
634 for ($i = 0; $i < @stuff; $i++) {
636 push @return, $stuff[$i];
642 sub singleton_prompt_help_cmd {
643 print colored $help_color, __ <<'EOF' ;
645 1 - select a numbered item
646 foo - select item based on unique prefix
647 - (empty) select nothing
651 sub prompt_help_cmd {
652 print colored $help_color, __ <<'EOF' ;
654 1 - select a single item
655 3-5 - select a range of items
656 2-3,6-9 - select multiple ranges
657 foo - select item based on unique prefix
658 -... - unselect specified items
660 - (empty) finish selecting
665 list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
673 if ($did eq 'added') {
674 printf(__n("added %d path\n", "added %d paths\n",
676 } elsif ($did eq 'updated') {
677 printf(__n("updated %d path\n", "updated %d paths\n",
679 } elsif ($did eq 'reverted') {
680 printf(__n("reverted %d path\n", "reverted %d paths\n",
683 printf(__n("touched %d path\n", "touched %d paths\n",
689 my @mods = list_modified('file-only');
692 my @update = list_and_choose({ PROMPT => __('Update'),
693 HEADER => $status_head, },
696 system(qw(git update-index --add --remove --),
697 map { $_->{VALUE} } @update);
698 say_n_paths('updated', @update);
704 my @update = list_and_choose({ PROMPT => __('Revert'),
705 HEADER => $status_head, },
708 if (is_initial_commit()) {
709 system(qw(git rm --cached),
710 map { $_->{VALUE} } @update);
713 my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
714 map { $_->{VALUE} } @update);
716 open $fh, '| git update-index --index-info'
723 if ($_->{INDEX_ADDDEL} &&
724 $_->{INDEX_ADDDEL} eq 'create') {
725 system(qw(git update-index --force-remove --),
727 printf(__("note: %s is untracked now.\n"), $_->{VALUE});
732 say_n_paths('reverted', @update);
737 sub add_untracked_cmd {
738 my @add = list_and_choose({ PROMPT => __('Add untracked') },
741 system(qw(git update-index --add --), @add);
742 say_n_paths('added', @add);
744 print __("No untracked files.\n");
752 open $fh, '| git ' . $cmd . " --recount --allow-overlap";
759 my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
760 if (defined $diff_algorithm) {
761 splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
763 if ($diff_compaction_heuristic) {
764 splice @diff_cmd, 1, 0, "--compaction-heuristic";
766 if (defined $patch_mode_revision) {
767 push @diff_cmd, get_diff_reference($patch_mode_revision);
769 my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path);
771 if ($diff_use_color) {
772 my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
773 if (defined $diff_filter) {
774 # quotemeta is overkill, but sufficient for shell-quoting
775 my $diff = join(' ', map { quotemeta } @display_cmd);
776 @display_cmd = ("$diff | $diff_filter");
779 @colored = run_cmd_pipe(@display_cmd);
781 my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
783 for (my $i = 0; $i < @diff; $i++) {
784 if ($diff[$i] =~ /^@@ /) {
785 push @hunk, { TEXT => [], DISPLAY => [],
788 push @{$hunk[-1]{TEXT}}, $diff[$i];
789 push @{$hunk[-1]{DISPLAY}},
790 (@colored ? $colored[$i] : $diff[$i]);
795 sub parse_diff_header {
798 my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
799 my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
800 my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
802 for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
804 $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
805 $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
807 push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
808 push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
810 return ($head, $mode, $deletion);
813 sub hunk_splittable {
816 my @s = split_hunk($text);
820 sub parse_hunk_header {
822 my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
823 $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
824 $o_cnt = 1 unless defined $o_cnt;
825 $n_cnt = 1 unless defined $n_cnt;
826 return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
830 my ($text, $display) = @_;
832 if (!defined $display) {
835 # If there are context lines in the middle of a hunk,
836 # it can be split, but we would need to take care of
839 my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
844 my $next_hunk_start = undef;
845 my $i = $hunk_start - 1;
859 while (++$i < @$text) {
860 my $line = $text->[$i];
861 my $display = $display->[$i];
863 if ($this->{ADDDEL} &&
864 !defined $next_hunk_start) {
865 # We have seen leading context and
866 # adds/dels and then here is another
867 # context, which is trailing for this
868 # split hunk and leading for the next
870 $next_hunk_start = $i;
872 push @{$this->{TEXT}}, $line;
873 push @{$this->{DISPLAY}}, $display;
876 if (defined $next_hunk_start) {
883 if (defined $next_hunk_start) {
884 # We are done with the current hunk and
885 # this is the first real change for the
887 $hunk_start = $next_hunk_start;
888 $o_ofs = $this->{OLD} + $this->{OCNT};
889 $n_ofs = $this->{NEW} + $this->{NCNT};
890 $o_ofs -= $this->{POSTCTX};
891 $n_ofs -= $this->{POSTCTX};
895 push @{$this->{TEXT}}, $line;
896 push @{$this->{DISPLAY}}, $display;
910 for my $hunk (@split) {
911 $o_ofs = $hunk->{OLD};
912 $n_ofs = $hunk->{NEW};
913 my $o_cnt = $hunk->{OCNT};
914 my $n_cnt = $hunk->{NCNT};
916 my $head = ("@@ -$o_ofs" .
917 (($o_cnt != 1) ? ",$o_cnt" : '') .
919 (($n_cnt != 1) ? ",$n_cnt" : '') .
921 my $display_head = $head;
922 unshift @{$hunk->{TEXT}}, $head;
923 if ($diff_use_color) {
924 $display_head = colored($fraginfo_color, $head);
926 unshift @{$hunk->{DISPLAY}}, $display_head;
931 sub find_last_o_ctx {
933 my $text = $it->{TEXT};
934 my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
936 my $last_o_ctx = $o_ofs + $o_cnt;
938 my $line = $text->[$i];
949 my ($prev, $this) = @_;
950 my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
951 parse_hunk_header($prev->{TEXT}[0]);
952 my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
953 parse_hunk_header($this->{TEXT}[0]);
955 my (@line, $i, $ofs, $o_cnt, $n_cnt);
958 for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
959 my $line = $prev->{TEXT}[$i];
960 if ($line =~ /^\+/) {
966 last if ($o1_ofs <= $ofs);
976 for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
977 my $line = $this->{TEXT}[$i];
978 if ($line =~ /^\+/) {
990 my $head = ("@@ -$o0_ofs" .
991 (($o_cnt != 1) ? ",$o_cnt" : '') .
993 (($n_cnt != 1) ? ",$n_cnt" : '') .
995 @{$prev->{TEXT}} = ($head, @line);
998 sub coalesce_overlapping_hunks {
1002 my ($last_o_ctx, $last_was_dirty);
1004 for (grep { $_->{USE} } @in) {
1005 if ($_->{TYPE} ne 'hunk') {
1009 my $text = $_->{TEXT};
1010 my ($o_ofs) = parse_hunk_header($text->[0]);
1011 if (defined $last_o_ctx &&
1012 $o_ofs <= $last_o_ctx &&
1015 merge_hunk($out[-1], $_);
1020 $last_o_ctx = find_last_o_ctx($out[-1]);
1021 $last_was_dirty = $_->{DIRTY};
1026 sub reassemble_patch {
1030 # Include everything in the header except the beginning of the diff.
1031 push @patch, (grep { !/^[-+]{3}/ } @$head);
1033 # Then include any headers from the hunk lines, which must
1034 # come before any actual hunk.
1035 while (@_ && $_[0] !~ /^@/) {
1039 # Then begin the diff.
1040 push @patch, grep { /^[-+]{3}/ } @$head;
1042 # And then the actual hunks.
1050 colored((/^@/ ? $fraginfo_color :
1051 /^\+/ ? $diff_new_color :
1052 /^-/ ? $diff_old_color :
1058 my %edit_hunk_manually_modes = (
1060 "If the patch applies cleanly, the edited hunk will immediately be
1061 marked for staging."),
1063 "If the patch applies cleanly, the edited hunk will immediately be
1064 marked for stashing."),
1066 "If the patch applies cleanly, the edited hunk will immediately be
1067 marked for unstaging."),
1068 reset_nothead => N__(
1069 "If the patch applies cleanly, the edited hunk will immediately be
1070 marked for applying."),
1071 checkout_index => N__(
1072 "If the patch applies cleanly, the edited hunk will immediately be
1073 marked for discarding"),
1074 checkout_head => N__(
1075 "If the patch applies cleanly, the edited hunk will immediately be
1076 marked for discarding."),
1077 checkout_nothead => N__(
1078 "If the patch applies cleanly, the edited hunk will immediately be
1079 marked for applying."),
1082 sub edit_hunk_manually {
1085 my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1087 open $fh, '>', $hunkfile
1088 or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
1089 print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
1090 print $fh @$oldtext;
1091 my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1092 my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
1093 my $comment_line_char = Git::get_comment_line_char;
1094 print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1096 To remove '%s' lines, make them ' ' lines (context).
1097 To remove '%s' lines, delete them.
1098 Lines starting with %s will be removed.
1100 __($edit_hunk_manually_modes{$patch_mode}),
1101 # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1103 If it does not apply cleanly, you will be given an opportunity to
1104 edit again. If all lines of the hunk are removed, then the edit is
1105 aborted and the hunk is left unchanged.
1109 chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
1110 system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1116 open $fh, '<', $hunkfile
1117 or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
1118 my @newtext = grep { !/^$comment_line_char/ } <$fh>;
1122 # Abort if nothing remains
1123 if (!grep { /\S/ } @newtext) {
1127 # Reinsert the first hunk header if the user accidentally deleted it
1128 if ($newtext[0] !~ /^@/) {
1129 unshift @newtext, $oldtext->[0];
1135 return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
1136 map { @{$_->{TEXT}} } @_);
1139 sub _restore_terminal_and_die {
1145 sub prompt_single_character {
1147 local $SIG{TERM} = \&_restore_terminal_and_die;
1148 local $SIG{INT} = \&_restore_terminal_and_die;
1150 my $key = ReadKey 0;
1152 if ($use_termcap and $key eq "\e") {
1153 while (!defined $term_escapes{$key}) {
1154 my $next = ReadKey 0.5;
1155 last if (!defined $next);
1160 print "$key" if defined $key;
1171 print colored $prompt_color, $prompt;
1172 my $line = prompt_single_character;
1173 return 0 if $line =~ /^n/i;
1174 return 1 if $line =~ /^y/i;
1178 sub edit_hunk_loop {
1179 my ($head, $hunk, $ix) = @_;
1180 my $text = $hunk->[$ix]->{TEXT};
1183 $text = edit_hunk_manually($text);
1184 if (!defined $text) {
1189 TYPE => $hunk->[$ix]->{TYPE},
1193 if (diff_applies($head,
1196 @{$hunk}[$ix+1..$#{$hunk}])) {
1197 $newhunk->{DISPLAY} = [color_diff(@{$text})];
1202 # TRANSLATORS: do not translate [y/n]
1203 # The program will only accept that input
1205 # Consider translating (saying "no" discards!) as
1206 # (saying "n" for "no" discards!) if the translation
1207 # of the word "no" does not start with n.
1208 __('Your edited hunk does not apply. Edit again '
1209 . '(saying "no" discards!) [y/n]? ')
1215 my %help_patch_modes = (
1217 "y - stage this hunk
1218 n - do not stage this hunk
1219 q - quit; do not stage this hunk or any of the remaining ones
1220 a - stage this hunk and all later hunks in the file
1221 d - do not stage this hunk or any of the later hunks in the file"),
1223 "y - stash this hunk
1224 n - do not stash this hunk
1225 q - quit; do not stash this hunk or any of the remaining ones
1226 a - stash this hunk and all later hunks in the file
1227 d - do not stash this hunk or any of the later hunks in the file"),
1229 "y - unstage this hunk
1230 n - do not unstage this hunk
1231 q - quit; do not unstage this hunk or any of the remaining ones
1232 a - unstage this hunk and all later hunks in the file
1233 d - do not unstage this hunk or any of the later hunks in the file"),
1234 reset_nothead => N__(
1235 "y - apply this hunk to index
1236 n - do not apply this hunk to index
1237 q - quit; do not apply this hunk or any of the remaining ones
1238 a - apply this hunk and all later hunks in the file
1239 d - do not apply this hunk or any of the later hunks in the file"),
1240 checkout_index => N__(
1241 "y - discard this hunk from worktree
1242 n - do not discard this hunk from worktree
1243 q - quit; do not discard this hunk or any of the remaining ones
1244 a - discard this hunk and all later hunks in the file
1245 d - do not discard this hunk or any of the later hunks in the file"),
1246 checkout_head => N__(
1247 "y - discard this hunk from index and worktree
1248 n - do not discard this hunk from index and worktree
1249 q - quit; do not discard this hunk or any of the remaining ones
1250 a - discard this hunk and all later hunks in the file
1251 d - do not discard this hunk or any of the later hunks in the file"),
1252 checkout_nothead => N__(
1253 "y - apply this hunk to index and worktree
1254 n - do not apply this hunk to index and worktree
1255 q - quit; do not apply this hunk or any of the remaining ones
1256 a - apply this hunk and all later hunks in the file
1257 d - do not apply this hunk or any of the later hunks in the file"),
1260 sub help_patch_cmd {
1261 print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", __ <<EOF ;
1262 g - select a hunk to go to
1263 / - search for a hunk matching the given regex
1264 j - leave this hunk undecided, see next undecided hunk
1265 J - leave this hunk undecided, see next hunk
1266 k - leave this hunk undecided, see previous undecided hunk
1267 K - leave this hunk undecided, see previous hunk
1268 s - split the current hunk into smaller hunks
1269 e - manually edit the current hunk
1276 my $ret = run_git_apply $cmd, @_;
1283 sub apply_patch_for_checkout_commit {
1284 my $reverse = shift;
1285 my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1286 my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
1288 if ($applies_worktree && $applies_index) {
1289 run_git_apply 'apply '.$reverse.' --cached', @_;
1290 run_git_apply 'apply '.$reverse, @_;
1292 } elsif (!$applies_index) {
1293 print colored $error_color, __("The selected hunks do not apply to the index!\n");
1294 if (prompt_yesno __("Apply them to the worktree anyway? ")) {
1295 return run_git_apply 'apply '.$reverse, @_;
1297 print colored $error_color, __("Nothing was applied.\n");
1306 sub patch_update_cmd {
1307 my @all_mods = list_modified($patch_mode_flavour{FILTER});
1308 error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
1309 for grep { $_->{UNMERGED} } @all_mods;
1310 @all_mods = grep { !$_->{UNMERGED} } @all_mods;
1312 my @mods = grep { !($_->{BINARY}) } @all_mods;
1317 print STDERR __("Only binary files changed.\n");
1319 print STDERR __("No changes.\n");
1327 @them = list_and_choose({ PROMPT => __('Patch update'),
1328 HEADER => $status_head, },
1332 return 0 if patch_update_file($_->{VALUE});
1336 # Generate a one line summary of a hunk.
1337 sub summarize_hunk {
1339 my $summary = $rhunk->{TEXT}[0];
1341 # Keep the line numbers, discard extra context.
1342 $summary =~ s/@@(.*?)@@.*/$1 /s;
1343 $summary .= " " x (20 - length $summary);
1345 # Add some user context.
1346 for my $line (@{$rhunk->{TEXT}}) {
1347 if ($line =~ m/^[+-].*\w/) {
1354 return substr($summary, 0, 80) . "\n";
1358 # Print a one-line summary of each hunk in the array ref in
1359 # the first argument, starting with the index in the 2nd.
1361 my ($hunks, $i) = @_;
1364 for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1366 if (defined $hunks->[$i]{USE}) {
1367 $status = $hunks->[$i]{USE} ? "+" : "-";
1372 summarize_hunk($hunks->[$i]);
1377 my %patch_update_prompt_modes = (
1379 mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "),
1380 deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "),
1381 hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "),
1384 mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "),
1385 deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "),
1386 hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "),
1389 mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "),
1390 deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "),
1391 hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
1394 mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
1395 deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
1396 hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
1399 mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
1400 deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
1401 hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
1404 mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
1405 deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
1406 hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
1408 checkout_nothead => {
1409 mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
1410 deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
1411 hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
1415 sub patch_update_file {
1419 my ($head, @hunk) = parse_diff($path);
1420 ($head, my $mode, my $deletion) = parse_diff_header($head);
1421 for (@{$head->{DISPLAY}}) {
1425 if (@{$mode->{TEXT}}) {
1426 unshift @hunk, $mode;
1428 if (@{$deletion->{TEXT}}) {
1429 foreach my $hunk (@hunk) {
1430 push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1431 push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1433 @hunk = ($deletion);
1436 $num = scalar @hunk;
1440 my ($prev, $next, $other, $undecided, $i);
1446 for ($i = 0; $i < $ix; $i++) {
1447 if (!defined $hunk[$i]{USE}) {
1456 for ($i = $ix + 1; $i < $num; $i++) {
1457 if (!defined $hunk[$i]{USE}) {
1463 if ($ix < $num - 1) {
1469 for ($i = 0; $i < $num; $i++) {
1470 if (!defined $hunk[$i]{USE}) {
1475 last if (!$undecided);
1477 if ($hunk[$ix]{TYPE} eq 'hunk' &&
1478 hunk_splittable($hunk[$ix]{TEXT})) {
1481 if ($hunk[$ix]{TYPE} eq 'hunk') {
1484 for (@{$hunk[$ix]{DISPLAY}}) {
1487 print colored $prompt_color,
1488 sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other);
1490 my $line = prompt_single_character;
1491 last unless defined $line;
1493 if ($line =~ /^y/i) {
1494 $hunk[$ix]{USE} = 1;
1496 elsif ($line =~ /^n/i) {
1497 $hunk[$ix]{USE} = 0;
1499 elsif ($line =~ /^a/i) {
1500 while ($ix < $num) {
1501 if (!defined $hunk[$ix]{USE}) {
1502 $hunk[$ix]{USE} = 1;
1508 elsif ($other =~ /g/ && $line =~ /^g(.*)/) {
1510 my $no = $ix > 10 ? $ix - 10 : 0;
1511 while ($response eq '') {
1512 $no = display_hunks(\@hunk, $no);
1514 print __("go to which hunk (<ret> to see more)? ");
1516 print __("go to which hunk? ");
1518 $response = <STDIN>;
1519 if (!defined $response) {
1524 if ($response !~ /^\s*\d+\s*$/) {
1525 error_msg sprintf(__("Invalid number: '%s'\n"),
1527 } elsif (0 < $response && $response <= $num) {
1528 $ix = $response - 1;
1530 error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1531 "Sorry, only %d hunks available.\n", $num), $num);
1535 elsif ($line =~ /^d/i) {
1536 while ($ix < $num) {
1537 if (!defined $hunk[$ix]{USE}) {
1538 $hunk[$ix]{USE} = 0;
1544 elsif ($line =~ /^q/i) {
1545 for ($i = 0; $i < $num; $i++) {
1546 if (!defined $hunk[$i]{USE}) {
1553 elsif ($line =~ m|^/(.*)|) {
1556 print colored $prompt_color, __("search for regex? ");
1558 if (defined $regex) {
1564 $search_string = qr{$regex}m;
1567 my ($err,$exp) = ($@, $1);
1568 $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1569 error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
1574 my $text = join ("", @{$hunk[$iy]{TEXT}});
1575 last if ($text =~ $search_string);
1577 $iy = 0 if ($iy >= $num);
1579 error_msg __("No hunk matches the given pattern\n");
1586 elsif ($line =~ /^K/) {
1587 if ($other =~ /K/) {
1591 error_msg __("No previous hunk\n");
1595 elsif ($line =~ /^J/) {
1596 if ($other =~ /J/) {
1600 error_msg __("No next hunk\n");
1604 elsif ($line =~ /^k/) {
1605 if ($other =~ /k/) {
1609 !defined $hunk[$ix]{USE});
1613 error_msg __("No previous hunk\n");
1617 elsif ($line =~ /^j/) {
1618 if ($other !~ /j/) {
1619 error_msg __("No next hunk\n");
1623 elsif ($other =~ /s/ && $line =~ /^s/) {
1624 my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
1626 print colored $header_color, sprintf(
1627 __n("Split into %d hunk.\n",
1628 "Split into %d hunks.\n",
1629 scalar(@split)), scalar(@split));
1631 splice (@hunk, $ix, 1, @split);
1632 $num = scalar @hunk;
1635 elsif ($other =~ /e/ && $line =~ /^e/) {
1636 my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1637 if (defined $newhunk) {
1638 splice @hunk, $ix, 1, $newhunk;
1642 help_patch_cmd($other);
1648 last if ($ix >= $num ||
1649 !defined $hunk[$ix]{USE});
1654 @hunk = coalesce_overlapping_hunks(@hunk);
1660 push @result, @{$_->{TEXT}};
1665 my @patch = reassemble_patch($head->{TEXT}, @result);
1666 my $apply_routine = $patch_mode_flavour{APPLY};
1667 &$apply_routine(@patch);
1676 my @mods = list_modified('index-only');
1677 @mods = grep { !($_->{BINARY}) } @mods;
1679 my (@them) = list_and_choose({ PROMPT => __('Review diff'),
1681 HEADER => $status_head, },
1684 my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
1685 system(qw(git diff -p --cached), $reference, '--',
1686 map { $_->{VALUE} } @them);
1695 # TRANSLATORS: please do not translate the command names
1696 # 'status', 'update', 'revert', etc.
1697 print colored $help_color, __ <<'EOF' ;
1698 status - show paths with changes
1699 update - add working tree state to the staged set of changes
1700 revert - revert staged set of changes back to the HEAD version
1701 patch - pick hunks and update selectively
1702 diff - view diff between HEAD and index
1703 add untracked - add contents of untracked files to the staged set of changes
1708 return unless @ARGV;
1709 my $arg = shift @ARGV;
1710 if ($arg =~ /--patch(?:=(.*))?/) {
1712 if ($1 eq 'reset') {
1713 $patch_mode = 'reset_head';
1714 $patch_mode_revision = 'HEAD';
1715 $arg = shift @ARGV or die __("missing --");
1717 $patch_mode_revision = $arg;
1718 $patch_mode = ($arg eq 'HEAD' ?
1719 'reset_head' : 'reset_nothead');
1720 $arg = shift @ARGV or die __("missing --");
1722 } elsif ($1 eq 'checkout') {
1723 $arg = shift @ARGV or die __("missing --");
1725 $patch_mode = 'checkout_index';
1727 $patch_mode_revision = $arg;
1728 $patch_mode = ($arg eq 'HEAD' ?
1729 'checkout_head' : 'checkout_nothead');
1730 $arg = shift @ARGV or die __("missing --");
1732 } elsif ($1 eq 'stage' or $1 eq 'stash') {
1734 $arg = shift @ARGV or die __("missing --");
1736 die sprintf(__("unknown --patch mode: %s"), $1);
1739 $patch_mode = 'stage';
1740 $arg = shift @ARGV or die __("missing --");
1742 die sprintf(__("invalid argument %s, expecting --"),
1743 $arg) unless $arg eq "--";
1744 %patch_mode_flavour = %{$patch_modes{$patch_mode}};
1747 elsif ($arg ne "--") {
1748 die sprintf(__("invalid argument %s, expecting --"), $arg);
1753 my @cmd = ([ 'status', \&status_cmd, ],
1754 [ 'update', \&update_cmd, ],
1755 [ 'revert', \&revert_cmd, ],
1756 [ 'add untracked', \&add_untracked_cmd, ],
1757 [ 'patch', \&patch_update_cmd, ],
1758 [ 'diff', \&diff_cmd, ],
1759 [ 'quit', \&quit_cmd, ],
1760 [ 'help', \&help_cmd, ],
1763 my ($it) = list_and_choose({ PROMPT => __('What now'),
1766 HEADER => __('*** Commands ***'),
1767 ON_EOF => \&quit_cmd,
1768 IMMEDIATE => 1 }, @cmd);