$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
$VERSION = '@@GIT_VERSION@@';
+# From which subdir have we been invoked?
+my $cmd_dir_prefix = eval {
+ command_oneline([qw/rev-parse --show-prefix/], STDERR => 0)
+} || '';
+
my $git_dir_user_set = 1 if defined $ENV{GIT_DIR};
$ENV{GIT_DIR} ||= '.git';
$Git::SVN::default_repo_id = 'svn';
'set-tree' => [ \&cmd_set_tree,
"Set an SVN repository to a git tree-ish",
{ 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
+ 'create-ignore' => [ \&cmd_create_ignore,
+ 'Create a .gitignore per svn:ignore',
+ { 'revision|r=i' => \$_revision
+ } ],
+ 'propget' => [ \&cmd_propget,
+ 'Print the value of a property on a file or directory',
+ { 'revision|r=i' => \$_revision } ],
'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
{ 'revision|r=i' => \$_revision
} ],
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
$gs ||= Git::SVN->new;
my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
- $gs->traverse_ignore(\*STDOUT, $gs->{path}, $r);
+ $gs->prop_walk($gs->{path}, $r, sub {
+ my ($gs, $path, $props) = @_;
+ print STDOUT "\n# $path\n";
+ my $s = $props->{'svn:ignore'} or return;
+ $s =~ s/[\r\n]+/\n/g;
+ chomp $s;
+ $s =~ s#^#$path#gm;
+ print STDOUT "$s\n";
+ });
+}
+
+sub cmd_create_ignore {
+ my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+ $gs ||= Git::SVN->new;
+ my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
+ $gs->prop_walk($gs->{path}, $r, sub {
+ my ($gs, $path, $props) = @_;
+ # $path is of the form /path/to/dir/
+ my $ignore = '.' . $path . '.gitignore';
+ my $s = $props->{'svn:ignore'} or return;
+ open(GITIGNORE, '>', $ignore)
+ or fatal("Failed to open `$ignore' for writing: $!\n");
+ $s =~ s/[\r\n]+/\n/g;
+ chomp $s;
+ # Prefix all patterns so that the ignore doesn't apply
+ # to sub-directories.
+ $s =~ s#^#/#gm;
+ print GITIGNORE "$s\n";
+ close(GITIGNORE)
+ or fatal("Failed to close `$ignore': $!\n");
+ command_noisy('add', $ignore);
+ });
+}
+
+# get_svnprops(PATH)
+# ------------------
+# Helper for cmd_propget below.
+sub get_svnprops {
+ my $path = shift;
+ my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+ $gs ||= Git::SVN->new;
+
+ # prefix THE PATH by the sub-directory from which the user
+ # invoked us.
+ $path = $cmd_dir_prefix . $path;
+ fatal("No such file or directory: $path\n") unless -e $path;
+ my $is_dir = -d $path ? 1 : 0;
+ $path = $gs->{path} . '/' . $path;
+
+ # canonicalize the path (otherwise libsvn will abort or fail to
+ # find the file)
+ # File::Spec->canonpath doesn't collapse x/../y into y (for a
+ # good reason), so let's do this manually.
+ $path =~ s#/+#/#g;
+ $path =~ s#/\.(?:/|$)#/#g;
+ $path =~ s#/[^/]+/\.\.##g;
+ $path =~ s#/$##g;
+
+ my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
+ my $props;
+ if ($is_dir) {
+ (undef, undef, $props) = $gs->ra->get_dir($path, $r);
+ }
+ else {
+ (undef, $props) = $gs->ra->get_file($path, $r, undef);
+ }
+ return $props;
+}
+
+# cmd_propget (PROP, PATH)
+# ------------------------
+# Print the SVN property PROP for PATH.
+sub cmd_propget {
+ my ($prop, $path) = @_;
+ $path = '.' if not defined $path;
+ usage(1) if not defined $prop;
+ my $props = get_svnprops($path);
+ if (not defined $props->{$prop}) {
+ fatal("`$path' does not have a `$prop' SVN property.\n");
+ }
+ print $props->{$prop} . "\n";
}
sub cmd_multi_init {
$url;
}
-sub traverse_ignore {
- my ($self, $fh, $path, $r) = @_;
- $path =~ s#^/+##g;
- my $ra = $self->ra;
- my ($dirent, undef, $props) = $ra->get_dir($path, $r);
+# prop_walk(PATH, REV, SUB)
+# -------------------------
+# Recursively traverse PATH at revision REV and invoke SUB for each
+# directory that contains a SVN property. SUB will be invoked as
+# follows: &SUB(gs, path, props); where `gs' is this instance of
+# Git::SVN, `path' the path to the directory where the properties
+# `props' were found. The `path' will be relative to point of checkout,
+# that is, if url://repo/trunk is the current Git branch, and that
+# directory contains a sub-directory `d', SUB will be invoked with `/d/'
+# as `path' (note the trailing `/').
+sub prop_walk {
+ my ($self, $path, $rev, $sub) = @_;
+
+ my ($dirent, undef, $props) = $self->ra->get_dir($path, $rev);
+ $path =~ s#^/*#/#g;
my $p = $path;
- $p =~ s#^\Q$self->{path}\E(/|$)##;
- print $fh length $p ? "\n# $p\n" : "\n# /\n";
- if (my $s = $props->{'svn:ignore'}) {
- $s =~ s/[\r\n]+/\n/g;
- chomp $s;
- if (length $p == 0) {
- $s =~ s#\n#\n/$p#g;
- print $fh "/$s\n";
- } else {
- $s =~ s#\n#\n/$p/#g;
- print $fh "/$p/$s\n";
- }
- }
+ # Strip the irrelevant part of the path.
+ $p =~ s#^/+\Q$self->{path}\E(/|$)#/#;
+ # Ensure the path is terminated by a `/'.
+ $p =~ s#/*$#/#;
+
+ # The properties contain all the internal SVN stuff nobody
+ # (usually) cares about.
+ my $interesting_props = 0;
+ foreach (keys %{$props}) {
+ # If it doesn't start with `svn:', it must be a
+ # user-defined property.
+ ++$interesting_props and next if $_ !~ /^svn:/;
+ # FIXME: Fragile, if SVN adds new public properties,
+ # this needs to be updated.
+ ++$interesting_props if /^svn:(?:ignore|keywords|executable
+ |eol-style|mime-type
+ |externals|needs-lock)$/x;
+ }
+ &$sub($self, $p, $props) if $interesting_props;
+
foreach (sort keys %$dirent) {
next if $dirent->{$_}->{kind} != $SVN::Node::dir;
- $self->traverse_ignore($fh, "$path/$_", $r);
+ $self->prop_walk($path . '/' . $_, $rev, $sub);
}
}
my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
$may_save = undef if $_no_auth_cache;
print STDERR "Error validating server certificate for '$realm':\n";
- if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
- print STDERR " - The certificate is not issued by a trusted ",
- "authority. Use the\n",
- " fingerprint to validate the certificate manually!\n";
- }
- if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
- print STDERR " - The certificate hostname does not match.\n";
- }
- if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
- print STDERR " - The certificate is not yet valid.\n";
- }
- if ($failures & $SVN::Auth::SSL::EXPIRED) {
- print STDERR " - The certificate has expired.\n";
- }
- if ($failures & $SVN::Auth::SSL::OTHER) {
- print STDERR " - The certificate has an unknown error.\n";
- }
+ {
+ no warnings 'once';
+ # All variables SVN::Auth::SSL::* are used only once,
+ # so we're shutting up Perl warnings about this.
+ if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+ print STDERR " - The certificate is not issued ",
+ "by a trusted authority. Use the\n",
+ " fingerprint to validate ",
+ "the certificate manually!\n";
+ }
+ if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+ print STDERR " - The certificate hostname ",
+ "does not match.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+ print STDERR " - The certificate is not yet valid.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::EXPIRED) {
+ print STDERR " - The certificate has expired.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::OTHER) {
+ print STDERR " - The certificate has ",
+ "an unknown error.\n";
+ }
+ } # no warnings 'once'
printf STDERR
"Certificate information:\n".
" - Hostname: %s\n".
$password;
}
-package main;
-
-{
- my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
- $SVN::Node::dir.$SVN::Node::unknown.
- $SVN::Node::none.$SVN::Node::file.
- $SVN::Node::dir.$SVN::Node::unknown.
- $SVN::Auth::SSL::CNMISMATCH.
- $SVN::Auth::SSL::NOTYETVALID.
- $SVN::Auth::SSL::EXPIRED.
- $SVN::Auth::SSL::UNKNOWNCA.
- $SVN::Auth::SSL::OTHER;
-}
-
package SVN::Git::Fetcher;
use vars qw/@ISA/;
use strict;
if (!defined $t) {
die "$full_path not known in r$self->{r} or we have a bug!\n";
}
- if ($t == $SVN::Node::none) {
- return $self->add_directory($full_path, $baton,
- undef, -1, $self->{pool});
- } elsif ($t == $SVN::Node::dir) {
- return $self->open_directory($full_path, $baton,
- $self->{r}, $self->{pool});
- }
- print STDERR "$full_path already exists in repository at ",
- "r$self->{r} and it is not a directory (",
- ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
+ {
+ no warnings 'once';
+ # SVN::Node::none and SVN::Node::file are used only once,
+ # so we're shutting up Perl's warnings about them.
+ if ($t == $SVN::Node::none) {
+ return $self->add_directory($full_path, $baton,
+ undef, -1, $self->{pool});
+ } elsif ($t == $SVN::Node::dir) {
+ return $self->open_directory($full_path, $baton,
+ $self->{r}, $self->{pool});
+ } # no warnings 'once'
+ print STDERR "$full_path already exists in repository at ",
+ "r$self->{r} and it is not a directory (",
+ ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
+ } # no warnings 'once'
exit 1;
}
my $dont_store_passwords = 1;
my $conf_t = ${$config}{'config'};
{
+ no warnings 'once';
# The usage of $SVN::_Core::SVN_CONFIG_* variables
# produces warnings that variables are used only once.
# I had not found the better way to shut them up, so
- # warnings are disabled in this block.
- no warnings;
+ # the warnings of type 'once' are disabled in this block.
if (SVN::_Core::svn_config_get_bool($conf_t,
$SVN::_Core::SVN_CONFIG_SECTION_AUTH,
$SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS,
1) == 0) {
$Git::SVN::Prompt::_no_auth_cache = 1;
}
- }
+ } # no warnings 'once'
my $self = SVN::Ra->new(url => $url, auth => $baton,
config => $config,
pool => SVN::Pool->new,