From 74a81227f95b52b1c3f7ac7ba84ac1a6e1708995 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:28:50 -0800 Subject: [PATCH] git-svn: correctly handle globs with a right-hand-side path component Several bugs were found and fixed while getting this to work: * Remember the 'R'(eplace) case of actions and treat it like we would an 'A'(dd) case. * Fix a small case of follow-parent missing a parent if a subdirectory was modified in the revision where the parent was copied. * dirents returned by get_dir sometimes expire if the data structure is too big and the pool is destroyed, so we cache get_dir (along with check_path and get_revprops) temporarily along with its pool. Signed-off-by: Eric Wong --- git-svn.perl | 84 +++++++++++++++++++++++++++++------------ t/t9108-git-svn-glob.sh | 53 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 24 deletions(-) create mode 100755 t/t9108-git-svn-glob.sh diff --git a/git-svn.perl b/git-svn.perl index ed363e972..50b7dcf25 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1148,7 +1148,8 @@ sub match_paths { my $c = ''; foreach (split m#/#, $self->{path}) { $c .= "/$_"; - next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); + next unless ($paths->{$c} && + ($paths->{$c}->{action} =~ /^[AR]$/)); if ($self->ra->check_path($self->{path}, $r) == $SVN::Node::dir) { return 1; @@ -1176,11 +1177,11 @@ sub find_parent_branch { my $i; while (@b_path_components) { $i = $paths->{'/'.join('/', @b_path_components)}; - last if $i; + last if $i && defined $i->{copyfrom_path}; unshift(@a_path_components, pop(@b_path_components)); } - return undef unless defined $i; - my $branch_from = $i->{copyfrom_path} or return undef; + return undef unless defined $i && defined $i->{copyfrom_path}; + my $branch_from = $i->{copyfrom_path}; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); @@ -2309,8 +2310,7 @@ my $RA; BEGIN { # enforce temporary pool usage for some simple functions my $e; - foreach (qw/get_latest_revnum rev_proplist get_file - check_path get_dir get_uuid get_repos_root/) { + foreach (qw/get_latest_revnum get_uuid get_repos_root/) { $e .= "sub $_ { my \$self = shift; my \$pool = SVN::Pool->new; @@ -2318,7 +2318,30 @@ BEGIN { \$pool->clear; wantarray ? \@ret : \$ret[0]; }\n"; } - eval $e; + + # get_dir needs $pool held in cache for dirents to work, + # check_path is cacheable and rev_proplist is close enough + # for our purposes. + foreach (qw/check_path get_dir rev_proplist/) { + $e .= "my \%${_}_cache; my \$${_}_rev = 0; sub $_ { + my \$self = shift; + my \$r = pop; + my \$k = join(\"\\0\", \@_); + if (my \$x = \$${_}_cache{\$r}->{\$k}) { + return wantarray ? \@\$x : \$x->[0]; + } + my \$pool = SVN::Pool->new; + my \@ret = \$self->SUPER::$_(\@_, \$r, \$pool); + if (\$r != \$${_}_rev) { + \%${_}_cache = ( pool => [] ); + \$${_}_rev = \$r; + } + \$${_}_cache{\$r}->{\$k} = \\\@ret; + push \@{\$${_}_cache{pool}}, \$pool; + wantarray ? \@ret : \$ret[0]; }\n"; + } + $e .= "\n1;"; + eval $e or die $@; } sub new { @@ -2564,8 +2587,34 @@ sub gs_fetch_loop_common { sub match_globs { my ($self, $exists, $paths, $globs, $r) = @_; + + sub get_dir_check { + my ($self, $exists, $g, $r) = @_; + my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; + return unless scalar @x == 3; + my $dirents = $x[0]; + foreach my $de (keys %$dirents) { + next if $dirents->{$de}->kind != $SVN::Node::dir; + my $p = $g->{path}->full_path($de); + next if $exists->{$p}; + next if (length $g->{path}->{right} && + ($self->check_path($p, $r) != + $SVN::Node::dir)); + $exists->{$p} = Git::SVN->init($self->{url}, $p, undef, + $g->{ref}->full_path($de), 1); + } + } foreach my $g (@$globs) { + if (my $path = $paths->{"/$g->{path}->{left}"}) { + if ($path->{action} =~ /^[AR]$/) { + get_dir_check($self, $exists, $g, $r); + } + } foreach (keys %$paths) { + if (/$g->{path}->{left_regex}/) { + next if $paths->{$_}->{action} !~ /^[AR]$/; + get_dir_check($self, $exists, $g, $r); + } next unless /$g->{path}->{regex}/; my $p = $1; my $pathname = $g->{path}->full_path($p); @@ -2578,22 +2627,8 @@ sub match_globs { foreach (split m#/#, $g->{path}->{left}) { $c .= "/$_"; next unless ($paths->{$c} && - ($paths->{$c}->{action} eq 'A')); - my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; - next unless scalar @x == 3; - my $dirents = $x[0]; - foreach my $de (keys %$dirents) { - next if $dirents->{$de}->kind != - $SVN::Node::dir; - my $p = $g->{path}->full_path($de); - next if $exists->{$p}; - next if (length $g->{path}->{right} && - ($self->check_path($p, $r) != - $SVN::Node::dir)); - $exists->{$p} = Git::SVN->init($self->{url}, - $p, undef, - $g->{ref}->full_path($de), 1); - } + ($paths->{$c}->{action} =~ /^[AR]$/)); + get_dir_check($self, $exists, $g, $r); } } values %$exists; @@ -3265,7 +3300,8 @@ sub new { if (length $right && !($right =~ s!^/+!!g)) { die "Missing leading '/' on right side of: '$glob' ($right)\n"; } - bless { left => $left, right => $right, + my $left_re = qr/^\/\Q$left\E(\/|$)/; + bless { left => $left, right => $right, left_regex => $left_re, regex => qr/$re/, glob => $glob }, $class; } diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh new file mode 100755 index 000000000..47cccdfd0 --- /dev/null +++ b/t/t9108-git-svn-glob.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Copyright (c) 2007 Eric Wong +test_description='git-svn globbing refspecs' +. ./lib-git-svn.sh + +cat > expect.end < trunk/src/a/readme && + echo 'goodbye world' > trunk/src/b/readme && + svn import -m 'initial' trunk $svnrepo/trunk && + svn co $svnrepo tmp && + cd tmp && + mkdir branches tags && + svn add branches tags && + svn cp trunk branches/start && + svn commit -m 'start a new branch' && + svn up && + echo 'hi' >> branches/start/src/b/readme && + echo 'hey' >> branches/start/src/a/readme && + svn commit -m 'hi' && + svn up && + svn cp branches/start tags/end && + echo 'bye' >> tags/end/src/b/readme && + echo 'aye' >> tags/end/src/a/readme && + svn commit -m 'the end' && + echo 'byebye' >> tags/end/src/b/readme && + svn commit -m 'nothing to see here' + cd .. && + git config --add svn-remote.svn.url $svnrepo && + git config --add svn-remote.svn.fetch \ + 'trunk/src/a:refs/remotes/trunk' && + git config --add svn-remote.svn.branches \ + 'branches/*/src/a:refs/remotes/branches/*' && + git config --add svn-remote.svn.tags\ + 'tags/*/src/a:refs/remotes/tags/*' && + git-svn multi-fetch && + git log --pretty=oneline refs/remotes/tags/end | \ + sed -e 's/^.\{41\}//' > output.end && + cmp expect.end output.end && + test \"\`git rev-parse refs/remotes/tags/end~1\`\" = \ + \"\`git rev-parse refs/remotes/branches/start\`\" && + test \"\`git rev-parse refs/remotes/branches/start~2\`\" = \ + \"\`git rev-parse refs/remotes/trunk\`\" + " + +test_done -- 2.26.2