use Git;
BEGIN {
- my $s;
+ # import functions from Git into our packages, en masse
+ no strict 'refs';
foreach (qw/command command_oneline command_noisy command_output_pipe
command_input_pipe command_close_pipe/) {
- $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
- "*Git::SVN::Migration::$_ = ".
- "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
+ for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
+ Git::SVN::Migration Git::SVN::Log Git::SVN),
+ __PACKAGE__) {
+ *{"${package}::$_"} = \&{"Git::$_"};
+ }
}
- eval $s;
}
my ($SVN);
my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
return if -f $index;
- chomp(my $bare = `git config --bool --get core.bare`);
- return if $bare eq 'true';
+ return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false';
return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
print STDERR "Checked out HEAD:\n ",
my $log = $cmd eq 'log';
while (<$authors>) {
chomp;
- next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
+ next unless /^(.+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
my ($user, $name, $email) = ($1, $2, $3);
if ($log) {
$Git::SVN::Log::rusers{"$name <$email>"} = $user;
sub extract_metadata {
my $id = shift or return (undef, undef, undef);
- my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
+ my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
\s([a-f\d\-]+)$/x);
if (!defined $rev || !$uuid || !$url) {
# some of the original repositories I made had
# identifiers like this:
- ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
+ ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
}
return ($url, $rev, $uuid);
}
sub working_head_info {
my ($head, $refs) = @_;
- my ($fh, $ctx) = command_output_pipe('rev-list', $head);
- while (my $hash = <$fh>) {
- chomp($hash);
- my ($url, $rev, $uuid) = cmt_metadata($hash);
+ my ($fh, $ctx) = command_output_pipe('log', $head);
+ my $hash;
+ my %max;
+ while (<$fh>) {
+ if ( m{^commit ($::sha1)$} ) {
+ unshift @$refs, $hash if $hash and $refs;
+ $hash = $1;
+ next;
+ }
+ next unless s{^\s*(git-svn-id:)}{$1};
+ my ($url, $rev, $uuid) = extract_metadata($_);
if (defined $url && defined $rev) {
+ next if $max{$url} and $max{$url} < $rev;
if (my $gs = Git::SVN->find_by_url($url)) {
my $c = $gs->rev_db_get($rev);
if ($c && $c eq $hash) {
close $fh; # break the pipe
return ($url, $rev, $uuid, $gs);
+ } else {
+ $max{$url} ||= $gs->rev_db_max;
}
}
}
- unshift @$refs, $hash if $refs;
}
command_close_pipe($fh, $ctx);
(undef, undef, undef, undef);
# some options are read globally, but can be overridden locally
# per [svn-remote "..."] section. Command-line options will *NOT*
# override options set in an [svn-remote "..."] section
- my $e;
- foreach (qw/follow_parent no_metadata use_svm_props
- use_svnsync_props/) {
- my $key = $_;
+ no strict 'refs';
+ for my $option (qw/follow_parent no_metadata use_svm_props
+ use_svnsync_props/) {
+ my $key = $option;
$key =~ tr/_//d;
- $e .= "sub $_ {
- my (\$self) = \@_;
- return \$self->{-$_} if exists \$self->{-$_};
- my \$k = \"svn-remote.\$self->{repo_id}\.$key\";
- eval { command_oneline(qw/config --get/, \$k) };
- if (\$@) {
- \$self->{-$_} = \$Git::SVN::_$_;
+ my $prop = "-$option";
+ *$option = sub {
+ my ($self) = @_;
+ return $self->{$prop} if exists $self->{$prop};
+ my $k = "svn-remote.$self->{repo_id}.$key";
+ eval { command_oneline(qw/config --get/, $k) };
+ if ($@) {
+ $self->{$prop} = ${"Git::SVN::_$option"};
} else {
- my \$v = command_oneline(qw/config --bool/,\$k);
- \$self->{-$_} = \$v eq 'false' ? 0 : 1;
+ my $v = command_oneline(qw/config --bool/,$k);
+ $self->{$prop} = $v eq 'false' ? 0 : 1;
}
- return \$self->{-$_} }\n";
+ return $self->{$prop};
+ }
}
- $e .= "1;\n";
- eval $e or die $@;
}
my %LOCKFILES;
foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) {
next unless m#^refs/remotes/$ref->{regex}$#;
my $p = $1;
- my $pathname = $path->full_path($p);
- my $refname = $ref->full_path($p);
+ my $pathname = desanitize_refname($path->full_path($p));
+ my $refname = desanitize_refname($ref->full_path($p));
if (my $existing = $fetch->{$pathname}) {
if ($existing ne $refname) {
die "Refspec conflict:\n",
my $r = {};
foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
- $r->{$1}->{fetch}->{$2} = $3;
+ my ($remote, $local_ref, $remote_ref) = ($1, $2, $3);
+ $local_ref =~ s{^/}{};
+ $r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
} elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
$r->{$1}->{url} = $2;
} elsif (m!^(.+)\.(branches|tags)=
unless ($no_write) {
command_noisy('config',
"svn-remote.$self->{repo_id}.url", $url);
+ $self->{path} =~ s{^/}{};
command_noisy('config', '--add',
"svn-remote.$self->{repo_id}.fetch",
"$self->{path}:".$self->refname);
$self;
}
-sub refname { "refs/remotes/$_[0]->{ref_id}" }
+sub refname {
+ my ($refname) = "refs/remotes/$_[0]->{ref_id}" ;
+
+ # 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:
+ if ($refname =~ m{/$}) {
+ die "ref: '$refname' ends with a trailing slash, this is ",
+ "not permitted by git nor Subversion\n";
+ }
+
+ # It cannot have ASCII control character space, tilde ~, caret ^,
+ # colon :, question-mark ?, asterisk *, space, or open bracket [
+ # anywhere.
+ #
+ # Additionally, % must be escaped because it is used for escaping
+ # and we want our escaped refname to be reversible
+ $refname =~ s{([ \%~\^:\?\*\[\t])}{uc sprintf('%%%02x',ord($1))}eg;
+
+ # no slash-separated component can begin with a dot .
+ # /.* becomes /%2E*
+ $refname =~ s{/\.}{/%2E}g;
+
+ # It cannot have two consecutive dots .. anywhere
+ # .. becomes %2E%2E
+ $refname =~ s{\.\.}{%2E%2E}g;
+
+ return $refname;
+}
+
+sub desanitize_refname {
+ my ($refname) = @_;
+ $refname =~ s{%(?:([0-9A-F]{2}))}{chr hex($1)}eg;
+ return $refname;
+}
sub svm_uuid {
my ($self) = @_;
return;
}
print "Rebuilding $db_path ...\n";
- my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
+ my ($log, $ctx) = command_output_pipe("log", $self->refname);
my $latest;
my $full_url = $self->full_url;
remove_username($full_url);
my $svn_uuid;
- while (<$rev_list>) {
- chomp;
- my $c = $_;
- die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
- my ($url, $rev, $uuid) = ::cmt_metadata($c);
+ my $c;
+ while (<$log>) {
+ if ( m{^commit ($::sha1)$} ) {
+ $c = $1;
+ next;
+ }
+ next unless s{^\s*(git-svn-id:)}{$1};
+ my ($url, $rev, $uuid) = ::extract_metadata($_);
remove_username($url);
# ignore merges (from set-tree)
$self->rev_db_set($rev, $c);
print "r$rev = $c\n";
}
- command_close_pipe($rev_list, $ctx);
+ command_close_pipe($log, $ctx);
print "Done rebuilding $db_path\n";
}
sub url_path {
my ($self, $path) = @_;
+ if ($self->{url} =~ m#^https?://#) {
+ $path =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
+ }
$self->{url} . '/' . $self->repo_path($path);
}
BEGIN {
# enforce temporary pool usage for some simple functions
- my $e;
- foreach (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
- $e .= "sub $_ {
- my \$self = shift;
- my \$pool = SVN::Pool->new;
- my \@ret = \$self->SUPER::$_(\@_,\$pool);
- \$pool->clear;
- wantarray ? \@ret : \$ret[0]; }\n";
+ no strict 'refs';
+ for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
+ my $SUPER = "SUPER::$f";
+ *$f = sub {
+ my $self = shift;
+ my $pool = SVN::Pool->new;
+ my @ret = $self->$SUPER(@_,$pool);
+ $pool->clear;
+ wantarray ? @ret : $ret[0];
+ };
}
-
- eval "$e; 1;" or die $@;
}
sub new {
SVN::Client::get_ssl_server_trust_file_provider(),
SVN::Client::get_simple_prompt_provider(
\&Git::SVN::Prompt::simple, 2),
+ SVN::Client::get_ssl_client_cert_file_provider(),
SVN::Client::get_ssl_client_cert_prompt_provider(
\&Git::SVN::Prompt::ssl_client_cert, 2),
SVN::Client::get_ssl_client_cert_pw_prompt_provider(
\&Git::SVN::Prompt::username, 2),
]);
my $config = SVN::Core::config_get_config($config_dir);
+ $RA = undef;
my $self = SVN::Ra->new(url => $url, auth => $baton,
config => $config,
pool => SVN::Pool->new,