summaryrefslogtreecommitdiff
path: root/perl/Git
diff options
context:
space:
mode:
Diffstat (limited to 'perl/Git')
-rw-r--r--perl/Git/SVN.pm111
-rw-r--r--perl/Git/SVN/Editor.pm41
-rw-r--r--perl/Git/SVN/Fetcher.pm11
-rw-r--r--perl/Git/SVN/Ra.pm8
4 files changed, 147 insertions, 24 deletions
diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm
index d9a52a52df..b2c14e2ff5 100644
--- a/perl/Git/SVN.pm
+++ b/perl/Git/SVN.pm
@@ -9,11 +9,10 @@ use vars qw/$_no_metadata
$_use_log_author $_add_author_from $_localtime/;
use Carp qw/croak/;
use File::Path qw/mkpath/;
-use File::Copy qw/copy/;
use IPC::Open3;
use Memoize; # core since 5.8.0, Jul 2002
-use Memoize::Storable;
use POSIX qw(:signal_h);
+use Time::Local;
use Git qw(
command
@@ -32,11 +31,7 @@ use Git::SVN::Utils qw(
add_path_to_url
);
-my $can_use_yaml;
-BEGIN {
- $can_use_yaml = eval { require Git::SVN::Memoize::YAML; 1};
-}
-
+my $memo_backend;
our $_follow_parent = 1;
our $_minimize_url = 'unset';
our $default_repo_id = 'svn';
@@ -1216,20 +1211,87 @@ sub do_fetch {
sub mkemptydirs {
my ($self, $r) = @_;
+ # add/remove/collect a paths table
+ #
+ # Paths are split into a tree of nodes, stored as a hash of hashes.
+ #
+ # Each node contains a 'path' entry for the path (if any) associated
+ # with that node and a 'children' entry for any nodes under that
+ # location.
+ #
+ # Removing a path requires a hash lookup for each component then
+ # dropping that node (and anything under it), which is substantially
+ # faster than a grep slice into a single hash of paths for large
+ # numbers of paths.
+ #
+ # For a large (200K) number of empty_dir directives this reduces
+ # scanning time to 3 seconds vs 10 minutes for grep+delete on a single
+ # hash of paths.
+ sub add_path {
+ my ($paths_table, $path) = @_;
+ my $node_ref;
+
+ foreach my $x (split('/', $path)) {
+ if (!exists($paths_table->{$x})) {
+ $paths_table->{$x} = { children => {} };
+ }
+
+ $node_ref = $paths_table->{$x};
+ $paths_table = $paths_table->{$x}->{children};
+ }
+
+ $node_ref->{path} = $path;
+ }
+
+ sub remove_path {
+ my ($paths_table, $path) = @_;
+ my $nodes_ref;
+ my $node_name;
+
+ foreach my $x (split('/', $path)) {
+ if (!exists($paths_table->{$x})) {
+ return;
+ }
+
+ $nodes_ref = $paths_table;
+ $node_name = $x;
+
+ $paths_table = $paths_table->{$x}->{children};
+ }
+
+ delete($nodes_ref->{$node_name});
+ }
+
+ sub collect_paths {
+ my ($paths_table, $paths_ref) = @_;
+
+ foreach my $v (values %$paths_table) {
+ my $p = $v->{path};
+ my $c = $v->{children};
+
+ collect_paths($c, $paths_ref);
+
+ if (defined($p)) {
+ push(@$paths_ref, $p);
+ }
+ }
+ }
+
sub scan {
- my ($r, $empty_dirs, $line) = @_;
+ my ($r, $paths_table, $line) = @_;
if (defined $r && $line =~ /^r(\d+)$/) {
return 0 if $1 > $r;
} elsif ($line =~ /^ \+empty_dir: (.+)$/) {
- $empty_dirs->{$1} = 1;
+ add_path($paths_table, $1);
} elsif ($line =~ /^ \-empty_dir: (.+)$/) {
- my @d = grep {m[^\Q$1\E(/|$)]} (keys %$empty_dirs);
- delete @$empty_dirs{@d};
+ remove_path($paths_table, $1);
}
1; # continue
};
- my %empty_dirs = ();
+ my @empty_dirs;
+ my %paths_table;
+
my $gz_file = "$self->{dir}/unhandled.log.gz";
if (-f $gz_file) {
if (!can_compress()) {
@@ -1240,7 +1302,7 @@ sub mkemptydirs {
die "Unable to open $gz_file: $!\n";
my $line;
while ($gz->gzreadline($line) > 0) {
- scan($r, \%empty_dirs, $line) or last;
+ scan($r, \%paths_table, $line) or last;
}
$gz->gzclose;
}
@@ -1249,13 +1311,14 @@ sub mkemptydirs {
if (open my $fh, '<', "$self->{dir}/unhandled.log") {
binmode $fh or croak "binmode: $!";
while (<$fh>) {
- scan($r, \%empty_dirs, $_) or last;
+ scan($r, \%paths_table, $_) or last;
}
close $fh;
}
+ collect_paths(\%paths_table, \@empty_dirs);
my $strip = qr/\A\Q@{[$self->path]}\E(?:\/|$)/;
- foreach my $d (sort keys %empty_dirs) {
+ foreach my $d (sort @empty_dirs) {
$d = uri_decode($d);
$d =~ s/$strip//;
next unless length($d);
@@ -1332,7 +1395,7 @@ sub parse_svn_date {
$ENV{TZ} = 'UTC';
my $epoch_in_UTC =
- POSIX::strftime('%s', $S, $M, $H, $d, $m - 1, $Y - 1900);
+ Time::Local::timelocal($S, $M, $H, $d, $m - 1, $Y - 1900);
# Determine our local timezone (including DST) at the
# time of $epoch_in_UTC. $Git::SVN::Log::TZ stored the
@@ -1578,7 +1641,16 @@ sub tie_for_persistent_memoization {
my $hash = shift;
my $path = shift;
- if ($can_use_yaml) {
+ unless ($memo_backend) {
+ if (eval { require Git::SVN::Memoize::YAML; 1}) {
+ $memo_backend = 1;
+ } else {
+ require Memoize::Storable;
+ $memo_backend = -1;
+ }
+ }
+
+ if ($memo_backend > 0) {
tie %$hash => 'Git::SVN::Memoize::YAML', "$path.yaml";
} else {
tie %$hash => 'Memoize::Storable', "$path.db", 'nstore';
@@ -2188,8 +2260,9 @@ sub rev_map_set {
# both of these options make our .rev_db file very, very important
# and we can't afford to lose it because rebuild() won't work
if ($self->use_svm_props || $self->no_metadata) {
+ require File::Copy;
$sync = 1;
- copy($db, $db_lock) or die "rev_map_set(@_): ",
+ File::Copy::copy($db, $db_lock) or die "rev_map_set(@_): ",
"Failed to copy: ",
"$db => $db_lock ($!)\n";
} else {
@@ -2365,7 +2438,7 @@ sub _new {
# 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/(.*)}) {
+ if ($ref_id =~ m{^refs/remotes/(.+)}) {
my $old_dir = "$ENV{GIT_DIR}/svn/$1";
if (-d $old_dir && ! -d $dir) {
$dir = $old_dir;
diff --git a/perl/Git/SVN/Editor.pm b/perl/Git/SVN/Editor.pm
index 34e8af966c..c50176eec9 100644
--- a/perl/Git/SVN/Editor.pm
+++ b/perl/Git/SVN/Editor.pm
@@ -5,7 +5,6 @@ use warnings;
use SVN::Core;
use SVN::Delta;
use Carp qw/croak/;
-use IO::File;
use Git qw/command command_oneline command_noisy command_output_pipe
command_input_pipe command_close_pipe
command_bidi_pipe command_close_bidi_pipe/;
@@ -288,6 +287,40 @@ sub apply_autoprops {
}
}
+sub check_attr {
+ my ($attr,$path) = @_;
+ my $val = command_oneline("check-attr", $attr, "--", $path);
+ if ($val) { $val =~ s/^[^:]*:\s*[^:]*:\s*(.*)\s*$/$1/; }
+ return $val;
+}
+
+sub apply_manualprops {
+ my ($self, $file, $fbat) = @_;
+ my $pending_properties = check_attr( "svn-properties", $file );
+ if ($pending_properties eq "") { return; }
+ # Parse the list of properties to set.
+ my @props = split(/;/, $pending_properties);
+ # TODO: get existing properties to compare to
+ # - this fails for add so currently not done
+ # my $existing_props = ::get_svnprops($file);
+ my $existing_props = {};
+ # TODO: caching svn properties or storing them in .gitattributes
+ # would make that faster
+ foreach my $prop (@props) {
+ # Parse 'name=value' syntax and set the property.
+ if ($prop =~ /([^=]+)=(.*)/) {
+ my ($n,$v) = ($1,$2);
+ for ($n, $v) {
+ s/^\s+//; s/\s+$//;
+ }
+ my $existing = $existing_props->{$n};
+ if (!defined($existing) || $existing ne $v) {
+ $self->change_file_prop($fbat, $n, $v);
+ }
+ }
+ }
+}
+
sub A {
my ($self, $m, $deletions) = @_;
my ($dir, $file) = split_path($m->{file_b});
@@ -296,6 +329,7 @@ sub A {
undef, -1);
print "\tA\t$m->{file_b}\n" unless $::_q;
$self->apply_autoprops($file, $fbat);
+ $self->apply_manualprops($m->{file_b}, $fbat);
$self->chg_file($fbat, $m);
$self->close_file($fbat,undef,$self->{pool});
}
@@ -311,6 +345,7 @@ sub C {
my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
$upa, $self->{r});
print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
+ $self->apply_manualprops($m->{file_b}, $fbat);
$self->chg_file($fbat, $m);
$self->close_file($fbat,undef,$self->{pool});
}
@@ -333,6 +368,7 @@ sub R {
$upa, $self->{r});
print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
$self->apply_autoprops($file, $fbat);
+ $self->apply_manualprops($m->{file_b}, $fbat);
$self->chg_file($fbat, $m);
$self->close_file($fbat,undef,$self->{pool});
@@ -348,6 +384,7 @@ sub M {
my $fbat = $self->open_file($self->repo_path($m->{file_b}),
$pbat,$self->{r},$self->{pool});
print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q;
+ $self->apply_manualprops($m->{file_b}, $fbat);
$self->chg_file($fbat, $m);
$self->close_file($fbat,undef,$self->{pool});
}
@@ -548,7 +585,7 @@ The interface will change as git-svn evolves.
=head1 DEPENDENCIES
Subversion perl bindings,
-the core L<Carp> and L<IO::File> modules,
+the core L<Carp> module,
and git's L<Git> helper module.
C<Git::SVN::Editor> has not been tested using callers other than
diff --git a/perl/Git/SVN/Fetcher.pm b/perl/Git/SVN/Fetcher.pm
index 10edb27732..d8c21ad915 100644
--- a/perl/Git/SVN/Fetcher.pm
+++ b/perl/Git/SVN/Fetcher.pm
@@ -7,7 +7,6 @@ use warnings;
use SVN::Delta;
use Carp qw/croak/;
use File::Basename qw/dirname/;
-use IO::File qw//;
use Git qw/command command_oneline command_noisy command_output_pipe
command_input_pipe command_close_pipe
command_bidi_pipe command_close_bidi_pipe/;
@@ -322,6 +321,14 @@ sub apply_textdelta {
# (but $base does not,) so dup() it for reading in close_file
open my $dup, '<&', $fh or croak $!;
my $base = $::_repository->temp_acquire("git_blob_${$}_$suffix");
+ # close_file may call temp_acquire on 'svn_hash', but because of the
+ # call chain, if the temp_acquire call from close_file ends up being the
+ # call that first creates the 'svn_hash' temp file, then the FileHandle
+ # that's created as a result will end up in an SVN::Pool that we clear
+ # in SVN::Ra::gs_fetch_loop_common. Avoid that by making sure the
+ # 'svn_hash' FileHandle is already created before close_file is called.
+ my $tmp_fh = $::_repository->temp_acquire('svn_hash');
+ $::_repository->temp_release($tmp_fh, 1);
if ($fb->{blob}) {
my ($base_is_link, $size);
@@ -600,7 +607,7 @@ developing git-svn.
=head1 DEPENDENCIES
L<SVN::Delta> from the Subversion perl bindings,
-the core L<Carp>, L<File::Basename>, and L<IO::File> modules,
+the core L<Carp> and L<File::Basename> modules,
and git's L<Git> helper module.
C<Git::SVN::Fetcher> has not been tested using callers other than
diff --git a/perl/Git/SVN/Ra.pm b/perl/Git/SVN/Ra.pm
index 622535e217..4a499fcb38 100644
--- a/perl/Git/SVN/Ra.pm
+++ b/perl/Git/SVN/Ra.pm
@@ -3,7 +3,6 @@ use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;
use strict;
use warnings;
use Memoize;
-use SVN::Client;
use Git::SVN::Utils qw(
canonicalize_url
canonicalize_path
@@ -42,6 +41,7 @@ END {
}
sub _auth_providers () {
+ require SVN::Client;
my @rv = (
SVN::Client::get_simple_provider(),
SVN::Client::get_ssl_server_trust_file_provider(),
@@ -247,7 +247,10 @@ sub get_log {
$ret;
}
+# uncommon, only for ancient SVN (<= 1.4.2)
sub trees_match {
+ require IO::File;
+ require SVN::Client;
my ($self, $url1, $rev1, $url2, $rev2) = @_;
my $ctx = SVN::Client->new(auth => _auth_providers);
my $out = IO::File->new_tmpfile;
@@ -391,6 +394,9 @@ sub longest_common_path {
sub gs_fetch_loop_common {
my ($self, $base, $head, $gsv, $globs) = @_;
return if ($base > $head);
+ # Make sure the cat_blob open2 FileHandle is created before calling
+ # SVN::Pool::new_default so that it does not incorrectly end up in the pool.
+ $::_repository->_open_cat_blob_if_needed;
my $gpool = SVN::Pool->new_default;
my $ra_url = $self->url;
my $reload_ra = sub {