]> asedeno.scripts.mit.edu Git - git.git/blobdiff - git-svn.perl
git-svn log: handle unreachable revisions like "svn log"
[git.git] / git-svn.perl
index 22bb47b34d030ec4d1b3dc7cda05465559e0a09d..0ecf7a59234de98df01957e29a2c558f0015c594 100755 (executable)
@@ -252,7 +252,7 @@ Usage: $0 <command> [options] [arguments]\n
                next if $cmd && $cmd ne $_;
                next if /^multi-/; # don't show deprecated commands
                print $fd '  ',pack('A17',$_),$cmd{$_}->[1],"\n";
-               foreach (keys %{$cmd{$_}->[2]}) {
+               foreach (sort keys %{$cmd{$_}->[2]}) {
                        # mixed-case options are for .git/config only
                        next if /[A-Z]/ && /^[a-z]+$/i;
                        # prints out arguments as they should be passed:
@@ -390,6 +390,9 @@ sub cmd_set_tree {
 
 sub cmd_dcommit {
        my $head = shift;
+       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";
        $head ||= 'HEAD';
        my @refs;
        my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
@@ -406,7 +409,8 @@ sub cmd_dcommit {
                     "If these changes depend on each other, re-running ",
                     "without --no-rebase will be required."
        }
-       foreach my $d (@$linear_refs) {
+       while (1) {
+               my $d = shift @$linear_refs or last;
                unless (defined $last_rev) {
                        (undef, $last_rev, undef) = cmt_metadata("$d~1");
                        unless (defined $last_rev) {
@@ -421,6 +425,9 @@ sub cmd_dcommit {
                        my %ed_opts = ( r => $last_rev,
                                        log => get_commit_entry($d)->{log},
                                        ra => Git::SVN::Ra->new($gs->full_url),
+                                       config => SVN::Core::config_get_config(
+                                               $Git::SVN::Ra::config_dir
+                                       ),
                                        tree_a => "$d~1",
                                        tree_b => $d,
                                        editor_cb => sub {
@@ -439,14 +446,14 @@ sub cmd_dcommit {
 
                        # we always want to rebase against the current HEAD,
                        # not any head that was passed to us
-                       my @diff = command('diff-tree', 'HEAD',
+                       my @diff = command('diff-tree', $d,
                                           $gs->refname, '--');
                        my @finish;
                        if (@diff) {
                                @finish = rebase_cmd();
-                               print STDERR "W: HEAD and ", $gs->refname,
+                               print STDERR "W: $d and ", $gs->refname,
                                             " differ, using @finish:\n",
-                                            "@diff";
+                                            join("\n", @diff), "\n";
                        } else {
                                print "No changes between current HEAD and ",
                                      $gs->refname,
@@ -455,6 +462,45 @@ sub cmd_dcommit {
                                @finish = qw/reset --mixed/;
                        }
                        command_noisy(@finish, $gs->refname);
+                       if (@diff) {
+                               @refs = ();
+                               my ($url_, $rev_, $uuid_, $gs_) =
+                                             working_head_info($head, \@refs);
+                               my ($linear_refs_, $parents_) =
+                                             linearize_history($gs_, \@refs);
+                               if (scalar(@$linear_refs) !=
+                                   scalar(@$linear_refs_)) {
+                                       fatal "# of revisions changed ",
+                                         "\nbefore:\n",
+                                         join("\n", @$linear_refs),
+                                         "\n\nafter:\n",
+                                         join("\n", @$linear_refs_), "\n",
+                                         'If you are attempting to commit ',
+                                         "merges, try running:\n\t",
+                                         'git rebase --interactive',
+                                         '--preserve-merges ',
+                                         $gs->refname,
+                                         "\nBefore dcommitting";
+                               }
+                               if ($url_ ne $url) {
+                                       fatal "URL mismatch after rebase: ",
+                                             "$url_ != $url";
+                               }
+                               if ($uuid_ ne $uuid) {
+                                       fatal "uuid mismatch after rebase: ",
+                                             "$uuid_ != $uuid";
+                               }
+                               # remap parents
+                               my (%p, @l, $i);
+                               for ($i = 0; $i < scalar @$linear_refs; $i++) {
+                                       my $new = $linear_refs_->[$i] or next;
+                                       $p{$new} =
+                                               $parents->{$linear_refs->[$i]};
+                                       push @l, $new;
+                               }
+                               $parents = \%p;
+                               $linear_refs = \@l;
+                       }
                        $last_rev = $cmt_rev;
                }
        }
@@ -2353,10 +2399,15 @@ sub rev_db_get {
        $ret;
 }
 
+# Finds the first svn revision that exists on (if $eq_ok is true) or
+# before $rev for the current branch.  It will not search any lower
+# than $min_rev.  Returns the git commit hash and svn revision number
+# if found, else (undef, undef).
 sub find_rev_before {
-       my ($self, $rev, $eq_ok) = @_;
+       my ($self, $rev, $eq_ok, $min_rev) = @_;
        --$rev unless $eq_ok;
-       while ($rev > 0) {
+       $min_rev ||= 1;
+       while ($rev >= $min_rev) {
                if (my $c = $self->rev_db_get($rev)) {
                        return ($rev, $c);
                }
@@ -2365,6 +2416,23 @@ sub find_rev_before {
        return (undef, undef);
 }
 
+# Finds the first svn revision that exists on (if $eq_ok is true) or
+# after $rev for the current branch.  It will not search any higher
+# than $max_rev.  Returns the git commit hash and svn revision number
+# if found, else (undef, undef).
+sub find_rev_after {
+       my ($self, $rev, $eq_ok, $max_rev) = @_;
+       ++$rev unless $eq_ok;
+       $max_rev ||= $self->rev_db_max();
+       while ($rev <= $max_rev) {
+               if (my $c = $self->rev_db_get($rev)) {
+                       return ($rev, $c);
+               }
+               ++$rev;
+       }
+       return (undef, undef);
+}
+
 sub _new {
        my ($class, $repo_id, $ref_id, $path) = @_;
        unless (defined $repo_id && length $repo_id) {
@@ -3180,6 +3248,25 @@ sub _auth_providers () {
        ]
 }
 
+sub escape_uri_only {
+       my ($uri) = @_;
+       my @tmp;
+       foreach (split m{/}, $uri) {
+               s/([^\w.-])/sprintf("%%%02X",ord($1))/eg;
+               push @tmp, $_;
+       }
+       join('/', @tmp);
+}
+
+sub escape_url {
+       my ($url) = @_;
+       if ($url =~ m#^(https?)://([^/]+)(.*)$#) {
+               my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
+               $url = "$scheme://$domain$uri";
+       }
+       $url;
+}
+
 sub new {
        my ($class, $url) = @_;
        $url =~ s!/+$!!;
@@ -3212,10 +3299,11 @@ sub new {
                        $Git::SVN::Prompt::_no_auth_cache = 1;
                }
        } # no warnings 'once'
-       my $self = SVN::Ra->new(url => $url, auth => $baton,
+       my $self = SVN::Ra->new(url => escape_url($url), auth => $baton,
                              config => $config,
                              pool => SVN::Pool->new,
                              auth_provider_callbacks => $callbacks);
+       $self->{url} = $url;
        $self->{svn_path} = $url;
        $self->{repos_root} = $self->get_repos_root;
        $self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##;
@@ -3341,7 +3429,7 @@ sub gs_do_switch {
 
        my $full_url = $self->{url};
        my $old_url = $full_url;
-       $full_url .= "/$path" if length $path;
+       $full_url .= '/' . escape_uri_only($path) if length $path;
        my ($ra, $reparented);
        if ($old_url ne $full_url) {
                if ($old_url !~ m#^svn(\+ssh)?://#) {
@@ -3635,6 +3723,7 @@ package Git::SVN::Log;
 use strict;
 use warnings;
 use POSIX qw/strftime/;
+use constant commit_log_separator => ('-' x 72) . "\n";
 use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
             %rusers $show_commit $incremental/;
 my $l_fmt;
@@ -3728,19 +3817,19 @@ sub git_svn_log_cmd {
                        push @cmd, $c;
                }
        } elsif (defined $r_max) {
-               my ($c_min, $c_max);
-               $c_max = $gs->rev_db_get($r_max);
-               $c_min = $gs->rev_db_get($r_min);
-               if (defined $c_min && defined $c_max) {
-                       if ($r_max > $r_max) {
-                               push @cmd, "$c_min..$c_max";
-                       } else {
-                               push @cmd, "$c_max..$c_min";
-                       }
-               } elsif ($r_max > $r_min) {
-                       push @cmd, $c_max;
+               if ($r_max < $r_min) {
+                       ($r_min, $r_max) = ($r_max, $r_min);
+               }
+               my (undef, $c_max) = $gs->find_rev_before($r_max, 1, $r_min);
+               my (undef, $c_min) = $gs->find_rev_after($r_min, 1, $r_max);
+               # If there are no commits in the range, both $c_max and $c_min
+               # will be undefined.  If there is at least 1 commit in the
+               # range, both will be defined.
+               return () if !defined $c_min || !defined $c_max;
+               if ($c_min eq $c_max) {
+                       push @cmd, '--max-count=1', $c_min;
                } else {
-                       push @cmd, $c_min;
+                       push @cmd, '--boundary', "$c_min..$c_max";
                }
        }
        return (@cmd, @files);
@@ -3848,7 +3937,7 @@ sub show_commit_changed_paths {
 
 sub show_commit_normal {
        my ($c) = @_;
-       print '-' x72, "\nr$c->{r} | ";
+       print commit_log_separator, "r$c->{r} | ";
        print "$c->{c} | " if $show_commit;
        print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
                                 localtime($c->{t_utc})), ' | ';
@@ -3909,12 +3998,16 @@ sub cmd_show_log {
 
        config_pager();
        @args = git_svn_log_cmd($r_min, $r_max, @args);
+       if (!@args) {
+               print commit_log_separator unless $incremental || $oneline;
+               return;
+       }
        my $log = command_output_pipe(@args);
        run_pager();
        my (@k, $c, $d, $stat);
        my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
        while (<$log>) {
-               if (/^${esc_color}commit ($::sha1_short)/o) {
+               if (/^${esc_color}commit -?($::sha1_short)/o) {
                        my $cmt = $1;
                        if ($c && cmt_showable($c) && $c->{r} != $r_last) {
                                $r_last = $c->{r};
@@ -3957,14 +4050,12 @@ sub cmd_show_log {
                process_commit($c, $r_min, $r_max, \@k);
        }
        if (@k) {
-               my $swap = $r_max;
-               $r_max = $r_min;
-               $r_min = $swap;
+               ($r_min, $r_max) = ($r_max, $r_min);
                process_commit($_, $r_min, $r_max) foreach reverse @k;
        }
 out:
        close $log;
-       print '-' x72,"\n" unless $incremental || $oneline;
+       print commit_log_separator unless $incremental || $oneline;
 }
 
 package Git::SVN::Migration;