git-svn: hopefully make 'fetch' more user-friendly
[gitweb.git] / git-svn.perl
index fdecffbbc75f49fc8e7c59361ba3e5eb90c269e2..3eed62fc0bcacd74827b1e7c901d352f6b843986 100755 (executable)
@@ -53,7 +53,7 @@ BEGIN
 my ($_stdin, $_help, $_edit,
        $_message, $_file,
        $_template, $_shared,
-       $_version,
+       $_version, $_fetch_all,
        $_merge, $_strategy, $_dry_run,
        $_prefix);
 $Git::SVN::_follow_parent = 1;
@@ -84,7 +84,9 @@ BEGIN
 
 my %cmd = (
        fetch => [ \&cmd_fetch, "Download new revisions from SVN",
-                       { 'revision|r=s' => \$_revision, %fc_opts } ],
+                       { 'revision|r=s' => \$_revision,
+                         'all|a' => \$_fetch_all,
+                          %fc_opts } ],
        init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
                          \%init_opts ],
@@ -106,8 +108,8 @@ BEGIN
                         'prefix=s' => \$_prefix,
                        } ],
        'multi-fetch' => [ \&cmd_multi_fetch,
-                       'Fetch multiple trees (like git-svnimport)',
-                       \%fc_opts ],
+                          "Deprecated alias for $0 fetch --all",
+                          { 'revision|r=s' => \$_revision, %fc_opts } ],
        'migrate' => [ sub { },
                       # no-op, we automatically run this anyways,
                       'Migrate configuration/metadata/layout from
@@ -226,16 +228,19 @@ sub cmd_init {
 }
 
 sub cmd_fetch {
-       if (@_) {
-               die "Additional fetch arguments are no longer supported.\n",
-                   "Use --follow-parent if you have moved/copied directories
-                   instead.\n";
+       if (grep /^\d+=./, @_) {
+               die "'<rev>=<commit>' fetch arguments are ",
+                   "no longer supported.\n";
        }
-       my $gs = Git::SVN->new;
-       $gs->fetch(parse_revision_argument());
-       if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) {
-               command_noisy(qw(update-ref refs/heads/master),
-                             $gs->{last_commit});
+       my ($remote) = @_;
+       if (@_ > 1) {
+               die "Usage: $0 fetch [--all|-a] [svn-remote]\n";
+       }
+       $remote ||= $Git::SVN::default_repo_id;
+       if ($_fetch_all) {
+               cmd_multi_fetch();
+       } else {
+               Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes());
        }
 }
 
@@ -276,11 +281,27 @@ sub cmd_set_tree {
 
 sub cmd_dcommit {
        my $head = shift;
-       my $gs = Git::SVN->new;
        $head ||= 'HEAD';
-       my @refs = command(qw/rev-list --no-merges/, $gs->refname."..$head");
+       my ($url, $rev, $uuid);
+       my ($fh, $ctx) = command_output_pipe('rev-list', $head);
+       my @refs;
+       my $c;
+       while (<$fh>) {
+               $c = $_;
+               chomp $c;
+               ($url, $rev, $uuid) = cmt_metadata($c);
+               last if (defined $url && defined $rev && defined $uuid);
+               unshift @refs, $c;
+       }
+       close $fh; # most likely breaking the pipe
+       unless (defined $url && defined $rev && defined $uuid) {
+               die "Unable to determine upstream SVN information from ",
+                   "$head history:\n  $ctx\n";
+       }
+       my $gs = Git::SVN->find_by_url($url) or
+                          die "Can't determine fetch information for $url\n";
        my $last_rev;
-       foreach my $d (reverse @refs) {
+       foreach my $d (@refs) {
                if (!verify_ref("$d~1")) {
                        fatal "Commit $d\n",
                              "has no parent commit, and therefore ",
@@ -300,13 +321,13 @@ sub cmd_dcommit {
                } else {
                        my %ed_opts = ( r => $last_rev,
                                        log => get_commit_entry($d)->{log},
-                                       ra => $gs->ra,
+                                       ra => Git::SVN::Ra->new($url),
                                        tree_a => "$d~1",
                                        tree_b => $d,
                                        editor_cb => sub {
                                               print "Committed r$_[0]\n";
                                               $last_rev = $_[0]; },
-                                       svn_path => $gs->{path} );
+                                       svn_path => '');
                        if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
                                print "No changes\n$d~1 == $d\n";
                        }
@@ -367,8 +388,7 @@ sub cmd_multi_init {
 sub cmd_multi_fetch {
        my $remotes = Git::SVN::read_all_remotes();
        foreach my $repo_id (sort keys %$remotes) {
-               if ($remotes->{$repo_id}->{url} &&
-                   $remotes->{$repo_id}->{fetch}) {
+               if ($remotes->{$repo_id}->{url}) {
                        Git::SVN::fetch_all($repo_id, $remotes);
                }
        }
@@ -425,18 +445,6 @@ sub cmd_commit_diff {
 
 ########################### utility functions #########################
 
-sub parse_revision_argument {
-       if (!defined $_revision || $_revision eq 'BASE:HEAD') {
-               return (undef, undef);
-       }
-       return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
-       return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
-       return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/);
-       return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/);
-       die "revision argument: $_revision not understood by git-svn\n",
-           "Try using the command-line svn client instead\n";
-}
-
 sub complete_svn_url {
        my ($url, $path) = @_;
        $path =~ s#/+$##;
@@ -483,6 +491,17 @@ sub complete_url_ls_init {
                        $gs = Git::SVN->init($url, $path, undef, $ref, 1);
                }
                if ($gs) {
+                       my $k = "svn-remote.$gs->{repo_id}.url";
+                       my $orig_url = eval {
+                               command_oneline(qw/config --get/, $k)
+                       };
+                       if ($orig_url && ($orig_url ne $gs->{url})) {
+                               die "$k already set: $orig_url\n",
+                                   "wanted to set to: $gs->{url}\n";
+                       }
+                       unless ($orig_url) {
+                               command_oneline('config', $k, $gs->{url});
+                       }
                        $remote_id = $gs->{repo_id};
                        last;
                }
@@ -729,6 +748,19 @@ sub resolve_local_globs {
        }
 }
 
+sub parse_revision_argument {
+       my ($base, $head) = @_;
+       if (!defined $::_revision || $::_revision eq 'BASE:HEAD') {
+               return ($base, $head);
+       }
+       return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/);
+       return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/);
+       return ($head, $head) if ($::_revision eq 'HEAD');
+       return ($base, $1) if ($::_revision =~ /^BASE:(\d+)$/);
+       return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/);
+       die "revision argument: $::_revision not understood by git-svn\n";
+}
+
 sub fetch_all {
        my ($repo_id, $remotes) = @_;
        my $remote = $remotes->{$repo_id};
@@ -736,7 +768,7 @@ sub fetch_all {
        my $url = $remote->{url};
        my (@gs, @globs);
        my $ra = Git::SVN::Ra->new($url);
-       my $uuid = $ra->uuid;
+       my $uuid = $ra->get_uuid;
        my $head = $ra->get_latest_revnum;
        my $base = $head;
 
@@ -751,14 +783,18 @@ sub fetch_all {
                }
        }
 
-       foreach my $p (sort keys %$fetch) {
-               my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
-               my $lr = $gs->rev_db_max;
-               if (defined $lr) {
-                       $base = $lr if ($lr < $base);
+       if ($fetch) {
+               foreach my $p (sort keys %$fetch) {
+                       my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
+                       my $lr = $gs->rev_db_max;
+                       if (defined $lr) {
+                               $base = $lr if ($lr < $base);
+                       }
+                       push @gs, $gs;
                }
-               push @gs, $gs;
        }
+
+       ($base, $head) = parse_revision_argument($base, $head);
        $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs);
 }
 
@@ -892,6 +928,35 @@ sub init_remote_config {
        $self->{url} = $url;
 }
 
+sub find_by_url { # repos_root and, path are optional
+       my ($class, $full_url, $repos_root, $path) = @_;
+       my $remotes = read_all_remotes();
+       if (defined $full_url && defined $repos_root && !defined $path) {
+               $path = $full_url;
+               $path =~ s#^\Q$repos_root\E(?:/|$)##;
+       }
+       foreach my $repo_id (keys %$remotes) {
+               my $u = $remotes->{$repo_id}->{url} or next;
+               next if defined $repos_root && $repos_root ne $u;
+
+               my $fetch = $remotes->{$repo_id}->{fetch} || {};
+               foreach (qw/branches tags/) {
+                       resolve_local_globs($u, $fetch,
+                                           $remotes->{$repo_id}->{$_});
+               }
+               my $p = $path;
+               unless (defined $p) {
+                       $p = $full_url;
+                       $p =~ s#^\Q$u\E(?:/|$)## or next;
+               }
+               foreach my $f (keys %$fetch) {
+                       next if $f ne $p;
+                       return Git::SVN->new($fetch->{$f}, $repo_id, $f);
+               }
+       }
+       undef;
+}
+
 sub init {
        my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_;
        my $self = _new($class, $repo_id, $ref_id, $path);
@@ -937,7 +1002,8 @@ sub new {
        $self->{url} = command_oneline('config', '--get',
                                       "svn-remote.$repo_id.url") or
                   die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
-       if (-z $self->{db_path} && ::verify_ref($self->refname.'^0')) {
+       if ((-z $self->db_path || ! -e $self->db_path) &&
+           ::verify_ref($self->refname.'^0')) {
                $self->rebuild;
        }
        $self;
@@ -945,43 +1011,115 @@ sub new {
 
 sub refname { "refs/remotes/$_[0]->{ref_id}" }
 
-sub set_svm_vars {
-       my ($self, $ra) = @_;
-       my $section = "svn-remote.$self->{repo_id}";
+sub svm_uuid {
+       my ($self) = @_;
+       return $self->{svm}->{uuid} if $self->svm;
+       $self->ra;
+       unless ($self->{svm}) {
+               die "SVM UUID not cached, and reading remotely failed\n";
+       }
+       $self->{svm}->{uuid};
+}
 
+sub svm {
+       my ($self) = @_;
+       return $self->{svm} if $self->{svm};
+       my $svm;
        # see if we have it in our config, first:
        eval {
-               $self->{svm} = {
+               my $section = "svn-remote.$self->{repo_id}";
+               $svm = {
                  source => tmp_config('--get', "$section.svm-source"),
                  uuid => tmp_config('--get', "$section.svm-uuid"),
                }
        };
-       return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid});
+       $self->{svm} = $svm if ($svm && $svm->{source} && $svm->{uuid});
+       $self->{svm};
+}
 
-       # nope, make sure we're connected to the repository root:
-       if ($ra->{repos_root} ne $self->{url}) {
-               $ra = Git::SVN::Ra->new($ra->{repos_root});
-       }
-       my $r = $ra->get_latest_revnum;
-       my ($props) = ($ra->get_dir('', $r))[2];
-       if (my $src = $props->{'svm:source'}) {
+sub _set_svm_vars {
+       my ($self, $ra) = @_;
+       return $ra if $self->svm;
+
+       my @err = ( "useSvmProps set, but failed to read SVM properties\n",
+                   "(svm:source, svm:mirror, svm:mirror) ",
+                   "from the following URLs:\n" );
+       sub read_svm_props {
+               my ($self, $props) = @_;
+               my $src = $props->{'svm:source'};
+               my $mirror = $props->{'svm:mirror'};
+               my $uuid = $props->{'svm:uuid'};
+               return undef if (!$src || !$mirror || !$uuid);
+
+               chomp($src, $mirror, $uuid);
+
+               $uuid =~ m{^[0-9a-f\-]{30,}$}
+                   or die "doesn't look right - svm:uuid is '$uuid'\n";
                # don't know what a '!' is there for, also the
                # username is of no interest
-               $src =~ s{!$}{};
+               $src =~ s{/?!$}{$mirror};
+               $src =~ s{/+$}{}; # no trailing slashes please
                $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1};
-               tmp_config('--add', "$section.svm-source", $src);
 
-               my $uuid = $props->{'svm:uuid'};
-               $uuid =~ m{^[0-9a-f\-]{30,}$}
-                   or die "doesn't look right - svm:uuid is '$uuid'\n";
+               my $section = "svn-remote.$self->{repo_id}";
+               tmp_config('--add', "$section.svm-source", $src);
                tmp_config('--add', "$section.svm-uuid", $uuid);
-
                $self->{svm} = { source => $src , uuid => $uuid };
+               return 1;
        }
-       if ($ra->{repos_root} ne $self->{url}) {
-               $ra = Git::SVN::Ra->new($self->{url});
+
+       my $r = $ra->get_latest_revnum;
+       my $path = $self->{path};
+       my @tried_a = ($path);
+       while (length $path) {
+               if ($self->read_svm_props(($ra->get_dir($path, $r))[2])) {
+                       return $ra;
+               }
+               $path =~ s#/?[^/]+$## && push @tried_a, $path;
        }
-       $ra;
+       if ($self->read_svm_props(($ra->get_dir('', $r))[2])) {
+               return $ra;
+       }
+
+       if ($ra->{repos_root} eq $self->{url}) {
+               die @err, map { "  $self->{url}/$_\n" } @tried_a, "\n";
+       }
+
+       # nope, make sure we're connected to the repository root:
+       my $ok;
+       my @tried_b;
+       $path = $ra->{svn_path};
+       $path =~ s#/?[^/]+$##; # we already tried this one above
+       $ra = Git::SVN::Ra->new($ra->{repos_root});
+       while (length $path) {
+               $ok = $self->read_svm_props(($ra->get_dir($path, $r))[2]);
+               last if $ok;
+               $path =~ s#/?[^/]+$## && push @tried_b, $path;
+       }
+       $ok = $self->read_svm_props(($ra->get_dir('', $r))[2]) unless $ok;
+       if (!$ok) {
+               die @err, map { "  $self->{url}/$_\n" } @tried_a, "\n",
+                         map { "  $ra->{url}/$_\n" } @tried_b, "\n"
+       }
+       Git::SVN::Ra->new($self->{url});
+}
+
+# this allows us to memoize our SVN::Ra UUID locally and avoid a
+# remote lookup (useful for 'git svn log').
+sub ra_uuid {
+       my ($self) = @_;
+       unless ($self->{ra_uuid}) {
+               my $key = "svn-remote.$self->{repo_id}.uuid";
+               my $uuid = eval { tmp_config('--get', $key) };
+               if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/) {
+                       $self->{ra_uuid} = $uuid;
+               } else {
+                       die "ra_uuid called without URL\n" unless $self->{url};
+                       $self->{ra_uuid} = $self->ra->get_uuid;
+                       tmp_config('--add', $key, $self->{ra_uuid});
+               }
+       }
+       $self->{ra_uuid};
 }
 
 sub ra {
@@ -992,7 +1130,7 @@ sub ra {
                        die "Can't have both 'noMetadata' and ",
                            "'useSvmProps' options set!\n";
                }
-               $ra = $self->set_svm_vars($ra);
+               $ra = $self->_set_svm_vars($ra);
                $self->{-want_revprops} = 1;
        }
        $ra;
@@ -1048,10 +1186,14 @@ sub last_rev_commit {
                        return ($rev, $c);
                }
        }
+       my $db_path = $self->db_path;
+       unless (-e $db_path) {
+               ($self->{last_rev}, $self->{last_commit}) = (undef, undef);
+               return (undef, undef);
+       }
        my $offset = -41; # from tail
        my $rl;
-       open my $fh, '<', $self->{db_path} or
-                                croak "$self->{db_path} not readable: $!\n";
+       open my $fh, '<', $db_path or croak "$db_path not readable: $!\n";
        sysseek($fh, $offset, 2); # don't care for errors
        sysread($fh, $rl, 41) == 41 or return (undef, undef);
        chomp $rl;
@@ -1062,7 +1204,7 @@ sub last_rev_commit {
                chomp $rl;
        }
        if ($c && $c ne $rl) {
-               die "$self->{db_path} and ", $self->refname,
+               die "$db_path and ", $self->refname,
                    " inconsistent!:\n$c != $rl\n";
        }
        my $rev = sysseek($fh, 0, 1) or croak $!;
@@ -1185,11 +1327,9 @@ sub do_git_commit {
                croak "$log_entry->{revision} = $c already exists! ",
                      "Why are we refetching it?\n";
        }
-       my $author = $log_entry->{author};
-       my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}}
-                          : ($author, "$author\@".$self->ra->uuid));
-       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
-       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name};
+       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
+                                                         $log_entry->{email};
        $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
 
        my $tree = $log_entry->{tree};
@@ -1227,6 +1367,8 @@ sub do_git_commit {
        print "r$log_entry->{revision}";
        if (defined $log_entry->{svm_revision}) {
                 print " (\@$log_entry->{svm_revision})";
+                $self->rev_db_set($log_entry->{svm_revision}, $commit,
+                                  0, $self->svm_uuid);
        }
        print " = $commit ($self->{ref_id})\n";
        if (defined $_repack && (--$_repack_nr == 0)) {
@@ -1298,23 +1440,7 @@ sub find_parent_branch {
        print STDERR  "Found possible branch point: ",
                      "$new_url => ", $self->full_url, ", $r\n";
        $branch_from =~ s#^/##;
-       my $remotes = read_all_remotes();
-       my $gs;
-       foreach my $repo_id (keys %$remotes) {
-               my $u = $remotes->{$repo_id}->{url} or next;
-               next if $url ne $u;
-               my $fetch = $remotes->{$repo_id}->{fetch};
-               foreach (qw/branches tags/) {
-                       resolve_local_globs($url, $fetch,
-                                           $remotes->{$repo_id}->{$_});
-               }
-               foreach my $f (keys %$fetch) {
-                       next if $f ne $branch_from;
-                       $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f);
-                       last;
-               }
-               last if $gs;
-       }
+       my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
        unless ($gs) {
                my $ref_id = $self->{ref_id};
                $ref_id =~ s/\@\d+$//;
@@ -1477,8 +1603,10 @@ sub make_log_entry {
        close $un or croak $!;
 
        $log_entry{date} = parse_svn_date($log_entry{date});
-       $log_entry{author} = check_author($log_entry{author});
        $log_entry{log} .= "\n";
+       my $author = $log_entry{author} = check_author($log_entry{author});
+       my ($name, $email) = defined $::users{$author} ? @{$::users{$author}}
+                                                      : ($author, undef);
        if (defined $headrev && $self->use_svm_props) {
                my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$};
                if ($uuid ne $self->{svm}->{uuid}) {
@@ -1490,10 +1618,14 @@ sub make_log_entry {
                $full_url .= "/$self->{path}" if length $self->{path};
                $log_entry{metadata} = "$full_url\@$r $uuid";
                $log_entry{svm_revision} = $r;
+               $email ||= "$author\@$uuid"
        } else {
                $log_entry{metadata} = $self->full_url . "\@$rev " .
-                                      $self->ra->uuid;
+                                      $self->ra->get_uuid;
+               $email ||= "$author\@" . $self->ra->get_uuid;
        }
+       $log_entry{name} = $name;
+       $log_entry{email} = $email;
        \%log_entry;
 }
 
@@ -1531,7 +1663,16 @@ sub set_tree {
 
 sub rebuild {
        my ($self) = @_;
-       print "Rebuilding $self->{db_path} ...\n";
+       my $db_path = $self->db_path;
+       if (-f $self->{db_root}) {
+               rename $self->{db_root}, $db_path or die
+                    "rename $self->{db_root} => $db_path failed: $!\n";
+               my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#);
+               symlink $base, $self->{db_root} or die
+                    "symlink $base => $self->{db_root} failed: $!\n";
+               return;
+       }
+       print "Rebuilding $db_path ...\n";
        my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
        my $latest;
        my $full_url = $self->full_url;
@@ -1558,7 +1699,7 @@ sub rebuild {
                print "r$rev = $c\n";
        }
        command_close_pipe($rev_list, $ctx);
-       print "Done rebuilding $self->{db_path}\n";
+       print "Done rebuilding $db_path\n";
 }
 
 # rev_db:
@@ -1574,42 +1715,59 @@ sub rebuild {
 # And yes, it's still pretty fast (faster than Tie::File).
 # These files are disposable unless noMetadata or useSvmProps is set
 
+sub _rev_db_set {
+       my ($fh, $rev, $commit) = @_;
+       my $offset = $rev * 41;
+       # assume that append is the common case:
+       seek $fh, 0, 2 or croak $!;
+       my $pos = tell $fh;
+       if ($pos < $offset) {
+               for (1 .. (($offset - $pos) / 41)) {
+                       print $fh (('0' x 40),"\n") or croak $!;
+               }
+       }
+       seek $fh, $offset, 0 or croak $!;
+       print $fh $commit,"\n" or croak $!;
+}
+
+sub mkfile {
+       my ($path) = @_;
+       unless (-e $path) {
+               my ($dir, $base) = ($path =~ m#^(.*?)/?([^/]+)$#);
+               mkpath([$dir]) unless -d $dir;
+               open my $fh, '>>', $path or die "Couldn't create $path: $!\n";
+               close $fh or die "Couldn't close (create) $path: $!\n";
+       }
+}
+
 sub rev_db_set {
-       my ($self, $rev, $commit, $update_ref) = @_;
-       length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
-       my ($db, $db_lock) = ($self->{db_path}, "$self->{db_path}.lock");
+       my ($self, $rev, $commit, $update_ref, $uuid) = @_;
+       length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
+       my $db = $self->db_path($uuid);
+       my $db_lock = "$db.lock";
        my $sig;
        if ($update_ref) {
                $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
                            $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
        }
+       mkfile($db);
+
        $LOCKFILES{$db_lock} = 1;
        my $sync;
-
        # both of these options make our .rev_db file very, very important
        # and we can't afford to lose it because rebuild() won't work
        if ($self->use_svm_props || $self->no_metadata) {
                $sync = 1;
                copy($db, $db_lock) or die "rev_db_set(@_): ",
-                                          "Failed to copy: ",
+                                          "Failed to copy: ",
                                           "$db => $db_lock ($!)\n";
        } else {
                rename $db, $db_lock or die "rev_db_set(@_): ",
-                                           "Failed to rename: ",
+                                           "Failed to rename: ",
                                            "$db => $db_lock ($!)\n";
        }
-       open my $fh, '+<', $db_lock or croak $!;
-       my $offset = $rev * 41;
-       # assume that append is the common case:
-       seek $fh, 0, 2 or croak $!;
-       my $pos = tell $fh;
-       if ($pos < $offset) {
-               for (1 .. (($offset - $pos) / 41)) {
-                       print $fh (('0' x 40),"\n") or croak $!;
-               }
-       }
-       seek $fh, $offset, 0 or croak $!;
-       print $fh $commit,"\n" or croak $!;
+       open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n";
+       _rev_db_set($fh, $rev, $commit);
        if ($sync) {
                $fh->flush or die "Couldn't flush $db_lock: $!\n";
                $fh->sync or die "Couldn't sync $db_lock: $!\n";
@@ -1631,19 +1789,20 @@ sub rev_db_set {
 
 sub rev_db_max {
        my ($self) = @_;
-       my @stat = stat $self->{db_path} or
-                       die "Couldn't stat $self->{db_path}: $!\n";
-       ($stat[7] % 41) == 0 or
-                       die "$self->{db_path} inconsistent size:$stat[7]\n";
+       my $db_path = $self->db_path;
+       my @stat = stat $db_path or return 0;
+       ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n";
        my $max = $stat[7] / 41;
        (($max > 0) ? $max - 1 : 0);
 }
 
 sub rev_db_get {
-       my ($self, $rev) = @_;
+       my ($self, $rev, $uuid) = @_;
        my $ret;
        my $offset = $rev * 41;
-       open my $fh, '<', $self->{db_path} or croak $!;
+       my $db_path = $self->db_path($uuid);
+       return undef unless -e $db_path;
+       open my $fh, '<', $db_path or croak $!;
        if (sysseek($fh, $offset, 0) == $offset) {
                my $read = sysread($fh, $ret, 40);
                $ret = undef if ($read != 40 || $ret eq ('0'x40));
@@ -1676,13 +1835,16 @@ sub _new {
        my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
        $_[3] = $path = '' unless (defined $path);
        mkpath([$dir]);
-       unless (-f "$dir/.rev_db") {
-               open my $fh, '>>', "$dir/.rev_db" or croak $!;
-               close $fh or croak $!;
-       }
-       bless { ref_id => $ref_id, dir => $dir, index => "$dir/index",
+       bless {
+               ref_id => $ref_id, dir => $dir, index => "$dir/index",
                path => $path, config => "$ENV{GIT_DIR}/svn/config",
-               db_path => "$dir/.rev_db", repo_id => $repo_id }, $class;
+               db_root => "$dir/.rev_db", repo_id => $repo_id }, $class;
+}
+
+sub db_path {
+       my ($self, $uuid) = @_;
+       $uuid ||= $self->ra_uuid;
+       "$self->{db_root}.$uuid";
 }
 
 sub uri_encode {
@@ -2519,11 +2681,6 @@ sub get_commit_editor {
        $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
 }
 
-sub uuid {
-       my ($self) = @_;
-       $self->{uuid} ||= $self->get_uuid;
-}
-
 sub gs_do_update {
        my ($self, $rev_a, $rev_b, $gs, $editor) = @_;
        my $new = ($rev_a == $rev_b);
@@ -2695,6 +2852,10 @@ sub gs_fetch_loop_common {
                        next if defined $gs->rev_db_get($max);
                        $gs->rev_db_set($max, 0 x40);
                }
+               foreach my $g (@$globs) {
+                       my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev";
+                       Git::SVN::tmp_config($k, $max);
+               }
                last if $max >= $head;
                $min = $max + 1;
                $max += $inc;
@@ -2880,8 +3041,25 @@ sub log_use_color {
 }
 
 sub git_svn_log_cmd {
-       my ($r_min, $r_max) = @_;
-       my $gs = Git::SVN->_new;
+       my ($r_min, $r_max, @args) = @_;
+       my $head = 'HEAD';
+       foreach my $x (@args) {
+               last if $x eq '--';
+               next unless ::verify_ref("$x^0");
+               $head = $x;
+               last;
+       }
+
+       my $url;
+       my ($fh, $ctx) = command_output_pipe('rev-list', $head);
+       while (<$fh>) {
+               chomp;
+               $url = (::cmt_metadata($_))[0];
+               last if defined $url;
+       }
+       close $fh; # break the pipe
+
+       my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new;
        my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
                   $gs->refname);
        push @cmd, '-r' unless $non_recursive;
@@ -3074,7 +3252,7 @@ sub cmd_show_log {
        }
 
        config_pager();
-       @args = (git_svn_log_cmd($r_min, $r_max), @args);
+       @args = (git_svn_log_cmd($r_min, $r_max, @args), @args);
        my $log = command_output_pipe(@args);
        run_pager();
        my (@k, $c, $d);
@@ -3140,6 +3318,9 @@ package Git::SVN::Migration;
 #            - info/url may remain for backwards compatibility
 #            - this is what we migrate up to this layout automatically,
 #            - this will be used by git svn init on single branches
+# v3.1 layout (auto migrated):
+#            - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink
+#              for backwards compatibility
 #
 # v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
 #            - this is only created for newly multi-init-ed