my ($_stdin, $_help, $_edit,
$_message, $_file,
$_template, $_shared,
- $_version,
+ $_version, $_fetch_all,
$_merge, $_strategy, $_dry_run,
$_prefix);
$Git::SVN::_follow_parent = 1;
my %cmd = (
fetch => [ \&cmd_fetch, "Download new revisions from SVN",
- { 'revision|r=s' => \$_revision, %fc_opts } ],
+ { 'revision|r=s' => \$_revision,
+ 'all|a' => \$_fetch_all,
+ %fc_opts } ],
init => [ \&cmd_init, "Initialize a repo for tracking" .
" (requires URL argument)",
\%init_opts ],
'prefix=s' => \$_prefix,
} ],
'multi-fetch' => [ \&cmd_multi_fetch,
- 'Fetch multiple trees (like git-svnimport)',
- \%fc_opts ],
+ "Deprecated alias for $0 fetch --all",
+ { 'revision|r=s' => \$_revision, %fc_opts } ],
'migrate' => [ sub { },
# no-op, we automatically run this anyways,
'Migrate configuration/metadata/layout from
}
sub cmd_fetch {
- if (@_) {
- die "Additional fetch arguments are no longer supported.\n",
- "Use --follow-parent if you have moved/copied directories
- instead.\n";
+ if (grep /^\d+=./, @_) {
+ die "'<rev>=<commit>' fetch arguments are ",
+ "no longer supported.\n";
}
- my $gs = Git::SVN->new;
- $gs->fetch(parse_revision_argument());
- if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) {
- command_noisy(qw(update-ref refs/heads/master),
- $gs->{last_commit});
+ my ($remote) = @_;
+ if (@_ > 1) {
+ die "Usage: $0 fetch [--all|-a] [svn-remote]\n";
+ }
+ $remote ||= $Git::SVN::default_repo_id;
+ if ($_fetch_all) {
+ cmd_multi_fetch();
+ } else {
+ Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes());
}
}
sub cmd_dcommit {
my $head = shift;
- my $gs = Git::SVN->new;
$head ||= 'HEAD';
- my @refs = command(qw/rev-list --no-merges/, $gs->refname."..$head");
+ my ($url, $rev, $uuid);
+ my ($fh, $ctx) = command_output_pipe('rev-list', $head);
+ my @refs;
+ my $c;
+ while (<$fh>) {
+ $c = $_;
+ chomp $c;
+ ($url, $rev, $uuid) = cmt_metadata($c);
+ last if (defined $url && defined $rev && defined $uuid);
+ unshift @refs, $c;
+ }
+ close $fh; # most likely breaking the pipe
+ unless (defined $url && defined $rev && defined $uuid) {
+ die "Unable to determine upstream SVN information from ",
+ "$head history:\n $ctx\n";
+ }
+ my $gs = Git::SVN->find_by_url($url) or
+ die "Can't determine fetch information for $url\n";
my $last_rev;
- foreach my $d (reverse @refs) {
+ foreach my $d (@refs) {
if (!verify_ref("$d~1")) {
fatal "Commit $d\n",
"has no parent commit, and therefore ",
} else {
my %ed_opts = ( r => $last_rev,
log => get_commit_entry($d)->{log},
- ra => $gs->ra,
+ ra => Git::SVN::Ra->new($url),
tree_a => "$d~1",
tree_b => $d,
editor_cb => sub {
print "Committed r$_[0]\n";
$last_rev = $_[0]; },
- svn_path => $gs->{path} );
+ svn_path => '');
if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
print "No changes\n$d~1 == $d\n";
}
sub cmd_multi_fetch {
my $remotes = Git::SVN::read_all_remotes();
foreach my $repo_id (sort keys %$remotes) {
- if ($remotes->{$repo_id}->{url} &&
- $remotes->{$repo_id}->{fetch}) {
+ if ($remotes->{$repo_id}->{url}) {
Git::SVN::fetch_all($repo_id, $remotes);
}
}
########################### utility functions #########################
-sub parse_revision_argument {
- if (!defined $_revision || $_revision eq 'BASE:HEAD') {
- return (undef, undef);
- }
- return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
- return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
- return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/);
- return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/);
- die "revision argument: $_revision not understood by git-svn\n",
- "Try using the command-line svn client instead\n";
-}
-
sub complete_svn_url {
my ($url, $path) = @_;
$path =~ s#/+$##;
$gs = Git::SVN->init($url, $path, undef, $ref, 1);
}
if ($gs) {
+ my $k = "svn-remote.$gs->{repo_id}.url";
+ my $orig_url = eval {
+ command_oneline(qw/config --get/, $k)
+ };
+ if ($orig_url && ($orig_url ne $gs->{url})) {
+ die "$k already set: $orig_url\n",
+ "wanted to set to: $gs->{url}\n";
+ }
+ unless ($orig_url) {
+ command_oneline('config', $k, $gs->{url});
+ }
$remote_id = $gs->{repo_id};
last;
}
}
}
+sub parse_revision_argument {
+ my ($base, $head) = @_;
+ if (!defined $::_revision || $::_revision eq 'BASE:HEAD') {
+ return ($base, $head);
+ }
+ return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/);
+ return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/);
+ return ($head, $head) if ($::_revision eq 'HEAD');
+ return ($base, $1) if ($::_revision =~ /^BASE:(\d+)$/);
+ return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/);
+ die "revision argument: $::_revision not understood by git-svn\n";
+}
+
sub fetch_all {
my ($repo_id, $remotes) = @_;
my $remote = $remotes->{$repo_id};
}
}
- foreach my $p (sort keys %$fetch) {
- my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
- my $lr = $gs->rev_db_max;
- if (defined $lr) {
- $base = $lr if ($lr < $base);
+ if ($fetch) {
+ foreach my $p (sort keys %$fetch) {
+ my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
+ my $lr = $gs->rev_db_max;
+ if (defined $lr) {
+ $base = $lr if ($lr < $base);
+ }
+ push @gs, $gs;
}
- push @gs, $gs;
}
+
+ ($base, $head) = parse_revision_argument($base, $head);
$ra->gs_fetch_loop_common($base, $head, \@gs, \@globs);
}
$self->{url} = $url;
}
+sub find_by_url { # repos_root and, path are optional
+ my ($class, $full_url, $repos_root, $path) = @_;
+ my $remotes = read_all_remotes();
+ if (defined $full_url && defined $repos_root && !defined $path) {
+ $path = $full_url;
+ $path =~ s#^\Q$repos_root\E(?:/|$)##;
+ }
+ foreach my $repo_id (keys %$remotes) {
+ my $u = $remotes->{$repo_id}->{url} or next;
+ 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}->{$_});
+ }
+ my $p = $path;
+ unless (defined $p) {
+ $p = $full_url;
+ $p =~ s#^\Q$u\E(?:/|$)## or next;
+ }
+ foreach my $f (keys %$fetch) {
+ next if $f ne $p;
+ return Git::SVN->new($fetch->{$f}, $repo_id, $f);
+ }
+ }
+ undef;
+}
+
sub init {
my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_;
my $self = _new($class, $repo_id, $ref_id, $path);
sub _set_svm_vars {
my ($self, $ra) = @_;
+ return $ra if $self->svm;
+
+ my @err = ( "useSvmProps set, but failed to read SVM properties\n",
+ "(svm:source, svm:mirror, svm:mirror) ",
+ "from the following URLs:\n" );
+ sub read_svm_props {
+ my ($self, $props) = @_;
+ my $src = $props->{'svm:source'};
+ my $mirror = $props->{'svm:mirror'};
+ my $uuid = $props->{'svm:uuid'};
+ return undef if (!$src || !$mirror || !$uuid);
- return $ra if ($self->svm);
-
- # nope, make sure we're connected to the repository root:
- if ($ra->{repos_root} ne $self->{url}) {
- $ra = Git::SVN::Ra->new($ra->{repos_root});
- }
- my $r = $ra->get_latest_revnum;
- my ($props) = ($ra->get_dir('', $r))[2];
- if (my $src = $props->{'svm:source'}) {
- my $section = "svn-remote.$self->{repo_id}";
+ chomp($src, $mirror, $uuid);
+ $uuid =~ m{^[0-9a-f\-]{30,}$}
+ or die "doesn't look right - svm:uuid is '$uuid'\n";
# don't know what a '!' is there for, also the
# username is of no interest
- $src =~ s{!$}{};
+ $src =~ s{/?!$}{$mirror};
+ $src =~ s{/+$}{}; # no trailing slashes please
$src =~ s{(^[a-z\+]*://)[^/@]*@}{$1};
- tmp_config('--add', "$section.svm-source", $src);
- my $uuid = $props->{'svm:uuid'};
- $uuid =~ m{^[0-9a-f\-]{30,}$}
- or die "doesn't look right - svm:uuid is '$uuid'\n";
+ my $section = "svn-remote.$self->{repo_id}";
+ tmp_config('--add', "$section.svm-source", $src);
tmp_config('--add', "$section.svm-uuid", $uuid);
-
$self->{svm} = { source => $src , uuid => $uuid };
+ return 1;
}
- if ($ra->{repos_root} ne $self->{url}) {
- $ra = Git::SVN::Ra->new($self->{url});
+
+ my $r = $ra->get_latest_revnum;
+ my $path = $self->{path};
+ my @tried_a = ($path);
+ while (length $path) {
+ if ($self->read_svm_props(($ra->get_dir($path, $r))[2])) {
+ return $ra;
+ }
+ $path =~ s#/?[^/]+$## && push @tried_a, $path;
}
- $ra;
+ if ($self->read_svm_props(($ra->get_dir('', $r))[2])) {
+ return $ra;
+ }
+
+ if ($ra->{repos_root} eq $self->{url}) {
+ die @err, map { " $self->{url}/$_\n" } @tried_a, "\n";
+ }
+
+ # nope, make sure we're connected to the repository root:
+ my $ok;
+ my @tried_b;
+ $path = $ra->{svn_path};
+ $path =~ s#/?[^/]+$##; # we already tried this one above
+ $ra = Git::SVN::Ra->new($ra->{repos_root});
+ while (length $path) {
+ $ok = $self->read_svm_props(($ra->get_dir($path, $r))[2]);
+ last if $ok;
+ $path =~ s#/?[^/]+$## && push @tried_b, $path;
+ }
+ $ok = $self->read_svm_props(($ra->get_dir('', $r))[2]) unless $ok;
+ if (!$ok) {
+ die @err, map { " $self->{url}/$_\n" } @tried_a, "\n",
+ map { " $ra->{url}/$_\n" } @tried_b, "\n"
+ }
+ Git::SVN::Ra->new($self->{url});
}
# this allows us to memoize our SVN::Ra UUID locally and avoid a
croak "$log_entry->{revision} = $c already exists! ",
"Why are we refetching it?\n";
}
- my $author = $log_entry->{author};
- my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}}
- : ($author, "$author\@".$self->ra->get_uuid));
- $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
- $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+ $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name};
+ $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
+ $log_entry->{email};
$ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
my $tree = $log_entry->{tree};
print STDERR "Found possible branch point: ",
"$new_url => ", $self->full_url, ", $r\n";
$branch_from =~ s#^/##;
- my $remotes = read_all_remotes();
- my $gs;
- foreach my $repo_id (keys %$remotes) {
- my $u = $remotes->{$repo_id}->{url} or next;
- next if $url ne $u;
- my $fetch = $remotes->{$repo_id}->{fetch};
- foreach (qw/branches tags/) {
- resolve_local_globs($url, $fetch,
- $remotes->{$repo_id}->{$_});
- }
- foreach my $f (keys %$fetch) {
- next if $f ne $branch_from;
- $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f);
- last;
- }
- last if $gs;
- }
+ my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
unless ($gs) {
my $ref_id = $self->{ref_id};
$ref_id =~ s/\@\d+$//;
close $un or croak $!;
$log_entry{date} = parse_svn_date($log_entry{date});
- $log_entry{author} = check_author($log_entry{author});
$log_entry{log} .= "\n";
+ my $author = $log_entry{author} = check_author($log_entry{author});
+ my ($name, $email) = defined $::users{$author} ? @{$::users{$author}}
+ : ($author, undef);
if (defined $headrev && $self->use_svm_props) {
my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$};
if ($uuid ne $self->{svm}->{uuid}) {
$full_url .= "/$self->{path}" if length $self->{path};
$log_entry{metadata} = "$full_url\@$r $uuid";
$log_entry{svm_revision} = $r;
+ $email ||= "$author\@$uuid"
} else {
$log_entry{metadata} = $self->full_url . "\@$rev " .
$self->ra->get_uuid;
+ $email ||= "$author\@" . $self->ra->get_uuid;
}
+ $log_entry{name} = $name;
+ $log_entry{email} = $email;
\%log_entry;
}
next if defined $gs->rev_db_get($max);
$gs->rev_db_set($max, 0 x40);
}
+ foreach my $g (@$globs) {
+ my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev";
+ Git::SVN::tmp_config($k, $max);
+ }
last if $max >= $head;
$min = $max + 1;
$max += $inc;
}
sub git_svn_log_cmd {
- my ($r_min, $r_max) = @_;
- my $gs = Git::SVN->_new;
+ my ($r_min, $r_max, @args) = @_;
+ my $head = 'HEAD';
+ foreach my $x (@args) {
+ last if $x eq '--';
+ next unless ::verify_ref("$x^0");
+ $head = $x;
+ last;
+ }
+
+ my $url;
+ my ($fh, $ctx) = command_output_pipe('rev-list', $head);
+ while (<$fh>) {
+ chomp;
+ $url = (::cmt_metadata($_))[0];
+ last if defined $url;
+ }
+ close $fh; # break the pipe
+
+ my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new;
my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
$gs->refname);
push @cmd, '-r' unless $non_recursive;
}
config_pager();
- @args = (git_svn_log_cmd($r_min, $r_max), @args);
+ @args = (git_svn_log_cmd($r_min, $r_max, @args), @args);
my $log = command_output_pipe(@args);
run_pager();
my (@k, $c, $d);