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 <normalperson@yhbt.net>
This commit is contained in:
parent
d2ae14346c
commit
e518192f3b
148
git-svn.perl
148
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;
|
||||
|
Loading…
Reference in New Issue
Block a user