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