@matheusbsbs / matheus-bagliano / commits / 6224406914
Fork 0

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 <marcnarc@xiplink.com> Acked-by: Eric Wong <normalperson@yhbt.net>

Marc Branchaud committed Jun 23, 2009 at 13:02 UTC 6224406914c3d3fecec73e32ed271dbdc0c8736a
2 files changed +170 -22
git-svn.perl
+56 -22
@@ -63,7 +63,7 @@ my ($SVN);
63 $sha1 = qr/[a-f\d]{40}/;
64 $sha1_short = qr/[a-f\d]{4,40}/;
65 my ($_stdin, $_help, $_edit,
66 - $_message, $_file,
66 + $_message, $_file, $_branch_dest,
67 $_template, $_shared,
68 $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
69 $_merge, $_strategy, $_dry_run, $_local,
@@ -92,11 +92,11 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
92 'localtime' => \$Git::SVN::_localtime,
93 %remote_opts );
94
95 -my ($_trunk, $_tags, $_branches, $_stdlayout);
95 +my ($_trunk, @_tags, @_branches, $_stdlayout);
96 my %icv;
97 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,
100 'stdlayout|s' => \$_stdlayout,
101 'minimize-url|m' => \$Git::SVN::_minimize_url,
102 'no-metadata' => sub { $icv{noMetadata} = 1 },
@@ -141,11 +141,13 @@ my %cmd = (
141 branch => [ \&cmd_branch,
142 'Create a branch in the SVN repository',
143 { 'message|m=s' => \$_message,
144 + 'destination|d=s' => \$_branch_dest,
145 'dry-run|n' => \$_dry_run,
146 'tag|t' => \$_tag } ],
147 tag => [ sub { $_tag = 1; cmd_branch(@_) },
148 'Create a tag in the SVN repository',
149 { 'message|m=s' => \$_message,
150 + 'destination|d=s' => \$_branch_dest,
151 'dry-run|n' => \$_dry_run } ],
152 'set-tree' => [ \&cmd_set_tree,
153 "Set an SVN repository to a git tree-ish",
@@ -365,7 +367,7 @@ sub init_subdir {
367 sub cmd_clone {
368 my ($url, $path) = @_;
369 if (!defined $path &&
368 - (defined $_trunk || defined $_branches || defined $_tags ||
370 + (defined $_trunk || @_branches || @_tags ||
371 defined $_stdlayout) &&
372 $url !~ m#^[a-z\+]+://#) {
373 $path = $url;
@@ -379,10 +381,10 @@ sub cmd_clone {
381 sub cmd_init {
382 if (defined $_stdlayout) {
383 $_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);
386 }
385 - if (defined $_trunk || defined $_branches || defined $_tags) {
387 + if (defined $_trunk || @_branches || @_tags) {
388 return cmd_multi_init(@_);
389 }
390 my $url = shift or die "SVN repository location required ",
@@ -630,7 +632,31 @@ sub cmd_branch {
632 my ($src, $rev, undef, $gs) = working_head_info($head);
633
634 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 + }
660 my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
661 my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());
662
@@ -837,7 +863,7 @@ sub cmd_proplist {
863
864 sub cmd_multi_init {
865 my $url = shift;
840 - unless (defined $_trunk || defined $_branches || defined $_tags) {
866 + unless (defined $_trunk || @_branches || @_tags) {
867 usage(1);
868 }
869
@@ -862,10 +888,14 @@ sub cmd_multi_init {
888 undef, $trunk_ref);
889 }
890 }
865 - return unless defined $_branches || defined $_tags;
891 + return unless @_branches || @_tags;
892 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 + }
899 }
900
901 sub cmd_multi_fetch {
@@ -1150,6 +1180,7 @@ sub complete_url_ls_init {
1180 die "--prefix='$pfx' must have a trailing slash '/'\n";
1181 }
1182 command_noisy('config',
1183 + '--add',
1184 "svn-remote.$gs->{repo_id}.$n",
1185 "$remote_path:refs/remotes/$pfx*" .
1186 ('/*' x (($remote_path =~ tr/*/*/) - 1)) );
@@ -1616,7 +1647,8 @@ sub fetch_all {
1647 # read the max revs for wildcard expansion (branches/*, tags/*)
1648 foreach my $t (qw/branches tags/) {
1649 defined $remote->{$t} or next;
1619 - push @globs, $remote->{$t};
1650 + push @globs, @{$remote->{$t}};
1651 +
1652 my $max_rev = eval { tmp_config(qw/--int --get/,
1653 "svn-remote.$repo_id.${t}-maxRev") };
1654 if (defined $max_rev && ($max_rev < $base)) {
@@ -1663,15 +1695,16 @@ sub read_all_remotes {
1695 } elsif (m!^(.+)\.(branches|tags)=
1696 (.*):refs/remotes/(.+)\s*$/!x) {
1697 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) };
1703 if (length($rs->{ref}->{right}) != 0) {
1704 die "The '*' glob character must be the last ",
1705 "character of '$g'\n";
1706 }
1707 + push @{ $r->{$1}->{$2} }, $rs;
1708 }
1709 }
1710
@@ -1811,9 +1844,10 @@ sub find_by_url { # repos_root and, path are optional
1844 next if defined $repos_root && $repos_root ne $u;
1845
1846 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 + }
1851 }
1852 my $p = $path;
1853 my $rwr = rewrite_root({repo_id => $repo_id});
t/t9138-git-svn-multiple-branches.sh new
+114
@@ -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