git-svn: correctly handle globs with a right-hand-side path component
authorEric Wong <normalperson@yhbt.net>
Sat, 10 Feb 2007 21:28:50 +0000 (13:28 -0800)
committerEric Wong <normalperson@yhbt.net>
Fri, 23 Feb 2007 08:57:12 +0000 (00:57 -0800)
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 <normalperson@yhbt.net>
git-svn.perl
t/t9108-git-svn-glob.sh [new file with mode: 0755]

index ed363e972df88cb0eb8e3f2a89dbcf00e162c192..50b7dcf255eacbeb6bdf25e7f420410a2f05c5d9 100755 (executable)
@@ -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 (executable)
index 0000000..47cccdf
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Copyright (c) 2007 Eric Wong
+test_description='git-svn globbing refspecs'
+. ./lib-git-svn.sh
+
+cat > expect.end <<EOF
+the end
+hi
+start a new branch
+initial
+EOF
+
+test_expect_success 'test refspec globbing' "
+       mkdir -p trunk/src/a trunk/src/b trunk/doc &&
+       echo 'hello world' > 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