diff options
Diffstat (limited to 'git-add--interactive.perl')
-rwxr-xr-x | git-add--interactive.perl | 505 |
1 files changed, 305 insertions, 200 deletions
diff --git a/git-add--interactive.perl b/git-add--interactive.perl index a329c5a1f8..28b325d754 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -3,7 +3,8 @@ use 5.008; use strict; use warnings; -use Git; +use Git qw(unquote_path); +use Git::I18N; binmode(STDOUT, ":raw"); @@ -44,7 +45,13 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); +my $diff_algorithm = $repo->config('diff.algorithm'); +my $diff_filter = $repo->config('interactive.difffilter'); + my $use_readkey = 0; +my $use_termcap = 0; +my %term_escapes; + sub ReadMode; sub ReadKey; if ($repo->config_bool("interactive.singlekey")) { @@ -53,6 +60,17 @@ if ($repo->config_bool("interactive.singlekey")) { Term::ReadKey->import; $use_readkey = 1; }; + if (!$use_readkey) { + print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n"; + } + eval { + require Term::Cap; + my $termcap = Term::Cap->Tgetent; + foreach (values %$termcap) { + $term_escapes{$_} = 1 if /^\e/; + } + $use_termcap = 1; + }; } sub colored { @@ -73,6 +91,7 @@ sub colored { } # command line options +my $patch_mode_only; my $patch_mode; my $patch_mode_revision; @@ -85,9 +104,6 @@ my %patch_modes = ( DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stage', - TARGET => '', - PARTICIPLE => 'staging', FILTER => 'file-only', IS_REVERSE => 0, }, @@ -95,9 +111,6 @@ my %patch_modes = ( DIFF => 'diff-index -p HEAD', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stash', - TARGET => '', - PARTICIPLE => 'stashing', FILTER => undef, IS_REVERSE => 0, }, @@ -105,9 +118,6 @@ my %patch_modes = ( DIFF => 'diff-index -p --cached', APPLY => sub { apply_patch 'apply -R --cached', @_; }, APPLY_CHECK => 'apply -R --cached', - VERB => 'Unstage', - TARGET => '', - PARTICIPLE => 'unstaging', FILTER => 'index-only', IS_REVERSE => 1, }, @@ -115,9 +125,6 @@ my %patch_modes = ( DIFF => 'diff-index -R -p --cached', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Apply', - TARGET => ' to index', - PARTICIPLE => 'applying', FILTER => 'index-only', IS_REVERSE => 0, }, @@ -125,9 +132,6 @@ my %patch_modes = ( DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply -R', @_; }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from worktree', - PARTICIPLE => 'discarding', FILTER => 'file-only', IS_REVERSE => 1, }, @@ -135,9 +139,6 @@ my %patch_modes = ( DIFF => 'diff-index -p', APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from index and worktree', - PARTICIPLE => 'discarding', FILTER => undef, IS_REVERSE => 1, }, @@ -145,18 +146,16 @@ my %patch_modes = ( DIFF => 'diff-index -R -p', APPLY => sub { apply_patch_for_checkout_commit '', @_ }, APPLY_CHECK => 'apply', - VERB => 'Apply', - TARGET => ' to index and worktree', - PARTICIPLE => 'applying', FILTER => undef, IS_REVERSE => 0, }, ); -my %patch_mode_flavour = %{$patch_modes{stage}}; +$patch_mode = 'stage'; +my %patch_mode_flavour = %{$patch_modes{$patch_mode}}; sub run_cmd_pipe { - if ($^O eq 'MSWin32' || $^O eq 'msys') { + if ($^O eq 'MSWin32') { my @invalid = grep {m/[":*]/} @_; die "$^O does not support: @invalid\n" if @invalid; my @args = map { m/ /o ? "\"$_\"": $_ } @_; @@ -175,47 +174,6 @@ if (!defined $GIT_DIR) { } chomp($GIT_DIR); -my %cquote_map = ( - "b" => chr(8), - "t" => chr(9), - "n" => chr(10), - "v" => chr(11), - "f" => chr(12), - "r" => chr(13), - "\\" => "\\", - "\042" => "\042", -); - -sub unquote_path { - local ($_) = @_; - my ($retval, $remainder); - if (!/^\042(.*)\042$/) { - return $_; - } - ($_, $retval) = ($1, ""); - while (/^([^\\]*)\\(.*)$/) { - $remainder = $2; - $retval .= $1; - for ($remainder) { - if (/^([0-3][0-7][0-7])(.*)$/) { - $retval .= chr(oct($1)); - $_ = $2; - last; - } - if (/^([\\\042btnvfr])(.*)$/) { - $retval .= $cquote_map{$1}; - $_ = $2; - last; - } - # This is malformed -- just return it as-is for now. - return $_[0]; - } - $_ = $remainder; - } - $retval .= $_; - return $retval; -} - sub refresh { my $fh; open $fh, 'git update-index --refresh |' @@ -234,8 +192,9 @@ sub list_untracked { run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); } -my $status_fmt = '%12s %12s %s'; -my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path'); +# TRANSLATORS: you can adjust this to align "git add -i" status menu +my $status_fmt = __('%12s %12s %s'); +my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path')); { my $initial; @@ -250,6 +209,17 @@ sub get_empty_tree { return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; } +sub get_diff_reference { + my $ref = shift; + if (defined $ref and $ref ne 'HEAD') { + return $ref; + } elsif (is_initial_commit()) { + return get_empty_tree(); + } else { + return 'HEAD'; + } +} + # Returns list of hashes, contents of each of which are: # VALUE: pathname # BINARY: is a binary path @@ -257,38 +227,23 @@ sub get_empty_tree { # FILE: is file different from index? # INDEX_ADDDEL: is it add/delete between HEAD and index? # FILE_ADDDEL: is it add/delete between index and file? +# UNMERGED: is the path unmerged sub list_modified { my ($only) = @_; my (%data, @return); my ($add, $del, $adddel, $file); - my @tracked = (); - - if (@ARGV) { - @tracked = map { - chomp $_; - unquote_path($_); - } run_cmd_pipe(qw(git ls-files --), @ARGV); - return if (!@tracked); - } - my $reference; - if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') { - $reference = $patch_mode_revision; - } elsif (is_initial_commit()) { - $reference = get_empty_tree(); - } else { - $reference = 'HEAD'; - } + my $reference = get_diff_reference($patch_mode_revision); for (run_cmd_pipe(qw(git diff-index --cached --numstat --summary), $reference, - '--', @tracked)) { + '--', @ARGV)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { my ($change, $bin); $file = unquote_path($file); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -297,7 +252,7 @@ sub list_modified { $data{$file} = { INDEX => $change, BINARY => $bin, - FILE => 'nothing', + FILE => __('nothing'), } } elsif (($adddel, $file) = @@ -307,19 +262,13 @@ sub list_modified { } } - for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) { + for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @ARGV)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { $file = unquote_path($file); - if (!exists $data{$file}) { - $data{$file} = +{ - INDEX => 'unchanged', - BINARY => 0, - }; - } my ($change, $bin); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -335,6 +284,18 @@ sub list_modified { $file = unquote_path($file); $data{$file}{FILE_ADDDEL} = $adddel; } + elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) { + $file = unquote_path($2); + if (!exists $data{$file}) { + $data{$file} = +{ + INDEX => __('unchanged'), + BINARY => 0, + }; + } + if ($1 eq 'U') { + $data{$file}{UNMERGED} = 1; + } + } } for (sort keys %data) { @@ -342,10 +303,10 @@ sub list_modified { if ($only) { if ($only eq 'index-only') { - next if ($it->{INDEX} eq 'unchanged'); + next if ($it->{INDEX} eq __('unchanged')); } if ($only eq 'file-only') { - next if ($it->{FILE} eq 'nothing'); + next if ($it->{FILE} eq __('nothing')); } } push @return, +{ @@ -488,6 +449,9 @@ sub error_msg { sub list_and_choose { my ($opts, @stuff) = @_; my (@chosen, @return); + if (!@stuff) { + return @return; + } my $i; my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY}; @@ -580,12 +544,12 @@ sub list_and_choose { else { $bottom = $top = find_unique($choice, @stuff); if (!defined $bottom) { - error_msg "Huh ($choice)?\n"; + error_msg sprintf(__("Huh (%s)?\n"), $choice); next TOPLOOP; } } if ($opts->{SINGLETON} && $bottom != $top) { - error_msg "Huh ($choice)?\n"; + error_msg sprintf(__("Huh (%s)?\n"), $choice); next TOPLOOP; } for ($i = $bottom-1; $i <= $top-1; $i++) { @@ -604,7 +568,7 @@ sub list_and_choose { } sub singleton_prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a numbered item foo - select item based on unique prefix @@ -613,7 +577,7 @@ EOF } sub prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a single item 3-5 - select a range of items @@ -634,12 +598,18 @@ sub status_cmd { sub say_n_paths { my $did = shift @_; my $cnt = scalar @_; - print "$did "; - if (1 < $cnt) { - print "$cnt paths\n"; - } - else { - print "one path\n"; + if ($did eq 'added') { + printf(__n("added %d path\n", "added %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'updated') { + printf(__n("updated %d path\n", "updated %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'reverted') { + printf(__n("reverted %d path\n", "reverted %d paths\n", + $cnt), $cnt); + } else { + printf(__n("touched %d path\n", "touched %d paths\n", + $cnt), $cnt); } } @@ -647,7 +617,7 @@ sub update_cmd { my @mods = list_modified('file-only'); return if (!@mods); - my @update = list_and_choose({ PROMPT => 'Update', + my @update = list_and_choose({ PROMPT => __('Update'), HEADER => $status_head, }, @mods); if (@update) { @@ -659,7 +629,7 @@ sub update_cmd { } sub revert_cmd { - my @update = list_and_choose({ PROMPT => 'Revert', + my @update = list_and_choose({ PROMPT => __('Revert'), HEADER => $status_head, }, list_modified()); if (@update) { @@ -682,7 +652,7 @@ sub revert_cmd { $_->{INDEX_ADDDEL} eq 'create') { system(qw(git update-index --force-remove --), $_->{VALUE}); - print "note: $_->{VALUE} is untracked now.\n"; + printf(__("note: %s is untracked now.\n"), $_->{VALUE}); } } } @@ -693,11 +663,13 @@ sub revert_cmd { } sub add_untracked_cmd { - my @add = list_and_choose({ PROMPT => 'Add untracked' }, + my @add = list_and_choose({ PROMPT => __('Add untracked') }, list_untracked()); if (@add) { system(qw(git update-index --add --), @add); say_n_paths('added', @add); + } else { + print __("No untracked files.\n"); } print "\n"; } @@ -705,7 +677,7 @@ sub add_untracked_cmd { sub run_git_apply { my $cmd = shift; my $fh; - open $fh, '| git ' . $cmd; + open $fh, '| git ' . $cmd . " --recount --allow-overlap"; print $fh @_; return close $fh; } @@ -713,13 +685,23 @@ sub run_git_apply { sub parse_diff { my ($path) = @_; my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); + if (defined $diff_algorithm) { + splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}"; + } if (defined $patch_mode_revision) { - push @diff_cmd, $patch_mode_revision; + push @diff_cmd, get_diff_reference($patch_mode_revision); } my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { - @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path); + my @display_cmd = ("git", @diff_cmd, qw(--color --), $path); + if (defined $diff_filter) { + # quotemeta is overkill, but sufficient for shell-quoting + my $diff = join(' ', map { quotemeta } @display_cmd); + @display_cmd = ("$diff | $diff_filter"); + } + + @colored = run_cmd_pipe(@display_cmd); } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; @@ -730,7 +712,7 @@ sub parse_diff { } push @{$hunk[-1]{TEXT}}, $diff[$i]; push @{$hunk[-1]{DISPLAY}}, - ($diff_use_color ? $colored[$i] : $diff[$i]); + (@colored ? $colored[$i] : $diff[$i]); } return @hunk; } @@ -998,29 +980,55 @@ sub color_diff { } @_; } +my %edit_hunk_manually_modes = ( + stage => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for staging."), + stash => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for stashing."), + reset_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for unstaging."), + reset_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), + checkout_index => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding."), + checkout_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding."), + checkout_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), +); + sub edit_hunk_manually { my ($oldtext) = @_; my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff"; my $fh; open $fh, '>', $hunkfile - or die "failed to open hunk edit file for writing: " . $!; - print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; + or die sprintf(__("failed to open hunk edit file for writing: %s"), $!); + print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n"); print $fh @$oldtext; - my $participle = $patch_mode_flavour{PARTICIPLE}; my $is_reverse = $patch_mode_flavour{IS_REVERSE}; my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-'); - print $fh <<EOF; -# --- -# To remove '$remove_minus' lines, make them ' ' lines (context). -# To remove '$remove_plus' lines, delete them. -# Lines starting with # will be removed. -# -# If the patch applies cleanly, the edited hunk will immediately be -# marked for $participle. If it does not apply cleanly, you will be given -# an opportunity to edit again. If all lines of the hunk are removed, -# then the edit is aborted and the hunk is left unchanged. + my $comment_line_char = Git::get_comment_line_char; + print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char), +--- +To remove '%s' lines, make them ' ' lines (context). +To remove '%s' lines, delete them. +Lines starting with %s will be removed. EOF +__($edit_hunk_manually_modes{$patch_mode}), +# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages. +__ <<EOF2 ; +If it does not apply cleanly, you will be given an opportunity to +edit again. If all lines of the hunk are removed, then the edit is +aborted and the hunk is left unchanged. +EOF2 close $fh; chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR))); @@ -1031,8 +1039,8 @@ EOF } open $fh, '<', $hunkfile - or die "failed to open hunk edit file for reading: " . $!; - my @newtext = grep { !/^#/ } <$fh>; + or die sprintf(__("failed to open hunk edit file for reading: %s"), $!); + my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>; close $fh; unlink $hunkfile; @@ -1049,8 +1057,7 @@ EOF } sub diff_applies { - my $fh; - return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check', + return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check', map { @{$_->{TEXT}} } @_); } @@ -1067,6 +1074,14 @@ sub prompt_single_character { ReadMode 'cbreak'; my $key = ReadKey 0; ReadMode 'restore'; + if ($use_termcap and $key eq "\e") { + while (!defined $term_escapes{$key}) { + my $next = ReadKey 0.5; + last if (!defined $next); + $key .= $next; + } + $key =~ s/\e/^[/; + } print "$key" if defined $key; print "\n"; return $key; @@ -1080,6 +1095,7 @@ sub prompt_yesno { while (1) { print colored $prompt_color, $prompt; my $line = prompt_single_character; + return undef unless defined $line; return 0 if $line =~ /^n/i; return 1 if $line =~ /^y/i; } @@ -1109,22 +1125,66 @@ sub edit_hunk_loop { } else { prompt_yesno( - 'Your edited hunk does not apply. Edit again ' - . '(saying "no" discards!) [y/n]? ' + # TRANSLATORS: do not translate [y/n] + # The program will only accept that input + # at this point. + # Consider translating (saying "no" discards!) as + # (saying "n" for "no" discards!) if the translation + # of the word "no" does not start with n. + __('Your edited hunk does not apply. Edit again ' + . '(saying "no" discards!) [y/n]? ') ) or return undef; } } } +my %help_patch_modes = ( + stage => N__( +"y - stage this hunk +n - do not stage this hunk +q - quit; do not stage this hunk or any of the remaining ones +a - stage this hunk and all later hunks in the file +d - do not stage this hunk or any of the later hunks in the file"), + stash => N__( +"y - stash this hunk +n - do not stash this hunk +q - quit; do not stash this hunk or any of the remaining ones +a - stash this hunk and all later hunks in the file +d - do not stash this hunk or any of the later hunks in the file"), + reset_head => N__( +"y - unstage this hunk +n - do not unstage this hunk +q - quit; do not unstage this hunk or any of the remaining ones +a - unstage this hunk and all later hunks in the file +d - do not unstage this hunk or any of the later hunks in the file"), + reset_nothead => N__( +"y - apply this hunk to index +n - do not apply this hunk to index +q - quit; do not apply this hunk or any of the remaining ones +a - apply this hunk and all later hunks in the file +d - do not apply this hunk or any of the later hunks in the file"), + checkout_index => N__( +"y - discard this hunk from worktree +n - do not discard this hunk from worktree +q - quit; do not discard this hunk or any of the remaining ones +a - discard this hunk and all later hunks in the file +d - do not discard this hunk or any of the later hunks in the file"), + checkout_head => N__( +"y - discard this hunk from index and worktree +n - do not discard this hunk from index and worktree +q - quit; do not discard this hunk or any of the remaining ones +a - discard this hunk and all later hunks in the file +d - do not discard this hunk or any of the later hunks in the file"), + checkout_nothead => N__( +"y - apply this hunk to index and worktree +n - do not apply this hunk to index and worktree +q - quit; do not apply this hunk or any of the remaining ones +a - apply this hunk and all later hunks in the file +d - do not apply this hunk or any of the later hunks in the file"), +); + sub help_patch_cmd { - my $verb = lc $patch_mode_flavour{VERB}; - my $target = $patch_mode_flavour{TARGET}; - print colored $help_color, <<EOF ; -y - $verb this hunk$target -n - do not $verb this hunk$target -q - quit; do not $verb this hunk nor any of the remaining ones -a - $verb this hunk and all later hunks in the file -d - do not $verb this hunk nor any of the later hunks in the file + print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", __ <<EOF ; g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -1139,7 +1199,7 @@ EOF sub apply_patch { my $cmd = shift; - my $ret = run_git_apply $cmd . ' --recount', @_; + my $ret = run_git_apply $cmd, @_; if (!$ret) { print STDERR @_; } @@ -1148,19 +1208,19 @@ sub apply_patch { sub apply_patch_for_checkout_commit { my $reverse = shift; - my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_; - my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_; + my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_; + my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_; if ($applies_worktree && $applies_index) { - run_git_apply 'apply '.$reverse.' --cached --recount', @_; - run_git_apply 'apply '.$reverse.' --recount', @_; + run_git_apply 'apply '.$reverse.' --cached', @_; + run_git_apply 'apply '.$reverse, @_; return 1; } elsif (!$applies_index) { - print colored $error_color, "The selected hunks do not apply to the index!\n"; - if (prompt_yesno "Apply them to the worktree anyway? ") { - return run_git_apply 'apply '.$reverse.' --recount', @_; + print colored $error_color, __("The selected hunks do not apply to the index!\n"); + if (prompt_yesno __("Apply them to the worktree anyway? ")) { + return run_git_apply 'apply '.$reverse, @_; } else { - print colored $error_color, "Nothing was applied.\n"; + print colored $error_color, __("Nothing was applied.\n"); return 0; } } else { @@ -1171,22 +1231,26 @@ sub apply_patch_for_checkout_commit { sub patch_update_cmd { my @all_mods = list_modified($patch_mode_flavour{FILTER}); + error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE}) + for grep { $_->{UNMERGED} } @all_mods; + @all_mods = grep { !$_->{UNMERGED} } @all_mods; + my @mods = grep { !($_->{BINARY}) } @all_mods; my @them; if (!@mods) { if (@all_mods) { - print STDERR "Only binary files changed.\n"; + print STDERR __("Only binary files changed.\n"); } else { - print STDERR "No changes.\n"; + print STDERR __("No changes.\n"); } return 0; } - if ($patch_mode) { + if ($patch_mode_only) { @them = @mods; } else { - @them = list_and_choose({ PROMPT => 'Patch update', + @them = list_and_choose({ PROMPT => __('Patch update'), HEADER => $status_head, }, @mods); } @@ -1218,7 +1282,7 @@ sub summarize_hunk { # Print a one-line summary of each hunk in the array ref in -# the first argument, starting wih the index in the 2nd. +# the first argument, starting with the index in the 2nd. sub display_hunks { my ($hunks, $i) = @_; my $ctr = 0; @@ -1236,6 +1300,44 @@ sub display_hunks { return $i; } +my %patch_update_prompt_modes = ( + stage => { + mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "), + }, + stash => { + mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_head => { + mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_nothead => { + mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "), + }, + checkout_index => { + mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_head => { + mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_nothead => { + mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "), + }, +); + sub patch_update_file { my $quit = 0; my ($ix, $num); @@ -1308,13 +1410,11 @@ sub patch_update_file { for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, $patch_mode_flavour{VERB}, - ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : - $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' : - ' this hunk'), - $patch_mode_flavour{TARGET}, - " [y,n,q,a,d,/$other,?]? "; + print colored $prompt_color, + sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other); + my $line = prompt_single_character; + last unless defined $line; if ($line) { if ($line =~ /^y/i) { $hunk[$ix]{USE} = 1; @@ -1335,12 +1435,12 @@ sub patch_update_file { my $response = $1; my $no = $ix > 10 ? $ix - 10 : 0; while ($response eq '') { - my $extra = ""; $no = display_hunks(\@hunk, $no); if ($no < $num) { - $extra = " (<ret> to see more)"; + print __("go to which hunk (<ret> to see more)? "); + } else { + print __("go to which hunk? "); } - print "go to which hunk$extra? "; $response = <STDIN>; if (!defined $response) { $response = ''; @@ -1348,11 +1448,13 @@ sub patch_update_file { chomp $response; } if ($response !~ /^\s*\d+\s*$/) { - error_msg "Invalid number: '$response'\n"; + error_msg sprintf(__("Invalid number: '%s'\n"), + $response); } elsif (0 < $response && $response <= $num) { $ix = $response - 1; } else { - error_msg "Sorry, only $num hunks available.\n"; + error_msg sprintf(__n("Sorry, only %d hunk available.\n", + "Sorry, only %d hunks available.\n", $num), $num); } next; } @@ -1366,19 +1468,18 @@ sub patch_update_file { next; } elsif ($line =~ /^q/i) { - while ($ix < $num) { - if (!defined $hunk[$ix]{USE}) { - $hunk[$ix]{USE} = 0; + for ($i = 0; $i < $num; $i++) { + if (!defined $hunk[$i]{USE}) { + $hunk[$i]{USE} = 0; } - $ix++; } $quit = 1; - next; + last; } elsif ($line =~ m|^/(.*)|) { my $regex = $1; if ($1 eq "") { - print colored $prompt_color, "search for regex? "; + print colored $prompt_color, __("search for regex? "); $regex = <STDIN>; if (defined $regex) { chomp $regex; @@ -1391,7 +1492,7 @@ sub patch_update_file { if ($@) { my ($err,$exp) = ($@, $1); $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//; - error_msg "Malformed search regexp $exp: $err\n"; + error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err); next; } my $iy = $ix; @@ -1401,7 +1502,7 @@ sub patch_update_file { $iy++; $iy = 0 if ($iy >= $num); if ($ix == $iy) { - error_msg "No hunk matches the given pattern\n"; + error_msg __("No hunk matches the given pattern\n"); last; } } @@ -1413,7 +1514,7 @@ sub patch_update_file { $ix--; } else { - error_msg "No previous hunk\n"; + error_msg __("No previous hunk\n"); } next; } @@ -1422,7 +1523,7 @@ sub patch_update_file { $ix++; } else { - error_msg "No next hunk\n"; + error_msg __("No next hunk\n"); } next; } @@ -1435,21 +1536,23 @@ sub patch_update_file { } } else { - error_msg "No previous hunk\n"; + error_msg __("No previous hunk\n"); } next; } elsif ($line =~ /^j/) { if ($other !~ /j/) { - error_msg "No next hunk\n"; + error_msg __("No next hunk\n"); next; } } elsif ($other =~ /s/ && $line =~ /^s/) { my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); if (1 < @split) { - print colored $header_color, "Split into ", - scalar(@split), " hunks.\n"; + print colored $header_color, sprintf( + __n("Split into %d hunk.\n", + "Split into %d hunks.\n", + scalar(@split)), scalar(@split)); } splice (@hunk, $ix, 1, @split); $num = scalar @hunk; @@ -1485,7 +1588,6 @@ sub patch_update_file { } if (@result) { - my $fh; my @patch = reassemble_patch($head->{TEXT}, @result); my $apply_routine = $patch_mode_flavour{APPLY}; &$apply_routine(@patch); @@ -1500,28 +1602,30 @@ sub diff_cmd { my @mods = list_modified('index-only'); @mods = grep { !($_->{BINARY}) } @mods; return if (!@mods); - my (@them) = list_and_choose({ PROMPT => 'Review diff', + my (@them) = list_and_choose({ PROMPT => __('Review diff'), IMMEDIATE => 1, HEADER => $status_head, }, @mods); return if (!@them); - my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD'; system(qw(git diff -p --cached), $reference, '--', map { $_->{VALUE} } @them); } sub quit_cmd { - print "Bye.\n"; + print __("Bye.\n"); exit(0); } sub help_cmd { - print colored $help_color, <<\EOF ; +# TRANSLATORS: please do not translate the command names +# 'status', 'update', 'revert', etc. + print colored $help_color, __ <<'EOF' ; status - show paths with changes update - add working tree state to the staged set of changes revert - revert staged set of changes back to the HEAD version patch - pick hunks and update selectively -diff - view diff between HEAD and index +diff - view diff between HEAD and index add untracked - add contents of untracked files to the staged set of changes EOF } @@ -1534,39 +1638,40 @@ sub process_args { if ($1 eq 'reset') { $patch_mode = 'reset_head'; $patch_mode_revision = 'HEAD'; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); if ($arg ne '--') { $patch_mode_revision = $arg; $patch_mode = ($arg eq 'HEAD' ? 'reset_head' : 'reset_nothead'); - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } } elsif ($1 eq 'checkout') { - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); if ($arg eq '--') { $patch_mode = 'checkout_index'; } else { $patch_mode_revision = $arg; $patch_mode = ($arg eq 'HEAD' ? 'checkout_head' : 'checkout_nothead'); - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } } elsif ($1 eq 'stage' or $1 eq 'stash') { $patch_mode = $1; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } else { - die "unknown --patch mode: $1"; + die sprintf(__("unknown --patch mode: %s"), $1); } } else { $patch_mode = 'stage'; - $arg = shift @ARGV or die "missing --"; + $arg = shift @ARGV or die __("missing --"); } - die "invalid argument $arg, expecting --" - unless $arg eq "--"; + die sprintf(__("invalid argument %s, expecting --"), + $arg) unless $arg eq "--"; %patch_mode_flavour = %{$patch_modes{$patch_mode}}; + $patch_mode_only = 1; } elsif ($arg ne "--") { - die "invalid argument $arg, expecting --"; + die sprintf(__("invalid argument %s, expecting --"), $arg); } } @@ -1581,10 +1686,10 @@ sub main_loop { [ 'help', \&help_cmd, ], ); while (1) { - my ($it) = list_and_choose({ PROMPT => 'What now', + my ($it) = list_and_choose({ PROMPT => __('What now'), SINGLETON => 1, LIST_FLAT => 4, - HEADER => '*** Commands ***', + HEADER => __('*** Commands ***'), ON_EOF => \&quit_cmd, IMMEDIATE => 1 }, @cmd); if ($it) { @@ -1600,7 +1705,7 @@ sub main_loop { process_args(); refresh(); -if ($patch_mode) { +if ($patch_mode_only) { patch_update_cmd(); } else { |