git-svn: avoid redundant get_log calls between invocations
[gitweb.git] / git-svn.perl
index 4e357dfcef8bdec39932d565d77e271bbb97b979..b1d91fa471b11ef6ab2f9c005bb1efac6ea53119 100755 (executable)
@@ -4,12 +4,8 @@
 use warnings;
 use strict;
 use vars qw/   $AUTHOR $VERSION
-               $SVN_URL
-               $GIT_SVN_INDEX $GIT_SVN
-               $GIT_DIR $GIT_SVN_DIR $REVDB
-               $_follow_parent $sha1 $sha1_short $_revision
-               $_cp_remote $_upgrade $_q
-               $_authors %users/;
+               $sha1 $sha1_short $_revision
+               $_q $_authors %users/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';
 
 $Git::SVN::default_repo_id = 'git-svn';
 $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
 
-my $LC_ALL = $ENV{LC_ALL};
 $Git::SVN::Log::TZ = $ENV{TZ};
-# make sure the svn binary gives consistent output between locales and TZs:
 $ENV{TZ} = 'UTC';
-$ENV{LC_ALL} = 'C';
 $| = 1; # unbuffer STDOUT
 
 sub fatal (@) { print STDERR @_; exit 1 }
@@ -59,22 +52,22 @@ BEGIN
 $sha1 = qr/[a-f\d]{40}/;
 $sha1_short = qr/[a-f\d]{4,40}/;
 my ($_stdin, $_help, $_edit,
-       $_repack, $_repack_nr, $_repack_flags,
-       $_message, $_file, $_no_metadata,
+       $_message, $_file,
        $_template, $_shared,
-       $_version, $_upgrade,
+       $_version,
        $_merge, $_strategy, $_dry_run,
        $_prefix);
 
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
                     'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache );
-my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent,
+my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent,
                'authors-file|A=s' => \$_authors,
-               'repack:i' => \$_repack,
-               'no-metadata' => \$_no_metadata,
+               'repack:i' => \$Git::SVN::_repack,
+               'no-metadata' => \$Git::SVN::_no_metadata,
                'quiet|q' => \$_q,
-               'repack-flags|repack-args|repack-opts=s' => \$_repack_flags,
+               'repack-flags|repack-args|repack-opts=s' =>
+                  \$Git::SVN::_repack_flags,
                %remote_opts );
 
 my ($_trunk, $_tags, $_branches);
@@ -106,9 +99,6 @@ BEGIN
                        { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
                        { 'revision|r=i' => \$_revision } ],
-       rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)",
-                       { 'copy-remote|remote=s' => \$_cp_remote,
-                         'upgrade' => \$_upgrade } ],
        'multi-init' => [ \&cmd_multi_init,
                        'Initialize multiple trees (like git-svnimport)',
                        { %multi_opts, %init_opts, %remote_opts,
@@ -155,20 +145,20 @@ BEGIN
 my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
 
 read_repo_config(\%opts);
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
-                               'version|V' => \$_version,
-                               'minimize-connections' =>
-                                 \$Git::SVN::Migration::_minimize,
-                               'id|i=s' => \$Git::SVN::default_ref_id);
+my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+                    'minimize-connections' => \$Git::SVN::Migration::_minimize,
+                    'id|i=s' => \$Git::SVN::default_ref_id,
+                    'svn-remote|remote|R=s' => \$Git::SVN::default_repo_id);
 exit 1 if (!$rv && $cmd ne 'log');
 
 usage(0) if $_help;
 version() if $_version;
 usage(1) unless defined $cmd;
 load_authors() if $_authors;
-unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) {
+unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) {
        Git::SVN::Migration::migration_check();
 }
+Git::SVN::init_vars();
 eval {
        Git::SVN::verify_remotes_sanity();
        $cmd{$cmd}->[0]->(@ARGV);
@@ -211,47 +201,6 @@ sub version {
        exit 0;
 }
 
-sub cmd_rebuild {
-       my $url = shift;
-       my $gs = $url ? Git::SVN->init($url)
-                     : eval { Git::SVN->new };
-       $gs ||= Git::SVN->_new;
-       if (!verify_ref($gs->refname.'^0')) {
-               $gs->copy_remote_ref;
-       }
-
-       my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname);
-       my $latest;
-       my $svn_uuid;
-       while (<$rev_list>) {
-               chomp;
-               my $c = $_;
-               fatal "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
-               my ($url, $rev, $uuid) = cmt_metadata($c);
-
-               # ignore merges (from set-tree)
-               next if (!defined $rev || !$uuid);
-
-               # if we merged or otherwise started elsewhere, this is
-               # how we break out of it
-               if ((defined $svn_uuid && ($uuid ne $svn_uuid)) ||
-                   ($gs->{url} && $url && ($url ne $gs->{url}))) {
-                       next;
-               }
-
-               unless (defined $latest) {
-                       if (!$gs->{url} && !$url) {
-                               fatal "SVN repository location required\n";
-                       }
-                       $gs = Git::SVN->init($url);
-                       $latest = $rev;
-               }
-               $gs->rev_db_set($rev, $c);
-               print "r$rev = $c\n";
-       }
-       command_close_pipe($rev_list, $ctx);
-}
-
 sub do_git_init_db {
        unless (-d $ENV{GIT_DIR}) {
                my @init_db = ('init');
@@ -678,11 +627,14 @@ sub cmt_metadata {
 package Git::SVN;
 use strict;
 use warnings;
-use vars qw/$default_repo_id $default_ref_id/;
+use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
+            $_repack $_repack_flags/;
 use Carp qw/croak/;
 use File::Path qw/mkpath/;
+use File::Copy qw/copy/;
 use IPC::Open3;
 
+my $_repack_nr;
 # properties that we do not log:
 my %SKIP_PROP;
 BEGIN {
@@ -694,24 +646,23 @@ BEGIN
                                        svn:entry:committed-date/;
 }
 
+my %LOCKFILES;
+END { unlink keys %LOCKFILES if %LOCKFILES }
+
 sub fetch_all {
        my ($repo_id, $url, $fetch) = @_;
        my @gs;
        my $ra = Git::SVN::Ra->new($url);
        my $head = $ra->get_latest_revnum;
        my $base = $head;
-       my $new_remote;
        foreach my $p (sort keys %$fetch) {
                my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
-               my $lr = $gs->last_rev;
+               my $lr = $gs->rev_db_max;
                if (defined $lr) {
                        $base = $lr if ($lr < $base);
-               } else {
-                       $new_remote = 1;
                }
                push @gs, $gs;
        }
-       $base = 0 if $new_remote;
        return if (++$base > $head);
        $ra->gs_fetch_loop_common($base, $head, @gs);
 }
@@ -728,6 +679,14 @@ sub read_all_remotes {
        $r;
 }
 
+sub init_vars {
+       if (defined $_repack) {
+               $_repack = 1000 if ($_repack <= 0);
+               $_repack_nr = $_repack;
+               $_repack_flags ||= '-d';
+       }
+}
+
 sub verify_remotes_sanity {
        return unless -d $ENV{GIT_DIR};
        my %seen;
@@ -863,6 +822,9 @@ sub new {
        $self->{url} = command_oneline('config', '--get',
                                       "svn-remote.$repo_id.url") or
                   die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
+       if (-z $self->{db_path} && ::verify_ref($self->refname.'^0')) {
+               $self->rebuild;
+       }
        $self;
 }
 
@@ -883,17 +845,6 @@ sub rel_path {
        $url;
 }
 
-sub copy_remote_ref {
-       my ($self) = @_;
-       my $origin = $::_cp_remote ? $::_cp_remote : 'origin';
-       my $ref = $self->refname;
-       if (command('ls-remote', $origin, $ref)) {
-               command_noisy('fetch', $origin, "$ref:$ref");
-       } elsif ($::_cp_remote && !$::_upgrade) {
-               die "Unable to find remote reference: $ref on $origin\n";
-       }
-}
-
 sub traverse_ignore {
        my ($self, $fh, $path, $r) = @_;
        $path =~ s#^/+##g;
@@ -944,13 +895,17 @@ sub last_rev_commit {
        $rl = readline $fh;
        defined $rl or return (undef, undef);
        chomp $rl;
-       while ($c ne $rl && tell $fh != 0) {
+       while (('0' x40) eq $rl && tell $fh != 0) {
                $offset -= 41;
                seek $fh, $offset, 2;
                $rl = readline $fh;
                defined $rl or return (undef, undef);
                chomp $rl;
        }
+       if ($c) {
+               die "$self->{db_path} and ", $self->refname,
+                   " inconsistent!:\n$c != $rl\n";
+       }
        my $rev = tell $fh;
        croak $! if ($rev < 0);
        $rev =  ($rev - 41) / 41;
@@ -962,7 +917,7 @@ sub last_rev_commit {
 sub get_fetch_range {
        my ($self, $min, $max) = @_;
        $max ||= $self->ra->get_latest_revnum;
-       $min ||= $self->last_rev || 0;
+       $min ||= $self->rev_db_max;
        (++$min, $max);
 }
 
@@ -1033,6 +988,12 @@ sub full_url {
 
 sub do_git_commit {
        my ($self, $log_entry) = @_;
+       my $lr = $self->last_rev;
+       if (defined $lr && $lr >= $log_entry->{revision}) {
+               die "Last fetched revision of ", $self->refname,
+                   " was r$lr, but we are about to fetch: ",
+                   "r$log_entry->{revision}!\n";
+       }
        if (my $c = $self->rev_db_get($log_entry->{revision})) {
                croak "$log_entry->{revision} = $c already exists! ",
                      "Why are we refetching it?\n";
@@ -1058,9 +1019,11 @@ sub do_git_commit {
        defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
                                                                   or croak $!;
        print $msg_fh $log_entry->{log} or croak $!;
-       print $msg_fh "\ngit-svn-id: ", $self->full_url, '@',
-                     $log_entry->{revision}, ' ',
-                     $self->ra->uuid, "\n" or croak $!;
+       unless ($_no_metadata) {
+               print $msg_fh "\ngit-svn-id: ", $self->full_url, '@',
+                             $log_entry->{revision}, ' ',
+                             $self->ra->uuid, "\n" or croak $!;
+       }
        $msg_fh->flush == 0 or croak $!;
        close $msg_fh or croak $!;
        chomp(my $commit = do { local $/; <$out_fh> });
@@ -1071,12 +1034,18 @@ sub do_git_commit {
                die "Failed to commit, invalid sha1: $commit\n";
        }
 
-       command_noisy('update-ref',$self->refname, $commit);
-       $self->rev_db_set($log_entry->{revision}, $commit);
+       $self->rev_db_set($log_entry->{revision}, $commit, 1);
 
        $self->{last_rev} = $log_entry->{revision};
        $self->{last_commit} = $commit;
-       print "r$log_entry->{revision} = $commit\n";
+       print "r$log_entry->{revision} = $commit ($self->{ref_id})\n";
+       if (defined $_repack && (--$_repack_nr == 0)) {
+               $_repack_nr = $_repack;
+               # repack doesn't use any arguments with spaces in them, does it?
+               print "Running git repack $_repack_flags ...\n";
+               command_noisy('repack', split(/\s+/, $_repack_flags));
+               print "Done repacking\n";
+       }
        return $commit;
 }
 
@@ -1092,10 +1061,14 @@ sub revisions_eq {
 
 sub find_parent_branch {
        my ($self, $paths, $rev) = @_;
-       return undef unless $::_follow_parent;
+       return undef unless $_follow_parent;
        unless (defined $paths) {
-               $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1,
-                                  sub { $paths = dup_changed_paths($_[0]) });
+               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, sub {
+                                  $paths =
+                                     Git::SVN::Ra::dup_changed_paths($_[0]) });
+               $SVN::Error::handler = $err_handler;
        }
        return undef unless defined $paths;
 
@@ -1145,7 +1118,7 @@ sub find_parent_branch {
                $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id);
        }
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
-       if ($::_follow_parent && (!defined $r0 || !defined $parent)) {
+       if ($_follow_parent && (!defined $r0 || !defined $parent)) {
                $gs->fetch(0, $r);
                ($r0, $parent) = $gs->last_rev_commit;
        }
@@ -1160,18 +1133,16 @@ sub find_parent_branch {
                        # at the moment), so we can't rely on it
                        $self->{last_commit} = $parent;
                        $ed = SVN::Git::Fetcher->new($self);
-                       $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1,
+                       $gs->ra->gs_do_switch($r0, $rev, $gs,
                                              $self->full_url, $ed)
                          or die "SVN connection failed somewhere...\n";
                } else {
                        print STDERR "Following parent with do_update\n";
                        $ed = SVN::Git::Fetcher->new($self);
-                       $self->ra->gs_do_update($rev, $rev, $self->{path},
-                                               1, $ed)
+                       $self->ra->gs_do_update($rev, $rev, $self, $ed)
                          or die "SVN connection failed somewhere...\n";
                }
                print STDERR "Successfully followed parent\n";
-               $ed->{new_fetch} = 1;
                return $self->make_log_entry($rev, [$parent], $ed);
        }
 not_found:
@@ -1207,10 +1178,8 @@ sub do_fetch {
                        return $log_entry;
                }
                $ed = SVN::Git::Fetcher->new($self);
-               $ed->{new_fetch} = 1;
        }
-       unless ($self->ra->gs_do_update($last_rev, $rev,
-                                       $self->{path}, 1, $ed)) {
+       unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) {
                die "SVN connection failed somewhere...\n";
        }
        $self->make_log_entry($rev, \@parents, $ed);
@@ -1281,8 +1250,6 @@ sub make_log_entry {
        my ($self, $rev, $parents, $ed) = @_;
        my $untracked = $self->get_untracked($ed);
 
-       return undef if (! $ed->{new_fetch} && ! $ed->{nr} && ! @$untracked);
-
        open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!;
        print $un "r$rev\n" or croak $!;
        print $un $_, "\n" foreach @$untracked;
@@ -1346,6 +1313,38 @@ sub set_tree {
        }
 }
 
+sub rebuild {
+       my ($self) = @_;
+       print "Rebuilding $self->{db_path} ...\n";
+       my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
+       my $latest;
+       my $full_url = $self->full_url;
+       my $svn_uuid;
+       while (<$rev_list>) {
+               chomp;
+               my $c = $_;
+               die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
+               my ($url, $rev, $uuid) = ::cmt_metadata($c);
+
+               # ignore merges (from set-tree)
+               next if (!defined $rev || !$uuid);
+
+               # if we merged or otherwise started elsewhere, this is
+               # how we break out of it
+               if ((defined $svn_uuid && ($uuid ne $svn_uuid)) ||
+                   ($full_url && $url && ($url ne $full_url))) {
+                       next;
+               }
+               $latest ||= $rev;
+               $svn_uuid ||= $uuid;
+
+               $self->rev_db_set($rev, $c);
+               print "r$rev = $c\n";
+       }
+       command_close_pipe($rev_list, $ctx);
+       print "Done rebuilding $self->{db_path}\n";
+}
+
 # rev_db:
 # Tie::File seems to be prone to offset errors if revisions get sparse,
 # it's not that fast, either.  Tie::File is also not in Perl 5.6.  So
@@ -1357,22 +1356,62 @@ sub set_tree {
 # to a revision: (41 * rev) is the byte offset.
 # A record of 40 0s denotes an empty revision.
 # And yes, it's still pretty fast (faster than Tie::File).
+# These files are disposable unless --no-metadata is set
 
 sub rev_db_set {
-       my ($self, $rev, $commit) = @_;
+       my ($self, $rev, $commit, $update_ref) = @_;
        length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
-       open my $fh, '+<', $self->{db_path} or croak $!;
+       my ($db, $db_lock) = ($self->{db_path}, "$self->{db_path}.lock");
+       my $sig;
+       if ($update_ref) {
+               $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
+                           $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
+       }
+       $LOCKFILES{$db_lock} = 1;
+       if ($_no_metadata) {
+               copy($db, $db_lock) or die "rev_db_set(@_): ",
+                                          "Failed to copy: ",
+                                          "$db => $db_lock ($!)\n";
+       } else {
+               rename $db, $db_lock or die "rev_db_set(@_): ",
+                                           "Failed to rename: ",
+                                           "$db => $db_lock ($!)\n";
+       }
+       open my $fh, '+<', $db_lock or croak $!;
        my $offset = $rev * 41;
        # assume that append is the common case:
        seek $fh, 0, 2 or croak $!;
        my $pos = tell $fh;
        if ($pos < $offset) {
-               print $fh (('0' x 40),"\n") x (($offset - $pos) / 41)
-                 or croak $!;
+               for (1 .. (($offset - $pos) / 41)) {
+                       print $fh (('0' x 40),"\n") or croak $!;
+               }
        }
        seek $fh, $offset, 0 or croak $!;
        print $fh $commit,"\n" or croak $!;
        close $fh or croak $!;
+       if ($update_ref) {
+               command_noisy('update-ref', '-m', "r$rev",
+                             $self->refname, $commit);
+       }
+       rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ",
+                                   "$db_lock => $db ($!)\n";
+       delete $LOCKFILES{$db_lock};
+       if ($update_ref) {
+               $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
+                           $SIG{USR1} = $SIG{USR2} = 'DEFAULT';
+               kill $sig, $$ if defined $sig;
+       }
+}
+
+sub rev_db_max {
+       my ($self) = @_;
+       my @stat = stat $self->{db_path} or
+                       die "Couldn't stat $self->{db_path}: $!\n";
+       ($stat[7] % 41) == 0 or
+                       die "$self->{db_path} inconsistent size:$stat[7]\n";
+       my $max = $stat[7] / 41;
+       (($max > 0) ? $max - 1 : 0);
 }
 
 sub rev_db_get {
@@ -1623,6 +1662,8 @@ sub delete_entry {
        my ($self, $path, $rev, $pb) = @_;
 
        my $gpath = $self->git_path($path);
+       return undef if ($gpath eq '');
+
        # remove entire directories.
        if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) {
                my ($ls, $ctx) = command_output_pipe(qw/ls-tree
@@ -2240,12 +2281,23 @@ sub uuid {
 }
 
 sub gs_do_update {
-       my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_;
+       my ($self, $rev_a, $rev_b, $gs, $editor) = @_;
+       my $new = ($rev_a == $rev_b);
+       my $path = $gs->{path};
+
+       my $ta = $self->check_path($path, $rev_a);
+       my $tb = $new ? $ta : $self->check_path($path, $rev_b);
+       return 1 if ($tb != $SVN::Node::dir && $ta != $SVN::Node::dir);
+       if ($ta == $SVN::Node::none) {
+               $rev_a = $rev_b;
+               $new = 1;
+       }
+
        my $pool = SVN::Pool->new;
        $editor->set_path_strip($path);
        my (@pc) = split m#/#, $path;
        my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
-                                       $recurse, $editor, $pool);
+                                       1, $editor, $pool);
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
 
        # Since we can't rely on svn_ra_reparent being available, we'll
@@ -2260,7 +2312,6 @@ sub gs_do_update {
        }
        die "BUG: '$sp' != '$final'\n" if ($sp ne $final);
 
-       my $new = ($rev_a == $rev_b);
        $reporter->set_path($sp, $rev_a, $new, @lock, $pool);
 
        $reporter->finish_report($pool);
@@ -2271,7 +2322,8 @@ sub gs_do_update {
 # this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and
 # svn_ra_reparent didn't work before 1.4)
 sub gs_do_switch {
-       my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_;
+       my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;
+       my $path = $gs->{path};
        my $pool = SVN::Pool->new;
 
        my $full_url = $self->{url};
@@ -2289,8 +2341,7 @@ sub gs_do_switch {
                }
        }
        $ra ||= $self;
-       my $reporter = $ra->do_switch($rev_b, '',
-                                     $recurse, $url_b, $editor, $pool);
+       my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
        $reporter->set_path('', $rev_a, 0, @lock, $pool);
        $reporter->finish_report($pool);
@@ -2308,46 +2359,67 @@ sub gs_fetch_loop_common {
        my ($self, $base, $head, @gs) = @_;
        my $inc = 1000;
        my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
-       my @paths = @gs == 1 ? ($gs[0]->{path}) : ('');
        foreach my $gs (@gs) {
                if (my $last_commit = $gs->last_commit) {
                        $gs->assert_index_clean($last_commit);
                }
-               $gs->{path_regex} = qr/^\/\Q$gs->{path}\E\/?/;
        }
        while (1) {
-               my @revs;
+               my %revs;
                my $err;
                my $err_handler = $SVN::Error::handler;
                $SVN::Error::handler = sub {
                        ($err) = @_;
                        skip_unknown_revs($err);
                };
-               $self->get_log(\@paths, $min, $max, 0, 1, 1,
-                   sub { push @revs, [ dup_changed_paths($_[0]), $_[1] ]; });
-               $SVN::Error::handler = $err_handler;
-
-               if (! @revs && $err && $max >= $head) {
-                       print STDERR "Branch probably deleted:\n  ",
-                                    $err->expanded_message,
-                                    "\nWill attempt to follow revisions ",
-                                    "r$min .. r$max ",
-                                    "committed before the deletion\n";
-                       @revs = map { [ undef, $_ ] } ($min .. $max);
-               }
-               foreach (@revs) {
-                       my ($paths, $r) = @$_;
-                       foreach my $gs (@gs) {
-                               if ($paths) {
-                                       grep /$gs->{path_regex}/, keys %$paths
-                                          or next;
+               foreach my $gs (@gs) {
+                       $self->get_log([$gs->{path}], $min, $max, 0, 1, 1, sub
+                                      { my ($paths, $rev) = @_;
+                                        push @{$revs{$rev}},
+                                             [ $gs,
+                                               dup_changed_paths($paths) ] });
+
+                       next unless ($err && $max >= $head);
+
+                       print STDERR "Path '$gs->{path}' ",
+                                    "was probably deleted:\n",
+                                    $err->expanded_message,
+                                    "\nWill attempt to follow ",
+                                    "revisions r$min .. r$max ",
+                                    "committed before the deletion\n";
+                       my $hi = $max;
+                       while (--$hi >= $min) {
+                               my $ok;
+                               $self->get_log([$gs->{path}], $min, $hi,
+                                              0, 1, 1, sub {
+                                       my ($paths, $rev) = @_;
+                                       $ok = $rev;
+                                       push @{$revs{$rev}}, [ $gs,
+                                          dup_changed_paths($_[0])]});
+                               if ($ok) {
+                                       print STDERR "r$min .. r$ok OK\n";
+                                       last;
                                }
+                       }
+               }
+               $SVN::Error::handler = $err_handler;
+               foreach my $r (sort {$a <=> $b} keys %revs) {
+                       foreach (@{$revs{$r}}) {
+                               my ($gs, $paths) = @$_;
+                               my $lr = $gs->last_rev;
+                               next if defined $lr && $lr >= $r;
                                next if defined $gs->rev_db_get($r);
                                if (my $log_entry = $gs->do_fetch($paths, $r)) {
                                        $gs->do_git_commit($log_entry);
                                }
                        }
                }
+               # pre-fill the .rev_db since it'll eventually get filled in
+               # with '0' x40 if something new gets committed
+               foreach my $gs (@gs) {
+                       next if defined $gs->rev_db_get($max);
+                       $gs->rev_db_set($max, 0 x40);
+               }
                last if $max >= $head;
                $min = $max + 1;
                $max += $inc;