l10n: Update git.pot (27 new, 1 removed messages)
[gitweb.git] / git-svn.perl
index 89f83fd27abe315804173a809b3c7ef00ead6527..3dc492d44d172a17e78e0aa583ec58e0c39226ad 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env perl
+#!/usr/bin/perl
 # Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
 # License: GPL v2 or later
 use 5.008;
 $Git::SVN::Ra::_log_window_size = 100;
 $Git::SVN::_minimize_url = 'unset';
 
-if (! exists $ENV{SVN_SSH}) {
-       if (exists $ENV{GIT_SSH}) {
-               $ENV{SVN_SSH} = $ENV{GIT_SSH};
-               if ($^O eq 'msys') {
-                       $ENV{SVN_SSH} =~ s/\\/\\\\/g;
-                       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
-               }
-       }
+if (! exists $ENV{SVN_SSH} && exists $ENV{GIT_SSH}) {
+       $ENV{SVN_SSH} = $ENV{GIT_SSH};
+}
+
+if (exists $ENV{SVN_SSH} && $^O eq 'msys') {
+       $ENV{SVN_SSH} =~ s/\\/\\\\/g;
+       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
 }
 
 $Git::SVN::Log::TZ = $ENV{TZ};
 $| = 1; # unbuffer STDOUT
 
 sub fatal (@) { print STDERR "@_\n"; exit 1 }
+
+# All SVN commands do it.  Otherwise we may die on SIGPIPE when the remote
+# repository decides to close the connection which we expect to be kept alive.
+$SIG{PIPE} = 'IGNORE';
+
+# Given a dot separated version number, "subtract" it from
+# the SVN::Core::VERSION; non-negaitive return means the SVN::Core
+# is at least at the version the caller asked for.
+sub compare_svn_version {
+       my (@ours) = split(/\./, $SVN::Core::VERSION);
+       my (@theirs) = split(/\./, $_[0]);
+       my ($i, $diff);
+
+       for ($i = 0; $i < @ours && $i < @theirs; $i++) {
+               $diff = $ours[$i] - $theirs[$i];
+               return $diff if ($diff);
+       }
+       return 1 if ($i < @ours);
+       return -1 if ($i < @theirs);
+       return 0;
+}
+
 sub _req_svn {
        require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
        require SVN::Ra;
        require SVN::Delta;
-       if ($SVN::Core::VERSION lt '1.1.0') {
+       if (::compare_svn_version('1.1.0') < 0) {
                fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
        }
 }
 my $can_compress = eval { require Compress::Zlib; 1};
 push @Git::SVN::Ra::ISA, 'SVN::Ra';
-push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
-push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
+push @Git::SVN::Editor::ISA, 'SVN::Delta::Editor';
 use Carp qw/croak/;
 use Digest::MD5;
 use IO::File qw//;
@@ -59,6 +79,8 @@ sub _req_svn {
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
 use IPC::Open3;
 use Git;
+use Git::SVN::Fetcher qw//;
+use Git::SVN::Prompt qw//;
 use Memoize;  # core since 5.8.0, Jul 2002
 
 BEGIN {
@@ -67,7 +89,7 @@ BEGIN
        foreach (qw/command command_oneline command_noisy command_output_pipe
                    command_input_pipe command_close_pipe
                    command_bidi_pipe command_close_bidi_pipe/) {
-               for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
+               for my $package ( qw(Git::SVN::Editor
                        Git::SVN::Migration Git::SVN::Log Git::SVN),
                        __PACKAGE__) {
                        *{"${package}::$_"} = \&{"Git::$_"};
@@ -85,15 +107,17 @@ BEGIN
        $_message, $_file, $_branch_dest,
        $_template, $_shared,
        $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
-       $_merge, $_strategy, $_dry_run, $_local,
+       $_merge, $_strategy, $_preserve_merges, $_dry_run, $_local,
        $_prefix, $_no_checkout, $_url, $_verbose,
-       $_git_format, $_commit_url, $_tag, $_merge_info);
+       $_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);
 $Git::SVN::_follow_parent = 1;
+$Git::SVN::Fetcher::_placeholder_filename = ".gitignore";
 $_q ||= 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
                     'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
-                    'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex );
+                    'ignore-paths=s' => \$Git::SVN::Fetcher::_ignore_regex,
+                    'ignore-refs=s' => \$Git::SVN::Ra::_ignore_refs_regex );
 my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'authors-file|A=s' => \$_authors,
                'authors-prog=s' => \$_authors_prog,
@@ -125,10 +149,10 @@ BEGIN
                  'rewrite-uuid=s' => sub { $icv{rewriteUUID} = $_[1] },
                   %remote_opts );
 my %cmt_opts = ( 'edit|e' => \$_edit,
-               'rmdir' => \$SVN::Git::Editor::_rmdir,
-               'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder,
-               'l=i' => \$SVN::Git::Editor::_rename_limit,
-               'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity
+               'rmdir' => \$Git::SVN::Editor::_rmdir,
+               'find-copies-harder' => \$Git::SVN::Editor::_find_copies_harder,
+               'l=i' => \$Git::SVN::Editor::_rename_limit,
+               'copy-similarity|C=i'=> \$Git::SVN::Editor::_cp_similarity
 );
 
 my %cmd = (
@@ -139,6 +163,10 @@ BEGIN
                           %fc_opts } ],
        clone => [ \&cmd_clone, "Initialize and fetch revisions",
                        { 'revision|r=s' => \$_revision,
+                         'preserve-empty-dirs' =>
+                               \$Git::SVN::Fetcher::_preserve_empty_dirs,
+                         'placeholder-filename=s' =>
+                               \$Git::SVN::Fetcher::_placeholder_filename,
                           %fc_opts, %init_opts } ],
        init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
@@ -158,6 +186,7 @@ BEGIN
                          'revision|r=i' => \$_revision,
                          'no-rebase' => \$_no_rebase,
                          'mergeinfo=s' => \$_merge_info,
+                         'interactive|i' => \$_interactive,
                        %cmt_opts, %fc_opts } ],
        branch => [ \&cmd_branch,
                    'Create a branch in the SVN repository',
@@ -227,6 +256,7 @@ BEGIN
                          'local|l' => \$_local,
                          'fetch-all|all' => \$_fetch_all,
                          'dry-run|n' => \$_dry_run,
+                         'preserve-merges|p' => \$_preserve_merges,
                          %fc_opts } ],
        'commit-diff' => [ \&cmd_commit_diff,
                           'Commit a diff between two trees',
@@ -251,6 +281,27 @@ BEGIN
                {} ],
 );
 
+use Term::ReadLine;
+package FakeTerm;
+sub new {
+       my ($class, $reason) = @_;
+       return bless \$reason, shift;
+}
+sub readline {
+       my $self = shift;
+       die "Cannot use readline on FakeTerm: $$self";
+}
+package main;
+
+my $term = eval {
+       $ENV{"GIT_SVN_NOTTY"}
+               ? new Term::ReadLine 'git-svn', \*STDIN, \*STDOUT
+               : new Term::ReadLine 'git-svn';
+};
+if ($@) {
+       $term = new FakeTerm "$@: going non-interactive";
+}
+
 my $cmd;
 for (my $i = 0; $i < @ARGV; $i++) {
        if (defined $cmd{$ARGV[$i]}) {
@@ -294,7 +345,7 @@ BEGIN
 if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) {
        Getopt::Long::Configure('pass_through');
 }
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+my $rv = GetOptions(%opts, 'h|H' => \$_help, 'version|V' => \$_version,
                     'minimize-connections' => \$Git::SVN::Migration::_minimize,
                     'id|i=s' => \$Git::SVN::default_ref_id,
                     'svn-remote|remote|R=s' => sub {
@@ -361,6 +412,36 @@ sub version {
        exit 0;
 }
 
+sub ask {
+       my ($prompt, %arg) = @_;
+       my $valid_re = $arg{valid_re};
+       my $default = $arg{default};
+       my $resp;
+       my $i = 0;
+
+       if ( !( defined($term->IN)
+            && defined( fileno($term->IN) )
+            && defined( $term->OUT )
+            && defined( fileno($term->OUT) ) ) ){
+               return defined($default) ? $default : undef;
+       }
+
+       while ($i++ < 10) {
+               $resp = $term->readline($prompt);
+               if (!defined $resp) { # EOF
+                       print "\n";
+                       return defined $default ? $default : undef;
+               }
+               if ($resp eq '' and defined $default) {
+                       return $default;
+               }
+               if (!defined $valid_re or $resp =~ /$valid_re/) {
+                       return $resp;
+               }
+       }
+       return undef;
+}
+
 sub do_git_init_db {
        unless (-d $ENV{GIT_DIR}) {
                my @init_db = ('init');
@@ -383,9 +464,18 @@ sub do_git_init_db {
                command_noisy('config', "$pfx.$i", $icv{$i});
                $set = $i;
        }
-       my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
-       command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
-               if defined $$ignore_regex;
+       my $ignore_paths_regex = \$Git::SVN::Fetcher::_ignore_regex;
+       command_noisy('config', "$pfx.ignore-paths", $$ignore_paths_regex)
+               if defined $$ignore_paths_regex;
+       my $ignore_refs_regex = \$Git::SVN::Ra::_ignore_refs_regex;
+       command_noisy('config', "$pfx.ignore-refs", $$ignore_refs_regex)
+               if defined $$ignore_refs_regex;
+
+       if (defined $Git::SVN::Fetcher::_preserve_empty_dirs) {
+               my $fname = \$Git::SVN::Fetcher::_placeholder_filename;
+               command_noisy('config', "$pfx.preserve-empty-dirs", 'true');
+               command_noisy('config', "$pfx.placeholder-filename", $$fname);
+       }
 }
 
 sub init_subdir {
@@ -497,6 +587,195 @@ sub cmd_set_tree {
        unlink $gs->{index};
 }
 
+sub split_merge_info_range {
+       my ($range) = @_;
+       if ($range =~ /(\d+)-(\d+)/) {
+               return (int($1), int($2));
+       } else {
+               return (int($range), int($range));
+       }
+}
+
+sub combine_ranges {
+       my ($in) = @_;
+
+       my @fnums = ();
+       my @arr = split(/,/, $in);
+       for my $element (@arr) {
+               my ($start, $end) = split_merge_info_range($element);
+               push @fnums, $start;
+       }
+
+       my @sorted = @arr [ sort {
+               $fnums[$a] <=> $fnums[$b]
+       } 0..$#arr ];
+
+       my @return = ();
+       my $last = -1;
+       my $first = -1;
+       for my $element (@sorted) {
+               my ($start, $end) = split_merge_info_range($element);
+
+               if ($last == -1) {
+                       $first = $start;
+                       $last = $end;
+                       next;
+               }
+               if ($start <= $last+1) {
+                       if ($end > $last) {
+                               $last = $end;
+                       }
+                       next;
+               }
+               if ($first == $last) {
+                       push @return, "$first";
+               } else {
+                       push @return, "$first-$last";
+               }
+               $first = $start;
+               $last = $end;
+       }
+
+       if ($first != -1) {
+               if ($first == $last) {
+                       push @return, "$first";
+               } else {
+                       push @return, "$first-$last";
+               }
+       }
+
+       return join(',', @return);
+}
+
+sub merge_revs_into_hash {
+       my ($hash, $minfo) = @_;
+       my @lines = split(' ', $minfo);
+
+       for my $line (@lines) {
+               my ($branchpath, $revs) = split(/:/, $line);
+
+               if (exists($hash->{$branchpath})) {
+                       # Merge the two revision sets
+                       my $combined = "$hash->{$branchpath},$revs";
+                       $hash->{$branchpath} = combine_ranges($combined);
+               } else {
+                       # Just do range combining for consolidation
+                       $hash->{$branchpath} = combine_ranges($revs);
+               }
+       }
+}
+
+sub merge_merge_info {
+       my ($mergeinfo_one, $mergeinfo_two) = @_;
+       my %result_hash = ();
+
+       merge_revs_into_hash(\%result_hash, $mergeinfo_one);
+       merge_revs_into_hash(\%result_hash, $mergeinfo_two);
+
+       my $result = '';
+       # Sort below is for consistency's sake
+       for my $branchname (sort keys(%result_hash)) {
+               my $revlist = $result_hash{$branchname};
+               $result .= "$branchname:$revlist\n"
+       }
+       return $result;
+}
+
+sub populate_merge_info {
+       my ($d, $gs, $uuid, $linear_refs, $rewritten_parent) = @_;
+
+       my %parentshash;
+       read_commit_parents(\%parentshash, $d);
+       my @parents = @{$parentshash{$d}};
+       if ($#parents > 0) {
+               # Merge commit
+               my $all_parents_ok = 1;
+               my $aggregate_mergeinfo = '';
+               my $rooturl = $gs->repos_root;
+
+               if (defined($rewritten_parent)) {
+                       # Replace first parent with newly-rewritten version
+                       shift @parents;
+                       unshift @parents, $rewritten_parent;
+               }
+
+               foreach my $parent (@parents) {
+                       my ($branchurl, $svnrev, $paruuid) =
+                               cmt_metadata($parent);
+
+                       unless (defined($svnrev)) {
+                               # Should have been caught be preflight check
+                               fatal "merge commit $d has ancestor $parent, but that change "
+                     ."does not have git-svn metadata!";
+                       }
+                       unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
+                               fatal "commit $parent git-svn metadata changed mid-run!";
+                       }
+                       my $branchpath = $1;
+
+                       my $ra = Git::SVN::Ra->new($branchurl);
+                       my (undef, undef, $props) =
+                               $ra->get_dir(canonicalize_path("."), $svnrev);
+                       my $par_mergeinfo = $props->{'svn:mergeinfo'};
+                       unless (defined $par_mergeinfo) {
+                               $par_mergeinfo = '';
+                       }
+                       # Merge previous mergeinfo values
+                       $aggregate_mergeinfo =
+                               merge_merge_info($aggregate_mergeinfo,
+                                                                $par_mergeinfo, 0);
+
+                       next if $parent eq $parents[0]; # Skip first parent
+                       # Add new changes being placed in tree by merge
+                       my @cmd = (qw/rev-list --reverse/,
+                                          $parent, qw/--not/);
+                       foreach my $par (@parents) {
+                               unless ($par eq $parent) {
+                                       push @cmd, $par;
+                               }
+                       }
+                       my @revsin = ();
+                       my ($revlist, $ctx) = command_output_pipe(@cmd);
+                       while (<$revlist>) {
+                               my $irev = $_;
+                               chomp $irev;
+                               my (undef, $csvnrev, undef) =
+                                       cmt_metadata($irev);
+                               unless (defined $csvnrev) {
+                                       # A child is missing SVN annotations...
+                                       # this might be OK, or might not be.
+                                       warn "W:child $irev is merged into revision "
+                                                ."$d but does not have git-svn metadata. "
+                                                ."This means git-svn cannot determine the "
+                                                ."svn revision numbers to place into the "
+                                                ."svn:mergeinfo property. You must ensure "
+                                                ."a branch is entirely committed to "
+                                                ."SVN before merging it in order for "
+                                                ."svn:mergeinfo population to function "
+                                                ."properly";
+                               }
+                               push @revsin, $csvnrev;
+                       }
+                       command_close_pipe($revlist, $ctx);
+
+                       last unless $all_parents_ok;
+
+                       # We now have a list of all SVN revnos which are
+                       # merged by this particular parent. Integrate them.
+                       next if $#revsin == -1;
+                       my $newmergeinfo = "$branchpath:" . join(',', @revsin);
+                       $aggregate_mergeinfo =
+                               merge_merge_info($aggregate_mergeinfo,
+                                                                $newmergeinfo, 1);
+               }
+               if ($all_parents_ok and $aggregate_mergeinfo) {
+                       return $aggregate_mergeinfo;
+               }
+       }
+
+       return undef;
+}
+
 sub cmd_dcommit {
        my $head = shift;
        command_noisy(qw/update-index --refresh/);
@@ -546,8 +825,88 @@ sub cmd_dcommit {
                     "If these changes depend on each other, re-running ",
                     "without --no-rebase may be required."
        }
+
+       if (defined $_interactive){
+               my $ask_default = "y";
+               foreach my $d (@$linear_refs){
+                       my ($fh, $ctx) = command_output_pipe(qw(show --summary), "$d");
+                       while (<$fh>){
+                               print $_;
+                       }
+                       command_close_pipe($fh, $ctx);
+                       $_ = ask("Commit this patch to SVN? ([y]es (default)|[n]o|[q]uit|[a]ll): ",
+                                valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
+                                default => $ask_default);
+                       die "Commit this patch reply required" unless defined $_;
+                       if (/^[nq]/i) {
+                               exit(0);
+                       } elsif (/^a/i) {
+                               last;
+                       }
+               }
+       }
+
        my $expect_url = $url;
+
+       my $push_merge_info = eval {
+               command_oneline(qw/config --get svn.pushmergeinfo/)
+               };
+       if (not defined($push_merge_info)
+                       or $push_merge_info eq "false"
+                       or $push_merge_info eq "no"
+                       or $push_merge_info eq "never") {
+               $push_merge_info = 0;
+       }
+
+       unless (defined($_merge_info) || ! $push_merge_info) {
+               # Preflight check of changes to ensure no issues with mergeinfo
+               # This includes check for uncommitted-to-SVN parents
+               # (other than the first parent, which we will handle),
+               # information from different SVN repos, and paths
+               # which are not underneath this repository root.
+               my $rooturl = $gs->repos_root;
+               foreach my $d (@$linear_refs) {
+                       my %parentshash;
+                       read_commit_parents(\%parentshash, $d);
+                       my @realparents = @{$parentshash{$d}};
+                       if ($#realparents > 0) {
+                               # Merge commit
+                               shift @realparents; # Remove/ignore first parent
+                               foreach my $parent (@realparents) {
+                                       my ($branchurl, $svnrev, $paruuid) = cmt_metadata($parent);
+                                       unless (defined $paruuid) {
+                                               # A parent is missing SVN annotations...
+                                               # abort the whole operation.
+                                               fatal "$parent is merged into revision $d, "
+                                                        ."but does not have git-svn metadata. "
+                                                        ."Either dcommit the branch or use a "
+                                                        ."local cherry-pick, FF merge, or rebase "
+                                                        ."instead of an explicit merge commit.";
+                                       }
+
+                                       unless ($paruuid eq $uuid) {
+                                               # Parent has SVN metadata from different repository
+                                               fatal "merge parent $parent for change $d has "
+                                                        ."git-svn uuid $paruuid, while current change "
+                                                        ."has uuid $uuid!";
+                                       }
+
+                                       unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
+                                               # This branch is very strange indeed.
+                                               fatal "merge parent $parent for $d is on branch "
+                                                        ."$branchurl, which is not under the "
+                                                        ."git-svn root $rooturl!";
+                                       }
+                               }
+                       }
+               }
+       }
+
+       my $rewritten_parent;
        Git::SVN::remove_username($expect_url);
+       if (defined($_merge_info)) {
+               $_merge_info =~ tr{ }{\n};
+       }
        while (1) {
                my $d = shift @$linear_refs or last;
                unless (defined $last_rev) {
@@ -561,6 +920,14 @@ sub cmd_dcommit {
                        print "diff-tree $d~1 $d\n";
                } else {
                        my $cmt_rev;
+
+                       unless (defined($_merge_info) || ! $push_merge_info) {
+                               $_merge_info = populate_merge_info($d, $gs,
+                                                            $uuid,
+                                                            $linear_refs,
+                                                            $rewritten_parent);
+                       }
+
                        my %ed_opts = ( r => $last_rev,
                                        log => get_commit_entry($d)->{log},
                                        ra => Git::SVN::Ra->new($url),
@@ -575,7 +942,7 @@ sub cmd_dcommit {
                                        },
                                        mergeinfo => $_merge_info,
                                        svn_path => '');
-                       if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+                       if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
                                print "No changes\n$d~1 == $d\n";
                        } elsif ($parents->{$d} && @{$parents->{$d}}) {
                                $gs->{inject_parents_dcommit}->{$cmt_rev} =
@@ -603,6 +970,9 @@ sub cmd_dcommit {
                                @finish = qw/reset --mixed/;
                        }
                        command_noisy(@finish, $gs->refname);
+
+                       $rewritten_parent = command_oneline(qw/rev-parse HEAD/);
+
                        if (@diff) {
                                @refs = ();
                                my ($url_, $rev_, $uuid_, $gs_) =
@@ -696,8 +1066,8 @@ sub cmd_branch {
                            " with the --destination argument.\n";
                }
                foreach my $g (@{$allglobs}) {
-                       # SVN::Git::Editor could probably be moved to Git.pm..
-                       my $re = SVN::Git::Editor::glob2pat($g->{path}->{left});
+                       # Git::SVN::Editor could probably be moved to Git.pm..
+                       my $re = Git::SVN::Editor::glob2pat($g->{path}->{left});
                        if ($_branch_dest =~ /$re/) {
                                $glob = $g;
                                last;
@@ -1055,7 +1425,7 @@ sub cmd_commit_diff {
                        tree_b => $tb,
                        editor_cb => sub { print "Committed r$_[0]\n" },
                        svn_path => $svn_path );
-       if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+       if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
                print "No changes\n$ta == $tb\n";
        }
 }
@@ -1123,7 +1493,7 @@ sub cmd_info {
        }
        ::_req_svn();
        $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" &&
-               ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir");
+               (::compare_svn_version('1.5.4') <= 0 || $file_type ne "dir");
        $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n";
 
        $result .= "Node Kind: " .
@@ -1224,6 +1594,7 @@ sub rebase_cmd {
        push @cmd, '-v' if $_verbose;
        push @cmd, qw/--merge/ if $_merge;
        push @cmd, "--strategy=$_strategy" if $_strategy;
+       push @cmd, "--preserve-merges" if $_preserve_merges;
        @cmd;
 }
 
@@ -1532,8 +1903,7 @@ sub cmt_sha2rev_batch {
 
 sub working_head_info {
        my ($head, $refs) = @_;
-       my @args = qw/log --no-color --no-decorate --first-parent
-                     --pretty=medium/;
+       my @args = qw/rev-list --first-parent --pretty=medium/;
        my ($fh, $ctx) = command_output_pipe(@args, $head);
        my $hash;
        my %max;
@@ -1683,8 +2053,10 @@ package Git::SVN;
 use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
 use IPC::Open3;
+use Time::Local;
 use Memoize;  # core since 5.8.0, Jul 2002
 use Memoize::Storable;
+use POSIX qw(:signal_h);
 
 my ($_gc_nr, $_gc_period);
 
@@ -1849,6 +2221,8 @@ sub read_all_remotes {
                        $r->{$1}->{url} = $2;
                } elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) {
                        $r->{$1}->{pushurl} = $2;
+               } elsif (m!^(.+)\.ignore-refs=\s*(.*)\s*$!) {
+                       $r->{$1}->{ignore_refs_regex} = $2;
                } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
                        my ($remote, $t, $local_ref, $remote_ref) =
                                                             ($1, $2, $3, $4);
@@ -1885,6 +2259,16 @@ sub read_all_remotes {
                }
        } keys %$r;
 
+       foreach my $remote (keys %$r) {
+               foreach ( grep { defined $_ }
+                         map { $r->{$remote}->{$_} } qw(branches tags) ) {
+                       foreach my $rs ( @$_ ) {
+                               $rs->{ignore_refs_regex} =
+                                   $r->{$remote}->{ignore_refs_regex};
+                       }
+               }
+       }
+
        $r;
 }
 
@@ -2773,7 +3157,7 @@ sub find_parent_branch {
                        # at the moment), so we can't rely on it
                        $self->{last_rev} = $r0;
                        $self->{last_commit} = $parent;
-                       $ed = SVN::Git::Fetcher->new($self, $gs->{path});
+                       $ed = Git::SVN::Fetcher->new($self, $gs->{path});
                        $gs->ra->gs_do_switch($r0, $rev, $gs,
                                              $self->full_url, $ed)
                          or die "SVN connection failed somewhere...\n";
@@ -2791,7 +3175,7 @@ sub find_parent_branch {
                } else {
                        print STDERR "Following parent with do_update\n"
                                     unless $::_q > 1;
-                       $ed = SVN::Git::Fetcher->new($self);
+                       $ed = Git::SVN::Fetcher->new($self);
                        $self->ra->gs_do_update($rev, $rev, $self, $ed)
                          or die "SVN connection failed somewhere...\n";
                }
@@ -2814,7 +3198,7 @@ sub do_fetch {
                        push @{$log_entry->{parents}}, $lc;
                        return $log_entry;
                }
-               $ed = SVN::Git::Fetcher->new($self);
+               $ed = Git::SVN::Fetcher->new($self);
                $last_rev = $self->{last_rev};
                $ed->{c} = $lc;
                @parents = ($lc);
@@ -2823,7 +3207,7 @@ sub do_fetch {
                if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
                        return $log_entry;
                }
-               $ed = SVN::Git::Fetcher->new($self);
+               $ed = Git::SVN::Fetcher->new($self);
        }
        unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) {
                die "SVN connection failed somewhere...\n";
@@ -2929,6 +3313,14 @@ sub get_untracked {
        \@out;
 }
 
+sub get_tz {
+       # some systmes don't handle or mishandle %z, so be creative.
+       my $t = shift || time;
+       my $gm = timelocal(gmtime($t));
+       my $sign = qw( + + - )[ $t <=> $gm ];
+       return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+}
+
 # parse_svn_date(DATE)
 # --------------------
 # Given a date (in UTC) from Subversion, return a string in the format
@@ -2961,8 +3353,7 @@ sub parse_svn_date {
                        delete $ENV{TZ};
                }
 
-               my $our_TZ =
-                   POSIX::strftime('%Z', $S, $M, $H, $d, $m - 1, $Y - 1900);
+               my $our_TZ = get_tz();
 
                # This converts $epoch_in_UTC into our local timezone.
                my ($sec, $min, $hour, $mday, $mon, $year,
@@ -3011,7 +3402,7 @@ sub other_gs {
                        my (undef, $max_commit) = $gs->rev_map_max(1);
                        last if (!$max_commit);
                        my ($url) = ::cmt_metadata($max_commit);
-                       last if ($url eq $gs->full_url);
+                       last if ($url eq $gs->metadata_url);
                        $ref_id .= '-';
                }
                print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
@@ -3521,7 +3912,7 @@ sub set_tree {
                        editor_cb => sub {
                               $self->set_tree_cb($log_entry, $tree, @_) },
                        svn_path => $self->{path} );
-       if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
+       if (!Git::SVN::Editor->new(\%ed_opts)->apply_diff) {
                print "No changes\nr$self->{last_rev} = $tree\n";
        }
 }
@@ -3562,7 +3953,7 @@ sub rebuild {
        my ($base_rev, $head) = ($partial ? $self->rev_map_max_norebuild(1) :
                (undef, undef));
        my ($log, $ctx) =
-           command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/,
+           command_output_pipe(qw/rev-list --pretty=raw --reverse/,
                                ($head ? "$head.." : "") . $self->refname,
                                '--');
        my $metadata_url = $self->metadata_url;
@@ -3694,11 +4085,14 @@ sub rev_map_set {
        length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
        my $db = $self->map_path($uuid);
        my $db_lock = "$db.lock";
-       my $sig;
+       my $sigmask;
        $update_ref ||= 0;
        if ($update_ref) {
-               $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
-                           $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
+               $sigmask = POSIX::SigSet->new();
+               my $signew = POSIX::SigSet->new(SIGINT, SIGHUP, SIGTERM,
+                       SIGALRM, SIGUSR1, SIGUSR2);
+               sigprocmask(SIG_BLOCK, $signew, $sigmask) or
+                       croak "Can't block signals: $!";
        }
        mkfile($db);
 
@@ -3737,9 +4131,8 @@ sub rev_map_set {
                                    "$db_lock => $db ($!)\n";
        delete $LOCKFILES{$db_lock};
        if ($update_ref) {
-               $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
-                           $SIG{USR1} = $SIG{USR2} = 'DEFAULT';
-               kill $sig, $$ if defined $sig;
+               sigprocmask(SIG_SETMASK, $sigmask) or
+                       croak "Can't restore signal mask: $!";
        }
 }
 
@@ -3935,525 +4328,7 @@ sub remove_username {
        $_[0] =~ s{^([^:]*://)[^@]+@}{$1};
 }
 
-package Git::SVN::Prompt;
-use strict;
-use warnings;
-require SVN::Core;
-use vars qw/$_no_auth_cache $_username/;
-
-sub simple {
-       my ($cred, $realm, $default_username, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       $default_username = $_username if defined $_username;
-       if (defined $default_username && length $default_username) {
-               if (defined $realm && length $realm) {
-                       print STDERR "Authentication realm: $realm\n";
-                       STDERR->flush;
-               }
-               $cred->username($default_username);
-       } else {
-               username($cred, $realm, $may_save, $pool);
-       }
-       $cred->password(_read_password("Password for '" .
-                                      $cred->username . "': ", $realm));
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub ssl_server_trust {
-       my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       print STDERR "Error validating server certificate for '$realm':\n";
-       {
-               no warnings 'once';
-               # All variables SVN::Auth::SSL::* are used only once,
-               # so we're shutting up Perl warnings about this.
-               if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
-                       print STDERR " - The certificate is not issued ",
-                           "by a trusted authority. Use the\n",
-                           "   fingerprint to validate ",
-                           "the certificate manually!\n";
-               }
-               if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
-                       print STDERR " - The certificate hostname ",
-                           "does not match.\n";
-               }
-               if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
-                       print STDERR " - The certificate is not yet valid.\n";
-               }
-               if ($failures & $SVN::Auth::SSL::EXPIRED) {
-                       print STDERR " - The certificate has expired.\n";
-               }
-               if ($failures & $SVN::Auth::SSL::OTHER) {
-                       print STDERR " - The certificate has ",
-                           "an unknown error.\n";
-               }
-       } # no warnings 'once'
-       printf STDERR
-               "Certificate information:\n".
-               " - Hostname: %s\n".
-               " - Valid: from %s until %s\n".
-               " - Issuer: %s\n".
-               " - Fingerprint: %s\n",
-               map $cert_info->$_, qw(hostname valid_from valid_until
-                                      issuer_dname fingerprint);
-       my $choice;
-prompt:
-       print STDERR $may_save ?
-             "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
-             "(R)eject or accept (t)emporarily? ";
-       STDERR->flush;
-       $choice = lc(substr(<STDIN> || 'R', 0, 1));
-       if ($choice =~ /^t$/i) {
-               $cred->may_save(undef);
-       } elsif ($choice =~ /^r$/i) {
-               return -1;
-       } elsif ($may_save && $choice =~ /^p$/i) {
-               $cred->may_save($may_save);
-       } else {
-               goto prompt;
-       }
-       $cred->accepted_failures($failures);
-       $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub ssl_client_cert {
-       my ($cred, $realm, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       print STDERR "Client certificate filename: ";
-       STDERR->flush;
-       chomp(my $filename = <STDIN>);
-       $cred->cert_file($filename);
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub ssl_client_cert_pw {
-       my ($cred, $realm, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       $cred->password(_read_password("Password: ", $realm));
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub username {
-       my ($cred, $realm, $may_save, $pool) = @_;
-       $may_save = undef if $_no_auth_cache;
-       if (defined $realm && length $realm) {
-               print STDERR "Authentication realm: $realm\n";
-       }
-       my $username;
-       if (defined $_username) {
-               $username = $_username;
-       } else {
-               print STDERR "Username: ";
-               STDERR->flush;
-               chomp($username = <STDIN>);
-       }
-       $cred->username($username);
-       $cred->may_save($may_save);
-       $SVN::_Core::SVN_NO_ERROR;
-}
-
-sub _read_password {
-       my ($prompt, $realm) = @_;
-       my $password = '';
-       if (exists $ENV{GIT_ASKPASS}) {
-               open(PH, "-|", $ENV{GIT_ASKPASS}, $prompt);
-               $password = <PH>;
-               $password =~ s/[\012\015]//; # \n\r
-               close(PH);
-       } else {
-               print STDERR $prompt;
-               STDERR->flush;
-               require Term::ReadKey;
-               Term::ReadKey::ReadMode('noecho');
-               while (defined(my $key = Term::ReadKey::ReadKey(0))) {
-                       last if $key =~ /[\012\015]/; # \n\r
-                       $password .= $key;
-               }
-               Term::ReadKey::ReadMode('restore');
-               print STDERR "\n";
-               STDERR->flush;
-       }
-       $password;
-}
-
-package SVN::Git::Fetcher;
-use vars qw/@ISA/;
-use strict;
-use warnings;
-use Carp qw/croak/;
-use IO::File qw//;
-use vars qw/$_ignore_regex/;
-
-# file baton members: path, mode_a, mode_b, pool, fh, blob, base
-sub new {
-       my ($class, $git_svn, $switch_path) = @_;
-       my $self = SVN::Delta::Editor->new;
-       bless $self, $class;
-       if (exists $git_svn->{last_commit}) {
-               $self->{c} = $git_svn->{last_commit};
-               $self->{empty_symlinks} =
-                                 _mark_empty_symlinks($git_svn, $switch_path);
-       }
-       $self->{ignore_regex} = eval { command_oneline('config', '--get',
-                            "svn-remote.$git_svn->{repo_id}.ignore-paths") };
-       $self->{empty} = {};
-       $self->{dir_prop} = {};
-       $self->{file_prop} = {};
-       $self->{absent_dir} = {};
-       $self->{absent_file} = {};
-       $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
-       $self->{pathnameencoding} = Git::config('svn.pathnameencoding');
-       $self;
-}
-
-# this uses the Ra object, so it must be called before do_{switch,update},
-# not inside them (when the Git::SVN::Fetcher object is passed) to
-# do_{switch,update}
-sub _mark_empty_symlinks {
-       my ($git_svn, $switch_path) = @_;
-       my $bool = Git::config_bool('svn.brokenSymlinkWorkaround');
-       return {} if (!defined($bool)) || (defined($bool) && ! $bool);
-
-       my %ret;
-       my ($rev, $cmt) = $git_svn->last_rev_commit;
-       return {} unless ($rev && $cmt);
-
-       # allow the warning to be printed for each revision we fetch to
-       # ensure the user sees it.  The user can also disable the workaround
-       # on the repository even while git svn is running and the next
-       # revision fetched will skip this expensive function.
-       my $printed_warning;
-       chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);
-       my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);
-       local $/ = "\0";
-       my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path};
-       $pfx .= '/' if length($pfx);
-       while (<$ls>) {
-               chomp;
-               s/\A100644 blob $empty_blob\t//o or next;
-               unless ($printed_warning) {
-                       print STDERR "Scanning for empty symlinks, ",
-                                    "this may take a while if you have ",
-                                    "many empty files\n",
-                                    "You may disable this with `",
-                                    "git config svn.brokenSymlinkWorkaround ",
-                                    "false'.\n",
-                                    "This may be done in a different ",
-                                    "terminal without restarting ",
-                                    "git svn\n";
-                       $printed_warning = 1;
-               }
-               my $path = $_;
-               my (undef, $props) =
-                              $git_svn->ra->get_file($pfx.$path, $rev, undef);
-               if ($props->{'svn:special'}) {
-                       $ret{$path} = 1;
-               }
-       }
-       command_close_pipe($ls, $ctx);
-       \%ret;
-}
-
-# returns true if a given path is inside a ".git" directory
-sub in_dot_git {
-       $_[0] =~ m{(?:^|/)\.git(?:/|$)};
-}
-
-# return value: 0 -- don't ignore, 1 -- ignore
-sub is_path_ignored {
-       my ($self, $path) = @_;
-       return 1 if in_dot_git($path);
-       return 1 if defined($self->{ignore_regex}) &&
-                   $path =~ m!$self->{ignore_regex}!;
-       return 0 unless defined($_ignore_regex);
-       return 1 if $path =~ m!$_ignore_regex!o;
-       return 0;
-}
-
-sub set_path_strip {
-       my ($self, $path) = @_;
-       $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
-}
-
-sub open_root {
-       { path => '' };
-}
-
-sub open_directory {
-       my ($self, $path, $pb, $rev) = @_;
-       { path => $path };
-}
-
-sub git_path {
-       my ($self, $path) = @_;
-       if (my $enc = $self->{pathnameencoding}) {
-               require Encode;
-               Encode::from_to($path, 'UTF-8', $enc);
-       }
-       if ($self->{path_strip}) {
-               $path =~ s!$self->{path_strip}!! or
-                 die "Failed to strip path '$path' ($self->{path_strip})\n";
-       }
-       $path;
-}
-
-sub delete_entry {
-       my ($self, $path, $rev, $pb) = @_;
-       return undef if $self->is_path_ignored($path);
-
-       my $gpath = $self->git_path($path);
-       return undef if ($gpath eq '');
-
-       # remove entire directories.
-       my ($tree) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
-                        =~ /\A040000 tree ([a-f\d]{40})\t\Q$gpath\E\0/);
-       if ($tree) {
-               my ($ls, $ctx) = command_output_pipe(qw/ls-tree
-                                                    -r --name-only -z/,
-                                                    $tree);
-               local $/ = "\0";
-               while (<$ls>) {
-                       chomp;
-                       my $rmpath = "$gpath/$_";
-                       $self->{gii}->remove($rmpath);
-                       print "\tD\t$rmpath\n" unless $::_q;
-               }
-               print "\tD\t$gpath/\n" unless $::_q;
-               command_close_pipe($ls, $ctx);
-       } else {
-               $self->{gii}->remove($gpath);
-               print "\tD\t$gpath\n" unless $::_q;
-       }
-       $self->{empty}->{$path} = 0;
-       undef;
-}
-
-sub open_file {
-       my ($self, $path, $pb, $rev) = @_;
-       my ($mode, $blob);
-
-       goto out if $self->is_path_ignored($path);
-
-       my $gpath = $self->git_path($path);
-       ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
-                            =~ /\A(\d{6}) blob ([a-f\d]{40})\t\Q$gpath\E\0/);
-       unless (defined $mode && defined $blob) {
-               die "$path was not found in commit $self->{c} (r$rev)\n";
-       }
-       if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {
-               $mode = '120000';
-       }
-out:
-       { 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) = @_;
-       my $mode;
-
-       if (!$self->is_path_ignored($path)) {
-               my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
-               delete $self->{empty}->{$dir};
-               $mode = '100644';
-       }
-       { path => $path, mode_a => $mode, mode_b => $mode,
-         pool => SVN::Pool->new, action => 'A' };
-}
-
-sub add_directory {
-       my ($self, $path, $cp_path, $cp_rev) = @_;
-       goto out if $self->is_path_ignored($path);
-       my $gpath = $self->git_path($path);
-       if ($gpath eq '') {
-               my ($ls, $ctx) = command_output_pipe(qw/ls-tree
-                                                    -r --name-only -z/,
-                                                    $self->{c});
-               local $/ = "\0";
-               while (<$ls>) {
-                       chomp;
-                       $self->{gii}->remove($_);
-                       print "\tD\t$_\n" unless $::_q;
-               }
-               command_close_pipe($ls, $ctx);
-               $self->{empty}->{$path} = 0;
-       }
-       my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
-       delete $self->{empty}->{$dir};
-       $self->{empty}->{$path} = 1;
-out:
-       { path => $path };
-}
-
-sub change_dir_prop {
-       my ($self, $db, $prop, $value) = @_;
-       return undef if $self->is_path_ignored($db->{path});
-       $self->{dir_prop}->{$db->{path}} ||= {};
-       $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
-       undef;
-}
-
-sub absent_directory {
-       my ($self, $path, $pb) = @_;
-       return undef if $self->is_path_ignored($path);
-       $self->{absent_dir}->{$pb->{path}} ||= [];
-       push @{$self->{absent_dir}->{$pb->{path}}}, $path;
-       undef;
-}
-
-sub absent_file {
-       my ($self, $path, $pb) = @_;
-       return undef if $self->is_path_ignored($path);
-       $self->{absent_file}->{$pb->{path}} ||= [];
-       push @{$self->{absent_file}->{$pb->{path}}}, $path;
-       undef;
-}
-
-sub change_file_prop {
-       my ($self, $fb, $prop, $value) = @_;
-       return undef if $self->is_path_ignored($fb->{path});
-       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;
-       } else {
-               $self->{file_prop}->{$fb->{path}} ||= {};
-               $self->{file_prop}->{$fb->{path}}->{$prop} = $value;
-       }
-       undef;
-}
-
-sub apply_textdelta {
-       my ($self, $fb, $exp) = @_;
-       return undef if $self->is_path_ignored($fb->{path});
-       my $fh = $::_repository->temp_acquire('svn_delta');
-       # $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 = $::_repository->temp_acquire('git_blob');
-
-       if ($fb->{blob}) {
-               my ($base_is_link, $size);
-
-               if ($fb->{mode_a} eq '120000' &&
-                   ! $self->{empty_symlinks}->{$fb->{path}}) {
-                       print $base 'link ' or die "print $!\n";
-                       $base_is_link = 1;
-               }
-       retry:
-               $size = $::_repository->cat_blob($fb->{blob}, $base);
-               die "Failed to read object $fb->{blob}" if ($size < 0);
-
-               if (defined $exp) {
-                       seek $base, 0, 0 or croak $!;
-                       my $got = ::md5sum($base);
-                       if ($got ne $exp) {
-                               my $err = "Checksum mismatch: ".
-                                      "$fb->{path} $fb->{blob}\n" .
-                                      "expected: $exp\n" .
-                                      "     got: $got\n";
-                               if ($base_is_link) {
-                                       warn $err,
-                                            "Retrying... (possibly ",
-                                            "a bad symlink from SVN)\n";
-                                       $::_repository->temp_reset($base);
-                                       $base_is_link = 0;
-                                       goto retry;
-                               }
-                               die $err;
-                       }
-               }
-       }
-       seek $base, 0, 0 or croak $!;
-       $fb->{fh} = $fh;
-       $fb->{base} = $base;
-       [ SVN::TxDelta::apply($base, $dup, undef, $fb->{path}, $fb->{pool}) ];
-}
-
-sub close_file {
-       my ($self, $fb, $exp) = @_;
-       return undef if $self->is_path_ignored($fb->{path});
-
-       my $hash;
-       my $path = $self->git_path($fb->{path});
-       if (my $fh = $fb->{fh}) {
-               if (defined $exp) {
-                       seek($fh, 0, 0) or croak $!;
-                       my $got = ::md5sum($fh);
-                       if ($got ne $exp) {
-                               die "Checksum mismatch: $path\n",
-                                   "expected: $exp\n    got: $got\n";
-                       }
-               }
-               if ($fb->{mode_b} == 120000) {
-                       sysseek($fh, 0, 0) or croak $!;
-                       my $rd = sysread($fh, my $buf, 5);
-
-                       if (!defined $rd) {
-                               croak "sysread: $!\n";
-                       } elsif ($rd == 0) {
-                               warn "$path has mode 120000",
-                                    " but it points to nothing\n",
-                                    "converting to an empty file with mode",
-                                    " 100644\n";
-                               $fb->{mode_b} = '100644';
-                       } elsif ($buf ne 'link ') {
-                               warn "$path has mode 120000",
-                                    " but is not a link\n";
-                       } else {
-                               my $tmp_fh = $::_repository->temp_acquire(
-                                       'svn_hash');
-                               my $res;
-                               while ($res = sysread($fh, my $str, 1024)) {
-                                       my $out = syswrite($tmp_fh, $str, $res);
-                                       defined($out) && $out == $res
-                                               or croak("write ",
-                                                       Git::temp_path($tmp_fh),
-                                                       ": $!\n");
-                               }
-                               defined $res or croak $!;
-
-                               ($fh, $tmp_fh) = ($tmp_fh, $fh);
-                               Git::temp_release($tmp_fh, 1);
-                       }
-               }
-
-               $hash = $::_repository->hash_and_insert_object(
-                               Git::temp_path($fh));
-               $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
-
-               Git::temp_release($fb->{base}, 1);
-               Git::temp_release($fh, 1);
-       } else {
-               $hash = $fb->{blob} or die "no blob information\n";
-       }
-       $fb->{pool}->clear;
-       $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;
-       print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $::_q;
-       undef;
-}
-
-sub abort_edit {
-       my $self = shift;
-       $self->{nr} = $self->{gii}->{nr};
-       delete $self->{gii};
-       $self->SUPER::abort_edit(@_);
-}
-
-sub close_edit {
-       my $self = shift;
-       $self->{git_commit_ok} = 1;
-       $self->{nr} = $self->{gii}->{nr};
-       delete $self->{gii};
-       $self->SUPER::close_edit(@_);
-}
-
-package SVN::Git::Editor;
+package Git::SVN::Editor;
 use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
 use strict;
 use warnings;
@@ -4643,7 +4518,7 @@ sub rmdirs {
 }
 
 sub open_or_add_dir {
-       my ($self, $full_path, $baton) = @_;
+       my ($self, $full_path, $baton, $deletions) = @_;
        my $t = $self->{types}->{$full_path};
        if (!defined $t) {
                die "$full_path not known in r$self->{r} or we have a bug!\n";
@@ -4652,7 +4527,7 @@ sub open_or_add_dir {
                no warnings 'once';
                # SVN::Node::none and SVN::Node::file are used only once,
                # so we're shutting up Perl's warnings about them.
-               if ($t == $SVN::Node::none) {
+               if ($t == $SVN::Node::none || defined($deletions->{$full_path})) {
                        return $self->add_directory($full_path, $baton,
                            undef, -1, $self->{pool});
                } elsif ($t == $SVN::Node::dir) {
@@ -4667,17 +4542,18 @@ sub open_or_add_dir {
 }
 
 sub ensure_path {
-       my ($self, $path) = @_;
+       my ($self, $path, $deletions) = @_;
        my $bat = $self->{bat};
        my $repo_path = $self->repo_path($path);
        return $bat->{''} unless (length $repo_path);
+
        my @p = split m#/+#, $repo_path;
        my $c = shift @p;
-       $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
+       $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''}, $deletions);
        while (@p) {
                my $c0 = $c;
                $c .= '/' . shift @p;
-               $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0});
+               $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{$c0}, $deletions);
        }
        return $bat->{$c};
 }
@@ -4734,9 +4610,9 @@ sub apply_autoprops {
 }
 
 sub A {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                        undef, -1);
        print "\tA\t$m->{file_b}\n" unless $::_q;
@@ -4746,9 +4622,9 @@ sub A {
 }
 
 sub C {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        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;
@@ -4765,9 +4641,9 @@ sub delete_entry {
 }
 
 sub R {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        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;
@@ -4776,14 +4652,14 @@ sub R {
        $self->close_file($fbat,undef,$self->{pool});
 
        ($dir, $file) = split_path($m->{file_a});
-       $pbat = $self->ensure_path($dir);
+       $pbat = $self->ensure_path($dir, $deletions);
        $self->delete_entry($m->{file_a}, $pbat);
 }
 
 sub M {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        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;
@@ -4853,9 +4729,9 @@ sub chg_file {
 }
 
 sub D {
-       my ($self, $m) = @_;
+       my ($self, $m, $deletions) = @_;
        my ($dir, $file) = split_path($m->{file_b});
-       my $pbat = $self->ensure_path($dir);
+       my $pbat = $self->ensure_path($dir, $deletions);
        print "\tD\t$m->{file_b}\n" unless $::_q;
        $self->delete_entry($m->{file_b}, $pbat);
 }
@@ -4887,11 +4763,19 @@ sub DESTROY {
 sub apply_diff {
        my ($self) = @_;
        my $mods = $self->{mods};
-       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+       my %o = ( D => 0, C => 1, R => 2, A => 3, M => 4, T => 5 );
+       my %deletions;
+
+       foreach my $m (@$mods) {
+               if ($m->{chg} eq "D") {
+                       $deletions{$m->{file_b}} = 1;
+               }
+       }
+
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
                my $f = $m->{chg};
                if (defined $o{$f}) {
-                       $self->$f($m);
+                       $self->$f($m, \%deletions);
                } else {
                        fatal("Invalid change type: $f");
                }
@@ -4902,7 +4786,7 @@ sub apply_diff {
                                       $self->{mergeinfo});
        }
        $self->rmdirs if $_rmdir;
-       if (@$mods == 0) {
+       if (@$mods == 0 && !defined($self->{mergeinfo})) {
                $self->abort_edit;
        } else {
                $self->close_edit;
@@ -4911,7 +4795,7 @@ sub apply_diff {
 }
 
 package Git::SVN::Ra;
-use vars qw/@ISA $config_dir $_log_window_size/;
+use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;
 use strict;
 use warnings;
 my ($ra_invalid, $can_do_switch, %ignored_err, $RA);
@@ -4933,7 +4817,7 @@ BEGIN
 }
 
 sub _auth_providers () {
-       [
+       my @rv = (
          SVN::Client::get_simple_provider(),
          SVN::Client::get_ssl_server_trust_file_provider(),
          SVN::Client::get_simple_prompt_provider(
@@ -4949,7 +4833,23 @@ ()
            \&Git::SVN::Prompt::ssl_server_trust),
          SVN::Client::get_username_prompt_provider(
            \&Git::SVN::Prompt::username, 2)
-       ]
+       );
+
+       # earlier 1.6.x versions would segfault, and <= 1.5.x didn't have
+       # this function
+       if (::compare_svn_version('1.6.15') >= 0) {
+               my $config = SVN::Core::config_get_config($config_dir);
+               my ($p, @a);
+               # config_get_config returns all config files from
+               # ~/.subversion, auth_get_platform_specific_client_providers
+               # just wants the config "file".
+               @a = ($config->{'config'}, undef);
+               $p = SVN::Core::auth_get_platform_specific_client_providers(@a);
+               # Insert the return value from
+               # auth_get_platform_specific_providers
+               unshift @rv, @$p;
+       }
+       \@rv;
 }
 
 sub escape_uri_only {
@@ -5096,7 +4996,7 @@ sub get_log {
        # drop it.  Therefore, the receiver callback passed to it
        # is made aware of this limitation by being wrapped if
        # the limit passed to is being wrapped.
-       if ($SVN::Core::VERSION le '1.2.0') {
+       if (::compare_svn_version('1.2.0') <= 0) {
                my $limit = splice(@args, 3, 1);
                if ($limit > 0) {
                        my $receiver = pop @args;
@@ -5128,7 +5028,8 @@ sub trees_match {
 
 sub get_commit_editor {
        my ($self, $log, $cb, $pool) = @_;
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
+
+       my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef, 0) : ();
        $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
 }
 
@@ -5146,7 +5047,7 @@ sub gs_do_update {
        my (@pc) = split m#/#, $path;
        my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
                                        1, $editor, $pool);
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+       my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
 
        # Since we can't rely on svn_ra_reparent being available, we'll
        # just have to do some magic with set_path to make it so
@@ -5196,7 +5097,7 @@ sub gs_do_switch {
        $ra ||= $self;
        $url_b = escape_url($url_b);
        my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
-       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+       my @lock = (::compare_svn_version('1.2.0') >= 0) ? (undef) : ();
        $reporter->set_path('', $rev_a, 0, @lock, $pool);
        $reporter->finish_report($pool);
 
@@ -5369,6 +5270,17 @@ sub get_dir_globbed {
        @finalents;
 }
 
+# return value: 0 -- don't ignore, 1 -- ignore
+sub is_ref_ignored {
+       my ($g, $p) = @_;
+       my $refname = $g->{ref}->full_path($p);
+       return 1 if defined($g->{ignore_refs_regex}) &&
+                   $refname =~ m!$g->{ignore_refs_regex}!;
+       return 0 unless defined($_ignore_refs_regex);
+       return 1 if $refname =~ m!$_ignore_refs_regex!o;
+       return 0;
+}
+
 sub match_globs {
        my ($self, $exists, $paths, $globs, $r) = @_;
 
@@ -5405,6 +5317,7 @@ sub match_globs {
                        next unless /$g->{path}->{regex}/;
                        my $p = $1;
                        my $pathname = $g->{path}->full_path($p);
+                       next if is_ref_ignored($g, $p);
                        next if $exists->{$pathname};
                        next if ($self->check_path($pathname, $r) !=
                                 $SVN::Node::dir);
@@ -5495,7 +5408,6 @@ package Git::SVN::Log;
 use strict;
 use warnings;
 use POSIX qw/strftime/;
-use Time::Local;
 use constant commit_log_separator => ('-' x 72) . "\n";
 use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
             %rusers $show_commit $incremental/;
@@ -5605,11 +5517,8 @@ sub run_pager {
 }
 
 sub format_svn_date {
-       # some systmes don't handle or mishandle %z, so be creative.
        my $t = shift || time;
-       my $gm = timelocal(gmtime($t));
-       my $sign = qw( + + - )[ $t <=> $gm ];
-       my $gmoff = sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
+       my $gmoff = Git::SVN::get_tz($t);
        return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
 }