Skip to content

Commit

Permalink
Merge git://git.bogomips.org/git-svn
Browse files Browse the repository at this point in the history
* git://git.bogomips.org/git-svn:
  git-svn: allow subset of branches/tags to be specified in glob spec
  git-svn: allow UUID to be manually remapped via rewriteUUID
  git-svn: update svn mergeinfo test suite
  git-svn: document --username/commit-url for branch/tag
  git-svn: add --username/commit-url options for branch/tag
  git-svn: respect commiturl option for branch/tag
  git-svn: fix mismatched src/dst errors for branch/tag
  git-svn: handle merge-base failures
  git-svn: ignore changeless commits when checking for a cherry-pick
  • Loading branch information
Junio C Hamano committed Jan 23, 2010
2 parents a88183f + 0757620 commit 2bd6dbb
Show file tree
Hide file tree
Showing 9 changed files with 1,018 additions and 162 deletions.
44 changes: 41 additions & 3 deletions Documentation/git-svn.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ COMMANDS
Set the 'useSvnsyncProps' option in the [svn-remote] config.
--rewrite-root=<URL>;;
Set the 'rewriteRoot' option in the [svn-remote] config.
--rewrite-uuid=<UUID>;;
Set the 'rewriteUUID' option in the [svn-remote] config.
--username=<USER>;;
For transports that SVN handles authentication for (http,
https, and plain svn), specify the username. For other
Expand Down Expand Up @@ -239,6 +241,19 @@ discouraged.
where <name> is the name of the SVN repository as specified by the -R option to
'init' (or "svn" by default).

--username;;
Specify the SVN username to perform the commit as. This option overrides
configuration property 'username'.

--commit-url;;
Use the specified URL to connect to the destination Subversion
repository. This is useful in cases where the source SVN
repository is read-only. This option overrides configuration
property 'commiturl'.
+
git config --get-all svn-remote.<name>.commiturl
+

'tag'::
Create a tag in the SVN repository. This is a shorthand for
'branch -t'.
Expand Down Expand Up @@ -616,6 +631,12 @@ svn-remote.<name>.rewriteRoot::
the repository with a public http:// or svn:// URL in the
metadata so users of it will see the public URL.

svn-remote.<name>.rewriteUUID::
Similar to the useSvmProps option; this is for users who need
to remap the UUID manually. This may be useful in situations
where the original UUID is not available via either useSvmProps
or useSvnsyncProps.

svn.brokenSymlinkWorkaround::
This disables potentially expensive checks to workaround
broken symlinks checked into SVN by broken clients. Set this
Expand All @@ -625,13 +646,14 @@ svn.brokenSymlinkWorkaround::
revision fetched. If unset, 'git svn' assumes this option to
be "true".

Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps
Since the noMetadata, rewriteRoot, rewriteUUID, useSvnsyncProps and useSvmProps
options all affect the metadata generated and used by 'git svn'; they
*must* be set in the configuration file before any history is imported
and these settings should never be changed once they are set.

Additionally, only one of these four options can be used per-svn-remote
section because they affect the 'git-svn-id:' metadata line.
Additionally, only one of these options can be used per svn-remote
section because they affect the 'git-svn-id:' metadata line, except
for rewriteRoot and rewriteUUID which can be used together.


BASIC EXAMPLES
Expand Down Expand Up @@ -816,6 +838,22 @@ independent path component (surrounded by '/' or EOL). This
type of configuration is not automatically created by 'init' and
should be manually entered with a text-editor or using 'git config'.

It is also possible to fetch a subset of branches or tags by using a
comma-separated list of names within braces. For example:

------------------------------------------------------------------------
[svn-remote "huge-project"]
url = http://server.org/svn
fetch = trunk/src:refs/remotes/trunk
branches = branches/{red,green}/src:refs/remotes/branches/*
tags = tags/{1.0,2.0}/src:refs/remotes/tags/*
------------------------------------------------------------------------

Note that git-svn keeps track of the highest revision in which a branch
or tag has appeared. If the subset of branches or tags is changed after
fetching, then .git/svn/.metadata must be manually edited to remove (or
reset) branches-maxRev and/or tags-maxRev as appropriate.

SEE ALSO
--------
linkgit:git-rebase[1]
Expand Down
160 changes: 128 additions & 32 deletions git-svn.perl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ BEGIN
'use-svm-props' => sub { $icv{useSvmProps} = 1 },
'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 },
'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] },
'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },
%remote_opts );
my %cmt_opts = ( 'edit|e' => \$_edit,
'rmdir' => \$SVN::Git::Editor::_rmdir,
Expand Down Expand Up @@ -155,12 +156,16 @@ BEGIN
{ 'message|m=s' => \$_message,
'destination|d=s' => \$_branch_dest,
'dry-run|n' => \$_dry_run,
'tag|t' => \$_tag } ],
'tag|t' => \$_tag,
'username=s' => \$Git::SVN::Prompt::_username,
'commit-url=s' => \$_commit_url } ],
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 } ],
'dry-run|n' => \$_dry_run,
'username=s' => \$Git::SVN::Prompt::_username,
'commit-url=s' => \$_commit_url } ],
'set-tree' => [ \&cmd_set_tree,
"Set an SVN repository to a git tree-ish",
{ 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ],
Expand Down Expand Up @@ -708,7 +713,21 @@ sub cmd_branch {
}
}
my ($lft, $rgt) = @{ $glob->{path} }{qw/left right/};
my $dst = join '/', $remote->{url}, $lft, $branch_name, ($rgt || ());
my $url;
if (defined $_commit_url) {
$url = $_commit_url;
} else {
$url = eval { command_oneline('config', '--get',
"svn-remote.$gs->{repo_id}.commiturl") };
if (!$url) {
$url = $remote->{url};
}
}
my $dst = join '/', $url, $lft, $branch_name, ($rgt || ());

if ($dst =~ /^https:/ && $src =~ /^http:/) {
$src=~s/^http:/https:/;
}

my $ctx = SVN::Client->new(
auth => Git::SVN::Ra::_auth_providers(),
Expand Down Expand Up @@ -1806,8 +1825,8 @@ sub read_all_remotes {
my $rs = {
t => $t,
remote => $remote,
path => Git::SVN::GlobSpec->new($local_ref),
ref => Git::SVN::GlobSpec->new($remote_ref) };
path => Git::SVN::GlobSpec->new($local_ref, 1),
ref => Git::SVN::GlobSpec->new($remote_ref, 0) };
if (length($rs->{ref}->{right}) != 0) {
die "The '*' glob character must be the last ",
"character of '$remote_ref'\n";
Expand Down Expand Up @@ -2189,6 +2208,10 @@ sub svnsync {
die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ",
"options set!\n";
}
if ($self->rewrite_uuid) {
die "Can't have both 'useSvnsyncProps' and 'rewriteUUID' ",
"options set!\n";
}

my $svnsync;
# see if we have it in our config, first:
Expand Down Expand Up @@ -2470,6 +2493,20 @@ sub rewrite_root {
$self->{-rewrite_root} = $rwr;
}

sub rewrite_uuid {
my ($self) = @_;
return $self->{-rewrite_uuid} if exists $self->{-rewrite_uuid};
my $k = "svn-remote.$self->{repo_id}.rewriteUUID";
my $rwid = eval { command_oneline(qw/config --get/, $k) };
if ($rwid) {
$rwid =~ s#/+$##;
if ($rwid !~ m#^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$#) {
die "$rwid is not a valid UUID (key: $k)\n";
}
}
$self->{-rewrite_uuid} = $rwid;
}

sub metadata_url {
my ($self) = @_;
($self->rewrite_root || $self->{url}) .
Expand Down Expand Up @@ -3052,12 +3089,36 @@ sub check_cherry_pick {
for my $range ( @ranges ) {
delete @commits{_rev_list($range)};
}
for my $commit (keys %commits) {
if (has_no_changes($commit)) {
delete $commits{$commit};
}
}
return (keys %commits);
}

sub has_no_changes {
my $commit = shift;

my @revs = split / /, command_oneline(
qw(rev-list --parents -1 -m), $commit);

# Commits with no parents, e.g. the start of a partial branch,
# have changes by definition.
return 1 if (@revs < 2);

# Commits with multiple parents, e.g a merge, have no changes
# by definition.
return 0 if (@revs > 2);

return (command_oneline("rev-parse", "$commit^{tree}") eq
command_oneline("rev-parse", "$commit~1^{tree}"));
}

BEGIN {
memoize 'lookup_svn_merge';
memoize 'check_cherry_pick';
memoize 'has_no_changes';
}

sub parents_exclude {
Expand Down Expand Up @@ -3134,10 +3195,21 @@ sub find_extra_svn_parents {
my $ranges = $ranges{$merge_tip};

# check out 'new' tips
my $merge_base = command_oneline(
"merge-base",
@$parents, $merge_tip,
);
my $merge_base;
eval {
$merge_base = command_oneline(
"merge-base",
@$parents, $merge_tip,
);
};
if ($@) {
die "An error occurred during merge-base"
unless $@->isa("Git::Error::Command");

warn "W: Cannot find common ancestor between ".
"@$parents and $merge_tip. Ignoring merge info.\n";
next;
}

# double check that there are no missing non-merge commits
my (@incomplete) = check_cherry_pick(
Expand Down Expand Up @@ -3253,6 +3325,10 @@ sub make_log_entry {
die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
"options set!\n";
}
if ($self->rewrite_uuid) {
die "Can't have both 'useSvmProps' and 'rewriteUUID' ",
"options set!\n";
}
my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}i;
# we don't want "SVM: initializing mirror for junk" ...
return undef if $r == 0;
Expand Down Expand Up @@ -3283,10 +3359,10 @@ sub make_log_entry {
} else {
my $url = $self->metadata_url;
remove_username($url);
$log_entry{metadata} = "$url\@$rev " .
$self->ra->get_uuid;
$email ||= "$author\@" . $self->ra->get_uuid;
$commit_email ||= "$author\@" . $self->ra->get_uuid;
my $uuid = $self->rewrite_uuid || $self->ra->get_uuid;
$log_entry{metadata} = "$url\@$rev " . $uuid;
$email ||= "$author\@" . $uuid;
$commit_email ||= "$author\@" . $uuid;
}
$log_entry{name} = $name;
$log_entry{email} = $email;
Expand Down Expand Up @@ -3368,7 +3444,7 @@ sub rebuild {
'--');
my $metadata_url = $self->metadata_url;
remove_username($metadata_url);
my $svn_uuid = $self->ra_uuid;
my $svn_uuid = $self->rewrite_uuid || $self->ra_uuid;
my $c;
while (<$log>) {
if ( m{^commit ($::sha1)$} ) {
Expand Down Expand Up @@ -5157,6 +5233,7 @@ sub match_globs {
next if (length $g->{path}->{right} &&
($self->check_path($p, $r) !=
$SVN::Node::dir));
next unless $p =~ /$g->{path}->{regex}/;
$exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
$g->{ref}->full_path($de), 1);
}
Expand Down Expand Up @@ -5930,29 +6007,48 @@ package Git::SVN::GlobSpec;
use warnings;

sub new {
my ($class, $glob) = @_;
my ($class, $glob, $pattern_ok) = @_;
my $re = $glob;
$re =~ s!/+$!!g; # no need for trailing slashes
$re =~ m!^([^*]*)(\*(?:/\*)*)(.*)$!;
my $temp = $re;
my ($left, $right) = ($1, $3);
$re = $2;
my $depth = $re =~ tr/*/*/;
if ($depth != $temp =~ tr/*/*/) {
die "Only one set of wildcard directories " .
"(e.g. '*' or '*/*/*') is supported: '$glob'\n";
my (@left, @right, @patterns);
my $state = "left";
my $die_msg = "Only one set of wildcard directories " .
"(e.g. '*' or '*/*/*') is supported: '$glob'\n";
for my $part (split(m|/|, $glob)) {
if ($part =~ /\*/ && $part ne "*") {
die "Invalid pattern in '$glob': $part\n";
} elsif ($pattern_ok && $part =~ /[{}]/ &&
$part !~ /^\{[^{}]+\}/) {
die "Invalid pattern in '$glob': $part\n";
}
if ($part eq "*") {
die $die_msg if $state eq "right";
$state = "pattern";
push(@patterns, "[^/]*");
} elsif ($pattern_ok && $part =~ /^\{(.*)\}$/) {
die $die_msg if $state eq "right";
$state = "pattern";
my $p = quotemeta($1);
$p =~ s/\\,/|/g;
push(@patterns, "(?:$p)");
} else {
if ($state eq "left") {
push(@left, $part);
} else {
push(@right, $part);
$state = "right";
}
}
}
my $depth = @patterns;
if ($depth == 0) {
die "One '*' is needed for glob: '$glob'\n";
}
$re =~ s!\*!\[^/\]*!g;
$re = quotemeta($left) . "($re)" . quotemeta($right);
if (length $left && !($left =~ s!/+$!!g)) {
die "Missing trailing '/' on left side of: '$glob' ($left)\n";
}
if (length $right && !($right =~ s!^/+!!g)) {
die "Missing leading '/' on right side of: '$glob' ($right)\n";
die "One '*' is needed in glob: '$glob'\n";
}
my $left = join('/', @left);
my $right = join('/', @right);
$re = join('/', @patterns);
$re = join('\/',
grep(length, quotemeta($left), "($re)", quotemeta($right)));
my $left_re = qr/^\/\Q$left\E(\/|$)/;
bless { left => $left, right => $right, left_regex => $left_re,
regex => qr/$re/, glob => $glob, depth => $depth }, $class;
Expand Down
2 changes: 1 addition & 1 deletion t/t9151-svn-mergeinfo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test_expect_success 'svn non-merge merge commits did not become git merge commit
[ -z "$bad_non_merges" ]
'

test_expect_success 'everything got merged in the end' '
test_expect_failure 'everything got merged in the end' '
unmerged=$(git rev-list --all --not master)
[ -z "$unmerged" ]
'
Expand Down
Loading

0 comments on commit 2bd6dbb

Please sign in to comment.