add tests of commit --squash
[gitweb.git] / git-svn.perl
index aceeca5a9487e47e281468d02ca10567b74217bf..18cfb2466d11aa28a795e3dabac70e236061fbe9 100755 (executable)
@@ -494,6 +494,7 @@ sub cmd_set_tree {
 
 sub cmd_dcommit {
        my $head = shift;
+       command_noisy(qw/update-index --refresh/);
        git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
                'Cannot dcommit with a dirty index.  Commit your changes first, '
                . "or stash them with `git stash'.\n";
@@ -963,6 +964,7 @@ sub cmd_multi_init {
        }
        do_git_init_db();
        if (defined $_trunk) {
+               $_trunk =~ s#^/+##;
                my $trunk_ref = 'refs/remotes/' . $_prefix . 'trunk';
                # try both old-style and new-style lookups:
                my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
@@ -1185,6 +1187,7 @@ sub cmd_reset {
                    "history\n";
        }
        my ($r, $c) = $gs->find_rev_before($target, not $_fetch_parent);
+       die "Cannot find SVN revision $target\n" unless defined($c);
        $gs->rev_map_set($r, $c, 'reset', $uuid);
        print "r$r = $c ($gs->{ref_id})\n";
 }
@@ -1510,7 +1513,8 @@ sub cmt_sha2rev_batch {
 
 sub working_head_info {
        my ($head, $refs) = @_;
-       my @args = ('log', '--no-color', '--first-parent', '--pretty=medium');
+       my @args = qw/log --no-color --no-decorate --first-parent
+                     --pretty=medium/;
        my ($fh, $ctx) = command_output_pipe(@args, $head);
        my $hash;
        my %max;
@@ -1817,6 +1821,7 @@ sub read_all_remotes {
                        die("svn-remote.$remote: remote ref '$remote_ref' "
                            . "must start with 'refs/'\n")
                                unless $remote_ref =~ m{^refs/};
+                       $local_ref = uri_decode($local_ref);
                        $r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
                        $r->{$remote}->{svm} = {} if $use_svm_props;
                } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) {
@@ -1829,6 +1834,7 @@ sub read_all_remotes {
                        die("svn-remote.$remote: remote ref '$remote_ref' ($t) "
                            . "must start with 'refs/'\n")
                                unless $remote_ref =~ m{^refs/};
+                       $local_ref = uri_decode($local_ref);
                        my $rs = {
                            t => $t,
                            remote => $remote,
@@ -2053,6 +2059,9 @@ sub new {
                         "\":$ref_id\$\" in config\n";
                ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
        }
+       $self->{path} =~ s{/+}{/}g;
+       $self->{path} =~ s{\A/}{};
+       $self->{path} =~ s{/\z}{};
        $self->{url} = command_oneline('config', '--get',
                                       "svn-remote.$repo_id.url") or
                   die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
@@ -2086,6 +2095,14 @@ sub refname {
        # .. becomes %2E%2E
        $refname =~ s{\.\.}{%2E%2E}g;
 
+       # trailing dots and .lock are not allowed
+       # .$ becomes %2E and .lock becomes %2Elock
+       $refname =~ s{\.(?=$|lock$)}{%2E};
+
+       # the sequence @{ is used to access the reflog
+       # @{ becomes %40{
+       $refname =~ s{\@\{}{%40\{}g;
+
        return $refname;
 }
 
@@ -2827,8 +2844,9 @@ sub mkemptydirs {
        foreach my $d (sort keys %empty_dirs) {
                $d = uri_decode($d);
                $d =~ s/$strip//;
+               next unless length($d);
                next if -d $d;
-               if (-e _) {
+               if (-e $d) {
                        warn "$d exists but is not a directory\n";
                } else {
                        print "creating empty directory: $d\n";
@@ -2942,18 +2960,29 @@ sub other_gs {
        my $gs = Git::SVN->find_by_url($new_url, $url, $branch_from);
        unless ($gs) {
                my $ref_id = $old_ref_id;
-               $ref_id =~ s/\@\d+$//;
+               $ref_id =~ s/\@\d+-*$//;
                $ref_id .= "\@$r";
                # just grow a tail if we're not unique enough :x
                $ref_id .= '-' while find_ref($ref_id);
-               print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
                my ($u, $p, $repo_id) = ($new_url, '', $ref_id);
                if ($u =~ s#^\Q$url\E(/|$)##) {
                        $p = $u;
                        $u = $url;
                        $repo_id = $self->{repo_id};
                }
-               $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+               while (1) {
+                       # It is possible to tag two different subdirectories at
+                       # the same revision.  If the url for an existing ref
+                       # does not match, we must either find a ref with a
+                       # matching url or create a new ref by growing a tail.
+                       $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1);
+                       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);
+                       $ref_id .= '-';
+               }
+               print STDERR "Initializing parent: $ref_id\n" unless $::_q > 1;
        }
        $gs
 }
@@ -3090,9 +3119,10 @@ sub _rev_list {
 sub check_cherry_pick {
        my $base = shift;
        my $tip = shift;
+       my $parents = shift;
        my @ranges = @_;
        my %commits = map { $_ => 1 }
-               _rev_list("--no-merges", $tip, "--not", $base);
+               _rev_list("--no-merges", $tip, "--not", $base, @$parents);
        for my $range ( @ranges ) {
                delete @commits{_rev_list($range)};
        }
@@ -3155,6 +3185,22 @@ sub has_no_changes {
                        LIST_CACHE => 'FAULT',
                ;
        }
+
+       sub unmemoize_svn_mergeinfo_functions {
+               return if not $memoized;
+               $memoized = 0;
+
+               Memoize::unmemoize 'lookup_svn_merge';
+               Memoize::unmemoize 'check_cherry_pick';
+               Memoize::unmemoize 'has_no_changes';
+       }
+}
+
+END {
+       # Force cache writeout explicitly instead of waiting for
+       # global destruction to avoid segfault in Storable:
+       # http://rt.cpan.org/Public/Bug/Display.html?id=36087
+       unmemoize_svn_mergeinfo_functions();
 }
 
 sub parents_exclude {
@@ -3252,6 +3298,7 @@ sub find_extra_svn_parents {
                # double check that there are no missing non-merge commits
                my (@incomplete) = check_cherry_pick(
                        $merge_base, $merge_tip,
+                       $parents,
                        @$ranges,
                       );
 
@@ -3605,6 +3652,7 @@ sub mkfile {
 
 sub rev_map_set {
        my ($self, $rev, $commit, $update_ref, $uuid) = @_;
+       defined $commit or die "missing arg3\n";
        length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
        my $db = $self->map_path($uuid);
        my $db_lock = "$db.lock";
@@ -3971,18 +4019,25 @@ sub username {
 
 sub _read_password {
        my ($prompt, $realm) = @_;
-       print STDERR $prompt;
-       STDERR->flush;
-       require Term::ReadKey;
-       Term::ReadKey::ReadMode('noecho');
        my $password = '';
-       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
-               last if $key =~ /[\012\015]/; # \n\r
-               $password .= $key;
+       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;
        }
-       Term::ReadKey::ReadMode('restore');
-       print STDERR "\n";
-       STDERR->flush;
        $password;
 }
 
@@ -3991,7 +4046,6 @@ package SVN::Git::Fetcher;
 use strict;
 use warnings;
 use Carp qw/croak/;
-use File::Temp qw/tempfile/;
 use IO::File qw//;
 use vars qw/$_ignore_regex/;
 
@@ -4013,6 +4067,7 @@ sub new {
        $self->{absent_dir} = {};
        $self->{absent_file} = {};
        $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
+       $self->{pathnameencoding} = Git::config('svn.pathnameencoding');
        $self;
 }
 
@@ -4096,6 +4151,10 @@ sub open_directory {
 
 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";
@@ -4484,6 +4543,10 @@ sub split_path {
 
 sub repo_path {
        my ($self, $path) = @_;
+       if (my $enc = $self->{pathnameencoding}) {
+               require Encode;
+               Encode::from_to($path, $enc, 'UTF-8');
+       }
        $self->{path_prefix}.(defined $path ? $path : '');
 }