git-svn: remove graft-branches command
[gitweb.git] / git-svn.perl
index dd639a1b9aca828144c14a804df9a55b90f2a373..e75021bce20d7d2dd2d8530089a19932dc87be6b 100755 (executable)
@@ -8,7 +8,8 @@
                $GIT_SVN_INDEX $GIT_SVN
                $GIT_DIR $GIT_SVN_DIR $REVDB
                $_follow_parent $sha1 $sha1_short $_revision
-               $_cp_remote $_upgrade/;
+               $_cp_remote $_upgrade $_rmdir $_q $_cp_similarity
+               $_find_copies_harder $_l $_authors %users/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
 $VERSION = '@@GIT_VERSION@@';
 
 $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;
 use File::Path qw/mkpath/;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
 use IPC::Open3;
-use Memoize;
 use Git;
-memoize('revisions_eq');
-memoize('cmt_metadata');
-memoize('get_commit_time');
 
 BEGIN {
        my $s;
@@ -70,20 +57,16 @@ BEGIN
 my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
 $sha1 = qr/[a-f\d]{40}/;
 $sha1_short = qr/[a-f\d]{4,40}/;
-my ($_stdin,$_help,$_rmdir,$_edit,
-       $_find_copies_harder, $_l, $_cp_similarity,
-       $_repack, $_repack_nr, $_repack_flags, $_q,
+my ($_stdin, $_help, $_edit,
+       $_repack, $_repack_nr, $_repack_flags,
        $_message, $_file, $_no_metadata,
-       $_template, $_shared, $_no_default_regex, $_no_graft_copy,
-       $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
+       $_template, $_shared,
+       $_version, $_upgrade,
        $_merge, $_strategy, $_dry_run,
        $_prefix);
-my (@_branch_from, %tree_map, %users);
 my @repo_path_split_cache;
 
-my %fc_opts = ( 'branch|b=s' => \@_branch_from,
-               'follow-parent|follow' => \$_follow_parent,
-               'branch-all-refs|B' => \$_branch_all_refs,
+my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent,
                'authors-file|A=s' => \$_authors,
                'repack:i' => \$_repack,
                'no-metadata' => \$_no_metadata,
@@ -111,25 +94,20 @@ BEGIN
        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, } ],
+       '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 => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)",
                        { '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' => [ \&cmd_multi_init,
                        'Initialize multiple trees (like git-svnimport)',
                        { %multi_opts, %init_opts,
@@ -139,7 +117,7 @@ BEGIN
                         'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
                         'prefix=s' => \$_prefix,
                        } ],
-       'multi-fetch' => [ \&multi_fetch,
+       'multi-fetch' => [ \&cmd_multi_fetch,
                        'Fetch multiple trees (like git-svnimport)',
                        \%fc_opts ],
        'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs',
@@ -154,7 +132,8 @@ BEGIN
                          '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,
@@ -178,13 +157,11 @@ BEGIN
                                'id|i=s' => \$GIT_SVN);
 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);
 exit 0;
@@ -295,97 +272,16 @@ sub cmd_init {
 }
 
 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 ||= Git::SVN::Ra->new($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);
-       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";
-               }
-       }
-       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.
-                       $SVN->dup->get_log([''], $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 = Git::SVN::Ra->new($SVN_URL);
+       my $gs = Git::SVN->new;
+       $gs->fetch(@_);
+       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 = ();
@@ -403,130 +299,81 @@ 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);
+       my $gs = Git::SVN->new;
+       my ($r_last, $cmt_last) = $gs->last_rev_commit;
+       $gs->fetch;
+       if ($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";
+       }
+       $gs->set_tree($_) foreach @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;
-       }
-       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 $pool = SVN::Pool->new;
-                       my $ed = SVN::Git::Editor->new(
-                                       {       r => $r_last,
-                                               ra => $SVN->dup,
-                                               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)
-                                               }, $pool)
-                                       );
-                       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;
-                       }
-                       $pool->clear;
-                       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);
-               }
-       }
-       $ENV{LC_ALL} = 'C';
-       unlink $commit_msg;
-}
-
-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 $ra = $gs->ra;
+                       my $pool = SVN::Pool->new;
+                       my %ed_opts = ( r => $last_rev,
+                                       ra => $ra->dup,
+                                       svn_path => $ra->{svn_path} );
+                       my $ed = SVN::Git::Editor->new(\%ed_opts,
+                                        $ra->get_commit_editor($::_message,
+                                        sub { print "Committed r$_[0]\n";
+                                              $last_rev = $_[0]; }),
+                                        $pool);
+                       my $mods = $ed->apply_diff("$d~1", $d);
+                       if (@$mods == 0) {
+                               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);
+       command_noisy(@finish, $gs->refname);
 }
 
 sub cmd_show_ignore {
@@ -535,40 +382,6 @@ sub cmd_show_ignore {
        $gs->traverse_ignore(\*STDOUT, '', $r);
 }
 
-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_multi_init {
        my $url = shift;
        unless (defined $_trunk || defined $_branches || defined $_tags) {
@@ -590,102 +403,67 @@ sub cmd_multi_init {
        complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
 }
 
-sub multi_fetch {
+sub cmd_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', @_);
+       if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") {
+               my $gs = Git::SVN->new('trunk');
+               $gs->fetch(@_);
        }
-       rec_fetch('', "$GIT_DIR/svn", @_);
-}
-
-sub commit_diff_usage {
-       print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
-       exit 1
+       rec_fetch('', "$ENV{GIT_DIR}/svn", @_);
 }
 
-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";
+# 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);
+       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};
+       }
+       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 ||= Git::SVN::Ra->new($SVN_URL);
+       my $ra ||= Git::SVN::Ra->new($url);
+       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 $rev_committed;
        my $pool = SVN::Pool->new;
-       my $ed = SVN::Git::Editor->new({        r => $r,
-                                               ra => $SVN->dup,
-                                               c => $tb,
-                                               svn_path => $SVN->{svn_path}
-                                       },
-                               $SVN->get_commit_editor($_message,
-                                       sub {
-                                               $rev_committed = $_[0];
-                                               print "Committed $_[0]\n";
-                                       },
-                                       $pool)
-                               );
-       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;
-               }
-       };
+       my %ed_opts = ( r => $r,
+                       ra => $ra->dup,
+                       svn_path => $ra->{svn_path} );
+       my $ed = SVN::Git::Editor->new(\%ed_opts,
+                                      $ra->get_commit_editor($_message,
+                                        sub { print "Committed r$_[0]\n" }),
+                                      $pool);
+       my $mods = $ed->apply_diff($ta, $tb);
+       if (@$mods == 0) {
+               print "No changes\n$ta == $tb\n";
+       }
        $pool->clear;
-       fatal "$@\n" if $@;
-       $_message = $_file = undef;
-       return $rev_committed;
 }
 
 ########################### utility functions #########################
 
-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 $?;
-}
-
 sub rec_fetch {
        my ($pfx, $p, @args) = @_;
        my @dir;
@@ -694,15 +472,16 @@ sub rec_fetch {
                        $pfx .= '/' if $pfx && $pfx !~ m!/$!;
                        my $id = $pfx . basename $_;
                        next if $id eq 'trunk';
-                       fetch_child_id($id, @args);
+                       my $gs = Git::SVN->new($id);
+                       $gs->fetch(@args);
                } elsif (-d $_) {
                        push @dir, $_;
                }
        }
        foreach (@dir) {
                my $x = $_;
-               $x =~ s!^\Q$GIT_DIR\E/svn/!!;
-               rec_fetch($x, $_);
+               $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
+               rec_fetch($x, $_, @args);
        }
 }
 
@@ -776,157 +555,6 @@ sub common_prefix {
        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 = Git::SVN::Ra->new($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) {
-               $SVN->dup->get_log([$path], $min, $max, 0, 2, 1,
-                       sub {
-                               libsvn_graft_file_copies($grafts, $tree_paths,
-                                                       $path, @_);
-                       });
-               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);
-               }
-       }
-}
-
 sub verify_ref {
        my ($ref) = @_;
        eval { command_oneline([ 'rev-parse', '--verify', $ref ],
@@ -963,7 +591,7 @@ sub setup_git_svn {
 
 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') {
@@ -972,7 +600,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;
@@ -982,146 +610,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 {
@@ -1142,124 +668,6 @@ 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]);
@@ -1277,43 +685,6 @@ sub check_upgrade_needed {
        }
 }
 
-# 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;
-                       }
-               }
-               close $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";
@@ -1388,20 +759,6 @@ sub migration_check {
        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);
-               }
-               --$r;
-       }
-       return (undef, undef);
-}
-
 sub init_vars {
        $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
        $Git::SVN::default = $GIT_SVN;
@@ -1409,7 +766,6 @@ sub init_vars {
        $REVDB = "$GIT_SVN_DIR/.rev_db";
        $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
        $SVN_URL = undef;
-       %tree_map = ();
 }
 
 # convert GetOpt::Long specs for use by git-config
@@ -1435,95 +791,6 @@ sub read_repo_config {
        }
 }
 
-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/;
-                       }
-               }
-               close $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;
@@ -1767,14 +1034,14 @@ sub assert_index_clean {
 }
 
 sub get_commit_parents {
-       my ($self, $log_msg, @parents) = @_;
+       my ($self, $log_entry, @parents) = @_;
        my (%seen, @ret, @tmp);
        # 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) {
-                       push @tmp, $2 if $1 == $log_msg->{revision};
+                       push @tmp, $2 if $1 == $log_entry->{revision};
                } else {
                        push @tmp, $p if $p =~ /^$::sha1_short$/o;
                }
@@ -1782,7 +1049,7 @@ sub get_commit_parents {
        if (my $cur = ::verify_ref($self->refname.'^0')) {
                push @tmp, $cur;
        }
-       push @tmp, $_ foreach (@{$log_msg->{parents}}, @tmp);
+       push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp);
        while (my $p = shift @tmp) {
                next if $seen{$p};
                $seen{$p} = 1;
@@ -1791,7 +1058,7 @@ sub get_commit_parents {
                last if @ret >= 16;
        }
        if (@tmp) {
-               die "r$log_msg->{revision}: No room for parents:\n\t",
+               die "r$log_entry->{revision}: No room for parents:\n\t",
                    join("\n\t", @tmp), "\n";
        }
        @ret;
@@ -1812,17 +1079,19 @@ sub check_upgrade_needed {
 }
 
 sub do_git_commit {
-       my ($self, $log_msg, @parents) = @_;
-       if (my $c = $self->rev_db_get($log_msg->{revision})) {
-               croak "$log_msg->{revision} = $c already exists! ",
+       my ($self, $log_entry, @parents) = @_;
+       if (my $c = $self->rev_db_get($log_entry->{revision})) {
+               croak "$log_entry->{revision} = $c already exists! ",
                      "Why are we refetching it?\n";
        }
-       my ($name, $email) = ::author_name_email($log_msg->{author}, $self->ra);
+       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_msg->{date};
+       $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
 
-       my $tree = $log_msg->{tree};
+       my $tree = $log_entry->{tree};
        if (!defined $tree) {
                $tree = $self->tmp_index_do(sub {
                                            command_oneline('write-tree') });
@@ -1830,14 +1099,15 @@ sub do_git_commit {
        die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o;
 
        my @exec = ('git-commit-tree', $tree);
-       foreach ($self->get_commit_parents($log_msg, @parents)) {
+       foreach ($self->get_commit_parents($log_entry, @parents)) {
                push @exec, '-p', $_;
        }
        defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
                                                                   or croak $!;
-       print $msg_fh $log_msg->{log} or croak $!;
-       print $msg_fh "\ngit-svn-id: $self->{ra}->{url}\@$log_msg->{revision}",
-                     " ", $self->ra->uuid,"\n" or croak $!;
+       print $msg_fh $log_entry->{log} or croak $!;
+       print $msg_fh "\ngit-svn-id: ", $self->ra->{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> });
@@ -1849,16 +1119,16 @@ sub do_git_commit {
        }
 
        command_noisy('update-ref',$self->refname, $commit);
-       $self->rev_db_set($log_msg->{revision}, $commit);
+       $self->rev_db_set($log_entry->{revision}, $commit);
 
-       $self->{last_rev} = $log_msg->{revision};
+       $self->{last_rev} = $log_entry->{revision};
        $self->{last_commit} = $commit;
-       print "r$log_msg->{revision} = $commit\n";
+       print "r$log_entry->{revision} = $commit\n";
        return $commit;
 }
 
 sub do_fetch {
-       my ($self, $paths, $rev) = @_; #, $author, $date, $msg) = @_;
+       my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_;
        my $ed = SVN::Git::Fetcher->new($self);
        my ($last_rev, @parents);
        if ($self->{last_commit}) {
@@ -1868,7 +1138,7 @@ sub do_fetch {
        } else {
                $last_rev = $rev;
        }
-       unless ($self->ra->do_update($last_rev, $rev, '', 1, $ed)) {
+       unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) {
                die "SVN connection failed somewhere...\n";
        }
        $self->make_log_entry($rev, \@parents, $ed);
@@ -1889,7 +1159,7 @@ sub write_untracked {
                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};
                                if (defined $v) {
                                        print $fh "  +$t: ",
@@ -1920,6 +1190,25 @@ sub write_untracked {
        }
 }
 
+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 check_author {
+       my ($author) = @_;
+       if (!defined $author || length $author == 0) {
+               $author = '(no author)';
+       }
+       if (defined $::_authors && ! defined $::users{$author}) {
+               die "Author: $author not defined in $::_authors file\n";
+       }
+       $author;
+}
+
 sub make_log_entry {
        my ($self, $rev, $parents, $untracked) = @_;
        my $rp = $self->ra->rev_proplist($rev);
@@ -1958,7 +1247,7 @@ sub fetch {
        while (1) {
                my @revs;
                $self->ra->get_log([''], $min, $max, 0, 1, 1, sub {
-                       my ($paths, $rev, $author, $date, $msg) = @_;
+                       my ($paths, $rev, $author, $date, $log) = @_;
                        push @revs, $rev });
                foreach (@revs) {
                        my $log_entry = $self->do_fetch(undef, $_);
@@ -1986,14 +1275,13 @@ sub set_tree_cb {
 
 sub set_tree {
        my ($self, $tree) = (shift, shift);
-       my $log_entry = get_commit_entry($tree);
+       my $log_entry = ::get_commit_entry($tree);
        unless ($self->{last_rev}) {
                fatal("Must have an existing revision to commit\n");
        }
        my $pool = SVN::Pool->new;
        my $ed = SVN::Git::Editor->new({ r => $self->{last_rev},
                                         ra => $self->ra->dup,
-                                        c => $tree,
                                         svn_path => $self->ra->{svn_path}
                                       },
                                       $self->ra->get_commit_editor(
@@ -2080,6 +1368,11 @@ sub _new {
                db_path => "$dir/.rev_db" }, $class;
 }
 
+sub uri_encode {
+       my ($f) = @_;
+       $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
+       $f
+}
 
 package Git::SVN::Prompt;
 use strict;
@@ -2225,160 +1518,6 @@ sub uri_decode {
        $f
 }
 
-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";
-       }
-       $msg = '' if ($rev == 0 && !defined $msg);
-
-       open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!;
-       my $h;
-       print $un "r$rev\n" or croak $!;
-       $h = $untracked->{empty};
-       foreach (sort keys %$h) {
-               my $act = $h->{$_} ? '+empty_dir' : '-empty_dir';
-               print $un "  $act: ", uri_encode($_), "\n" or croak $!;
-               warn "W: $act: $_\n";
-       }
-       foreach my $t (qw/dir_prop file_prop/) {
-               $h = $untracked->{$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};
-                               my $v = $h->{$path}->{$prop};
-                               if (defined $v) {
-                                       print $un "  +$t: ",
-                                                 uri_encode($ppath), ' ',
-                                                 uri_encode($prop), ' ',
-                                                 uri_encode($v), "\n"
-                                                 or croak $!;
-                               } else {
-                                       print $un "  -$t: ",
-                                                 uri_encode($ppath), ' ',
-                                                 uri_encode($prop), "\n"
-                                                 or croak $!;
-                               }
-                       }
-               }
-       }
-       foreach my $t (qw/absent_file absent_directory/) {
-               $h = $untracked->{$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 $!;
-                               warn "W: $t: $parent/$path ",
-                                    "Insufficient permissions?\n";
-                       }
-               }
-       }
-
-       # revprops (make this optional? it's an extra network trip...)
-       my $rp = $SVN->rev_proplist($rev);
-       foreach (sort keys %$rp) {
-               next if /^svn:(?:author|date|log)$/;
-               print $un "  rev_prop: ", uri_encode($_), ' ',
-                         uri_encode($rp->{$_}), "\n";
-       }
-       close $un or croak $!;
-
-       { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
-         author => $author, msg => $msg."\n", parents => $parents || [],
-         revprops => $rp }
-}
-
-sub libsvn_fetch {
-       my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
-       my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q });
-       my (undef, $last_rev, undef) = cmt_metadata($last_commit);
-       unless ($SVN->gs_do_update($last_rev, $rev, '', 1, $ed)) {
-               die "SVN connection failed somewhere...\n";
-       }
-       libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed);
-}
-
-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;
-       }
-       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);
-       }
-       return (undef, undef);
-}
-
-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 libsvn_traverse_ignore {
-       my ($fh, $path, $r) = @_;
-       $path =~ s#^/+##g;
-       my ($dirent, undef, $props) = $SVN->get_dir($path, $r);
-       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";
-               } 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);
-       }
-}
-
 sub revisions_eq {
        my ($path, $r0, $r1) = @_;
        return 1 if $r0 == $r1;
@@ -2390,7 +1529,7 @@ sub revisions_eq {
 }
 
 sub libsvn_find_parent_branch {
-       my ($paths, $rev, $author, $date, $msg) = @_;
+       my ($paths, $rev, $author, $date, $log) = @_;
        my $svn_path = '/'.$SVN->{svn_path};
 
        # look for a parent from another branch:
@@ -2442,7 +1581,7 @@ sub libsvn_find_parent_branch {
                command_noisy('read-tree', $parent);
                unless ($SVN->can_do_switch) {
                        return _libsvn_new_tree($paths, $rev, $author, $date,
-                                               $msg, [$parent]);
+                                               $log, [$parent]);
                }
                # do_switch works with svn/trunk >= r22312, but that is not
                # included with SVN 1.4.2 (the latest version at the moment),
@@ -2451,166 +1590,19 @@ sub libsvn_find_parent_branch {
                my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q });
                $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or
                                   die "SVN connection failed somewhere...\n";
-               return libsvn_log_entry($rev, $author, $date, $msg, [$parent]);
+               return libsvn_log_entry($rev, $author, $date, $log, [$parent]);
        }
        print STDERR "Nope, branch point not imported or unknown\n";
        return undef;
 }
 
-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, []);
-}
-
 sub _libsvn_new_tree {
-       my ($paths, $rev, $author, $date, $msg, $parents) = @_;
+       my ($paths, $rev, $author, $date, $log, $parents) = @_;
        my $ed = SVN::Git::Fetcher->new({q => $_q});
        unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) {
                die "SVN connection failed somewhere...\n";
        }
-       libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed);
-}
-
-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;
-       }
-       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 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);
-       }
-}
-
-sub set_index {
-       my $old = $ENV{GIT_INDEX_FILE};
-       $ENV{GIT_INDEX_FILE} = shift;
-       return $old;
-}
-
-sub restore_index {
-       my ($old) = @_;
-       if (defined $old) {
-               $ENV{GIT_INDEX_FILE} = $old;
-       } else {
-               delete $ENV{GIT_INDEX_FILE};
-       }
-}
-
-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;
-               }
-       } else {
-               fetch("$rev=$c");
-       }
-}
-
-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";
-};
-
-# 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
-# modules, but I'm not sure which is most portable...  So I'll just
-# go with something that's plain-text, but still capable of
-# being randomly accessed.  So here's my ultra-simple fixed-width
-# database.  All records are 40 characters + "\n", so it's easy to seek
-# 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) = @_;
-       length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
-       open my $fh, '+<', $file 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);
-       }
-       seek $fh, $offset, 0 or croak $!;
-       print $fh $commit,"\n";
-       close $fh or croak $!;
-}
-
-sub revdb_get {
-       my ($file, $rev) = @_;
-       my $ret;
-       my $offset = $rev * 41;
-       open my $fh, '<', $file or croak $!;
-       seek $fh, $offset, 0;
-       if (tell $fh == $offset) {
-               $ret = readline $fh;
-               if (defined $ret) {
-                       chomp $ret;
-                       $ret = undef if ($ret =~ /^0{40}$/);
-               }
-       }
-       close $fh or croak $!;
-       return $ret;
+       libsvn_log_entry($rev, $author, $date, $log, $parents, $ed);
 }
 
 {
@@ -2637,15 +1629,14 @@ 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->{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->{gui}, $self->{ctx}) = command_input_pipe(
-                                            qw/update-index -z --index-info/);
+       ($self->{gui}, $self->{ctx}) = $git_svn->tmp_index_do(
+              sub { command_input_pipe(qw/update-index -z --index-info/) } );
        require Digest::MD5;
        $self;
 }
@@ -2843,7 +1834,7 @@ sub new {
        my $git_svn = shift;
        my $self = SVN::Delta::Editor->new(@_);
        bless $self, $class;
-       foreach (qw/svn_path c r ra /) {
+       foreach (qw/svn_path r ra/) {
                die "$_ required!\n" unless (defined $git_svn->{$_});
                $self->{$_} = $git_svn->{$_};
        }
@@ -2868,7 +1859,7 @@ sub url_path {
 }
 
 sub rmdirs {
-       my ($self, $q) = @_;
+       my ($self, $tree_b) = @_;
        my $rm = $self->{rm};
        delete $rm->{''}; # we never delete the url we're tracking
        return unless %$rm;
@@ -2887,7 +1878,7 @@ sub rmdirs {
        return unless %$rm;
 
        my ($fh, $ctx) = command_output_pipe(
-                                  qw/ls-tree --name-only -r -z/, $self->{c});
+                                  qw/ls-tree --name-only -r -z/, $tree_b);
        local $/ = "\0";
        while (<$fh>) {
                chomp;
@@ -2906,7 +1897,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};
        }
@@ -2945,23 +1936,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});
 }
@@ -2975,12 +1966,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});
 
@@ -2990,12 +1981,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});
 }
@@ -3046,10 +2037,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);
 }
 
@@ -3069,6 +2060,77 @@ sub abort_edit {
        $self->{pool}->clear;
 }
 
+# this drives the editor
+sub apply_diff {
+       my ($self, $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$::_l" if defined $::_l;
+       push @diff_tree, $tree_a, $tree_b;
+       my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
+       my $nl = $/;
+       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);
+       $/ = $nl;
+
+       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($tree_b) if $::_rmdir;
+       if (@mods == 0) {
+               $self->abort_edit;
+       } else {
+               $self->close_edit;
+       }
+       \@mods;
+}
+
 package Git::SVN::Ra;
 use vars qw/@ISA $config_dir/;
 use strict;
@@ -3144,9 +2206,9 @@ sub get_log {
 }
 
 sub get_commit_editor {
-       my ($self, $msg, $cb, $pool) = @_;
+       my ($self, $log, $cb, $pool) = @_;
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
-       $self->SUPER::get_commit_editor($msg, $cb, @lock, $pool);
+       $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
 }
 
 sub uuid {
@@ -3211,13 +2273,13 @@ sub cmt_showable {
        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;
+               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: /, @msg))[-1]);
+                               (grep(/^git-svn-id: /, @log))[-1]);
        }
        return defined $c->{r};
 }
@@ -3320,7 +2382,7 @@ sub get_author_info {
        $author =~ s/(?:^\s*|\s*$)//g;
        $dest->{a_raw} = $author;
        my $au;
-       if ($_authors) {
+       if ($::_authors) {
                $au = $rusers{$author} || undef;
        }
        if (!$au) {
@@ -3503,9 +2565,9 @@ sub cmd_show_log {
 
 Data structures:
 
-$log_msg hashref as returned by libsvn_log_entry()
+$log_entry hashref as returned by libsvn_log_entry()
 {
-       msg => 'whitespace-formatted log entry
+       log => 'whitespace-formatted log entry
 ',                                             # trailing newline is preserved
        revision => '8',                        # integer
        date => '2004-02-24T17:01:44.108345Z',  # commit date