git-svn: add a 'rebase' command

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>
This commit is contained in:
Eric Wong 2007-02-16 03:22:40 -08:00
parent 1e889ef36c
commit 905f8b7dfc

View File

@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit,
$_template, $_shared, $_template, $_shared,
$_version, $_fetch_all, $_version, $_fetch_all,
$_merge, $_strategy, $_dry_run, $_merge, $_strategy, $_dry_run,
$_prefix, $_no_checkout); $_prefix, $_no_checkout, $_verbose);
$Git::SVN::_follow_parent = 1; $Git::SVN::_follow_parent = 1;
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
'config-dir=s' => \$Git::SVN::Ra::config_dir, 'config-dir=s' => \$Git::SVN::Ra::config_dir,
@ -88,7 +88,7 @@ my %cmt_opts = ( 'edit|e' => \$_edit,
my %cmd = ( my %cmd = (
fetch => [ \&cmd_fetch, "Download new revisions from SVN", fetch => [ \&cmd_fetch, "Download new revisions from SVN",
{ 'revision|r=s' => \$_revision, { 'revision|r=s' => \$_revision,
'all|a' => \$_fetch_all, 'fetch-all|all' => \$_fetch_all,
%fc_opts } ], %fc_opts } ],
init => [ \&cmd_init, "Initialize a repo for tracking" . init => [ \&cmd_init, "Initialize a repo for tracking" .
" (requires URL argument)", " (requires URL argument)",
@ -101,7 +101,9 @@ my %cmd = (
'Commit several diffs to merge with upstream', 'Commit several diffs to merge with upstream',
{ 'merge|m|M' => \$_merge, { 'merge|m|M' => \$_merge,
'strategy|s=s' => \$_strategy, 'strategy|s=s' => \$_strategy,
'verbose|v' => \$_verbose,
'dry-run|n' => \$_dry_run, 'dry-run|n' => \$_dry_run,
'fetch-all|all' => \$_fetch_all,
%cmt_opts, %fc_opts } ], %cmt_opts, %fc_opts } ],
'set-tree' => [ \&cmd_set_tree, 'set-tree' => [ \&cmd_set_tree,
"Set an SVN repository to a git tree-ish", "Set an SVN repository to a git tree-ish",
@ -129,6 +131,12 @@ my %cmd = (
'color' => \$Git::SVN::Log::color, 'color' => \$Git::SVN::Log::color,
'pager=s' => \$Git::SVN::Log::pager, '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-diff' => [ \&cmd_commit_diff,
'Commit a diff between two trees', 'Commit a diff between two trees',
{ 'message|m=s' => \$_message, { 'message|m=s' => \$_message,
@ -248,7 +256,7 @@ sub cmd_fetch {
} }
my ($remote) = @_; my ($remote) = @_;
if (@_ > 1) { 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; $remote ||= $Git::SVN::default_repo_id;
if ($_fetch_all) { if ($_fetch_all) {
@ -296,21 +304,12 @@ sub cmd_set_tree {
sub cmd_dcommit { sub cmd_dcommit {
my $head = shift; my $head = shift;
$head ||= 'HEAD'; $head ||= 'HEAD';
my ($url, $rev, $uuid);
my ($fh, $ctx) = command_output_pipe('rev-list', $head);
my @refs; my @refs;
my $c; my ($url, $rev, $uuid) = working_head_info($head, \@refs);
while (<$fh>) { my $c = $refs[-1];
$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
unless (defined $url && defined $rev && defined $uuid) { unless (defined $url && defined $rev && defined $uuid) {
die "Unable to determine upstream SVN information from ", 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 $gs = Git::SVN->find_by_url($url);
my $last_rev; my $last_rev;
@ -354,15 +353,13 @@ sub cmd_dcommit {
"now resync your SVN::Mirror repository.\n"; "now resync your SVN::Mirror repository.\n";
return; return;
} }
$gs->fetch; $_fetch_all ? $gs->fetch_all : $gs->fetch;
# we always want to rebase against the current HEAD, not any # we always want to rebase against the current HEAD, not any
# head that was passed to us # head that was passed to us
my @diff = command('diff-tree', 'HEAD', $gs->refname, '--'); my @diff = command('diff-tree', 'HEAD', $gs->refname, '--');
my @finish; my @finish;
if (@diff) { if (@diff) {
@finish = qw/rebase/; @finish = rebase_cmd();
push @finish, qw/--merge/ if $_merge;
push @finish, "--strategy=$_strategy" if $_strategy;
print STDERR "W: HEAD and ", $gs->refname, " differ, ", print STDERR "W: HEAD and ", $gs->refname, " differ, ",
"using @finish:\n", "@diff"; "using @finish:\n", "@diff";
} else { } else {
@ -374,6 +371,24 @@ sub cmd_dcommit {
command_noisy(@finish, $gs->refname); 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 { sub cmd_show_ignore {
my $gs = Git::SVN->new; my $gs = Git::SVN->new;
my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
@ -468,6 +483,14 @@ sub cmd_commit_diff {
########################### utility functions ######################### ########################### 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 { sub post_fetch_checkout {
return if $_no_checkout; return if $_no_checkout;
my $gs = $Git::SVN::_head or return; my $gs = $Git::SVN::_head or return;
@ -687,6 +710,20 @@ sub cmt_metadata {
command(qw/cat-file commit/, shift)))[-1]); 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; package Git::SVN;
use strict; use strict;
use warnings; use warnings;
@ -783,6 +820,12 @@ sub parse_revision_argument {
sub fetch_all { sub fetch_all {
my ($repo_id, $remotes) = @_; 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 my $remote = $remotes->{$repo_id} or
die "[svn-remote \"$repo_id\"] unknown\n"; die "[svn-remote \"$repo_id\"] unknown\n";
my $fetch = $remote->{fetch}; my $fetch = $remote->{fetch};
@ -3085,15 +3128,7 @@ sub git_svn_log_cmd {
last; last;
} }
my $url; my $url = (::working_head_info($head))[0];
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 $gs = Git::SVN->find_by_url($url) || Git::SVN->_new; my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new;
my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
$gs->refname); $gs->refname);