1package Git::SVN::Migration; 2# these version numbers do NOT correspond to actual version numbers 3# of git or git-svn. They are just relative. 4# 5# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD 6# 7# v1 layout: .git/$id/info/url, refs/remotes/$id 8# 9# v2 layout: .git/svn/$id/info/url, refs/remotes/$id 10# 11# v3 layout: .git/svn/$id, refs/remotes/$id 12# - info/url may remain for backwards compatibility 13# - this is what we migrate up to this layout automatically, 14# - this will be used by git svn init on single branches 15# v3.1 layout (auto migrated): 16# - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink 17# for backwards compatibility 18# 19# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id 20# - this is only created for newly multi-init-ed 21# repositories. Similar in spirit to the 22# --use-separate-remotes option in git-clone (now default) 23# - we do not automatically migrate to this (following 24# the example set by core git) 25# 26# v5 layout: .rev_db.$UUID => .rev_map.$UUID 27# - newer, more-efficient format that uses 24-bytes per record 28# with no filler space. 29# - use xxd -c24 < .rev_map.$UUID to view and debug 30# - This is a one-way migration, repositories updated to the 31# new format will not be able to use old git-svn without 32# rebuilding the .rev_db. Rebuilding the rev_db is not 33# possible if noMetadata or useSvmProps are set; but should 34# be no problem for users that use the (sensible) defaults. 35use strict; 36use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : (); 37use Carp qw/croak/; 38use File::Path qw/mkpath/; 39use File::Basename qw/dirname basename/; 40 41our $_minimize; 42use Git qw( 43 command 44 command_noisy 45 command_output_pipe 46 command_close_pipe 47 command_oneline 48); 49use Git::SVN; 50 51sub migrate_from_v0 { 52 my $git_dir = $ENV{GIT_DIR}; 53 return undef unless -d $git_dir; 54 my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); 55 my $migrated = 0; 56 while (<$fh>) { 57 chomp; 58 my ($id, $orig_ref) = ($_, $_); 59 next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#; 60 my $info_url = command_oneline(qw(rev-parse --git-path), 61 "$id/info/url"); 62 next unless -f $info_url; 63 my $new_ref = "refs/remotes/$id"; 64 if (::verify_ref("$new_ref^0")) { 65 print STDERR "W: $orig_ref is probably an old ", 66 "branch used by an ancient version of ", 67 "git-svn.\n", 68 "However, $new_ref also exists.\n", 69 "We will not be able ", 70 "to use this branch until this ", 71 "ambiguity is resolved.\n"; 72 next; 73 } 74 print STDERR "Migrating from v0 layout...\n" if !$migrated; 75 print STDERR "Renaming ref: $orig_ref => $new_ref\n"; 76 command_noisy('update-ref', $new_ref, $orig_ref); 77 command_noisy('update-ref', '-d', $orig_ref, $orig_ref); 78 $migrated++; 79 } 80 command_close_pipe($fh, $ctx); 81 print STDERR "Done migrating from v0 layout...\n" if $migrated; 82 $migrated; 83} 84 85sub migrate_from_v1 { 86 my $git_dir = $ENV{GIT_DIR}; 87 my $migrated = 0; 88 return $migrated unless -d $git_dir; 89 my $svn_dir = Git::SVN::svn_dir(); 90 91 # just in case somebody used 'svn' as their $id at some point... 92 return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url"; 93 94 print STDERR "Migrating from a git-svn v1 layout...\n"; 95 mkpath([$svn_dir]); 96 print STDERR "Data from a previous version of git-svn exists, but\n\t", 97 "$svn_dir\n\t(required for this version ", 98 "($::VERSION) of git-svn) does not exist.\n"; 99 my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); 100 while (<$fh>) { 101 my $x = $_; 102 next unless $x =~ s#^refs/remotes/##; 103 chomp $x; 104 my $info_url = command_oneline(qw(rev-parse --git-path), 105 "$x/info/url"); 106 next unless -f $info_url; 107 my $u = eval { ::file_to_s($info_url) }; 108 next unless $u; 109 my $dn = dirname("$svn_dir/$x"); 110 mkpath([$dn]) unless -d $dn; 111 if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID: 112 mkpath(["$svn_dir/svn"]); 113 print STDERR " - $git_dir/$x/info => ", 114 "$svn_dir/$x/info\n"; 115 rename "$git_dir/$x/info", "$svn_dir/$x/info" or 116 croak "$!: $x"; 117 # don't worry too much about these, they probably 118 # don't exist with repos this old (save for index, 119 # and we can easily regenerate that) 120 foreach my $f (qw/unhandled.log index .rev_db/) { 121 rename "$git_dir/$x/$f", "$svn_dir/$x/$f"; 122 } 123 } else { 124 print STDERR " - $git_dir/$x => $svn_dir/$x\n"; 125 rename "$git_dir/$x", "$svn_dir/$x" or croak "$!: $x"; 126 } 127 $migrated++; 128 } 129 command_close_pipe($fh, $ctx); 130 print STDERR "Done migrating from a git-svn v1 layout\n"; 131 $migrated; 132} 133 134sub read_old_urls { 135 my ($l_map, $pfx, $path) = @_; 136 my @dir; 137 foreach (<$path/*>) { 138 if (-r "$_/info/url") { 139 $pfx .= '/' if $pfx && $pfx !~ m!/$!; 140 my $ref_id = $pfx . basename $_; 141 my $url = ::file_to_s("$_/info/url"); 142 $l_map->{$ref_id} = $url; 143 } elsif (-d $_) { 144 push @dir, $_; 145 } 146 } 147 my $svn_dir = Git::SVN::svn_dir(); 148 foreach (@dir) { 149 my $x = $_; 150 $x =~ s!^\Q$svn_dir\E/!!o; 151 read_old_urls($l_map, $x, $_); 152 } 153} 154 155sub migrate_from_v2 { 156 my @cfg = command(qw/config -l/); 157 return if grep /^svn-remote\..+\.url=/, @cfg; 158 my %l_map; 159 read_old_urls(\%l_map, '', Git::SVN::svn_dir()); 160 my $migrated = 0; 161 162 require Git::SVN; 163 foreach my $ref_id (sort keys %l_map) { 164 eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) }; 165 if ($@) { 166 Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); 167 } 168 $migrated++; 169 } 170 $migrated; 171} 172 173sub minimize_connections { 174 require Git::SVN; 175 require Git::SVN::Ra; 176 177 my $r = Git::SVN::read_all_remotes(); 178 my $new_urls = {}; 179 my $root_repos = {}; 180 foreach my $repo_id (keys %$r) { 181 my $url = $r->{$repo_id}->{url} or next; 182 my $fetch = $r->{$repo_id}->{fetch} or next; 183 my $ra = Git::SVN::Ra->new($url); 184 185 # skip existing cases where we already connect to the root 186 if (($ra->url eq $ra->{repos_root}) || 187 ($ra->{repos_root} eq $repo_id)) { 188 $root_repos->{$ra->url} = $repo_id; 189 next; 190 } 191 192 my $root_ra = Git::SVN::Ra->new($ra->{repos_root}); 193 my $root_path = $ra->url; 194 $root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##; 195 foreach my $path (keys %$fetch) { 196 my $ref_id = $fetch->{$path}; 197 my $gs = Git::SVN->new($ref_id, $repo_id, $path); 198 199 # make sure we can read when connecting to 200 # a higher level of a repository 201 my ($last_rev, undef) = $gs->last_rev_commit; 202 if (!defined $last_rev) { 203 $last_rev = eval { 204 $root_ra->get_latest_revnum; 205 }; 206 next if $@; 207 } 208 my $new = $root_path; 209 $new .= length $path ? "/$path" : ''; 210 eval { 211 $root_ra->get_log([$new], $last_rev, $last_rev, 212 0, 0, 1, sub { }); 213 }; 214 next if $@; 215 $new_urls->{$ra->{repos_root}}->{$new} = 216 { ref_id => $ref_id, 217 old_repo_id => $repo_id, 218 old_path => $path }; 219 } 220 } 221 222 my @emptied; 223 foreach my $url (keys %$new_urls) { 224 # see if we can re-use an existing [svn-remote "repo_id"] 225 # instead of creating a(n ugly) new section: 226 my $repo_id = $root_repos->{$url} || $url; 227 228 my $fetch = $new_urls->{$url}; 229 foreach my $path (keys %$fetch) { 230 my $x = $fetch->{$path}; 231 Git::SVN->init($url, $path, $repo_id, $x->{ref_id}); 232 my $pfx = "svn-remote.$x->{old_repo_id}"; 233 234 my $old_fetch = quotemeta("$x->{old_path}:". 235 "$x->{ref_id}"); 236 command_noisy(qw/config --unset/, 237 "$pfx.fetch", '^'. $old_fetch . '$'); 238 delete $r->{$x->{old_repo_id}}-> 239 {fetch}->{$x->{old_path}}; 240 if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) { 241 command_noisy(qw/config --unset/, 242 "$pfx.url"); 243 push @emptied, $x->{old_repo_id} 244 } 245 } 246 } 247 if (@emptied) { 248 my $file = $ENV{GIT_CONFIG} || 249 command_oneline(qw(rev-parse --git-path config)); 250 print STDERR <<EOF; 251The following [svn-remote] sections in your config file ($file) are empty 252and can be safely removed: 253EOF 254 print STDERR "[svn-remote \"$_\"]\n" foreach @emptied; 255 } 256} 257 258sub migration_check { 259 migrate_from_v0(); 260 migrate_from_v1(); 261 migrate_from_v2(); 262 minimize_connections() if $_minimize; 263} 264 2651; 266