svn: allow branches outside of refs/remotes

It may be convenient for some users to store svn remote tracking
branches outside of the refs/remotes/ heirarchy.

To accomplish this feat, this patch includes the entire path to
the ref in $r->{'refname'} in &read_all_remotes and tries to change
references to this entry so the new value makes sense.

[ew: fixed backwards compatibility, long lines]

Signed-off-by: Adam Brewster <adambrewster@gmail.com>
Signed-off-by: Eric Wong <normalperson@yhbt.net>
This commit is contained in:
Adam Brewster 2009-08-11 23:14:27 -04:00 committed by Eric Wong
parent b186a261b1
commit 6f5748e14c
6 changed files with 96 additions and 54 deletions

View File

@ -909,7 +909,7 @@ sub cmd_multi_init {
} }
do_git_init_db(); do_git_init_db();
if (defined $_trunk) { if (defined $_trunk) {
my $trunk_ref = $_prefix . 'trunk'; my $trunk_ref = 'refs/remotes/' . $_prefix . 'trunk';
# try both old-style and new-style lookups: # try both old-style and new-style lookups:
my $gs_trunk = eval { Git::SVN->new($trunk_ref) }; my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
unless ($gs_trunk) { unless ($gs_trunk) {
@ -1654,23 +1654,23 @@ sub resolve_local_globs {
return unless defined $glob_spec; return unless defined $glob_spec;
my $ref = $glob_spec->{ref}; my $ref = $glob_spec->{ref};
my $path = $glob_spec->{path}; my $path = $glob_spec->{path};
foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) { foreach (command(qw#for-each-ref --format=%(refname) refs/#)) {
next unless m#^refs/remotes/$ref->{regex}$#; next unless m#^$ref->{regex}$#;
my $p = $1; my $p = $1;
my $pathname = desanitize_refname($path->full_path($p)); my $pathname = desanitize_refname($path->full_path($p));
my $refname = desanitize_refname($ref->full_path($p)); my $refname = desanitize_refname($ref->full_path($p));
if (my $existing = $fetch->{$pathname}) { if (my $existing = $fetch->{$pathname}) {
if ($existing ne $refname) { if ($existing ne $refname) {
die "Refspec conflict:\n", die "Refspec conflict:\n",
"existing: refs/remotes/$existing\n", "existing: $existing\n",
" globbed: refs/remotes/$refname\n"; " globbed: $refname\n";
} }
my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; my $u = (::cmt_metadata("$refname"))[0];
$u =~ s!^\Q$url\E(/|$)!! or die $u =~ s!^\Q$url\E(/|$)!! or die
"refs/remotes/$refname: '$url' not found in '$u'\n"; "$refname: '$url' not found in '$u'\n";
if ($pathname ne $u) { if ($pathname ne $u) {
warn "W: Refspec glob conflict ", warn "W: Refspec glob conflict ",
"(ref: refs/remotes/$refname):\n", "(ref: $refname):\n",
"expected path: $pathname\n", "expected path: $pathname\n",
" real path: $u\n", " real path: $u\n",
"Continuing ahead with $u\n"; "Continuing ahead with $u\n";
@ -1748,33 +1748,35 @@ sub read_all_remotes {
my $use_svm_props = eval { command_oneline(qw/config --bool my $use_svm_props = eval { command_oneline(qw/config --bool
svn.useSvmProps/) }; svn.useSvmProps/) };
$use_svm_props = $use_svm_props eq 'true' if $use_svm_props; $use_svm_props = $use_svm_props eq 'true' if $use_svm_props;
my $svn_refspec = qr{\s*/?(.*?)\s*:\s*(.+?)\s*};
foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
if (m!^(.+)\.fetch=\s*(.*)\s*:\s*(.+)\s*$!) { if (m!^(.+)\.fetch=$svn_refspec$!) {
my ($remote, $local_ref, $_remote_ref) = ($1, $2, $3); my ($remote, $local_ref, $remote_ref) = ($1, $2, $3);
die("svn-remote.$remote: remote ref '$_remote_ref' " die("svn-remote.$remote: remote ref '$remote_ref' "
. "must start with 'refs/remotes/'\n") . "must start with 'refs/'\n")
unless $_remote_ref =~ m{^refs/remotes/(.+)}; unless $remote_ref =~ m{^refs/};
my $remote_ref = $1;
$local_ref =~ s{^/}{};
$r->{$remote}->{fetch}->{$local_ref} = $remote_ref; $r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
$r->{$remote}->{svm} = {} if $use_svm_props; $r->{$remote}->{svm} = {} if $use_svm_props;
} elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) { } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
$r->{$1}->{svm} = {}; $r->{$1}->{svm} = {};
} elsif (m!^(.+)\.url=\s*(.*)\s*$!) { } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
$r->{$1}->{url} = $2; $r->{$1}->{url} = $2;
} elsif (m!^(.+)\.(branches|tags)= } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
(.*):refs/remotes/(.+)\s*$/!x) { my ($remote, $t, $local_ref, $remote_ref) =
my ($p, $g) = ($3, $4); ($1, $2, $3, $4);
die("svn-remote.$remote: remote ref '$remote_ref' ($t) "
. "must start with 'refs/'\n")
unless $remote_ref =~ m{^refs/};
my $rs = { my $rs = {
t => $2, t => $t,
remote => $1, remote => $remote,
path => Git::SVN::GlobSpec->new($p), path => Git::SVN::GlobSpec->new($local_ref),
ref => Git::SVN::GlobSpec->new($g) }; ref => Git::SVN::GlobSpec->new($remote_ref) };
if (length($rs->{ref}->{right}) != 0) { if (length($rs->{ref}->{right}) != 0) {
die "The '*' glob character must be the last ", die "The '*' glob character must be the last ",
"character of '$g'\n"; "character of '$remote_ref'\n";
} }
push @{ $r->{$1}->{$2} }, $rs; push @{ $r->{$remote}->{$t} }, $rs;
} }
} }
@ -1882,9 +1884,9 @@ sub init_remote_config {
} }
} }
my ($xrepo_id, $xpath) = find_ref($self->refname); my ($xrepo_id, $xpath) = find_ref($self->refname);
if (defined $xpath) { if (!$no_write && defined $xpath) {
die "svn-remote.$xrepo_id.fetch already set to track ", die "svn-remote.$xrepo_id.fetch already set to track ",
"$xpath:refs/remotes/", $self->refname, "\n"; "$xpath:", $self->refname, "\n";
} }
unless ($no_write) { unless ($no_write) {
command_noisy('config', command_noisy('config',
@ -1959,7 +1961,7 @@ sub find_ref {
my ($ref_id) = @_; my ($ref_id) = @_;
foreach (command(qw/config -l/)) { foreach (command(qw/config -l/)) {
next unless m!^svn-remote\.(.+)\.fetch= next unless m!^svn-remote\.(.+)\.fetch=
\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; \s*/?(.*?)\s*:\s*(.+?)\s*$!x;
my ($repo_id, $path, $ref) = ($1, $2, $3); my ($repo_id, $path, $ref) = ($1, $2, $3);
if ($ref eq $ref_id) { if ($ref eq $ref_id) {
$path = '' if ($path =~ m#^\./?#); $path = '' if ($path =~ m#^\./?#);
@ -1976,16 +1978,16 @@ sub new {
if (!defined $repo_id) { if (!defined $repo_id) {
die "Could not find a \"svn-remote.*.fetch\" key ", die "Could not find a \"svn-remote.*.fetch\" key ",
"in the repository configuration matching: ", "in the repository configuration matching: ",
"refs/remotes/$ref_id\n"; "$ref_id\n";
} }
} }
my $self = _new($class, $repo_id, $ref_id, $path); my $self = _new($class, $repo_id, $ref_id, $path);
if (!defined $self->{path} || !length $self->{path}) { if (!defined $self->{path} || !length $self->{path}) {
my $fetch = command_oneline('config', '--get', my $fetch = command_oneline('config', '--get',
"svn-remote.$repo_id.fetch", "svn-remote.$repo_id.fetch",
":refs/remotes/$ref_id\$") or ":$ref_id\$") or
die "Failed to read \"svn-remote.$repo_id.fetch\" ", die "Failed to read \"svn-remote.$repo_id.fetch\" ",
"\":refs/remotes/$ref_id\$\" in config\n"; "\":$ref_id\$\" in config\n";
($self->{path}, undef) = split(/\s*:\s*/, $fetch); ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
} }
$self->{url} = command_oneline('config', '--get', $self->{url} = command_oneline('config', '--get',
@ -1996,7 +1998,7 @@ sub new {
} }
sub refname { sub refname {
my ($refname) = "refs/remotes/$_[0]->{ref_id}" ; my ($refname) = $_[0]->{ref_id} ;
# It cannot end with a slash /, we'll throw up on this because # It cannot end with a slash /, we'll throw up on this because
# SVN can't have directories with a slash in their name, either: # SVN can't have directories with a slash in their name, either:
@ -3331,12 +3333,23 @@ sub _new {
} }
unless (defined $ref_id && length $ref_id) { unless (defined $ref_id && length $ref_id) {
$_prefix = '' unless defined($_prefix); $_prefix = '' unless defined($_prefix);
$_[2] = $ref_id = $_prefix . $Git::SVN::default_ref_id; $_[2] = $ref_id =
"refs/remotes/$_prefix$Git::SVN::default_ref_id";
} }
$_[1] = $repo_id; $_[1] = $repo_id;
my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
# Older repos imported by us used $GIT_DIR/svn/foo instead of
# $GIT_DIR/svn/refs/remotes/foo when tracking refs/remotes/foo
if ($ref_id =~ m{^refs/remotes/(.*)}) {
my $old_dir = "$ENV{GIT_DIR}/svn/$1";
if (-d $old_dir && ! -d $dir) {
$dir = $old_dir;
}
}
$_[3] = $path = '' unless (defined $path); $_[3] = $path = '' unless (defined $path);
mkpath(["$ENV{GIT_DIR}/svn"]); mkpath([$dir]);
bless { bless {
ref_id => $ref_id, dir => $dir, index => "$dir/index", ref_id => $ref_id, dir => $dir, index => "$dir/index",
path => $path, config => "$ENV{GIT_DIR}/svn/config", path => $path, config => "$ENV{GIT_DIR}/svn/config",
@ -5509,7 +5522,7 @@ sub minimize_connections {
my $pfx = "svn-remote.$x->{old_repo_id}"; my $pfx = "svn-remote.$x->{old_repo_id}";
my $old_fetch = quotemeta("$x->{old_path}:". my $old_fetch = quotemeta("$x->{old_path}:".
"refs/remotes/$x->{ref_id}"); "$x->{ref_id}");
command_noisy(qw/config --unset/, command_noisy(qw/config --unset/,
"$pfx.fetch", '^'. $old_fetch . '$'); "$pfx.fetch", '^'. $old_fetch . '$');
delete $r->{$x->{old_repo_id}}-> delete $r->{$x->{old_repo_id}}->
@ -5578,7 +5591,7 @@ sub new {
my ($class, $glob) = @_; my ($class, $glob) = @_;
my $re = $glob; my $re = $glob;
$re =~ s!/+$!!g; # no need for trailing slashes $re =~ s!/+$!!g; # no need for trailing slashes
$re =~ m!^([^*]*)(\*(?:/\*)*)([^*]*)$!; $re =~ m!^([^*]*)(\*(?:/\*)*)(.*)$!;
my $temp = $re; my $temp = $re;
my ($left, $right) = ($1, $3); my ($left, $right) = ($1, $3);
$re = $2; $re = $2;

View File

@ -14,7 +14,7 @@ if ! test_have_prereq PERL; then
fi fi
GIT_DIR=$PWD/.git GIT_DIR=$PWD/.git
GIT_SVN_DIR=$GIT_DIR/svn/git-svn GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn
SVN_TREE=$GIT_SVN_DIR/svn-tree SVN_TREE=$GIT_SVN_DIR/svn-tree
svn >/dev/null 2>&1 svn >/dev/null 2>&1

View File

@ -172,11 +172,11 @@ test_expect_success "follow-parent is atomic" '
git update-ref refs/remotes/flunk@18 refs/remotes/stunk~2 && git update-ref refs/remotes/flunk@18 refs/remotes/stunk~2 &&
git update-ref -d refs/remotes/stunk && git update-ref -d refs/remotes/stunk &&
git config --unset svn-remote.svn.fetch stunk && git config --unset svn-remote.svn.fetch stunk &&
mkdir -p "$GIT_DIR"/svn/flunk@18 && mkdir -p "$GIT_DIR"/svn/refs/remotes/flunk@18 &&
rev_map=$(cd "$GIT_DIR"/svn/stunk && ls .rev_map*) && rev_map=$(cd "$GIT_DIR"/svn/refs/remotes/stunk && ls .rev_map*) &&
dd if="$GIT_DIR"/svn/stunk/$rev_map \ dd if="$GIT_DIR"/svn/refs/remotes/stunk/$rev_map \
of="$GIT_DIR"/svn/flunk@18/$rev_map bs=24 count=1 && of="$GIT_DIR"/svn/refs/remotes/flunk@18/$rev_map bs=24 count=1 &&
rm -rf "$GIT_DIR"/svn/stunk && rm -rf "$GIT_DIR"/svn/refs/remotes/stunk &&
git svn init --minimize-url -i flunk "$svnrepo"/flunk && git svn init --minimize-url -i flunk "$svnrepo"/flunk &&
git svn fetch -i flunk && git svn fetch -i flunk &&
git svn init --minimize-url -i stunk "$svnrepo"/stunk && git svn init --minimize-url -i stunk "$svnrepo"/stunk &&

View File

@ -16,9 +16,7 @@ test_expect_success 'setup old-looking metadata' '
cd .. && cd .. &&
git svn init "$svnrepo" && git svn init "$svnrepo" &&
git svn fetch && git svn fetch &&
mv "$GIT_DIR"/svn/* "$GIT_DIR"/ && rm -rf "$GIT_DIR"/svn &&
mv "$GIT_DIR"/svn/.metadata "$GIT_DIR"/ &&
rmdir "$GIT_DIR"/svn &&
git update-ref refs/heads/git-svn-HEAD refs/${remotes_git_svn} && git update-ref refs/heads/git-svn-HEAD refs/${remotes_git_svn} &&
git update-ref refs/heads/svn-HEAD refs/${remotes_git_svn} && git update-ref refs/heads/svn-HEAD refs/${remotes_git_svn} &&
git update-ref -d refs/${remotes_git_svn} refs/${remotes_git_svn} git update-ref -d refs/${remotes_git_svn} refs/${remotes_git_svn}
@ -87,7 +85,7 @@ test_expect_success 'migrate --minimize on old inited layout' '
rm -rf "$GIT_DIR"/svn && rm -rf "$GIT_DIR"/svn &&
for i in `cat fetch.out`; do for i in `cat fetch.out`; do
path=`expr $i : "\([^:]*\):.*$"` path=`expr $i : "\([^:]*\):.*$"`
ref=`expr $i : "[^:]*:refs/remotes/\(.*\)$"` ref=`expr $i : "[^:]*:\(refs/remotes/.*\)$"`
if test -z "$ref"; then continue; fi if test -z "$ref"; then continue; fi
if test -n "$path"; then path="/$path"; fi if test -n "$path"; then path="/$path"; fi
( mkdir -p "$GIT_DIR"/svn/$ref/info/ && ( mkdir -p "$GIT_DIR"/svn/$ref/info/ &&
@ -107,16 +105,16 @@ test_expect_success 'migrate --minimize on old inited layout' '
test_expect_success ".rev_db auto-converted to .rev_map.UUID" ' test_expect_success ".rev_db auto-converted to .rev_map.UUID" '
git svn fetch -i trunk && git svn fetch -i trunk &&
test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" && test -z "$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db.* 2>/dev/null)" &&
expect="$(ls "$GIT_DIR"/svn/trunk/.rev_map.*)" && expect="$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_map.*)" &&
test -n "$expect" && test -n "$expect" &&
rev_db="$(echo $expect | sed -e "s,_map,_db,")" && rev_db="$(echo $expect | sed -e "s,_map,_db,")" &&
convert_to_rev_db "$expect" "$rev_db" && convert_to_rev_db "$expect" "$rev_db" &&
rm -f "$expect" && rm -f "$expect" &&
test -f "$rev_db" && test -f "$rev_db" &&
git svn fetch -i trunk && git svn fetch -i trunk &&
test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" && test -z "$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db.* 2>/dev/null)" &&
test ! -e "$GIT_DIR"/svn/trunk/.rev_db && test ! -e "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db &&
test -f "$expect" test -f "$expect"
' '

View File

@ -28,26 +28,26 @@ test_expect_success 'Setup repo' 'git svn init "$svnrepo"'
test_expect_success 'Fetch repo' 'git svn fetch' test_expect_success 'Fetch repo' 'git svn fetch'
test_expect_success 'make backup copy of unhandled.log' ' test_expect_success 'make backup copy of unhandled.log' '
cp .git/svn/git-svn/unhandled.log tmp cp .git/svn/refs/remotes/git-svn/unhandled.log tmp
' '
test_expect_success 'create leftover index' '> .git/svn/git-svn/index' test_expect_success 'create leftover index' '> .git/svn/refs/remotes/git-svn/index'
test_expect_success 'git svn gc runs' 'git svn gc' test_expect_success 'git svn gc runs' 'git svn gc'
test_expect_success 'git svn index removed' '! test -f .git/svn/git-svn/index' test_expect_success 'git svn index removed' '! test -f .git/svn/refs/remotes/git-svn/index'
if perl -MCompress::Zlib -e 0 2>/dev/null if perl -MCompress::Zlib -e 0 2>/dev/null
then then
test_expect_success 'git svn gc produces a valid gzip file' ' test_expect_success 'git svn gc produces a valid gzip file' '
gunzip .git/svn/git-svn/unhandled.log.gz gunzip .git/svn/refs/remotes/git-svn/unhandled.log.gz
' '
else else
say "Perl Compress::Zlib unavailable, skipping gunzip test" say "Perl Compress::Zlib unavailable, skipping gunzip test"
fi fi
test_expect_success 'git svn gc does not change unhandled.log files' ' test_expect_success 'git svn gc does not change unhandled.log files' '
test_cmp .git/svn/git-svn/unhandled.log tmp/unhandled.log test_cmp .git/svn/refs/remotes/git-svn/unhandled.log tmp/unhandled.log
' '
test_done test_done

31
t/t9144-git-svn-old-rev_map.sh Executable file
View File

@ -0,0 +1,31 @@
#!/bin/sh
#
# Copyright (c) 2009 Eric Wong
test_description='git svn old rev_map preservd'
. ./lib-git-svn.sh
test_expect_success 'setup test repository with old layout' '
mkdir i &&
(cd i && > a) &&
svn_cmd import -m- i "$svnrepo" &&
git svn init "$svnrepo" &&
git svn fetch &&
test -d .git/svn/refs/remotes/git-svn/ &&
! test -e .git/svn/git-svn/ &&
mv .git/svn/refs/remotes/git-svn .git/svn/ &&
rm -r .git/svn/refs
'
test_expect_success 'old layout continues to work' '
svn_cmd import -m- i "$svnrepo/b" &&
git svn rebase &&
echo a >> b/a &&
git add b/a &&
git commit -m- -a &&
git svn dcommit &&
! test -d .git/svn/refs/ &&
test -e .git/svn/git-svn/
'
test_done