1package Git::SVN::Migration; 2# these version numbers do NOT correspond to actual version numbers 3# of git nor 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; 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); 48 49sub migrate_from_v0 { 50my$git_dir=$ENV{GIT_DIR}; 51returnundefunless-d $git_dir; 52my($fh,$ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); 53my$migrated=0; 54while(<$fh>) { 55chomp; 56my($id,$orig_ref) = ($_,$_); 57next unless$id=~ s#^refs/heads/(.+)-HEAD$#$1#; 58next unless-f "$git_dir/$id/info/url"; 59my$new_ref="refs/remotes/$id"; 60if(::verify_ref("$new_ref^0")) { 61print STDERR "W:$orig_refis probably an old ", 62"branch used by an ancient version of ", 63"git-svn.\n", 64"However,$new_refalso exists.\n", 65"We will not be able ", 66"to use this branch until this ", 67"ambiguity is resolved.\n"; 68next; 69} 70print STDERR "Migrating from v0 layout...\n"if!$migrated; 71print STDERR "Renaming ref:$orig_ref=>$new_ref\n"; 72 command_noisy('update-ref',$new_ref,$orig_ref); 73 command_noisy('update-ref','-d',$orig_ref,$orig_ref); 74$migrated++; 75} 76 command_close_pipe($fh,$ctx); 77print STDERR "Done migrating from v0 layout...\n"if$migrated; 78$migrated; 79} 80 81sub migrate_from_v1 { 82my$git_dir=$ENV{GIT_DIR}; 83my$migrated=0; 84return$migratedunless-d $git_dir; 85my$svn_dir="$git_dir/svn"; 86 87# just in case somebody used 'svn' as their $id at some point... 88return$migratedif-d $svn_dir&& ! -f "$svn_dir/info/url"; 89 90print STDERR "Migrating from a git-svn v1 layout...\n"; 91 mkpath([$svn_dir]); 92print STDERR "Data from a previous version of git-svn exists, but\n\t", 93"$svn_dir\n\t(required for this version ", 94"($::VERSION) of git-svn) does not exist.\n"; 95my($fh,$ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); 96while(<$fh>) { 97my$x=$_; 98next unless$x=~ s#^refs/remotes/##; 99chomp$x; 100next unless-f "$git_dir/$x/info/url"; 101my$u=eval{ ::file_to_s("$git_dir/$x/info/url") }; 102next unless$u; 103my$dn= dirname("$git_dir/svn/$x"); 104 mkpath([$dn])unless-d $dn; 105if($xeq'svn') {# they used 'svn' as GIT_SVN_ID: 106 mkpath(["$git_dir/svn/svn"]); 107print STDERR " -$git_dir/$x/info=> ", 108"$git_dir/svn/$x/info\n"; 109rename"$git_dir/$x/info","$git_dir/svn/$x/info"or 110 croak "$!:$x"; 111# don't worry too much about these, they probably 112# don't exist with repos this old (save for index, 113# and we can easily regenerate that) 114foreachmy$f(qw/unhandled.log index .rev_db/) { 115rename"$git_dir/$x/$f","$git_dir/svn/$x/$f"; 116} 117}else{ 118print STDERR " -$git_dir/$x=>$git_dir/svn/$x\n"; 119rename"$git_dir/$x","$git_dir/svn/$x"or 120 croak "$!:$x"; 121} 122$migrated++; 123} 124 command_close_pipe($fh,$ctx); 125print STDERR "Done migrating from a git-svn v1 layout\n"; 126$migrated; 127} 128 129sub read_old_urls { 130my($l_map,$pfx,$path) =@_; 131my@dir; 132foreach(<$path/*>) { 133if(-r "$_/info/url") { 134$pfx.='/'if$pfx&&$pfx!~ m!/$!; 135my$ref_id=$pfx. basename $_; 136my$url= ::file_to_s("$_/info/url"); 137$l_map->{$ref_id} =$url; 138}elsif(-d $_) { 139push@dir,$_; 140} 141} 142foreach(@dir) { 143my$x=$_; 144$x=~s!^\Q$ENV{GIT_DIR}\E/svn/!!o; 145 read_old_urls($l_map,$x,$_); 146} 147} 148 149sub migrate_from_v2 { 150my@cfg= command(qw/config -l/); 151return ifgrep/^svn-remote\..+\.url=/,@cfg; 152my%l_map; 153 read_old_urls(\%l_map,'',"$ENV{GIT_DIR}/svn"); 154my$migrated=0; 155 156require Git::SVN; 157foreachmy$ref_id(sort keys%l_map) { 158eval{ Git::SVN->init($l_map{$ref_id},'',undef,$ref_id) }; 159if($@) { 160 Git::SVN->init($l_map{$ref_id},'',$ref_id,$ref_id); 161} 162$migrated++; 163} 164$migrated; 165} 166 167sub minimize_connections { 168require Git::SVN; 169require Git::SVN::Ra; 170 171my$r= Git::SVN::read_all_remotes(); 172my$new_urls= {}; 173my$root_repos= {}; 174foreachmy$repo_id(keys%$r) { 175my$url=$r->{$repo_id}->{url}ornext; 176my$fetch=$r->{$repo_id}->{fetch}ornext; 177my$ra= Git::SVN::Ra->new($url); 178 179# skip existing cases where we already connect to the root 180if(($ra->{url}eq$ra->{repos_root}) || 181($ra->{repos_root}eq$repo_id)) { 182$root_repos->{$ra->{url}} =$repo_id; 183next; 184} 185 186my$root_ra= Git::SVN::Ra->new($ra->{repos_root}); 187my$root_path=$ra->{url}; 188$root_path=~ s#^\Q$ra->{repos_root}\E(/|$)##; 189foreachmy$path(keys%$fetch) { 190my$ref_id=$fetch->{$path}; 191my$gs= Git::SVN->new($ref_id,$repo_id,$path); 192 193# make sure we can read when connecting to 194# a higher level of a repository 195my($last_rev,undef) =$gs->last_rev_commit; 196if(!defined$last_rev) { 197$last_rev=eval{ 198$root_ra->get_latest_revnum; 199}; 200next if$@; 201} 202my$new=$root_path; 203$new.=length$path?"/$path":''; 204eval{ 205$root_ra->get_log([$new],$last_rev,$last_rev, 2060,0,1,sub{ }); 207}; 208next if$@; 209$new_urls->{$ra->{repos_root}}->{$new} = 210{ ref_id =>$ref_id, 211 old_repo_id =>$repo_id, 212 old_path =>$path}; 213} 214} 215 216my@emptied; 217foreachmy$url(keys%$new_urls) { 218# see if we can re-use an existing [svn-remote "repo_id"] 219# instead of creating a(n ugly) new section: 220my$repo_id=$root_repos->{$url} ||$url; 221 222my$fetch=$new_urls->{$url}; 223foreachmy$path(keys%$fetch) { 224my$x=$fetch->{$path}; 225 Git::SVN->init($url,$path,$repo_id,$x->{ref_id}); 226my$pfx="svn-remote.$x->{old_repo_id}"; 227 228my$old_fetch=quotemeta("$x->{old_path}:". 229"$x->{ref_id}"); 230 command_noisy(qw/config --unset/, 231"$pfx.fetch",'^'.$old_fetch.'$'); 232delete$r->{$x->{old_repo_id}}-> 233{fetch}->{$x->{old_path}}; 234if(!keys%{$r->{$x->{old_repo_id}}->{fetch}}) { 235 command_noisy(qw/config --unset/, 236"$pfx.url"); 237push@emptied,$x->{old_repo_id} 238} 239} 240} 241if(@emptied) { 242my$file=$ENV{GIT_CONFIG} ||"$ENV{GIT_DIR}/config"; 243print STDERR <<EOF; 244The following [svn-remote] sections in your config file ($file) are empty 245and can be safely removed: 246EOF 247print STDERR "[svn-remote\"$_\"]\n"foreach@emptied; 248} 249} 250 251sub migration_check { 252 migrate_from_v0(); 253 migrate_from_v1(); 254 migrate_from_v2(); 255 minimize_connections()if$_minimize; 256} 257 2581;