Skip to content

Commit 6224406

Browse files
marcnarcEric Wong
authored andcommitted
git svn: Support multiple branch and tag paths in the svn repository.
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 <[email protected]> Acked-by: Eric Wong <[email protected]>
1 parent 195643f commit 6224406

File tree

2 files changed

+170
-22
lines changed

2 files changed

+170
-22
lines changed

git-svn.perl

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ BEGIN
6363
$sha1 = qr/[a-f\d]{40}/;
6464
$sha1_short = qr/[a-f\d]{4,40}/;
6565
my ($_stdin, $_help, $_edit,
66-
$_message, $_file,
66+
$_message, $_file, $_branch_dest,
6767
$_template, $_shared,
6868
$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
6969
$_merge, $_strategy, $_dry_run, $_local,
@@ -92,11 +92,11 @@ BEGIN
9292
'localtime' => \$Git::SVN::_localtime,
9393
%remote_opts );
9494

95-
my ($_trunk, $_tags, $_branches, $_stdlayout);
95+
my ($_trunk, @_tags, @_branches, $_stdlayout);
9696
my %icv;
9797
my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
98-
'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags,
99-
'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix,
98+
'trunk|T=s' => \$_trunk, 'tags|t=s@' => \@_tags,
99+
'branches|b=s@' => \@_branches, 'prefix=s' => \$_prefix,
100100
'stdlayout|s' => \$_stdlayout,
101101
'minimize-url|m' => \$Git::SVN::_minimize_url,
102102
'no-metadata' => sub { $icv{noMetadata} = 1 },
@@ -141,11 +141,13 @@ BEGIN
141141
branch => [ \&cmd_branch,
142142
'Create a branch in the SVN repository',
143143
{ 'message|m=s' => \$_message,
144+
'destination|d=s' => \$_branch_dest,
144145
'dry-run|n' => \$_dry_run,
145146
'tag|t' => \$_tag } ],
146147
tag => [ sub { $_tag = 1; cmd_branch(@_) },
147148
'Create a tag in the SVN repository',
148149
{ 'message|m=s' => \$_message,
150+
'destination|d=s' => \$_branch_dest,
149151
'dry-run|n' => \$_dry_run } ],
150152
'set-tree' => [ \&cmd_set_tree,
151153
"Set an SVN repository to a git tree-ish",
@@ -365,7 +367,7 @@ sub init_subdir {
365367
sub cmd_clone {
366368
my ($url, $path) = @_;
367369
if (!defined $path &&
368-
(defined $_trunk || defined $_branches || defined $_tags ||
370+
(defined $_trunk || @_branches || @_tags ||
369371
defined $_stdlayout) &&
370372
$url !~ m#^[a-z\+]+://#) {
371373
$path = $url;
@@ -379,10 +381,10 @@ sub cmd_clone {
379381
sub cmd_init {
380382
if (defined $_stdlayout) {
381383
$_trunk = 'trunk' if (!defined $_trunk);
382-
$_tags = 'tags' if (!defined $_tags);
383-
$_branches = 'branches' if (!defined $_branches);
384+
@_tags = 'tags' if (! @_tags);
385+
@_branches = 'branches' if (! @_branches);
384386
}
385-
if (defined $_trunk || defined $_branches || defined $_tags) {
387+
if (defined $_trunk || @_branches || @_tags) {
386388
return cmd_multi_init(@_);
387389
}
388390
my $url = shift or die "SVN repository location required ",
@@ -630,7 +632,31 @@ sub cmd_branch {
630632
my ($src, $rev, undef, $gs) = working_head_info($head);
631633

632634
my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}};
633-
my $glob = $remote->{ $_tag ? 'tags' : 'branches' };
635+
my $allglobs = $remote->{ $_tag ? 'tags' : 'branches' };
636+
my $glob;
637+
if ($#{$allglobs} == 0) {
638+
$glob = $allglobs->[0];
639+
} else {
640+
unless(defined $_branch_dest) {
641+
die "Multiple ",
642+
$_tag ? "tag" : "branch",
643+
" paths defined for Subversion repository.\n",
644+
"You must specify where you want to create the ",
645+
$_tag ? "tag" : "branch",
646+
" with the --destination argument.\n";
647+
}
648+
foreach my $g (@{$allglobs}) {
649+
if ($_branch_dest eq $g->{path}->{left}) {
650+
$glob = $g;
651+
last;
652+
}
653+
}
654+
unless (defined $glob) {
655+
die "Unknown ",
656+
$_tag ? "tag" : "branch",
657+
" destination $_branch_dest\n";
658+
}
659+
}
634660
my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
635661
my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());
636662

@@ -837,7 +863,7 @@ sub cmd_proplist {
837863

838864
sub cmd_multi_init {
839865
my $url = shift;
840-
unless (defined $_trunk || defined $_branches || defined $_tags) {
866+
unless (defined $_trunk || @_branches || @_tags) {
841867
usage(1);
842868
}
843869

@@ -862,10 +888,14 @@ sub cmd_multi_init {
862888
undef, $trunk_ref);
863889
}
864890
}
865-
return unless defined $_branches || defined $_tags;
891+
return unless @_branches || @_tags;
866892
my $ra = $url ? Git::SVN::Ra->new($url) : undef;
867-
complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
868-
complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
893+
foreach my $path (@_branches) {
894+
complete_url_ls_init($ra, $path, '--branches/-b', $_prefix);
895+
}
896+
foreach my $path (@_tags) {
897+
complete_url_ls_init($ra, $path, '--tags/-t', $_prefix.'tags/');
898+
}
869899
}
870900

871901
sub cmd_multi_fetch {
@@ -1150,6 +1180,7 @@ sub complete_url_ls_init {
11501180
die "--prefix='$pfx' must have a trailing slash '/'\n";
11511181
}
11521182
command_noisy('config',
1183+
'--add',
11531184
"svn-remote.$gs->{repo_id}.$n",
11541185
"$remote_path:refs/remotes/$pfx*" .
11551186
('/*' x (($remote_path =~ tr/*/*/) - 1)) );
@@ -1616,7 +1647,8 @@ sub fetch_all {
16161647
# read the max revs for wildcard expansion (branches/*, tags/*)
16171648
foreach my $t (qw/branches tags/) {
16181649
defined $remote->{$t} or next;
1619-
push @globs, $remote->{$t};
1650+
push @globs, @{$remote->{$t}};
1651+
16201652
my $max_rev = eval { tmp_config(qw/--int --get/,
16211653
"svn-remote.$repo_id.${t}-maxRev") };
16221654
if (defined $max_rev && ($max_rev < $base)) {
@@ -1663,15 +1695,16 @@ sub read_all_remotes {
16631695
} elsif (m!^(.+)\.(branches|tags)=
16641696
(.*):refs/remotes/(.+)\s*$/!x) {
16651697
my ($p, $g) = ($3, $4);
1666-
my $rs = $r->{$1}->{$2} = {
1667-
t => $2,
1668-
remote => $1,
1669-
path => Git::SVN::GlobSpec->new($p),
1670-
ref => Git::SVN::GlobSpec->new($g) };
1698+
my $rs = {
1699+
t => $2,
1700+
remote => $1,
1701+
path => Git::SVN::GlobSpec->new($p),
1702+
ref => Git::SVN::GlobSpec->new($g) };
16711703
if (length($rs->{ref}->{right}) != 0) {
16721704
die "The '*' glob character must be the last ",
16731705
"character of '$g'\n";
16741706
}
1707+
push @{ $r->{$1}->{$2} }, $rs;
16751708
}
16761709
}
16771710

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

18131846
my $fetch = $remotes->{$repo_id}->{fetch} || {};
1814-
foreach (qw/branches tags/) {
1815-
resolve_local_globs($u, $fetch,
1816-
$remotes->{$repo_id}->{$_});
1847+
foreach my $t (qw/branches tags/) {
1848+
foreach my $globspec (@{$remotes->{$repo_id}->{$t}}) {
1849+
resolve_local_globs($u, $fetch, $globspec);
1850+
}
18171851
}
18181852
my $p = $path;
18191853
my $rwr = rewrite_root({repo_id => $repo_id});

t/t9138-git-svn-multiple-branches.sh

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2009 Marc Branchaud
4+
#
5+
6+
test_description='git svn multiple branch and tag paths in the svn repo'
7+
. ./lib-git-svn.sh
8+
9+
test_expect_success 'setup svnrepo' '
10+
mkdir project \
11+
project/trunk \
12+
project/b_one \
13+
project/b_two \
14+
project/tags_A \
15+
project/tags_B &&
16+
echo 1 > project/trunk/a.file &&
17+
svn import -m "$test_description" project "$svnrepo/project" &&
18+
rm -rf project &&
19+
svn cp -m "Branch 1" "$svnrepo/project/trunk" \
20+
"$svnrepo/project/b_one/first" &&
21+
svn cp -m "Tag 1" "$svnrepo/project/trunk" \
22+
"$svnrepo/project/tags_A/1.0" &&
23+
svn co "$svnrepo/project" svn_project &&
24+
cd svn_project &&
25+
.
26+
echo 2 > trunk/a.file &&
27+
svn ci -m "Change 1" trunk/a.file &&
28+
svn cp -m "Branch 2" "$svnrepo/project/trunk" \
29+
"$svnrepo/project/b_one/second" &&
30+
svn cp -m "Tag 2" "$svnrepo/project/trunk" \
31+
"$svnrepo/project/tags_A/2.0" &&
32+
echo 3 > trunk/a.file &&
33+
svn ci -m "Change 2" trunk/a.file &&
34+
svn cp -m "Branch 3" "$svnrepo/project/trunk" \
35+
"$svnrepo/project/b_two/1" &&
36+
svn cp -m "Tag 3" "$svnrepo/project/trunk" \
37+
"$svnrepo/project/tags_A/3.0" &&
38+
echo 4 > trunk/a.file &&
39+
svn ci -m "Change 3" trunk/a.file &&
40+
svn cp -m "Branch 4" "$svnrepo/project/trunk" \
41+
"$svnrepo/project/b_two/2" &&
42+
svn cp -m "Tag 4" "$svnrepo/project/trunk" \
43+
"$svnrepo/project/tags_A/4.0" &&
44+
svn up &&
45+
echo 5 > b_one/first/a.file &&
46+
svn ci -m "Change 4" b_one/first/a.file &&
47+
svn cp -m "Tag 5" "$svnrepo/project/b_one/first" \
48+
"$svnrepo/project/tags_B/v5" &&
49+
echo 6 > b_one/second/a.file &&
50+
svn ci -m "Change 5" b_one/second/a.file &&
51+
svn cp -m "Tag 6" "$svnrepo/project/b_one/second" \
52+
"$svnrepo/project/tags_B/v6" &&
53+
echo 7 > b_two/1/a.file &&
54+
svn ci -m "Change 6" b_two/1/a.file &&
55+
svn cp -m "Tag 7" "$svnrepo/project/b_two/1" \
56+
"$svnrepo/project/tags_B/v7" &&
57+
echo 8 > b_two/2/a.file &&
58+
svn ci -m "Change 7" b_two/2/a.file &&
59+
svn cp -m "Tag 8" "$svnrepo/project/b_two/2" \
60+
"$svnrepo/project/tags_B/v8" &&
61+
cd ..
62+
'
63+
64+
test_expect_success 'clone multiple branch and tag paths' '
65+
git svn clone -T trunk \
66+
-b b_one/* --branches b_two/* \
67+
-t tags_A/* --tags tags_B \
68+
"$svnrepo/project" git_project &&
69+
cd git_project &&
70+
git rev-parse refs/remotes/first &&
71+
git rev-parse refs/remotes/second &&
72+
git rev-parse refs/remotes/1 &&
73+
git rev-parse refs/remotes/2 &&
74+
git rev-parse refs/remotes/tags/1.0 &&
75+
git rev-parse refs/remotes/tags/2.0 &&
76+
git rev-parse refs/remotes/tags/3.0 &&
77+
git rev-parse refs/remotes/tags/4.0 &&
78+
git rev-parse refs/remotes/tags/v5 &&
79+
git rev-parse refs/remotes/tags/v6 &&
80+
git rev-parse refs/remotes/tags/v7 &&
81+
git rev-parse refs/remotes/tags/v8 &&
82+
cd ..
83+
'
84+
85+
test_expect_success 'Multiple branch or tag paths require -d' '
86+
cd git_project &&
87+
test_must_fail git svn branch -m "No new branch" Nope &&
88+
test_must_fail git svn tag -m "No new tag" Tagless &&
89+
test_must_fail git rev-parse refs/remotes/Nope &&
90+
test_must_fail git rev-parse refs/remotes/tags/Tagless &&
91+
cd ../svn_project &&
92+
svn up &&
93+
test_must_fail test -d b_one/Nope &&
94+
test_must_fail test -d b_two/Nope &&
95+
test_must_fail test -d tags_A/Tagless &&
96+
test_must_fail test -d tags_B/Tagless &&
97+
cd ..
98+
'
99+
100+
test_expect_success 'create new branches and tags' '
101+
( cd git_project && git svn branch -m "New branch 1" -d project/b_one New1 ) &&
102+
( cd svn_project && svn up && test -e b_one/New1/a.file ) &&
103+
104+
( cd git_project && git svn branch -m "New branch 2" -d project/b_two New2 ) &&
105+
( cd svn_project && svn up && test -e b_two/New2/a.file ) &&
106+
107+
( cd git_project && git svn branch -t -m "New tag 1" -d project/tags_A Tag1 ) &&
108+
( cd svn_project && svn up && test -e tags_A/Tag1/a.file )
109+
110+
( cd git_project && git svn tag -m "New tag 2" -d project/tags_B Tag2 ) &&
111+
( cd svn_project && svn up && test -e tags_B/Tag2/a.file )
112+
'
113+
114+
test_done

0 commit comments

Comments
 (0)