diff options
-rwxr-xr-x | git-svn.perl | 110 | ||||
-rwxr-xr-x | t/t9107-git-svn-migrate.sh | 25 |
2 files changed, 129 insertions, 6 deletions
diff --git a/git-svn.perl b/git-svn.perl index 4084e0657b..15d65e21e1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -119,8 +119,6 @@ my %cmd = ( \%fc_opts ], 'migrate' => [ sub { }, # no-op, we automatically run this anyways, - # we may add a flag to automatically optimize the - # configuration to avoid reconnects in the future 'Migrate configuration/metadata/layout from previous versions of git-svn', \%remote_opts ], @@ -158,6 +156,8 @@ my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, + 'minimize-connections' => + \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); @@ -702,10 +702,22 @@ BEGIN { svn:entry:committed-date/; } -# we allow dashes, unlike remotes2config.sh +sub read_all_remotes { + my $r = {}; + foreach (grep { s/^svn-remote\.// } command(qw/repo-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; +} + +# we allow more chars than remotes2config.sh... sub sanitize_remote_name { my ($name) = @_; - $name =~ tr/A-Za-z0-9-/./c; + $name =~ tr{A-Za-z0-9:,/+-}{.}c; $name; } @@ -2467,7 +2479,8 @@ use strict; use warnings; use Carp qw/croak/; use File::Path qw/mkpath/; -use File::Basename qw/dirname/; +use File::Basename qw/dirname basename/; +use vars qw/$_minimize/; sub migrate_from_v0 { my $git_dir = $ENV{GIT_DIR}; @@ -2577,16 +2590,101 @@ sub migrate_from_v2 { my $migrated = 0; foreach my $ref_id (sort keys %l_map) { - Git::SVN->init($l_map{$ref_id}, $ref_id); + 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/repo-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/repo-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__ diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 53318f1b1a..74a45ec647 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -46,6 +46,7 @@ test_expect_success 'initialize a multi-repository repo' " grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out " +# refs should all be different, but the trees should all be the same: test_expect_success 'multi-fetch works on partial urls + paths' " git-svn multi-fetch && for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do @@ -59,5 +60,29 @@ test_expect_success 'multi-fetch works on partial urls + paths' " refs/remotes/\$j\`\" ||exit 1; done; done " +test_expect_success 'migrate --minimize on old multi-inited layout' " + git repo-config --unset-all svn-remote.git-svn.fetch && + git repo-config --unset-all svn-remote.git-svn.url && + rm -rf $GIT_DIR/svn && + for i in \`cat fetch.out\`; do + path=\`expr \$i : '\\([^:]*\\):.*$'\` + ref=\`expr \$i : '[^:]*:refs/remotes/\\(.*\\)$'\` + if test -z \"\$ref\"; then continue; fi + if test -n \"\$path\"; then path=\"/\$path\"; fi + ( mkdir -p $GIT_DIR/svn/\$ref/info/ && + echo $svnrepo\$path > $GIT_DIR/svn/\$ref/info/url ) || exit 1; + done && + git-svn migrate --minimize && + test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && + git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + grep '^trunk:refs/remotes/trunk$' fetch.out && + grep '^branches/a:refs/remotes/a$' fetch.out && + grep '^branches/b:refs/remotes/b$' fetch.out && + grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && + grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && + grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + grep '^:refs/remotes/git-svn' fetch.out + " + test_done |