Merge branch 'jk/maint-gitweb-test-use-sane-perl'
authorJunio C Hamano <gitster@pobox.com>
Wed, 2 May 2012 20:53:40 +0000 (13:53 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 2 May 2012 20:53:40 +0000 (13:53 -0700)
When using a Perl script on a system where "perl" found on user's $PATH
could be ancient or otherwise broken, we allow builders to specify the
path to a good copy of Perl with $PERL_PATH. The gitweb test forgot to
use that Perl when running its test.

By Jeff King (1) and Zbigniew Jędrzejewski-Szmek (1)
* jk/maint-gitweb-test-use-sane-perl:
Consistently use perl from /usr/bin/ for scripts
t/gitweb-lib: use $PERL_PATH to run gitweb

1  2 
git-svn.perl
t/gitweb-lib.sh
diff --combined git-svn.perl
index 427da9e7a1a10ad9949eaa0c97d7fd0aa3436c3e,40a823876b0ce9fc96f7cd094590d07057d8c79c..9bec80829891549589b3e502eaa2a8c74b58d523
@@@ -1,4 -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;
@@@ -22,13 -22,14 +22,13 @@@ $Git::SVN::default_ref_id = $ENV{GIT_SV
  $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};
@@@ -36,11 -37,6 +36,11 @@@ $ENV{TZ} = 'UTC'
  $| = 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';
 +
  sub _req_svn {
        require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
        require SVN::Ra;
@@@ -91,15 -87,13 +91,15 @@@ my ($_stdin, $_help, $_edit
        $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
        $_merge, $_strategy, $_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;
 +$SVN::Git::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' => \$SVN::Git::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,
@@@ -145,10 -139,6 +145,10 @@@ my %cmd = 
                           %fc_opts } ],
        clone => [ \&cmd_clone, "Initialize and fetch revisions",
                        { 'revision|r=s' => \$_revision,
 +                        'preserve-empty-dirs' =>
 +                              \$SVN::Git::Fetcher::_preserve_empty_dirs,
 +                        'placeholder-filename=s' =>
 +                              \$SVN::Git::Fetcher::_placeholder_filename,
                           %fc_opts, %init_opts } ],
        init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
                          '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',
                {} ],
  );
  
 +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]}) {
@@@ -326,7 -294,7 +326,7 @@@ read_git_config(\%opts)
  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 {
@@@ -393,36 -361,6 +393,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');
                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 = \$SVN::Git::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 $SVN::Git::Fetcher::_preserve_empty_dirs) {
 +              my $fname = \$SVN::Git::Fetcher::_placeholder_filename;
 +              command_noisy('config', "$pfx.preserve-empty-dirs", 'true');
 +              command_noisy('config', "$pfx.placeholder-filename", $$fname);
 +      }
  }
  
  sub init_subdir {
@@@ -568,195 -497,6 +568,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/);
                     "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) {
                        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),
                                @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_) =
@@@ -1883,7 -1532,8 +1883,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;
@@@ -2033,10 -1683,8 +2033,10 @@@ use Carp qw/croak/
  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);
  
@@@ -2201,8 -1849,6 +2201,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);
                }
        } 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;
  }
  
@@@ -3293,14 -2929,6 +3293,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
@@@ -3333,7 -2961,8 +3333,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,
@@@ -3382,7 -3011,7 +3382,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;
@@@ -3933,7 -3562,7 +3933,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;
@@@ -4065,14 -3694,11 +4065,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);
  
                                    "$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: $!";
        }
  }
  
@@@ -4453,13 -4080,12 +4453,13 @@@ sub _read_password 
  }
  
  package SVN::Git::Fetcher;
 -use vars qw/@ISA/;
 +use vars qw/@ISA $_ignore_regex $_preserve_empty_dirs $_placeholder_filename
 +            @deleted_gpath %added_placeholder $repo_id/;
  use strict;
  use warnings;
  use Carp qw/croak/;
 +use File::Basename qw/dirname/;
  use IO::File qw//;
 -use vars qw/$_ignore_regex/;
  
  # file baton members: path, mode_a, mode_b, pool, fh, blob, base
  sub new {
                $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") };
 +
 +      # some options are read globally, but can be overridden locally
 +      # per [svn-remote "..."] section.  Command-line options will *NOT*
 +      # override options set in an [svn-remote "..."] section
 +      $repo_id = $git_svn->{repo_id};
 +      my $k = "svn-remote.$repo_id.ignore-paths";
 +      my $v = eval { command_oneline('config', '--get', $k) };
 +      $self->{ignore_regex} = $v;
 +
 +      $k = "svn-remote.$repo_id.preserve-empty-dirs";
 +      $v = eval { command_oneline('config', '--get', '--bool', $k) };
 +      if ($v && $v eq 'true') {
 +              $_preserve_empty_dirs = 1;
 +              $k = "svn-remote.$repo_id.placeholder-filename";
 +              $v = eval { command_oneline('config', '--get', $k) };
 +              $_placeholder_filename = $v;
 +      }
 +
 +      # Load the list of placeholder files added during previous invocations.
 +      $k = "svn-remote.$repo_id.added-placeholder";
 +      $v = eval { command_oneline('config', '--get-all', $k) };
 +      if ($_preserve_empty_dirs && $v) {
 +              # command() prints errors to stderr, so we only call it if
 +              # command_oneline() succeeded.
 +              my @v = command('config', '--get-all', $k);
 +              $added_placeholder{ dirname($_) } = $_ foreach @v;
 +      }
 +
        $self->{empty} = {};
        $self->{dir_prop} = {};
        $self->{file_prop} = {};
@@@ -4627,8 -4227,6 +4627,8 @@@ sub delete_entry 
                $self->{gii}->remove($gpath);
                print "\tD\t$gpath\n" unless $::_q;
        }
 +      # Don't add to @deleted_gpath if we're deleting a placeholder file.
 +      push @deleted_gpath, $gpath unless $added_placeholder{dirname($path)};
        $self->{empty}->{$path} = 0;
        undef;
  }
@@@ -4661,15 -4259,7 +4661,15 @@@ sub add_file 
                my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
                delete $self->{empty}->{$dir};
                $mode = '100644';
 +
 +              if ($added_placeholder{$dir}) {
 +                      # Remove our placeholder file, if we created one.
 +                      delete_entry($self, $added_placeholder{$dir})
 +                              unless $path eq $added_placeholder{$dir};
 +                      delete $added_placeholder{$dir}
 +              }
        }
 +
        { path => $path, mode_a => $mode, mode_b => $mode,
          pool => SVN::Pool->new, action => 'A' };
  }
@@@ -4687,7 -4277,6 +4687,7 @@@ sub add_directory 
                        chomp;
                        $self->{gii}->remove($_);
                        print "\tD\t$_\n" unless $::_q;
 +                      push @deleted_gpath, $gpath;
                }
                command_close_pipe($ls, $ctx);
                $self->{empty}->{$path} = 0;
        my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
        delete $self->{empty}->{$dir};
        $self->{empty}->{$path} = 1;
 +
 +      if ($added_placeholder{$dir}) {
 +              # Remove our placeholder file, if we created one.
 +              delete_entry($self, $added_placeholder{$dir});
 +              delete $added_placeholder{$dir}
 +      }
 +
  out:
        { path => $path };
  }
@@@ -4865,96 -4447,12 +4865,96 @@@ sub abort_edit 
  
  sub close_edit {
        my $self = shift;
 +
 +      if ($_preserve_empty_dirs) {
 +              my @empty_dirs;
 +
 +              # Any entry flagged as empty that also has an associated
 +              # dir_prop represents a newly created empty directory.
 +              foreach my $i (keys %{$self->{empty}}) {
 +                      push @empty_dirs, $i if exists $self->{dir_prop}->{$i};
 +              }
 +
 +              # Search for directories that have become empty due subsequent
 +              # file deletes.
 +              push @empty_dirs, $self->find_empty_directories();
 +
 +              # Finally, add a placeholder file to each empty directory.
 +              $self->add_placeholder_file($_) foreach (@empty_dirs);
 +
 +              $self->stash_placeholder_list();
 +      }
 +
        $self->{git_commit_ok} = 1;
        $self->{nr} = $self->{gii}->{nr};
        delete $self->{gii};
        $self->SUPER::close_edit(@_);
  }
  
 +sub find_empty_directories {
 +      my ($self) = @_;
 +      my @empty_dirs;
 +      my %dirs = map { dirname($_) => 1 } @deleted_gpath;
 +
 +      foreach my $dir (sort keys %dirs) {
 +              next if $dir eq ".";
 +
 +              # If there have been any additions to this directory, there is
 +              # no reason to check if it is empty.
 +              my $skip_added = 0;
 +              foreach my $t (qw/dir_prop file_prop/) {
 +                      foreach my $path (keys %{ $self->{$t} }) {
 +                              if (exists $self->{$t}->{dirname($path)}) {
 +                                      $skip_added = 1;
 +                                      last;
 +                              }
 +                      }
 +                      last if $skip_added;
 +              }
 +              next if $skip_added;
 +
 +              # Use `git ls-tree` to get the filenames of this directory
 +              # that existed prior to this particular commit.
 +              my $ls = command('ls-tree', '-z', '--name-only',
 +                               $self->{c}, "$dir/");
 +              my %files = map { $_ => 1 } split(/\0/, $ls);
 +
 +              # Remove the filenames that were deleted during this commit.
 +              delete $files{$_} foreach (@deleted_gpath);
 +
 +              # Report the directory if there are no filenames left.
 +              push @empty_dirs, $dir unless (scalar %files);
 +      }
 +      @empty_dirs;
 +}
 +
 +sub add_placeholder_file {
 +      my ($self, $dir) = @_;
 +      my $path = "$dir/$_placeholder_filename";
 +      my $gpath = $self->git_path($path);
 +
 +      my $fh = $::_repository->temp_acquire($gpath);
 +      my $hash = $::_repository->hash_and_insert_object(Git::temp_path($fh));
 +      Git::temp_release($fh, 1);
 +      $self->{gii}->update('100644', $hash, $gpath) or croak $!;
 +
 +      # The directory should no longer be considered empty.
 +      delete $self->{empty}->{$dir} if exists $self->{empty}->{$dir};
 +
 +      # Keep track of any placeholder files we create.
 +      $added_placeholder{$dir} = $path;
 +}
 +
 +sub stash_placeholder_list {
 +      my ($self) = @_;
 +      my $k = "svn-remote.$repo_id.added-placeholder";
 +      my $v = eval { command_oneline('config', '--get-all', $k) };
 +      command_noisy('config', '--unset-all', $k) if $v;
 +      foreach (values %added_placeholder) {
 +              command_noisy('config', '--add', $k, $_);
 +      }
 +}
 +
  package SVN::Git::Editor;
  use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
  use strict;
@@@ -5145,7 -4643,7 +5145,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";
                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) {
  }
  
  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};
  }
@@@ -5237,9 -4734,9 +5237,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;
  }
  
  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;
@@@ -5268,9 -4765,9 +5268,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;
        $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;
@@@ -5356,9 -4853,9 +5356,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);
  }
@@@ -5390,19 -4887,11 +5390,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");
                }
                                       $self->{mergeinfo});
        }
        $self->rmdirs if $_rmdir;
 -      if (@$mods == 0) {
 +      if (@$mods == 0 && !defined($self->{mergeinfo})) {
                $self->abort_edit;
        } else {
                $self->close_edit;
  }
  
  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);
@@@ -5444,7 -4933,7 +5444,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(
            \&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 ($SVN::Core::VERSION gt '1.6.12') {
 +              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 {
@@@ -5896,17 -5369,6 +5896,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) = @_;
  
                        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);
@@@ -6034,6 -5495,7 +6034,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/;
@@@ -6143,8 -5605,11 +6143,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));
  }
  
diff --combined t/gitweb-lib.sh
index 21d11d6c2d65982d94f933b2a673b744cbc2e28a,f5fd940ff9bc2ffa5c900aca5ea41fbc851bc517..ae2dc4604f708d320d88d4f99ca9d575d88b6124
@@@ -16,7 -16,6 +16,7 @@@ our \$projectroot = "$safe_pwd"
  our \$project_maxdepth = 8;
  our \$home_link_str = 'projects';
  our \$site_name = '[localhost]';
 +our \$site_html_head_string = '';
  our \$site_header = '';
  our \$site_footer = '';
  our \$home_text = 'indextext.html';
@@@ -69,7 -68,7 +69,7 @@@ gitweb_run () 
        # written to web server logs, so we are not interested in that:
        # we are interested only in properly formatted errors/warnings
        rm -f gitweb.log &&
-       perl -- "$SCRIPT_NAME" \
+       "$PERL_PATH" -- "$SCRIPT_NAME" \
                >gitweb.output 2>gitweb.log &&
        perl -w -e '
                open O, ">gitweb.headers";