From e518192f3be92097ba550098dbb69d769ca18429 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 8 Feb 2007 12:53:57 -0800 Subject: [PATCH] git-svn: implement auto-discovery of branches/tags This is similar to the way git proper handles refs, except we use the keys 'branches' and 'tags' to distinguish when we want to use wildcards. The left-hand side of the ':' contains the remote path, and must have one asterisk ('*') in it for the branch name. The asterisk may be in any component of the path as long as is it on its own directory level. The right-hand side contains the refname and must have the asterisk as the last path component. branches = branches/*:refs/remotes/* tags = tags/*:refs/remotes/tags/* Signed-off-by: Eric Wong --- git-svn.perl | 148 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 28 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 5e1a64c6d..21d53054f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -701,14 +701,32 @@ sub resolve_local_globs { sub fetch_all { my ($repo_id, $remotes) = @_; - my $fetch = $remotes->{$repo_id}->{fetch}; - my $url = $remotes->{$repo_id}->{url}; - my @gs; - resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{branches}); - resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{tags}); + my $remote = $remotes->{$repo_id}; + my $fetch = $remote->{fetch}; + my $url = $remote->{url}; + my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); + my $uuid = $ra->uuid; my $head = $ra->get_latest_revnum; my $base = $head; + + # read the max revs for wildcard expansion (branches/*, tags/*) + foreach my $t (qw/branches tags/) { + defined $remote->{$t} or next; + push @globs, $remote->{$t}; + my $f = "$ENV{GIT_DIR}/svn/.$uuid.$t"; + if (open my $fh, '<', $f) { + chomp(my $max_rev = <$fh>); + close $fh or die "Error closing $f: $!\n"; + + if ($max_rev !~ /^\d+$/) { + die "$max_rev (in $f) is not an integer!\n"; + } + $remote->{$t}->{max_rev} = $max_rev; + $base = $max_rev if ($max_rev < $base); + } + } + foreach my $p (sort keys %$fetch) { my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); my $lr = $gs->rev_db_max; @@ -717,8 +735,7 @@ sub fetch_all { } push @gs, $gs; } - return if (++$base > $head); - $ra->gs_fetch_loop_common($base, $head, @gs); + $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs); } sub read_all_remotes { @@ -732,6 +749,7 @@ sub read_all_remotes { (.*):refs/remotes/(.+)\s*$/!x) { my ($p, $g) = ($3, $4); my $rs = $r->{$1}->{$2} = { + t => $2, path => Git::SVN::GlobSpec->new($p), ref => Git::SVN::GlobSpec->new($g) }; if (length($rs->{ref}->{right}) != 0) { @@ -793,20 +811,26 @@ sub init_remote_config { my $r = read_all_remotes(); my $existing = find_existing_remote($url, $r); if ($existing) { - print STDERR "Using existing ", - "[svn-remote \"$existing\"]\n"; + unless ($no_write) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + } $self->{repo_id} = $existing; } else { my $min_url = Git::SVN::Ra->new($url)->minimize_url; $existing = find_existing_remote($min_url, $r); if ($existing) { - print STDERR "Using existing ", - "[svn-remote \"$existing\"]\n"; + unless ($no_write) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + } $self->{repo_id} = $existing; } if ($min_url ne $url) { - print STDERR "Using higher level of URL: ", - "$url => $min_url\n"; + unless ($no_write) { + print STDERR "Using higher level of URL: ", + "$url => $min_url\n"; + } my $old_path = $self->{path}; $self->{path} = $url; $self->{path} =~ s!^\Q$min_url\E/*!!; @@ -1122,8 +1146,8 @@ sub match_paths { foreach (split m#/#, $self->{path}) { $c .= "/$_"; next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); - my @x = eval { $self->ra->get_dir($self->{path}, $r) }; - if (scalar @x == 3) { + if ($self->ra->check_path($self->{path}, $r) == + $SVN::Node::dir) { return 1; } } @@ -1172,6 +1196,10 @@ sub find_parent_branch { my $u = $remotes->{$repo_id}->{url} or next; next if $url ne $u; my $fetch = $remotes->{$repo_id}->{fetch}; + foreach (qw/branches tags/) { + resolve_local_globs($url, $fetch, + $remotes->{$repo_id}->{$_}); + } foreach my $f (keys %$fetch) { next if $f ne $branch_from; $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); @@ -1238,7 +1266,7 @@ sub do_fetch { my ($self, $paths, $rev) = @_; my $ed; my ($last_rev, @parents); - if ($self->{last_commit}) { + if ($self->last_commit) { $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; $ed->{c} = $self->{last_commit}; @@ -1354,8 +1382,7 @@ sub fetch { my ($self, $min_rev, $max_rev, @parents) = @_; my ($last_rev, $last_commit) = $self->last_rev_commit; my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); - return if ($base > $head); - $self->ra->gs_fetch_loop_common($base, $head, $self); + $self->ra->gs_fetch_loop_common($base, $head, [$self]); } sub set_tree_cb { @@ -2430,12 +2457,14 @@ sub gs_do_switch { } sub gs_fetch_loop_common { - my ($self, $base, $head, @gs) = @_; + my ($self, $base, $head, $gsv, $globs) = @_; + return if ($base > $head); my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my %common; - foreach my $gs (@gs) { + my $common_max = scalar @$gsv; + + foreach my $gs (@$gsv) { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } @@ -2447,9 +2476,21 @@ sub gs_fetch_loop_common { $common{$p}++; } } + $globs ||= []; + $common_max += scalar @$globs; + foreach my $glob (@$globs) { + my @tmp = split m#/#, $glob->{path}->{left}; + my $p = ''; + foreach (@tmp) { + $p .= length($p) ? "/$_" : $_; + $common{$p} ||= 0; + $common{$p}++; + } + } + my $longest_path = ''; foreach (sort {length $b <=> length $a} keys %common) { - if ($common{$_} == @gs) { + if ($common{$_} == $common_max) { $longest_path = $_; last; } @@ -2491,9 +2532,12 @@ sub gs_fetch_loop_common { } $SVN::Error::handler = $err_handler; + my %exists = map { $_->{path} => $_ } @$gsv; foreach my $r (sort {$a <=> $b} keys %revs) { my ($paths, $logged) = @{$revs{$r}}; - foreach my $gs (@gs) { + + foreach my $gs ($self->match_globs(\%exists, $paths, + $globs, $r)) { if ($gs->rev_db_max >= $r) { next; } @@ -2504,10 +2548,22 @@ sub gs_fetch_loop_common { $gs->do_git_commit($log_entry); } } + foreach my $g (@$globs) { + my $f = "$ENV{GIT_DIR}/svn/." . + $self->uuid . ".$g->{t}"; + open my $fh, '>', "$f.tmp" or + die "Can't open $f.tmp for writing: $!"; + print $fh "$r\n" or + die "Couldn't write to $f: $!\n"; + close $fh or die "Error closing $f: $!\n"; + rename "$f.tmp", $f or + die "Couldn't rename ", + "$f.tmp => $f: $!\n"; + } } # pre-fill the .rev_db since it'll eventually get filled in # with '0' x40 if something new gets committed - foreach my $gs (@gs) { + foreach my $gs (@$gsv) { next if defined $gs->rev_db_get($max); $gs->rev_db_set($max, 0 x40); } @@ -2518,6 +2574,43 @@ sub gs_fetch_loop_common { } } +sub match_globs { + my ($self, $exists, $paths, $globs, $r) = @_; + foreach my $g (@$globs) { + foreach (keys %$paths) { + next unless /$g->{path}->{regex}/; + my $p = $1; + my $pathname = $g->{path}->full_path($p); + next if $exists->{$pathname}; + $exists->{$pathname} = Git::SVN->init( + $self->{url}, $pathname, undef, + $g->{ref}->full_path($p), 1); + } + my $c = ''; + 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); + } + } + } + values %$exists; +} + sub minimize_url { my ($self) = @_; return $self->{url} if ($self->{url} eq $self->{repos_root}); @@ -3167,16 +3260,15 @@ use warnings; sub new { my ($class, $glob) = @_; - warn "glob: $glob\n"; my $re = $glob; $re =~ s!/+$!!g; # no need for trailing slashes my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); my ($left, $right) = ($1, $2); if ($nr > 1) { - warn "Only one '*' wildcard expansion ", - "is supported (got $nr): '$glob'\n"; + die "Only one '*' wildcard expansion ", + "is supported (got $nr): '$glob'\n"; } elsif ($nr == 0) { - warn "One '*' is needed for glob: '$glob'\n"; + die "One '*' is needed for glob: '$glob'\n"; } $re = quotemeta($left) . $re . quotemeta($right); $left =~ s!/+$!!g; -- 2.30.2