git-svn: make (multi-)fetch safer but slower
[gitweb.git] / git-svn.perl
index 83ec03d4786a39f250f8f83c075c9dabf782b1db..efc55156639316259a246e50d8ae7a51439b8155 100755 (executable)
@@ -4,33 +4,19 @@
 use warnings;
 use strict;
 use vars qw/   $AUTHOR $VERSION
-               $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
-               $GIT_SVN_INDEX $GIT_SVN
-               $GIT_DIR $GIT_SVN_DIR $REVDB/;
+               $sha1 $sha1_short $_revision
+               $_q $_authors %users/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';
 
-use Cwd qw/abs_path/;
-$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
-$ENV{GIT_DIR} = $GIT_DIR;
+$ENV{GIT_DIR} ||= '.git';
+$Git::SVN::default_repo_id = 'git-svn';
+$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
 
-my $LC_ALL = $ENV{LC_ALL};
-my $TZ = $ENV{TZ};
-# make sure the svn binary gives consistent output between locales and TZs:
+$Git::SVN::Log::TZ = $ENV{TZ};
 $ENV{TZ} = 'UTC';
-$ENV{LC_ALL} = 'C';
 $| = 1; # unbuffer STDOUT
 
-# properties that we do not log:
-my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1,
-             'svn:special' => 1,
-             'svn:executable' => 1,
-             'svn:entry:committed-rev' => 1,
-             'svn:entry:last-author' => 1,
-             'svn:entry:uuid' => 1,
-             'svn:entry:committed-date' => 1,
-);
-
 sub fatal (@) { print STDERR @_; exit 1 }
 require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
 require SVN::Ra;
 if ($SVN::Core::VERSION lt '1.1.0') {
        fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n";
 }
+push @Git::SVN::Ra::ISA, 'SVN::Ra';
 push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
 push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
-*SVN::Git::Fetcher::process_rm = *process_rm;
 use Carp qw/croak/;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
 use File::Path qw/mkpath/;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
-use POSIX qw/strftime/;
 use IPC::Open3;
-use Memoize;
-use Git qw/command command_oneline command_noisy
-           command_output_pipe command_input_pipe command_close_pipe/;
-memoize('revisions_eq');
-memoize('cmt_metadata');
-memoize('get_commit_time');
+use Git;
+
+BEGIN {
+       my $s;
+       foreach (qw/command command_oneline command_noisy command_output_pipe
+                   command_input_pipe command_close_pipe/) {
+               $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
+                     "*Git::SVN::Migration::$_ = ".
+                     "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
+       }
+       eval $s;
+}
 
 my ($SVN);
 
 my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
-my $sha1 = qr/[a-f\d]{40}/;
-my $sha1_short = qr/[a-f\d]{4,40}/;
-my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
-my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
-       $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
-       $_repack, $_repack_nr, $_repack_flags, $_q,
-       $_message, $_file, $_follow_parent, $_no_metadata,
-       $_template, $_shared, $_no_default_regex, $_no_graft_copy,
-       $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
-       $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
-       $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
-       $_username, $_config_dir, $_no_auth_cache,
-       $_pager, $_color, $_prefix);
-my (@_branch_from, %tree_map, %users, %rusers, %equiv);
-my ($_svn_can_do_switch);
-my @repo_path_split_cache;
-
-my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
-               'branch|b=s' => \@_branch_from,
-               'follow-parent|follow' => \$_follow_parent,
-               'branch-all-refs|B' => \$_branch_all_refs,
+$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,
+       $_template, $_shared,
+       $_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' => \$Git::SVN::_follow_parent,
                'authors-file|A=s' => \$_authors,
                'repack:i' => \$_repack,
-               'no-metadata' => \$_no_metadata,
+               'no-metadata' => \$Git::SVN::_no_metadata,
                'quiet|q' => \$_q,
-               'username=s' => \$_username,
-               'config-dir=s' => \$_config_dir,
-               'no-auth-cache' => \$_no_auth_cache,
-               'ignore-nodate' => \$_ignore_nodate,
-               'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
+               'repack-flags|repack-args|repack-opts=s' => \$_repack_flags,
+               %remote_opts );
 
 my ($_trunk, $_tags, $_branches);
 my %multi_opts = ( 'trunk|T=s' => \$_trunk,
                'branches|b=s' => \$_branches );
 my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared );
 my %cmt_opts = ( 'edit|e' => \$_edit,
-               'rmdir' => \$_rmdir,
-               'find-copies-harder' => \$_find_copies_harder,
-               'l=i' => \$_l,
-               'copy-similarity|C=i'=> \$_cp_similarity
+               'rmdir' => \$SVN::Git::Editor::_rmdir,
+               'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder,
+               'l=i' => \$SVN::Git::Editor::_rename_limit,
+               'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity
 );
 
 my %cmd = (
        fetch => [ \&cmd_fetch, "Download new revisions from SVN",
                        { 'revision|r=s' => \$_revision, %fc_opts } ],
-       init => [ \&init, "Initialize a repo for tracking" .
+       init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
                          \%init_opts ],
-       dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
+       dcommit => [ \&cmd_dcommit,
+                    'Commit several diffs to merge with upstream',
                        { 'merge|m|M' => \$_merge,
                          'strategy|s=s' => \$_strategy,
                          'dry-run|n' => \$_dry_run,
                        %cmt_opts, %fc_opts } ],
-       'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish",
-                       {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
-       'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
+       'set-tree' => [ \&cmd_set_tree,
+                       "Set an SVN repository to a git tree-ish",
+                       { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
+       'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
                        { 'revision|r=i' => \$_revision } ],
-       rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
-                       { 'no-ignore-externals' => \$_no_ignore_ext,
-                         'copy-remote|remote=s' => \$_cp_remote,
-                         'upgrade' => \$_upgrade } ],
-       'graft-branches' => [ \&graft_branches,
-                       'Detect merges/branches from already imported history',
-                       { 'merge-rx|m' => \@_opt_m,
-                         'branch|b=s' => \@_branch_from,
-                         'branch-all-refs|B' => \$_branch_all_refs,
-                         'no-default-regex' => \$_no_default_regex,
-                         'no-graft-copy' => \$_no_graft_copy } ],
-       'multi-init' => [ \&multi_init,
+       'multi-init' => [ \&cmd_multi_init,
                        'Initialize multiple trees (like git-svnimport)',
-                       { %multi_opts, %init_opts,
+                       { %multi_opts, %init_opts, %remote_opts,
                         'revision|r=i' => \$_revision,
-                        'username=s' => \$_username,
-                        'config-dir=s' => \$_config_dir,
-                        'no-auth-cache' => \$_no_auth_cache,
                         'prefix=s' => \$_prefix,
                        } ],
-       'multi-fetch' => [ \&multi_fetch,
+       'multi-fetch' => [ \&cmd_multi_fetch,
                        'Fetch multiple trees (like git-svnimport)',
                        \%fc_opts ],
-       'log' => [ \&show_log, 'Show commit logs',
-                       { 'limit=i' => \$_limit,
+       'migrate' => [ sub { },
+                      # no-op, we automatically run this anyways,
+                      'Migrate configuration/metadata/layout from
+                       previous versions of git-svn',
+                       \%remote_opts ],
+       'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs',
+                       { 'limit=i' => \$Git::SVN::Log::limit,
                          'revision|r=s' => \$_revision,
-                         'verbose|v' => \$_verbose,
-                         'incremental' => \$_incremental,
-                         'oneline' => \$_oneline,
-                         'show-commit' => \$_show_commit,
-                         'non-recursive' => \$_non_recursive,
+                         'verbose|v' => \$Git::SVN::Log::verbose,
+                         'incremental' => \$Git::SVN::Log::incremental,
+                         'oneline' => \$Git::SVN::Log::oneline,
+                         'show-commit' => \$Git::SVN::Log::show_commit,
+                         'non-recursive' => \$Git::SVN::Log::non_recursive,
                          'authors-file|A=s' => \$_authors,
-                         'color' => \$_color,
-                         'pager=s' => \$_pager,
+                         'color' => \$Git::SVN::Log::color,
+                         'pager=s' => \$Git::SVN::Log::pager,
                        } ],
-       'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
+       'commit-diff' => [ \&cmd_commit_diff,
+                          'Commit a diff between two trees',
                        { 'message|m=s' => \$_message,
                          'file|F=s' => \$_file,
                          'revision|r=s' => \$_revision,
 my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
 
 read_repo_config(\%opts);
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
-                               'version|V' => \$_version,
-                               'id|i=s' => \$GIT_SVN);
+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');
 
-set_default_vals();
 usage(0) if $_help;
 version() if $_version;
 usage(1) unless defined $cmd;
-init_vars();
 load_authors() if $_authors;
-load_all_refs() if $_branch_all_refs;
-migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
-$cmd{$cmd}->[0]->(@ARGV);
+unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) {
+       Git::SVN::Migration::migration_check();
+}
+eval {
+       Git::SVN::verify_remotes_sanity();
+       $cmd{$cmd}->[0]->(@ARGV);
+};
+fatal $@ if $@;
 exit 0;
 
 ####################### primary functions ######################
@@ -221,173 +200,46 @@ sub version {
        exit 0;
 }
 
-sub rebuild {
-       if (!verify_ref("refs/remotes/$GIT_SVN^0")) {
-               copy_remote_ref();
-       }
-       $SVN_URL = shift or undef;
-       my $newest_rev = 0;
-       if ($_upgrade) {
-               command_noisy('update-ref',"refs/remotes/$GIT_SVN","
-                             $GIT_SVN-HEAD");
-       } else {
-               check_upgrade_needed();
-       }
-
-       my ($rev_list, $ctx) = command_output_pipe("rev-list",
-                                                  "refs/remotes/$GIT_SVN");
-       my $latest;
-       while (<$rev_list>) {
-               chomp;
-               my $c = $_;
-               croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
-               my @commit = grep(/^git-svn-id: /,
-                                 command(qw/cat-file commit/, $c));
-               next if (!@commit); # skip merges
-               my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
-               if (!defined $rev || !$uuid) {
-                       croak "Unable to extract revision or UUID from ",
-                               "$c, $commit[$#commit]\n";
-               }
-
-               # if we merged or otherwise started elsewhere, this is
-               # how we break out of it
-               next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
-               next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
-
-               unless (defined $latest) {
-                       if (!$SVN_URL && !$url) {
-                               croak "SVN repository location required: $url\n";
-                       }
-                       $SVN_URL ||= $url;
-                       $SVN_UUID ||= $uuid;
-                       setup_git_svn();
-                       $latest = $rev;
-               }
-               revdb_set($REVDB, $rev, $c);
-               print "r$rev = $c\n";
-               $newest_rev = $rev if ($rev > $newest_rev);
+sub do_git_init_db {
+       unless (-d $ENV{GIT_DIR}) {
+               my @init_db = ('init');
+               push @init_db, "--template=$_template" if defined $_template;
+               push @init_db, "--shared" if defined $_shared;
+               command_noisy(@init_db);
        }
-       command_close_pipe($rev_list, $ctx);
 }
 
-sub init {
+sub cmd_init {
        my $url = shift or die "SVN repository location required " .
                                "as a command-line argument\n";
-       $url =~ s!/+$!!; # strip trailing slash
-
        if (my $repo_path = shift) {
                unless (-d $repo_path) {
                        mkpath([$repo_path]);
                }
-               $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git";
-               init_vars();
+               chdir $repo_path or croak $!;
+               $ENV{GIT_DIR} = $repo_path . "/.git";
        }
+       do_git_init_db();
 
-       $SVN_URL = $url;
-       unless (-d $GIT_DIR) {
-               my @init_db = ('init');
-               push @init_db, "--template=$_template" if defined $_template;
-               push @init_db, "--shared" if defined $_shared;
-               command_noisy(@init_db);
-       }
-       setup_git_svn();
+       Git::SVN->init($url);
 }
 
 sub cmd_fetch {
-       fetch_child_id($GIT_SVN, @_);
-}
-
-sub fetch {
-       check_upgrade_needed();
-       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       my $ret = fetch_lib(@_);
-       if ($ret->{commit} && !verify_ref('refs/heads/master^0')) {
-               command_noisy(qw(update-ref refs/heads/master),$ret->{commit});
-       }
-       return $ret;
-}
-
-sub fetch_lib {
-       my (@parents) = @_;
-       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       $SVN ||= libsvn_connect($SVN_URL);
-       my ($last_rev, $last_commit) = svn_grab_base_rev();
-       my ($base, $head) = libsvn_parse_revision($last_rev);
-       if ($base > $head) {
-               return { revision => $last_rev, commit => $last_commit }
-       }
-       my $index = set_index($GIT_SVN_INDEX);
-
-       # limit ourselves and also fork() since get_log won't release memory
-       # after processing a revision and SVN stuff seems to leak
-       my $inc = 1000;
-       my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
-       read_uuid();
-       if (defined $last_commit) {
-               unless (-e $GIT_SVN_INDEX) {
-                       command_noisy('read-tree', $last_commit);
-               }
-               my $x = command_oneline('write-tree');
-               my ($y) = (command(qw/cat-file commit/, $last_commit)
-                                                       =~ /^tree ($sha1)/m);
-               if ($y ne $x) {
-                       unlink $GIT_SVN_INDEX or croak $!;
-                       command_noisy('read-tree', $last_commit);
-               }
-               $x = command_oneline('write-tree');
-               if ($y ne $x) {
-                       print STDERR "trees ($last_commit) $y != $x\n",
-                                "Something is seriously wrong...\n";
-               }
+       if (@_) {
+               die "Additional fetch arguments are no longer supported.\n",
+                   "Use --follow-parent if you have moved/copied directories
+                   instead.\n";
        }
-       while (1) {
-               # fork, because using SVN::Pool with get_log() still doesn't
-               # seem to help enough to keep memory usage down.
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $SVN::Error::handler = \&libsvn_skip_unknown_revs;
-
-                       # Yes I'm perfectly aware that the fourth argument
-                       # below is the limit revisions number.  Unfortunately
-                       # performance sucks with it enabled, so it's much
-                       # faster to fetch revision ranges instead of relying
-                       # on the limiter.
-                       libsvn_get_log(libsvn_dup_ra($SVN), [''],
-                                       $min, $max, 0, 1, 1,
-                               sub {
-                                       my $log_msg;
-                                       if ($last_commit) {
-                                               $log_msg = libsvn_fetch(
-                                                       $last_commit, @_);
-                                               $last_commit = git_commit(
-                                                       $log_msg,
-                                                       $last_commit,
-                                                       @parents);
-                                       } else {
-                                               $log_msg = libsvn_new_tree(@_);
-                                               $last_commit = git_commit(
-                                                       $log_msg, @parents);
-                                       }
-                               });
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-               ($last_rev, $last_commit) = svn_grab_base_rev();
-               last if ($max >= $head);
-               $min = $max + 1;
-               $max += $inc;
-               $max = $head if ($max > $head);
-               $SVN = libsvn_connect($SVN_URL);
+       my $gs = Git::SVN->new;
+       $gs->fetch(parse_revision_argument());
+       if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) {
+               command_noisy(qw(update-ref refs/heads/master),
+                             $gs->{last_commit});
        }
-       restore_index($index);
-       return { revision => $last_rev, commit => $last_commit };
 }
 
-sub commit {
+sub cmd_set_tree {
        my (@commits) = @_;
-       check_upgrade_needed();
        if ($_stdin || !@commits) {
                print "Reading from stdin...\n";
                @commits = ();
@@ -405,741 +257,239 @@ sub commit {
                } elsif (scalar @tmp > 1) {
                        push @revs, reverse(command('rev-list',@tmp));
                } else {
-                       die "Failed to rev-parse $c\n";
+                       fatal "Failed to rev-parse $c\n";
                }
        }
-       commit_lib(@revs);
-       print "Done committing ",scalar @revs," revisions to SVN\n";
-}
-
-sub commit_lib {
-       my (@revs) = @_;
-       my ($r_last, $cmt_last) = svn_grab_base_rev();
-       defined $r_last or die "Must have an existing revision to commit\n";
-       my $fetched = fetch();
-       if ($r_last != $fetched->{revision}) {
-               print STDERR "There are new revisions that were fetched ",
-                               "and need to be merged (or acknowledged) ",
-                               "before committing.\n",
-                               "last rev: $r_last\n",
-                               " current: $fetched->{revision}\n";
-               exit 1;
-       }
-       read_uuid();
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-       my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
-
-       my $repo;
-       set_svn_commit_env();
-       foreach my $c (@revs) {
-               my $log_msg = get_commit_message($c, $commit_msg);
-
-               # fork for each commit because there's a memory leak I
-               # can't track down... (it's probably in the SVN code)
-               defined(my $pid = open my $fh, '-|') or croak $!;
-               if (!$pid) {
-                       my $ed = SVN::Git::Editor->new(
-                                       {       r => $r_last,
-                                               ra => libsvn_dup_ra($SVN),
-                                               c => $c,
-                                               svn_path => $SVN->{svn_path},
-                                       },
-                                       $SVN->get_commit_editor(
-                                               $log_msg->{msg},
-                                               sub {
-                                                       libsvn_commit_cb(
-                                                               @_, $c,
-                                                               $log_msg->{msg},
-                                                               $r_last,
-                                                               $cmt_last)
-                                               },
-                                               @lock)
-                                       );
-                       my $mods = libsvn_checkout_tree($cmt_last, $c, $ed);
-                       if (@$mods == 0) {
-                               print "No changes\nr$r_last = $cmt_last\n";
-                               $ed->abort_edit;
-                       } else {
-                               $ed->close_edit;
-                       }
-                       exit 0;
-               }
-               my ($r_new, $cmt_new, $no);
-               while (<$fh>) {
-                       print $_;
-                       chomp;
-                       if (/^r(\d+) = ($sha1)$/o) {
-                               ($r_new, $cmt_new) = ($1, $2);
-                       } elsif ($_ eq 'No changes') {
-                               $no = 1;
-                       }
-               }
-               close $fh or exit 1;
-               if (! defined $r_new && ! defined $cmt_new) {
-                       unless ($no) {
-                               die "Failed to parse revision information\n";
-                       }
-               } else {
-                       ($r_last, $cmt_last) = ($r_new, $cmt_new);
-               }
+       my $gs = Git::SVN->new;
+       my ($r_last, $cmt_last) = $gs->last_rev_commit;
+       $gs->fetch;
+       if (defined $gs->{last_rev} && $r_last != $gs->{last_rev}) {
+               fatal "There are new revisions that were fetched ",
+                     "and need to be merged (or acknowledged) ",
+                     "before committing.\nlast rev: $r_last\n",
+                     " current: $gs->{last_rev}\n";
        }
-       $ENV{LC_ALL} = 'C';
-       unlink $commit_msg;
+       $gs->set_tree($_) foreach @revs;
+       print "Done committing ",scalar @revs," revisions to SVN\n";
 }
 
-sub dcommit {
-       my $head = shift || 'HEAD';
-       my $gs = "refs/remotes/$GIT_SVN";
-       my @refs = command(qw/rev-list --no-merges/, "$gs..$head");
+sub cmd_dcommit {
+       my $head = shift;
+       my $gs = Git::SVN->new;
+       $head ||= 'HEAD';
+       my @refs = command(qw/rev-list --no-merges/, $gs->refname."..$head");
        my $last_rev;
        foreach my $d (reverse @refs) {
                if (!verify_ref("$d~1")) {
-                       die "Commit $d\n",
-                           "has no parent commit, and therefore ",
-                           "nothing to diff against.\n",
-                           "You should be working from a repository ",
-                           "originally created by git-svn\n";
+                       fatal "Commit $d\n",
+                             "has no parent commit, and therefore ",
+                             "nothing to diff against.\n",
+                             "You should be working from a repository ",
+                             "originally created by git-svn\n";
                }
                unless (defined $last_rev) {
                        (undef, $last_rev, undef) = cmt_metadata("$d~1");
                        unless (defined $last_rev) {
-                               die "Unable to extract revision information ",
-                                   "from commit $d~1\n";
+                               fatal "Unable to extract revision information ",
+                                     "from commit $d~1\n";
                        }
                }
                if ($_dry_run) {
                        print "diff-tree $d~1 $d\n";
                } else {
-                       if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) {
-                               $last_rev = $r;
-                       } # else: no changes, same $last_rev
+                       my %ed_opts = ( r => $last_rev,
+                                       log => get_commit_entry($d)->{log},
+                                       ra => $gs->ra,
+                                       tree_a => "$d~1",
+                                       tree_b => $d,
+                                       editor_cb => sub {
+                                              print "Committed r$_[0]\n";
+                                              $last_rev = $_[0]; },
+                                       svn_path => $gs->{path} );
+                       if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+                               print "No changes\n$d~1 == $d\n";
+                       }
                }
        }
        return if $_dry_run;
-       fetch();
-       my @diff = command('diff-tree', 'HEAD', $gs, '--');
+       $gs->fetch;
+       # we always want to rebase against the current HEAD, not any
+       # head that was passed to us
+       my @diff = command('diff-tree', 'HEAD', $gs->refname, '--');
        my @finish;
        if (@diff) {
                @finish = qw/rebase/;
                push @finish, qw/--merge/ if $_merge;
                push @finish, "--strategy=$_strategy" if $_strategy;
-               print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff;
+               print STDERR "W: HEAD and ", $gs->refname, " differ, ",
+                            "using @finish:\n", "@diff";
        } else {
-               print "No changes between current HEAD and $gs\n",
-                     "Resetting to the latest $gs\n";
+               print "No changes between current HEAD and ",
+                     $gs->refname, "\nResetting to the latest ",
+                     $gs->refname, "\n";
                @finish = qw/reset --mixed/;
        }
-       command_noisy(@finish, $gs);
-}
-
-sub show_ignore {
-       $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       my $repo;
-       $SVN ||= libsvn_connect($SVN_URL);
-       my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
-       libsvn_traverse_ignore(\*STDOUT, '', $r);
+       command_noisy(@finish, $gs->refname);
 }
 
-sub graft_branches {
-       my $gr_file = "$GIT_DIR/info/grafts";
-       my ($grafts, $comments) = read_grafts($gr_file);
-       my $gr_sha1;
-
-       if (%$grafts) {
-               # temporarily disable our grafts file to make this idempotent
-               chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file));
-               rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
-       }
-
-       my $l_map = read_url_paths();
-       my @re = map { qr/$_/is } @_opt_m if @_opt_m;
-       unless ($_no_default_regex) {
-               push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
-                       qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
-                       qr/\b(?:from|of)\s+([\w\.\-]+)/i );
-       }
-       foreach my $u (keys %$l_map) {
-               if (@re) {
-                       foreach my $p (keys %{$l_map->{$u}}) {
-                               graft_merge_msg($grafts,$l_map,$u,$p,@re);
-                       }
-               }
-               unless ($_no_graft_copy) {
-                       graft_file_copy_lib($grafts,$l_map,$u);
-               }
-       }
-       graft_tree_joins($grafts);
-
-       write_grafts($grafts, $comments, $gr_file);
-       unlink "$gr_file~$gr_sha1" if $gr_sha1;
+sub cmd_show_ignore {
+       my $gs = Git::SVN->new;
+       my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
+       $gs->traverse_ignore(\*STDOUT, '', $r);
 }
 
-sub multi_init {
+sub cmd_multi_init {
        my $url = shift;
        unless (defined $_trunk || defined $_branches || defined $_tags) {
                usage(1);
        }
-       if (defined $_trunk) {
-               my $trunk_url = complete_svn_url($url, $_trunk);
-               my $ch_id;
-               if ($GIT_SVN eq 'git-svn') {
-                       $ch_id = 1;
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
-               }
-               init_vars();
-               unless (-d $GIT_SVN_DIR) {
-                       if ($ch_id) {
-                               print "GIT_SVN_ID set to 'trunk' for ",
-                                     "$trunk_url ($_trunk)\n";
-                       }
-                       init($trunk_url);
-                       command_noisy('repo-config', 'svn.trunk', $trunk_url);
-               }
-       }
+       do_git_init_db();
        $_prefix = '' unless defined $_prefix;
-       complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix);
-       complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/');
-}
-
-sub multi_fetch {
-       # try to do trunk first, since branches/tags
-       # may be descended from it.
-       if (-e "$GIT_DIR/svn/trunk/info/url") {
-               fetch_child_id('trunk', @_);
-       }
-       rec_fetch('', "$GIT_DIR/svn", @_);
-}
-
-sub show_log {
-       my (@args) = @_;
-       my ($r_min, $r_max);
-       my $r_last = -1; # prevent dupes
-       rload_authors() if $_authors;
-       if (defined $TZ) {
-               $ENV{TZ} = $TZ;
-       } else {
-               delete $ENV{TZ};
-       }
-       if (defined $_revision) {
-               if ($_revision =~ /^(\d+):(\d+)$/) {
-                       ($r_min, $r_max) = ($1, $2);
-               } elsif ($_revision =~ /^\d+$/) {
-                       $r_min = $r_max = $_revision;
-               } else {
-                       print STDERR "-r$_revision is not supported, use ",
-                               "standard \'git log\' arguments instead\n";
-                       exit 1;
-               }
-       }
-
-       config_pager();
-       @args = (git_svn_log_cmd($r_min, $r_max), @args);
-       my $log = command_output_pipe(@args);
-       run_pager();
-       my (@k, $c, $d);
-
-       while (<$log>) {
-               if (/^${_esc_color}commit ($sha1_short)/o) {
-                       my $cmt = $1;
-                       if ($c && cmt_showable($c) && $c->{r} != $r_last) {
-                               $r_last = $c->{r};
-                               process_commit($c, $r_min, $r_max, \@k) or
-                                                               goto out;
-                       }
-                       $d = undef;
-                       $c = { c => $cmt };
-               } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) {
-                       get_author_info($c, $1, $2, $3);
-               } elsif (/^${_esc_color}(?:tree|parent|committer) /) {
-                       # ignore
-               } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) {
-                       push @{$c->{raw}}, $_;
-               } elsif (/^${_esc_color}[ACRMDT]\t/) {
-                       # we could add $SVN->{svn_path} here, but that requires
-                       # remote access at the moment (repo_path_split)...
-                       s#^(${_esc_color})([ACRMDT])\t#$1   $2 #;
-                       push @{$c->{changed}}, $_;
-               } elsif (/^${_esc_color}diff /) {
-                       $d = 1;
-                       push @{$c->{diff}}, $_;
-               } elsif ($d) {
-                       push @{$c->{diff}}, $_;
-               } elsif (/^${_esc_color}    (git-svn-id:.+)$/) {
-                       ($c->{url}, $c->{r}, undef) = extract_metadata($1);
-               } elsif (s/^${_esc_color}    //) {
-                       push @{$c->{l}}, $_;
-               }
-       }
-       if ($c && defined $c->{r} && $c->{r} != $r_last) {
-               $r_last = $c->{r};
-               process_commit($c, $r_min, $r_max, \@k);
-       }
-       if (@k) {
-               my $swap = $r_max;
-               $r_max = $r_min;
-               $r_min = $swap;
-               process_commit($_, $r_min, $r_max) foreach reverse @k;
-       }
-out:
-       eval { command_close_pipe($log) };
-       print '-' x72,"\n" unless $_incremental || $_oneline;
-}
-
-sub commit_diff_usage {
-       print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
-       exit 1
-}
-
-sub commit_diff {
-       my $ta = shift or commit_diff_usage();
-       my $tb = shift or commit_diff_usage();
-       if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
-               print STDERR "Needed URL or usable git-svn id command-line\n";
-               commit_diff_usage();
-       }
-       my $r = shift;
-       unless (defined $r) {
-               if (defined $_revision) {
-                       $r = $_revision
-               } else {
-                       die "-r|--revision is a required argument\n";
-               }
+       $url =~ s#/+$## if defined $url;
+       if (defined $_trunk) {
+               my $trunk_ref = $_prefix . 'trunk';
+               # try both old-style and new-style lookups:
+               my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
+               unless ($gs_trunk) {
+                       my ($trunk_url, $trunk_path) =
+                                             complete_svn_url($url, $_trunk);
+                       $gs_trunk = Git::SVN->init($trunk_url, $trunk_path,
+                                                  undef, $trunk_ref);
+               }
+       }
+       return unless defined $_branches || defined $_tags;
+       my $ra = $url ? Git::SVN::Ra->new($url) : undef;
+       complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
+       complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
+}
+
+sub cmd_multi_fetch {
+       my $remotes = Git::SVN::read_all_remotes();
+       foreach my $repo_id (sort keys %$remotes) {
+               my $url = $remotes->{$repo_id}->{url} or next;
+               my $fetch = $remotes->{$repo_id}->{fetch} or next;
+               Git::SVN::fetch_all($repo_id, $url, $fetch);
+       }
+}
+
+# this command is special because it requires no metadata
+sub cmd_commit_diff {
+       my ($ta, $tb, $url) = @_;
+       my $usage = "Usage: $0 commit-diff -r<revision> ".
+                   "<tree-ish> <tree-ish> [<URL>]\n";
+       fatal($usage) if (!defined $ta || !defined $tb);
+       my $svn_path;
+       if (!defined $url) {
+               my $gs = eval { Git::SVN->new };
+               if (!$gs) {
+                       fatal("Needed URL or usable git-svn --id in ",
+                             "the command-line\n", $usage);
+               }
+               $url = $gs->{url};
+               $svn_path = $gs->{path};
+       }
+       unless (defined $_revision) {
+               fatal("-r|--revision is a required argument\n", $usage);
        }
        if (defined $_message && defined $_file) {
-               print STDERR "Both --message/-m and --file/-F specified ",
-                               "for the commit message.\n",
-                               "I have no idea what you mean\n";
-               exit 1;
+               fatal("Both --message/-m and --file/-F specified ",
+                     "for the commit message.\n",
+                     "I have no idea what you mean\n");
        }
        if (defined $_file) {
                $_message = file_to_s($_file);
        } else {
-               $_message ||= get_commit_message($tb,
-                                       "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
+               $_message ||= get_commit_entry($tb)->{log};
        }
-       $SVN ||= libsvn_connect($SVN_URL);
+       my $ra ||= Git::SVN::Ra->new($url);
+       $svn_path ||= $ra->{svn_path};
+       my $r = $_revision;
        if ($r eq 'HEAD') {
-               $r = $SVN->get_latest_revnum;
+               $r = $ra->get_latest_revnum;
        } elsif ($r !~ /^\d+$/) {
                die "revision argument: $r not understood by git-svn\n";
        }
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-       my $rev_committed;
-       my $ed = SVN::Git::Editor->new({        r => $r,
-                                               ra => libsvn_dup_ra($SVN),
-                                               c => $tb,
-                                               svn_path => $SVN->{svn_path}
-                                       },
-                               $SVN->get_commit_editor($_message,
-                                       sub {
-                                               $rev_committed = $_[0];
-                                               print "Committed $_[0]\n";
-                                       }, @lock)
-                               );
-       eval {
-               my $mods = libsvn_checkout_tree($ta, $tb, $ed);
-               if (@$mods == 0) {
-                       print "No changes\n$ta == $tb\n";
-                       $ed->abort_edit;
-               } else {
-                       $ed->close_edit;
-               }
-       };
-       fatal "$@\n" if $@;
-       $_message = $_file = undef;
-       return $rev_committed;
-}
-
-########################### utility functions #########################
-
-sub cmt_showable {
-       my ($c) = @_;
-       return 1 if defined $c->{r};
-       if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
-                               $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
-               my @msg = command(qw/cat-file commit/, $c->{c});
-               shift @msg while ($msg[0] ne "\n");
-               shift @msg;
-               @{$c->{l}} = grep !/^git-svn-id: /, @msg;
-
-               (undef, $c->{r}, undef) = extract_metadata(
-                               (grep(/^git-svn-id: /, @msg))[-1]);
-       }
-       return defined $c->{r};
-}
-
-sub log_use_color {
-       return 1 if $_color;
-       my ($dc, $dcvar);
-       $dcvar = 'color.diff';
-       $dc = `git-repo-config --get $dcvar`;
-       if ($dc eq '') {
-               # nothing at all; fallback to "diff.color"
-               $dcvar = 'diff.color';
-               $dc = `git-repo-config --get $dcvar`;
-       }
-       chomp($dc);
-       if ($dc eq 'auto') {
-               my $pc;
-               $pc = `git-repo-config --get color.pager`;
-               if ($pc eq '') {
-                       # does not have it -- fallback to pager.color
-                       $pc = `git-repo-config --bool --get pager.color`;
-               }
-               else {
-                       $pc = `git-repo-config --bool --get color.pager`;
-                       if ($?) {
-                               $pc = 'false';
-                       }
-               }
-               chomp($pc);
-               if (-t *STDOUT || (defined $_pager && $pc eq 'true')) {
-                       return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
-               }
-               return 0;
-       }
-       return 0 if $dc eq 'never';
-       return 1 if $dc eq 'always';
-       chomp($dc = `git-repo-config --bool --get $dcvar`);
-       return ($dc eq 'true');
-}
-
-sub git_svn_log_cmd {
-       my ($r_min, $r_max) = @_;
-       my @cmd = (qw/log --abbrev-commit --pretty=raw
-                       --default/, "refs/remotes/$GIT_SVN");
-       push @cmd, '-r' unless $_non_recursive;
-       push @cmd, qw/--raw --name-status/ if $_verbose;
-       push @cmd, '--color' if log_use_color();
-       return @cmd unless defined $r_max;
-       if ($r_max == $r_min) {
-               push @cmd, '--max-count=1';
-               if (my $c = revdb_get($REVDB, $r_max)) {
-                       push @cmd, $c;
-               }
-       } else {
-               my ($c_min, $c_max);
-               $c_max = revdb_get($REVDB, $r_max);
-               $c_min = revdb_get($REVDB, $r_min);
-               if (defined $c_min && defined $c_max) {
-                       if ($r_max > $r_max) {
-                               push @cmd, "$c_min..$c_max";
-                       } else {
-                               push @cmd, "$c_max..$c_min";
-                       }
-               } elsif ($r_max > $r_min) {
-                       push @cmd, $c_max;
-               } else {
-                       push @cmd, $c_min;
-               }
+       my %ed_opts = ( r => $r,
+                       log => $_message,
+                       ra => $ra,
+                       tree_a => $ta,
+                       tree_b => $tb,
+                       editor_cb => sub { print "Committed r$_[0]\n" },
+                       svn_path => $svn_path );
+       if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+               print "No changes\n$ta == $tb\n";
        }
-       return @cmd;
 }
 
-sub fetch_child_id {
-       my $id = shift;
-       print "Fetching $id\n";
-       my $ref = "$GIT_DIR/refs/remotes/$id";
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-               init_vars();
-               fetch(@_);
-               exit 0;
-       }
-       while (<$fh>) {
-               print $_;
-               check_repack() if (/^r\d+ = $sha1/o);
-       }
-       close $fh or croak $?;
-}
+########################### utility functions #########################
 
-sub rec_fetch {
-       my ($pfx, $p, @args) = @_;
-       my @dir;
-       foreach (sort <$p/*>) {
-               if (-r "$_/info/url") {
-                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
-                       my $id = $pfx . basename $_;
-                       next if $id eq 'trunk';
-                       fetch_child_id($id, @args);
-               } elsif (-d $_) {
-                       push @dir, $_;
-               }
-       }
-       foreach (@dir) {
-               my $x = $_;
-               $x =~ s!^\Q$GIT_DIR\E/svn/!!;
-               rec_fetch($x, $_);
+sub parse_revision_argument {
+       if (!defined $_revision || $_revision eq 'BASE:HEAD') {
+               return (undef, undef);
        }
+       return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
+       return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
+       return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/);
+       return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/);
+       die "revision argument: $_revision not understood by git-svn\n",
+           "Try using the command-line svn client instead\n";
 }
 
 sub complete_svn_url {
        my ($url, $path) = @_;
        $path =~ s#/+$##;
-       $url =~ s#/+$## if $url;
        if ($path !~ m#^[a-z\+]+://#) {
-               $path = '/' . $path if ($path !~ m#^/#);
                if (!defined $url || $url !~ m#^[a-z\+]+://#) {
                        fatal("E: '$path' is not a complete URL ",
                              "and a separate URL is not specified\n");
                }
-               $path = $url . $path;
+               return ($url, $path);
        }
-       return $path;
+       return ($path, '');
 }
 
 sub complete_url_ls_init {
-       my ($url, $path, $switch, $pfx) = @_;
-       unless ($path) {
+       my ($ra, $repo_path, $switch, $pfx) = @_;
+       unless ($repo_path) {
                print STDERR "W: $switch not specified\n";
                return;
        }
-       my $full_url = complete_svn_url($url, $path);
-       my @ls = libsvn_ls_fullurl($full_url);
-       defined(my $pid = fork) or croak $!;
-       if (!$pid) {
-               foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) {
-                       $u =~ s#/+$##;
-                       if ($u !~ m!\Q$full_url\E/(.+)$!) {
-                               print STDERR "W: Unrecognized URL: $u\n";
-                               die "This should never happen\n";
-                       }
-                       # don't try to init already existing refs
-                       my $id = $pfx.$1;
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       unless (-d $GIT_SVN_DIR) {
-                               print "init $u => $id\n";
-                               init($u);
-                       }
+       $repo_path =~ s#/+$##;
+       if ($repo_path =~ m#^[a-z\+]+://#) {
+               $ra = Git::SVN::Ra->new($repo_path);
+               $repo_path = '';
+       } else {
+               $repo_path =~ s#^/+##;
+               unless ($ra) {
+                       fatal("E: '$repo_path' is not a complete URL ",
+                             "and a separate URL is not specified\n");
                }
-               exit 0;
        }
-       waitpid $pid, 0;
-       croak $? if $?;
-       my ($n) = ($switch =~ /^--(\w+)/);
-       command_noisy('repo-config', "svn.$n", $full_url);
-}
-
-sub common_prefix {
-       my $paths = shift;
-       my %common;
-       foreach (@$paths) {
-               my @tmp = split m#/#, $_;
-               my $p = '';
-               while (my $x = shift @tmp) {
-                       $p .= "/$x";
-                       $common{$p} ||= 0;
-                       $common{$p}++;
-               }
-       }
-       foreach (sort {length $b <=> length $a} keys %common) {
-               if ($common{$_} == @$paths) {
-                       return $_;
-               }
-       }
-       return '';
-}
-
-# grafts set here are 'stronger' in that they're based on actual tree
-# matches, and won't be deleted from merge-base checking in write_grafts()
-sub graft_tree_joins {
-       my $grafts = shift;
-       map_tree_joins() if (@_branch_from && !%tree_map);
-       return unless %tree_map;
-
-       git_svn_each(sub {
-               my $i = shift;
-               my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i");
-               my ($fh, $ctx) = command_output_pipe(@args);
-               while (<$fh>) {
-                       next unless /^commit ($sha1)$/o;
-                       my $c = $1;
-                       my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
-                       next unless $tree_map{$t};
-
-                       my $l;
-                       do {
-                               $l = readline $fh;
-                       } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
-
-                       my ($s, $tz) = ($1, $2);
-                       if ($tz =~ s/^\+//) {
-                               $s += tz_to_s_offset($tz);
-                       } elsif ($tz =~ s/^\-//) {
-                               $s -= tz_to_s_offset($tz);
-                       }
-
-                       my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
-
-                       foreach my $p (@{$tree_map{$t}}) {
-                               next if $p eq $c;
-                               my $mb = eval { command('merge-base', $c, $p) };
-                               next unless ($@ || $?);
-                               if (defined $r_a) {
-                                       # see if SVN says it's a relative
-                                       my ($url_b, $r_b, $uuid_b) =
-                                                       cmt_metadata($p);
-                                       next if (defined $url_b &&
-                                                       defined $url_a &&
-                                                       ($url_a eq $url_b) &&
-                                                       ($uuid_a eq $uuid_b));
-                                       if ($uuid_a eq $uuid_b) {
-                                               if ($r_b < $r_a) {
-                                                       $grafts->{$c}->{$p} = 2;
-                                                       next;
-                                               } elsif ($r_b > $r_a) {
-                                                       $grafts->{$p}->{$c} = 2;
-                                                       next;
-                                               }
-                                       }
-                               }
-                               my $ct = get_commit_time($p);
-                               if ($ct < $s) {
-                                       $grafts->{$c}->{$p} = 2;
-                               } elsif ($ct > $s) {
-                                       $grafts->{$p}->{$c} = 2;
-                               }
-                               # what should we do when $ct == $s ?
-                       }
-               }
-               command_close_pipe($fh, $ctx);
-       });
-}
-
-sub graft_file_copy_lib {
-       my ($grafts, $l_map, $u) = @_;
-       my $tree_paths = $l_map->{$u};
-       my $pfx = common_prefix([keys %$tree_paths]);
-       my ($repo, $path) = repo_path_split($u.$pfx);
-       $SVN = libsvn_connect($repo);
-
-       my ($base, $head) = libsvn_parse_revision();
-       my $inc = 1000;
-       my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
-       my $eh = $SVN::Error::handler;
-       $SVN::Error::handler = \&libsvn_skip_unknown_revs;
-       while (1) {
-               my $pool = SVN::Pool->new;
-               libsvn_get_log(libsvn_dup_ra($SVN), [$path],
-                              $min, $max, 0, 2, 1,
-                       sub {
-                               libsvn_graft_file_copies($grafts, $tree_paths,
-                                                       $path, @_);
-                       }, $pool);
-               $pool->clear;
-               last if ($max >= $head);
-               $min = $max + 1;
-               $max += $inc;
-               $max = $head if ($max > $head);
-       }
-       $SVN::Error::handler = $eh;
-}
-
-sub process_merge_msg_matches {
-       my ($grafts, $l_map, $u, $p, $c, @matches) = @_;
-       my (@strong, @weak);
-       foreach (@matches) {
-               # merging with ourselves is not interesting
-               next if $_ eq $p;
-               if ($l_map->{$u}->{$_}) {
-                       push @strong, $_;
-               } else {
-                       push @weak, $_;
-               }
-       }
-       foreach my $w (@weak) {
-               last if @strong;
-               # no exact match, use branch name as regexp.
-               my $re = qr/\Q$w\E/i;
-               foreach (keys %{$l_map->{$u}}) {
-                       if (/$re/) {
-                               push @strong, $l_map->{$u}->{$_};
-                               last;
-                       }
-               }
-               last if @strong;
-               $w = basename($w);
-               $re = qr/\Q$w\E/i;
-               foreach (keys %{$l_map->{$u}}) {
-                       if (/$re/) {
-                               push @strong, $l_map->{$u}->{$_};
-                               last;
-                       }
-               }
-       }
-       my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+)
-                                       \s(?:[a-f\d\-]+)$/xsm);
-       unless (defined $rev) {
-               ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+)
-                                       \@(?:[a-f\d\-]+)/xsm);
-               return unless defined $rev;
-       }
-       foreach my $m (@strong) {
-               my ($r0, $s0) = find_rev_before($rev, $m, 1);
-               $grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
-       }
-}
-
-sub graft_merge_msg {
-       my ($grafts, $l_map, $u, $p, @re) = @_;
-
-       my $x = $l_map->{$u}->{$p};
-       my $rl = rev_list_raw("refs/remotes/$x");
-       while (my $c = next_rev_list_entry($rl)) {
-               foreach my $re (@re) {
-                       my (@br) = ($c->{m} =~ /$re/g);
-                       next unless @br;
-                       process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);
+       my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
+       my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r);
+       my $url = $ra->{url};
+       foreach my $d (sort keys %$dirent) {
+               next if ($dirent->{$d}->kind != $SVN::Node::dir);
+               my $path =  "$repo_path/$d";
+               my $ref = "$pfx$d";
+               my $gs = eval { Git::SVN->new($ref) };
+               # don't try to init already existing refs
+               unless ($gs) {
+                       print "init $url/$path => $ref\n";
+                       Git::SVN->init($url, $path, undef, $ref);
                }
        }
 }
 
-sub read_uuid {
-       return if $SVN_UUID;
-       my $pool = SVN::Pool->new;
-       $SVN_UUID = $SVN->get_uuid($pool);
-       $pool->clear;
-}
-
 sub verify_ref {
        my ($ref) = @_;
        eval { command_oneline([ 'rev-parse', '--verify', $ref ],
                               { STDERR => 0 }); };
 }
 
-sub repo_path_split {
-       my $full_url = shift;
-       $full_url =~ s#/+$##;
-
-       foreach (@repo_path_split_cache) {
-               if ($full_url =~ s#$_##) {
-                       my $u = $1;
-                       $full_url =~ s#^/+##;
-                       return ($u, $full_url);
-               }
-       }
-       my $tmp = libsvn_connect($full_url);
-       return ($tmp->{repos_root}, $tmp->{svn_path});
-}
-
-sub setup_git_svn {
-       defined $SVN_URL or croak "SVN repository location required\n";
-       unless (-d $GIT_DIR) {
-               croak "GIT_DIR=$GIT_DIR does not exist!\n";
-       }
-       mkpath([$GIT_SVN_DIR]);
-       mkpath(["$GIT_SVN_DIR/info"]);
-       open my $fh, '>>',$REVDB or croak $!;
-       close $fh;
-       s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
-
-}
-
 sub get_tree_from_treeish {
        my ($treeish) = @_;
-       croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
+       # $treeish can be a symbolic ref, too:
        my $type = command_oneline(qw/cat-file -t/, $treeish);
        my $expected;
        while ($type eq 'tag') {
@@ -1148,7 +498,7 @@ sub get_tree_from_treeish {
        if ($type eq 'commit') {
                $expected = (grep /^tree /, command(qw/cat-file commit/,
                                                    $treeish))[0];
-               ($expected) = ($expected =~ /^tree ($sha1)$/);
+               ($expected) = ($expected =~ /^tree ($sha1)$/o);
                die "Unable to get tree from $treeish\n" unless $expected;
        } elsif ($type eq 'tree') {
                $expected = $treeish;
@@ -1158,146 +508,44 @@ sub get_tree_from_treeish {
        return $expected;
 }
 
-sub get_diff {
-       my ($from, $treeish) = @_;
-       print "diff-tree $from $treeish\n";
-       my @diff_tree = qw(diff-tree -z -r);
-       if ($_cp_similarity) {
-               push @diff_tree, "-C$_cp_similarity";
-       } else {
-               push @diff_tree, '-C';
-       }
-       push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
-       push @diff_tree, "-l$_l" if defined $_l;
-       push @diff_tree, $from, $treeish;
-       my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
-       local $/ = "\0";
-       my $state = 'meta';
-       my @mods;
-       while (<$diff_fh>) {
-               chomp $_; # this gets rid of the trailing "\0"
-               if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
-                                       $sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
-                       push @mods, {   mode_a => $1, mode_b => $2,
-                                       sha1_b => $3, chg => $4 };
-                       if ($4 =~ /^(?:C|R)$/) {
-                               $state = 'file_a';
-                       } else {
-                               $state = 'file_b';
-                       }
-               } elsif ($state eq 'file_a') {
-                       my $x = $mods[$#mods] or croak "Empty array\n";
-                       if ($x->{chg} !~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       $x->{file_a} = $_;
-                       $state = 'file_b';
-               } elsif ($state eq 'file_b') {
-                       my $x = $mods[$#mods] or croak "Empty array\n";
-                       if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
-                               croak "Error parsing $_, $x->{chg}\n";
-                       }
-                       $x->{file_b} = $_;
-                       $state = 'meta';
-               } else {
-                       croak "Error parsing $_\n";
-               }
-       }
-       command_close_pipe($diff_fh, $ctx);
-       return \@mods;
-}
-
-sub libsvn_checkout_tree {
-       my ($from, $treeish, $ed) = @_;
-       my $mods = get_diff($from, $treeish);
-       return $mods unless (scalar @$mods);
-       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               my $f = $m->{chg};
-               if (defined $o{$f}) {
-                       $ed->$f($m, $_q);
-               } else {
-                       croak "Invalid change type: $f\n";
-               }
-       }
-       $ed->rmdirs($_q) if $_rmdir;
-       return $mods;
-}
-
-sub get_commit_message {
-       my ($commit, $commit_msg) = (@_);
-       my %log_msg = ( msg => '' );
-       open my $msg, '>', $commit_msg or croak $!;
+sub get_commit_entry {
+       my ($treeish) = shift;
+       my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) );
+       my $commit_editmsg = "$ENV{GIT_DIR}/COMMIT_EDITMSG";
+       my $commit_msg = "$ENV{GIT_DIR}/COMMIT_MSG";
+       open my $log_fh, '>', $commit_editmsg or croak $!;
 
-       my $type = command_oneline(qw/cat-file -t/, $commit);
+       my $type = command_oneline(qw/cat-file -t/, $treeish);
        if ($type eq 'commit' || $type eq 'tag') {
                my ($msg_fh, $ctx) = command_output_pipe('cat-file',
-                                                        $type, $commit);
+                                                        $type, $treeish);
                my $in_msg = 0;
                while (<$msg_fh>) {
                        if (!$in_msg) {
                                $in_msg = 1 if (/^\s*$/);
                        } elsif (/^git-svn-id: /) {
-                               # skip this, we regenerate the correct one
-                               # on re-fetch anyways
+                               # skip this for now, we regenerate the
+                               # correct one on re-fetch anyways
+                               # TODO: set *:merge properties or like...
                        } else {
-                               print $msg $_ or croak $!;
+                               print $log_fh $_ or croak $!;
                        }
                }
                command_close_pipe($msg_fh, $ctx);
        }
-       close $msg or croak $!;
+       close $log_fh or croak $!;
 
        if ($_edit || ($type eq 'tree')) {
                my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
-               system($editor, $commit_msg);
-       }
-
-       # file_to_s removes all trailing newlines, so just use chomp() here:
-       open $msg, '<', $commit_msg or croak $!;
-       { local $/; chomp($log_msg{msg} = <$msg>); }
-       close $msg or croak $!;
-
-       return \%log_msg;
-}
-
-sub set_svn_commit_env {
-       if (defined $LC_ALL) {
-               $ENV{LC_ALL} = $LC_ALL;
-       } else {
-               delete $ENV{LC_ALL};
-       }
-}
-
-sub rev_list_raw {
-       my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_);
-       return { fh => $fh, ctx => $c, t => { } };
-}
-
-sub next_rev_list_entry {
-       my $rl = shift;
-       my $fh = $rl->{fh};
-       my $x = $rl->{t};
-       while (<$fh>) {
-               if (/^commit ($sha1)$/o) {
-                       if ($x->{c}) {
-                               $rl->{t} = { c => $1 };
-                               return $x;
-                       } else {
-                               $x->{c} = $1;
-                       }
-               } elsif (/^parent ($sha1)$/o) {
-                       $x->{p}->{$1} = 1;
-               } elsif (s/^    //) {
-                       $x->{m} ||= '';
-                       $x->{m} .= $_;
-               }
+               # TODO: strip out spaces, comments, like git-commit.sh
+               system($editor, $commit_editmsg);
        }
-       command_close_pipe($fh, $rl->{ctx});
-       return ($x != $rl->{t}) ? $x : undef;
+       rename $commit_editmsg, $commit_msg or croak $!;
+       open $log_fh, '<', $commit_msg or croak $!;
+       { local $/; chomp($log_entry{log} = <$log_fh>); }
+       close $log_fh or croak $!;
+       unlink $commit_msg;
+       \%log_entry;
 }
 
 sub s_to_file {
@@ -1318,291 +566,32 @@ sub file_to_s {
        return $ret;
 }
 
-sub assert_revision_unknown {
-       my $r = shift;
-       if (my $c = revdb_get($REVDB, $r)) {
-               croak "$r = $c already exists! Why are we refetching it?";
-       }
-}
-
-sub git_commit {
-       my ($log_msg, @parents) = @_;
-       assert_revision_unknown($log_msg->{revision});
-       map_tree_joins() if (@_branch_from && !%tree_map);
-
-       my (@tmp_parents, @exec_parents, %seen_parent);
-       if (my $lparents = $log_msg->{parents}) {
-               @tmp_parents = @$lparents
-       }
-       # commit parents can be conditionally bound to a particular
-       # svn revision via: "svn_revno=commit_sha1", filter them out here:
-       foreach my $p (@parents) {
-               next unless defined $p;
-               if ($p =~ /^(\d+)=($sha1_short)$/o) {
-                       if ($1 == $log_msg->{revision}) {
-                               push @tmp_parents, $2;
-                       }
-               } else {
-                       push @tmp_parents, $p if $p =~ /$sha1_short/o;
-               }
-       }
-       my $tree = $log_msg->{tree};
-       if (!defined $tree) {
-               my $index = set_index($GIT_SVN_INDEX);
-               $tree = command_oneline('write-tree');
-               croak $? if $?;
-               restore_index($index);
-       }
-       # just in case we clobber the existing ref, we still want that ref
-       # as our parent:
-       if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) {
-               chomp $cur;
-               push @tmp_parents, $cur;
-       }
-
-       if (exists $tree_map{$tree}) {
-               foreach my $p (@{$tree_map{$tree}}) {
-                       my $skip;
-                       foreach (@tmp_parents) {
-                               # see if a common parent is found
-                               my $mb = eval { command('merge-base', $_, $p) };
-                               next if ($@ || $?);
-                               $skip = 1;
-                               last;
-                       }
-                       next if $skip;
-                       my ($url_p, $r_p, $uuid_p) = cmt_metadata($p);
-                       next if (($SVN_UUID eq $uuid_p) &&
-                                               ($log_msg->{revision} > $r_p));
-                       next if (defined $url_p && defined $SVN_URL &&
-                                               ($SVN_UUID eq $uuid_p) &&
-                                               ($url_p eq $SVN_URL));
-                       push @tmp_parents, $p;
-               }
-       }
-       foreach (@tmp_parents) {
-               next if $seen_parent{$_};
-               $seen_parent{$_} = 1;
-               push @exec_parents, $_;
-               # MAXPARENT is defined to 16 in commit-tree.c:
-               last if @exec_parents > 16;
-       }
-
-       set_commit_env($log_msg);
-       my @exec = ('git-commit-tree', $tree);
-       push @exec, '-p', $_  foreach @exec_parents;
-       defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
-                                                               or croak $!;
-       print $msg_fh $log_msg->{msg} or croak $!;
-       unless ($_no_metadata) {
-               print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
-                                       " $SVN_UUID\n" or croak $!;
-       }
-       $msg_fh->flush == 0 or croak $!;
-       close $msg_fh or croak $!;
-       chomp(my $commit = do { local $/; <$out_fh> });
-       close $out_fh or croak $!;
-       waitpid $pid, 0;
-       croak $? if $?;
-       if ($commit !~ /^$sha1$/o) {
-               die "Failed to commit, invalid sha1: $commit\n";
-       }
-       command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit);
-       revdb_set($REVDB, $log_msg->{revision}, $commit);
-
-       # this output is read via pipe, do not change:
-       print "r$log_msg->{revision} = $commit\n";
-       return $commit;
-}
-
-sub check_repack {
-       if ($_repack && (--$_repack_nr == 0)) {
-               $_repack_nr = $_repack;
-               # repack doesn't use any arguments with spaces in them, does it?
-               command_noisy('repack', split(/\s+/, $_repack_flags));
-       }
-}
-
-sub set_commit_env {
-       my ($log_msg) = @_;
-       my $author = $log_msg->{author};
-       if (!defined $author || length $author == 0) {
-               $author = '(no author)';
-       }
-       my ($name,$email) = defined $users{$author} ?  @{$users{$author}}
-                               : ($author,"$author\@$SVN_UUID");
-       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
-       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
-       $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
-}
-
-sub check_upgrade_needed {
-       if (!-r $REVDB) {
-               -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-               open my $fh, '>>',$REVDB or croak $!;
-               close $fh;
-       }
-       return unless eval {
-               command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"],
-                       {STDERR => 0});
-       };
-       my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") };
-       if ($@ || !$head) {
-               print STDERR "Please run: $0 rebuild --upgrade\n";
-               exit 1;
-       }
-}
-
-# fills %tree_map with a reverse mapping of trees to commits.  Useful
-# for finding parents to commit on.
-sub map_tree_joins {
-       my %seen;
-       foreach my $br (@_branch_from) {
-               my $pipe = command_output_pipe(qw/rev-list
-                                           --topo-order --pretty=raw/, $br);
-               while (<$pipe>) {
-                       if (/^commit ($sha1)$/o) {
-                               my $commit = $1;
-
-                               # if we've seen a commit,
-                               # we've seen its parents
-                               last if $seen{$commit};
-                               my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
-                               unless (defined $tree) {
-                                       die "Failed to parse commit $commit\n";
-                               }
-                               push @{$tree_map{$tree}}, $commit;
-                               $seen{$commit} = 1;
-                       }
-               }
-               eval { command_close_pipe($pipe) };
-       }
-}
-
-sub load_all_refs {
-       if (@_branch_from) {
-               print STDERR '--branch|-b parameters are ignored when ',
-                       "--branch-all-refs|-B is passed\n";
-       }
-
-       # don't worry about rev-list on non-commit objects/tags,
-       # it shouldn't blow up if a ref is a blob or tree...
-       @_branch_from = command(qw/rev-parse --symbolic --all/);
-}
-
 # '<svn username> = real-name <email address>' mapping based on git-svnimport:
 sub load_authors {
        open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+       my $log = $cmd eq 'log';
        while (<$authors>) {
                chomp;
                next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
                my ($user, $name, $email) = ($1, $2, $3);
-               $users{$user} = [$name, $email];
-       }
-       close $authors or croak $!;
-}
-
-sub rload_authors {
-       open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
-       while (<$authors>) {
-               chomp;
-               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
-               my ($user, $name, $email) = ($1, $2, $3);
-               $rusers{"$name <$email>"} = $user;
-       }
-       close $authors or croak $!;
-}
-
-sub git_svn_each {
-       my $sub = shift;
-       foreach (command(qw/rev-parse --symbolic --all/)) {
-               next unless s#^refs/remotes/##;
-               chomp $_;
-               next unless -f "$GIT_DIR/svn/$_/info/url";
-               &$sub($_);
-       }
-}
-
-sub migrate_revdb {
-       git_svn_each(sub {
-               my $id = shift;
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       exit 0 if -r $REVDB;
-                       print "Upgrading svn => git mapping...\n";
-                       -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
-                       open my $fh, '>>',$REVDB or croak $!;
-                       close $fh;
-                       rebuild();
-                       print "Done upgrading. You may now delete the ",
-                               "deprecated $GIT_SVN_DIR/revs directory\n";
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-       });
-}
-
-sub migration_check {
-       migrate_revdb() unless (-e $REVDB);
-       return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
-       print "Upgrading repository...\n";
-       unless (-d "$GIT_DIR/svn") {
-               mkdir "$GIT_DIR/svn" or croak $!;
-       }
-       print "Data from a previous version of git-svn exists, but\n\t",
-                               "$GIT_SVN_DIR\n\t(required for this version ",
-                               "($VERSION) of git-svn) does not.\n";
-
-       foreach my $x (command(qw/rev-parse --symbolic --all/)) {
-               next unless $x =~ s#^refs/remotes/##;
-               chomp $x;
-               next unless -f "$GIT_DIR/$x/info/url";
-               my $u = eval { file_to_s("$GIT_DIR/$x/info/url") };
-               next unless $u;
-               my $dn = dirname("$GIT_DIR/svn/$x");
-               mkpath([$dn]) unless -d $dn;
-               rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
-       }
-       migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
-       print "Done upgrading.\n";
-}
-
-sub find_rev_before {
-       my ($r, $id, $eq_ok) = @_;
-       my $f = "$GIT_DIR/svn/$id/.rev_db";
-       return (undef,undef) unless -r $f;
-       --$r unless $eq_ok;
-       while ($r > 0) {
-               if (my $c = revdb_get($f, $r)) {
-                       return ($r, $c);
+               if ($log) {
+                       $Git::SVN::Log::rusers{"$name <$email>"} = $user;
+               } else {
+                       $users{$user} = [$name, $email];
                }
-               --$r;
        }
-       return (undef, undef);
-}
-
-sub init_vars {
-       $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
-       $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
-       $REVDB = "$GIT_SVN_DIR/.rev_db";
-       $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
-       $SVN_URL = undef;
-       $SVN_WC = "$GIT_SVN_DIR/tree";
-       %tree_map = ();
+       close $authors or croak $!;
 }
 
-# convert GetOpt::Long specs for use by git-repo-config
+# convert GetOpt::Long specs for use by git-config
 sub read_repo_config {
-       return unless -d $GIT_DIR;
+       return unless -d $ENV{GIT_DIR};
        my $opts = shift;
        foreach my $o (keys %$opts) {
                my $v = $opts->{$o};
                my ($key) = ($o =~ /^([a-z\-]+)/);
                $key =~ s/-//g;
-               my $arg = 'git-repo-config';
+               my $arg = 'git-config';
                $arg .= ' --int' if ($o =~ /[:=]i$/);
                $arg .= ' --bool' if ($o !~ /[:=][sfi]$/);
                if (ref $v eq 'ARRAY') {
@@ -1610,134 +599,13 @@ sub read_repo_config {
                        @$v = @tmp if @tmp;
                } else {
                        chomp(my $tmp = `$arg --get svn.$key`);
-                       if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {
+                       if ($tmp && !($arg =~ / --bool/ && $tmp eq 'false')) {
                                $$v = $tmp;
                        }
                }
        }
 }
 
-sub set_default_vals {
-       if (defined $_repack) {
-               $_repack = 1000 if ($_repack <= 0);
-               $_repack_nr = $_repack;
-               $_repack_flags ||= '-d';
-       }
-}
-
-sub read_grafts {
-       my $gr_file = shift;
-       my ($grafts, $comments) = ({}, {});
-       if (open my $fh, '<', $gr_file) {
-               my @tmp;
-               while (<$fh>) {
-                       if (/^($sha1)\s+/) {
-                               my $c = $1;
-                               if (@tmp) {
-                                       @{$comments->{$c}} = @tmp;
-                                       @tmp = ();
-                               }
-                               foreach my $p (split /\s+/, $_) {
-                                       $grafts->{$c}->{$p} = 1;
-                               }
-                       } else {
-                               push @tmp, $_;
-                       }
-               }
-               close $fh or croak $!;
-               @{$comments->{'END'}} = @tmp if @tmp;
-       }
-       return ($grafts, $comments);
-}
-
-sub write_grafts {
-       my ($grafts, $comments, $gr_file) = @_;
-
-       open my $fh, '>', $gr_file or croak $!;
-       foreach my $c (sort keys %$grafts) {
-               if ($comments->{$c}) {
-                       print $fh $_ foreach @{$comments->{$c}};
-               }
-               my $p = $grafts->{$c};
-               my %x; # real parents
-               delete $p->{$c}; # commits are not self-reproducing...
-               my $ch = command_output_pipe(qw/cat-file commit/, $c);
-               while (<$ch>) {
-                       if (/^parent ($sha1)/) {
-                               $x{$1} = $p->{$1} = 1;
-                       } else {
-                               last unless /^\S/;
-                       }
-               }
-               eval { command_close_pipe($ch) }; # breaking the pipe
-
-               # if real parents are the only ones in the grafts, drop it
-               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-               my (@ip, @jp, $mb);
-               my %del = %x;
-               @ip = @jp = keys %$p;
-               foreach my $i (@ip) {
-                       next if $del{$i} || $p->{$i} == 2;
-                       foreach my $j (@jp) {
-                               next if $i eq $j || $del{$j} || $p->{$j} == 2;
-                               $mb = eval { command('merge-base', $i, $j) };
-                               next unless $mb;
-                               chomp $mb;
-                               next if $x{$mb};
-                               if ($mb eq $j) {
-                                       delete $p->{$i};
-                                       $del{$i} = 1;
-                               } elsif ($mb eq $i) {
-                                       delete $p->{$j};
-                                       $del{$j} = 1;
-                               }
-                       }
-               }
-
-               # if real parents are the only ones in the grafts, drop it
-               next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
-
-               print $fh $c, ' ', join(' ', sort keys %$p),"\n";
-       }
-       if ($comments->{'END'}) {
-               print $fh $_ foreach @{$comments->{'END'}};
-       }
-       close $fh or croak $!;
-}
-
-sub read_url_paths_all {
-       my ($l_map, $pfx, $p) = @_;
-       my @dir;
-       foreach (<$p/*>) {
-               if (-r "$_/info/url") {
-                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
-                       my $id = $pfx . basename $_;
-                       my $url = file_to_s("$_/info/url");
-                       my ($u, $p) = repo_path_split($url);
-                       $l_map->{$u}->{$p} = $id;
-               } elsif (-d $_) {
-                       push @dir, $_;
-               }
-       }
-       foreach (@dir) {
-               my $x = $_;
-               $x =~ s!^\Q$GIT_DIR\E/svn/!!o;
-               read_url_paths_all($l_map, $x, $_);
-       }
-}
-
-# this one only gets ids that have been imported, not new ones
-sub read_url_paths {
-       my $l_map = {};
-       git_svn_each(sub { my $x = shift;
-                       my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
-                       my ($u, $p) = repo_path_split($url);
-                       $l_map->{$u}->{$p} = $x;
-                       });
-       return $l_map;
-}
-
 sub extract_metadata {
        my $id = shift or return (undef, undef, undef);
        my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
@@ -1755,773 +623,708 @@ sub cmt_metadata {
                command(qw/cat-file commit/, shift)))[-1]);
 }
 
-sub get_commit_time {
-       my $cmt = shift;
-       my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt);
-       while (<$fh>) {
-               /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
-               my ($s, $tz) = ($1, $2);
-               if ($tz =~ s/^\+//) {
-                       $s += tz_to_s_offset($tz);
-               } elsif ($tz =~ s/^\-//) {
-                       $s -= tz_to_s_offset($tz);
+package Git::SVN;
+use strict;
+use warnings;
+use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent/;
+use Carp qw/croak/;
+use File::Path qw/mkpath/;
+use IPC::Open3;
+
+# properties that we do not log:
+my %SKIP_PROP;
+BEGIN {
+       %SKIP_PROP = map { $_ => 1 } qw/svn:wc:ra_dav:version-url
+                                       svn:special svn:executable
+                                       svn:entry:committed-rev
+                                       svn:entry:last-author
+                                       svn:entry:uuid
+                                       svn:entry:committed-date/;
+}
+
+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;
+               if (defined $lr) {
+                       $base = $lr if ($lr < $base);
+               } else {
+                       $new_remote = 1;
                }
-               eval { command_close_pipe($fh) };
-               return $s;
+               push @gs, $gs;
        }
-       die "Can't get commit time for commit: $cmt\n";
+       $base = 0 if $new_remote;
+       return if (++$base > $head);
+       $ra->gs_fetch_loop_common($base, $head, @gs);
 }
 
-sub tz_to_s_offset {
-       my ($tz) = @_;
-       $tz =~ s/(\d\d)$//;
-       return ($1 * 60) + ($tz * 3600);
+sub read_all_remotes {
+       my $r = {};
+       foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
+               if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
+                       $r->{$1}->{fetch}->{$2} = $3;
+               } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
+                       $r->{$1}->{url} = $2;
+               }
+       }
+       $r;
 }
 
-# adapted from pager.c
-sub config_pager {
-       $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
-       if (!defined $_pager) {
-               $_pager = 'less';
-       } elsif (length $_pager == 0 || $_pager eq 'cat') {
-               $_pager = undef;
+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*$!) {
+                       if ($seen{$1}) {
+                               die "Remote ref refs/remote/$1 is tracked by",
+                                   "\n  \"$_\"\nand\n  \"$seen{$1}\"\n",
+                                   "Please resolve this ambiguity in ",
+                                   "your git configuration file before ",
+                                   "continuing\n";
+                       }
+                       $seen{$1} = $_;
+               }
        }
 }
 
-sub run_pager {
-       return unless -t *STDOUT;
-       pipe my $rfd, my $wfd or return;
-       defined(my $pid = fork) or croak $!;
-       if (!$pid) {
-               open STDOUT, '>&', $wfd or croak $!;
-               return;
+# we allow more chars than remotes2config.sh...
+sub sanitize_remote_name {
+       my ($name) = @_;
+       $name =~ tr{A-Za-z0-9:,/+-}{.}c;
+       $name;
+}
+
+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;
        }
-       open STDIN, '<&', $rfd or croak $!;
-       $ENV{LESS} ||= 'FRSX';
-       exec $_pager or croak "Can't run pager: $! ($_pager)\n";
+       $existing;
 }
 
-sub get_author_info {
-       my ($dest, $author, $t, $tz) = @_;
-       $author =~ s/(?:^\s*|\s*$)//g;
-       $dest->{a_raw} = $author;
-       my $_a;
-       if ($_authors) {
-               $_a = $rusers{$author} || undef;
+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;
+               }
        }
-       if (!$_a) {
-               ($_a) = ($author =~ /<([^>]+)\@[^>]+>$/);
+       my $orig_url;
+       if (!$existing) {
+               # verify that we aren't overwriting anything:
+               $orig_url = eval {
+                       command_oneline('config', '--get',
+                                       "svn-remote.$self->{repo_id}.url")
+               };
+               if ($orig_url && ($orig_url ne $url)) {
+                       die "svn-remote.$self->{repo_id}.url already set: ",
+                           "$orig_url\nwanted to set to: $url\n";
+               }
        }
-       $dest->{t} = $t;
-       $dest->{tz} = $tz;
-       $dest->{a} = $_a;
-       # Date::Parse isn't in the standard Perl distro :(
-       if ($tz =~ s/^\+//) {
-               $t += tz_to_s_offset($tz);
-       } elsif ($tz =~ s/^\-//) {
-               $t -= tz_to_s_offset($tz);
+       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";
        }
-       $dest->{t_utc} = $t;
+       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 process_commit {
-       my ($c, $r_min, $r_max, $defer) = @_;
-       if (defined $r_min && defined $r_max) {
-               if ($r_min == $c->{r} && $r_min == $r_max) {
-                       show_commit($c);
-                       return 0;
-               }
-               return 1 if $r_min == $r_max;
-               if ($r_min < $r_max) {
-                       # we need to reverse the print order
-                       return 0 if (defined $_limit && --$_limit < 0);
-                       push @$defer, $c;
-                       return 1;
-               }
-               if ($r_min != $r_max) {
-                       return 1 if ($r_min < $c->{r});
-                       return 1 if ($r_max > $c->{r});
-               }
+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);
        }
-       return 0 if (defined $_limit && --$_limit < 0);
-       show_commit($c);
-       return 1;
+       $self;
 }
 
-sub show_commit {
-       my $c = shift;
-       if ($_oneline) {
-               my $x = "\n";
-               if (my $l = $c->{l}) {
-                       while ($l->[0] =~ /^\s*$/) { shift @$l }
-                       $x = $l->[0];
+sub find_ref {
+       my ($ref_id) = @_;
+       foreach (command(qw/config -l/)) {
+               next unless m!^svn-remote\.(.+)\.fetch=
+                             \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
+               my ($repo_id, $path, $ref) = ($1, $2, $3);
+               if ($ref eq $ref_id) {
+                       $path = '' if ($path =~ m#^\./?#);
+                       return ($repo_id, $path);
                }
-               $_l_fmt ||= 'A' . length($c->{r});
-               print 'r',pack($_l_fmt, $c->{r}),' | ';
-               print "$c->{c} | " if $_show_commit;
-               print $x;
-       } else {
-               show_commit_normal($c);
        }
+       (undef, undef, undef);
 }
 
-sub show_commit_changed_paths {
-       my ($c) = @_;
-       return unless $c->{changed};
-       print "Changed paths:\n", @{$c->{changed}};
+sub new {
+       my ($class, $ref_id, $repo_id, $path) = @_;
+       if (defined $ref_id && !defined $repo_id && !defined $path) {
+               ($repo_id, $path) = find_ref($ref_id);
+               if (!defined $repo_id) {
+                       die "Could not find a \"svn-remote.*.fetch\" key ",
+                           "in the repository configuration matching: ",
+                           "refs/remotes/$ref_id\n";
+               }
+       }
+       my $self = _new($class, $repo_id, $ref_id, $path);
+       if (!defined $self->{path} || !length $self->{path}) {
+               my $fetch = command_oneline('config', '--get',
+                                           "svn-remote.$repo_id.fetch",
+                                           ":refs/remotes/$ref_id\$") or
+                    die "Failed to read \"svn-remote.$repo_id.fetch\" ",
+                        "\":refs/remotes/$ref_id\$\" in config\n";
+               ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
+       }
+       $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;
 }
 
-sub show_commit_normal {
-       my ($c) = @_;
-       print '-' x72, "\nr$c->{r} | ";
-       print "$c->{c} | " if $_show_commit;
-       print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
-                                localtime($c->{t_utc})), ' | ';
-       my $nr_line = 0;
+sub refname { "refs/remotes/$_[0]->{ref_id}" }
 
-       if (my $l = $c->{l}) {
-               while ($l->[$#$l] eq "\n" && $#$l > 0
-                                         && $l->[($#$l - 1)] eq "\n") {
-                       pop @$l;
-               }
-               $nr_line = scalar @$l;
-               if (!$nr_line) {
-                       print "1 line\n\n\n";
+sub ra {
+       my ($self) = shift;
+       Git::SVN::Ra->new($self->{url});
+}
+
+sub rel_path {
+       my ($self) = @_;
+       my $repos_root = $self->ra->{repos_root};
+       return $self->{path} if ($self->{url} eq $repos_root);
+       my $url = $self->{url} .
+                 (length $self->{path} ? "/$self->{path}" : $self->{path});
+       $url =~ s!^\Q$repos_root\E/*!!g;
+       $url;
+}
+
+sub traverse_ignore {
+       my ($self, $fh, $path, $r) = @_;
+       $path =~ s#^/+##g;
+       my $ra = $self->ra;
+       my ($dirent, undef, $props) = $ra->get_dir($path, $r);
+       my $p = $path;
+       $p =~ s#^\Q$ra->{svn_path}\E/##;
+       print $fh length $p ? "\n# $p\n" : "\n# /\n";
+       if (my $s = $props->{'svn:ignore'}) {
+               $s =~ s/[\r\n]+/\n/g;
+               chomp $s;
+               if (length $p == 0) {
+                       $s =~ s#\n#\n/$p#g;
+                       print $fh "/$s\n";
                } else {
-                       if ($nr_line == 1) {
-                               $nr_line = '1 line';
-                       } else {
-                               $nr_line .= ' lines';
-                       }
-                       print $nr_line, "\n";
-                       show_commit_changed_paths($c);
-                       print "\n";
-                       print $_ foreach @$l;
+                       $s =~ s#\n#\n/$p/#g;
+                       print $fh "/$p/$s\n";
                }
-       } else {
-               print "1 line\n";
-               show_commit_changed_paths($c);
-               print "\n";
-
        }
-       foreach my $x (qw/raw diff/) {
-               if ($c->{$x}) {
-                       print "\n";
-                       print $_ foreach @{$c->{$x}}
-               }
+       foreach (sort keys %$dirent) {
+               next if $dirent->{$_}->kind != $SVN::Node::dir;
+               $self->traverse_ignore($fh, "$path/$_", $r);
        }
 }
 
-sub _simple_prompt {
-       my ($cred, $realm, $default_username, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       $default_username = $_username if defined $_username;
-       if (defined $default_username && length $default_username) {
-               if (defined $realm && length $realm) {
-                       print STDERR "Authentication realm: $realm\n";
-                       STDERR->flush;
-               }
-               $cred->username($default_username);
-       } else {
-               _username_prompt($cred, $realm, $may_save, $pool);
+sub last_rev { ($_[0]->last_rev_commit)[0] }
+sub last_commit { ($_[0]->last_rev_commit)[1] }
+
+# returns the newest SVN revision number and newest commit SHA1
+sub last_rev_commit {
+       my ($self) = @_;
+       if (defined $self->{last_rev} && defined $self->{last_commit}) {
+               return ($self->{last_rev}, $self->{last_commit});
+       }
+       my $c = ::verify_ref($self->refname.'^0');
+       if ($c) {
+               my $rev = (::cmt_metadata($c))[1];
+               if (defined $rev) {
+                       ($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
+                       return ($rev, $c);
+               }
+       }
+       my $offset = -41; # from tail
+       my $rl;
+       open my $fh, '<', $self->{db_path} or
+                                croak "$self->{db_path} not readable: $!\n";
+       seek $fh, $offset, 2;
+       $rl = readline $fh;
+       defined $rl or return (undef, undef);
+       chomp $rl;
+       while ($c ne $rl && tell $fh != 0) {
+               $offset -= 41;
+               seek $fh, $offset, 2;
+               $rl = readline $fh;
+               defined $rl or return (undef, undef);
+               chomp $rl;
        }
-       $cred->password(_read_password("Password for '" .
-                                      $cred->username . "': ", $realm));
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
+       my $rev = tell $fh;
+       croak $! if ($rev < 0);
+       $rev =  ($rev - 41) / 41;
+       close $fh or croak $!;
+       ($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
+       return ($rev, $c);
 }
 
-sub _ssl_server_trust_prompt {
-       my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       print STDERR "Error validating server certificate for '$realm':\n";
-       if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
-               print STDERR " - The certificate is not issued by a trusted ",
-                     "authority. Use the\n",
-                     "   fingerprint to validate the certificate manually!\n";
-       }
-       if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
-               print STDERR " - The certificate hostname does not match.\n";
-       }
-       if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
-               print STDERR " - The certificate is not yet valid.\n";
-       }
-       if ($failures & $SVN::Auth::SSL::EXPIRED) {
-               print STDERR " - The certificate has expired.\n";
-       }
-       if ($failures & $SVN::Auth::SSL::OTHER) {
-               print STDERR " - The certificate has an unknown error.\n";
-       }
-       printf STDERR
-               "Certificate information:\n".
-               " - Hostname: %s\n".
-               " - Valid: from %s until %s\n".
-               " - Issuer: %s\n".
-               " - Fingerprint: %s\n",
-               map $cert_info->$_, qw(hostname valid_from valid_until
-                                      issuer_dname fingerprint);
-       my $choice;
-prompt:
-       print STDERR $may_save ?
-             "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
-             "(R)eject or accept (t)emporarily? ";
-       STDERR->flush;
-       $choice = lc(substr(<STDIN> || 'R', 0, 1));
-       if ($choice =~ /^t$/i) {
-               $cred->may_save(undef);
-       } elsif ($choice =~ /^r$/i) {
-               return -1;
-       } elsif ($may_save && $choice =~ /^p$/i) {
-               $cred->may_save($may_save);
+sub get_fetch_range {
+       my ($self, $min, $max) = @_;
+       $max ||= $self->ra->get_latest_revnum;
+       $min ||= $self->last_rev || 0;
+       (++$min, $max);
+}
+
+sub tmp_index_do {
+       my ($self, $sub) = @_;
+       my $old_index = $ENV{GIT_INDEX_FILE};
+       $ENV{GIT_INDEX_FILE} = $self->{index};
+       my @ret = &$sub;
+       if ($old_index) {
+               $ENV{GIT_INDEX_FILE} = $old_index;
        } else {
-               goto prompt;
+               delete $ENV{GIT_INDEX_FILE};
        }
-       $cred->accepted_failures($failures);
-       $SVN::_Core::SVN_NO_ERROR;
+       wantarray ? @ret : $ret[0];
 }
 
-sub _ssl_client_cert_prompt {
-       my ($cred, $realm, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       print STDERR "Client certificate filename: ";
-       STDERR->flush;
-       chomp(my $filename = <STDIN>);
-       $cred->cert_file($filename);
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
-}
+sub assert_index_clean {
+       my ($self, $treeish) = @_;
 
-sub _ssl_client_cert_pw_prompt {
-       my ($cred, $realm, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       $cred->password(_read_password("Password: ", $realm));
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
+       $self->tmp_index_do(sub {
+               command_noisy('read-tree', $treeish) unless -e $self->{index};
+               my $x = command_oneline('write-tree');
+               my ($y) = (command(qw/cat-file commit/, $treeish) =~
+                          /^tree ($::sha1)/mo);
+               if ($y ne $x) {
+                       unlink $self->{index} or croak $!;
+                       command_noisy('read-tree', $treeish);
+               }
+               $x = command_oneline('write-tree');
+               if ($y ne $x) {
+                       ::fatal "trees ($treeish) $y != $x\n",
+                               "Something is seriously wrong...\n";
+               }
+       });
 }
 
-sub _username_prompt {
-       my ($cred, $realm, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       if (defined $realm && length $realm) {
-               print STDERR "Authentication realm: $realm\n";
+sub get_commit_parents {
+       my ($self, $log_entry) = @_;
+       my (%seen, @ret, @tmp);
+       # legacy support for 'set-tree'; this is only used by set_tree_cb:
+       if (my $ip = $self->{inject_parents}) {
+               if (my $commit = delete $ip->{$log_entry->{revision}}) {
+                       push @tmp, $commit;
+               }
        }
-       my $username;
-       if (defined $_username) {
-               $username = $_username;
-       } else {
-               print STDERR "Username: ";
-               STDERR->flush;
-               chomp($username = <STDIN>);
+       if (my $cur = ::verify_ref($self->refname.'^0')) {
+               push @tmp, $cur;
        }
-       $cred->username($username);
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
+       push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp);
+       while (my $p = shift @tmp) {
+               next if $seen{$p};
+               $seen{$p} = 1;
+               push @ret, $p;
+               # MAXPARENT is defined to 16 in commit-tree.c:
+               last if @ret >= 16;
+       }
+       if (@tmp) {
+               die "r$log_entry->{revision}: No room for parents:\n\t",
+                   join("\n\t", @tmp), "\n";
+       }
+       @ret;
 }
 
-sub _read_password {
-       my ($prompt, $realm) = @_;
-       print STDERR $prompt;
-       STDERR->flush;
-       require Term::ReadKey;
-       Term::ReadKey::ReadMode('noecho');
-       my $password = '';
-       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
-               last if $key =~ /[\012\015]/; # \n\r
-               $password .= $key;
+sub full_url {
+       my ($self) = @_;
+       $self->{url} . (length $self->{path} ? '/' . $self->{path} : '');
+}
+
+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";
        }
-       Term::ReadKey::ReadMode('restore');
-       print STDERR "\n";
-       STDERR->flush;
-       $password;
-}
+       if (my $c = $self->rev_db_get($log_entry->{revision})) {
+               croak "$log_entry->{revision} = $c already exists! ",
+                     "Why are we refetching it?\n";
+       }
+       my $author = $log_entry->{author};
+       my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}}
+                          : ($author, "$author\@".$self->ra->uuid));
+       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
+       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+       $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
 
-sub libsvn_connect {
-       my ($url) = @_;
-       SVN::_Core::svn_config_ensure($_config_dir, undef);
-       my ($baton, $callbacks) = SVN::Core::auth_open_helper([
-           SVN::Client::get_simple_provider(),
-           SVN::Client::get_ssl_server_trust_file_provider(),
-           SVN::Client::get_simple_prompt_provider(
-             \&_simple_prompt, 2),
-           SVN::Client::get_ssl_client_cert_prompt_provider(
-             \&_ssl_client_cert_prompt, 2),
-           SVN::Client::get_ssl_client_cert_pw_prompt_provider(
-             \&_ssl_client_cert_pw_prompt, 2),
-           SVN::Client::get_username_provider(),
-           SVN::Client::get_ssl_server_trust_prompt_provider(
-             \&_ssl_server_trust_prompt),
-           SVN::Client::get_username_prompt_provider(
-             \&_username_prompt, 2),
-         ]);
-       my $config = SVN::Core::config_get_config($_config_dir);
-       my $ra = SVN::Ra->new(url => $url, auth => $baton,
-                             config => $config,
-                             pool => SVN::Pool->new,
-                             auth_provider_callbacks => $callbacks);
-       $ra->{svn_path} = $url;
-       $ra->{repos_root} = $ra->get_repos_root;
-       $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##;
-       push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/;
-       return $ra;
-}
+       my $tree = $log_entry->{tree};
+       if (!defined $tree) {
+               $tree = $self->tmp_index_do(sub {
+                                           command_oneline('write-tree') });
+       }
+       die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o;
 
-sub libsvn_can_do_switch {
-       unless (defined $_svn_can_do_switch) {
-               my $pool = SVN::Pool->new;
-               my $rep = eval {
-                       $SVN->do_switch(1, '', 0, $SVN->{url},
-                                       SVN::Delta::Editor->new, $pool);
-               };
-               if ($@) {
-                       $_svn_can_do_switch = 0;
-               } else {
-                       $rep->abort_report($pool);
-                       $_svn_can_do_switch = 1;
-               }
-               $pool->clear;
+       my @exec = ('git-commit-tree', $tree);
+       foreach ($self->get_commit_parents($log_entry)) {
+               push @exec, '-p', $_;
+       }
+       defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
+                                                                  or croak $!;
+       print $msg_fh $log_entry->{log} 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> });
+       close $out_fh or croak $!;
+       waitpid $pid, 0;
+       croak $? if $?;
+       if ($commit !~ /^$::sha1$/o) {
+               die "Failed to commit, invalid sha1: $commit\n";
        }
-       $_svn_can_do_switch;
-}
 
-sub libsvn_dup_ra {
-       my ($ra) = @_;
-       SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url
-                    auth auth_provider_callbacks repos_root svn_path/);
+       command_noisy('update-ref',$self->refname, $commit);
+       $self->rev_db_set($log_entry->{revision}, $commit);
+
+       $self->{last_rev} = $log_entry->{revision};
+       $self->{last_commit} = $commit;
+       print "r$log_entry->{revision} = $commit ($self->{ref_id})\n";
+       return $commit;
 }
 
-sub uri_encode {
-       my ($f) = @_;
-       $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
-       $f
+sub revisions_eq {
+       my ($self, $r0, $r1) = @_;
+       return 1 if $r0 == $r1;
+       my $nr = 0;
+       $self->ra->get_log([$self->{path}], $r0, $r1,
+                          0, 0, 1, sub { $nr++ });
+       return 0 if ($nr > 1);
+       return 1;
 }
 
-sub uri_decode {
-       my ($f) = @_;
-       $f =~ tr/+/ /;
-       $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge;
-       $f
+sub find_parent_branch {
+       my ($self, $paths, $rev) = @_;
+       return undef unless $_follow_parent;
+       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, sub {
+                                  $paths =
+                                     Git::SVN::Ra::dup_changed_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->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};
+       my $new_url = $repos_root . $branch_from;
+       print STDERR  "Found possible branch point: ",
+                     "$new_url => ", $self->full_url, ", $r\n";
+       $branch_from =~ s#^/##;
+       my $remotes = read_all_remotes();
+       my $gs;
+       foreach my $repo_id (keys %$remotes) {
+               my $u = $remotes->{$repo_id}->{url} or next;
+               next if $url ne $u;
+               my $fetch = $remotes->{$repo_id}->{fetch};
+               foreach my $f (keys %$fetch) {
+                       next if $f ne $branch_from;
+                       $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f);
+                       last;
+               }
+               last if $gs;
+       }
+       unless ($gs) {
+               my $ref_id = $self->{ref_id};
+               $ref_id =~ s/\@\d+$//;
+               $ref_id .= "\@$r";
+               # just grow a tail if we're not unique enough :x
+               $ref_id .= '-' while find_ref($ref_id);
+               print STDERR "Initializing parent: $ref_id\n";
+               $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)) {
+               $gs->fetch(0, $r);
+               ($r0, $parent) = $gs->last_rev_commit;
+       }
+       if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) {
+               print STDERR "Found branch parent: ($self->{ref_id}) $parent\n";
+               $self->assert_index_clean($parent);
+               my $ed;
+               if ($self->ra->can_do_switch) {
+                       print STDERR "Following parent with do_switch\n";
+                       # do_switch works with svn/trunk >= r22312, but that
+                       # is not included with SVN 1.4.3 (the latest version
+                       # 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,
+                                             $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, $ed)
+                         or die "SVN connection failed somewhere...\n";
+               }
+               print STDERR "Successfully followed parent\n";
+               return $self->make_log_entry($rev, [$parent], $ed);
+       }
+not_found:
+       print STDERR "Branch parent for path: '/",
+                    $self->rel_path, "' @ r$rev not found:\n";
+       return undef unless $paths;
+       print STDERR "Changed paths:\n";
+       foreach my $x (sort keys %$paths) {
+               my $p = $paths->{$x};
+               print STDERR "\t$p->{action}\t$x";
+               if ($p->{copyfrom_path}) {
+                       print STDERR "(from $p->{copyfrom_path}: ",
+                                    "$p->{copyfrom_rev})";
+               }
+               print STDERR "\n";
+       }
+       print STDERR '-'x72, "\n";
+       return undef;
 }
 
-sub libsvn_log_entry {
-       my ($rev, $author, $date, $msg, $parents, $untracked) = @_;
-       my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
-                                        (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x)
-                               or die "Unable to parse date: $date\n";
-       if (defined $author && length $author > 0 &&
-           defined $_authors && ! defined $users{$author}) {
-               die "Author: $author not defined in $_authors file\n";
+sub do_fetch {
+       my ($self, $paths, $rev) = @_;
+       my $ed;
+       my ($last_rev, @parents);
+       if ($self->{last_commit}) {
+               $ed = SVN::Git::Fetcher->new($self);
+               $last_rev = $self->{last_rev};
+               $ed->{c} = $self->{last_commit};
+               @parents = ($self->{last_commit});
+       } else {
+               $last_rev = $rev;
+               if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
+                       return $log_entry;
+               }
+               $ed = SVN::Git::Fetcher->new($self);
        }
-       $msg = '' if ($rev == 0 && !defined $msg);
+       unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) {
+               die "SVN connection failed somewhere...\n";
+       }
+       $self->make_log_entry($rev, \@parents, $ed);
+}
 
-       open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!;
-       my $h;
-       print $un "r$rev\n" or croak $!;
-       $h = $untracked->{empty};
+sub get_untracked {
+       my ($self, $ed) = @_;
+       my @out;
+       my $h = $ed->{empty};
        foreach (sort keys %$h) {
                my $act = $h->{$_} ? '+empty_dir' : '-empty_dir';
-               print $un "  $act: ", uri_encode($_), "\n" or croak $!;
+               push @out, "  $act: " . uri_encode($_);
                warn "W: $act: $_\n";
        }
        foreach my $t (qw/dir_prop file_prop/) {
-               $h = $untracked->{$t} or next;
+               $h = $ed->{$t} or next;
                foreach my $path (sort keys %$h) {
                        my $ppath = $path eq '' ? '.' : $path;
                        foreach my $prop (sort keys %{$h->{$path}}) {
-                               next if $SKIP{$prop};
+                               next if $SKIP_PROP{$prop};
                                my $v = $h->{$path}->{$prop};
+                               my $t_ppath_prop = "$t: " .
+                                                   uri_encode($ppath) . ' ' .
+                                                   uri_encode($prop);
                                if (defined $v) {
-                                       print $un "  +$t: ",
-                                                 uri_encode($ppath), ' ',
-                                                 uri_encode($prop), ' ',
-                                                 uri_encode($v), "\n"
-                                                 or croak $!;
+                                       push @out, "  +$t_ppath_prop " .
+                                                  uri_encode($v);
                                } else {
-                                       print $un "  -$t: ",
-                                                 uri_encode($ppath), ' ',
-                                                 uri_encode($prop), "\n"
-                                                 or croak $!;
+                                       push @out, "  -$t_ppath_prop";
                                }
                        }
                }
        }
        foreach my $t (qw/absent_file absent_directory/) {
-               $h = $untracked->{$t} or next;
+               $h = $ed->{$t} or next;
                foreach my $parent (sort keys %$h) {
                        foreach my $path (sort @{$h->{$parent}}) {
-                               print $un "  $t: ",
-                                     uri_encode("$parent/$path"), "\n"
-                                     or croak $!;
+                               push @out, "  $t: " .
+                                          uri_encode("$parent/$path");
                                warn "W: $t: $parent/$path ",
                                     "Insufficient permissions?\n";
                        }
                }
        }
-
-       # revprops (make this optional? it's an extra network trip...)
-       my $pool = SVN::Pool->new;
-       my $rp = $SVN->rev_proplist($rev, $pool);
-       foreach (sort keys %$rp) {
-               next if /^svn:(?:author|date|log)$/;
-               print $un "  rev_prop: ", uri_encode($_), ' ',
-                         uri_encode($rp->{$_}), "\n";
-       }
-       $pool->clear;
-       close $un or croak $!;
-
-       { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
-         author => $author, msg => $msg."\n", parents => $parents || [],
-         revprops => $rp }
-}
-
-sub process_rm {
-       my ($gui, $last_commit, $f, $q) = @_;
-       # remove entire directories.
-       if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
-               my ($ls, $ctx) = command_output_pipe(qw/ls-tree
-                                                    -r --name-only -z/,
-                                                    $last_commit,'--',$f);
-               local $/ = "\0";
-               while (<$ls>) {
-                       print $gui '0 ',0 x 40,"\t",$_ or croak $!;
-                       print "\tD\t$_\n" unless $q;
-               }
-               print "\tD\t$f/\n" unless $q;
-               command_close_pipe($ls, $ctx);
-               return $SVN::Node::dir;
-       } else {
-               print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
-               print "\tD\t$f\n" unless $q;
-               return $SVN::Node::file;
-       }
+       \@out;
 }
 
-sub libsvn_fetch {
-       my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
-       my $pool = SVN::Pool->new;
-       my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q });
-       my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-       my (undef, $last_rev, undef) = cmt_metadata($last_commit);
-       $reporter->set_path('', $last_rev, 0, @lock, $pool);
-       $reporter->finish_report($pool);
-       $pool->clear;
-       unless ($ed->{git_commit_ok}) {
-               die "SVN connection failed somewhere...\n";
-       }
-       libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed);
+sub parse_svn_date {
+       my $date = shift || return '+0000 1970-01-01 00:00:00';
+       my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
+                                           (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or
+                                        croak "Unable to parse date: $date\n";
+       "+0000 $Y-$m-$d $H:$M:$S";
 }
 
-sub svn_grab_base_rev {
-       my $c = eval { command_oneline([qw/rev-parse --verify/,
-                                       "refs/remotes/$GIT_SVN^0"],
-                                       { STDERR => 0 }) };
-       if (defined $c && length $c) {
-               my ($url, $rev, $uuid) = cmt_metadata($c);
-               return ($rev, $c) if defined $rev;
+sub check_author {
+       my ($author) = @_;
+       if (!defined $author || length $author == 0) {
+               $author = '(no author)';
        }
-       if ($_no_metadata) {
-               my $offset = -41; # from tail
-               my $rl;
-               open my $fh, '<', $REVDB or
-                       die "--no-metadata specified and $REVDB not readable\n";
-               seek $fh, $offset, 2;
-               $rl = readline $fh;
-               defined $rl or return (undef, undef);
-               chomp $rl;
-               while ($c ne $rl && tell $fh != 0) {
-                       $offset -= 41;
-                       seek $fh, $offset, 2;
-                       $rl = readline $fh;
-                       defined $rl or return (undef, undef);
-                       chomp $rl;
-               }
-               my $rev = tell $fh;
-               croak $! if ($rev < -1);
-               $rev =  ($rev - 41) / 41;
-               close $fh or croak $!;
-               return ($rev, $c);
+       if (defined $::_authors && ! defined $::users{$author}) {
+               die "Author: $author not defined in $::_authors file\n";
        }
-       return (undef, undef);
+       $author;
 }
 
-sub libsvn_parse_revision {
-       my $base = shift;
-       my $head = $SVN->get_latest_revnum();
-       if (!defined $_revision || $_revision eq 'BASE:HEAD') {
-               return ($base + 1, $head) if (defined $base);
-               return (0, $head);
-       }
-       return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
-       return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
-       if ($_revision =~ /^BASE:(\d+)$/) {
-               return ($base + 1, $1) if (defined $base);
-               return (0, $head);
-       }
-       return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/);
-       die "revision argument: $_revision not understood by git-svn\n",
-               "Try using the command-line svn client instead\n";
-}
+sub make_log_entry {
+       my ($self, $rev, $parents, $ed) = @_;
+       my $untracked = $self->get_untracked($ed);
 
-sub libsvn_traverse_ignore {
-       my ($fh, $path, $r) = @_;
-       $path =~ s#^/+##g;
-       my $pool = SVN::Pool->new;
-       my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
-       my $p = $path;
-       $p =~ s#^\Q$SVN->{svn_path}\E/##;
-       print $fh length $p ? "\n# $p\n" : "\n# /\n";
-       if (my $s = $props->{'svn:ignore'}) {
-               $s =~ s/[\r\n]+/\n/g;
-               chomp $s;
-               if (length $p == 0) {
-                       $s =~ s#\n#\n/$p#g;
-                       print $fh "/$s\n";
+       open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!;
+       print $un "r$rev\n" or croak $!;
+       print $un $_, "\n" foreach @$untracked;
+       my %log_entry = ( parents => $parents || [], revision => $rev,
+                         log => '');
+       my $rp = $self->ra->rev_proplist($rev);
+       foreach (sort keys %$rp) {
+               my $v = $rp->{$_};
+               if (/^svn:(author|date|log)$/) {
+                       $log_entry{$1} = $v;
                } else {
-                       $s =~ s#\n#\n/$p/#g;
-                       print $fh "/$p/$s\n";
-               }
-       }
-       foreach (sort keys %$dirent) {
-               next if $dirent->{$_}->kind != $SVN::Node::dir;
-               libsvn_traverse_ignore($fh, "$path/$_", $r);
-       }
-       $pool->clear;
-}
-
-sub revisions_eq {
-       my ($path, $r0, $r1) = @_;
-       return 1 if $r0 == $r1;
-       my $nr = 0;
-       # should be OK to use Pool here (r1 - r0) should be small
-       my $pool = SVN::Pool->new;
-       libsvn_get_log($SVN, [$path], $r0, $r1,
-                       0, 0, 1, sub {$nr++}, $pool);
-       $pool->clear;
-       return 0 if ($nr > 1);
-       return 1;
-}
-
-sub libsvn_find_parent_branch {
-       my ($paths, $rev, $author, $date, $msg) = @_;
-       my $svn_path = '/'.$SVN->{svn_path};
-
-       # look for a parent from another branch:
-       my $i = $paths->{$svn_path} or return;
-       my $branch_from = $i->copyfrom_path or return;
-       my $r = $i->copyfrom_rev;
-       print STDERR  "Found possible branch point: ",
-                               "$branch_from => $svn_path, $r\n";
-       $branch_from =~ s#^/##;
-       my $l_map = {};
-       read_url_paths_all($l_map, '', "$GIT_DIR/svn");
-       my $url = $SVN->{repos_root};
-       defined $l_map->{$url} or return;
-       my $id = $l_map->{$url}->{$branch_from};
-       if (!defined $id && $_follow_parent) {
-               print STDERR "Following parent: $branch_from\@$r\n";
-               # auto create a new branch and follow it
-               $id = basename($branch_from);
-               $id .= '@'.$r if -r "$GIT_DIR/svn/$id";
-               while (-r "$GIT_DIR/svn/$id") {
-                       # just grow a tail if we're not unique enough :x
-                       $id .= '-';
-               }
-       }
-       return unless defined $id;
-
-       my ($r0, $parent) = find_rev_before($r,$id,1);
-       if ($_follow_parent && (!defined $r0 || !defined $parent)) {
-               defined(my $pid = fork) or croak $!;
-               if (!$pid) {
-                       $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
-                       init_vars();
-                       $SVN_URL = "$url/$branch_from";
-                       $SVN = undef;
-                       setup_git_svn();
-                       # we can't assume SVN_URL exists at r+1:
-                       $_revision = "0:$r";
-                       fetch_lib();
-                       exit 0;
-               }
-               waitpid $pid, 0;
-               croak $? if $?;
-               ($r0, $parent) = find_rev_before($r,$id,1);
-       }
-       return unless (defined $r0 && defined $parent);
-       if (revisions_eq($branch_from, $r0, $r)) {
-               unlink $GIT_SVN_INDEX;
-               print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
-               command_noisy('read-tree', $parent);
-               unless (libsvn_can_do_switch()) {
-                       return _libsvn_new_tree($paths, $rev, $author, $date,
-                                               $msg, [$parent]);
-               }
-               # do_switch works with svn/trunk >= r22312, but that is not
-               # included with SVN 1.4.2 (the latest version at the moment),
-               # so we can't rely on it.
-               my $ra = libsvn_connect("$url/$branch_from");
-               my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q });
-               my $pool = SVN::Pool->new;
-               my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url},
-                                             $ed, $pool);
-               my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-               $reporter->set_path('', $r0, 0, @lock, $pool);
-               $reporter->finish_report($pool);
-               $pool->clear;
-               unless ($ed->{git_commit_ok}) {
-                       die "SVN connection failed somewhere...\n";
+                       print $un "  rev_prop: ", uri_encode($_), ' ',
+                                 uri_encode($v), "\n";
                }
-               return libsvn_log_entry($rev, $author, $date, $msg, [$parent]);
-       }
-       print STDERR "Nope, branch point not imported or unknown\n";
-       return undef;
-}
-
-sub libsvn_get_log {
-       my ($ra, @args) = @_;
-       $args[4]-- if $args[4] && ! $_follow_parent;
-       if ($SVN::Core::VERSION le '1.2.0') {
-               splice(@args, 3, 1);
-       }
-       $ra->get_log(@args);
-}
-
-sub libsvn_new_tree {
-       if (my $log_entry = libsvn_find_parent_branch(@_)) {
-               return $log_entry;
        }
-       my ($paths, $rev, $author, $date, $msg) = @_; # $pool is last
-       _libsvn_new_tree($paths, $rev, $author, $date, $msg, []);
-}
+       close $un or croak $!;
 
-sub _libsvn_new_tree {
-       my ($paths, $rev, $author, $date, $msg, $parents) = @_;
-       my $pool = SVN::Pool->new;
-       my $ed = SVN::Git::Fetcher->new({q => $_q});
-       my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
-       $reporter->set_path('', $rev, 1, @lock, $pool);
-       $reporter->finish_report($pool);
-       $pool->clear;
-       unless ($ed->{git_commit_ok}) {
-               die "SVN connection failed somewhere...\n";
-       }
-       libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed);
+       $log_entry{date} = parse_svn_date($log_entry{date});
+       $log_entry{author} = check_author($log_entry{author});
+       $log_entry{log} .= "\n";
+       \%log_entry;
 }
 
-sub find_graft_path_commit {
-       my ($tree_paths, $p1, $r1) = @_;
-       foreach my $x (keys %$tree_paths) {
-               next unless ($p1 =~ /^\Q$x\E/);
-               my $i = $tree_paths->{$x};
-               my ($r0, $parent) = find_rev_before($r1,$i,1);
-               return $parent if (defined $r0 && $r0 == $r1);
-               print STDERR "r$r1 of $i not imported\n";
-               next;
+sub fetch {
+       my ($self, $min_rev, $max_rev, @parents) = @_;
+       my ($last_rev, $last_commit) = $self->last_rev_commit;
+       my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev);
+       return if ($base > $head);
+       $self->ra->gs_fetch_loop_common($base, $head, $self);
+}
+
+sub set_tree_cb {
+       my ($self, $log_entry, $tree, $rev, $date, $author) = @_;
+       # TODO: enable and test optimized commits:
+       if (0 && $rev == ($self->{last_rev} + 1)) {
+               $log_entry->{revision} = $rev;
+               $log_entry->{author} = $author;
+               $self->do_git_commit($log_entry, "$rev=$tree");
+       } else {
+               $self->{inject_parents} = { $rev => $tree };
+               $self->fetch(undef, undef);
        }
-       return undef;
 }
 
-sub find_graft_path_parents {
-       my ($grafts, $tree_paths, $c, $p0, $r0) = @_;
-       foreach my $x (keys %$tree_paths) {
-               next unless ($p0 =~ /^\Q$x\E/);
-               my $i = $tree_paths->{$x};
-               my ($r, $parent) = find_rev_before($r0, $i, 1);
-               if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
-                       my ($url_b, undef, $uuid_b) = cmt_metadata($c);
-                       my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
-                       next if ($url_a && $url_b && $url_a eq $url_b &&
-                                                       $uuid_b eq $uuid_a);
-                       $grafts->{$c}->{$parent} = 1;
-               }
+sub set_tree {
+       my ($self, $tree) = (shift, shift);
+       my $log_entry = ::get_commit_entry($tree);
+       unless ($self->{last_rev}) {
+               fatal("Must have an existing revision to commit\n");
        }
-}
-
-sub libsvn_graft_file_copies {
-       my ($grafts, $tree_paths, $path, $paths, $rev) = @_;
-       foreach (keys %$paths) {
-               my $i = $paths->{$_};
-               my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,
-                                       $i->copyfrom_rev);
-               next unless (defined $p0 && defined $r0);
-
-               my $p1 = $_;
-               $p1 =~ s#^/##;
-               $p0 =~ s#^/##;
-               my $c = find_graft_path_commit($tree_paths, $p1, $rev);
-               next unless $c;
-               find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);
+       my %ed_opts = ( r => $self->{last_rev},
+                       log => $log_entry->{log},
+                       ra => $self->ra,
+                       tree_a => $self->{last_commit},
+                       tree_b => $tree,
+                       editor_cb => sub {
+                              $self->set_tree_cb($log_entry, $tree, @_) },
+                       svn_path => $self->{path} );
+       if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+               print "No changes\nr$self->{last_rev} = $tree\n";
        }
 }
 
-sub set_index {
-       my $old = $ENV{GIT_INDEX_FILE};
-       $ENV{GIT_INDEX_FILE} = shift;
-       return $old;
-}
+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);
 
-sub restore_index {
-       my ($old) = @_;
-       if (defined $old) {
-               $ENV{GIT_INDEX_FILE} = $old;
-       } else {
-               delete $ENV{GIT_INDEX_FILE};
-       }
-}
+               # ignore merges (from set-tree)
+               next if (!defined $rev || !$uuid);
 
-sub libsvn_commit_cb {
-       my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;
-       if ($_optimize_commits && $rev == ($r_last + 1)) {
-               my $log = libsvn_log_entry($rev,$committer,$date,$msg);
-               $log->{tree} = get_tree_from_treeish($c);
-               my $cmt = git_commit($log, $cmt_last, $c);
-               my @diff = command('diff-tree', $cmt, $c);
-               if (@diff) {
-                       print STDERR "Trees differ: $cmt $c\n",
-                                       join('',@diff),"\n";
-                       exit 1;
+               # 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;
                }
-       } else {
-               fetch("$rev=$c");
-       }
-}
+               $latest ||= $rev;
+               $svn_uuid ||= $uuid;
 
-sub libsvn_ls_fullurl {
-       my $fullurl = shift;
-       my $ra = libsvn_connect($fullurl);
-       my @ret;
-       my $pool = SVN::Pool->new;
-       my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
-       my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool);
-       foreach my $d (sort keys %$dirent) {
-               if ($dirent->{$d}->kind == $SVN::Node::dir) {
-                       push @ret, "$d/"; # add '/' for compat with cli svn
-               }
+               $self->rev_db_set($rev, $c);
+               print "r$rev = $c\n";
        }
-       $pool->clear;
-       return @ret;
+       command_close_pipe($rev_list, $ctx);
+       print "Done rebuilding $self->{db_path}\n";
 }
 
-
-sub libsvn_skip_unknown_revs {
-       my $err = shift;
-       my $errno = $err->apr_err();
-       # Maybe the branch we're tracking didn't
-       # exist when the repo started, so it's
-       # not an error if it doesn't, just continue
-       #
-       # Wonderfully consistent library, eh?
-       # 160013 - svn:// and file://
-       # 175002 - http(s)://
-       # 175007 - http(s):// (this repo required authorization, too...)
-       #   More codes may be discovered later...
-       if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
-               return;
-       }
-       croak "Error from SVN, ($errno): ", $err->expanded_message,"\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
 # one of my favorite modules is out :<  Next up would be one of the DBM
@@ -2532,29 +1335,30 @@ sub libsvn_skip_unknown_revs {
 # 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).
-sub revdb_set {
-       my ($file, $rev, $commit) = @_;
+
+sub rev_db_set {
+       my ($self, $rev, $commit) = @_;
        length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
-       open my $fh, '+<', $file or croak $!;
+       open my $fh, '+<', $self->{db_path} 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);
+               print $fh (('0' x 40),"\n") x (($offset - $pos) / 41)
+                 or croak $!;
        }
        seek $fh, $offset, 0 or croak $!;
-       print $fh $commit,"\n";
+       print $fh $commit,"\n" or croak $!;
        close $fh or croak $!;
 }
 
-sub revdb_get {
-       my ($file, $rev) = @_;
+sub rev_db_get {
+       my ($self, $rev) = @_;
        my $ret;
        my $offset = $rev * 41;
-       open my $fh, '<', $file or croak $!;
-       seek $fh, $offset, 0;
-       if (tell $fh == $offset) {
+       open my $fh, '<', $self->{db_path} or croak $!;
+       if (seek $fh, $offset, 0) {
                $ret = readline $fh;
                if (defined $ret) {
                        chomp $ret;
@@ -2562,58 +1366,218 @@ sub revdb_get {
                }
        }
        close $fh or croak $!;
-       return $ret;
+       $ret;
 }
 
-sub copy_remote_ref {
-       my $origin = $_cp_remote ? $_cp_remote : 'origin';
-       my $ref = "refs/remotes/$GIT_SVN";
-       if (command('ls-remote', $origin, $ref)) {
-               command_noisy('fetch', $origin, "$ref:$ref");
-       } elsif ($_cp_remote && !$_upgrade) {
-               die "Unable to find remote reference: ",
-                               "refs/remotes/$GIT_SVN on $origin\n";
+sub find_rev_before {
+       my ($self, $rev, $eq_ok) = @_;
+       --$rev unless $eq_ok;
+       while ($rev > 0) {
+               if (my $c = $self->rev_db_get($rev)) {
+                       return ($rev, $c);
+               }
+               --$rev;
        }
+       return (undef, undef);
 }
 
-{
-       my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
-                               $SVN::Node::dir.$SVN::Node::unknown.
-                               $SVN::Node::none.$SVN::Node::file.
-                               $SVN::Node::dir.$SVN::Node::unknown.
-                               $SVN::Auth::SSL::CNMISMATCH.
-                               $SVN::Auth::SSL::NOTYETVALID.
-                               $SVN::Auth::SSL::EXPIRED.
-                               $SVN::Auth::SSL::UNKNOWNCA.
-                               $SVN::Auth::SSL::OTHER;
+sub _new {
+       my ($class, $repo_id, $ref_id, $path) = @_;
+       unless (defined $repo_id && length $repo_id) {
+               $repo_id = $Git::SVN::default_repo_id;
+       }
+       unless (defined $ref_id && length $ref_id) {
+               $_[2] = $ref_id = $Git::SVN::default_ref_id;
+       }
+       $_[1] = $repo_id = sanitize_remote_name($repo_id);
+       my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
+       $_[3] = $path = '' unless (defined $path);
+       mkpath([$dir]);
+       unless (-f "$dir/.rev_db") {
+               open my $fh, '>>', "$dir/.rev_db" or croak $!;
+               close $fh or croak $!;
+       }
+       bless { ref_id => $ref_id, dir => $dir, index => "$dir/index",
+               path => $path,
+               db_path => "$dir/.rev_db", repo_id => $repo_id }, $class;
 }
 
-package SVN::Git::Fetcher;
-use vars qw/@ISA/;
+sub uri_encode {
+       my ($f) = @_;
+       $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
+       $f
+}
+
+package Git::SVN::Prompt;
 use strict;
 use warnings;
-use Carp qw/croak/;
-use IO::File qw//;
-use Git qw/command command_oneline command_noisy
-           command_output_pipe command_input_pipe command_close_pipe/;
+require SVN::Core;
+use vars qw/$_no_auth_cache $_username/;
 
-# file baton members: path, mode_a, mode_b, pool, fh, blob, base
-sub new {
-       my ($class, $git_svn) = @_;
-       my $self = SVN::Delta::Editor->new;
-       bless $self, $class;
-       $self->{c} = $git_svn->{c} if exists $git_svn->{c};
-       $self->{q} = $git_svn->{q};
-       $self->{empty} = {};
-       $self->{dir_prop} = {};
-       $self->{file_prop} = {};
-       $self->{absent_dir} = {};
-       $self->{absent_file} = {};
-       ($self->{gui}, $self->{ctx}) = command_input_pipe(
-                                            qw/update-index -z --index-info/);
-       require Digest::MD5;
-       $self;
-}
+sub simple {
+       my ($cred, $realm, $default_username, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $default_username = $_username if defined $_username;
+       if (defined $default_username && length $default_username) {
+               if (defined $realm && length $realm) {
+                       print STDERR "Authentication realm: $realm\n";
+                       STDERR->flush;
+               }
+               $cred->username($default_username);
+       } else {
+               username($cred, $realm, $may_save, $pool);
+       }
+       $cred->password(_read_password("Password for '" .
+                                      $cred->username . "': ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub ssl_server_trust {
+       my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print STDERR "Error validating server certificate for '$realm':\n";
+       if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+               print STDERR " - The certificate is not issued by a trusted ",
+                     "authority. Use the\n",
+                     "   fingerprint to validate the certificate manually!\n";
+       }
+       if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+               print STDERR " - The certificate hostname does not match.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+               print STDERR " - The certificate is not yet valid.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::EXPIRED) {
+               print STDERR " - The certificate has expired.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::OTHER) {
+               print STDERR " - The certificate has an unknown error.\n";
+       }
+       printf STDERR
+               "Certificate information:\n".
+               " - Hostname: %s\n".
+               " - Valid: from %s until %s\n".
+               " - Issuer: %s\n".
+               " - Fingerprint: %s\n",
+               map $cert_info->$_, qw(hostname valid_from valid_until
+                                      issuer_dname fingerprint);
+       my $choice;
+prompt:
+       print STDERR $may_save ?
+             "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
+             "(R)eject or accept (t)emporarily? ";
+       STDERR->flush;
+       $choice = lc(substr(<STDIN> || 'R', 0, 1));
+       if ($choice =~ /^t$/i) {
+               $cred->may_save(undef);
+       } elsif ($choice =~ /^r$/i) {
+               return -1;
+       } elsif ($may_save && $choice =~ /^p$/i) {
+               $cred->may_save($may_save);
+       } else {
+               goto prompt;
+       }
+       $cred->accepted_failures($failures);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub ssl_client_cert {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print STDERR "Client certificate filename: ";
+       STDERR->flush;
+       chomp(my $filename = <STDIN>);
+       $cred->cert_file($filename);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub ssl_client_cert_pw {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $cred->password(_read_password("Password: ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub username {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       if (defined $realm && length $realm) {
+               print STDERR "Authentication realm: $realm\n";
+       }
+       my $username;
+       if (defined $_username) {
+               $username = $_username;
+       } else {
+               print STDERR "Username: ";
+               STDERR->flush;
+               chomp($username = <STDIN>);
+       }
+       $cred->username($username);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _read_password {
+       my ($prompt, $realm) = @_;
+       print STDERR $prompt;
+       STDERR->flush;
+       require Term::ReadKey;
+       Term::ReadKey::ReadMode('noecho');
+       my $password = '';
+       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+               last if $key =~ /[\012\015]/; # \n\r
+               $password .= $key;
+       }
+       Term::ReadKey::ReadMode('restore');
+       print STDERR "\n";
+       STDERR->flush;
+       $password;
+}
+
+package main;
+
+{
+       my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
+                               $SVN::Node::dir.$SVN::Node::unknown.
+                               $SVN::Node::none.$SVN::Node::file.
+                               $SVN::Node::dir.$SVN::Node::unknown.
+                               $SVN::Auth::SSL::CNMISMATCH.
+                               $SVN::Auth::SSL::NOTYETVALID.
+                               $SVN::Auth::SSL::EXPIRED.
+                               $SVN::Auth::SSL::UNKNOWNCA.
+                               $SVN::Auth::SSL::OTHER;
+}
+
+package SVN::Git::Fetcher;
+use vars qw/@ISA/;
+use strict;
+use warnings;
+use Carp qw/croak/;
+use IO::File qw//;
+use Digest::MD5;
+
+# file baton members: path, mode_a, mode_b, pool, fh, blob, base
+sub new {
+       my ($class, $git_svn) = @_;
+       my $self = SVN::Delta::Editor->new;
+       bless $self, $class;
+       $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit};
+       $self->{empty} = {};
+       $self->{dir_prop} = {};
+       $self->{file_prop} = {};
+       $self->{absent_dir} = {};
+       $self->{absent_file} = {};
+       $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
+       $self;
+}
+
+sub set_path_strip {
+       my ($self, $path) = @_;
+       $self->{path_strip} = qr/^\Q$path\E\/?/;
+}
 
 sub open_root {
        { path => '' };
@@ -2624,16 +1588,46 @@ sub open_directory {
        { path => $path };
 }
 
+sub git_path {
+       my ($self, $path) = @_;
+       if ($self->{path_strip}) {
+               $path =~ s!$self->{path_strip}!! or
+                 die "Failed to strip path '$path' ($self->{path_strip})\n";
+       }
+       $path;
+}
+
 sub delete_entry {
        my ($self, $path, $rev, $pb) = @_;
-       my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q});
-       $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir;
+
+       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
+                                                    -r --name-only -z/,
+                                                    $self->{c}, '--', $gpath);
+               local $/ = "\0";
+               while (<$ls>) {
+                       chomp;
+                       $self->{gii}->remove($_);
+                       print "\tD\t$_\n" unless $self->{q};
+               }
+               print "\tD\t$gpath/\n" unless $self->{q};
+               command_close_pipe($ls, $ctx);
+               $self->{empty}->{$path} = 0
+       } else {
+               $self->{gii}->remove($gpath);
+               print "\tD\t$gpath\n" unless $self->{q};
+       }
        undef;
 }
 
 sub open_file {
        my ($self, $path, $pb, $rev) = @_;
-       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path)
+       my $gpath = $self->git_path($path);
+       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
                             =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
        unless (defined $mode && defined $blob) {
                die "$path was not found in commit $self->{c} (r$rev)\n";
@@ -2732,7 +1726,7 @@ sub apply_textdelta {
 sub close_file {
        my ($self, $fb, $exp) = @_;
        my $hash;
-       my $path = $fb->{path};
+       my $path = $self->git_path($fb->{path});
        if (my $fh = $fb->{fh}) {
                seek($fh, 0, 0) or croak $!;
                my $md5 = Digest::MD5->new;
@@ -2760,65 +1754,163 @@ sub close_file {
                $hash = $fb->{blob} or die "no blob information\n";
        }
        $fb->{pool}->clear;
-       my $gui = $self->{gui};
-       print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!;
+       $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;
        print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q};
        undef;
 }
 
 sub abort_edit {
        my $self = shift;
-       eval { command_close_pipe($self->{gui}, $self->{ctx}) };
+       $self->{nr} = $self->{gii}->{nr};
+       delete $self->{gii};
        $self->SUPER::abort_edit(@_);
 }
 
 sub close_edit {
        my $self = shift;
-       command_close_pipe($self->{gui}, $self->{ctx});
        $self->{git_commit_ok} = 1;
+       $self->{nr} = $self->{gii}->{nr};
+       delete $self->{gii};
        $self->SUPER::close_edit(@_);
 }
 
 package SVN::Git::Editor;
-use vars qw/@ISA/;
+use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
 use strict;
 use warnings;
 use Carp qw/croak/;
 use IO::File;
-use Git qw/command command_oneline command_noisy
-           command_output_pipe command_input_pipe command_close_pipe/;
+use Digest::MD5;
 
 sub new {
-       my $class = shift;
-       my $git_svn = shift;
-       my $self = SVN::Delta::Editor->new(@_);
+       my ($class, $opts) = @_;
+       foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) {
+               die "$_ required!\n" unless (defined $opts->{$_});
+       }
+
+       my $pool = SVN::Pool->new;
+       my $mods = generate_diff($opts->{tree_a}, $opts->{tree_b});
+       my $types = check_diff_paths($opts->{ra}, $opts->{svn_path},
+                                    $opts->{r}, $mods);
+
+       # $opts->{ra} functions should not be used after this:
+       my @ce  = $opts->{ra}->get_commit_editor($opts->{log},
+                                               $opts->{editor_cb}, $pool);
+       my $self = SVN::Delta::Editor->new(@ce, $pool);
        bless $self, $class;
-       foreach (qw/svn_path c r ra /) {
-               die "$_ required!\n" unless (defined $git_svn->{$_});
-               $self->{$_} = $git_svn->{$_};
+       foreach (qw/svn_path r tree_a tree_b/) {
+               $self->{$_} = $opts->{$_};
        }
-       $self->{pool} = SVN::Pool->new;
+       $self->{url} = $opts->{ra}->{url};
+       $self->{mods} = $mods;
+       $self->{types} = $types;
+       $self->{pool} = $pool;
        $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
        $self->{rm} = { };
-       require Digest::MD5;
+       $self->{path_prefix} = length $self->{svn_path} ?
+                              "$self->{svn_path}/" : '';
        return $self;
 }
 
+sub generate_diff {
+       my ($tree_a, $tree_b) = @_;
+       my @diff_tree = qw(diff-tree -z -r);
+       if ($_cp_similarity) {
+               push @diff_tree, "-C$_cp_similarity";
+       } else {
+               push @diff_tree, '-C';
+       }
+       push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+       push @diff_tree, "-l$_rename_limit" if defined $_rename_limit;
+       push @diff_tree, $tree_a, $tree_b;
+       my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
+       local $/ = "\0";
+       my $state = 'meta';
+       my @mods;
+       while (<$diff_fh>) {
+               chomp $_; # this gets rid of the trailing "\0"
+               if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
+                                       $::sha1\s($::sha1)\s
+                                       ([MTCRAD])\d*$/xo) {
+                       push @mods, {   mode_a => $1, mode_b => $2,
+                                       sha1_b => $3, chg => $4 };
+                       if ($4 =~ /^(?:C|R)$/) {
+                               $state = 'file_a';
+                       } else {
+                               $state = 'file_b';
+                       }
+               } elsif ($state eq 'file_a') {
+                       my $x = $mods[$#mods] or croak "Empty array\n";
+                       if ($x->{chg} !~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       $x->{file_a} = $_;
+                       $state = 'file_b';
+               } elsif ($state eq 'file_b') {
+                       my $x = $mods[$#mods] or croak "Empty array\n";
+                       if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       $x->{file_b} = $_;
+                       $state = 'meta';
+               } else {
+                       croak "Error parsing $_\n";
+               }
+       }
+       command_close_pipe($diff_fh, $ctx);
+       \@mods;
+}
+
+sub check_diff_paths {
+       my ($ra, $pfx, $rev, $mods) = @_;
+       my %types;
+       $pfx .= '/' if length $pfx;
+
+       sub type_diff_paths {
+               my ($ra, $types, $path, $rev) = @_;
+               my @p = split m#/+#, $path;
+               my $c = shift @p;
+               unless (defined $types->{$c}) {
+                       $types->{$c} = $ra->check_path($c, $rev);
+               }
+               while (@p) {
+                       $c .= '/' . shift @p;
+                       next if defined $types->{$c};
+                       $types->{$c} = $ra->check_path($c, $rev);
+               }
+       }
+
+       foreach my $m (@$mods) {
+               foreach my $f (qw/file_a file_b/) {
+                       next unless defined $m->{$f};
+                       my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);
+                       if (length $pfx.$dir && ! defined $types{$dir}) {
+                               type_diff_paths($ra, \%types, $pfx.$dir, $rev);
+                       }
+               }
+       }
+       \%types;
+}
+
 sub split_path {
        return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
 }
 
 sub repo_path {
-       (defined $_[1] && length $_[1]) ? $_[1] : ''
+       my ($self, $path) = @_;
+       $self->{path_prefix}.(defined $path ? $path : '');
 }
 
 sub url_path {
        my ($self, $path) = @_;
-       $self->{ra}->{url} . '/' . $self->repo_path($path);
+       $self->{url} . '/' . $self->repo_path($path);
 }
 
 sub rmdirs {
-       my ($self, $q) = @_;
+       my ($self) = @_;
        my $rm = $self->{rm};
        delete $rm->{''}; # we never delete the url we're tracking
        return unless %$rm;
@@ -2836,8 +1928,8 @@ sub rmdirs {
        delete $rm->{''}; # we never delete the url we're tracking
        return unless %$rm;
 
-       my ($fh, $ctx) = command_output_pipe(
-                                  qw/ls-tree --name-only -r -z/, $self->{c});
+       my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/,
+                                            $self->{tree_b});
        local $/ = "\0";
        while (<$fh>) {
                chomp;
@@ -2846,7 +1938,7 @@ sub rmdirs {
                        delete $rm->{join '/', @dn};
                }
                unless (%$rm) {
-                       eval { command_close_pipe($fh) };
+                       close $fh;
                        return;
                }
        }
@@ -2856,7 +1948,7 @@ sub rmdirs {
        foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
                $self->close_directory($bat->{$d}, $p);
                my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
-               print "\tD+\t$d/\n" unless $q;
+               print "\tD+\t$d/\n" unless $::_q;
                $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
                delete $bat->{$d};
        }
@@ -2864,9 +1956,10 @@ sub rmdirs {
 
 sub open_or_add_dir {
        my ($self, $full_path, $baton) = @_;
-       my $p = SVN::Pool->new;
-       my $t = $self->{ra}->check_path($full_path, $self->{r}, $p);
-       $p->clear;
+       my $t = $self->{types}->{$full_path};
+       if (!defined $t) {
+               die "$full_path not known in r$self->{r} or we have a bug!\n";
+       }
        if ($t == $SVN::Node::none) {
                return $self->add_directory($full_path, $baton,
                                                undef, -1, $self->{pool});
@@ -2883,9 +1976,9 @@ sub open_or_add_dir {
 sub ensure_path {
        my ($self, $path) = @_;
        my $bat = $self->{bat};
-       $path = $self->repo_path($path);
-       return $bat->{''} unless (length $path);
-       my @p = split m#/+#, $path;
+       my $repo_path = $self->repo_path($path);
+       return $bat->{''} unless (length $repo_path);
+       my @p = split m#/+#, $repo_path;
        my $c = shift @p;
        $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
        while (@p) {
@@ -2897,23 +1990,23 @@ sub ensure_path {
 }
 
 sub A {
-       my ($self, $m, $q) = @_;
+       my ($self, $m) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                        undef, -1);
-       print "\tA\t$m->{file_b}\n" unless $q;
+       print "\tA\t$m->{file_b}\n" unless $::_q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
 
 sub C {
-       my ($self, $m, $q) = @_;
+       my ($self, $m) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
-       print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q;
+       print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
@@ -2927,12 +2020,12 @@ sub delete_entry {
 }
 
 sub R {
-       my ($self, $m, $q) = @_;
+       my ($self, $m) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
-       print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q;
+       print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 
@@ -2942,12 +2035,12 @@ sub R {
 }
 
 sub M {
-       my ($self, $m, $q) = @_;
+       my ($self, $m) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
        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;
+       print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q;
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
 }
@@ -2998,10 +2091,10 @@ sub chg_file {
 }
 
 sub D {
-       my ($self, $m, $q) = @_;
+       my ($self, $m) = @_;
        my ($dir, $file) = split_path($m->{file_b});
        my $pbat = $self->ensure_path($dir);
-       print "\tD\t$m->{file_b}\n" unless $q;
+       print "\tD\t$m->{file_b}\n" unless $::_q;
        $self->delete_entry($m->{file_b}, $pbat);
 }
 
@@ -3018,22 +2111,910 @@ sub close_edit {
 sub abort_edit {
        my ($self) = @_;
        $self->SUPER::abort_edit($self->{pool});
+}
+
+sub DESTROY {
+       my $self = shift;
+       $self->SUPER::DESTROY(@_);
        $self->{pool}->clear;
 }
 
-__END__
+# this drives the editor
+sub apply_diff {
+       my ($self) = @_;
+       my $mods = $self->{mods};
+       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+               my $f = $m->{chg};
+               if (defined $o{$f}) {
+                       $self->$f($m);
+               } else {
+                       fatal("Invalid change type: $f\n");
+               }
+       }
+       $self->rmdirs if $_rmdir;
+       if (@$mods == 0) {
+               $self->abort_edit;
+       } else {
+               $self->close_edit;
+       }
+       return scalar @$mods;
+}
 
-Data structures:
+package Git::SVN::Ra;
+use vars qw/@ISA $config_dir/;
+use strict;
+use warnings;
+my ($can_do_switch);
+my $RA;
 
-$log_msg hashref as returned by libsvn_log_entry()
-{
-       msg => 'whitespace-formatted log entry
-',                                             # trailing newline is preserved
-       revision => '8',                        # integer
-       date => '2004-02-24T17:01:44.108345Z',  # commit date
-       author => 'committer name'
+BEGIN {
+       # enforce temporary pool usage for some simple functions
+       my $e;
+       foreach (qw/get_latest_revnum rev_proplist get_file
+                   check_path get_dir get_uuid get_repos_root/) {
+               $e .= "sub $_ {
+                       my \$self = shift;
+                       my \$pool = SVN::Pool->new;
+                       my \@ret = \$self->SUPER::$_(\@_,\$pool);
+                       \$pool->clear;
+                       wantarray ? \@ret : \$ret[0]; }\n";
+       }
+       eval $e;
+}
+
+sub new {
+       my ($class, $url) = @_;
+       $url =~ s!/+$!!;
+       return $RA if ($RA && $RA->{url} eq $url);
+
+       SVN::_Core::svn_config_ensure($config_dir, undef);
+       my ($baton, $callbacks) = SVN::Core::auth_open_helper([
+           SVN::Client::get_simple_provider(),
+           SVN::Client::get_ssl_server_trust_file_provider(),
+           SVN::Client::get_simple_prompt_provider(
+             \&Git::SVN::Prompt::simple, 2),
+           SVN::Client::get_ssl_client_cert_prompt_provider(
+             \&Git::SVN::Prompt::ssl_client_cert, 2),
+           SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+             \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
+           SVN::Client::get_username_provider(),
+           SVN::Client::get_ssl_server_trust_prompt_provider(
+             \&Git::SVN::Prompt::ssl_server_trust),
+           SVN::Client::get_username_prompt_provider(
+             \&Git::SVN::Prompt::username, 2),
+         ]);
+       my $config = SVN::Core::config_get_config($config_dir);
+       my $self = SVN::Ra->new(url => $url, auth => $baton,
+                             config => $config,
+                             pool => SVN::Pool->new,
+                             auth_provider_callbacks => $callbacks);
+       $self->{svn_path} = $url;
+       $self->{repos_root} = $self->get_repos_root;
+       $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##;
+       $RA = bless $self, $class;
+}
+
+sub DESTROY {
+       # do not call the real DESTROY since we store ourselves in $RA
+}
+
+sub get_log {
+       my ($self, @args) = @_;
+       my $pool = SVN::Pool->new;
+       splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0');
+       my $ret = $self->SUPER::get_log(@args, $pool);
+       $pool->clear;
+       $ret;
+}
+
+sub get_commit_editor {
+       my ($self, $log, $cb, $pool) = @_;
+       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+       $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
+}
+
+sub uuid {
+       my ($self) = @_;
+       $self->{uuid} ||= $self->get_uuid;
+}
+
+sub gs_do_update {
+       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 : ''),
+                                       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
+       # just have to do some magic with set_path to make it so
+       # we only want a partial path.
+       my $sp = '';
+       my $final = join('/', @pc);
+       while (@pc) {
+               $reporter->set_path($sp, $rev_b, 0, @lock, $pool);
+               $sp .= '/' if length $sp;
+               $sp .= shift @pc;
+       }
+       die "BUG: '$sp' != '$final'\n" if ($sp ne $final);
+
+       $reporter->set_path($sp, $rev_a, $new, @lock, $pool);
+
+       $reporter->finish_report($pool);
+       $pool->clear;
+       $editor->{git_commit_ok};
+}
+
+# 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, $gs, $url_b, $editor) = @_;
+       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 ($ra, $reparented);
+       if ($old_url ne $full_url) {
+               if ($old_url !~ m#^svn(\+ssh)?://#) {
+                       SVN::_Ra::svn_ra_reparent($self->{session}, $full_url,
+                                                 $pool);
+                       $self->{url} = $full_url;
+                       $reparented = 1;
+               } else {
+                       $ra = Git::SVN::Ra->new($full_url);
+               }
+       }
+       $ra ||= $self;
+       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);
+
+       if ($reparented) {
+               SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);
+               $self->{url} = $old_url;
+       }
+
+       $pool->clear;
+       $editor->{git_commit_ok};
+}
+
+sub gs_fetch_loop_common {
+       my ($self, $base, $head, @gs) = @_;
+       my $inc = 1000;
+       my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
+       foreach my $gs (@gs) {
+               if (my $last_commit = $gs->last_commit) {
+                       $gs->assert_index_clean($last_commit);
+               }
+       }
+       while (1) {
+               my %revs;
+               my $err;
+               my $err_handler = $SVN::Error::handler;
+               $SVN::Error::handler = sub {
+                       ($err) = @_;
+                       skip_unknown_revs($err);
+               };
+               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);
+                               }
+                       }
+               }
+               last if $max >= $head;
+               $min = $max + 1;
+               $max += $inc;
+               $max = $head if ($max > $head);
+       }
+}
+
+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) {
+               my $pool = SVN::Pool->new;
+               my $rep = eval {
+                       $self->do_switch(1, '', 0, $self->{url},
+                                        SVN::Delta::Editor->new, $pool);
+               };
+               if ($@) {
+                       $can_do_switch = 0;
+               } else {
+                       $rep->abort_report($pool);
+                       $can_do_switch = 1;
+               }
+               $pool->clear;
+       }
+       $can_do_switch;
+}
+
+sub skip_unknown_revs {
+       my ($err) = @_;
+       my $errno = $err->apr_err();
+       # Maybe the branch we're tracking didn't
+       # exist when the repo started, so it's
+       # not an error if it doesn't, just continue
+       #
+       # Wonderfully consistent library, eh?
+       # 160013 - svn:// and file://
+       # 175002 - http(s)://
+       # 175007 - http(s):// (this repo required authorization, too...)
+       #   More codes may be discovered later...
+       if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
+               return;
+       }
+       die "Error from SVN, ($errno): ", $err->expanded_message,"\n";
+}
+
+# svn_log_changed_path_t objects passed to get_log are likely to be
+# overwritten even if only the refs are copied to an external variable,
+# so we should dup the structures in their entirety.  Using an externally
+# passed pool (instead of our temporary and quickly cleared pool in
+# Git::SVN::Ra) does not help matters at all...
+sub dup_changed_paths {
+       my ($paths) = @_;
+       return undef unless $paths;
+       my %ret;
+       foreach my $p (keys %$paths) {
+               my $i = $paths->{$p};
+               my %s = map { $_ => $i->$_ }
+                             qw/copyfrom_path copyfrom_rev action/;
+               $ret{$p} = \%s;
+       }
+       \%ret;
+}
+
+package Git::SVN::Log;
+use strict;
+use warnings;
+use POSIX qw/strftime/;
+use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
+            %rusers $show_commit $incremental/;
+my $l_fmt;
+
+sub cmt_showable {
+       my ($c) = @_;
+       return 1 if defined $c->{r};
+       if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
+                               $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
+               my @log = command(qw/cat-file commit/, $c->{c});
+               shift @log while ($log[0] ne "\n");
+               shift @log;
+               @{$c->{l}} = grep !/^git-svn-id: /, @log;
+
+               (undef, $c->{r}, undef) = ::extract_metadata(
+                               (grep(/^git-svn-id: /, @log))[-1]);
+       }
+       return defined $c->{r};
+}
+
+sub log_use_color {
+       return 1 if $color;
+       my ($dc, $dcvar);
+       $dcvar = 'color.diff';
+       $dc = `git-config --get $dcvar`;
+       if ($dc eq '') {
+               # nothing at all; fallback to "diff.color"
+               $dcvar = 'diff.color';
+               $dc = `git-config --get $dcvar`;
+       }
+       chomp($dc);
+       if ($dc eq 'auto') {
+               my $pc;
+               $pc = `git-config --get color.pager`;
+               if ($pc eq '') {
+                       # does not have it -- fallback to pager.color
+                       $pc = `git-config --bool --get pager.color`;
+               }
+               else {
+                       $pc = `git-config --bool --get color.pager`;
+                       if ($?) {
+                               $pc = 'false';
+                       }
+               }
+               chomp($pc);
+               if (-t *STDOUT || (defined $pager && $pc eq 'true')) {
+                       return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
+               }
+               return 0;
+       }
+       return 0 if $dc eq 'never';
+       return 1 if $dc eq 'always';
+       chomp($dc = `git-config --bool --get $dcvar`);
+       return ($dc eq 'true');
+}
+
+sub git_svn_log_cmd {
+       my ($r_min, $r_max) = @_;
+       my $gs = Git::SVN->_new;
+       my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
+                  $gs->refname);
+       push @cmd, '-r' unless $non_recursive;
+       push @cmd, qw/--raw --name-status/ if $verbose;
+       push @cmd, '--color' if log_use_color();
+       return @cmd unless defined $r_max;
+       if ($r_max == $r_min) {
+               push @cmd, '--max-count=1';
+               if (my $c = $gs->rev_db_get($r_max)) {
+                       push @cmd, $c;
+               }
+       } else {
+               my ($c_min, $c_max);
+               $c_max = $gs->rev_db_get($r_max);
+               $c_min = $gs->rev_db_get($r_min);
+               if (defined $c_min && defined $c_max) {
+                       if ($r_max > $r_max) {
+                               push @cmd, "$c_min..$c_max";
+                       } else {
+                               push @cmd, "$c_max..$c_min";
+                       }
+               } elsif ($r_max > $r_min) {
+                       push @cmd, $c_max;
+               } else {
+                       push @cmd, $c_min;
+               }
+       }
+       return @cmd;
+}
+
+# adapted from pager.c
+sub config_pager {
+       $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
+       if (!defined $pager) {
+               $pager = 'less';
+       } elsif (length $pager == 0 || $pager eq 'cat') {
+               $pager = undef;
+       }
+}
+
+sub run_pager {
+       return unless -t *STDOUT;
+       pipe my $rfd, my $wfd or return;
+       defined(my $pid = fork) or ::fatal "Can't fork: $!\n";
+       if (!$pid) {
+               open STDOUT, '>&', $wfd or
+                                    ::fatal "Can't redirect to stdout: $!\n";
+               return;
+       }
+       open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n";
+       $ENV{LESS} ||= 'FRSX';
+       exec $pager or ::fatal "Can't run pager: $! ($pager)\n";
+}
+
+sub tz_to_s_offset {
+       my ($tz) = @_;
+       $tz =~ s/(\d\d)$//;
+       return ($1 * 60) + ($tz * 3600);
+}
+
+sub get_author_info {
+       my ($dest, $author, $t, $tz) = @_;
+       $author =~ s/(?:^\s*|\s*$)//g;
+       $dest->{a_raw} = $author;
+       my $au;
+       if ($::_authors) {
+               $au = $rusers{$author} || undef;
+       }
+       if (!$au) {
+               ($au) = ($author =~ /<([^>]+)\@[^>]+>$/);
+       }
+       $dest->{t} = $t;
+       $dest->{tz} = $tz;
+       $dest->{a} = $au;
+       # Date::Parse isn't in the standard Perl distro :(
+       if ($tz =~ s/^\+//) {
+               $t += tz_to_s_offset($tz);
+       } elsif ($tz =~ s/^\-//) {
+               $t -= tz_to_s_offset($tz);
+       }
+       $dest->{t_utc} = $t;
+}
+
+sub process_commit {
+       my ($c, $r_min, $r_max, $defer) = @_;
+       if (defined $r_min && defined $r_max) {
+               if ($r_min == $c->{r} && $r_min == $r_max) {
+                       show_commit($c);
+                       return 0;
+               }
+               return 1 if $r_min == $r_max;
+               if ($r_min < $r_max) {
+                       # we need to reverse the print order
+                       return 0 if (defined $limit && --$limit < 0);
+                       push @$defer, $c;
+                       return 1;
+               }
+               if ($r_min != $r_max) {
+                       return 1 if ($r_min < $c->{r});
+                       return 1 if ($r_max > $c->{r});
+               }
+       }
+       return 0 if (defined $limit && --$limit < 0);
+       show_commit($c);
+       return 1;
+}
+
+sub show_commit {
+       my $c = shift;
+       if ($oneline) {
+               my $x = "\n";
+               if (my $l = $c->{l}) {
+                       while ($l->[0] =~ /^\s*$/) { shift @$l }
+                       $x = $l->[0];
+               }
+               $l_fmt ||= 'A' . length($c->{r});
+               print 'r',pack($l_fmt, $c->{r}),' | ';
+               print "$c->{c} | " if $show_commit;
+               print $x;
+       } else {
+               show_commit_normal($c);
+       }
+}
+
+sub show_commit_changed_paths {
+       my ($c) = @_;
+       return unless $c->{changed};
+       print "Changed paths:\n", @{$c->{changed}};
+}
+
+sub show_commit_normal {
+       my ($c) = @_;
+       print '-' x72, "\nr$c->{r} | ";
+       print "$c->{c} | " if $show_commit;
+       print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
+                                localtime($c->{t_utc})), ' | ';
+       my $nr_line = 0;
+
+       if (my $l = $c->{l}) {
+               while ($l->[$#$l] eq "\n" && $#$l > 0
+                                         && $l->[($#$l - 1)] eq "\n") {
+                       pop @$l;
+               }
+               $nr_line = scalar @$l;
+               if (!$nr_line) {
+                       print "1 line\n\n\n";
+               } else {
+                       if ($nr_line == 1) {
+                               $nr_line = '1 line';
+                       } else {
+                               $nr_line .= ' lines';
+                       }
+                       print $nr_line, "\n";
+                       show_commit_changed_paths($c);
+                       print "\n";
+                       print $_ foreach @$l;
+               }
+       } else {
+               print "1 line\n";
+               show_commit_changed_paths($c);
+               print "\n";
+
+       }
+       foreach my $x (qw/raw diff/) {
+               if ($c->{$x}) {
+                       print "\n";
+                       print $_ foreach @{$c->{$x}}
+               }
+       }
+}
+
+sub cmd_show_log {
+       my (@args) = @_;
+       my ($r_min, $r_max);
+       my $r_last = -1; # prevent dupes
+       if (defined $TZ) {
+               $ENV{TZ} = $TZ;
+       } else {
+               delete $ENV{TZ};
+       }
+       if (defined $::_revision) {
+               if ($::_revision =~ /^(\d+):(\d+)$/) {
+                       ($r_min, $r_max) = ($1, $2);
+               } elsif ($::_revision =~ /^\d+$/) {
+                       $r_min = $r_max = $::_revision;
+               } else {
+                       ::fatal "-r$::_revision is not supported, use ",
+                               "standard \'git log\' arguments instead\n";
+               }
+       }
+
+       config_pager();
+       @args = (git_svn_log_cmd($r_min, $r_max), @args);
+       my $log = command_output_pipe(@args);
+       run_pager();
+       my (@k, $c, $d);
+       my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
+       while (<$log>) {
+               if (/^${esc_color}commit ($::sha1_short)/o) {
+                       my $cmt = $1;
+                       if ($c && cmt_showable($c) && $c->{r} != $r_last) {
+                               $r_last = $c->{r};
+                               process_commit($c, $r_min, $r_max, \@k) or
+                                                               goto out;
+                       }
+                       $d = undef;
+                       $c = { c => $cmt };
+               } elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) {
+                       get_author_info($c, $1, $2, $3);
+               } elsif (/^${esc_color}(?:tree|parent|committer) /o) {
+                       # ignore
+               } elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) {
+                       push @{$c->{raw}}, $_;
+               } elsif (/^${esc_color}[ACRMDT]\t/) {
+                       # we could add $SVN->{svn_path} here, but that requires
+                       # remote access at the moment (repo_path_split)...
+                       s#^(${esc_color})([ACRMDT])\t#$1   $2 #o;
+                       push @{$c->{changed}}, $_;
+               } elsif (/^${esc_color}diff /o) {
+                       $d = 1;
+                       push @{$c->{diff}}, $_;
+               } elsif ($d) {
+                       push @{$c->{diff}}, $_;
+               } elsif (/^${esc_color}    (git-svn-id:.+)$/o) {
+                       ($c->{url}, $c->{r}, undef) = ::extract_metadata($1);
+               } elsif (s/^${esc_color}    //o) {
+                       push @{$c->{l}}, $_;
+               }
+       }
+       if ($c && defined $c->{r} && $c->{r} != $r_last) {
+               $r_last = $c->{r};
+               process_commit($c, $r_min, $r_max, \@k);
+       }
+       if (@k) {
+               my $swap = $r_max;
+               $r_max = $r_min;
+               $r_min = $swap;
+               process_commit($_, $r_min, $r_max) foreach reverse @k;
+       }
+out:
+       close $log;
+       print '-' x72,"\n" unless $incremental || $oneline;
+}
+
+package Git::SVN::Migration;
+# these version numbers do NOT correspond to actual version numbers
+# of git nor git-svn.  They are just relative.
+#
+# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
+#
+# v1 layout: .git/$id/info/url, refs/remotes/$id
+#
+# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
+#
+# v3 layout: .git/svn/$id, refs/remotes/$id
+#            - info/url may remain for backwards compatibility
+#            - this is what we migrate up to this layout automatically,
+#            - this will be used by git svn init on single branches
+#
+# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
+#            - this is only created for newly multi-init-ed
+#              repositories.  Similar in spirit to the
+#              --use-separate-remotes option in git-clone (now default)
+#            - we do not automatically migrate to this (following
+#              the example set by core git)
+use strict;
+use warnings;
+use Carp qw/croak/;
+use File::Path qw/mkpath/;
+use File::Basename qw/dirname basename/;
+use vars qw/$_minimize/;
+
+sub migrate_from_v0 {
+       my $git_dir = $ENV{GIT_DIR};
+       return undef unless -d $git_dir;
+       my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+       my $migrated = 0;
+       while (<$fh>) {
+               chomp;
+               my ($id, $orig_ref) = ($_, $_);
+               next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
+               next unless -f "$git_dir/$id/info/url";
+               my $new_ref = "refs/remotes/$id";
+               if (::verify_ref("$new_ref^0")) {
+                       print STDERR "W: $orig_ref is probably an old ",
+                                    "branch used by an ancient version of ",
+                                    "git-svn.\n",
+                                    "However, $new_ref also exists.\n",
+                                    "We will not be able ",
+                                    "to use this branch until this ",
+                                    "ambiguity is resolved.\n";
+                       next;
+               }
+               print STDERR "Migrating from v0 layout...\n" if !$migrated;
+               print STDERR "Renaming ref: $orig_ref => $new_ref\n";
+               command_noisy('update-ref', $new_ref, $orig_ref);
+               command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
+               $migrated++;
+       }
+       command_close_pipe($fh, $ctx);
+       print STDERR "Done migrating from v0 layout...\n" if $migrated;
+       $migrated;
+}
+
+sub migrate_from_v1 {
+       my $git_dir = $ENV{GIT_DIR};
+       my $migrated = 0;
+       return $migrated unless -d $git_dir;
+       my $svn_dir = "$git_dir/svn";
+
+       # just in case somebody used 'svn' as their $id at some point...
+       return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
+
+       print STDERR "Migrating from a git-svn v1 layout...\n";
+       mkpath([$svn_dir]);
+       print STDERR "Data from a previous version of git-svn exists, but\n\t",
+                    "$svn_dir\n\t(required for this version ",
+                    "($::VERSION) of git-svn) does not. exist\n";
+       my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
+       while (<$fh>) {
+               my $x = $_;
+               next unless $x =~ s#^refs/remotes/##;
+               chomp $x;
+               next unless -f "$git_dir/$x/info/url";
+               my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
+               next unless $u;
+               my $dn = dirname("$git_dir/svn/$x");
+               mkpath([$dn]) unless -d $dn;
+               if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
+                       mkpath(["$git_dir/svn/svn"]);
+                       print STDERR " - $git_dir/$x/info => ",
+                                       "$git_dir/svn/$x/info\n";
+                       rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
+                              croak "$!: $x";
+                       # don't worry too much about these, they probably
+                       # don't exist with repos this old (save for index,
+                       # and we can easily regenerate that)
+                       foreach my $f (qw/unhandled.log index .rev_db/) {
+                               rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
+                       }
+               } else {
+                       print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
+                       rename "$git_dir/$x", "$git_dir/svn/$x" or
+                              croak "$!: $x";
+               }
+               $migrated++;
+       }
+       command_close_pipe($fh, $ctx);
+       print STDERR "Done migrating from a git-svn v1 layout\n";
+       $migrated;
+}
+
+sub read_old_urls {
+       my ($l_map, $pfx, $path) = @_;
+       my @dir;
+       foreach (<$path/*>) {
+               if (-r "$_/info/url") {
+                       $pfx .= '/' if $pfx && $pfx !~ m!/$!;
+                       my $ref_id = $pfx . basename $_;
+                       my $url = ::file_to_s("$_/info/url");
+                       $l_map->{$ref_id} = $url;
+               } elsif (-d $_) {
+                       push @dir, $_;
+               }
+       }
+       foreach (@dir) {
+               my $x = $_;
+               $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
+               read_old_urls($l_map, $x, $_);
+       }
+}
+
+sub migrate_from_v2 {
+       my @cfg = command(qw/config -l/);
+       return if grep /^svn-remote\..+\.url=/, @cfg;
+       my %l_map;
+       read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
+       my $migrated = 0;
+
+       foreach my $ref_id (sort keys %l_map) {
+               Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
+               $migrated++;
+       }
+       $migrated;
+}
+
+sub minimize_connections {
+       my $r = Git::SVN::read_all_remotes();
+       my $new_urls = {};
+       my $root_repos = {};
+       foreach my $repo_id (keys %$r) {
+               my $url = $r->{$repo_id}->{url} or next;
+               my $fetch = $r->{$repo_id}->{fetch} or next;
+               my $ra = Git::SVN::Ra->new($url);
+
+               # skip existing cases where we already connect to the root
+               if (($ra->{url} eq $ra->{repos_root}) ||
+                   (Git::SVN::sanitize_remote_name($ra->{repos_root}) eq
+                    $repo_id)) {
+                       $root_repos->{$ra->{url}} = $repo_id;
+                       next;
+               }
+
+               my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
+               my $root_path = $ra->{url};
+               $root_path =~ s#^\Q$ra->{repos_root}\E/*##;
+               foreach my $path (keys %$fetch) {
+                       my $ref_id = $fetch->{$path};
+                       my $gs = Git::SVN->new($ref_id, $repo_id, $path);
+
+                       # make sure we can read when connecting to
+                       # a higher level of a repository
+                       my ($last_rev, undef) = $gs->last_rev_commit;
+                       if (!defined $last_rev) {
+                               $last_rev = eval {
+                                       $root_ra->get_latest_revnum;
+                               };
+                               next if $@;
+                       }
+                       my $new = $root_path;
+                       $new .= length $path ? "/$path" : '';
+                       eval {
+                               $root_ra->get_log([$new], $last_rev, $last_rev,
+                                                 0, 0, 1, sub { });
+                       };
+                       next if $@;
+                       $new_urls->{$ra->{repos_root}}->{$new} =
+                               { ref_id => $ref_id,
+                                 old_repo_id => $repo_id,
+                                 old_path => $path };
+               }
+       }
+
+       my @emptied;
+       foreach my $url (keys %$new_urls) {
+               # see if we can re-use an existing [svn-remote "repo_id"]
+               # instead of creating a(n ugly) new section:
+               my $repo_id = $root_repos->{$url} ||
+                             Git::SVN::sanitize_remote_name($url);
+
+               my $fetch = $new_urls->{$url};
+               foreach my $path (keys %$fetch) {
+                       my $x = $fetch->{$path};
+                       Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
+                       my $pfx = "svn-remote.$x->{old_repo_id}";
+
+                       my $old_fetch = quotemeta("$x->{old_path}:".
+                                                 "refs/remotes/$x->{ref_id}");
+                       command_noisy(qw/config --unset/,
+                                     "$pfx.fetch", '^'. $old_fetch . '$');
+                       delete $r->{$x->{old_repo_id}}->
+                              {fetch}->{$x->{old_path}};
+                       if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
+                               command_noisy(qw/config --unset/,
+                                             "$pfx.url");
+                               push @emptied, $x->{old_repo_id}
+                       }
+               }
+       }
+       if (@emptied) {
+               my $file = $ENV{GIT_CONFIG} || $ENV{GIT_CONFIG_LOCAL} ||
+                          "$ENV{GIT_DIR}/config";
+               print STDERR <<EOF;
+The following [svn-remote] sections in your config file ($file) are empty
+and can be safely removed:
+EOF
+               print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
+       }
+}
+
+sub migration_check {
+       migrate_from_v0();
+       migrate_from_v1();
+       migrate_from_v2();
+       minimize_connections() if $_minimize;
+}
+
+package Git::IndexInfo;
+use strict;
+use warnings;
+use Git qw/command_input_pipe command_close_pipe/;
+
+sub new {
+       my ($class) = @_;
+       my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/);
+       bless { gui => $gui, ctx => $ctx, nr => 0}, $class;
+}
+
+sub remove {
+       my ($self, $path) = @_;
+       if (print { $self->{gui} } '0 ', 0 x 40, "\t", $path, "\0") {
+               return ++$self->{nr};
+       }
+       undef;
+}
+
+sub update {
+       my ($self, $mode, $hash, $path) = @_;
+       if (print { $self->{gui} } $mode, ' ', $hash, "\t", $path, "\0") {
+               return ++$self->{nr};
+       }
+       undef;
+}
+
+sub DESTROY {
+       my ($self) = @_;
+       command_close_pipe($self->{gui}, $self->{ctx});
+}
+
+__END__
+
+Data structures:
+
+$log_entry hashref as returned by libsvn_log_entry()
+{
+       log => 'whitespace-formatted log entry
+',                                             # trailing newline is preserved
+       revision => '8',                        # integer
+       date => '2004-02-24T17:01:44.108345Z',  # commit date
+       author => 'committer name'
 };
 
+
+# this is generated by generate_diff();
 @mods = array of diff-index line hashes, each element represents one line
        of diff-index output