Commits

Anonymous committed a00439a

git-svn: add --follow-parent and --no-metadata options to fetch

--follow-parent:
This is especially helpful when we're tracking a directory
that has been moved around within the repository, or if we
started tracking a branch and never tracked the trunk it was
descended from.

This relies on the SVN::* libraries to work. We can't
reliably parse path info from the svn command-line client
without relying on XML, so it's better just to have the SVN::*
libs installed.

This also removes oldvalue verification when calling update-ref

In SVN, branches can be deleted, and then recreated under the
same path as the original one with different ancestry
information, causing parent information to be mismatched /
misordered.

Also force the current ref, if existing, to be a parent,
regardless of whether or not it was specified.

--no-metadata:
This gets rid of the git-svn-id: lines at the end of every commit.

With this, you lose the ability to use the rebuild command. If
you ever lose your .git/svn/git-svn/.rev_db file, you won't be
able to fetch again, either. This is fine for one-shot imports.

Also fix some issues with multi-fetch --follow-parent that were
exposed while testing this. Additionally, repack checking is
simplified greatly.

git-svn log will not work on repositories using this, either.

Signed-off-by: Eric Wong <normalperson@yhbt.net>
Signed-off-by: Junio C Hamano <junkio@cox.net>

Comments (0)

Files changed (2)

contrib/git-svn/git-svn.perl

 # make sure the svn binary gives consistent output between locales and TZs:
 $ENV{TZ} = 'UTC';
 $ENV{LC_ALL} = 'C';
+$| = 1; # unbuffer STDOUT
 
 # If SVN:: library support is added, please make the dependencies
 # optional and preserve the capability to use the command-line client.
 my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
 	$_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
 	$_repack, $_repack_nr, $_repack_flags,
-	$_message, $_file,
+	$_message, $_file, $_follow_parent, $_no_metadata,
 	$_template, $_shared, $_no_default_regex, $_no_graft_copy,
 	$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
 	$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m);
 
 my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
 		'branch|b=s' => \@_branch_from,
+		'follow-parent|follow' => \$_follow_parent,
 		'branch-all-refs|B' => \$_branch_all_refs,
 		'authors-file|A=s' => \$_authors,
 		'repack:i' => \$_repack,
+		'no-metadata' => \$_no_metadata,
 		'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 
 my ($_trunk, $_tags, $_branches);
 	my $id = shift;
 	print "Fetching $id\n";
 	my $ref = "$GIT_DIR/refs/remotes/$id";
-	my $ca = file_to_s($ref) if (-r $ref);
-	defined(my $pid = fork) or croak $!;
+	defined(my $pid = open my $fh, '-|') or croak $!;
 	if (!$pid) {
+		$_repack = undef;
 		$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
 		init_vars();
 		fetch(@_);
 		exit 0;
 	}
-	waitpid $pid, 0;
-	croak $? if $?;
-	return unless $_repack || -r $ref;
-
-	my $cb = file_to_s($ref);
-
-	defined($pid = open my $fh, '-|') or croak $!;
-	my $url = file_to_s("$GIT_DIR/svn/$id/info/url");
-	$url = qr/\Q$url\E/;
-	if (!$pid) {
-		exec qw/git-rev-list --pretty=raw/,
-				$ca ? "$ca..$cb" : $cb or croak $!;
-	}
 	while (<$fh>) {
-		if (/^    git-svn-id: $url\@\d+ [a-f0-9\-]+$/) {
-			check_repack();
-		} elsif (/^    git-svn-id: \S+\@\d+ [a-f0-9\-]+$/) {
-			last;
-		}
+		print $_;
+		check_repack() if (/^r\d+ = $sha1/);
 	}
-	close $fh;
+	close $fh or croak $?;
 }
 
 sub rec_fetch {
 		croak $? if $?;
 		restore_index($index);
 	}
+
+	# just in case we clobber the existing ref, we still want that ref
+	# as our parent:
+	if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
+		push @tmp_parents, $cur;
+	}
+
 	if (exists $tree_map{$tree}) {
 		foreach my $p (@{$tree_map{$tree}}) {
 			my $skip;
 		last if @exec_parents > 16;
 	}
 
-	defined(my $pid = open my $out_fh, '-|') or croak $!;
-	if ($pid == 0) {
-		my $msg_fh = IO::File->new_tmpfile or croak $!;
-		print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ",
-					"$SVN_URL\@$log_msg->{revision}",
+	set_commit_env($log_msg);
+	my @exec = ('git-commit-tree', $tree);
+	push @exec, '-p', $_  foreach @exec_parents;
+	defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
+								or croak $!;
+	print $msg_fh $log_msg->{msg} or croak $!;
+	unless ($_no_metadata) {
+		print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
 					" $SVN_UUID\n" or croak $!;
-		$msg_fh->flush == 0 or croak $!;
-		seek $msg_fh, 0, 0 or croak $!;
-		set_commit_env($log_msg);
-		my @exec = ('git-commit-tree',$tree);
-		push @exec, '-p', $_  foreach @exec_parents;
-		open STDIN, '<&', $msg_fh or croak $!;
-		exec @exec or croak $!;
 	}
+	$msg_fh->flush == 0 or croak $!;
+	close $msg_fh or croak $!;
 	chomp(my $commit = do { local $/; <$out_fh> });
-	close $out_fh or croak $?;
+	close $out_fh or croak $!;
+	waitpid $pid, 0;
+	croak $? if $?;
 	if ($commit !~ /^$sha1$/o) {
-		croak "Failed to commit, invalid sha1: $commit\n";
+		die "Failed to commit, invalid sha1: $commit\n";
 	}
-	my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
-	if (my $primary_parent = shift @exec_parents) {
-		quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0");
-		push @update_ref, $primary_parent unless $?;
-	}
-	sys(@update_ref);
+	sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
 	revdb_set($REVDB, $log_msg->{revision}, $commit);
 
 	# this output is read via pipe, do not change:
 }
 
 sub svn_compat_check {
+	if ($_follow_parent) {
+		print STDERR 'E: --follow-parent functionality is only ',
+				"available when SVN libraries are used\n";
+		exit 1;
+	}
 	my @co_help = safe_qx(qw(svn co -h));
 	unless (grep /ignore-externals/,@co_help) {
 		print STDERR "W: Installed svn version does not support ",
 	close $fh or croak $!;
 }
 
+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;
 	# redirect STDOUT for SVN 1.1.x compatibility
 	open my $stdout, '>&', \*STDOUT or croak $!;
 	open STDOUT, '>&', $in or croak $!;
-	$| = 1; # not sure if this is necessary, better safe than sorry...
 	my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
 	$in->flush == 0 or croak $!;
 	open STDOUT, '>&', $stdout or croak $!;
 	close $fh;
 	if (defined $c && length $c) {
 		my ($url, $rev, $uuid) = cmt_metadata($c);
+		return ($rev, $c) if defined $rev;
+	}
+	if ($_no_metadata) {
+		my $offset = -41; # from tail
+		my $rl;
+		open my $fh, '<', $REVDB or
+			die "--no-metadata specified and $REVDB not readable\n";
+		seek $fh, $offset, 2;
+		$rl = readline $fh;
+		defined $rl or return (undef, undef);
+		chomp $rl;
+		while ($c ne $rl && tell $fh != 0) {
+			$offset -= 41;
+			seek $fh, $offset, 2;
+			$rl = readline $fh;
+			defined $rl or return (undef, undef);
+			chomp $rl;
+		}
+		my $rev = tell $fh;
+		croak $! if ($rev < -1);
+		$rev =  ($rev - 41) / 41;
+		close $fh or croak $!;
 		return ($rev, $c);
 	}
 	return (undef, undef);
 	print STDERR  "Found possible branch point: ",
 				"$branch_from => $svn_path, $r\n";
 	$branch_from =~ s#^/##;
-	my $l_map = read_url_paths();
+	my $l_map = {};
+	read_url_paths_all($l_map, '', "$GIT_DIR/svn");
 	my $url = $SVN->{url};
 	defined $l_map->{$url} or return;
-	my $id = $l_map->{$url}->{$branch_from} or return;
+	my $id = $l_map->{$url}->{$branch_from};
+	if (!defined $id && $_follow_parent) {
+		print STDERR "Following parent: $branch_from\@$r\n";
+		# auto create a new branch and follow it
+		$id = basename($branch_from);
+		$id .= '@'.$r if -r "$GIT_DIR/svn/$id";
+		while (-r "$GIT_DIR/svn/$id") {
+			# just grow a tail if we're not unique enough :x
+			$id .= '-';
+		}
+	}
+	return unless defined $id;
+
 	my ($r0, $parent) = find_rev_before($r,$id,1);
+	if ($_follow_parent && (!defined $r0 || !defined $parent)) {
+		defined(my $pid = fork) or croak $!;
+		if (!$pid) {
+			$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
+			init_vars();
+			$SVN_URL = "$url/$branch_from";
+			$SVN_LOG = $SVN = undef;
+			setup_git_svn();
+			# we can't assume SVN_URL exists at r+1:
+			$_revision = "0:$r";
+			fetch_lib();
+			exit 0;
+		}
+		waitpid $pid, 0;
+		croak $? if $?;
+		($r0, $parent) = find_rev_before($r,$id,1);
+	}
 	return unless (defined $r0 && defined $parent);
 	if (revisions_eq($branch_from, $r0, $r)) {
 		unlink $GIT_SVN_INDEX;
-		print STDERR "Found branch parent: $parent\n";
+		print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
 		sys(qw/git-read-tree/, $parent);
 		return libsvn_fetch($parent, $paths, $rev,
 					$author, $date, $msg);
 }
 ;
 
+# retval of read_url_paths{,_all}();
+$l_map = {
+	# repository root url
+	'https://svn.musicpd.org' => {
+		# repository path 		# GIT_SVN_ID
+		'mpd/trunk'		=>	'trunk',
+		'mpd/tags/0.11.5'	=>	'tags/0.11.5',
+	},
+}
+
 Notes:
 	I don't trust the each() function on unless I created %hash myself
 	because the internal iterator may not have started at base.

contrib/git-svn/t/t0004-follow-parent.sh

+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-svn --follow-parent fetching'
+. ./lib-git-svn.sh
+
+if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
+then
+	echo 'Skipping: --follow-parent needs SVN libraries'
+	test_done
+	exit 0
+fi
+
+test_expect_success 'initialize repo' "
+	mkdir import &&
+	cd import &&
+	mkdir -p trunk &&
+	echo hello > trunk/readme &&
+	svn import -m 'initial' . $svnrepo &&
+	cd .. &&
+	svn co $svnrepo wc &&
+	cd wc &&
+	echo world >> trunk/readme &&
+	svn commit -m 'another commit' &&
+	svn up &&
+	svn mv -m 'rename to thunk' trunk thunk &&
+	svn up &&
+	echo goodbye >> thunk/readme &&
+	svn commit -m 'bye now' &&
+	cd ..
+	"
+
+test_expect_success 'init and fetch --follow-parent a moved directory' "
+	git-svn init -i thunk $svnrepo/thunk &&
+	git-svn fetch --follow-parent -i thunk &&
+	git-rev-parse --verify refs/remotes/trunk &&
+	test '$?' -eq '0'
+	"
+
+test_debug 'gitk --all &'
+
+test_done