Skip to content

Commit

Permalink
git svn: Support multiple branch and tag paths in the svn repository.
Browse files Browse the repository at this point in the history
This enables git-svn.perl to read multiple 'branches' and 'tags' entries in
svn-remote config sections.  The init and clone subcommands also support
multiple --branches and --tags arguments.

The branch (and tag) subcommand gets a new argument: --destination (or -d).
This argument is required if there are multiple branches (or tags) entries
configured for the remote Subversion repository.  The argument's value
specifies which branch (or tag) path to use to create the branch (or tag).
The specified value must match the left side (without wildcards) of one of
the branches (or tags) refspecs in the svn-remote's config.

[ew: avoided explicit loop when combining globs with "push"]

Signed-off-by: Marc Branchaud <marcnarc@xiplink.com>
Acked-by: Eric Wong <normalperson@yhbt.net>
  • Loading branch information
Marc Branchaud authored and Eric Wong committed Jun 25, 2009
1 parent 195643f commit 6224406
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 22 deletions.
78 changes: 56 additions & 22 deletions git-svn.perl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ BEGIN
$sha1 = qr/[a-f\d]{40}/;
$sha1_short = qr/[a-f\d]{4,40}/;
my ($_stdin, $_help, $_edit,
$_message, $_file,
$_message, $_file, $_branch_dest,
$_template, $_shared,
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
$_merge, $_strategy, $_dry_run, $_local,
Expand Down Expand Up @@ -92,11 +92,11 @@ BEGIN
'localtime' => \$Git::SVN::_localtime,
%remote_opts );

my ($_trunk, $_tags, $_branches, $_stdlayout);
my ($_trunk, @_tags, @_branches, $_stdlayout);
my %icv;
my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags,
'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix,
'trunk|T=s' => \$_trunk, 'tags|t=s@' => \@_tags,
'branches|b=s@' => \@_branches, 'prefix=s' => \$_prefix,
'stdlayout|s' => \$_stdlayout,
'minimize-url|m' => \$Git::SVN::_minimize_url,
'no-metadata' => sub { $icv{noMetadata} = 1 },
Expand Down Expand Up @@ -141,11 +141,13 @@ BEGIN
branch => [ \&cmd_branch,
'Create a branch in the SVN repository',
{ 'message|m=s' => \$_message,
'destination|d=s' => \$_branch_dest,
'dry-run|n' => \$_dry_run,
'tag|t' => \$_tag } ],
tag => [ sub { $_tag = 1; cmd_branch(@_) },
'Create a tag in the SVN repository',
{ 'message|m=s' => \$_message,
'destination|d=s' => \$_branch_dest,
'dry-run|n' => \$_dry_run } ],
'set-tree' => [ \&cmd_set_tree,
"Set an SVN repository to a git tree-ish",
Expand Down Expand Up @@ -365,7 +367,7 @@ sub init_subdir {
sub cmd_clone {
my ($url, $path) = @_;
if (!defined $path &&
(defined $_trunk || defined $_branches || defined $_tags ||
(defined $_trunk || @_branches || @_tags ||
defined $_stdlayout) &&
$url !~ m#^[a-z\+]+://#) {
$path = $url;
Expand All @@ -379,10 +381,10 @@ sub cmd_clone {
sub cmd_init {
if (defined $_stdlayout) {
$_trunk = 'trunk' if (!defined $_trunk);
$_tags = 'tags' if (!defined $_tags);
$_branches = 'branches' if (!defined $_branches);
@_tags = 'tags' if (! @_tags);
@_branches = 'branches' if (! @_branches);
}
if (defined $_trunk || defined $_branches || defined $_tags) {
if (defined $_trunk || @_branches || @_tags) {
return cmd_multi_init(@_);
}
my $url = shift or die "SVN repository location required ",
Expand Down Expand Up @@ -630,7 +632,31 @@ sub cmd_branch {
my ($src, $rev, undef, $gs) = working_head_info($head);

my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}};
my $glob = $remote->{ $_tag ? 'tags' : 'branches' };
my $allglobs = $remote->{ $_tag ? 'tags' : 'branches' };
my $glob;
if ($#{$allglobs} == 0) {
$glob = $allglobs->[0];
} else {
unless(defined $_branch_dest) {
die "Multiple ",
$_tag ? "tag" : "branch",
" paths defined for Subversion repository.\n",
"You must specify where you want to create the ",
$_tag ? "tag" : "branch",
" with the --destination argument.\n";
}
foreach my $g (@{$allglobs}) {
if ($_branch_dest eq $g->{path}->{left}) {
$glob = $g;
last;
}
}
unless (defined $glob) {
die "Unknown ",
$_tag ? "tag" : "branch",
" destination $_branch_dest\n";
}
}
my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());

Expand Down Expand Up @@ -837,7 +863,7 @@ sub cmd_proplist {

sub cmd_multi_init {
my $url = shift;
unless (defined $_trunk || defined $_branches || defined $_tags) {
unless (defined $_trunk || @_branches || @_tags) {
usage(1);
}

Expand All @@ -862,10 +888,14 @@ sub cmd_multi_init {
undef, $trunk_ref);
}
}
return unless defined $_branches || defined $_tags;
return unless @_branches || @_tags;
my $ra = $url ? Git::SVN::Ra->new($url) : undef;
complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
foreach my $path (@_branches) {
complete_url_ls_init($ra, $path, '--branches/-b', $_prefix);
}
foreach my $path (@_tags) {
complete_url_ls_init($ra, $path, '--tags/-t', $_prefix.'tags/');
}
}

sub cmd_multi_fetch {
Expand Down Expand Up @@ -1150,6 +1180,7 @@ sub complete_url_ls_init {
die "--prefix='$pfx' must have a trailing slash '/'\n";
}
command_noisy('config',
'--add',
"svn-remote.$gs->{repo_id}.$n",
"$remote_path:refs/remotes/$pfx*" .
('/*' x (($remote_path =~ tr/*/*/) - 1)) );
Expand Down Expand Up @@ -1616,7 +1647,8 @@ sub fetch_all {
# read the max revs for wildcard expansion (branches/*, tags/*)
foreach my $t (qw/branches tags/) {
defined $remote->{$t} or next;
push @globs, $remote->{$t};
push @globs, @{$remote->{$t}};

my $max_rev = eval { tmp_config(qw/--int --get/,
"svn-remote.$repo_id.${t}-maxRev") };
if (defined $max_rev && ($max_rev < $base)) {
Expand Down Expand Up @@ -1663,15 +1695,16 @@ sub read_all_remotes {
} elsif (m!^(.+)\.(branches|tags)=
(.*):refs/remotes/(.+)\s*$/!x) {
my ($p, $g) = ($3, $4);
my $rs = $r->{$1}->{$2} = {
t => $2,
remote => $1,
path => Git::SVN::GlobSpec->new($p),
ref => Git::SVN::GlobSpec->new($g) };
my $rs = {
t => $2,
remote => $1,
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";
}
push @{ $r->{$1}->{$2} }, $rs;
}
}

Expand Down Expand Up @@ -1811,9 +1844,10 @@ sub find_by_url { # repos_root and, path are optional
next if defined $repos_root && $repos_root ne $u;

my $fetch = $remotes->{$repo_id}->{fetch} || {};
foreach (qw/branches tags/) {
resolve_local_globs($u, $fetch,
$remotes->{$repo_id}->{$_});
foreach my $t (qw/branches tags/) {
foreach my $globspec (@{$remotes->{$repo_id}->{$t}}) {
resolve_local_globs($u, $fetch, $globspec);
}
}
my $p = $path;
my $rwr = rewrite_root({repo_id => $repo_id});
Expand Down
114 changes: 114 additions & 0 deletions t/t9138-git-svn-multiple-branches.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/bin/sh
#
# Copyright (c) 2009 Marc Branchaud
#

test_description='git svn multiple branch and tag paths in the svn repo'
. ./lib-git-svn.sh

test_expect_success 'setup svnrepo' '
mkdir project \
project/trunk \
project/b_one \
project/b_two \
project/tags_A \
project/tags_B &&
echo 1 > project/trunk/a.file &&
svn import -m "$test_description" project "$svnrepo/project" &&
rm -rf project &&
svn cp -m "Branch 1" "$svnrepo/project/trunk" \
"$svnrepo/project/b_one/first" &&
svn cp -m "Tag 1" "$svnrepo/project/trunk" \
"$svnrepo/project/tags_A/1.0" &&
svn co "$svnrepo/project" svn_project &&
cd svn_project &&
.
echo 2 > trunk/a.file &&
svn ci -m "Change 1" trunk/a.file &&
svn cp -m "Branch 2" "$svnrepo/project/trunk" \
"$svnrepo/project/b_one/second" &&
svn cp -m "Tag 2" "$svnrepo/project/trunk" \
"$svnrepo/project/tags_A/2.0" &&
echo 3 > trunk/a.file &&
svn ci -m "Change 2" trunk/a.file &&
svn cp -m "Branch 3" "$svnrepo/project/trunk" \
"$svnrepo/project/b_two/1" &&
svn cp -m "Tag 3" "$svnrepo/project/trunk" \
"$svnrepo/project/tags_A/3.0" &&
echo 4 > trunk/a.file &&
svn ci -m "Change 3" trunk/a.file &&
svn cp -m "Branch 4" "$svnrepo/project/trunk" \
"$svnrepo/project/b_two/2" &&
svn cp -m "Tag 4" "$svnrepo/project/trunk" \
"$svnrepo/project/tags_A/4.0" &&
svn up &&
echo 5 > b_one/first/a.file &&
svn ci -m "Change 4" b_one/first/a.file &&
svn cp -m "Tag 5" "$svnrepo/project/b_one/first" \
"$svnrepo/project/tags_B/v5" &&
echo 6 > b_one/second/a.file &&
svn ci -m "Change 5" b_one/second/a.file &&
svn cp -m "Tag 6" "$svnrepo/project/b_one/second" \
"$svnrepo/project/tags_B/v6" &&
echo 7 > b_two/1/a.file &&
svn ci -m "Change 6" b_two/1/a.file &&
svn cp -m "Tag 7" "$svnrepo/project/b_two/1" \
"$svnrepo/project/tags_B/v7" &&
echo 8 > b_two/2/a.file &&
svn ci -m "Change 7" b_two/2/a.file &&
svn cp -m "Tag 8" "$svnrepo/project/b_two/2" \
"$svnrepo/project/tags_B/v8" &&
cd ..
'

test_expect_success 'clone multiple branch and tag paths' '
git svn clone -T trunk \
-b b_one/* --branches b_two/* \
-t tags_A/* --tags tags_B \
"$svnrepo/project" git_project &&
cd git_project &&
git rev-parse refs/remotes/first &&
git rev-parse refs/remotes/second &&
git rev-parse refs/remotes/1 &&
git rev-parse refs/remotes/2 &&
git rev-parse refs/remotes/tags/1.0 &&
git rev-parse refs/remotes/tags/2.0 &&
git rev-parse refs/remotes/tags/3.0 &&
git rev-parse refs/remotes/tags/4.0 &&
git rev-parse refs/remotes/tags/v5 &&
git rev-parse refs/remotes/tags/v6 &&
git rev-parse refs/remotes/tags/v7 &&
git rev-parse refs/remotes/tags/v8 &&
cd ..
'

test_expect_success 'Multiple branch or tag paths require -d' '
cd git_project &&
test_must_fail git svn branch -m "No new branch" Nope &&
test_must_fail git svn tag -m "No new tag" Tagless &&
test_must_fail git rev-parse refs/remotes/Nope &&
test_must_fail git rev-parse refs/remotes/tags/Tagless &&
cd ../svn_project &&
svn up &&
test_must_fail test -d b_one/Nope &&
test_must_fail test -d b_two/Nope &&
test_must_fail test -d tags_A/Tagless &&
test_must_fail test -d tags_B/Tagless &&
cd ..
'

test_expect_success 'create new branches and tags' '
( cd git_project && git svn branch -m "New branch 1" -d project/b_one New1 ) &&
( cd svn_project && svn up && test -e b_one/New1/a.file ) &&
( cd git_project && git svn branch -m "New branch 2" -d project/b_two New2 ) &&
( cd svn_project && svn up && test -e b_two/New2/a.file ) &&
( cd git_project && git svn branch -t -m "New tag 1" -d project/tags_A Tag1 ) &&
( cd svn_project && svn up && test -e tags_A/Tag1/a.file )
( cd git_project && git svn tag -m "New tag 2" -d project/tags_B Tag2 ) &&
( cd svn_project && svn up && test -e tags_B/Tag2/a.file )
'

test_done

0 comments on commit 6224406

Please sign in to comment.