git-svn: avoid an extra svn_ra connection during commits

Before, we needed a separate svn_ra instance to run
our check_path calls once the editor was active; but
we can avoid that by running all the check_path calls
before our editor is active.

Signed-off-by: Eric Wong <normalperson@yhbt.net>
diff --git a/git-svn.perl b/git-svn.perl
index 1d448e7..e771bf5 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -352,16 +352,22 @@
 			my $log = get_commit_entry($d)->{log};
 			my $ra = $gs->ra;
 			my $pool = SVN::Pool->new;
+			my $mods = generate_diff("$d~1", $d);
+			my $types = check_diff_paths($ra,
+			                             $gs->{path},
+			                             $last_rev,
+						     $mods);
 			my %ed_opts = ( r => $last_rev,
-			                ra => $ra->dup,
+			                mods => $mods,
+					url => $ra->{url},
+					types => $types,
 			                svn_path => $gs->{path} );
 			my $ed = SVN::Git::Editor->new(\%ed_opts,
 			                 $ra->get_commit_editor($log,
 			                 sub { print "Committed r$_[0]\n";
 					       $last_rev = $_[0]; }),
 			                 $pool);
-			my $mods = $ed->apply_diff("$d~1", $d);
-			if (@$mods == 0) {
+			if (!$ed->apply_diff($mods, $d)) {
 				print "No changes\n$d~1 == $d\n";
 			}
 		}
@@ -469,15 +475,15 @@
 		die "revision argument: $r not understood by git-svn\n";
 	}
 	my $pool = SVN::Pool->new;
-	my %ed_opts = ( r => $r,
-	                ra => $ra->dup,
-	                svn_path => $svn_path );
+	my $mods = generate_diff($ta, $tb);
+	my $types = check_diff_paths($ra, $svn_path, $r, $mods);
+	my %ed_opts = ( r => $r, url => $ra->{url}, svn_path => $svn_path,
+	                mods => $mods, types => $types );
 	my $ed = SVN::Git::Editor->new(\%ed_opts,
 	                               $ra->get_commit_editor($_message,
 	                                 sub { print "Committed r$_[0]\n" }),
 	                               $pool);
-	my $mods = $ed->apply_diff($ta, $tb);
-	if (@$mods == 0) {
+	if (!$ed->apply_diff($mods, $tb)) {
 		print "No changes\n$ta == $tb\n";
 	}
 	$pool->clear;
@@ -708,6 +714,92 @@
 	return ($1 * 60) + ($tz * 3600);
 }
 
+sub generate_diff {
+	my ($tree_a, $tree_b) = @_;
+	my @diff_tree = qw(diff-tree -z -r);
+	if ($_cp_similarity) {
+		push @diff_tree, "-C$_cp_similarity";
+	} else {
+		push @diff_tree, '-C';
+	}
+	push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+	push @diff_tree, "-l$_l" if defined $_l;
+	push @diff_tree, $tree_a, $tree_b;
+	my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
+	local $/ = "\0";
+	my $state = 'meta';
+	my @mods;
+	while (<$diff_fh>) {
+		chomp $_; # this gets rid of the trailing "\0"
+		if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
+					$sha1\s($sha1)\s
+					([MTCRAD])\d*$/xo) {
+			push @mods, {	mode_a => $1, mode_b => $2,
+					sha1_b => $3, chg => $4 };
+			if ($4 =~ /^(?:C|R)$/) {
+				$state = 'file_a';
+			} else {
+				$state = 'file_b';
+			}
+		} elsif ($state eq 'file_a') {
+			my $x = $mods[$#mods] or croak "Empty array\n";
+			if ($x->{chg} !~ /^(?:C|R)$/) {
+				croak "Error parsing $_, $x->{chg}\n";
+			}
+			$x->{file_a} = $_;
+			$state = 'file_b';
+		} elsif ($state eq 'file_b') {
+			my $x = $mods[$#mods] or croak "Empty array\n";
+			if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
+				croak "Error parsing $_, $x->{chg}\n";
+			}
+			if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
+				croak "Error parsing $_, $x->{chg}\n";
+			}
+			$x->{file_b} = $_;
+			$state = 'meta';
+		} else {
+			croak "Error parsing $_\n";
+		}
+	}
+	command_close_pipe($diff_fh, $ctx);
+	\@mods;
+}
+
+sub check_diff_paths {
+	my ($ra, $pfx, $rev, $mods) = @_;
+	my %types;
+	$pfx .= '/' if length $pfx;
+
+	sub type_diff_paths {
+		my ($ra, $types, $path, $rev) = @_;
+		my @p = split m#/+#, $path;
+		my $c = shift @p;
+		unless (defined $types->{$c}) {
+			$types->{$c} = $ra->check_path($c, $rev);
+		}
+		while (@p) {
+			$c .= '/' . shift @p;
+			next if defined $types->{$c};
+			$types->{$c} = $ra->check_path($c, $rev);
+		}
+	}
+
+	foreach my $m (@$mods) {
+		foreach my $f (qw/file_a file_b/) {
+			next unless defined $m->{$f};
+			my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);
+			if (length $pfx.$dir && ! defined $types{$dir}) {
+				type_diff_paths($ra, \%types, $pfx.$dir, $rev);
+			}
+		}
+	}
+	use Data::Dumper;
+	warn Dumper \%types;
+	warn Dumper $mods;
+	\%types;
+}
+
 package Git::SVN;
 use strict;
 use warnings;
@@ -1375,18 +1467,20 @@
 		fatal("Must have an existing revision to commit\n");
 	}
 	my $pool = SVN::Pool->new;
-	my $ed = SVN::Git::Editor->new({ r => $self->{last_rev},
-	                                 ra => $self->ra->dup,
-	                                 svn_path => $self->{path}
-	                               },
+	my $mods = ::generate_diff($self->{last_commit}, $tree);
+	my $types = ::check_diff_paths($self->ra, $self->{path},
+	                             $self->{last_rev}, $mods);
+	my %ed_opts = ( r => $self->{last_rev}, url => $self->ra->{url},
+	                svn_path => $self->{path},
+	                mods => $mods, types => $types );
+	my $ed = SVN::Git::Editor->new(\%ed_opts,
 	                               $self->ra->get_commit_editor(
 	                                 $log_entry->{log}, sub {
 	                                   $self->set_tree_cb($log_entry,
 					                      $tree, @_);
 	                               }),
 	                               $pool);
-	my $mods = $ed->apply_diff($self->{last_commit}, $tree);
-	if (@$mods == 0) {
+	if (!$ed->apply_diff($mods, $tree)) {
 		print "No changes\nr$self->{last_rev} = $tree\n";
 	}
 	$pool->clear;
@@ -1898,7 +1992,7 @@
 	my $git_svn = shift;
 	my $self = SVN::Delta::Editor->new(@_);
 	bless $self, $class;
-	foreach (qw/svn_path r ra/) {
+	foreach (qw/svn_path mods url types r/) {
 		die "$_ required!\n" unless (defined $git_svn->{$_});
 		$self->{$_} = $git_svn->{$_};
 	}
@@ -1922,7 +2016,7 @@
 
 sub url_path {
 	my ($self, $path) = @_;
-	$self->{ra}->{url} . '/' . $self->repo_path($path);
+	$self->{url} . '/' . $self->repo_path($path);
 }
 
 sub rmdirs {
@@ -1972,7 +2066,10 @@
 
 sub open_or_add_dir {
 	my ($self, $full_path, $baton) = @_;
-	my $t = $self->{ra}->check_path($full_path, $self->{r});
+	my $t = $self->{types}->{$full_path};
+	if (!defined $t) {
+		die "$full_path not known in r$self->{r} or we have a bug!\n";
+	}
 	if ($t == $SVN::Node::none) {
 		return $self->add_directory($full_path, $baton,
 						undef, -1, $self->{pool});
@@ -1989,9 +2086,9 @@
 sub ensure_path {
 	my ($self, $path) = @_;
 	my $bat = $self->{bat};
-	$path = $self->repo_path($path);
-	return $bat->{''} unless (length $path);
-	my @p = split m#/+#, $path;
+	my $repo_path = $self->repo_path($path);
+	return $bat->{''} unless (length $repo_path);
+	my @p = split m#/+#, $repo_path;
 	my $c = shift @p;
 	$bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
 	while (@p) {
@@ -2129,59 +2226,9 @@
 
 # this drives the editor
 sub apply_diff {
-	my ($self, $tree_a, $tree_b) = @_;
-	my @diff_tree = qw(diff-tree -z -r);
-	if ($::_cp_similarity) {
-		push @diff_tree, "-C$::_cp_similarity";
-	} else {
-		push @diff_tree, '-C';
-	}
-	push @diff_tree, '--find-copies-harder' if $::_find_copies_harder;
-	push @diff_tree, "-l$::_l" if defined $::_l;
-	push @diff_tree, $tree_a, $tree_b;
-	my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
-	my $nl = $/;
-	local $/ = "\0";
-	my $state = 'meta';
-	my @mods;
-	while (<$diff_fh>) {
-		chomp $_; # this gets rid of the trailing "\0"
-		if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
-					$::sha1\s($::sha1)\s
-					([MTCRAD])\d*$/xo) {
-			push @mods, {	mode_a => $1, mode_b => $2,
-					sha1_b => $3, chg => $4 };
-			if ($4 =~ /^(?:C|R)$/) {
-				$state = 'file_a';
-			} else {
-				$state = 'file_b';
-			}
-		} elsif ($state eq 'file_a') {
-			my $x = $mods[$#mods] or croak "Empty array\n";
-			if ($x->{chg} !~ /^(?:C|R)$/) {
-				croak "Error parsing $_, $x->{chg}\n";
-			}
-			$x->{file_a} = $_;
-			$state = 'file_b';
-		} elsif ($state eq 'file_b') {
-			my $x = $mods[$#mods] or croak "Empty array\n";
-			if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
-				croak "Error parsing $_, $x->{chg}\n";
-			}
-			if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
-				croak "Error parsing $_, $x->{chg}\n";
-			}
-			$x->{file_b} = $_;
-			$state = 'meta';
-		} else {
-			croak "Error parsing $_\n";
-		}
-	}
-	command_close_pipe($diff_fh, $ctx);
-	$/ = $nl;
-
+	my ($self, $mods, $tree_b) = @_;
 	my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
-	foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @mods) {
+	foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
 		my $f = $m->{chg};
 		if (defined $o{$f}) {
 			$self->$f($m);
@@ -2190,12 +2237,12 @@
 		}
 	}
 	$self->rmdirs($tree_b) if $::_rmdir;
-	if (@mods == 0) {
+	if (@$mods == 0) {
 		$self->abort_edit;
 	} else {
 		$self->close_edit;
 	}
-	\@mods;
+	return scalar @$mods;
 }
 
 package Git::SVN::Ra;
@@ -2256,14 +2303,6 @@
 	# do not call the real DESTROY since we store ourselves in %RA
 }
 
-sub dup {
-	my ($self) = @_;
-	my $dup = SVN::Ra->new(pool => SVN::Pool->new,
-				map { $_ => $self->{$_} } qw/config url
-	             auth auth_provider_callbacks repos_root svn_path/);
-	bless $dup, ref $self;
-}
-
 sub get_log {
 	my ($self, @args) = @_;
 	my $pool = SVN::Pool->new;
@@ -2922,6 +2961,8 @@
 	author => 'committer name'
 };
 
+
+# this is generated by generate_diff();
 @mods = array of diff-index line hashes, each element represents one line
 	of diff-index output