git-svn: add 'clone' command, an alias for init + fetch
[gitweb.git] / git-svn.perl
index 7ffbf641392f398743337d602696c328cec80b79..2cc7c33381fe5394f08eb88e720e412ebe63ffb5 100755 (executable)
@@ -56,7 +56,7 @@ BEGIN
        $_template, $_shared,
        $_version, $_fetch_all,
        $_merge, $_strategy, $_dry_run,
-       $_prefix, $_no_checkout);
+       $_prefix, $_no_checkout, $_verbose);
 $Git::SVN::_follow_parent = 1;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
@@ -88,8 +88,11 @@ BEGIN
 my %cmd = (
        fetch => [ \&cmd_fetch, "Download new revisions from SVN",
                        { 'revision|r=s' => \$_revision,
-                         'all|a' => \$_fetch_all,
+                         'fetch-all|all' => \$_fetch_all,
                           %fc_opts } ],
+       clone => [ \&cmd_clone, "Initialize and fetch revisions",
+                       { 'revision|r=s' => \$_revision,
+                          %fc_opts, %init_opts } ],
        init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
                          \%init_opts ],
@@ -101,7 +104,9 @@ BEGIN
                     'Commit several diffs to merge with upstream',
                        { 'merge|m|M' => \$_merge,
                          'strategy|s=s' => \$_strategy,
+                         'verbose|v' => \$_verbose,
                          'dry-run|n' => \$_dry_run,
+                         'fetch-all|all' => \$_fetch_all,
                        %cmt_opts, %fc_opts } ],
        'set-tree' => [ \&cmd_set_tree,
                        "Set an SVN repository to a git tree-ish",
@@ -129,6 +134,12 @@ BEGIN
                          'color' => \$Git::SVN::Log::color,
                          'pager=s' => \$Git::SVN::Log::pager,
                        } ],
+       'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory",
+                       { 'merge|m|M' => \$_merge,
+                         'verbose|v' => \$_verbose,
+                         'strategy|s=s' => \$_strategy,
+                         'fetch-all|all' => \$_fetch_all,
+                         %fc_opts } ],
        'commit-diff' => [ \&cmd_commit_diff,
                           'Commit a diff between two trees',
                        { 'message|m=s' => \$_message,
@@ -159,7 +170,7 @@ BEGIN
 version() if $_version;
 usage(1) unless defined $cmd;
 load_authors() if $_authors;
-unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) {
+unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) {
        Git::SVN::Migration::migration_check();
 }
 Git::SVN::init_vars();
@@ -229,6 +240,22 @@ sub init_subdir {
        $ENV{GIT_DIR} = $repo_path . "/.git";
 }
 
+sub cmd_clone {
+       my ($url, $path) = @_;
+       if (!defined $path &&
+           (defined $_trunk || defined $_branches || defined $_tags) &&
+           $url !~ m#^[a-z\+]+://#) {
+               $path = $url;
+       }
+       warn "--path: $path\n" if defined $path;
+       $path = basename($url) if !defined $path || !length $path;
+       warn "++path: $path\n" if defined $path;
+       mkpath([$path]);
+       chdir $path or die "Couldn't chdir to $path\n";
+       cmd_init(@_);
+       Git::SVN::fetch_all($Git::SVN::default_repo_id);
+}
+
 sub cmd_init {
        if (defined $_trunk || defined $_branches || defined $_tags) {
                return cmd_multi_init(@_);
@@ -248,7 +275,7 @@ sub cmd_fetch {
        }
        my ($remote) = @_;
        if (@_ > 1) {
-               die "Usage: $0 fetch [--all|-a] [svn-remote]\n";
+               die "Usage: $0 fetch [--all] [svn-remote]\n";
        }
        $remote ||= $Git::SVN::default_repo_id;
        if ($_fetch_all) {
@@ -296,21 +323,12 @@ sub cmd_set_tree {
 sub cmd_dcommit {
        my $head = shift;
        $head ||= 'HEAD';
-       my ($url, $rev, $uuid);
-       my ($fh, $ctx) = command_output_pipe('rev-list', $head);
        my @refs;
-       my $c;
-       while (<$fh>) {
-               $c = $_;
-               chomp $c;
-               ($url, $rev, $uuid) = cmt_metadata($c);
-               last if (defined $url && defined $rev && defined $uuid);
-               unshift @refs, $c;
-       }
-       close $fh; # most likely breaking the pipe
+       my ($url, $rev, $uuid) = working_head_info($head, \@refs);
+       my $c = $refs[-1];
        unless (defined $url && defined $rev && defined $uuid) {
                die "Unable to determine upstream SVN information from ",
-                   "$head history:\n  $ctx\n";
+                   "$head history\n";
        }
        my $gs = Git::SVN->find_by_url($url);
        my $last_rev;
@@ -354,15 +372,13 @@ sub cmd_dcommit {
                     "now resync your SVN::Mirror repository.\n";
                return;
        }
-       $gs->fetch;
+       $_fetch_all ? $gs->fetch_all : $gs->fetch;
        # we always want to rebase against the current HEAD, not any
        # head that was passed to us
        my @diff = command('diff-tree', 'HEAD', $gs->refname, '--');
        my @finish;
        if (@diff) {
-               @finish = qw/rebase/;
-               push @finish, qw/--merge/ if $_merge;
-               push @finish, "--strategy=$_strategy" if $_strategy;
+               @finish = rebase_cmd();
                print STDERR "W: HEAD and ", $gs->refname, " differ, ",
                             "using @finish:\n", "@diff";
        } else {
@@ -374,6 +390,24 @@ sub cmd_dcommit {
        command_noisy(@finish, $gs->refname);
 }
 
+sub cmd_rebase {
+       command_noisy(qw/update-index --refresh/);
+       my $url = (working_head_info('HEAD'))[0];
+       if (!defined $url) {
+               die "Unable to determine upstream SVN information from ",
+                   "working tree history\n";
+       }
+
+       my $gs = Git::SVN->find_by_url($url);
+       if (command(qw/diff-index HEAD --/)) {
+               print STDERR "Cannot rebase with uncommited changes:\n";
+               command_noisy('status');
+               exit 1;
+       }
+       $_fetch_all ? $gs->fetch_all : $gs->fetch;
+       command_noisy(rebase_cmd(), $gs->refname);
+}
+
 sub cmd_show_ignore {
        my $gs = Git::SVN->new;
        my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
@@ -468,6 +502,14 @@ sub cmd_commit_diff {
 
 ########################### utility functions #########################
 
+sub rebase_cmd {
+       my @cmd = qw/rebase/;
+       push @cmd, '-v' if $_verbose;
+       push @cmd, qw/--merge/ if $_merge;
+       push @cmd, "--strategy=$_strategy" if $_strategy;
+       @cmd;
+}
+
 sub post_fetch_checkout {
        return if $_no_checkout;
        my $gs = $Git::SVN::_head or return;
@@ -687,6 +729,20 @@ sub cmt_metadata {
                command(qw/cat-file commit/, shift)))[-1]);
 }
 
+sub working_head_info {
+       my ($head, $refs) = @_;
+       my ($url, $rev, $uuid);
+       my ($fh, $ctx) = command_output_pipe('rev-list', $head);
+       while (<$fh>) {
+               chomp;
+               ($url, $rev, $uuid) = cmt_metadata($_);
+               last if (defined $url && defined $rev && defined $uuid);
+               unshift @$refs, $_ if $refs;
+       }
+       close $fh; # break the pipe
+       ($url, $rev, $uuid);
+}
+
 package Git::SVN;
 use strict;
 use warnings;
@@ -783,6 +839,12 @@ sub parse_revision_argument {
 
 sub fetch_all {
        my ($repo_id, $remotes) = @_;
+       if (ref $repo_id) {
+               my $gs = $repo_id;
+               $repo_id = undef;
+               $repo_id = $gs->{repo_id};
+       }
+       $remotes ||= read_all_remotes();
        my $remote = $remotes->{$repo_id} or
                     die "[svn-remote \"$repo_id\"] unknown\n";
        my $fetch = $remote->{fetch};
@@ -801,6 +863,8 @@ sub fetch_all {
                                         "svn-remote.$repo_id.${t}-maxRev") };
                if (defined $max_rev && ($max_rev < $base)) {
                        $base = $max_rev;
+               } elsif (!defined $max_rev) {
+                       $base = 0;
                }
        }
 
@@ -1023,10 +1087,7 @@ sub new {
        $self->{url} = command_oneline('config', '--get',
                                       "svn-remote.$repo_id.url") or
                   die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
-       if ((-z $self->db_path || ! -e $self->db_path) &&
-           ::verify_ref($self->refname.'^0')) {
-               $self->rebuild;
-       }
+       $self->rebuild;
        $self;
 }
 
@@ -1244,7 +1305,12 @@ sub get_fetch_range {
 
 sub tmp_config {
        my (@args) = @_;
-       my $config = "$ENV{GIT_DIR}/svn/config";
+       my $old_def_config = "$ENV{GIT_DIR}/svn/config";
+       my $config = "$ENV{GIT_DIR}/svn/.metadata";
+       if (-e $old_def_config && ! -e $config) {
+               rename $old_def_config, $config or
+                      die "Failed rename $old_def_config => $config: $!\n";
+       }
        my $old_config = $ENV{GIT_CONFIG};
        $ENV{GIT_CONFIG} = $config;
        $@ = undef;
@@ -1694,6 +1760,8 @@ sub set_tree {
 sub rebuild {
        my ($self) = @_;
        my $db_path = $self->db_path;
+       return if (-e $db_path && ! -z $db_path);
+       return unless ::verify_ref($self->refname.'^0');
        if (-f $self->{db_root}) {
                rename $self->{db_root}, $db_path or die
                     "rename $self->{db_root} => $db_path failed: $!\n";
@@ -1820,6 +1888,7 @@ sub rev_db_set {
 
 sub rev_db_max {
        my ($self) = @_;
+       $self->rebuild;
        my $db_path = $self->db_path;
        my @stat = stat $db_path or return 0;
        ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n";
@@ -3085,15 +3154,7 @@ sub git_svn_log_cmd {
                last;
        }
 
-       my $url;
-       my ($fh, $ctx) = command_output_pipe('rev-list', $head);
-       while (<$fh>) {
-               chomp;
-               $url = (::cmt_metadata($_))[0];
-               last if defined $url;
-       }
-       close $fh; # break the pipe
-
+       my $url = (::working_head_info($head))[0];
        my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new;
        my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
                   $gs->refname);