Straight cut & paste.
Signed-off-by: Eric Wong <normalperson@yhbt.net>
use Git::SVN;
use Git::SVN::Log;
+use Git::SVN::Migration;
use Git::SVN::Utils qw(fatal can_compress);
use Git qw(
}
-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
-# 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
-# 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)
-#
-# v5 layout: .rev_db.$UUID => .rev_map.$UUID
-# - newer, more-efficient format that uses 24-bytes per record
-# with no filler space.
-# - use xxd -c24 < .rev_map.$UUID to view and debug
-# - This is a one-way migration, repositories updated to the
-# new format will not be able to use old git-svn without
-# rebuilding the .rev_db. Rebuilding the rev_db is not
-# possible if noMetadata or useSvmProps are set; but should
-# be no problem for users that use the (sensible) defaults.
-use strict;
-use warnings;
-use Carp qw/croak/;
-use File::Path qw/mkpath/;
-use File::Basename qw/dirname basename/;
-
-our $_minimize;
-use Git qw(
- command
- command_noisy
- command_output_pipe
- command_close_pipe
-);
-
-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;
-
- require Git::SVN;
- foreach my $ref_id (sort keys %l_map) {
- eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
- if ($@) {
- Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
- }
- $migrated++;
- }
- $migrated;
-}
-
-sub minimize_connections {
- require Git::SVN;
- require Git::SVN::Ra;
-
- 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}) ||
- ($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} || $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}:".
- "$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_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;
-}
-
package Git::IndexInfo;
use strict;
use warnings;
--- /dev/null
+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
+# 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
+# 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)
+#
+# v5 layout: .rev_db.$UUID => .rev_map.$UUID
+# - newer, more-efficient format that uses 24-bytes per record
+# with no filler space.
+# - use xxd -c24 < .rev_map.$UUID to view and debug
+# - This is a one-way migration, repositories updated to the
+# new format will not be able to use old git-svn without
+# rebuilding the .rev_db. Rebuilding the rev_db is not
+# possible if noMetadata or useSvmProps are set; but should
+# be no problem for users that use the (sensible) defaults.
+use strict;
+use warnings;
+use Carp qw/croak/;
+use File::Path qw/mkpath/;
+use File::Basename qw/dirname basename/;
+
+our $_minimize;
+use Git qw(
+ command
+ command_noisy
+ command_output_pipe
+ command_close_pipe
+);
+
+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;
+
+ require Git::SVN;
+ foreach my $ref_id (sort keys %l_map) {
+ eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
+ if ($@) {
+ Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
+ }
+ $migrated++;
+ }
+ $migrated;
+}
+
+sub minimize_connections {
+ require Git::SVN;
+ require Git::SVN::Ra;
+
+ 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}) ||
+ ($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} || $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}:".
+ "$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_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;
+}
+
+1;
modules += Git/SVN/Fetcher
modules += Git/SVN/Editor
modules += Git/SVN/Log
+modules += Git/SVN/Migration
modules += Git/SVN/Prompt
modules += Git/SVN/Ra
modules += Git/SVN/Utils
use strict;
use warnings;
-use Test::More tests => 4;
+use Test::More tests => 5;
require_ok 'Git::SVN';
require_ok 'Git::SVN::Utils';
require_ok 'Git::SVN::Ra';
require_ok 'Git::SVN::Log';
+require_ok 'Git::SVN::Migration';