i18n: unpack-trees: avoid substituting only a verb in sentences
[gitweb.git] / perl / Git / SVN / Ra.pm
index 77bceb913ab866b9552487879394b6adc84567b4..e7646968011234c09e1565577357a7678c015762 100644 (file)
@@ -2,9 +2,11 @@ package Git::SVN::Ra;
 use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;
 use strict;
 use warnings;
-use SVN::Client;
+use Memoize;
 use Git::SVN::Utils qw(
        canonicalize_url
+       canonicalize_path
+       add_path_to_url
 );
 
 use SVN::Ra;
@@ -30,7 +32,16 @@ BEGIN
        }
 }
 
+# serf has a bug that leads to a coredump upon termination if the
+# remote access object is left around (not fixed yet in serf 1.3.1).
+# Explicitly free it to work around the issue.
+END {
+       $RA = undef;
+       $ra_invalid = 1;
+}
+
 sub _auth_providers () {
+       require SVN::Client;
        my @rv = (
          SVN::Client::get_simple_provider(),
          SVN::Client::get_ssl_server_trust_file_provider(),
@@ -66,60 +77,55 @@ ()
        \@rv;
 }
 
-sub escape_uri_only {
-       my ($uri) = @_;
-       my @tmp;
-       foreach (split m{/}, $uri) {
-               s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
-               push @tmp, $_;
+sub prepare_config_once {
+       SVN::_Core::svn_config_ensure($config_dir, undef);
+       my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);
+       my $config = SVN::Core::config_get_config($config_dir);
+       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
+       # 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) {
+               my $val = '1';
+               if (::compare_svn_version('1.9.0') < 0) { # pre-SVN r1553823
+                       my $dont_store_passwords = 1;
+                       $val = bless \$dont_store_passwords, "_p_void";
+               }
+               SVN::_Core::svn_auth_set_parameter($baton,
+                   $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,
+                   $val);
        }
-       join('/', @tmp);
-}
-
-sub escape_url {
-       my ($url) = @_;
-       if ($url =~ m#^(https?)://([^/]+)(.*)$#) {
-               my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
-               $url = "$scheme://$domain$uri";
+       if (SVN::_Core::svn_config_get_bool($conf_t,
+           $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
+           $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
+           1) == 0) {
+               $Git::SVN::Prompt::_no_auth_cache = 1;
        }
-       $url;
+
+       return ($config, $baton, $callbacks);
+} # no warnings 'once'
+
+INIT {
+       Memoize::memoize '_auth_providers';
+       Memoize::memoize 'prepare_config_once';
 }
 
 sub new {
        my ($class, $url) = @_;
-       $url =~ s!/+$!!;
+       $url = canonicalize_url($url);
        return $RA if ($RA && $RA->url eq $url);
 
        ::_req_svn();
 
-       SVN::_Core::svn_config_ensure($config_dir, undef);
-       my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);
-       my $config = SVN::Core::config_get_config($config_dir);
        $RA = undef;
-       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
-               # 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) {
-                       SVN::_Core::svn_auth_set_parameter($baton,
-                           $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,
-                           bless (\$dont_store_passwords, "_p_void"));
-               }
-               if (SVN::_Core::svn_config_get_bool($conf_t,
-                   $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
-                   $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
-                   1) == 0) {
-                       $Git::SVN::Prompt::_no_auth_cache = 1;
-               }
-       } # no warnings 'once'
-       my $self = SVN::Ra->new(url => escape_url($url), auth => $baton,
+       my ($config, $baton, $callbacks) = prepare_config_once();
+       my $self = SVN::Ra->new(url => $url, auth => $baton,
                              config => $config,
                              pool => SVN::Pool->new,
                              auth_provider_callbacks => $callbacks);
@@ -173,7 +179,17 @@ sub get_dir {
                }
        }
        my $pool = SVN::Pool->new;
-       my ($d, undef, $props) = $self->SUPER::get_dir($dir, $r, $pool);
+       my ($d, undef, $props);
+
+       if (::compare_svn_version('1.4.0') >= 0) {
+               # n.b. in addition to being potentially more efficient,
+               # this works around what appears to be a bug in some
+               # SVN 1.8 versions
+               my $kind = 1; # SVN_DIRENT_KIND
+               ($d, undef, $props) = $self->get_dir2($dir, $r, $kind, $pool);
+       } else {
+               ($d, undef, $props) = $self->SUPER::get_dir($dir, $r, $pool);
+       }
        my %dirents = map { $_ => { kind => $d->{$_}->kind } } keys %$d;
        $pool->clear;
        if ($r != $cache->{r}) {
@@ -184,10 +200,6 @@ sub get_dir {
        wantarray ? (\%dirents, $r, $props) : \%dirents;
 }
 
-sub DESTROY {
-       # do not call the real DESTROY since we store ourselves in $RA
-}
-
 # get_log(paths, start, end, limit,
 #         discover_changed_paths, strict_node_history, receiver)
 sub get_log {
@@ -215,6 +227,7 @@ sub get_log {
                                qw/copyfrom_path copyfrom_rev action/;
                        if ($s{'copyfrom_path'}) {
                                $s{'copyfrom_path'} =~ s/$prefix_regex//;
+                               $s{'copyfrom_path'} = canonicalize_path($s{'copyfrom_path'});
                        }
                        $_[0]{$p} = \%s;
                }
@@ -238,7 +251,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;
@@ -305,27 +321,30 @@ sub gs_do_switch {
        my $path = $gs->path;
        my $pool = SVN::Pool->new;
 
-       my $full_url = $self->url;
-       my $old_url = $full_url;
-       $full_url .= '/' . $path if length $path;
+       my $old_url = $self->url;
+       my $full_url = add_path_to_url( $self->url, $path );
        my ($ra, $reparented);
 
-       if ($old_url =~ m#^svn(\+ssh)?://# ||
+       if ($old_url =~ m#^svn(\+\w+)?://# ||
            ($full_url =~ m#^https?://# &&
-            escape_url($full_url) ne $full_url)) {
+            canonicalize_url($full_url) ne $full_url)) {
                $_[0] = undef;
                $self = undef;
                $RA = undef;
                $ra = Git::SVN::Ra->new($full_url);
                $ra_invalid = 1;
        } elsif ($old_url ne $full_url) {
-               SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);
+               SVN::_Ra::svn_ra_reparent(
+                       $self->{session},
+                       canonicalize_url($full_url),
+                       $pool
+               );
                $self->url($full_url);
                $reparented = 1;
        }
 
        $ra ||= $self;
-       $url_b = escape_url($url_b);
+       $url_b = canonicalize_url($url_b);
        my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
        my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
        $reporter->set_path('', $rev_a, 0, @lock, $pool);
@@ -379,10 +398,22 @@ 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 {
+               $_[0] = undef;
+               $self = undef;
+               $RA = undef;
+               $gpool->clear;
+               $self = Git::SVN::Ra->new($ra_url);
+               $ra_invalid = undef;
+       };
        my $inc = $_log_window_size;
        my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
        my $longest_path = longest_common_path($gsv, $globs);
-       my $ra_url = $self->url;
        my $find_trailing_edge;
        while (1) {
                my %revs;
@@ -427,9 +458,9 @@ sub gs_fetch_loop_common {
                }
                $SVN::Error::handler = $err_handler;
 
-               my %exists = map { $_->{path} => $_ } @$gsv;
+               my %exists = map { $_->path => $_ } @$gsv;
                foreach my $r (sort {$a <=> $b} keys %revs) {
-                       my ($paths, $logged) = @{$revs{$r}};
+                       my ($paths, $logged) = @{delete $revs{$r}};
 
                        foreach my $gs ($self->match_globs(\%exists, $paths,
                                                           $globs, $r)) {
@@ -452,13 +483,7 @@ sub gs_fetch_loop_common {
                                        "$g->{t}-maxRev";
                                Git::SVN::tmp_config($k, $r);
                        }
-                       if ($ra_invalid) {
-                               $_[0] = undef;
-                               $self = undef;
-                               $RA = undef;
-                               $self = Git::SVN::Ra->new($ra_url);
-                               $ra_invalid = undef;
-                       }
+                       $reload_ra->() if $ra_invalid;
                }
                # pre-fill the .rev_db since it'll eventually get filled in
                # with '0' x40 if something new gets committed
@@ -475,6 +500,8 @@ sub gs_fetch_loop_common {
                $min = $max + 1;
                $max += $inc;
                $max = $head if ($max > $head);
+
+               $reload_ra->();
        }
        Git::SVN::gc();
 }
@@ -573,14 +600,15 @@ sub minimize_url {
        my @components = split(m!/!, $self->{svn_path});
        my $c = '';
        do {
-               $url .= "/$c" if length $c;
+               $url = add_path_to_url($url, $c);
                eval {
                        my $ra = (ref $self)->new($url);
                        my $latest = $ra->get_latest_revnum;
                        $ra->get_log("", $latest, 0, 1, 0, 1, sub {});
                };
        } while ($@ && ($c = shift @components));
-       $url;
+
+       return canonicalize_url($url);
 }
 
 sub can_do_switch {
@@ -637,6 +665,8 @@ sub skip_unknown_revs {
 1;
 __END__
 
+=head1 NAME
+
 Git::SVN::Ra - Subversion remote access functions for git-svn
 
 =head1 SYNOPSIS