git-svn: add support for metadata in .git/config
Of course, we handle metadata migrations from previous versions and we have added unit tests. The new .git/config remotes resemble non-SVN remotes. Below is an example with comments: [svn-remote "git-svn"] ; like non-svn remotes, we have one URL per-remote url = http://foo.bar.org/svn ; 'fetch' keys are done in the same way as non-svn ; remotes, too. With the left-hand-side of the ':' ; being the remote (SVN) repository path relative to the ; above 'url' key; and the right-hand-side being a ; remote ref in git (refs/remotes/*). ; An empty left-hand-side means that it will fetch ; the entire contents of the 'url' key. ; old-style (migrated from previous versions of git-svn) ; are like this: fetch = :refs/remotes/git-svn ; this is created by a current version of git-svn ; using the multi-init command with an explicit ; url (specified above). This allows multi-init ; to reuse SVN::Ra connections. fetch = trunk:refs/remotes/trunk fetch = branches/a:refs/remotes/a fetch = branches/b:refs/remotes/b fetch = tags/0.1:refs/remotes/tags/0.1 fetch = tags/0.2:refs/remotes/tags/0.2 fetch = tags/0.3:refs/remotes/tags/0.3 [svn-remote "alt"] ; this is another old-style remote migrated over ; to the new config format url = http://foo.bar.org/alt fetch = :refs/remotes/alt Signed-off-by: Eric Wong <normalperson@yhbt.net>maint
							parent
							
								
									d05d72e07e
								
							
						
					
					
						commit
						706587fc6d
					
				
							
								
								
									
										579
									
								
								git-svn.perl
								
								
								
								
							
							
						
						
									
										579
									
								
								git-svn.perl
								
								
								
								
							| 
						 | 
				
			
			@ -13,9 +13,8 @@ use vars qw/	$AUTHOR $VERSION
 | 
			
		|||
$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 | 
			
		||||
$VERSION = '@@GIT_VERSION@@';
 | 
			
		||||
 | 
			
		||||
use Cwd qw/abs_path/;
 | 
			
		||||
$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
 | 
			
		||||
$ENV{GIT_DIR} = $GIT_DIR;
 | 
			
		||||
$ENV{GIT_DIR} ||= '.git';
 | 
			
		||||
$Git::SVN::default_repo_id = $ENV{GIT_SVN_ID} || 'git-svn';
 | 
			
		||||
 | 
			
		||||
my $LC_ALL = $ENV{LC_ALL};
 | 
			
		||||
$Git::SVN::Log::TZ = $ENV{TZ};
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +46,7 @@ BEGIN {
 | 
			
		|||
	foreach (qw/command command_oneline command_noisy command_output_pipe
 | 
			
		||||
	            command_input_pipe command_close_pipe/) {
 | 
			
		||||
		$s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
 | 
			
		||||
		      "*Git::SVN::Migration::$_ = ".
 | 
			
		||||
		      "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
 | 
			
		||||
	}
 | 
			
		||||
	eval $s;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,17 +64,17 @@ my ($_stdin, $_help, $_edit,
 | 
			
		|||
	$_version, $_upgrade,
 | 
			
		||||
	$_merge, $_strategy, $_dry_run,
 | 
			
		||||
	$_prefix);
 | 
			
		||||
my @repo_path_split_cache;
 | 
			
		||||
 | 
			
		||||
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
 | 
			
		||||
                    'config-dir=s' => \$Git::SVN::Ra::config_dir,
 | 
			
		||||
                    'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache );
 | 
			
		||||
my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent,
 | 
			
		||||
		'authors-file|A=s' => \$_authors,
 | 
			
		||||
		'repack:i' => \$_repack,
 | 
			
		||||
		'no-metadata' => \$_no_metadata,
 | 
			
		||||
		'quiet|q' => \$_q,
 | 
			
		||||
		'username=s' => \$Git::SVN::Prompt::_username,
 | 
			
		||||
		'config-dir=s' => \$Git::SVN::Ra::config_dir,
 | 
			
		||||
		'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
 | 
			
		||||
		'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 | 
			
		||||
		'repack-flags|repack-args|repack-opts=s' => \$_repack_flags,
 | 
			
		||||
		%remote_opts );
 | 
			
		||||
 | 
			
		||||
my ($_trunk, $_tags, $_branches);
 | 
			
		||||
my %multi_opts = ( 'trunk|T=s' => \$_trunk,
 | 
			
		||||
| 
						 | 
				
			
			@ -110,16 +110,20 @@ my %cmd = (
 | 
			
		|||
			  'upgrade' => \$_upgrade } ],
 | 
			
		||||
	'multi-init' => [ \&cmd_multi_init,
 | 
			
		||||
			'Initialize multiple trees (like git-svnimport)',
 | 
			
		||||
			{ %multi_opts, %init_opts,
 | 
			
		||||
			{ %multi_opts, %init_opts, %remote_opts,
 | 
			
		||||
			 'revision|r=i' => \$_revision,
 | 
			
		||||
			 'username=s' => \$Git::SVN::Prompt::_username,
 | 
			
		||||
			 'config-dir=s' => \$Git::SVN::Ra::config_dir,
 | 
			
		||||
			 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
 | 
			
		||||
			 'prefix=s' => \$_prefix,
 | 
			
		||||
			} ],
 | 
			
		||||
	'multi-fetch' => [ \&cmd_multi_fetch,
 | 
			
		||||
			'Fetch multiple trees (like git-svnimport)',
 | 
			
		||||
			\%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 ],
 | 
			
		||||
	'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs',
 | 
			
		||||
			{ 'limit=i' => \$Git::SVN::Log::limit,
 | 
			
		||||
			  'revision|r=s' => \$_revision,
 | 
			
		||||
| 
						 | 
				
			
			@ -154,15 +158,16 @@ my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
 | 
			
		|||
read_repo_config(\%opts);
 | 
			
		||||
my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
 | 
			
		||||
				'version|V' => \$_version,
 | 
			
		||||
				'id|i=s' => \$GIT_SVN);
 | 
			
		||||
				'id|i=s' => \$Git::SVN::default_repo_id);
 | 
			
		||||
exit 1 if (!$rv && $cmd ne 'log');
 | 
			
		||||
 | 
			
		||||
usage(0) if $_help;
 | 
			
		||||
version() if $_version;
 | 
			
		||||
usage(1) unless defined $cmd;
 | 
			
		||||
init_vars();
 | 
			
		||||
load_authors() if $_authors;
 | 
			
		||||
migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
 | 
			
		||||
unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) {
 | 
			
		||||
	Git::SVN::Migration::migration_check();
 | 
			
		||||
}
 | 
			
		||||
$cmd{$cmd}->[0]->(@ARGV);
 | 
			
		||||
exit 0;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -203,17 +208,12 @@ sub version {
 | 
			
		|||
 | 
			
		||||
sub cmd_rebuild {
 | 
			
		||||
	my $url = shift;
 | 
			
		||||
	my $gs = $url ? Git::SVN->init(undef, $url)
 | 
			
		||||
	my $gs = $url ? Git::SVN->init($url)
 | 
			
		||||
	              : eval { Git::SVN->new };
 | 
			
		||||
	$gs ||= Git::SVN->_new;
 | 
			
		||||
	if (!verify_ref($gs->refname.'^0')) {
 | 
			
		||||
		$gs->copy_remote_ref;
 | 
			
		||||
	}
 | 
			
		||||
	if ($_upgrade) {
 | 
			
		||||
		command_noisy('update-ref',$gs->refname, $gs->{id}.'-HEAD');
 | 
			
		||||
	} else {
 | 
			
		||||
		$gs->check_upgrade_needed;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname);
 | 
			
		||||
	my $latest;
 | 
			
		||||
| 
						 | 
				
			
			@ -238,7 +238,7 @@ sub cmd_rebuild {
 | 
			
		|||
			if (!$gs->{url} && !$url) {
 | 
			
		||||
				fatal "SVN repository location required\n";
 | 
			
		||||
			}
 | 
			
		||||
			$gs = Git::SVN->init(undef, $url);
 | 
			
		||||
			$gs = Git::SVN->init($url);
 | 
			
		||||
			$latest = $rev;
 | 
			
		||||
		}
 | 
			
		||||
		$gs->rev_db_set($rev, $c);
 | 
			
		||||
| 
						 | 
				
			
			@ -268,7 +268,7 @@ sub cmd_init {
 | 
			
		|||
	}
 | 
			
		||||
	do_git_init_db();
 | 
			
		||||
 | 
			
		||||
	Git::SVN->init(undef, $url);
 | 
			
		||||
	Git::SVN->init($url);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub cmd_fetch {
 | 
			
		||||
| 
						 | 
				
			
			@ -389,28 +389,35 @@ sub cmd_multi_init {
 | 
			
		|||
	}
 | 
			
		||||
	do_git_init_db();
 | 
			
		||||
	$_prefix = '' unless defined $_prefix;
 | 
			
		||||
	$url =~ s#/+$## if defined $url;
 | 
			
		||||
	if (defined $_trunk) {
 | 
			
		||||
		my $gs_trunk = eval { Git::SVN->new($_prefix . 'trunk') };
 | 
			
		||||
		my $trunk_ref = $_prefix . 'trunk';
 | 
			
		||||
		# try both old-style and new-style lookups:
 | 
			
		||||
		my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
 | 
			
		||||
		unless ($gs_trunk) {
 | 
			
		||||
			my $trunk_url = complete_svn_url($url, $_trunk);
 | 
			
		||||
			$gs_trunk = Git::SVN->init($_prefix . 'trunk',
 | 
			
		||||
			                           $trunk_url);
 | 
			
		||||
			command_noisy('config', 'svn.trunk', $trunk_url);
 | 
			
		||||
			my ($trunk_url, $trunk_path) =
 | 
			
		||||
			                      complete_svn_url($url, $_trunk);
 | 
			
		||||
			$gs_trunk = Git::SVN->init($trunk_url, $trunk_path,
 | 
			
		||||
						   undef, $trunk_ref);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return unless defined $_branches || defined $_tags;
 | 
			
		||||
	my $ra = $url ? Git::SVN::Ra->new($url) : undef;
 | 
			
		||||
	complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
 | 
			
		||||
	complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub cmd_multi_fetch {
 | 
			
		||||
	# try to do trunk first, since branches/tags
 | 
			
		||||
	# may be descended from it.
 | 
			
		||||
	if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") {
 | 
			
		||||
		my $gs = Git::SVN->new('trunk');
 | 
			
		||||
		$gs->fetch(@_);
 | 
			
		||||
	my @gs;
 | 
			
		||||
	foreach (command(qw/config -l/)) {
 | 
			
		||||
		next unless m!^svn-remote\.(.+)\.fetch=
 | 
			
		||||
		              \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
 | 
			
		||||
		my ($repo_id, $path, $ref_id) = ($1, $2, $3);
 | 
			
		||||
		push @gs, Git::SVN->new($ref_id, $repo_id, $path);
 | 
			
		||||
	}
 | 
			
		||||
	foreach (@gs) {
 | 
			
		||||
		$_->fetch;
 | 
			
		||||
	}
 | 
			
		||||
	rec_fetch('', "$ENV{GIT_DIR}/svn", @_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# this command is special because it requires no metadata
 | 
			
		||||
| 
						 | 
				
			
			@ -464,95 +471,50 @@ sub cmd_commit_diff {
 | 
			
		|||
 | 
			
		||||
########################### utility functions #########################
 | 
			
		||||
 | 
			
		||||
sub rec_fetch {
 | 
			
		||||
	my ($pfx, $p, @args) = @_;
 | 
			
		||||
	my @dir;
 | 
			
		||||
	foreach (sort <$p/*>) {
 | 
			
		||||
		if (-r "$_/info/url") {
 | 
			
		||||
			$pfx .= '/' if $pfx && $pfx !~ m!/$!;
 | 
			
		||||
			my $id = $pfx . basename $_;
 | 
			
		||||
			next if $id eq 'trunk';
 | 
			
		||||
			my $gs = Git::SVN->new($id);
 | 
			
		||||
			$gs->fetch(@args);
 | 
			
		||||
		} elsif (-d $_) {
 | 
			
		||||
			push @dir, $_;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	foreach (@dir) {
 | 
			
		||||
		my $x = $_;
 | 
			
		||||
		$x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
 | 
			
		||||
		rec_fetch($x, $_, @args);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub complete_svn_url {
 | 
			
		||||
	my ($url, $path) = @_;
 | 
			
		||||
	$path =~ s#/+$##;
 | 
			
		||||
	$url =~ s#/+$## if $url;
 | 
			
		||||
	if ($path !~ m#^[a-z\+]+://#) {
 | 
			
		||||
		$path = '/' . $path if ($path !~ m#^/#);
 | 
			
		||||
		if (!defined $url || $url !~ m#^[a-z\+]+://#) {
 | 
			
		||||
			fatal("E: '$path' is not a complete URL ",
 | 
			
		||||
			      "and a separate URL is not specified\n");
 | 
			
		||||
		}
 | 
			
		||||
		$path = $url . $path;
 | 
			
		||||
		return ($url, $path);
 | 
			
		||||
	}
 | 
			
		||||
	return $path;
 | 
			
		||||
	return ($path, '');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub complete_url_ls_init {
 | 
			
		||||
	my ($ra, $path, $switch, $pfx) = @_;
 | 
			
		||||
	unless ($path) {
 | 
			
		||||
	my ($ra, $repo_path, $switch, $pfx) = @_;
 | 
			
		||||
	unless ($repo_path) {
 | 
			
		||||
		print STDERR "W: $switch not specified\n";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	$path =~ s#/+$##;
 | 
			
		||||
	if ($path =~ m#^[a-z\+]+://#) {
 | 
			
		||||
		$ra = Git::SVN::Ra->new($path);
 | 
			
		||||
		$path = '';
 | 
			
		||||
	$repo_path =~ s#/+$##;
 | 
			
		||||
	if ($repo_path =~ m#^[a-z\+]+://#) {
 | 
			
		||||
		$ra = Git::SVN::Ra->new($repo_path);
 | 
			
		||||
		$repo_path = '';
 | 
			
		||||
	} else {
 | 
			
		||||
		$path =~ s#^/+##;
 | 
			
		||||
		$repo_path =~ s#^/+##;
 | 
			
		||||
		unless ($ra) {
 | 
			
		||||
			fatal("E: '$path' is not a complete URL ",
 | 
			
		||||
			fatal("E: '$repo_path' is not a complete URL ",
 | 
			
		||||
			      "and a separate URL is not specified\n");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
 | 
			
		||||
	my ($dirent, undef, undef) = $ra->get_dir($path, $r);
 | 
			
		||||
	my $url = $ra->{url} . (length $path ? "/$path" : '');
 | 
			
		||||
	my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r);
 | 
			
		||||
	my $url = $ra->{url};
 | 
			
		||||
	foreach my $d (sort keys %$dirent) {
 | 
			
		||||
		next if ($dirent->{$d}->kind != $SVN::Node::dir);
 | 
			
		||||
		my $u =  "$url/$d";
 | 
			
		||||
		my $id = "$pfx$d";
 | 
			
		||||
		my $gs = eval { Git::SVN->new($id) };
 | 
			
		||||
		my $path =  "$repo_path/$d";
 | 
			
		||||
		my $ref = "$pfx$d";
 | 
			
		||||
		my $gs = eval { Git::SVN->new($ref) };
 | 
			
		||||
		# don't try to init already existing refs
 | 
			
		||||
		unless ($gs) {
 | 
			
		||||
			print "init $u => $id\n";
 | 
			
		||||
			Git::SVN->init($id, $u);
 | 
			
		||||
			print "init $url/$path => $ref\n";
 | 
			
		||||
			Git::SVN->init($url, $path, undef, $ref);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	my ($n) = ($switch =~ /^--(\w+)/);
 | 
			
		||||
	command_noisy('config', "svn.$n", $url);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub common_prefix {
 | 
			
		||||
	my $paths = shift;
 | 
			
		||||
	my %common;
 | 
			
		||||
	foreach (@$paths) {
 | 
			
		||||
		my @tmp = split m#/#, $_;
 | 
			
		||||
		my $p = '';
 | 
			
		||||
		while (my $x = shift @tmp) {
 | 
			
		||||
			$p .= "/$x";
 | 
			
		||||
			$common{$p} ||= 0;
 | 
			
		||||
			$common{$p}++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	foreach (sort {length $b <=> length $a} keys %common) {
 | 
			
		||||
		if ($common{$_} == @$paths) {
 | 
			
		||||
			return $_;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return '';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub verify_ref {
 | 
			
		||||
| 
						 | 
				
			
			@ -561,34 +523,6 @@ sub verify_ref {
 | 
			
		|||
	                       { STDERR => 0 }); };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub repo_path_split {
 | 
			
		||||
	my $full_url = shift;
 | 
			
		||||
	$full_url =~ s#/+$##;
 | 
			
		||||
 | 
			
		||||
	foreach (@repo_path_split_cache) {
 | 
			
		||||
		if ($full_url =~ s#$_##) {
 | 
			
		||||
			my $u = $1;
 | 
			
		||||
			$full_url =~ s#^/+##;
 | 
			
		||||
			return ($u, $full_url);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	my $tmp = Git::SVN::Ra->new($full_url);
 | 
			
		||||
	return ($tmp->{repos_root}, $tmp->{svn_path});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub setup_git_svn {
 | 
			
		||||
	defined $SVN_URL or croak "SVN repository location required\n";
 | 
			
		||||
	unless (-d $GIT_DIR) {
 | 
			
		||||
		croak "GIT_DIR=$GIT_DIR does not exist!\n";
 | 
			
		||||
	}
 | 
			
		||||
	mkpath([$GIT_SVN_DIR]);
 | 
			
		||||
	mkpath(["$GIT_SVN_DIR/info"]);
 | 
			
		||||
	open my $fh, '>>',$REVDB or croak $!;
 | 
			
		||||
	close $fh;
 | 
			
		||||
	s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub get_tree_from_treeish {
 | 
			
		||||
	my ($treeish) = @_;
 | 
			
		||||
	# $treeish can be a symbolic ref, too:
 | 
			
		||||
| 
						 | 
				
			
			@ -668,23 +602,6 @@ sub file_to_s {
 | 
			
		|||
	return $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub check_upgrade_needed {
 | 
			
		||||
	if (!-r $REVDB) {
 | 
			
		||||
		-d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
 | 
			
		||||
		open my $fh, '>>',$REVDB or croak $!;
 | 
			
		||||
		close $fh;
 | 
			
		||||
	}
 | 
			
		||||
	return unless eval {
 | 
			
		||||
		command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"],
 | 
			
		||||
		        {STDERR => 0});
 | 
			
		||||
	};
 | 
			
		||||
	my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") };
 | 
			
		||||
	if ($@ || !$head) {
 | 
			
		||||
		print STDERR "Please run: $0 rebuild --upgrade\n";
 | 
			
		||||
		exit 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# '<svn username> = real-name <email address>' mapping based on git-svnimport:
 | 
			
		||||
sub load_authors {
 | 
			
		||||
	open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
 | 
			
		||||
| 
						 | 
				
			
			@ -702,75 +619,9 @@ sub load_authors {
 | 
			
		|||
	close $authors or croak $!;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub git_svn_each {
 | 
			
		||||
	my $sub = shift;
 | 
			
		||||
	foreach (command(qw/rev-parse --symbolic --all/)) {
 | 
			
		||||
		next unless s#^refs/remotes/##;
 | 
			
		||||
		chomp $_;
 | 
			
		||||
		next unless -f "$GIT_DIR/svn/$_/info/url";
 | 
			
		||||
		&$sub($_);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub migrate_revdb {
 | 
			
		||||
	git_svn_each(sub {
 | 
			
		||||
		my $id = shift;
 | 
			
		||||
		defined(my $pid = fork) or croak $!;
 | 
			
		||||
		if (!$pid) {
 | 
			
		||||
			$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
 | 
			
		||||
			init_vars();
 | 
			
		||||
			exit 0 if -r $REVDB;
 | 
			
		||||
			print "Upgrading svn => git mapping...\n";
 | 
			
		||||
			-d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
 | 
			
		||||
			open my $fh, '>>',$REVDB or croak $!;
 | 
			
		||||
			close $fh;
 | 
			
		||||
			rebuild();
 | 
			
		||||
			print "Done upgrading. You may now delete the ",
 | 
			
		||||
				"deprecated $GIT_SVN_DIR/revs directory\n";
 | 
			
		||||
			exit 0;
 | 
			
		||||
		}
 | 
			
		||||
		waitpid $pid, 0;
 | 
			
		||||
		croak $? if $?;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub migration_check {
 | 
			
		||||
	migrate_revdb() unless (-e $REVDB);
 | 
			
		||||
	return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
 | 
			
		||||
	print "Upgrading repository...\n";
 | 
			
		||||
	unless (-d "$GIT_DIR/svn") {
 | 
			
		||||
		mkdir "$GIT_DIR/svn" or croak $!;
 | 
			
		||||
	}
 | 
			
		||||
	print "Data from a previous version of git-svn exists, but\n\t",
 | 
			
		||||
				"$GIT_SVN_DIR\n\t(required for this version ",
 | 
			
		||||
				"($VERSION) of git-svn) does not.\n";
 | 
			
		||||
 | 
			
		||||
	foreach my $x (command(qw/rev-parse --symbolic --all/)) {
 | 
			
		||||
		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;
 | 
			
		||||
		rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
 | 
			
		||||
	}
 | 
			
		||||
	migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
 | 
			
		||||
	print "Done upgrading.\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub init_vars {
 | 
			
		||||
	$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
 | 
			
		||||
	$Git::SVN::default = $GIT_SVN;
 | 
			
		||||
	$GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
 | 
			
		||||
	$REVDB = "$GIT_SVN_DIR/.rev_db";
 | 
			
		||||
	$GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
 | 
			
		||||
	$SVN_URL = undef;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# convert GetOpt::Long specs for use by git-config
 | 
			
		||||
sub read_repo_config {
 | 
			
		||||
	return unless -d $GIT_DIR;
 | 
			
		||||
	return unless -d $ENV{GIT_DIR};
 | 
			
		||||
	my $opts = shift;
 | 
			
		||||
	foreach my $o (keys %$opts) {
 | 
			
		||||
		my $v = $opts->{$o};
 | 
			
		||||
| 
						 | 
				
			
			@ -791,38 +642,6 @@ sub read_repo_config {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub read_url_paths_all {
 | 
			
		||||
	my ($l_map, $pfx, $p) = @_;
 | 
			
		||||
	my @dir;
 | 
			
		||||
	foreach (<$p/*>) {
 | 
			
		||||
		if (-r "$_/info/url") {
 | 
			
		||||
			$pfx .= '/' if $pfx && $pfx !~ m!/$!;
 | 
			
		||||
			my $id = $pfx . basename $_;
 | 
			
		||||
			my $url = file_to_s("$_/info/url");
 | 
			
		||||
			my ($u, $p) = repo_path_split($url);
 | 
			
		||||
			$l_map->{$u}->{$p} = $id;
 | 
			
		||||
		} elsif (-d $_) {
 | 
			
		||||
			push @dir, $_;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	foreach (@dir) {
 | 
			
		||||
		my $x = $_;
 | 
			
		||||
		$x =~ s!^\Q$GIT_DIR\E/svn/!!o;
 | 
			
		||||
		read_url_paths_all($l_map, $x, $_);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# this one only gets ids that have been imported, not new ones
 | 
			
		||||
sub read_url_paths {
 | 
			
		||||
	my $l_map = {};
 | 
			
		||||
	git_svn_each(sub { my $x = shift;
 | 
			
		||||
			my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
 | 
			
		||||
			my ($u, $p) = repo_path_split($url);
 | 
			
		||||
			$l_map->{$u}->{$p} = $x;
 | 
			
		||||
			});
 | 
			
		||||
	return $l_map;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub extract_metadata {
 | 
			
		||||
	my $id = shift or return (undef, undef, undef);
 | 
			
		||||
	my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
 | 
			
		||||
| 
						 | 
				
			
			@ -866,7 +685,7 @@ sub tz_to_s_offset {
 | 
			
		|||
package Git::SVN;
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use vars qw/$default/;
 | 
			
		||||
use vars qw/$default_repo_id/;
 | 
			
		||||
use Carp qw/croak/;
 | 
			
		||||
use File::Path qw/mkpath/;
 | 
			
		||||
use IPC::Open3;
 | 
			
		||||
| 
						 | 
				
			
			@ -882,28 +701,76 @@ BEGIN {
 | 
			
		|||
	                                svn:entry:committed-date/;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# we allow dashes, unlike remotes2config.sh
 | 
			
		||||
sub sanitize_remote_name {
 | 
			
		||||
	my ($name) = @_;
 | 
			
		||||
	$name =~ tr/A-Za-z0-9-/./c;
 | 
			
		||||
	$name;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub init {
 | 
			
		||||
	my ($class, $id, $url) = @_;
 | 
			
		||||
	my $self = _new($class, $id);
 | 
			
		||||
	mkpath(["$self->{dir}/info"]);
 | 
			
		||||
	my ($class, $url, $path, $repo_id, $ref_id) = @_;
 | 
			
		||||
	my $self = _new($class, $repo_id, $ref_id, $path);
 | 
			
		||||
	mkpath([$self->{dir}]);
 | 
			
		||||
	if (defined $url) {
 | 
			
		||||
		$url =~ s!/+$!!; # strip trailing slash
 | 
			
		||||
		::s_to_file($url, "$self->{dir}/info/url");
 | 
			
		||||
		my $orig_url = eval {
 | 
			
		||||
			command_oneline('config', '--get',
 | 
			
		||||
			                "svn-remote.$repo_id.url")
 | 
			
		||||
		};
 | 
			
		||||
		if ($orig_url) {
 | 
			
		||||
			if ($orig_url ne $url) {
 | 
			
		||||
				die "svn-remote.$repo_id.url already set: ",
 | 
			
		||||
				    "$orig_url\nwanted to set to: $url\n";
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			command_noisy('config',
 | 
			
		||||
			              "svn-remote.$repo_id.url", $url);
 | 
			
		||||
		}
 | 
			
		||||
		command_noisy('config', '--add',
 | 
			
		||||
		              "svn-remote.$repo_id.fetch",
 | 
			
		||||
		              "$path:".$self->refname);
 | 
			
		||||
	}
 | 
			
		||||
	$self->{url} = $url;
 | 
			
		||||
	unless (-f $self->{db_path}) {
 | 
			
		||||
		open my $fh, '>>', $self->{db_path} or croak $!;
 | 
			
		||||
		close $fh or croak $!;
 | 
			
		||||
	}
 | 
			
		||||
	$self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub find_ref {
 | 
			
		||||
	my ($ref_id) = @_;
 | 
			
		||||
	foreach (command(qw/config -l/)) {
 | 
			
		||||
		next unless m!^svn-remote\.(.+)\.fetch=
 | 
			
		||||
		              \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
 | 
			
		||||
		my ($repo_id, $path, $ref) = ($1, $2, $3);
 | 
			
		||||
		if ($ref eq $ref_id) {
 | 
			
		||||
			$path = '' if ($path =~ m#^\./?#);
 | 
			
		||||
			return ($repo_id, $path);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	(undef, undef, undef);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
	my ($class, $id) = @_;
 | 
			
		||||
	my $self = _new($class, $id);
 | 
			
		||||
	$self->{url} = ::file_to_s("$self->{dir}/info/url");
 | 
			
		||||
	my ($class, $ref_id, $repo_id, $path) = @_;
 | 
			
		||||
	if (defined $ref_id && !defined $repo_id && !defined $path) {
 | 
			
		||||
		($repo_id, $path) = find_ref($ref_id);
 | 
			
		||||
		if (!defined $repo_id) {
 | 
			
		||||
			die "Could not find a \"svn-remote.*.fetch\" key ",
 | 
			
		||||
			    "in the repository configuration matching: ",
 | 
			
		||||
			    "refs/remotes/$ref_id\n";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	my $self = _new($class, $repo_id, $ref_id, $path);
 | 
			
		||||
	$self->{url} = command_oneline('config', '--get',
 | 
			
		||||
	                               "svn-remote.$repo_id.url") or
 | 
			
		||||
                  die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
 | 
			
		||||
	$self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub refname { "refs/remotes/$_[0]->{id}" }
 | 
			
		||||
sub refname { "refs/remotes/$_[0]->{ref_id}" }
 | 
			
		||||
 | 
			
		||||
sub ra {
 | 
			
		||||
	my ($self) = shift;
 | 
			
		||||
| 
						 | 
				
			
			@ -952,7 +819,7 @@ sub last_rev_commit {
 | 
			
		|||
		return ($self->{last_rev}, $self->{last_commit});
 | 
			
		||||
	}
 | 
			
		||||
	my $c = ::verify_ref($self->refname.'^0');
 | 
			
		||||
	if (defined $c && length $c) {
 | 
			
		||||
	if ($c) {
 | 
			
		||||
		my $rev = (::cmt_metadata($c))[1];
 | 
			
		||||
		if (defined $rev) {
 | 
			
		||||
			($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
 | 
			
		||||
| 
						 | 
				
			
			@ -1064,18 +931,9 @@ sub get_commit_parents {
 | 
			
		|||
	@ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub check_upgrade_needed {
 | 
			
		||||
sub full_url {
 | 
			
		||||
	my ($self) = @_;
 | 
			
		||||
	if (!-r $self->{db_path}) {
 | 
			
		||||
		-d $self->{dir} or mkpath([$self->{dir}]);
 | 
			
		||||
		open my $fh, '>>', $self->{db_path} or croak $!;
 | 
			
		||||
		close $fh;
 | 
			
		||||
	}
 | 
			
		||||
	return unless ::verify_ref($self->{id}.'-HEAD^0');
 | 
			
		||||
	my $head = ::verify_ref($self->refname.'^0');
 | 
			
		||||
	if ($@ || !$head) {
 | 
			
		||||
		::fatal("Please run: $0 rebuild --upgrade\n");
 | 
			
		||||
	}
 | 
			
		||||
	$self->ra->{url} . (length $self->{path} ? '/' . $self->{path} : '');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub do_git_commit {
 | 
			
		||||
| 
						 | 
				
			
			@ -1105,7 +963,7 @@ sub do_git_commit {
 | 
			
		|||
	defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
 | 
			
		||||
	                                                           or croak $!;
 | 
			
		||||
	print $msg_fh $log_entry->{log} or croak $!;
 | 
			
		||||
	print $msg_fh "\ngit-svn-id: ", $self->ra->{url}, '@',
 | 
			
		||||
	print $msg_fh "\ngit-svn-id: ", $self->full_url, '@',
 | 
			
		||||
	              $log_entry->{revision}, ' ',
 | 
			
		||||
		      $self->ra->uuid, "\n" or croak $!;
 | 
			
		||||
	$msg_fh->flush == 0 or croak $!;
 | 
			
		||||
| 
						 | 
				
			
			@ -1128,7 +986,7 @@ sub do_git_commit {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
sub do_fetch {
 | 
			
		||||
	my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_;
 | 
			
		||||
	my ($self, $paths, $rev) = @_;
 | 
			
		||||
	my $ed = SVN::Git::Fetcher->new($self);
 | 
			
		||||
	my ($last_rev, @parents);
 | 
			
		||||
	if ($self->{last_commit}) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1138,7 +996,8 @@ sub do_fetch {
 | 
			
		|||
	} else {
 | 
			
		||||
		$last_rev = $rev;
 | 
			
		||||
	}
 | 
			
		||||
	unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) {
 | 
			
		||||
	unless ($self->ra->gs_do_update($last_rev, $rev,
 | 
			
		||||
	                                $self->{path}, 1, $ed)) {
 | 
			
		||||
		die "SVN connection failed somewhere...\n";
 | 
			
		||||
	}
 | 
			
		||||
	$self->make_log_entry($rev, \@parents, $ed);
 | 
			
		||||
| 
						 | 
				
			
			@ -1361,11 +1220,19 @@ sub rev_db_get {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
sub _new {
 | 
			
		||||
	my ($class, $id) = @_;
 | 
			
		||||
	$id ||= $Git::SVN::default;
 | 
			
		||||
	my $dir = "$ENV{GIT_DIR}/svn/$id";
 | 
			
		||||
	bless { id => $id, dir => $dir, index => "$dir/index",
 | 
			
		||||
	        db_path => "$dir/.rev_db" }, $class;
 | 
			
		||||
	my ($class, $repo_id, $ref_id, $path) = @_;
 | 
			
		||||
	unless (defined $repo_id && length $repo_id) {
 | 
			
		||||
		$repo_id = $Git::SVN::default_repo_id;
 | 
			
		||||
	}
 | 
			
		||||
	unless (defined $ref_id && length $ref_id) {
 | 
			
		||||
		$_[2] = $ref_id = $repo_id;
 | 
			
		||||
	}
 | 
			
		||||
	$_[1] = $repo_id = sanitize_remote_name($repo_id);
 | 
			
		||||
	my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
 | 
			
		||||
	$_[3] = $path = '' unless (defined $path);
 | 
			
		||||
	bless { ref_id => $ref_id, dir => $dir, index => "$dir/index",
 | 
			
		||||
	        path => $path,
 | 
			
		||||
	        db_path => "$dir/.rev_db", repo_id => $repo_id }, $class;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub uri_encode {
 | 
			
		||||
| 
						 | 
				
			
			@ -1630,6 +1497,9 @@ sub new {
 | 
			
		|||
	my $self = SVN::Delta::Editor->new;
 | 
			
		||||
	bless $self, $class;
 | 
			
		||||
	$self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit};
 | 
			
		||||
	if (length $git_svn->{path}) {
 | 
			
		||||
		$self->{path_strip} = qr/\Q$git_svn->{path}\E\/?/;
 | 
			
		||||
	}
 | 
			
		||||
	$self->{empty} = {};
 | 
			
		||||
	$self->{dir_prop} = {};
 | 
			
		||||
	$self->{file_prop} = {};
 | 
			
		||||
| 
						 | 
				
			
			@ -1650,33 +1520,41 @@ sub open_directory {
 | 
			
		|||
	{ path => $path };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub git_path {
 | 
			
		||||
	my ($self, $path) = @_;
 | 
			
		||||
	$path =~ s!$self->{path_strip}!! if $self->{path_strip};
 | 
			
		||||
	$path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub delete_entry {
 | 
			
		||||
	my ($self, $path, $rev, $pb) = @_;
 | 
			
		||||
	my $gui = $self->{gui};
 | 
			
		||||
 | 
			
		||||
	my $gpath = $self->git_path($path);
 | 
			
		||||
	# remove entire directories.
 | 
			
		||||
	if (command('ls-tree', $self->{c}, '--', $path) =~ /^040000 tree/) {
 | 
			
		||||
	if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) {
 | 
			
		||||
		my ($ls, $ctx) = command_output_pipe(qw/ls-tree
 | 
			
		||||
		                                     -r --name-only -z/,
 | 
			
		||||
				                     $self->{c}, '--', $path);
 | 
			
		||||
				                     $self->{c}, '--', $gpath);
 | 
			
		||||
		local $/ = "\0";
 | 
			
		||||
		while (<$ls>) {
 | 
			
		||||
			print $gui '0 ',0 x 40,"\t",$_ or croak $!;
 | 
			
		||||
			print "\tD\t$_\n" unless $self->{q};
 | 
			
		||||
		}
 | 
			
		||||
		print "\tD\t$path/\n" unless $self->{q};
 | 
			
		||||
		print "\tD\t$gpath/\n" unless $self->{q};
 | 
			
		||||
		command_close_pipe($ls, $ctx);
 | 
			
		||||
		$self->{empty}->{$path} = 0
 | 
			
		||||
	} else {
 | 
			
		||||
		print $gui '0 ',0 x 40,"\t",$path,"\0" or croak $!;
 | 
			
		||||
		print "\tD\t$path\n" unless $self->{q};
 | 
			
		||||
		print $gui '0 ',0 x 40,"\t",$gpath,"\0" or croak $!;
 | 
			
		||||
		print "\tD\t$gpath\n" unless $self->{q};
 | 
			
		||||
	}
 | 
			
		||||
	undef;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub open_file {
 | 
			
		||||
	my ($self, $path, $pb, $rev) = @_;
 | 
			
		||||
	my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path)
 | 
			
		||||
	my $gpath = $self->git_path($path);
 | 
			
		||||
	my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
 | 
			
		||||
	                     =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
 | 
			
		||||
	unless (defined $mode && defined $blob) {
 | 
			
		||||
		die "$path was not found in commit $self->{c} (r$rev)\n";
 | 
			
		||||
| 
						 | 
				
			
			@ -1775,7 +1653,7 @@ sub apply_textdelta {
 | 
			
		|||
sub close_file {
 | 
			
		||||
	my ($self, $fb, $exp) = @_;
 | 
			
		||||
	my $hash;
 | 
			
		||||
	my $path = $fb->{path};
 | 
			
		||||
	my $path = $self->git_path($fb->{path});
 | 
			
		||||
	if (my $fh = $fb->{fh}) {
 | 
			
		||||
		seek($fh, 0, 0) or croak $!;
 | 
			
		||||
		my $md5 = Digest::MD5->new;
 | 
			
		||||
| 
						 | 
				
			
			@ -2223,7 +2101,7 @@ sub gs_do_update {
 | 
			
		|||
	                                $editor, $pool);
 | 
			
		||||
	my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
 | 
			
		||||
	my $new = ($rev_a == $rev_b);
 | 
			
		||||
	$reporter->set_path($path, $rev_a, $new, @lock, $pool);
 | 
			
		||||
	$reporter->set_path('', $rev_a, $new, @lock, $pool);
 | 
			
		||||
	$reporter->finish_report($pool);
 | 
			
		||||
	$pool->clear;
 | 
			
		||||
	$editor->{git_commit_ok};
 | 
			
		||||
| 
						 | 
				
			
			@ -2561,6 +2439,153 @@ out:
 | 
			
		|||
	print '-' x72,"\n" unless $incremental || $oneline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
#
 | 
			
		||||
# 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)
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use Carp qw/croak/;
 | 
			
		||||
use File::Path qw/mkpath/;
 | 
			
		||||
use File::Basename qw/dirname/;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
	foreach my $ref_id (sort keys %l_map) {
 | 
			
		||||
		Git::SVN->init($l_map{$ref_id}, $ref_id);
 | 
			
		||||
		$migrated++;
 | 
			
		||||
	}
 | 
			
		||||
	$migrated;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub migration_check {
 | 
			
		||||
	migrate_from_v0();
 | 
			
		||||
	migrate_from_v1();
 | 
			
		||||
	migrate_from_v2();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__END__
 | 
			
		||||
 | 
			
		||||
Data structures:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
#!/bin/sh
 | 
			
		||||
# Copyright (c) 2006 Eric Wong
 | 
			
		||||
test_description='git-svn metadata migrations from previous versions'
 | 
			
		||||
. ./lib-git-svn.sh
 | 
			
		||||
 | 
			
		||||
test_expect_success 'setup old-looking metadata' "
 | 
			
		||||
	cp $GIT_DIR/config $GIT_DIR/config-old-git-svn &&
 | 
			
		||||
	git-svn init $svnrepo &&
 | 
			
		||||
	git-svn fetch &&
 | 
			
		||||
	for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3; do
 | 
			
		||||
		mkdir -p \$i && echo hello >> \$i/README || exit 1; done &&
 | 
			
		||||
	git ls-files -o trunk branches tags | git update-index --add --stdin &&
 | 
			
		||||
	git commit -m 'test' &&
 | 
			
		||||
	git-svn dcommit &&
 | 
			
		||||
	mv $GIT_DIR/svn/* $GIT_DIR/ &&
 | 
			
		||||
	rmdir $GIT_DIR/svn &&
 | 
			
		||||
	git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn &&
 | 
			
		||||
	git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn &&
 | 
			
		||||
	git-update-ref -d refs/remotes/git-svn refs/remotes/git-svn
 | 
			
		||||
	"
 | 
			
		||||
 | 
			
		||||
head=`git rev-parse --verify refs/heads/git-svn-HEAD^0`
 | 
			
		||||
test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'"
 | 
			
		||||
 | 
			
		||||
test_expect_success 'initialize old-style (v0) git-svn layout' "
 | 
			
		||||
	mkdir -p $GIT_DIR/git-svn/info $GIT_DIR/svn/info &&
 | 
			
		||||
	echo $svnrepo > $GIT_DIR/git-svn/info/url &&
 | 
			
		||||
	echo $svnrepo > $GIT_DIR/svn/info/url &&
 | 
			
		||||
	git-svn migrate &&
 | 
			
		||||
	! test -d $GIT_DIR/git-svn &&
 | 
			
		||||
	git-rev-parse --verify refs/remotes/git-svn^0 &&
 | 
			
		||||
	git-rev-parse --verify refs/remotes/svn^0 &&
 | 
			
		||||
	test \`git repo-config --get svn-remote.git-svn.url\` = '$svnrepo' &&
 | 
			
		||||
	test \`git repo-config --get svn-remote.git-svn.fetch\` = \
 | 
			
		||||
             ':refs/remotes/git-svn'
 | 
			
		||||
	"
 | 
			
		||||
 | 
			
		||||
test_expect_success 'initialize a multi-repository repo' "
 | 
			
		||||
	git-svn multi-init $svnrepo -T trunk -t tags -b branches &&
 | 
			
		||||
	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
 | 
			
		||||
	"
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
		git rev-parse --verify refs/remotes/\$i^0 >> refs.out || exit 1;
 | 
			
		||||
	    done &&
 | 
			
		||||
	test -z \"\`sort < refs.out | uniq -d\`\" &&
 | 
			
		||||
	for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do
 | 
			
		||||
	  for j in trunk a b tags/0.1 tags/0.2 tags/0.3; do
 | 
			
		||||
		if test \$j != \$i; then continue; fi
 | 
			
		||||
	    test -z \"\`git diff refs/remotes/\$i \
 | 
			
		||||
	                         refs/remotes/\$j\`\" ||exit 1; done; done
 | 
			
		||||
	"
 | 
			
		||||
 | 
			
		||||
test_done
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue