Merge branch 'ms/git-svn-1.7'
authorJunio C Hamano <gitster@pobox.com>
Wed, 22 Aug 2012 18:51:20 +0000 (11:51 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 22 Aug 2012 18:51:20 +0000 (11:51 -0700)
A series by Michael Schwern via Eric to update git-svn to revamp the
way URLs are internally passed around, to make it work with SVN 1.7.

* ms/git-svn-1.7:
git-svn: remove ad-hoc canonicalizations
git-svn: canonicalize newly-minted URLs
git-svn: introduce add_path_to_url function
git-svn: canonicalize earlier
git-svn: replace URL escapes with canonicalization
git-svn: attempt to mimic SVN 1.7 URL canonicalization
t9107: fix typo
t9118: workaround inconsistency between SVN versions
Git::SVN{,::Ra}: canonicalize earlier
git-svn: path canonicalization uses SVN API
Git::SVN::Utils: remove irrelevant comment
git-svn: add join_paths() to safely concatenate paths
git-svn: factor out _collapse_dotdot function
git-svn: use SVN 1.7 to canonicalize when possible
git-svn: move canonicalization to Git::SVN::Utils
use Git::SVN{,::RA}->url accessor globally
use Git::SVN->path accessor globally
Git::SVN::Ra: use accessor for URLs
Git::SVN: use accessor for URLs internally
Git::SVN: use accessors internally for path

1  2 
git-svn.perl
perl/Git/SVN.pm
diff --combined git-svn.perl
index 828b8f0c8e6de81593db108495565e591cd91363,56d1ba712a12695de3fc2d25adfa33539596892e..0d77ffb0b92b9e94454aeb94b434d9f86acd8a13
@@@ -29,7 -29,16 +29,16 @@@ use Git::SVN::Prompt
  use Git::SVN::Log;
  use Git::SVN::Migration;
  
- use Git::SVN::Utils qw(fatal can_compress);
+ use Git::SVN::Utils qw(
+       fatal
+       can_compress
+       canonicalize_path
+       canonicalize_url
+       join_paths
+       add_path_to_url
+       join_paths
+ );
  use Git qw(
        git_cmd_try
        command
@@@ -777,44 -786,6 +786,44 @@@ sub populate_merge_info 
        return undef;
  }
  
 +sub dcommit_rebase {
 +      my ($is_last, $current, $fetched_ref, $svn_error) = @_;
 +      my @diff;
 +
 +      if ($svn_error) {
 +              print STDERR "\nERROR from SVN:\n",
 +                              $svn_error->expanded_message, "\n";
 +      }
 +      unless ($_no_rebase) {
 +              # we always want to rebase against the current HEAD,
 +              # not any head that was passed to us
 +              @diff = command('diff-tree', $current,
 +                         $fetched_ref, '--');
 +              my @finish;
 +              if (@diff) {
 +                      @finish = rebase_cmd();
 +                      print STDERR "W: $current and ", $fetched_ref,
 +                                   " differ, using @finish:\n",
 +                                   join("\n", @diff), "\n";
 +              } elsif ($is_last) {
 +                      print "No changes between ", $current, " and ",
 +                            $fetched_ref,
 +                            "\nResetting to the latest ",
 +                            $fetched_ref, "\n";
 +                      @finish = qw/reset --mixed/;
 +              }
 +              command_noisy(@finish, $fetched_ref) if @finish;
 +      }
 +      if ($svn_error) {
 +              die "ERROR: Not all changes have been committed into SVN"
 +                      .($_no_rebase ? ".\n" : ", however the committed\n"
 +                      ."ones (if any) seem to be successfully integrated "
 +                      ."into the working tree.\n")
 +                      ."Please see the above messages for details.\n";
 +      }
 +      return @diff;
 +}
 +
  sub cmd_dcommit {
        my $head = shift;
        command_noisy(qw/update-index --refresh/);
        }
  
        my $rewritten_parent;
 +      my $current_head = command_oneline(qw/rev-parse HEAD/);
        Git::SVN::remove_username($expect_url);
        if (defined($_merge_info)) {
                $_merge_info =~ tr{ }{\n};
                                        },
                                        mergeinfo => $_merge_info,
                                        svn_path => '');
 +
 +                      my $err_handler = $SVN::Error::handler;
 +                      $SVN::Error::handler = sub {
 +                              my $err = shift;
 +                              dcommit_rebase(1, $current_head, $gs->refname,
 +                                      $err);
 +                      };
 +
                        if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
                                print "No changes\n$d~1 == $d\n";
                        } elsif ($parents->{$d} && @{$parents->{$d}}) {
                                                               $parents->{$d};
                        }
                        $_fetch_all ? $gs->fetch_all : $gs->fetch;
 +                      $SVN::Error::handler = $err_handler;
                        $last_rev = $cmt_rev;
                        next if $_no_rebase;
  
 -                      # we always want to rebase against the current HEAD,
 -                      # not any head that was passed to us
 -                      my @diff = command('diff-tree', $d,
 -                                         $gs->refname, '--');
 -                      my @finish;
 -                      if (@diff) {
 -                              @finish = rebase_cmd();
 -                              print STDERR "W: $d and ", $gs->refname,
 -                                           " differ, using @finish:\n",
 -                                           join("\n", @diff), "\n";
 -                      } else {
 -                              print "No changes between current HEAD and ",
 -                                    $gs->refname,
 -                                    "\nResetting to the latest ",
 -                                    $gs->refname, "\n";
 -                              @finish = qw/reset --mixed/;
 -                      }
 -                      command_noisy(@finish, $gs->refname);
 +                      my @diff = dcommit_rebase(@$linear_refs == 0, $d,
 +                                              $gs->refname, undef);
  
 -                      $rewritten_parent = command_oneline(qw/rev-parse HEAD/);
 +                      $rewritten_parent = command_oneline(qw/rev-parse/,
 +                                                      $gs->refname);
  
                        if (@diff) {
 +                              $current_head = command_oneline(qw/rev-parse
 +                                                              HEAD/);
                                @refs = ();
                                my ($url_, $rev_, $uuid_, $gs_) =
                                              working_head_info('HEAD', \@refs);
                                }
                                $parents = \%p;
                                $linear_refs = \@l;
 +                              undef $last_rev;
                        }
                }
        }
@@@ -1231,7 -1204,7 +1240,7 @@@ sub cmd_show_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 {
+       $gs->prop_walk($gs->path, $r, sub {
                my ($gs, $path, $props) = @_;
                print STDOUT "\n# $path\n";
                my $s = $props->{'svn:ignore'} or return;
@@@ -1247,7 -1220,7 +1256,7 @@@ sub cmd_show_externals 
        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 {
+       $gs->prop_walk($gs->path, $r, sub {
                my ($gs, $path, $props) = @_;
                print STDOUT "\n# $path\n";
                my $s = $props->{'svn:externals'} or return;
@@@ -1262,7 -1235,7 +1271,7 @@@ 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 {
+       $gs->prop_walk($gs->path, $r, sub {
                my ($gs, $path, $props) = @_;
                # $path is of the form /path/to/dir/
                $path = '.' . $path;
@@@ -1292,31 -1265,6 +1301,6 @@@ sub cmd_mkdirs 
        $gs->mkemptydirs($_revision);
  }
  
- sub canonicalize_path {
-       my ($path) = @_;
-       my $dot_slash_added = 0;
-       if (substr($path, 0, 1) ne "/") {
-               $path = "./" . $path;
-               $dot_slash_added = 1;
-       }
-       # 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;
-       $path =~ s#^\./## if $dot_slash_added;
-       $path =~ s#^/##;
-       $path =~ s#^\.$##;
-       return $path;
- }
- sub canonicalize_url {
-       my ($url) = @_;
-       $url =~ s#^([^:]+://[^/]*/)(.*)$#$1 . canonicalize_path($2)#e;
-       return $url;
- }
  # get_svnprops(PATH)
  # ------------------
  # Helper for cmd_propget and cmd_proplist below.
@@@ -1330,7 -1278,7 +1314,7 @@@ sub get_svnprops 
        $path = $cmd_dir_prefix . $path;
        fatal("No such file or directory: $path") unless -e $path;
        my $is_dir = -d $path ? 1 : 0;
-       $path = $gs->{path} . '/' . $path;
+       $path = join_paths($gs->{path}, $path);
  
        # canonicalize the path (otherwise libsvn will abort or fail to
        # find the file)
@@@ -1431,8 -1379,8 +1415,8 @@@ sub cmd_commit_diff 
                        fatal("Needed URL or usable git-svn --id in ",
                              "the command-line\n", $usage);
                }
-               $url = $gs->{url};
-               $svn_path = $gs->{path};
+               $url = $gs->url;
+               $svn_path = $gs->path;
        }
        unless (defined $_revision) {
                fatal("-r|--revision is a required argument\n", $usage);
        }
  }
  
- 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, $_;
-       }
-       join('/', @tmp);
- }
- sub escape_url {
-       my ($url) = @_;
-       if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
-               my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
-               $url = "$scheme://$domain$uri";
-       }
-       $url;
- }
  
  sub cmd_info {
        my $path = canonicalize_path(defined($_[0]) ? $_[0] : ".");
        # canonicalize_path() will return "" to make libsvn 1.5.x happy,
        $path = "." if $path eq "";
  
-       my $full_url = $url . ($fullpath eq "" ? "" : "/$fullpath");
+       my $full_url = canonicalize_url( add_path_to_url( $url, $fullpath ) );
  
        if ($_url) {
-               print escape_url($full_url), "\n";
+               print "$full_url\n";
                return;
        }
  
        my $result = "Path: $path\n";
        $result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
-       $result .= "URL: " . escape_url($full_url) . "\n";
+       $result .= "URL: $full_url\n";
  
        eval {
                my $repos_root = $gs->repos_root;
                Git::SVN::remove_username($repos_root);
-               $result .= "Repository Root: " . escape_url($repos_root) . "\n";
+               $result .= "Repository Root: " . canonicalize_url($repos_root) . "\n";
        };
        if ($@) {
                $result .= "Repository Root: (offline)\n";
@@@ -1669,7 -1599,9 +1635,9 @@@ sub post_fetch_checkout 
  
  sub complete_svn_url {
        my ($url, $path) = @_;
-       $path =~ s#/+$##;
+       $path = canonicalize_path($path);
+       # If the path is not a URL...
        if ($path !~ m#^[a-z\+]+://#) {
                if (!defined $url || $url !~ m#^[a-z\+]+://#) {
                        fatal("E: '$path' is not a complete URL ",
@@@ -1686,7 -1618,7 +1654,7 @@@ sub complete_url_ls_init 
                print STDERR "W: $switch not specified\n";
                return;
        }
-       $repo_path =~ s#/+$##;
+       $repo_path = canonicalize_path($repo_path);
        if ($repo_path =~ m#^[a-z\+]+://#) {
                $ra = Git::SVN::Ra->new($repo_path);
                $repo_path = '';
                              "and a separate URL is not specified");
                }
        }
-       my $url = $ra->{url};
+       my $url = $ra->url;
        my $gs = Git::SVN->init($url, undef, undef, undef, 1);
        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})) {
+       if ($orig_url && ($orig_url ne $gs->url)) {
                die "$k already set: $orig_url\n",
-                   "wanted to set to: $gs->{url}\n";
+                   "wanted to set to: $gs->url\n";
        }
-       command_oneline('config', $k, $gs->{url}) unless $orig_url;
-       my $remote_path = "$gs->{path}/$repo_path";
+       command_oneline('config', $k, $gs->url) unless $orig_url;
+       my $remote_path = join_paths( $gs->path, $repo_path );
        $remote_path =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
-       $remote_path =~ s#/+#/#g;
        $remote_path =~ s#^/##g;
        $remote_path .= "/*" if $remote_path !~ /\*/;
        my ($n) = ($switch =~ /^--(\w+)/);
diff --combined perl/Git/SVN.pm
index 8478d0c95293b531547084e19b6680cf73187469,08891452a1abbe69aefe7442ffd44f8f3c7660c0..acb25394f433bc4043f835be4944b13785d46356
@@@ -23,7 -23,14 +23,14 @@@ use Git qw
      command_output_pipe
      command_close_pipe
  );
- use Git::SVN::Utils qw(fatal can_compress);
+ use Git::SVN::Utils qw(
+       fatal
+       can_compress
+       join_paths
+       canonicalize_path
+       canonicalize_url
+       add_path_to_url
+ );
  
  my $can_use_yaml;
  BEGIN {
@@@ -195,9 -202,9 +202,9 @@@ sub read_all_remotes 
                } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
                        $r->{$1}->{svm} = {};
                } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
-                       $r->{$1}->{url} = $2;
+                       $r->{$1}->{url} = canonicalize_url($2);
                } elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) {
-                       $r->{$1}->{pushurl} = $2;
+                       $r->{$1}->{pushurl} = canonicalize_url($2);
                } elsif (m!^(.+)\.ignore-refs=\s*(.*)\s*$!) {
                        $r->{$1}->{ignore_refs_regex} = $2;
                } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
@@@ -290,7 -297,7 +297,7 @@@ sub find_existing_remote 
  
  sub init_remote_config {
        my ($self, $url, $no_write) = @_;
-       $url =~ s!/+$!!; # strip trailing slash
+       $url = canonicalize_url($url);
        my $r = read_all_remotes();
        my $existing = find_existing_remote($url, $r);
        if ($existing) {
                                print STDERR "Using higher level of URL: ",
                                             "$url => $min_url\n";
                        }
-                       my $old_path = $self->{path};
-                       $self->{path} = $url;
-                       $self->{path} =~ s!^\Q$min_url\E(/|$)!!;
-                       if (length $old_path) {
-                               $self->{path} .= "/$old_path";
-                       }
+                       my $old_path = $self->path;
+                       $url =~ s!^\Q$min_url\E(/|$)!!;
+                       $url = join_paths($url, $old_path);
+                       $self->path($url);
                        $url = $min_url;
                }
        }
        unless ($no_write) {
                command_noisy('config',
                              "svn-remote.$self->{repo_id}.url", $url);
-               $self->{path} =~ s{^/}{};
-               $self->{path} =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
+               my $path = $self->path;
+               $path =~ s{^/}{};
+               $path =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg;
+               $self->path($path);
                command_noisy('config', '--add',
                              "svn-remote.$self->{repo_id}.fetch",
-                             "$self->{path}:".$self->refname);
+                             $self->path.":".$self->refname);
        }
-       $self->{url} = $url;
+       $self->url($url);
  }
  
  sub find_by_url { # repos_root and, path are optional
        my ($class, $full_url, $repos_root, $path) = @_;
  
+       $full_url = canonicalize_url($full_url);
        return undef unless defined $full_url;
        remove_username($full_url);
        remove_username($repos_root) if defined $repos_root;
                        }
                        $p =~ s#^\Q$z\E(?:/|$)#$prefix# or next;
                }
+               # remote fetch paths are not URI escaped.  Decode ours
+               # so they match
+               $p = uri_decode($p);
                foreach my $f (keys %$fetch) {
                        next if $f ne $p;
                        return Git::SVN->new($fetch->{$f}, $repo_id, $f);
@@@ -435,20 -449,25 +449,25 @@@ sub new 
                }
        }
        my $self = _new($class, $repo_id, $ref_id, $path);
-       if (!defined $self->{path} || !length $self->{path}) {
+       if (!defined $self->path || !length $self->path) {
                my $fetch = command_oneline('config', '--get',
                                            "svn-remote.$repo_id.fetch",
                                            ":$ref_id\$") or
                     die "Failed to read \"svn-remote.$repo_id.fetch\" ",
                         "\":$ref_id\$\" in config\n";
-               ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
+               my($path) = split(/\s*:\s*/, $fetch);
+               $self->path($path);
        }
-       $self->{path} =~ s{/+}{/}g;
-       $self->{path} =~ s{\A/}{};
-       $self->{path} =~ s{/\z}{};
-       $self->{url} = command_oneline('config', '--get',
-                                      "svn-remote.$repo_id.url") or
+       {
+               my $path = $self->path;
+               $path =~ s{\A/}{};
+               $path =~ s{/\z}{};
+               $self->path($path);
+       }
+       my $url = command_oneline('config', '--get',
+                                 "svn-remote.$repo_id.url") or
                    die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
+       $self->url($url);
        $self->{pushurl} = eval { command_oneline('config', '--get',
                                  "svn-remote.$repo_id.pushurl") };
        $self->rebuild;
@@@ -552,8 -571,7 +571,7 @@@ sub _set_svm_vars 
                # username is of no interest
                $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1};
  
-               my $replace = $ra->{url};
-               $replace .= "/$path" if length $path;
+               my $replace = add_path_to_url($ra->url, $path);
  
                my $section = "svn-remote.$self->{repo_id}";
                tmp_config("$section.svm-source", $src);
        }
  
        my $r = $ra->get_latest_revnum;
-       my $path = $self->{path};
+       my $path = $self->path;
        my %tried;
        while (length $path) {
-               unless ($tried{"$self->{url}/$path"}) {
+               my $try = add_path_to_url($self->url, $path);
+               unless ($tried{$try}) {
                        return $ra if $self->read_svm_props($ra, $path, $r);
-                       $tried{"$self->{url}/$path"} = 1;
+                       $tried{$try} = 1;
                }
                $path =~ s#/?[^/]+$##;
        }
        die "Path: '$path' should be ''\n" if $path ne '';
        return $ra if $self->read_svm_props($ra, $path, $r);
-       $tried{"$self->{url}/$path"} = 1;
+       $tried{ add_path_to_url($self->url, $path) } = 1;
  
-       if ($ra->{repos_root} eq $self->{url}) {
+       if ($ra->{repos_root} eq $self->url) {
                die @err, (map { "  $_\n" } keys %tried), "\n";
        }
  
        $path = $ra->{svn_path};
        $ra = Git::SVN::Ra->new($ra->{repos_root});
        while (length $path) {
-               unless ($tried{"$ra->{url}/$path"}) {
+               my $try = add_path_to_url($ra->url, $path);
+               unless ($tried{$try}) {
                        $ok = $self->read_svm_props($ra, $path, $r);
                        last if $ok;
-                       $tried{"$ra->{url}/$path"} = 1;
+                       $tried{$try} = 1;
                }
                $path =~ s#/?[^/]+$##;
        }
        die "Path: '$path' should be ''\n" if $path ne '';
        $ok ||= $self->read_svm_props($ra, $path, $r);
-       $tried{"$ra->{url}/$path"} = 1;
+       $tried{ add_path_to_url($ra->url, $path) } = 1;
        if (!$ok) {
                die @err, (map { "  $_\n" } keys %tried), "\n";
        }
-       Git::SVN::Ra->new($self->{url});
+       Git::SVN::Ra->new($self->url);
  }
  
  sub svnsync {
@@@ -670,7 -690,7 +690,7 @@@ sub ra_uuid 
                if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/i) {
                        $self->{ra_uuid} = $uuid;
                } else {
-                       die "ra_uuid called without URL\n" unless $self->{url};
+                       die "ra_uuid called without URL\n" unless $self->url;
                        $self->{ra_uuid} = $self->ra->get_uuid;
                        tmp_config('--add', $key, $self->{ra_uuid});
                }
@@@ -694,7 -714,7 +714,7 @@@ sub repos_root 
  
  sub ra {
        my ($self) = shift;
-       my $ra = Git::SVN::Ra->new($self->{url});
+       my $ra = Git::SVN::Ra->new($self->url);
        $self->_set_repos_root($ra->{repos_root});
        if ($self->use_svm_props && !$self->{svm}) {
                if ($self->no_metadata) {
@@@ -728,7 -748,7 +748,7 @@@ sub prop_walk 
        $path =~ s#^/*#/#g;
        my $p = $path;
        # Strip the irrelevant part of the path.
-       $p =~ s#^/+\Q$self->{path}\E(/|$)#/#;
+       $p =~ s#^/+\Q@{[$self->path]}\E(/|$)#/#;
        # Ensure the path is terminated by a `/'.
        $p =~ s#/*$#/#;
  
  
        foreach (sort keys %$dirent) {
                next if $dirent->{$_}->{kind} != $SVN::Node::dir;
-               $self->prop_walk($self->{path} . $p . $_, $rev, $sub);
+               $self->prop_walk($self->path . $p . $_, $rev, $sub);
        }
  }
  
@@@ -919,20 -939,19 +939,19 @@@ sub rewrite_uuid 
  
  sub metadata_url {
        my ($self) = @_;
-       ($self->rewrite_root || $self->{url}) .
-          (length $self->{path} ? '/' . $self->{path} : '');
+       my $url = $self->rewrite_root || $self->url;
+       return canonicalize_url( add_path_to_url( $url, $self->path ) );
  }
  
  sub full_url {
        my ($self) = @_;
-       $self->{url} . (length $self->{path} ? '/' . $self->{path} : '');
+       return canonicalize_url( add_path_to_url( $self->url, $self->path ) );
  }
  
  sub full_pushurl {
        my ($self) = @_;
        if ($self->{pushurl}) {
-               return $self->{pushurl} . (length $self->{path} ? '/' .
-                      $self->{path} : '');
+               return canonicalize_url( add_path_to_url( $self->{pushurl}, $self->path ) );
        } else {
                return $self->full_url;
        }
@@@ -1048,20 -1067,20 +1067,20 @@@ sub do_git_commit 
  
  sub match_paths {
        my ($self, $paths, $r) = @_;
-       return 1 if $self->{path} eq '';
-       if (my $path = $paths->{"/$self->{path}"}) {
+       return 1 if $self->path eq '';
+       if (my $path = $paths->{"/".$self->path}) {
                return ($path->{action} eq 'D') ? 0 : 1;
        }
-       $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//;
+       $self->{path_regex} ||= qr{^/\Q@{[$self->path]}\E/};
        if (grep /$self->{path_regex}/, keys %$paths) {
                return 1;
        }
        my $c = '';
-       foreach (split m#/#, $self->{path}) {
+       foreach (split m#/#, $self->path) {
                $c .= "/$_";
                next unless ($paths->{$c} &&
                             ($paths->{$c}->{action} =~ /^[AR]$/));
-               if ($self->ra->check_path($self->{path}, $r) ==
+               if ($self->ra->check_path($self->path, $r) ==
                    $SVN::Node::dir) {
                        return 1;
                }
@@@ -1075,14 -1094,14 +1094,14 @@@ sub find_parent_branch 
        unless (defined $paths) {
                my $err_handler = $SVN::Error::handler;
                $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs;
-               $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1,
+               $self->ra->get_log([$self->path], $rev, $rev, 0, 1, 1,
                                   sub { $paths = $_[0] });
                $SVN::Error::handler = $err_handler;
        }
        return undef unless defined $paths;
  
        # look for a parent from another branch:
-       my @b_path_components = split m#/#, $self->{path};
+       my @b_path_components = split m#/#, $self->path;
        my @a_path_components;
        my $i;
        while (@b_path_components) {
        }
        my $r = $i->{copyfrom_rev};
        my $repos_root = $self->ra->{repos_root};
-       my $url = $self->ra->{url};
-       my $new_url = $url . $branch_from;
+       my $url = $self->ra->url;
+       my $new_url = canonicalize_url( add_path_to_url( $url, $branch_from ) );
        print STDERR  "Found possible branch point: ",
                      "$new_url => ", $self->full_url, ", $r\n"
                      unless $::_q > 1;
                        ($base, $head) = parse_revision_argument(0, $r);
                } else {
                        if ($r0 < $r) {
-                               $gs->ra->get_log([$gs->{path}], $r0 + 1, $r, 1,
+                               $gs->ra->get_log([$gs->path], $r0 + 1, $r, 1,
                                        0, 1, sub { $base = $_[1] - 1 });
                        }
                }
                        # at the moment), so we can't rely on it
                        $self->{last_rev} = $r0;
                        $self->{last_commit} = $parent;
-                       $ed = Git::SVN::Fetcher->new($self, $gs->{path});
+                       $ed = Git::SVN::Fetcher->new($self, $gs->path);
                        $gs->ra->gs_do_switch($r0, $rev, $gs,
                                              $self->full_url, $ed)
                          or die "SVN connection failed somewhere...\n";
@@@ -1235,7 -1254,7 +1254,7 @@@ sub mkemptydirs 
                close $fh;
        }
  
-       my $strip = qr/\A\Q$self->{path}\E(?:\/|$)/;
+       my $strip = qr/\A\Q@{[$self->path]}\E(?:\/|$)/;
        foreach my $d (sort keys %empty_dirs) {
                $d = uri_decode($d);
                $d =~ s/$strip//;
@@@ -1429,12 -1448,11 +1448,11 @@@ sub find_extra_svk_parents 
        for my $ticket ( @tickets ) {
                my ($uuid, $path, $rev) = split /:/, $ticket;
                if ( $uuid eq $self->ra_uuid ) {
-                       my $url = $self->{url};
-                       my $repos_root = $url;
+                       my $repos_root = $self->url;
                        my $branch_from = $path;
                        $branch_from =~ s{^/}{};
-                       my $gs = $self->other_gs($repos_root."/".$branch_from,
-                                                $url,
+                       my $gs = $self->other_gs(add_path_to_url( $repos_root, $branch_from ),
+                                                $repos_root,
                                                 $branch_from,
                                                 $rev,
                                                 $self->{ref_id});
@@@ -1616,24 -1634,6 +1634,24 @@@ sub tie_for_persistent_memoization 
                Memoize::unmemoize 'has_no_changes';
        }
  
 +      sub clear_memoized_mergeinfo_caches {
 +              die "Only call this method in non-memoized context" if ($memoized);
 +
 +              my $cache_path = "$ENV{GIT_DIR}/svn/.caches/";
 +              return unless -d $cache_path;
 +
 +              for my $cache_file (("$cache_path/lookup_svn_merge",
 +                                   "$cache_path/check_cherry_pick",
 +                                   "$cache_path/has_no_changes")) {
 +                      for my $suffix (qw(yaml db)) {
 +                              my $file = "$cache_file.$suffix";
 +                              next unless -e $file;
 +                              unlink($file) or die "unlink($file) failed: $!\n";
 +                      }
 +              }
 +      }
 +
 +
        Memoize::memoize 'Git::SVN::repos_root';
  }
  
@@@ -1693,7 -1693,7 +1711,7 @@@ sub find_extra_svn_parents 
        # are now marked as merge, we can add the tip as a parent.
        my @merges = split "\n", $mergeinfo;
        my @merge_tips;
-       my $url = $self->{url};
+       my $url = $self->url;
        my $uuid = $self->ra_uuid;
        my %ranges;
        for my $merge ( @merges ) {
@@@ -1875,8 -1875,9 +1893,9 @@@ sub make_log_entry 
                $email ||= "$author\@$uuid";
                $commit_email ||= "$author\@$uuid";
        } elsif ($self->use_svnsync_props) {
-               my $full_url = $self->svnsync->{url};
-               $full_url .= "/$self->{path}" if length $self->{path};
+               my $full_url = canonicalize_url(
+                       add_path_to_url( $self->svnsync->{url}, $self->path )
+               );
                remove_username($full_url);
                my $uuid = $self->svnsync->{uuid};
                $log_entry{metadata} = "$full_url\@$rev $uuid";
@@@ -1923,7 -1924,7 +1942,7 @@@ sub set_tree 
                        tree_b => $tree,
                        editor_cb => sub {
                               $self->set_tree_cb($log_entry, $tree, @_) },
-                       svn_path => $self->{path} );
+                       svn_path => $self->path );
        if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
                print "No changes\nr$self->{last_rev} = $tree\n";
        }
@@@ -2125,13 -2126,8 +2144,13 @@@ sub rev_map_set 
  
        sysopen(my $fh, $db_lock, O_RDWR | O_CREAT)
             or croak "Couldn't open $db_lock: $!\n";
 -      $update_ref eq 'reset' ? _rev_map_reset($fh, $rev, $commit) :
 -                               _rev_map_set($fh, $rev, $commit);
 +      if ($update_ref eq 'reset') {
 +              clear_memoized_mergeinfo_caches();
 +              _rev_map_reset($fh, $rev, $commit);
 +      } else {
 +              _rev_map_set($fh, $rev, $commit);
 +      }
 +
        if ($sync) {
                $fh->flush or die "Couldn't flush $db_lock: $!\n";
                $fh->sync or die "Couldn't sync $db_lock: $!\n";
@@@ -2299,10 -2295,39 +2318,39 @@@ sub _new 
  
        $_[3] = $path = '' unless (defined $path);
        mkpath([$dir]);
-       bless {
+       my $obj = bless {
                ref_id => $ref_id, dir => $dir, index => "$dir/index",
-               path => $path, config => "$ENV{GIT_DIR}/svn/config",
+               config => "$ENV{GIT_DIR}/svn/config",
                map_root => "$dir/.rev_map", repo_id => $repo_id }, $class;
+       # Ensure it gets canonicalized
+       $obj->path($path);
+       return $obj;
+ }
+ sub path {
+       my $self = shift;
+       if (@_) {
+               my $path = shift;
+               $self->{path} = canonicalize_path($path);
+               return;
+       }
+       return $self->{path};
+ }
+ sub url {
+       my $self = shift;
+       if (@_) {
+               my $url = shift;
+               $self->{url} = canonicalize_url($url);
+               return;
+       }
+       return $self->{url};
  }
  
  # for read-only access of old .rev_db formats