Merge branch 'maint'
authorJunio C Hamano <junkio@cox.net>
Sun, 3 Dec 2006 01:26:58 +0000 (17:26 -0800)
committerJunio C Hamano <junkio@cox.net>
Sun, 3 Dec 2006 01:26:58 +0000 (17:26 -0800)
* maint:
git-svn: avoid fetching files twice in the same revision

1  2 
git-svn.perl
diff --combined git-svn.perl
index 3891122d73c6f76f41f31cc6280f9d6c40581d62,b53273eaea77c80fbfc3e1791a5eeab16c1b4c7e..d0bd0bdeb8462f7ad4550b5fe406f4e0a1a0ee1c
@@@ -21,7 -21,6 +21,7 @@@ $ENV{TZ} = 'UTC'
  $ENV{LC_ALL} = 'C';
  $| = 1; # unbuffer STDOUT
  
 +sub fatal (@) { print STDERR $@; exit 1 }
  # If SVN:: library support is added, please make the dependencies
  # optional and preserve the capability to use the command-line client.
  # use eval { require SVN::... } to make it lazy load
@@@ -40,7 -39,7 +40,7 @@@ memoize('revisions_eq')
  memoize('cmt_metadata');
  memoize('get_commit_time');
  
 -my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
 +my ($SVN, $_use_lib);
  
  sub nag_lib {
        print STDERR <<EOF;
@@@ -60,7 -59,6 +60,7 @@@ nag_lib() unless $_use_lib
  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,
@@@ -68,9 -66,7 +68,9 @@@
        $_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);
 +      $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
 +      $_username, $_config_dir, $_no_auth_cache, $_xfer_delta,
 +      $_pager, $_color);
  my (@_branch_from, %tree_map, %users, %rusers, %equiv);
  my ($_svn_co_url_revs, $_svn_pg_peg_revs);
  my @repo_path_split_cache;
@@@ -83,9 -79,6 +83,9 @@@ my %fc_opts = ( 'no-ignore-externals' =
                'repack:i' => \$_repack,
                'no-metadata' => \$_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);
  
@@@ -124,12 -117,7 +124,12 @@@ my %cmd = 
                          'no-graft-copy' => \$_no_graft_copy } ],
        'multi-init' => [ \&multi_init,
                        'Initialize multiple trees (like git-svnimport)',
 -                      { %multi_opts, %fc_opts } ],
 +                      { %multi_opts, %init_opts,
 +                       'revision|r=i' => \$_revision,
 +                       'username=s' => \$_username,
 +                       'config-dir=s' => \$_config_dir,
 +                       'no-auth-cache' => \$_no_auth_cache,
 +                      } ],
        'multi-fetch' => [ \&multi_fetch,
                        'Fetch multiple trees (like git-svnimport)',
                        \%fc_opts ],
                          'show-commit' => \$_show_commit,
                          'non-recursive' => \$_non_recursive,
                          'authors-file|A=s' => \$_authors,
 +                        'color' => \$_color,
 +                        'pager=s' => \$_pager,
                        } ],
        'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
                        { 'message|m=s' => \$_message,
@@@ -391,7 -377,10 +391,7 @@@ sub fetch_cmd 
  sub fetch_lib {
        my (@parents) = @_;
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
 -      my $repo;
 -      ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
 -      $SVN_LOG ||= libsvn_connect($repo);
 -      $SVN ||= libsvn_connect($repo);
 +      $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) {
                        # performance sucks with it enabled, so it's much
                        # faster to fetch revision ranges instead of relying
                        # on the limiter.
 -                      libsvn_get_log($SVN_LOG, '/'.$SVN_PATH,
 +                      libsvn_get_log(libsvn_dup_ra($SVN), [''],
                                        $min, $max, 0, 1, 1,
                                sub {
                                        my $log_msg;
@@@ -535,6 -524,7 +535,6 @@@ sub commit_lib 
        my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
  
        my $repo;
 -      ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
        set_svn_commit_env();
        foreach my $c (@revs) {
                my $log_msg = get_commit_message($c, $commit_msg);
                # can't track down... (it's probably in the SVN code)
                defined(my $pid = open my $fh, '-|') or croak $!;
                if (!$pid) {
 -                      $SVN_LOG = libsvn_connect($repo);
 -                      $SVN = libsvn_connect($repo);
                        my $ed = SVN::Git::Editor->new(
                                        {       r => $r_last,
 -                                              ra => $SVN_LOG,
 +                                              ra => libsvn_dup_ra($SVN),
                                                c => $c,
 -                                              svn_path => $SVN_PATH
 +                                              svn_path => $SVN->{svn_path},
                                        },
                                        $SVN->get_commit_editor(
                                                $log_msg->{msg},
                                $no = 1;
                        }
                }
 -              close $fh or croak $?;
 +              close $fh or exit 1;
                if (! defined $r_new && ! defined $cmt_new) {
                        unless ($no) {
                                die "Failed to parse revision information\n";
@@@ -665,9 -657,10 +665,9 @@@ sub show_ignore_cmd 
  
  sub show_ignore_lib {
        my $repo;
 -      ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
 -      $SVN ||= libsvn_connect($repo);
 +      $SVN ||= libsvn_connect($SVN_URL);
        my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
 -      libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r);
 +      libsvn_traverse_ignore(\*STDOUT, $SVN->{svn_path}, $r);
  }
  
  sub graft_branches {
@@@ -768,17 -761,16 +768,17 @@@ sub show_log 
                }
        }
  
 +      config_pager();
        my $pid = open(my $log,'-|');
        defined $pid or croak $!;
        if (!$pid) {
                exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
        }
 -      setup_pager();
 +      run_pager();
        my (@k, $c, $d);
  
        while (<$log>) {
 -              if (/^commit ($sha1_short)/o) {
 +              if (/^${_esc_color}commit ($sha1_short)/o) {
                        my $cmt = $1;
                        if ($c && cmt_showable($c) && $c->{r} != $r_last) {
                                $r_last = $c->{r};
                        }
                        $d = undef;
                        $c = { c => $cmt };
 -              } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
 +              } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) {
                        get_author_info($c, $1, $2, $3);
 -              } elsif (/^(?:tree|parent|committer) /) {
 +              } elsif (/^${_esc_color}(?:tree|parent|committer) /) {
                        # ignore
 -              } elsif (/^:\d{6} \d{6} $sha1_short/o) {
 +              } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) {
                        push @{$c->{raw}}, $_;
 -              } elsif (/^[ACRMDT]\t/) {
 -                      # we could add $SVN_PATH here, but that requires
 +              } elsif (/^${_esc_color}[ACRMDT]\t/) {
 +                      # we could add $SVN->{svn_path} here, but that requires
                        # remote access at the moment (repo_path_split)...
 -                      s#^([ACRMDT])\t#   $1 #;
 +                      s#^(${_esc_color})([ACRMDT])\t#$1   $2 #;
                        push @{$c->{changed}}, $_;
 -              } elsif (/^diff /) {
 +              } elsif (/^${_esc_color}diff /) {
                        $d = 1;
                        push @{$c->{diff}}, $_;
                } elsif ($d) {
                        push @{$c->{diff}}, $_;
 -              } elsif (/^    (git-svn-id:.+)$/) {
 +              } elsif (/^${_esc_color}    (git-svn-id:.+)$/) {
                        ($c->{url}, $c->{r}, undef) = extract_metadata($1);
 -              } elsif (s/^    //) {
 +              } elsif (s/^${_esc_color}    //) {
                        push @{$c->{l}}, $_;
                }
        }
@@@ -860,7 -852,10 +860,7 @@@ sub commit_diff 
                $_message ||= get_commit_message($tb,
                                        "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
        }
 -      my $repo;
 -      ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
 -      $SVN_LOG ||= libsvn_connect($repo);
 -      $SVN ||= libsvn_connect($repo);
 +      $SVN ||= libsvn_connect($SVN_URL);
        if ($r eq 'HEAD') {
                $r = $SVN->get_latest_revnum;
        } elsif ($r !~ /^\d+$/) {
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
        my $rev_committed;
        my $ed = SVN::Git::Editor->new({        r => $r,
 -                                              ra => $SVN_LOG, c => $tb,
 -                                              svn_path => $SVN_PATH
 +                                              ra => libsvn_dup_ra($SVN),
 +                                              c => $tb,
 +                                              svn_path => $SVN->{svn_path}
                                        },
                                $SVN->get_commit_editor($_message,
                                        sub {
                                                print "Committed $_[0]\n";
                                        }, @lock)
                                );
 -      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;
 -      }
 +      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;
  }
@@@ -911,30 -902,12 +911,30 @@@ sub cmt_showable 
        return defined $c->{r};
  }
  
 +sub log_use_color {
 +      return 1 if $_color;
 +      my $dc;
 +      chomp($dc = `git-repo-config --get diff.color`);
 +      if ($dc eq 'auto') {
 +              if (-t *STDOUT || (defined $_pager &&
 +                  `git-repo-config --bool --get pager.color` !~ /^false/)) {
 +                      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 diff.color`);
 +      $dc eq 'true';
 +}
 +
  sub git_svn_log_cmd {
        my ($r_min, $r_max) = @_;
        my @cmd = (qw/git-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';
@@@ -1170,7 -1143,8 +1170,7 @@@ sub graft_file_copy_lib 
        my $tree_paths = $l_map->{$u};
        my $pfx = common_prefix([keys %$tree_paths]);
        my ($repo, $path) = repo_path_split($u.$pfx);
 -      $SVN_LOG ||= libsvn_connect($repo);
 -      $SVN ||= libsvn_connect($repo);
 +      $SVN = libsvn_connect($repo);
  
        my ($base, $head) = libsvn_parse_revision();
        my $inc = 1000;
        $SVN::Error::handler = \&libsvn_skip_unknown_revs;
        while (1) {
                my $pool = SVN::Pool->new;
 -              libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1,
 +              libsvn_get_log(libsvn_dup_ra($SVN), [$path],
 +                             $min, $max, 0, 2, 1,
                        sub {
                                libsvn_graft_file_copies($grafts, $tree_paths,
                                                        $path, @_);
@@@ -1290,9 -1263,13 +1290,9 @@@ sub repo_path_split 
                        return ($u, $full_url);
                }
        }
 -
        if ($_use_lib) {
                my $tmp = libsvn_connect($full_url);
 -              my $url = $tmp->get_repos_root;
 -              $full_url =~ s#^\Q$url\E/*##;
 -              push @repo_path_split_cache, qr/^(\Q$url\E)/;
 -              return ($url, $full_url);
 +              return ($tmp->{repos_root}, $tmp->{svn_path});
        } else {
                my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
                $path =~ s#^/+##;
@@@ -2561,18 -2538,14 +2561,18 @@@ sub tz_to_s_offset 
        return ($1 * 60) + ($tz * 3600);
  }
  
 -sub setup_pager { # translated to Perl from pager.c
 -      return unless (-t *STDOUT);
 -      my $pager = $ENV{PAGER};
 -      if (!defined $pager) {
 -              $pager = 'less';
 -      } elsif (length $pager == 0 || $pager eq 'cat') {
 -              return;
 +# 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 croak $!;
        if (!$pid) {
                return;
        }
        open STDIN, '<&', $rfd or croak $!;
 -      $ENV{LESS} ||= '-S';
 -      exec $pager or croak "Can't run pager: $!\n";;
 +      $ENV{LESS} ||= 'FRSX';
 +      exec $_pager or croak "Can't run pager: $! ($_pager)\n";
  }
  
  sub get_author_info {
@@@ -2707,184 -2680,29 +2707,184 @@@ sub libsvn_load 
                require SVN::Ra;
                require SVN::Delta;
                push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
 +              push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
 +              *SVN::Git::Fetcher::process_rm = *process_rm;
 +              *SVN::Git::Fetcher::safe_qx = *safe_qx;
                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::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;
                1;
        };
  }
  
 +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 "Authentication realm: $realm\n";
 +              }
 +              $cred->username($default_username);
 +      } else {
 +              _username_prompt($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_prompt {
 +      my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
 +      $may_save = undef if $_no_auth_cache;
 +      print "Error validating server certificate for '$realm':\n";
 +      if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
 +              print " - 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 " - The certificate hostname does not match.\n";
 +      }
 +      if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
 +              print " - The certificate is not yet valid.\n";
 +      }
 +      if ($failures & $SVN::Auth::SSL::EXPIRED) {
 +              print " - The certificate has expired.\n";
 +      }
 +      if ($failures & $SVN::Auth::SSL::OTHER) {
 +              print " - The certificate has an unknown error.\n";
 +      }
 +      printf( "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 $may_save ?
 +            "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
 +            "(R)eject or accept (t)emporarily? ";
 +      $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_prompt {
 +      my ($cred, $realm, $may_save, $pool) = @_;
 +      $may_save = undef if $_no_auth_cache;
 +      print "Client certificate filename: ";
 +      chomp(my $filename = <STDIN>);
 +      $cred->cert_file($filename);
 +      $cred->may_save($may_save);
 +      $SVN::_Core::SVN_NO_ERROR;
 +}
 +
 +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;
 +}
 +
 +sub _username_prompt {
 +      my ($cred, $realm, $may_save, $pool) = @_;
 +      $may_save = undef if $_no_auth_cache;
 +      if (defined $realm && length $realm) {
 +              print "Authentication realm: $realm\n";
 +      }
 +      my $username;
 +      if (defined $_username) {
 +              $username = $_username;
 +      } else {
 +              print "Username: ";
 +              chomp($username = <STDIN>);
 +      }
 +      $cred->username($username);
 +      $cred->may_save($may_save);
 +      $SVN::_Core::SVN_NO_ERROR;
 +}
 +
 +sub _read_password {
 +      my ($prompt, $realm) = @_;
 +      print $prompt;
 +      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 "\n";
 +      $password;
 +}
 +
  sub libsvn_connect {
        my ($url) = @_;
 -      my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
 -                        SVN::Client::get_ssl_server_trust_file_provider(),
 -                        SVN::Client::get_username_provider()]);
 -      my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
 -      return $s;
 +      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);
 +
 +      my $df = $ENV{GIT_SVN_DELTA_FETCH};
 +      if (defined $df) {
 +              $_xfer_delta = $df;
 +      } else {
 +              $_xfer_delta = ($url =~ m#^file://#) ? undef : 1;
 +      }
 +      $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;
 +}
 +
 +sub libsvn_dup_ra {
 +      my ($ra) = @_;
 +      SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url
 +                   auth auth_provider_callbacks repos_root svn_path/);
  }
  
  sub libsvn_get_file {
        my ($gui, $f, $rev, $chg) = @_;
 -      my $p = $f;
 -      if (length $SVN_PATH > 0) {
 -              return unless ($p =~ s#^\Q$SVN_PATH\E/##);
 -      }
 +      $f =~ s#^/##;
        print "\t$chg\t$f\n" unless $_q;
  
        my ($hash, $pid, $in, $out);
                waitpid $pid, 0;
                $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
        }
 -      print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!;
 +      print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!;
  }
  
  sub libsvn_log_entry {
  }
  
  sub process_rm {
 -      my ($gui, $last_commit, $f) = @_;
 -      $f =~ s#^\Q$SVN_PATH\E/?## or return;
 +      my ($gui, $last_commit, $f, $q) = @_;
        # remove entire directories.
        if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
                defined(my $pid = open my $ls, '-|') or croak $!;
                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;
                close $ls or croak $?;
        } else {
                print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
 +              print "\tD\t$f\n" unless $q;
        }
  }
  
  sub libsvn_fetch {
 +      $_xfer_delta ? libsvn_fetch_delta(@_) : libsvn_fetch_full(@_);
 +}
 +
 +sub libsvn_fetch_delta {
 +      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]);
 +}
 +
 +sub libsvn_fetch_full {
        my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
        open my $gui, '| git-update-index -z --index-info' or croak $!;
-       my @amr;
+       my %amr;
 +      my $p = $SVN->{svn_path};
        foreach my $f (keys %$paths) {
                my $m = $paths->{$f}->action();
 -              $f =~ s#^/+##;
 +              if (length $p) {
 +                      $f =~ s#^/\Q$p\E/##;
 +                      next if $f =~ m#^/#;
 +              } else {
 +                      $f =~ s#^/##;
 +              }
                if ($m =~ /^[DR]$/) {
 -                      print "\t$m\t$f\n" unless $_q;
 -                      process_rm($gui, $last_commit, $f);
 +                      process_rm($gui, $last_commit, $f, $_q);
                        next if $m eq 'D';
                        # 'R' can be file replacements, too, right?
                }
                my $t = $SVN->check_path($f, $rev, $pool);
                if ($t == $SVN::Node::file) {
                        if ($m =~ /^[AMR]$/) {
-                               push @amr, [ $m, $f ];
+                               $amr{$f} = $m;
                        } else {
                                die "Unrecognized action: $m, ($f r$rev)\n";
                        }
                        my @traversed = ();
                        libsvn_traverse($gui, '', $f, $rev, \@traversed);
                        foreach (@traversed) {
-                               push @amr, [ $m, $_ ]
+                               $amr{$_} = $m;
                        }
                }
                $pool->clear;
        }
-       foreach (@amr) {
-               libsvn_get_file($gui, $_->[1], $rev, $_->[0]);
+       foreach (keys %amr) {
+               libsvn_get_file($gui, $_, $rev, $amr{$_});
        }
        close $gui or croak $?;
        return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
@@@ -3080,9 -2871,9 +3080,9 @@@ sub libsvn_parse_revision 
  
  sub libsvn_traverse {
        my ($gui, $pfx, $path, $rev, $files) = @_;
 -      my $cwd = "$pfx/$path";
 +      my $cwd = length $pfx ? "$pfx/$path" : $path;
        my $pool = SVN::Pool->new;
 -      $cwd =~ s#^/+##g;
 +      $cwd =~ s#^\Q$SVN->{svn_path}\E##;
        my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
        foreach my $d (keys %$dirent) {
                my $t = $dirent->{$d}->kind;
@@@ -3106,7 -2897,7 +3106,7 @@@ sub libsvn_traverse_ignore 
        my $pool = SVN::Pool->new;
        my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
        my $p = $path;
 -      $p =~ s#^\Q$SVN_PATH\E/?##;
 +      $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;
@@@ -3133,8 -2924,8 +3133,8 @@@ sub revisions_eq 
        if ($_use_lib) {
                # 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, 1, 1, sub {$nr++}, $pool);
 +              libsvn_get_log($SVN, [$path], $r0, $r1,
 +                              0, 0, 1, sub {$nr++}, $pool);
                $pool->clear;
        } else {
                my ($url, undef) = repo_path_split($SVN_URL);
  
  sub libsvn_find_parent_branch {
        my ($paths, $rev, $author, $date, $msg) = @_;
 -      my $svn_path = '/'.$SVN_PATH;
 +      my $svn_path = '/'.$SVN->{svn_path};
  
        # look for a parent from another branch:
        my $i = $paths->{$svn_path} or return;
        $branch_from =~ s#^/##;
        my $l_map = {};
        read_url_paths_all($l_map, '', "$GIT_DIR/svn");
 -      my $url = $SVN->{url};
 +      my $url = $SVN->{repos_root};
        defined $l_map->{$url} or return;
        my $id = $l_map->{$url}->{$branch_from};
        if (!defined $id && $_follow_parent) {
                        $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
                        init_vars();
                        $SVN_URL = "$url/$branch_from";
 -                      $SVN_LOG = $SVN = undef;
 +                      $SVN = undef;
                        setup_git_svn();
                        # we can't assume SVN_URL exists at r+1:
                        $_revision = "0:$r";
                unlink $GIT_SVN_INDEX;
                print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
                sys(qw/git-read-tree/, $parent);
 -              return libsvn_fetch($parent, $paths, $rev,
 +              # I can't seem to get do_switch() to work correctly with
 +              # the SWIG interface (TypeError when passing switch_url...),
 +              # so we'll unconditionally bypass the delta interface here
 +              # for now
 +              return libsvn_fetch_full($parent, $paths, $rev,
                                        $author, $date, $msg);
        }
        print STDERR "Nope, branch point not imported or unknown\n";
  
  sub libsvn_get_log {
        my ($ra, @args) = @_;
 +      $args[4]-- if $args[4] && $_xfer_delta && ! $_follow_parent;
        if ($SVN::Core::VERSION le '1.2.0') {
                splice(@args, 3, 1);
        }
@@@ -3222,22 -3008,9 +3222,22 @@@ sub libsvn_new_tree 
                return $log_entry;
        }
        my ($paths, $rev, $author, $date, $msg) = @_;
 -      open my $gui, '| git-update-index -z --index-info' or croak $!;
 -      libsvn_traverse($gui, '', $SVN_PATH, $rev);
 -      close $gui or croak $?;
 +      if ($_xfer_delta) {
 +              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";
 +              }
 +      } else {
 +              open my $gui, '| git-update-index -z --index-info' or croak $!;
 +              libsvn_traverse($gui, '', $SVN->{svn_path}, $rev);
 +              close $gui or croak $?;
 +      }
        return libsvn_log_entry($rev, $author, $date, $msg);
  }
  
@@@ -3321,11 -3094,12 +3321,11 @@@ sub libsvn_commit_cb 
  
  sub libsvn_ls_fullurl {
        my $fullurl = shift;
 -      my ($repo, $path) = repo_path_split($fullurl);
 -      $SVN ||= libsvn_connect($repo);
 +      my $ra = libsvn_connect($fullurl);
        my @ret;
        my $pool = SVN::Pool->new;
 -      my ($dirent, undef, undef) = $SVN->get_dir($path,
 -                                              $SVN->get_latest_revnum, $pool);
 +      my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
 +      my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool);
        foreach my $d (keys %$dirent) {
                if ($dirent->{$d}->kind == $SVN::Node::dir) {
                        push @ret, "$d/"; # add '/' for compat with cli svn
@@@ -3346,9 -3120,8 +3346,9 @@@ sub libsvn_skip_unknown_revs 
        # 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 == 175002 || $errno == 160013) {
 +      if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
                return;
        }
        croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
@@@ -3407,142 -3180,6 +3407,142 @@@ sub copy_remote_ref 
                                "refs/remotes/$GIT_SVN on $origin\n";
        }
  }
 +package SVN::Git::Fetcher;
 +use vars qw/@ISA/;
 +use strict;
 +use warnings;
 +use Carp qw/croak/;
 +use IO::File qw//;
 +
 +# 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;
 +      open my $gui, '| git-update-index -z --index-info' or croak $!;
 +      $self->{gui} = $gui;
 +      $self->{c} = $git_svn->{c} if exists $git_svn->{c};
 +      $self->{q} = $git_svn->{q};
 +      require Digest::MD5;
 +      $self;
 +}
 +
 +sub delete_entry {
 +      my ($self, $path, $rev, $pb) = @_;
 +      process_rm($self->{gui}, $self->{c}, $path, $self->{q});
 +      undef;
 +}
 +
 +sub open_file {
 +      my ($self, $path, $pb, $rev) = @_;
 +      my ($mode, $blob) = (safe_qx('git-ls-tree',$self->{c},'--',$path)
 +                           =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
 +      { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
 +        pool => SVN::Pool->new, action => 'M' };
 +}
 +
 +sub add_file {
 +      my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
 +      { path => $path, mode_a => 100644, mode_b => 100644,
 +        pool => SVN::Pool->new, action => 'A' };
 +}
 +
 +sub change_file_prop {
 +      my ($self, $fb, $prop, $value) = @_;
 +      if ($prop eq 'svn:executable') {
 +              if ($fb->{mode_b} != 120000) {
 +                      $fb->{mode_b} = defined $value ? 100755 : 100644;
 +              }
 +      } elsif ($prop eq 'svn:special') {
 +              $fb->{mode_b} = defined $value ? 120000 : 100644;
 +      }
 +      undef;
 +}
 +
 +sub apply_textdelta {
 +      my ($self, $fb, $exp) = @_;
 +      my $fh = IO::File->new_tmpfile;
 +      $fh->autoflush(1);
 +      # $fh gets auto-closed() by SVN::TxDelta::apply(),
 +      # (but $base does not,) so dup() it for reading in close_file
 +      open my $dup, '<&', $fh or croak $!;
 +      my $base = IO::File->new_tmpfile;
 +      $base->autoflush(1);
 +      if ($fb->{blob}) {
 +              defined (my $pid = fork) or croak $!;
 +              if (!$pid) {
 +                      open STDOUT, '>&', $base or croak $!;
 +                      print STDOUT 'link ' if ($fb->{mode_a} == 120000);
 +                      exec qw/git-cat-file blob/, $fb->{blob} or croak $!;
 +              }
 +              waitpid $pid, 0;
 +              croak $? if $?;
 +
 +              if (defined $exp) {
 +                      seek $base, 0, 0 or croak $!;
 +                      my $md5 = Digest::MD5->new;
 +                      $md5->addfile($base);
 +                      my $got = $md5->hexdigest;
 +                      die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
 +                          "expected: $exp\n",
 +                          "     got: $got\n" if ($got ne $exp);
 +              }
 +      }
 +      seek $base, 0, 0 or croak $!;
 +      $fb->{fh} = $dup;
 +      $fb->{base} = $base;
 +      [ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ];
 +}
 +
 +sub close_file {
 +      my ($self, $fb, $exp) = @_;
 +      my $hash;
 +      my $path = $fb->{path};
 +      if (my $fh = $fb->{fh}) {
 +              seek($fh, 0, 0) or croak $!;
 +              my $md5 = Digest::MD5->new;
 +              $md5->addfile($fh);
 +              my $got = $md5->hexdigest;
 +              die "Checksum mismatch: $path\n",
 +                  "expected: $exp\n    got: $got\n" if ($got ne $exp);
 +              seek($fh, 0, 0) or croak $!;
 +              if ($fb->{mode_b} == 120000) {
 +                      read($fh, my $buf, 5) == 5 or croak $!;
 +                      $buf eq 'link ' or die "$path has mode 120000",
 +                                             "but is not a link\n";
 +              }
 +              defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
 +              if (!$pid) {
 +                      open STDIN, '<&', $fh or croak $!;
 +                      exec qw/git-hash-object -w --stdin/ or croak $!;
 +              }
 +              chomp($hash = do { local $/; <$out> });
 +              close $out or croak $!;
 +              close $fh or croak $!;
 +              $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
 +              close $fb->{base} or croak $!;
 +      } else {
 +              $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 $!;
 +      print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q};
 +      undef;
 +}
 +
 +sub abort_edit {
 +      my $self = shift;
 +      close $self->{gui};
 +      $self->SUPER::abort_edit(@_);
 +}
 +
 +sub close_edit {
 +      my $self = shift;
 +      close $self->{gui} or croak $!;
 +      $self->{git_commit_ok} = 1;
 +      $self->SUPER::close_edit(@_);
 +}
  
  package SVN::Git::Editor;
  use vars qw/@ISA/;
@@@ -3572,7 -3209,8 +3572,7 @@@ sub split_path 
  }
  
  sub repo_path {
 -      (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]"
 -                                      : $_[0]->{svn_path}
 +      (defined $_[1] && length $_[1]) ? $_[1] : ''
  }
  
  sub url_path {
@@@ -3604,9 -3242,10 +3604,9 @@@ sub rmdirs 
                exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
        }
        local $/ = "\0";
 -      my @svn_path = split m#/#, $self->{svn_path};
        while (<$fh>) {
                chomp;
 -              my @dn = (@svn_path, (split m#/#, $_));
 +              my @dn = split m#/#, $_;
                while (pop @dn) {
                        delete $rm->{join '/', @dn};
                }