diff --git a/git-svn.perl b/git-svn.perl index 66b4c20fd9..6874eabffa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -367,9 +367,10 @@ sub cmd_multi_init { sub cmd_multi_fetch { my $remotes = Git::SVN::read_all_remotes(); foreach my $repo_id (sort keys %$remotes) { - my $url = $remotes->{$repo_id}->{url} or next; - my $fetch = $remotes->{$repo_id}->{fetch} or next; - Git::SVN::fetch_all($repo_id, $url, $fetch); + if ($remotes->{$repo_id}->{url} && + $remotes->{$repo_id}->{fetch}) { + Git::SVN::fetch_all($repo_id, $remotes); + } } } @@ -479,9 +480,12 @@ sub complete_url_ls_init { # don't try to init already existing refs unless ($gs) { print "init $url/$path => $ref\n"; - $gs = Git::SVN->init($url, $path, undef, $ref); + $gs = Git::SVN->init($url, $path, undef, $ref, 1); + } + if ($gs) { + $remote_id = $gs->{repo_id}; + last; } - $remote_id ||= $gs->{repo_id} if $gs; } if (defined $remote_id) { $remote_path = "$ra->{svn_path}/$repo_path/*"; @@ -489,7 +493,7 @@ sub complete_url_ls_init { $remote_path =~ s#^/##g; my ($n) = ($switch =~ /^--(\w+)/); command_noisy('config', "svn-remote.$remote_id.$n", - $remote_path); + "$remote_path:refs/remotes/$pfx*"); } } @@ -660,9 +664,48 @@ BEGIN { my %LOCKFILES; END { unlink keys %LOCKFILES if %LOCKFILES } +sub resolve_local_globs { + my ($url, $fetch, $glob_spec) = @_; + return unless defined $glob_spec; + my $ref = $glob_spec->{ref}; + my $path = $glob_spec->{path}; + foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) { + next unless m#^refs/remotes/$ref->{regex}$#; + my $p = $1; + my $pathname = $path->full_path($p); + my $refname = $ref->full_path($p); + if (my $existing = $fetch->{$pathname}) { + if ($existing ne $refname) { + die "Refspec conflict:\n", + "existing: refs/remotes/$existing\n", + " globbed: refs/remotes/$refname\n"; + } + my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; + $u =~ s!^\Q$url\E/?!! or die + "refs/remotes/$refname: '$url' not found in '$u'\n"; + if ($pathname ne $u) { + warn "W: Refspec glob conflict ", + "(ref: refs/remotes/$refname):\n", + "expected path: $pathname\n", + " real path: $u\n", + "Continuing ahead with $u\n"; + next; + } + } else { + warn "Globbed ($path->{glob}:$ref->{glob}): ", + "$pathname == $refname\n"; + $fetch->{$pathname} = $refname; + } + } +} + sub fetch_all { - my ($repo_id, $url, $fetch) = @_; + 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 $ra = Git::SVN::Ra->new($url); my $head = $ra->get_latest_revnum; my $base = $head; @@ -685,6 +728,16 @@ sub read_all_remotes { $r->{$1}->{fetch}->{$2} = $3; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { $r->{$1}->{url} = $2; + } elsif (m!^(.+)\.(branches|tags)= + (.*):refs/remotes/(.+)\s*$/!x) { + my ($p, $g) = ($3, $4); + my $rs = $r->{$1}->{$2} = { + path => Git::SVN::GlobSpec->new($p), + ref => Git::SVN::GlobSpec->new($g) }; + if (length($rs->{ref}->{right}) != 0) { + die "The '*' glob character must be the last ", + "character of '$g'\n"; + } } } $r; @@ -3072,10 +3125,67 @@ sub DESTROY { command_close_pipe($self->{gui}, $self->{ctx}); } +package Git::SVN::GlobSpec; +use strict; +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"; + } elsif ($nr == 0) { + warn "One '*' is needed for glob: '$glob'\n"; + } + $re = quotemeta($left) . $re . quotemeta($right); + $left =~ s!/+$!!g; + $right =~ s!^/+!!g; + bless { left => $left, right => $right, + regex => qr/$re/, glob => $glob }, $class; +} + +sub full_path { + my ($self, $path) = @_; + return (length $self->{left} ? "$self->{left}/" : '') . + $path . (length $self->{right} ? "/$self->{right}" : ''); +} + __END__ Data structures: + +$remotes = { # returned by read_all_remotes() + 'svn' => { + # svn-remote.svn.url=https://svn.musicpd.org + url => 'https://svn.musicpd.org', + # svn-remote.svn.fetch=mpd/trunk:trunk + fetch => { + 'mpd/trunk' => 'trunk', + }, + # svn-remote.svn.tags=mpd/tags/*:tags/* + tags => { + path => { + left => 'mpd/tags', + right => '', + regex => qr!mpd/tags/([^/]+)$!, + glob => 'tags/*', + }, + ref => { + left => 'tags', + right => '', + regex => qr!tags/([^/]+)$!, + glob => 'tags/*', + }, + } + } +}; + $log_entry hashref as returned by libsvn_log_entry() { log => 'whitespace-formatted log entry diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 0fbfd264ec..8376429bcb 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -6,7 +6,7 @@ test_description='git-svn metadata migrations from previous versions' test_expect_success 'setup old-looking metadata' " cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && mkdir import && - cd import + cd import && for i in trunk branches/a branches/b \ tags/0.1 tags/0.2 tags/0.3; do mkdir -p \$i && \ @@ -43,11 +43,19 @@ test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && - grep '^branches/a:refs/remotes/a$' fetch.out && - grep '^branches/b:refs/remotes/b$' fetch.out && - grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && - grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && - grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + test -n \"\`git-config --get svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$'\`\" && + test -n \"\`git-config --get svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$'\`\" && + git config --unset svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$' && + git config --unset svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$' && + git-config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' && + git-config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' && + for i in tags/0.1 tags/0.2 tags/0.3; do + git-config --add svn-remote.svn.fetch \ + \$i:refs/remotes/\$i || exit 1; done " # refs should all be different, but the trees should all be the same: