git-svn: --follow-parent now works on sub-directories of larger branches
[gitweb.git] / git-svn.perl
index 88c022701de2d14af64271caf3a9b9ee88970f82..123d4d63f425739b59e18f66577e529419cb94ed 100755 (executable)
@@ -277,8 +277,13 @@ sub cmd_init {
 }
 
 sub cmd_fetch {
+       if (@_) {
+               die "Additional fetch arguments are no longer supported.\n",
+                   "Use --follow-parent if you have moved/copied directories
+                   instead.\n";
+       }
        my $gs = Git::SVN->new;
-       $gs->fetch(@_);
+       $gs->fetch;
        if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) {
                command_noisy(qw(update-ref refs/heads/master),
                              $gs->{last_commit});
@@ -720,6 +725,7 @@ sub read_all_remotes {
 }
 
 sub verify_remotes_sanity {
+       return unless -d $ENV{GIT_DIR};
        my %seen;
        foreach (command(qw/config -l/)) {
                if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) {
@@ -742,35 +748,78 @@ sub sanitize_remote_name {
        $name;
 }
 
-sub init {
-       my ($class, $url, $path, $repo_id, $ref_id) = @_;
-       my $self = _new($class, $repo_id, $ref_id, $path);
-       if (defined $url) {
-               $url =~ s!/+$!!; # strip trailing slash
+sub find_existing_remote {
+       my ($url, $remotes) = @_;
+       my $existing;
+       foreach my $repo_id (keys %$remotes) {
+               my $u = $remotes->{$repo_id}->{url} or next;
+               next if $u ne $url;
+               $existing = $repo_id;
+               last;
+       }
+       $existing;
+}
 
+sub init_remote_config {
+       my ($self, $url) = @_;
+       $url =~ s!/+$!!; # strip trailing slash
+       my $r = read_all_remotes();
+       my $existing = find_existing_remote($url, $r);
+       if ($existing) {
+               print STDERR "Using existing ",
+                            "[svn-remote \"$existing\"]\n";
+               $self->{repo_id} = $existing;
+       } else {
+               my $min_url = Git::SVN::Ra->new($url)->minimize_url;
+               $existing = find_existing_remote($min_url, $r);
+               if ($existing) {
+                       print STDERR "Using existing ",
+                                    "[svn-remote \"$existing\"]\n";
+                       $self->{repo_id} = $existing;
+               }
+               if ($min_url ne $url) {
+                       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";
+                       }
+                       $url = $min_url;
+               }
+       }
+       my $orig_url;
+       if (!$existing) {
                # verify that we aren't overwriting anything:
-               my $orig_url = eval {
+               $orig_url = eval {
                        command_oneline('config', '--get',
-                                       "svn-remote.$repo_id.url")
+                                       "svn-remote.$self->{repo_id}.url")
                };
                if ($orig_url && ($orig_url ne $url)) {
-                       die "svn-remote.$repo_id.url already set: ",
+                       die "svn-remote.$self->{repo_id}.url already set: ",
                            "$orig_url\nwanted to set to: $url\n";
                }
-               my ($xrepo_id, $xpath) = find_ref($self->refname);
-               if (defined $xpath) {
-                       die "svn-remote.$xrepo_id.fetch already set to track ",
-                           "$xpath:refs/remotes/", $self->refname, "\n";
-               }
-               if (!$orig_url) {
-                       command_noisy('config',
-                                     "svn-remote.$repo_id.url", $url);
-               }
-               command_noisy('config', '--add',
-                             "svn-remote.$repo_id.fetch",
-                             "$path:".$self->refname);
        }
+       my ($xrepo_id, $xpath) = find_ref($self->refname);
+       if (defined $xpath) {
+               die "svn-remote.$xrepo_id.fetch already set to track ",
+                   "$xpath:refs/remotes/", $self->refname, "\n";
+       }
+       command_noisy('config',
+                     "svn-remote.$self->{repo_id}.url", $url);
+       command_noisy('config', '--add',
+                     "svn-remote.$self->{repo_id}.fetch",
+                     "$self->{path}:".$self->refname);
        $self->{url} = $url;
+}
+
+sub init {
+       my ($class, $url, $path, $repo_id, $ref_id) = @_;
+       my $self = _new($class, $repo_id, $ref_id, $path);
+       if (defined $url) {
+               $self->init_remote_config($url);
+       }
        $self;
 }
 
@@ -1050,10 +1099,24 @@ sub revisions_eq {
 
 sub find_parent_branch {
        my ($self, $paths, $rev) = @_;
+       return undef unless $::_follow_parent;
 
        # look for a parent from another branch:
-       my $i = $paths->{'/'.$self->rel_path} or return;
-       my $branch_from = $i->copyfrom_path or return;
+       my @b_path_components = split m#/#, $self->rel_path;
+       my @a_path_components;
+       my $i;
+       while (@b_path_components) {
+               $i = $paths->{'/'.join('/', @b_path_components)};
+               last if $i;
+               unshift(@a_path_components, pop(@b_path_components));
+       }
+       goto not_found unless defined $i;
+       my $branch_from = $i->copyfrom_path or goto not_found;
+       if (@a_path_components) {
+               print STDERR "branch_from: $branch_from => ";
+               $branch_from .= '/'.join('/', @a_path_components);
+               print STDERR $branch_from, "\n";
+       }
        my $r = $i->copyfrom_rev;
        my $repos_root = $self->ra->{repos_root};
        my $url = $self->ra->{url};
@@ -1083,10 +1146,11 @@ sub find_parent_branch {
        }
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
        if ($::_follow_parent && (!defined $r0 || !defined $parent)) {
-               foreach (0 .. $r) {
-                       my $log_entry = eval { $gs->do_fetch(undef, $_) };
+               $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub {
+                       my ($paths, $rev) = @_;
+                       my $log_entry = eval { $gs->do_fetch($paths, $rev) };
                        $gs->do_git_commit($log_entry) if $log_entry;
-               }
+               });
                ($r0, $parent) = $gs->last_rev_commit;
        }
        if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) {
@@ -1112,7 +1176,18 @@ sub find_parent_branch {
                }
                return $self->make_log_entry($rev, [$parent], $ed);
        }
-       print STDERR "Branch parent not found...\n";
+not_found:
+       print STDERR "Branch parent for path: '/",
+                    $self->rel_path, "' not found\n";
+       return undef unless $paths;
+       foreach my $x (sort keys %$paths) {
+               my $p = $paths->{$x};
+               print STDERR '  ', $p->action, '  ', $x;
+               if (my $cp_from = $p->copyfrom_path) {
+                       print STDERR "(from $cp_from:", $p->copyfrom_rev, ')';
+               }
+               print STDERR "\n";
+       }
        return undef;
 }
 
@@ -2191,6 +2266,19 @@ sub gs_do_switch {
        $editor->{git_commit_ok};
 }
 
+sub minimize_url {
+       my ($self) = @_;
+       return $self->{url} if ($self->{url} eq $self->{repos_root});
+       my $url = $self->{repos_root};
+       my @components = split(m!/!, $self->{svn_path});
+       my $c = '';
+       do {
+               $url .= "/$c" if length $c;
+               eval { (ref $self)->new($url)->get_latest_revnum };
+       } while ($@ && ($c = shift @components));
+       $url;
+}
+
 sub can_do_switch {
        my $self = shift;
        unless (defined $can_do_switch) {