git-svn: --follow-parent now works on sub-directories of larger branches
[gitweb.git] / git-svn.perl
index 261e33d02302d68c0d10fc3d3ad5e59d13975e01..123d4d63f425739b59e18f66577e529419cb94ed 100755 (executable)
@@ -13,9 +13,9 @@
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';
 
-use Cwd qw/abs_path/;
-$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
-$ENV{GIT_DIR} = $GIT_DIR;
+$ENV{GIT_DIR} ||= '.git';
+$Git::SVN::default_repo_id = 'git-svn';
+$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
 
 my $LC_ALL = $ENV{LC_ALL};
 $Git::SVN::Log::TZ = $ENV{TZ};
@@ -47,6 +47,7 @@ BEGIN
        foreach (qw/command command_oneline command_noisy command_output_pipe
                    command_input_pipe command_close_pipe/) {
                $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
+                     "*Git::SVN::Migration::$_ = ".
                      "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
        }
        eval $s;
@@ -60,24 +61,21 @@ BEGIN
 my ($_stdin, $_help, $_edit,
        $_repack, $_repack_nr, $_repack_flags,
        $_message, $_file, $_no_metadata,
-       $_template, $_shared, $_no_default_regex, $_no_graft_copy,
-       $_version, $_upgrade, $_branch_all_refs, @_opt_m,
+       $_template, $_shared,
+       $_version, $_upgrade,
        $_merge, $_strategy, $_dry_run,
        $_prefix);
-my (@_branch_from, %tree_map);
-my @repo_path_split_cache;
 
-my %fc_opts = ( 'branch|b=s' => \@_branch_from,
-               'follow-parent|follow' => \$_follow_parent,
-               'branch-all-refs|B' => \$_branch_all_refs,
+my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
+                    'config-dir=s' => \$Git::SVN::Ra::config_dir,
+                    'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache );
+my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent,
                'authors-file|A=s' => \$_authors,
                'repack:i' => \$_repack,
                'no-metadata' => \$_no_metadata,
                'quiet|q' => \$_q,
-               'username=s' => \$Git::SVN::Prompt::_username,
-               'config-dir=s' => \$Git::SVN::Ra::config_dir,
-               'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
-               'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
+               'repack-flags|repack-args|repack-opts=s' => \$_repack_flags,
+               %remote_opts );
 
 my ($_trunk, $_tags, $_branches);
 my %multi_opts = ( 'trunk|T=s' => \$_trunk,
@@ -111,25 +109,20 @@ BEGIN
        rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)",
                        { 'copy-remote|remote=s' => \$_cp_remote,
                          'upgrade' => \$_upgrade } ],
-       'graft-branches' => [ \&graft_branches,
-                       'Detect merges/branches from already imported history',
-                       { 'merge-rx|m' => \@_opt_m,
-                         'branch|b=s' => \@_branch_from,
-                         'branch-all-refs|B' => \$_branch_all_refs,
-                         'no-default-regex' => \$_no_default_regex,
-                         'no-graft-copy' => \$_no_graft_copy } ],
        'multi-init' => [ \&cmd_multi_init,
                        'Initialize multiple trees (like git-svnimport)',
-                       { %multi_opts, %init_opts,
+                       { %multi_opts, %init_opts, %remote_opts,
                         'revision|r=i' => \$_revision,
-                        'username=s' => \$Git::SVN::Prompt::_username,
-                        'config-dir=s' => \$Git::SVN::Ra::config_dir,
-                        'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
                         'prefix=s' => \$_prefix,
                        } ],
        'multi-fetch' => [ \&cmd_multi_fetch,
                        'Fetch multiple trees (like git-svnimport)',
                        \%fc_opts ],
+       'migrate' => [ sub { },
+                      # no-op, we automatically run this anyways,
+                      'Migrate configuration/metadata/layout from
+                       previous versions of git-svn',
+                       \%remote_opts ],
        'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs',
                        { 'limit=i' => \$Git::SVN::Log::limit,
                          'revision|r=s' => \$_revision,
@@ -164,18 +157,23 @@ BEGIN
 read_repo_config(\%opts);
 my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
                                'version|V' => \$_version,
-                               'id|i=s' => \$GIT_SVN);
+                               'minimize-connections' =>
+                                 \$Git::SVN::Migration::_minimize,
+                               'id|i=s' => \$Git::SVN::default_ref_id);
 exit 1 if (!$rv && $cmd ne 'log');
 
-set_default_vals();
 usage(0) if $_help;
 version() if $_version;
 usage(1) unless defined $cmd;
-init_vars();
 load_authors() if $_authors;
-load_all_refs() if $_branch_all_refs;
-migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
-$cmd{$cmd}->[0]->(@ARGV);
+unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) {
+       Git::SVN::Migration::migration_check();
+}
+eval {
+       Git::SVN::verify_remotes_sanity();
+       $cmd{$cmd}->[0]->(@ARGV);
+};
+fatal $@ if $@;
 exit 0;
 
 ####################### primary functions ######################
@@ -215,17 +213,12 @@ sub version {
 
 sub cmd_rebuild {
        my $url = shift;
-       my $gs = $url ? Git::SVN->init(undef, $url)
+       my $gs = $url ? Git::SVN->init($url)
                      : eval { Git::SVN->new };
        $gs ||= Git::SVN->_new;
        if (!verify_ref($gs->refname.'^0')) {
                $gs->copy_remote_ref;
        }
-       if ($_upgrade) {
-               command_noisy('update-ref',$gs->refname, $gs->{id}.'-HEAD');
-       } else {
-               $gs->check_upgrade_needed;
-       }
 
        my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname);
        my $latest;
@@ -250,7 +243,7 @@ sub cmd_rebuild {
                        if (!$gs->{url} && !$url) {
                                fatal "SVN repository location required\n";
                        }
-                       $gs = Git::SVN->init(undef, $url);
+                       $gs = Git::SVN->init($url);
                        $latest = $rev;
                }
                $gs->rev_db_set($rev, $c);
@@ -280,12 +273,17 @@ sub cmd_init {
        }
        do_git_init_db();
 
-       Git::SVN->init(undef, $url);
+       Git::SVN->init($url);
 }
 
 sub cmd_fetch {
+       if (@_) {
+               die "Additional fetch arguments are no longer supported.\n",
+                   "Use --follow-parent if you have moved/copied directories
+                   instead.\n";
+       }
        my $gs = Git::SVN->new;
-       $gs->fetch(@_);
+       $gs->fetch;
        if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) {
                command_noisy(qw(update-ref refs/heads/master),
                              $gs->{last_commit});
@@ -351,13 +349,14 @@ sub cmd_dcommit {
                if ($_dry_run) {
                        print "diff-tree $d~1 $d\n";
                } else {
+                       my $log = get_commit_entry($d)->{log};
                        my $ra = $gs->ra;
                        my $pool = SVN::Pool->new;
                        my %ed_opts = ( r => $last_rev,
                                        ra => $ra->dup,
                                        svn_path => $ra->{svn_path} );
                        my $ed = SVN::Git::Editor->new(\%ed_opts,
-                                        $ra->get_commit_editor($::_message,
+                                        $ra->get_commit_editor($log,
                                         sub { print "Committed r$_[0]\n";
                                               $last_rev = $_[0]; }),
                                         $pool);
@@ -394,40 +393,6 @@ sub cmd_show_ignore {
        $gs->traverse_ignore(\*STDOUT, '', $r);
 }
 
-sub graft_branches {
-       my $gr_file = "$GIT_DIR/info/grafts";
-       my ($grafts, $comments) = read_grafts($gr_file);
-       my $gr_sha1;
-
-       if (%$grafts) {
-               # temporarily disable our grafts file to make this idempotent
-               chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file));
-               rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
-       }
-
-       my $l_map = read_url_paths();
-       my @re = map { qr/$_/is } @_opt_m if @_opt_m;
-       unless ($_no_default_regex) {
-               push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
-                       qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
-                       qr/\b(?:from|of)\s+([\w\.\-]+)/i );
-       }
-       foreach my $u (keys %$l_map) {
-               if (@re) {
-                       foreach my $p (keys %{$l_map->{$u}}) {
-                               graft_merge_msg($grafts,$l_map,$u,$p,@re);
-                       }
-               }
-               unless ($_no_graft_copy) {
-                       graft_file_copy_lib($grafts,$l_map,$u);
-               }
-       }
-       graft_tree_joins($grafts);
-
-       write_grafts($grafts, $comments, $gr_file);
-       unlink "$gr_file~$gr_sha1" if $gr_sha1;
-}
-
 sub cmd_multi_init {
        my $url = shift;
        unless (defined $_trunk || defined $_branches || defined $_tags) {
@@ -435,28 +400,35 @@ sub cmd_multi_init {
        }
        do_git_init_db();
        $_prefix = '' unless defined $_prefix;
+       $url =~ s#/+$## if defined $url;
        if (defined $_trunk) {
-               my $gs_trunk = eval { Git::SVN->new($_prefix . 'trunk') };
+               my $trunk_ref = $_prefix . 'trunk';
+               # try both old-style and new-style lookups:
+               my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
                unless ($gs_trunk) {
-                       my $trunk_url = complete_svn_url($url, $_trunk);
-                       $gs_trunk = Git::SVN->init($_prefix . 'trunk',
-                                                  $trunk_url);
-                       command_noisy('config', 'svn.trunk', $trunk_url);
+                       my ($trunk_url, $trunk_path) =
+                                             complete_svn_url($url, $_trunk);
+                       $gs_trunk = Git::SVN->init($trunk_url, $trunk_path,
+                                                  undef, $trunk_ref);
                }
        }
+       return unless defined $_branches || defined $_tags;
        my $ra = $url ? Git::SVN::Ra->new($url) : undef;
        complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
        complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
 }
 
 sub cmd_multi_fetch {
-       # try to do trunk first, since branches/tags
-       # may be descended from it.
-       if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") {
-               my $gs = Git::SVN->new('trunk');
-               $gs->fetch(@_);
+       my @gs;
+       foreach (command(qw/config -l/)) {
+               next unless m!^svn-remote\.(.+)\.fetch=
+                             \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
+               my ($repo_id, $path, $ref_id) = ($1, $2, $3);
+               push @gs, Git::SVN->new($ref_id, $repo_id, $path);
+       }
+       foreach (@gs) {
+               $_->fetch;
        }
-       rec_fetch('', "$ENV{GIT_DIR}/svn", @_);
 }
 
 # this command is special because it requires no metadata
@@ -510,244 +482,48 @@ sub cmd_commit_diff {
 
 ########################### utility functions #########################
 
-sub rec_fetch {
-       my ($pfx, $p, @args) = @_;
-       my @dir;
-       foreach (sort <$p/*>) {
-               if (-r "$_/info/url") {
-                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
-                       my $id = $pfx . basename $_;
-                       next if $id eq 'trunk';
-                       my $gs = Git::SVN->new($id);
-                       $gs->fetch(@args);
-               } elsif (-d $_) {
-                       push @dir, $_;
-               }
-       }
-       foreach (@dir) {
-               my $x = $_;
-               $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
-               rec_fetch($x, $_, @args);
-       }
-}
-
 sub complete_svn_url {
        my ($url, $path) = @_;
        $path =~ s#/+$##;
-       $url =~ s#/+$## if $url;
        if ($path !~ m#^[a-z\+]+://#) {
-               $path = '/' . $path if ($path !~ m#^/#);
                if (!defined $url || $url !~ m#^[a-z\+]+://#) {
                        fatal("E: '$path' is not a complete URL ",
                              "and a separate URL is not specified\n");
                }
-               $path = $url . $path;
+               return ($url, $path);
        }
-       return $path;
+       return ($path, '');
 }
 
 sub complete_url_ls_init {
-       my ($ra, $path, $switch, $pfx) = @_;
-       unless ($path) {
+       my ($ra, $repo_path, $switch, $pfx) = @_;
+       unless ($repo_path) {
                print STDERR "W: $switch not specified\n";
                return;
        }
-       $path =~ s#/+$##;
-       if ($path =~ m#^[a-z\+]+://#) {
-               $ra = Git::SVN::Ra->new($path);
-               $path = '';
+       $repo_path =~ s#/+$##;
+       if ($repo_path =~ m#^[a-z\+]+://#) {
+               $ra = Git::SVN::Ra->new($repo_path);
+               $repo_path = '';
        } else {
-               $path =~ s#^/+##;
+               $repo_path =~ s#^/+##;
                unless ($ra) {
-                       fatal("E: '$path' is not a complete URL ",
+                       fatal("E: '$repo_path' is not a complete URL ",
                              "and a separate URL is not specified\n");
                }
        }
        my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
-       my ($dirent, undef, undef) = $ra->get_dir($path, $r);
-       my $url = $ra->{url} . (length $path ? "/$path" : '');
+       my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r);
+       my $url = $ra->{url};
        foreach my $d (sort keys %$dirent) {
                next if ($dirent->{$d}->kind != $SVN::Node::dir);
-               my $u =  "$url/$d";
-               my $id = "$pfx$d";
-               my $gs = eval { Git::SVN->new($id) };
+               my $path =  "$repo_path/$d";
+               my $ref = "$pfx$d";
+               my $gs = eval { Git::SVN->new($ref) };
                # don't try to init already existing refs
                unless ($gs) {
-                       print "init $u => $id\n";
-                       Git::SVN->init($id, $u);
-               }
-       }
-       my ($n) = ($switch =~ /^--(\w+)/);
-       command_noisy('config', "svn.$n", $url);
-}
-
-sub common_prefix {
-       my $paths = shift;
-       my %common;
-       foreach (@$paths) {
-               my @tmp = split m#/#, $_;
-               my $p = '';
-               while (my $x = shift @tmp) {
-                       $p .= "/$x";
-                       $common{$p} ||= 0;
-                       $common{$p}++;
-               }
-       }
-       foreach (sort {length $b <=> length $a} keys %common) {
-               if ($common{$_} == @$paths) {
-                       return $_;
-               }
-       }
-       return '';
-}
-
-# grafts set here are 'stronger' in that they're based on actual tree
-# matches, and won't be deleted from merge-base checking in write_grafts()
-sub graft_tree_joins {
-       my $grafts = shift;
-       map_tree_joins() if (@_branch_from && !%tree_map);
-       return unless %tree_map;
-
-       git_svn_each(sub {
-               my $i = shift;
-               my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i");
-               my ($fh, $ctx) = command_output_pipe(@args);
-               while (<$fh>) {
-                       next unless /^commit ($sha1)$/o;
-                       my $c = $1;
-                       my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
-                       next unless $tree_map{$t};
-
-                       my $l;
-                       do {
-                               $l = readline $fh;
-                       } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
-
-                       my ($s, $tz) = ($1, $2);
-                       if ($tz =~ s/^\+//) {
-                               $s += tz_to_s_offset($tz);
-                       } elsif ($tz =~ s/^\-//) {
-                               $s -= tz_to_s_offset($tz);
-                       }
-
-                       my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
-
-                       foreach my $p (@{$tree_map{$t}}) {
-                               next if $p eq $c;
-                               my $mb = eval { command('merge-base', $c, $p) };
-                               next unless ($@ || $?);
-                               if (defined $r_a) {
-                                       # see if SVN says it's a relative
-                                       my ($url_b, $r_b, $uuid_b) =
-                                                       cmt_metadata($p);
-                                       next if (defined $url_b &&
-                                                       defined $url_a &&
-                                                       ($url_a eq $url_b) &&
-                                                       ($uuid_a eq $uuid_b));
-                                       if ($uuid_a eq $uuid_b) {
-                                               if ($r_b < $r_a) {
-                                                       $grafts->{$c}->{$p} = 2;
-                                                       next;
-                                               } elsif ($r_b > $r_a) {
-                                                       $grafts->{$p}->{$c} = 2;
-                                                       next;
-                                               }
-                                       }
-                               }
-                               my $ct = get_commit_time($p);
-                               if ($ct < $s) {
-                                       $grafts->{$c}->{$p} = 2;
-                               } elsif ($ct > $s) {
-                                       $grafts->{$p}->{$c} = 2;
-                               }
-                               # what should we do when $ct == $s ?
-                       }
-               }
-               command_close_pipe($fh, $ctx);
-       });
-}
-
-sub graft_file_copy_lib {
-       my ($grafts, $l_map, $u) = @_;
-       my $tree_paths = $l_map->{$u};
-       my $pfx = common_prefix([keys %$tree_paths]);
-       my ($repo, $path) = repo_path_split($u.$pfx);
-       $SVN = Git::SVN::Ra->new($repo);
-
-       my ($base, $head) = libsvn_parse_revision();
-       my $inc = 1000;
-       my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
-       my $eh = $SVN::Error::handler;
-       $SVN::Error::handler = \&libsvn_skip_unknown_revs;
-       while (1) {
-               $SVN->dup->get_log([$path], $min, $max, 0, 2, 1,
-                       sub {
-                               libsvn_graft_file_copies($grafts, $tree_paths,
-                                                       $path, @_);
-                       });
-               last if ($max >= $head);
-               $min = $max + 1;
-               $max += $inc;
-               $max = $head if ($max > $head);
-       }
-       $SVN::Error::handler = $eh;
-}
-
-sub process_merge_msg_matches {
-       my ($grafts, $l_map, $u, $p, $c, @matches) = @_;
-       my (@strong, @weak);
-       foreach (@matches) {
-               # merging with ourselves is not interesting
-               next if $_ eq $p;
-               if ($l_map->{$u}->{$_}) {
-                       push @strong, $_;
-               } else {
-                       push @weak, $_;
-               }
-       }
-       foreach my $w (@weak) {
-               last if @strong;
-               # no exact match, use branch name as regexp.
-               my $re = qr/\Q$w\E/i;
-               foreach (keys %{$l_map->{$u}}) {
-                       if (/$re/) {
-                               push @strong, $l_map->{$u}->{$_};
-                               last;
-                       }
-               }
-               last if @strong;
-               $w = basename($w);
-               $re = qr/\Q$w\E/i;
-               foreach (keys %{$l_map->{$u}}) {
-                       if (/$re/) {
-                               push @strong, $l_map->{$u}->{$_};
-                               last;
-                       }
-               }
-       }
-       my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+)
-                                       \s(?:[a-f\d\-]+)$/xsm);
-       unless (defined $rev) {
-               ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+)
-                                       \@(?:[a-f\d\-]+)/xsm);
-               return unless defined $rev;
-       }
-       foreach my $m (@strong) {
-               my ($r0, $s0) = find_rev_before($rev, $m, 1);
-               $grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
-       }
-}
-
-sub graft_merge_msg {
-       my ($grafts, $l_map, $u, $p, @re) = @_;
-
-       my $x = $l_map->{$u}->{$p};
-       my $rl = rev_list_raw("refs/remotes/$x");
-       while (my $c = next_rev_list_entry($rl)) {
-               foreach my $re (@re) {
-                       my (@br) = ($c->{m} =~ /$re/g);
-                       next unless @br;
-                       process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);
+                       print "init $url/$path => $ref\n";
+                       Git::SVN->init($url, $path, undef, $ref);
                }
        }
 }
@@ -758,34 +534,6 @@ sub verify_ref {
                               { STDERR => 0 }); };
 }
 
-sub repo_path_split {
-       my $full_url = shift;
-       $full_url =~ s#/+$##;
-
-       foreach (@repo_path_split_cache) {
-               if ($full_url =~ s#$_##) {
-                       my $u = $1;
-                       $full_url =~ s#^/+##;
-                       return ($u, $full_url);
-               }
-       }
-       my $tmp = Git::SVN::Ra->new($full_url);
-       return ($tmp->{repos_root}, $tmp->{svn_path});
-}
-
-sub setup_git_svn {
-       defined $SVN_URL or croak "SVN repository location required\n";
-       unless (-d $GIT_DIR) {
-               croak "GIT_DIR=$GIT_DIR does not exist!\n";
-       }
-       mkpath([$GIT_SVN_DIR]);
-       mkpath(["$GIT_SVN_DIR/info"]);
-       open my $fh, '>>',$REVDB or croak $!;
-       close $fh;
-       s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
-
-}
-
 sub get_tree_from_treeish {
        my ($treeish) = @_;
        # $treeish can be a symbolic ref, too:
@@ -807,58 +555,6 @@ sub get_tree_from_treeish {
        return $expected;
 }
 
-sub get_diff {
-       my ($from, $treeish) = @_;
-       print "diff-tree $from $treeish\n";
-       my @diff_tree = qw(diff-tree -z -r);
-       if ($_cp_similarity) {
-               push @diff_tree, "-C$_cp_similarity";
-       } else {
-               push @diff_tree, '-C';
-       }
-       push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
-       push @diff_tree, "-l$_l" if defined $_l;
-       push @diff_tree, $from, $treeish;
-       my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
-       local $/ = "\0";
-       my $state = 'meta';
-       my @mods;
-       while (<$diff_fh>) {
-               chomp $_; # this gets rid of the trailing "\0"
-               if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
-                                       $sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
-                       push @mods, {   mode_a => $1, mode_b => $2,
-                                       sha1_b => $3, chg => $4 };
-                       if ($4 =~ /^(?:C|R)$/) {
-                               $state = 'file_a';
-                       } else {
-                               $state = 'file_b';
-                       }
-               } elsif ($state eq 'file_a') {
-                       my $x = $mods[$#mods] or croak "Empty array\n";
-                       if ($x->{chg} !~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       $x->{file_a} = $_;
-                       $state = 'file_b';
-               } elsif ($state eq 'file_b') {
-                       my $x = $mods[$#mods] or croak "Empty array\n";
-                       if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       $x->{file_b} = $_;
-                       $state = 'meta';
-               } else {
-                       croak "Error parsing $_\n";
-               }
-       }
-       command_close_pipe($diff_fh, $ctx);
-       return \@mods;
-}
-
 sub get_commit_entry {
        my ($treeish) = shift;
        my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) );
@@ -899,34 +595,6 @@ sub get_commit_entry {
        \%log_entry;
 }
 
-sub rev_list_raw {
-       my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_);
-       return { fh => $fh, ctx => $c, t => { } };
-}
-
-sub next_rev_list_entry {
-       my $rl = shift;
-       my $fh = $rl->{fh};
-       my $x = $rl->{t};
-       while (<$fh>) {
-               if (/^commit ($sha1)$/o) {
-                       if ($x->{c}) {
-                               $rl->{t} = { c => $1 };
-                               return $x;
-                       } else {
-                               $x->{c} = $1;
-                       }
-               } elsif (/^parent ($sha1)$/o) {
-                       $x->{p}->{$1} = 1;
-               } elsif (s/^    //) {
-                       $x->{m} ||= '';
-                       $x->{m} .= $_;
-               }
-       }
-       command_close_pipe($fh, $rl->{ctx});
-       return ($x != $rl->{t}) ? $x : undef;
-}
-
 sub s_to_file {
        my ($str, $file, $mode) = @_;
        open my $fd,'>',$file or croak $!;
@@ -945,60 +613,6 @@ sub file_to_s {
        return $ret;
 }
 
-sub check_upgrade_needed {
-       if (!-r $REVDB) {
-               -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-               open my $fh, '>>',$REVDB or croak $!;
-               close $fh;
-       }
-       return unless eval {
-               command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"],
-                       {STDERR => 0});
-       };
-       my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") };
-       if ($@ || !$head) {
-               print STDERR "Please run: $0 rebuild --upgrade\n";
-               exit 1;
-       }
-}
-
-# fills %tree_map with a reverse mapping of trees to commits.  Useful
-# for finding parents to commit on.
-sub map_tree_joins {
-       my %seen;
-       foreach my $br (@_branch_from) {
-               my $pipe = command_output_pipe(qw/rev-list
-                                           --topo-order --pretty=raw/, $br);
-               while (<$pipe>) {
-                       if (/^commit ($sha1)$/o) {
-                               my $commit = $1;
-
-                               # if we've seen a commit,
-                               # we've seen its parents
-                               last if $seen{$commit};
-                               my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
-                               unless (defined $tree) {
-                                       die "Failed to parse commit $commit\n";
-                               }
-                               push @{$tree_map{$tree}}, $commit;
-                               $seen{$commit} = 1;
-                       }
-               }
-               close $pipe;
-       }
-}
-
-sub load_all_refs {
-       if (@_branch_from) {
-               print STDERR '--branch|-b parameters are ignored when ',
-                       "--branch-all-refs|-B is passed\n";
-       }
-
-       # don't worry about rev-list on non-commit objects/tags,
-       # it shouldn't blow up if a ref is a blob or tree...
-       @_branch_from = command(qw/rev-parse --symbolic --all/);
-}
-
 # '<svn username> = real-name <email address>' mapping based on git-svnimport:
 sub load_authors {
        open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
@@ -1016,90 +630,9 @@ sub load_authors {
        close $authors or croak $!;
 }
 
-sub git_svn_each {
-       my $sub = shift;
-       foreach (command(qw/rev-parse --symbolic --all/)) {
-               next unless s#^refs/remotes/##;
-               chomp $_;
-               next unless -f "$GIT_DIR/svn/$_/info/url";
-               &$sub($_);
-       }
-}
-
-sub migrate_revdb {
-       git_svn_each(sub {
-               my $id = shift;
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       exit 0 if -r $REVDB;
-                       print "Upgrading svn => git mapping...\n";
-                       -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-                       open my $fh, '>>',$REVDB or croak $!;
-                       close $fh;
-                       rebuild();
-                       print "Done upgrading. You may now delete the ",
-                               "deprecated $GIT_SVN_DIR/revs directory\n";
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-       });
-}
-
-sub migration_check {
-       migrate_revdb() unless (-e $REVDB);
-       return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
-       print "Upgrading repository...\n";
-       unless (-d "$GIT_DIR/svn") {
-               mkdir "$GIT_DIR/svn" or croak $!;
-       }
-       print "Data from a previous version of git-svn exists, but\n\t",
-                               "$GIT_SVN_DIR\n\t(required for this version ",
-                               "($VERSION) of git-svn) does not.\n";
-
-       foreach my $x (command(qw/rev-parse --symbolic --all/)) {
-               next unless $x =~ s#^refs/remotes/##;
-               chomp $x;
-               next unless -f "$GIT_DIR/$x/info/url";
-               my $u = eval { file_to_s("$GIT_DIR/$x/info/url") };
-               next unless $u;
-               my $dn = dirname("$GIT_DIR/svn/$x");
-               mkpath([$dn]) unless -d $dn;
-               rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
-       }
-       migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
-       print "Done upgrading.\n";
-}
-
-sub find_rev_before {
-       my ($r, $id, $eq_ok) = @_;
-       my $f = "$GIT_DIR/svn/$id/.rev_db";
-       return (undef,undef) unless -r $f;
-       --$r unless $eq_ok;
-       while ($r > 0) {
-               if (my $c = revdb_get($f, $r)) {
-                       return ($r, $c);
-               }
-               --$r;
-       }
-       return (undef, undef);
-}
-
-sub init_vars {
-       $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
-       $Git::SVN::default = $GIT_SVN;
-       $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
-       $REVDB = "$GIT_SVN_DIR/.rev_db";
-       $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
-       $SVN_URL = undef;
-       %tree_map = ();
-}
-
 # convert GetOpt::Long specs for use by git-config
 sub read_repo_config {
-       return unless -d $GIT_DIR;
+       return unless -d $ENV{GIT_DIR};
        my $opts = shift;
        foreach my $o (keys %$opts) {
                my $v = $opts->{$o};
@@ -1120,127 +653,6 @@ sub read_repo_config {
        }
 }
 
-sub set_default_vals {
-       if (defined $_repack) {
-               $_repack = 1000 if ($_repack <= 0);
-               $_repack_nr = $_repack;
-               $_repack_flags ||= '-d';
-       }
-}
-
-sub read_grafts {
-       my $gr_file = shift;
-       my ($grafts, $comments) = ({}, {});
-       if (open my $fh, '<', $gr_file) {
-               my @tmp;
-               while (<$fh>) {
-                       if (/^($sha1)\s+/) {
-                               my $c = $1;
-                               if (@tmp) {
-                                       @{$comments->{$c}} = @tmp;
-                                       @tmp = ();
-                               }
-                               foreach my $p (split /\s+/, $_) {
-                                       $grafts->{$c}->{$p} = 1;
-                               }
-                       } else {
-                               push @tmp, $_;
-                       }
-               }
-               close $fh or croak $!;
-               @{$comments->{'END'}} = @tmp if @tmp;
-       }
-       return ($grafts, $comments);
-}
-
-sub write_grafts {
-       my ($grafts, $comments, $gr_file) = @_;
-
-       open my $fh, '>', $gr_file or croak $!;
-       foreach my $c (sort keys %$grafts) {
-               if ($comments->{$c}) {
-                       print $fh $_ foreach @{$comments->{$c}};
-               }
-               my $p = $grafts->{$c};
-               my %x; # real parents
-               delete $p->{$c}; # commits are not self-reproducing...
-               my $ch = command_output_pipe(qw/cat-file commit/, $c);
-               while (<$ch>) {
-                       if (/^parent ($sha1)/) {
-                               $x{$1} = $p->{$1} = 1;
-                       } else {
-                               last unless /^\S/;
-                       }
-               }
-               close $ch; # breaking the pipe
-
-               # if real parents are the only ones in the grafts, drop it
-               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-               my (@ip, @jp, $mb);
-               my %del = %x;
-               @ip = @jp = keys %$p;
-               foreach my $i (@ip) {
-                       next if $del{$i} || $p->{$i} == 2;
-                       foreach my $j (@jp) {
-                               next if $i eq $j || $del{$j} || $p->{$j} == 2;
-                               $mb = eval { command('merge-base', $i, $j) };
-                               next unless $mb;
-                               chomp $mb;
-                               next if $x{$mb};
-                               if ($mb eq $j) {
-                                       delete $p->{$i};
-                                       $del{$i} = 1;
-                               } elsif ($mb eq $i) {
-                                       delete $p->{$j};
-                                       $del{$j} = 1;
-                               }
-                       }
-               }
-
-               # if real parents are the only ones in the grafts, drop it
-               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-               print $fh $c, ' ', join(' ', sort keys %$p),"\n";
-       }
-       if ($comments->{'END'}) {
-               print $fh $_ foreach @{$comments->{'END'}};
-       }
-       close $fh or croak $!;
-}
-
-sub read_url_paths_all {
-       my ($l_map, $pfx, $p) = @_;
-       my @dir;
-       foreach (<$p/*>) {
-               if (-r "$_/info/url") {
-                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
-                       my $id = $pfx . basename $_;
-                       my $url = file_to_s("$_/info/url");
-                       my ($u, $p) = repo_path_split($url);
-                       $l_map->{$u}->{$p} = $id;
-               } elsif (-d $_) {
-                       push @dir, $_;
-               }
-       }
-       foreach (@dir) {
-               my $x = $_;
-               $x =~ s!^\Q$GIT_DIR\E/svn/!!o;
-               read_url_paths_all($l_map, $x, $_);
-       }
-}
-
-# this one only gets ids that have been imported, not new ones
-sub read_url_paths {
-       my $l_map = {};
-       git_svn_each(sub { my $x = shift;
-                       my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
-                       my ($u, $p) = repo_path_split($url);
-                       $l_map->{$u}->{$p} = $x;
-                       });
-       return $l_map;
-}
-
 sub extract_metadata {
        my $id = shift or return (undef, undef, undef);
        my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
@@ -1284,7 +696,7 @@ sub tz_to_s_offset {
 package Git::SVN;
 use strict;
 use warnings;
-use vars qw/$default/;
+use vars qw/$default_repo_id $default_ref_id/;
 use Carp qw/croak/;
 use File::Path qw/mkpath/;
 use IPC::Open3;
@@ -1300,34 +712,173 @@ BEGIN
                                        svn:entry:committed-date/;
 }
 
+sub read_all_remotes {
+       my $r = {};
+       foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
+               if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
+                       $r->{$1}->{fetch}->{$2} = $3;
+               } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
+                       $r->{$1}->{url} = $2;
+               }
+       }
+       $r;
+}
+
+sub verify_remotes_sanity {
+       return unless -d $ENV{GIT_DIR};
+       my %seen;
+       foreach (command(qw/config -l/)) {
+               if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) {
+                       if ($seen{$1}) {
+                               die "Remote ref refs/remote/$1 is tracked by",
+                                   "\n  \"$_\"\nand\n  \"$seen{$1}\"\n",
+                                   "Please resolve this ambiguity in ",
+                                   "your git configuration file before ",
+                                   "continuing\n";
+                       }
+                       $seen{$1} = $_;
+               }
+       }
+}
+
+# we allow more chars than remotes2config.sh...
+sub sanitize_remote_name {
+       my ($name) = @_;
+       $name =~ tr{A-Za-z0-9:,/+-}{.}c;
+       $name;
+}
+
+sub find_existing_remote {
+       my ($url, $remotes) = @_;
+       my $existing;
+       foreach my $repo_id (keys %$remotes) {
+               my $u = $remotes->{$repo_id}->{url} or next;
+               next if $u ne $url;
+               $existing = $repo_id;
+               last;
+       }
+       $existing;
+}
+
+sub init_remote_config {
+       my ($self, $url) = @_;
+       $url =~ s!/+$!!; # strip trailing slash
+       my $r = read_all_remotes();
+       my $existing = find_existing_remote($url, $r);
+       if ($existing) {
+               print STDERR "Using existing ",
+                            "[svn-remote \"$existing\"]\n";
+               $self->{repo_id} = $existing;
+       } else {
+               my $min_url = Git::SVN::Ra->new($url)->minimize_url;
+               $existing = find_existing_remote($min_url, $r);
+               if ($existing) {
+                       print STDERR "Using existing ",
+                                    "[svn-remote \"$existing\"]\n";
+                       $self->{repo_id} = $existing;
+               }
+               if ($min_url ne $url) {
+                       print STDERR "Using higher level of URL: ",
+                                    "$url => $min_url\n";
+                       my $old_path = $self->{path};
+                       $self->{path} = $url;
+                       $self->{path} =~ s!^\Q$min_url\E/*!!;
+                       if (length $old_path) {
+                               $self->{path} .= "/$old_path";
+                       }
+                       $url = $min_url;
+               }
+       }
+       my $orig_url;
+       if (!$existing) {
+               # verify that we aren't overwriting anything:
+               $orig_url = eval {
+                       command_oneline('config', '--get',
+                                       "svn-remote.$self->{repo_id}.url")
+               };
+               if ($orig_url && ($orig_url ne $url)) {
+                       die "svn-remote.$self->{repo_id}.url already set: ",
+                           "$orig_url\nwanted to set to: $url\n";
+               }
+       }
+       my ($xrepo_id, $xpath) = find_ref($self->refname);
+       if (defined $xpath) {
+               die "svn-remote.$xrepo_id.fetch already set to track ",
+                   "$xpath:refs/remotes/", $self->refname, "\n";
+       }
+       command_noisy('config',
+                     "svn-remote.$self->{repo_id}.url", $url);
+       command_noisy('config', '--add',
+                     "svn-remote.$self->{repo_id}.fetch",
+                     "$self->{path}:".$self->refname);
+       $self->{url} = $url;
+}
+
 sub init {
-       my ($class, $id, $url) = @_;
-       my $self = _new($class, $id);
-       mkpath(["$self->{dir}/info"]);
+       my ($class, $url, $path, $repo_id, $ref_id) = @_;
+       my $self = _new($class, $repo_id, $ref_id, $path);
        if (defined $url) {
-               $url =~ s!/+$!!; # strip trailing slash
-               ::s_to_file($url, "$self->{dir}/info/url");
+               $self->init_remote_config($url);
        }
-       $self->{url} = $url;
-       open my $fh, '>>', $self->{db_path} or croak $!;
-       close $fh or croak $!;
        $self;
 }
 
+sub find_ref {
+       my ($ref_id) = @_;
+       foreach (command(qw/config -l/)) {
+               next unless m!^svn-remote\.(.+)\.fetch=
+                             \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
+               my ($repo_id, $path, $ref) = ($1, $2, $3);
+               if ($ref eq $ref_id) {
+                       $path = '' if ($path =~ m#^\./?#);
+                       return ($repo_id, $path);
+               }
+       }
+       (undef, undef, undef);
+}
+
 sub new {
-       my ($class, $id) = @_;
-       my $self = _new($class, $id);
-       $self->{url} = ::file_to_s("$self->{dir}/info/url");
+       my ($class, $ref_id, $repo_id, $path) = @_;
+       if (defined $ref_id && !defined $repo_id && !defined $path) {
+               ($repo_id, $path) = find_ref($ref_id);
+               if (!defined $repo_id) {
+                       die "Could not find a \"svn-remote.*.fetch\" key ",
+                           "in the repository configuration matching: ",
+                           "refs/remotes/$ref_id\n";
+               }
+       }
+       my $self = _new($class, $repo_id, $ref_id, $path);
+       if (!defined $self->{path} || !length $self->{path}) {
+               my $fetch = command_oneline('config', '--get',
+                                           "svn-remote.$repo_id.fetch",
+                                           ":refs/remotes/$ref_id\$") or
+                    die "Failed to read \"svn-remote.$repo_id.fetch\" ",
+                        "\":refs/remotes/$ref_id\$\" in config\n";
+               ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
+       }
+       $self->{url} = command_oneline('config', '--get',
+                                      "svn-remote.$repo_id.url") or
+                  die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
        $self;
 }
 
-sub refname { "refs/remotes/$_[0]->{id}" }
+sub refname { "refs/remotes/$_[0]->{ref_id}" }
 
 sub ra {
        my ($self) = shift;
        $self->{ra} ||= Git::SVN::Ra->new($self->{url});
 }
 
+sub rel_path {
+       my ($self) = @_;
+       my $repos_root = $self->ra->{repos_root};
+       return $self->{path} if ($self->{url} eq $repos_root);
+       my $url = $self->{url} .
+                 (length $self->{path} ? "/$self->{path}" : $self->{path});
+       $url =~ s!^\Q$repos_root\E/*!!g;
+       $url;
+}
+
 sub copy_remote_ref {
        my ($self) = @_;
        my $origin = $::_cp_remote ? $::_cp_remote : 'origin';
@@ -1370,7 +921,7 @@ sub last_rev_commit {
                return ($self->{last_rev}, $self->{last_commit});
        }
        my $c = ::verify_ref($self->refname.'^0');
-       if (defined $c && length $c) {
+       if ($c) {
                my $rev = (::cmt_metadata($c))[1];
                if (defined $rev) {
                        ($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
@@ -1482,18 +1033,9 @@ sub get_commit_parents {
        @ret;
 }
 
-sub check_upgrade_needed {
+sub full_url {
        my ($self) = @_;
-       if (!-r $self->{db_path}) {
-               -d $self->{dir} or mkpath([$self->{dir}]);
-               open my $fh, '>>', $self->{db_path} or croak $!;
-               close $fh;
-       }
-       return unless ::verify_ref($self->{id}.'-HEAD^0');
-       my $head = ::verify_ref($self->refname.'^0');
-       if ($@ || !$head) {
-               ::fatal("Please run: $0 rebuild --upgrade\n");
-       }
+       $self->ra->{url} . (length $self->{path} ? '/' . $self->{path} : '');
 }
 
 sub do_git_commit {
@@ -1523,7 +1065,7 @@ sub do_git_commit {
        defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
                                                                   or croak $!;
        print $msg_fh $log_entry->{log} or croak $!;
-       print $msg_fh "\ngit-svn-id: ", $self->ra->{url}, '@',
+       print $msg_fh "\ngit-svn-id: ", $self->full_url, '@',
                      $log_entry->{revision}, ' ',
                      $self->ra->uuid, "\n" or croak $!;
        $msg_fh->flush == 0 or croak $!;
@@ -1545,18 +1087,128 @@ sub do_git_commit {
        return $commit;
 }
 
+sub revisions_eq {
+       my ($self, $r0, $r1) = @_;
+       return 1 if $r0 == $r1;
+       my $nr = 0;
+       $self->ra->get_log([$self->{path}], $r0, $r1,
+                          0, 0, 1, sub { $nr++ });
+       return 0 if ($nr > 1);
+       return 1;
+}
+
+sub find_parent_branch {
+       my ($self, $paths, $rev) = @_;
+       return undef unless $::_follow_parent;
+
+       # look for a parent from another branch:
+       my @b_path_components = split m#/#, $self->rel_path;
+       my @a_path_components;
+       my $i;
+       while (@b_path_components) {
+               $i = $paths->{'/'.join('/', @b_path_components)};
+               last if $i;
+               unshift(@a_path_components, pop(@b_path_components));
+       }
+       goto not_found unless defined $i;
+       my $branch_from = $i->copyfrom_path or goto not_found;
+       if (@a_path_components) {
+               print STDERR "branch_from: $branch_from => ";
+               $branch_from .= '/'.join('/', @a_path_components);
+               print STDERR $branch_from, "\n";
+       }
+       my $r = $i->copyfrom_rev;
+       my $repos_root = $self->ra->{repos_root};
+       my $url = $self->ra->{url};
+       my $new_url = $repos_root . $branch_from;
+       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 my $f (keys %$fetch) {
+                       next if $f ne $branch_from;
+                       $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f);
+                       last;
+               }
+               last if $gs;
+       }
+       unless ($gs) {
+               my $ref_id = $branch_from;
+               $ref_id .= "\@$r" if find_ref($ref_id);
+               # just grow a tail if we're not unique enough :x
+               $ref_id .= '-' while find_ref($ref_id);
+               $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id);
+       }
+       my ($r0, $parent) = $gs->find_rev_before($r, 1);
+       if ($::_follow_parent && (!defined $r0 || !defined $parent)) {
+               $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub {
+                       my ($paths, $rev) = @_;
+                       my $log_entry = eval { $gs->do_fetch($paths, $rev) };
+                       $gs->do_git_commit($log_entry) if $log_entry;
+               });
+               ($r0, $parent) = $gs->last_rev_commit;
+       }
+       if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) {
+               print STDERR "Found branch parent: ($self->{ref_id}) $parent\n";
+               $self->assert_index_clean($parent);
+               my $ed;
+               if ($self->ra->can_do_switch) {
+                       print STDERR "Following parent with do_switch\n";
+                       # do_switch works with svn/trunk >= r22312, but that
+                       # is not included with SVN 1.4.2 (the latest version
+                       # at the moment), so we can't rely on it
+                       $self->{last_commit} = $parent;
+                       $ed = SVN::Git::Fetcher->new($self);
+                       $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1,
+                                             $self->full_url, $ed)
+                         or die "SVN connection failed somewhere...\n";
+               } else {
+                       print STDERR "Following parent with do_update\n";
+                       $ed = SVN::Git::Fetcher->new($self);
+                       $self->ra->gs_do_update($rev, $rev, $self->{path},
+                                               1, $ed)
+                         or die "SVN connection failed somewhere...\n";
+               }
+               return $self->make_log_entry($rev, [$parent], $ed);
+       }
+not_found:
+       print STDERR "Branch parent for path: '/",
+                    $self->rel_path, "' not found\n";
+       return undef unless $paths;
+       foreach my $x (sort keys %$paths) {
+               my $p = $paths->{$x};
+               print STDERR '  ', $p->action, '  ', $x;
+               if (my $cp_from = $p->copyfrom_path) {
+                       print STDERR "(from $cp_from:", $p->copyfrom_rev, ')';
+               }
+               print STDERR "\n";
+       }
+       return undef;
+}
+
 sub do_fetch {
-       my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_;
-       my $ed = SVN::Git::Fetcher->new($self);
+       my ($self, $paths, $rev) = @_;
+       my $ed;
        my ($last_rev, @parents);
        if ($self->{last_commit}) {
+               $ed = SVN::Git::Fetcher->new($self);
                $last_rev = $self->{last_rev};
                $ed->{c} = $self->{last_commit};
                @parents = ($self->{last_commit});
        } else {
                $last_rev = $rev;
+               if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
+                       return $log_entry;
+               }
+               $ed = SVN::Git::Fetcher->new($self);
        }
-       unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) {
+       unless ($self->ra->gs_do_update($last_rev, $rev,
+                                       $self->{path}, 1, $ed)) {
                die "SVN connection failed somewhere...\n";
        }
        $self->make_log_entry($rev, \@parents, $ed);
@@ -1664,11 +1316,11 @@ sub fetch {
        $SVN::Error::handler = \&skip_unknown_revs;
        while (1) {
                my @revs;
-               $self->ra->get_log([''], $min, $max, 0, 1, 1, sub {
+               $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub {
                        my ($paths, $rev, $author, $date, $log) = @_;
-                       push @revs, $rev });
+                       push @revs, [ $paths, $rev ] });
                foreach (@revs) {
-                       my $log_entry = $self->do_fetch(undef, $_);
+                       my $log_entry = $self->do_fetch(@$_);
                        $self->do_git_commit($log_entry, @parents);
                }
                last if $max >= $head;
@@ -1778,12 +1430,37 @@ sub rev_db_get {
        $ret;
 }
 
+sub find_rev_before {
+       my ($self, $rev, $eq_ok) = @_;
+       --$rev unless $eq_ok;
+       while ($rev > 0) {
+               if (my $c = $self->rev_db_get($rev)) {
+                       return ($rev, $c);
+               }
+               --$rev;
+       }
+       return (undef, undef);
+}
+
 sub _new {
-       my ($class, $id) = @_;
-       $id ||= $Git::SVN::default;
-       my $dir = "$ENV{GIT_DIR}/svn/$id";
-       bless { id => $id, dir => $dir, index => "$dir/index",
-               db_path => "$dir/.rev_db" }, $class;
+       my ($class, $repo_id, $ref_id, $path) = @_;
+       unless (defined $repo_id && length $repo_id) {
+               $repo_id = $Git::SVN::default_repo_id;
+       }
+       unless (defined $ref_id && length $ref_id) {
+               $_[2] = $ref_id = $Git::SVN::default_ref_id;
+       }
+       $_[1] = $repo_id = sanitize_remote_name($repo_id);
+       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",
+               path => $path,
+               db_path => "$dir/.rev_db", repo_id => $repo_id }, $class;
 }
 
 sub uri_encode {
@@ -1936,198 +1613,6 @@ sub uri_decode {
        $f
 }
 
-sub libsvn_parse_revision {
-       my $base = shift;
-       my $head = $SVN->get_latest_revnum();
-       if (!defined $_revision || $_revision eq 'BASE:HEAD') {
-               return ($base + 1, $head) if (defined $base);
-               return (0, $head);
-       }
-       return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
-       return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
-       if ($_revision =~ /^BASE:(\d+)$/) {
-               return ($base + 1, $1) if (defined $base);
-               return (0, $head);
-       }
-       return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/);
-       die "revision argument: $_revision not understood by git-svn\n",
-               "Try using the command-line svn client instead\n";
-}
-
-sub libsvn_traverse_ignore {
-       my ($fh, $path, $r) = @_;
-       $path =~ s#^/+##g;
-       my ($dirent, undef, $props) = $SVN->get_dir($path, $r);
-       my $p = $path;
-       $p =~ s#^\Q$SVN->{svn_path}\E/##;
-       print $fh length $p ? "\n# $p\n" : "\n# /\n";
-       if (my $s = $props->{'svn:ignore'}) {
-               $s =~ s/[\r\n]+/\n/g;
-               chomp $s;
-               if (length $p == 0) {
-                       $s =~ s#\n#\n/$p#g;
-                       print $fh "/$s\n";
-               } else {
-                       $s =~ s#\n#\n/$p/#g;
-                       print $fh "/$p/$s\n";
-               }
-       }
-       foreach (sort keys %$dirent) {
-               next if $dirent->{$_}->kind != $SVN::Node::dir;
-               libsvn_traverse_ignore($fh, "$path/$_", $r);
-       }
-}
-
-sub revisions_eq {
-       my ($path, $r0, $r1) = @_;
-       return 1 if $r0 == $r1;
-       my $nr = 0;
-       # should be OK to use Pool here (r1 - r0) should be small
-       $SVN->get_log([$path], $r0, $r1, 0, 0, 1, sub {$nr++});
-       return 0 if ($nr > 1);
-       return 1;
-}
-
-sub libsvn_find_parent_branch {
-       my ($paths, $rev, $author, $date, $log) = @_;
-       my $svn_path = '/'.$SVN->{svn_path};
-
-       # look for a parent from another branch:
-       my $i = $paths->{$svn_path} or return;
-       my $branch_from = $i->copyfrom_path or return;
-       my $r = $i->copyfrom_rev;
-       print STDERR  "Found possible branch point: ",
-                               "$branch_from => $svn_path, $r\n";
-       $branch_from =~ s#^/##;
-       my $l_map = {};
-       read_url_paths_all($l_map, '', "$GIT_DIR/svn");
-       my $url = $SVN->{repos_root};
-       defined $l_map->{$url} or return;
-       my $id = $l_map->{$url}->{$branch_from};
-       if (!defined $id && $_follow_parent) {
-               print STDERR "Following parent: $branch_from\@$r\n";
-               # auto create a new branch and follow it
-               $id = basename($branch_from);
-               $id .= '@'.$r if -r "$GIT_DIR/svn/$id";
-               while (-r "$GIT_DIR/svn/$id") {
-                       # just grow a tail if we're not unique enough :x
-                       $id .= '-';
-               }
-       }
-       return unless defined $id;
-
-       my ($r0, $parent) = find_rev_before($r,$id,1);
-       if ($_follow_parent && (!defined $r0 || !defined $parent)) {
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       $SVN_URL = "$url/$branch_from";
-                       $SVN = undef;
-                       setup_git_svn();
-                       # we can't assume SVN_URL exists at r+1:
-                       $_revision = "0:$r";
-                       fetch_lib();
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-               ($r0, $parent) = find_rev_before($r,$id,1);
-       }
-       return unless (defined $r0 && defined $parent);
-       if (revisions_eq($branch_from, $r0, $r)) {
-               unlink $GIT_SVN_INDEX;
-               print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
-               command_noisy('read-tree', $parent);
-               unless ($SVN->can_do_switch) {
-                       return _libsvn_new_tree($paths, $rev, $author, $date,
-                                               $log, [$parent]);
-               }
-               # do_switch works with svn/trunk >= r22312, but that is not
-               # included with SVN 1.4.2 (the latest version at the moment),
-               # so we can't rely on it.
-               my $ra = Git::SVN::Ra->new("$url/$branch_from");
-               my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q });
-               $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or
-                                  die "SVN connection failed somewhere...\n";
-               return libsvn_log_entry($rev, $author, $date, $log, [$parent]);
-       }
-       print STDERR "Nope, branch point not imported or unknown\n";
-       return undef;
-}
-
-sub _libsvn_new_tree {
-       my ($paths, $rev, $author, $date, $log, $parents) = @_;
-       my $ed = SVN::Git::Fetcher->new({q => $_q});
-       unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) {
-               die "SVN connection failed somewhere...\n";
-       }
-       libsvn_log_entry($rev, $author, $date, $log, $parents, $ed);
-}
-
-sub find_graft_path_commit {
-       my ($tree_paths, $p1, $r1) = @_;
-       foreach my $x (keys %$tree_paths) {
-               next unless ($p1 =~ /^\Q$x\E/);
-               my $i = $tree_paths->{$x};
-               my ($r0, $parent) = find_rev_before($r1,$i,1);
-               return $parent if (defined $r0 && $r0 == $r1);
-               print STDERR "r$r1 of $i not imported\n";
-               next;
-       }
-       return undef;
-}
-
-sub find_graft_path_parents {
-       my ($grafts, $tree_paths, $c, $p0, $r0) = @_;
-       foreach my $x (keys %$tree_paths) {
-               next unless ($p0 =~ /^\Q$x\E/);
-               my $i = $tree_paths->{$x};
-               my ($r, $parent) = find_rev_before($r0, $i, 1);
-               if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
-                       my ($url_b, undef, $uuid_b) = cmt_metadata($c);
-                       my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
-                       next if ($url_a && $url_b && $url_a eq $url_b &&
-                                                       $uuid_b eq $uuid_a);
-                       $grafts->{$c}->{$parent} = 1;
-               }
-       }
-}
-
-sub libsvn_graft_file_copies {
-       my ($grafts, $tree_paths, $path, $paths, $rev) = @_;
-       foreach (keys %$paths) {
-               my $i = $paths->{$_};
-               my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,
-                                       $i->copyfrom_rev);
-               next unless (defined $p0 && defined $r0);
-
-               my $p1 = $_;
-               $p1 =~ s#^/##;
-               $p0 =~ s#^/##;
-               my $c = find_graft_path_commit($tree_paths, $p1, $rev);
-               next unless $c;
-               find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);
-       }
-}
-
-sub revdb_get {
-       my ($file, $rev) = @_;
-       my $ret;
-       my $offset = $rev * 41;
-       open my $fh, '<', $file or croak $!;
-       seek $fh, $offset, 0;
-       if (tell $fh == $offset) {
-               $ret = readline $fh;
-               if (defined $ret) {
-                       chomp $ret;
-                       $ret = undef if ($ret =~ /^0{40}$/);
-               }
-       }
-       close $fh or croak $!;
-       return $ret;
-}
-
 {
        my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
                                $SVN::Node::dir.$SVN::Node::unknown.
@@ -2164,6 +1649,11 @@ sub new {
        $self;
 }
 
+sub set_path_strip {
+       my ($self, $path) = @_;
+       $self->{path_strip} = qr/^\Q$path\E\/?/;
+}
+
 sub open_root {
        { path => '' };
 }
@@ -2173,33 +1663,41 @@ sub open_directory {
        { path => $path };
 }
 
+sub git_path {
+       my ($self, $path) = @_;
+       $path =~ s!$self->{path_strip}!! if $self->{path_strip};
+       $path;
+}
+
 sub delete_entry {
        my ($self, $path, $rev, $pb) = @_;
        my $gui = $self->{gui};
 
+       my $gpath = $self->git_path($path);
        # remove entire directories.
-       if (command('ls-tree', $self->{c}, '--', $path) =~ /^040000 tree/) {
+       if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) {
                my ($ls, $ctx) = command_output_pipe(qw/ls-tree
                                                     -r --name-only -z/,
-                                                    $self->{c}, '--', $path);
+                                                    $self->{c}, '--', $gpath);
                local $/ = "\0";
                while (<$ls>) {
                        print $gui '0 ',0 x 40,"\t",$_ or croak $!;
                        print "\tD\t$_\n" unless $self->{q};
                }
-               print "\tD\t$path/\n" unless $self->{q};
+               print "\tD\t$gpath/\n" unless $self->{q};
                command_close_pipe($ls, $ctx);
                $self->{empty}->{$path} = 0
        } else {
-               print $gui '0 ',0 x 40,"\t",$path,"\0" or croak $!;
-               print "\tD\t$path\n" unless $self->{q};
+               print $gui '0 ',0 x 40,"\t",$gpath,"\0" or croak $!;
+               print "\tD\t$gpath\n" unless $self->{q};
        }
        undef;
 }
 
 sub open_file {
        my ($self, $path, $pb, $rev) = @_;
-       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path)
+       my $gpath = $self->git_path($path);
+       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
                             =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
        unless (defined $mode && defined $blob) {
                die "$path was not found in commit $self->{c} (r$rev)\n";
@@ -2298,7 +1796,7 @@ sub apply_textdelta {
 sub close_file {
        my ($self, $fb, $exp) = @_;
        my $hash;
-       my $path = $fb->{path};
+       my $path = $self->git_path($fb->{path});
        if (my $fh = $fb->{fh}) {
                seek($fh, 0, 0) or croak $!;
                my $md5 = Digest::MD5->new;
@@ -2659,6 +2157,7 @@ package Git::SVN::Ra;
 use strict;
 use warnings;
 my ($can_do_switch);
+my %RA;
 
 BEGIN {
        # enforce temporary pool usage for some simple functions
@@ -2677,6 +2176,9 @@ BEGIN
 
 sub new {
        my ($class, $url) = @_;
+       $url =~ s!/+$!!;
+       return $RA{$url} if $RA{$url};
+
        SVN::_Core::svn_config_ensure($config_dir, undef);
        my ($baton, $callbacks) = SVN::Core::auth_open_helper([
            SVN::Client::get_simple_provider(),
@@ -2701,13 +2203,11 @@ sub new {
        $self->{svn_path} = $url;
        $self->{repos_root} = $self->get_repos_root;
        $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##;
-       bless $self, $class;
+       $RA{$url} = bless $self, $class;
 }
 
 sub DESTROY {
-       my $self = shift;
-       $self->{pool}->clear if $self->{pool};
-       $self->SUPER::DESTROY(@_);
+       # do not call the real DESTROY since we store ourselves in %RA
 }
 
 sub dup {
@@ -2742,11 +2242,12 @@ sub uuid {
 sub gs_do_update {
        my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_;
        my $pool = SVN::Pool->new;
+       $editor->set_path_strip($path);
        my $reporter = $self->do_update($rev_b, $path, $recurse,
                                        $editor, $pool);
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
        my $new = ($rev_a == $rev_b);
-       $reporter->set_path($path, $rev_a, $new, @lock, $pool);
+       $reporter->set_path('', $rev_a, $new, @lock, $pool);
        $reporter->finish_report($pool);
        $pool->clear;
        $editor->{git_commit_ok};
@@ -2755,15 +2256,29 @@ sub gs_do_update {
 sub gs_do_switch {
        my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_;
        my $pool = SVN::Pool->new;
+       $editor->set_path_strip($path);
        my $reporter = $self->do_switch($rev_b, $path, $recurse,
                                        $url_b, $editor, $pool);
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-       $reporter->set_path($path, $rev_a, 0, @lock, $pool);
+       $reporter->set_path('', $rev_a, 0, @lock, $pool);
        $reporter->finish_report($pool);
        $pool->clear;
        $editor->{git_commit_ok};
 }
 
+sub minimize_url {
+       my ($self) = @_;
+       return $self->{url} if ($self->{url} eq $self->{repos_root});
+       my $url = $self->{repos_root};
+       my @components = split(m!/!, $self->{svn_path});
+       my $c = '';
+       do {
+               $url .= "/$c" if length $c;
+               eval { (ref $self)->new($url)->get_latest_revnum };
+       } while ($@ && ($c = shift @components));
+       $url;
+}
+
 sub can_do_switch {
        my $self = shift;
        unless (defined $can_do_switch) {
@@ -3084,6 +2599,239 @@ sub cmd_show_log {
        print '-' x72,"\n" unless $incremental || $oneline;
 }
 
+package Git::SVN::Migration;
+# these version numbers do NOT correspond to actual version numbers
+# of git nor git-svn.  They are just relative.
+#
+# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
+#
+# v1 layout: .git/$id/info/url, refs/remotes/$id
+#
+# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
+#
+# v3 layout: .git/svn/$id, refs/remotes/$id
+#            - 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
+#
+# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
+#            - this is only created for newly multi-init-ed
+#              repositories.  Similar in spirit to the
+#              --use-separate-remotes option in git-clone (now default)
+#            - we do not automatically migrate to this (following
+#              the example set by core git)
+use strict;
+use warnings;
+use Carp qw/croak/;
+use File::Path qw/mkpath/;
+use File::Basename qw/dirname basename/;
+use vars qw/$_minimize/;
+
+sub migrate_from_v0 {
+       my $git_dir = $ENV{GIT_DIR};
+       return undef unless -d $git_dir;
+       my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+       my $migrated = 0;
+       while (<$fh>) {
+               chomp;
+               my ($id, $orig_ref) = ($_, $_);
+               next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
+               next unless -f "$git_dir/$id/info/url";
+               my $new_ref = "refs/remotes/$id";
+               if (::verify_ref("$new_ref^0")) {
+                       print STDERR "W: $orig_ref is probably an old ",
+                                    "branch used by an ancient version of ",
+                                    "git-svn.\n",
+                                    "However, $new_ref also exists.\n",
+                                    "We will not be able ",
+                                    "to use this branch until this ",
+                                    "ambiguity is resolved.\n";
+                       next;
+               }
+               print STDERR "Migrating from v0 layout...\n" if !$migrated;
+               print STDERR "Renaming ref: $orig_ref => $new_ref\n";
+               command_noisy('update-ref', $new_ref, $orig_ref);
+               command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
+               $migrated++;
+       }
+       command_close_pipe($fh, $ctx);
+       print STDERR "Done migrating from v0 layout...\n" if $migrated;
+       $migrated;
+}
+
+sub migrate_from_v1 {
+       my $git_dir = $ENV{GIT_DIR};
+       my $migrated = 0;
+       return $migrated unless -d $git_dir;
+       my $svn_dir = "$git_dir/svn";
+
+       # just in case somebody used 'svn' as their $id at some point...
+       return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
+
+       print STDERR "Migrating from a git-svn v1 layout...\n";
+       mkpath([$svn_dir]);
+       print STDERR "Data from a previous version of git-svn exists, but\n\t",
+                    "$svn_dir\n\t(required for this version ",
+                    "($::VERSION) of git-svn) does not. exist\n";
+       my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+       while (<$fh>) {
+               my $x = $_;
+               next unless $x =~ s#^refs/remotes/##;
+               chomp $x;
+               next unless -f "$git_dir/$x/info/url";
+               my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
+               next unless $u;
+               my $dn = dirname("$git_dir/svn/$x");
+               mkpath([$dn]) unless -d $dn;
+               if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
+                       mkpath(["$git_dir/svn/svn"]);
+                       print STDERR " - $git_dir/$x/info => ",
+                                       "$git_dir/svn/$x/info\n";
+                       rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
+                              croak "$!: $x";
+                       # don't worry too much about these, they probably
+                       # don't exist with repos this old (save for index,
+                       # and we can easily regenerate that)
+                       foreach my $f (qw/unhandled.log index .rev_db/) {
+                               rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
+                       }
+               } else {
+                       print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
+                       rename "$git_dir/$x", "$git_dir/svn/$x" or
+                              croak "$!: $x";
+               }
+               $migrated++;
+       }
+       command_close_pipe($fh, $ctx);
+       print STDERR "Done migrating from a git-svn v1 layout\n";
+       $migrated;
+}
+
+sub read_old_urls {
+       my ($l_map, $pfx, $path) = @_;
+       my @dir;
+       foreach (<$path/*>) {
+               if (-r "$_/info/url") {
+                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
+                       my $ref_id = $pfx . basename $_;
+                       my $url = ::file_to_s("$_/info/url");
+                       $l_map->{$ref_id} = $url;
+               } elsif (-d $_) {
+                       push @dir, $_;
+               }
+       }
+       foreach (@dir) {
+               my $x = $_;
+               $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
+               read_old_urls($l_map, $x, $_);
+       }
+}
+
+sub migrate_from_v2 {
+       my @cfg = command(qw/config -l/);
+       return if grep /^svn-remote\..+\.url=/, @cfg;
+       my %l_map;
+       read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
+       my $migrated = 0;
+
+       foreach my $ref_id (sort keys %l_map) {
+               Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
+               $migrated++;
+       }
+       $migrated;
+}
+
+sub minimize_connections {
+       my $r = Git::SVN::read_all_remotes();
+       my $new_urls = {};
+       my $root_repos = {};
+       foreach my $repo_id (keys %$r) {
+               my $url = $r->{$repo_id}->{url} or next;
+               my $fetch = $r->{$repo_id}->{fetch} or next;
+               my $ra = Git::SVN::Ra->new($url);
+
+               # skip existing cases where we already connect to the root
+               if (($ra->{url} eq $ra->{repos_root}) ||
+                   (Git::SVN::sanitize_remote_name($ra->{repos_root}) eq
+                    $repo_id)) {
+                       $root_repos->{$ra->{url}} = $repo_id;
+                       next;
+               }
+
+               my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
+               my $root_path = $ra->{url};
+               $root_path =~ s#^\Q$ra->{repos_root}\E/*##;
+               foreach my $path (keys %$fetch) {
+                       my $ref_id = $fetch->{$path};
+                       my $gs = Git::SVN->new($ref_id, $repo_id, $path);
+
+                       # make sure we can read when connecting to
+                       # a higher level of a repository
+                       my ($last_rev, undef) = $gs->last_rev_commit;
+                       if (!defined $last_rev) {
+                               $last_rev = eval {
+                                       $root_ra->get_latest_revnum;
+                               };
+                               next if $@;
+                       }
+                       my $new = $root_path;
+                       $new .= length $path ? "/$path" : '';
+                       eval {
+                               $root_ra->get_log([$new], $last_rev, $last_rev,
+                                                 0, 0, 1, sub { });
+                       };
+                       next if $@;
+                       $new_urls->{$ra->{repos_root}}->{$new} =
+                               { ref_id => $ref_id,
+                                 old_repo_id => $repo_id,
+                                 old_path => $path };
+               }
+       }
+
+       my @emptied;
+       foreach my $url (keys %$new_urls) {
+               # see if we can re-use an existing [svn-remote "repo_id"]
+               # instead of creating a(n ugly) new section:
+               my $repo_id = $root_repos->{$url} ||
+                             Git::SVN::sanitize_remote_name($url);
+
+               my $fetch = $new_urls->{$url};
+               foreach my $path (keys %$fetch) {
+                       my $x = $fetch->{$path};
+                       Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
+                       my $pfx = "svn-remote.$x->{old_repo_id}";
+
+                       my $old_fetch = quotemeta("$x->{old_path}:".
+                                                 "refs/remotes/$x->{ref_id}");
+                       command_noisy(qw/config --unset/,
+                                     "$pfx.fetch", '^'. $old_fetch . '$');
+                       delete $r->{$x->{old_repo_id}}->
+                              {fetch}->{$x->{old_path}};
+                       if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
+                               command_noisy(qw/config --unset/,
+                                             "$pfx.url");
+                               push @emptied, $x->{old_repo_id}
+                       }
+               }
+       }
+       if (@emptied) {
+               my $file = $ENV{GIT_CONFIG} || $ENV{GIT_CONFIG_LOCAL} ||
+                          "$ENV{GIT_DIR}/config";
+               print STDERR <<EOF;
+The following [svn-remote] sections in your config file ($file) are empty
+and can be safely removed:
+EOF
+               print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
+       }
+}
+
+sub migration_check {
+       migrate_from_v0();
+       migrate_from_v1();
+       migrate_from_v2();
+       minimize_connections() if $_minimize;
+}
+
 __END__
 
 Data structures: