git-svn: add a 'rebase' command
authorEric Wong <normalperson@yhbt.net>
Fri, 16 Feb 2007 11:22:40 +0000 (03:22 -0800)
committerEric Wong <normalperson@yhbt.net>
Fri, 23 Feb 2007 08:57:13 +0000 (00:57 -0800)
This works similarly to 'svn update' or 'git pull' except that
it preserves linear history with 'git rebase' instead of 'git
merge' for ease of dcommit-ing with git-svn.

While we're at it, put the working_head_info() logic
into its own function and allow --fetch-all/--all for
dcommit and rebase (which will fetch all refs in the
current [svn-remote] instead of just the working one).

Note that the '-a' switch (short for --fetch-all/--all) has been
removed as it conflicts with the non-svn 'git fetch'

Signed-off-by: Eric Wong <normalperson@yhbt.net>
git-svn.perl

index 7ffbf64..eca08bd 100755 (executable)
@@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit,
        $_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,7 +88,7 @@ my %cmt_opts = ( 'edit|e' => \$_edit,
 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 } ],
        init => [ \&cmd_init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
@@ -101,7 +101,9 @@ my %cmd = (
                     '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 +131,12 @@ my %cmd = (
                          '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,
@@ -248,7 +256,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 +304,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 +353,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 +371,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 +483,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 +710,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 +820,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};
@@ -3085,15 +3128,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);