git-svn: Fix for svn paths removed > log-window-size revisions ago
[gitweb.git] / git-svn.perl
index d9197989d2e063ecf0bbbac143a046f8de79ce52..5836ddec8a8070516cf020f93c09d821147171cb 100755 (executable)
@@ -47,7 +47,8 @@ BEGIN
        # import functions from Git into our packages, en masse
        no strict 'refs';
        foreach (qw/command command_oneline command_noisy command_output_pipe
-                   command_input_pipe command_close_pipe/) {
+                   command_input_pipe command_close_pipe
+                   command_bidi_pipe command_close_bidi_pipe/) {
                for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
                        Git::SVN::Migration Git::SVN::Log Git::SVN),
                        __PACKAGE__) {
@@ -63,7 +64,7 @@ BEGIN
 my ($_stdin, $_help, $_edit,
        $_message, $_file,
        $_template, $_shared,
-       $_version, $_fetch_all, $_no_rebase,
+       $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
        $_merge, $_strategy, $_dry_run, $_local,
        $_prefix, $_no_checkout, $_url, $_verbose,
        $_git_format, $_commit_url, $_tag);
@@ -112,6 +113,7 @@ BEGIN
        fetch => [ \&cmd_fetch, "Download new revisions from SVN",
                        { 'revision|r=s' => \$_revision,
                          'fetch-all|all' => \$_fetch_all,
+                         'parent|p' => \$_fetch_parent,
                           %fc_opts } ],
        clone => [ \&cmd_clone, "Initialize and fetch revisions",
                        { 'revision|r=s' => \$_revision,
@@ -145,7 +147,7 @@ BEGIN
                   'dry-run|n' => \$_dry_run } ],
        'set-tree' => [ \&cmd_set_tree,
                        "Set an SVN repository to a git tree-ish",
-                       { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
+                       { 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'create-ignore' => [ \&cmd_create_ignore,
                             'Create a .gitignore per svn:ignore',
                             { 'revision|r=i' => \$_revision
@@ -326,6 +328,7 @@ sub do_git_init_db {
                command_noisy(@init_db);
                $_repository = Git->repository(Repository => ".git");
        }
+       command_noisy('config', 'core.autocrlf', 'false');
        my $set;
        my $pfx = "svn-remote.$Git::SVN::default_repo_id";
        foreach my $i (keys %icv) {
@@ -334,6 +337,9 @@ sub do_git_init_db {
                command_noisy('config', "$pfx.$i", $icv{$i});
                $set = $i;
        }
+       my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
+       command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
+               if defined $$ignore_regex;
 }
 
 sub init_subdir {
@@ -381,12 +387,21 @@ sub cmd_fetch {
        }
        my ($remote) = @_;
        if (@_ > 1) {
-               die "Usage: $0 fetch [--all] [svn-remote]\n";
+               die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
        }
-       $remote ||= $Git::SVN::default_repo_id;
-       if ($_fetch_all) {
+       if ($_fetch_parent) {
+               my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+               unless ($gs) {
+                       die "Unable to determine upstream SVN information from ",
+                           "working tree history\n";
+               }
+               # just fetch, don't checkout.
+               $_no_checkout = 'true';
+               $_fetch_all ? $gs->fetch_all : $gs->fetch;
+       } elsif ($_fetch_all) {
                cmd_multi_fetch();
        } else {
+               $remote ||= $Git::SVN::default_repo_id;
                Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes());
        }
 }
@@ -1254,6 +1269,40 @@ sub cmt_metadata {
                command(qw/cat-file commit/, shift)))[-1]);
 }
 
+sub cmt_sha2rev_batch {
+       my %s2r;
+       my ($pid, $in, $out, $ctx) = command_bidi_pipe(qw/cat-file --batch/);
+       my $list = shift;
+
+       foreach my $sha (@{$list}) {
+               my $first = 1;
+               my $size = 0;
+               print $out $sha, "\n";
+
+               while (my $line = <$in>) {
+                       if ($first && $line =~ /^[[:xdigit:]]{40}\smissing$/) {
+                               last;
+                       } elsif ($first &&
+                              $line =~ /^[[:xdigit:]]{40}\scommit\s(\d+)$/) {
+                               $first = 0;
+                               $size = $1;
+                               next;
+                       } elsif ($line =~ /^(git-svn-id: )/) {
+                               my (undef, $rev, undef) =
+                                                     extract_metadata($line);
+                               $s2r{$sha} = $rev;
+                       }
+
+                       $size -= length($line);
+                       last if ($size == 0);
+               }
+       }
+
+       command_close_bidi_pipe($pid, $in, $out, $ctx);
+
+       return \%s2r;
+}
+
 sub working_head_info {
        my ($head, $refs) = @_;
        my @args = ('log', '--no-color', '--first-parent', '--pretty=medium');
@@ -3286,6 +3335,8 @@ sub new {
                $self->{empty_symlinks} =
                                  _mark_empty_symlinks($git_svn, $switch_path);
        }
+       $self->{ignore_regex} = eval { command_oneline('config', '--get',
+                            "svn-remote.$git_svn->{repo_id}.ignore-paths") };
        $self->{empty} = {};
        $self->{dir_prop} = {};
        $self->{file_prop} = {};
@@ -3350,8 +3401,10 @@ sub in_dot_git {
 
 # return value: 0 -- don't ignore, 1 -- ignore
 sub is_path_ignored {
-       my ($path) = @_;
+       my ($self, $path) = @_;
        return 1 if in_dot_git($path);
+       return 1 if defined($self->{ignore_regex}) &&
+                   $path =~ m!$self->{ignore_regex}!;
        return 0 unless defined($_ignore_regex);
        return 1 if $path =~ m!$_ignore_regex!o;
        return 0;
@@ -3382,7 +3435,7 @@ sub git_path {
 
 sub delete_entry {
        my ($self, $path, $rev, $pb) = @_;
-       return undef if is_path_ignored($path);
+       return undef if $self->is_path_ignored($path);
 
        my $gpath = $self->git_path($path);
        return undef if ($gpath eq '');
@@ -3415,7 +3468,7 @@ sub open_file {
        my ($self, $path, $pb, $rev) = @_;
        my ($mode, $blob);
 
-       goto out if is_path_ignored($path);
+       goto out if $self->is_path_ignored($path);
 
        my $gpath = $self->git_path($path);
        ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
@@ -3435,7 +3488,7 @@ sub add_file {
        my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
        my $mode;
 
-       if (!is_path_ignored($path)) {
+       if (!$self->is_path_ignored($path)) {
                my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
                delete $self->{empty}->{$dir};
                $mode = '100644';
@@ -3446,7 +3499,7 @@ sub add_file {
 
 sub add_directory {
        my ($self, $path, $cp_path, $cp_rev) = @_;
-       goto out if is_path_ignored($path);
+       goto out if $self->is_path_ignored($path);
        my $gpath = $self->git_path($path);
        if ($gpath eq '') {
                my ($ls, $ctx) = command_output_pipe(qw/ls-tree
@@ -3470,7 +3523,7 @@ sub add_directory {
 
 sub change_dir_prop {
        my ($self, $db, $prop, $value) = @_;
-       return undef if is_path_ignored($db->{path});
+       return undef if $self->is_path_ignored($db->{path});
        $self->{dir_prop}->{$db->{path}} ||= {};
        $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
        undef;
@@ -3478,7 +3531,7 @@ sub change_dir_prop {
 
 sub absent_directory {
        my ($self, $path, $pb) = @_;
-       return undef if is_path_ignored($path);
+       return undef if $self->is_path_ignored($path);
        $self->{absent_dir}->{$pb->{path}} ||= [];
        push @{$self->{absent_dir}->{$pb->{path}}}, $path;
        undef;
@@ -3486,7 +3539,7 @@ sub absent_directory {
 
 sub absent_file {
        my ($self, $path, $pb) = @_;
-       return undef if is_path_ignored($path);
+       return undef if $self->is_path_ignored($path);
        $self->{absent_file}->{$pb->{path}} ||= [];
        push @{$self->{absent_file}->{$pb->{path}}}, $path;
        undef;
@@ -3494,7 +3547,7 @@ sub absent_file {
 
 sub change_file_prop {
        my ($self, $fb, $prop, $value) = @_;
-       return undef if is_path_ignored($fb->{path});
+       return undef if $self->is_path_ignored($fb->{path});
        if ($prop eq 'svn:executable') {
                if ($fb->{mode_b} != 120000) {
                        $fb->{mode_b} = defined $value ? 100755 : 100644;
@@ -3510,7 +3563,7 @@ sub change_file_prop {
 
 sub apply_textdelta {
        my ($self, $fb, $exp) = @_;
-       return undef if is_path_ignored($fb->{path});
+       return undef if $self->is_path_ignored($fb->{path});
        my $fh = $::_repository->temp_acquire('svn_delta');
        # $fh gets auto-closed() by SVN::TxDelta::apply(),
        # (but $base does not,) so dup() it for reading in close_file
@@ -3557,7 +3610,7 @@ sub apply_textdelta {
 
 sub close_file {
        my ($self, $fb, $exp) = @_;
-       return undef if is_path_ignored($fb->{path});
+       return undef if $self->is_path_ignored($fb->{path});
 
        my $hash;
        my $path = $self->git_path($fb->{path});
@@ -4385,6 +4438,7 @@ sub gs_fetch_loop_common {
        my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
        my $longest_path = longest_common_path($gsv, $globs);
        my $ra_url = $self->{url};
+       my $find_trailing_edge;
        while (1) {
                my %revs;
                my $err;
@@ -4402,8 +4456,10 @@ sub gs_fetch_loop_common {
                               sub { $revs{$_[1]} = _cb(@_) });
                if ($err) {
                        print "Checked through r$max\r";
+               } else {
+                       $find_trailing_edge = 1;
                }
-               if ($err && $max >= $head) {
+               if ($err and $find_trailing_edge) {
                        print STDERR "Path '$longest_path' ",
                                     "was probably deleted:\n",
                                     $err->expanded_message,
@@ -4422,6 +4478,7 @@ sub gs_fetch_loop_common {
                                        last;
                                }
                        }
+                       $find_trailing_edge = 0;
                }
                $SVN::Error::handler = $err_handler;
 
@@ -4991,11 +5048,22 @@ sub cmd_blame {
                                                  '--', $path);
                my ($sha1);
                my %authors;
+               my @buffer;
+               my %dsha; #distinct sha keys
+
                while (my $line = <$fh>) {
+                       push @buffer, $line;
+                       if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
+                               $dsha{$1} = 1;
+                       }
+               }
+
+               my $s2r = ::cmt_sha2rev_batch([keys %dsha]);
+
+               foreach my $line (@buffer) {
                        if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
-                               $sha1 = $1;
-                               (undef, $rev, undef) = ::cmt_metadata($1);
-                               $rev = '0' if (!$rev);
+                               $rev = $s2r->{$1};
+                               $rev = '0' if (!$rev)
                        }
                        elsif ($line =~ /^author (.*)/) {
                                $authors{$rev} = $1;