Add 'git svn reset' to unwind 'git svn fetch'
Add a command to unwind the effects of fetch by moving the rev_map and refs/remotes/git-svn back to an old SVN revision. This allows revisions to be re-fetched. Ideally SVN revs would be immutable, but permissions changes in the SVN repository or indiscriminate use of '--ignore-paths' can create situations where fetch cannot make progress. Signed-off-by: Ben Jackson <ben@ben.com> Acked-by: Eric Wong <normalperson@yhbt.net>
This commit is contained in:
parent
ca5e880ec2
commit
195643f2fc
@ -216,7 +216,7 @@ config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
|
|||||||
The following features from `svn log' are supported:
|
The following features from `svn log' are supported:
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
--revision=<n>[:<n>];;
|
-r/--revision=<n>[:<n>];;
|
||||||
is supported, non-numeric args are not:
|
is supported, non-numeric args are not:
|
||||||
HEAD, NEXT, BASE, PREV, etc ...
|
HEAD, NEXT, BASE, PREV, etc ...
|
||||||
-v/--verbose;;
|
-v/--verbose;;
|
||||||
@ -314,6 +314,63 @@ Any other arguments are passed directly to 'git-log'
|
|||||||
Shows the Subversion externals. Use -r/--revision to specify a
|
Shows the Subversion externals. Use -r/--revision to specify a
|
||||||
specific revision.
|
specific revision.
|
||||||
|
|
||||||
|
'reset'::
|
||||||
|
Undoes the effects of 'fetch' back to the specified revision.
|
||||||
|
This allows you to re-'fetch' an SVN revision. Normally the
|
||||||
|
contents of an SVN revision should never change and 'reset'
|
||||||
|
should not be necessary. However, if SVN permissions change,
|
||||||
|
or if you alter your --ignore-paths option, a 'fetch' may fail
|
||||||
|
with "not found in commit" (file not previously visible) or
|
||||||
|
"checksum mismatch" (missed a modification). If the problem
|
||||||
|
file cannot be ignored forever (with --ignore-paths) the only
|
||||||
|
way to repair the repo is to use 'reset'.
|
||||||
|
|
||||||
|
Only the rev_map and refs/remotes/git-svn are changed. Follow 'reset'
|
||||||
|
with a 'fetch' and then 'git-reset' or 'git-rebase' to move local
|
||||||
|
branches onto the new tree.
|
||||||
|
|
||||||
|
-r/--revision=<n>;;
|
||||||
|
Specify the most recent revision to keep. All later revisions
|
||||||
|
are discarded.
|
||||||
|
-p/--parent;;
|
||||||
|
Discard the specified revision as well, keeping the nearest
|
||||||
|
parent instead.
|
||||||
|
Example:;;
|
||||||
|
Assume you have local changes in "master", but you need to refetch "r2".
|
||||||
|
|
||||||
|
------------
|
||||||
|
r1---r2---r3 remotes/git-svn
|
||||||
|
\
|
||||||
|
A---B master
|
||||||
|
------------
|
||||||
|
|
||||||
|
Fix the ignore-paths or SVN permissions problem that caused "r2" to
|
||||||
|
be incomplete in the first place. Then:
|
||||||
|
|
||||||
|
[verse]
|
||||||
|
git svn reset -r2 -p
|
||||||
|
git svn fetch
|
||||||
|
|
||||||
|
------------
|
||||||
|
r1---r2'--r3' remotes/git-svn
|
||||||
|
\
|
||||||
|
r2---r3---A---B master
|
||||||
|
------------
|
||||||
|
|
||||||
|
Then fixup "master" with 'git-rebase'.
|
||||||
|
Do NOT use 'git-merge' or your history will not be compatible with a
|
||||||
|
future 'dcommit'!
|
||||||
|
|
||||||
|
[verse]
|
||||||
|
git rebase --onto remotes/git-svn A^ master
|
||||||
|
|
||||||
|
------------
|
||||||
|
r1---r2'--r3' remotes/git-svn
|
||||||
|
\
|
||||||
|
A'--B' master
|
||||||
|
------------
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
|
45
git-svn.perl
45
git-svn.perl
@ -211,6 +211,10 @@ my %cmd = (
|
|||||||
'blame' => [ \&Git::SVN::Log::cmd_blame,
|
'blame' => [ \&Git::SVN::Log::cmd_blame,
|
||||||
"Show what revision and author last modified each line of a file",
|
"Show what revision and author last modified each line of a file",
|
||||||
{ 'git-format' => \$_git_format } ],
|
{ 'git-format' => \$_git_format } ],
|
||||||
|
'reset' => [ \&cmd_reset,
|
||||||
|
"Undo fetches back to the specified SVN revision",
|
||||||
|
{ 'revision|r=s' => \$_revision,
|
||||||
|
'parent|p' => \$_fetch_parent } ],
|
||||||
);
|
);
|
||||||
|
|
||||||
my $cmd;
|
my $cmd;
|
||||||
@ -1054,6 +1058,20 @@ sub cmd_info {
|
|||||||
print $result, "\n";
|
print $result, "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub cmd_reset {
|
||||||
|
my $target = shift || $_revision or die "SVN revision required\n";
|
||||||
|
$target = $1 if $target =~ /^r(\d+)$/;
|
||||||
|
$target =~ /^\d+$/ or die "Numeric SVN revision expected\n";
|
||||||
|
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
|
||||||
|
unless ($gs) {
|
||||||
|
die "Unable to determine upstream SVN information from ".
|
||||||
|
"history\n";
|
||||||
|
}
|
||||||
|
my ($r, $c) = $gs->find_rev_before($target, not $_fetch_parent);
|
||||||
|
$gs->rev_map_set($r, $c, 'reset', $uuid);
|
||||||
|
print "r$r = $c ($gs->{ref_id})\n";
|
||||||
|
}
|
||||||
|
|
||||||
########################### utility functions #########################
|
########################### utility functions #########################
|
||||||
|
|
||||||
sub rebase_cmd {
|
sub rebase_cmd {
|
||||||
@ -3023,6 +3041,14 @@ sub _rev_map_set {
|
|||||||
croak "write: $!";
|
croak "write: $!";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub _rev_map_reset {
|
||||||
|
my ($fh, $rev, $commit) = @_;
|
||||||
|
my $c = _rev_map_get($fh, $rev);
|
||||||
|
$c eq $commit or die "_rev_map_reset(@_) commit $c does not match!\n";
|
||||||
|
my $offset = sysseek($fh, 0, SEEK_CUR) or croak "seek: $!";
|
||||||
|
truncate $fh, $offset or croak "truncate: $!";
|
||||||
|
}
|
||||||
|
|
||||||
sub mkfile {
|
sub mkfile {
|
||||||
my ($path) = @_;
|
my ($path) = @_;
|
||||||
unless (-e $path) {
|
unless (-e $path) {
|
||||||
@ -3039,6 +3065,7 @@ sub rev_map_set {
|
|||||||
my $db = $self->map_path($uuid);
|
my $db = $self->map_path($uuid);
|
||||||
my $db_lock = "$db.lock";
|
my $db_lock = "$db.lock";
|
||||||
my $sig;
|
my $sig;
|
||||||
|
$update_ref ||= 0;
|
||||||
if ($update_ref) {
|
if ($update_ref) {
|
||||||
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
|
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
|
||||||
$SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
|
$SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
|
||||||
@ -3062,7 +3089,8 @@ sub rev_map_set {
|
|||||||
|
|
||||||
sysopen(my $fh, $db_lock, O_RDWR | O_CREAT)
|
sysopen(my $fh, $db_lock, O_RDWR | O_CREAT)
|
||||||
or croak "Couldn't open $db_lock: $!\n";
|
or croak "Couldn't open $db_lock: $!\n";
|
||||||
_rev_map_set($fh, $rev, $commit);
|
$update_ref eq 'reset' ? _rev_map_reset($fh, $rev, $commit) :
|
||||||
|
_rev_map_set($fh, $rev, $commit);
|
||||||
if ($sync) {
|
if ($sync) {
|
||||||
$fh->flush or die "Couldn't flush $db_lock: $!\n";
|
$fh->flush or die "Couldn't flush $db_lock: $!\n";
|
||||||
$fh->sync or die "Couldn't sync $db_lock: $!\n";
|
$fh->sync or die "Couldn't sync $db_lock: $!\n";
|
||||||
@ -3070,7 +3098,9 @@ sub rev_map_set {
|
|||||||
close $fh or croak $!;
|
close $fh or croak $!;
|
||||||
if ($update_ref) {
|
if ($update_ref) {
|
||||||
$_head = $self;
|
$_head = $self;
|
||||||
command_noisy('update-ref', '-m', "r$rev",
|
my $note = "";
|
||||||
|
$note = " ($update_ref)" if ($update_ref !~ /^\d*$/);
|
||||||
|
command_noisy('update-ref', '-m', "r$rev$note",
|
||||||
$self->refname, $commit);
|
$self->refname, $commit);
|
||||||
}
|
}
|
||||||
rename $db_lock, $db or die "rev_map_set(@_): ", "Failed to rename: ",
|
rename $db_lock, $db or die "rev_map_set(@_): ", "Failed to rename: ",
|
||||||
@ -3132,12 +3162,19 @@ sub rev_map_get {
|
|||||||
return undef unless -e $map_path;
|
return undef unless -e $map_path;
|
||||||
|
|
||||||
sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!";
|
sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!";
|
||||||
|
my $c = _rev_map_get($fh, $rev);
|
||||||
|
close($fh) or croak "close: $!";
|
||||||
|
$c
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _rev_map_get {
|
||||||
|
my ($fh, $rev) = @_;
|
||||||
|
|
||||||
binmode $fh or croak "binmode: $!";
|
binmode $fh or croak "binmode: $!";
|
||||||
my $size = (stat($fh))[7];
|
my $size = (stat($fh))[7];
|
||||||
($size % 24) == 0 or croak "inconsistent size: $size";
|
($size % 24) == 0 or croak "inconsistent size: $size";
|
||||||
|
|
||||||
if ($size == 0) {
|
if ($size == 0) {
|
||||||
close $fh or croak "close: $fh";
|
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3155,11 +3192,9 @@ sub rev_map_get {
|
|||||||
} elsif ($r > $rev) {
|
} elsif ($r > $rev) {
|
||||||
$u = $i - 24;
|
$u = $i - 24;
|
||||||
} else { # $r == $rev
|
} else { # $r == $rev
|
||||||
close($fh) or croak "close: $!";
|
|
||||||
return $c eq ('0' x 40) ? undef : $c;
|
return $c eq ('0' x 40) ? undef : $c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close($fh) or croak "close: $!";
|
|
||||||
undef;
|
undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
66
t/t9139-git-svn-reset.sh
Executable file
66
t/t9139-git-svn-reset.sh
Executable file
@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2009 Ben Jackson
|
||||||
|
#
|
||||||
|
|
||||||
|
test_description='git svn reset'
|
||||||
|
. ./lib-git-svn.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup test repository' '
|
||||||
|
svn_cmd co "$svnrepo" s &&
|
||||||
|
(
|
||||||
|
cd s &&
|
||||||
|
mkdir vis &&
|
||||||
|
echo always visible > vis/vis.txt &&
|
||||||
|
svn_cmd add vis &&
|
||||||
|
svn_cmd commit -m "create visible files" &&
|
||||||
|
mkdir hid &&
|
||||||
|
echo initially hidden > hid/hid.txt &&
|
||||||
|
svn_cmd add hid &&
|
||||||
|
svn_cmd commit -m "create initially hidden files" &&
|
||||||
|
svn_cmd up &&
|
||||||
|
echo mod >> vis/vis.txt &&
|
||||||
|
svn_cmd commit -m "modify vis" &&
|
||||||
|
svn_cmd up
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clone SVN repository with hidden directory' '
|
||||||
|
git svn init "$svnrepo" g &&
|
||||||
|
( cd g && git svn fetch --ignore-paths="^hid" )
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'modify hidden file in SVN repo' '
|
||||||
|
( cd s &&
|
||||||
|
echo mod hidden >> hid/hid.txt &&
|
||||||
|
svn_cmd commit -m "modify hid" &&
|
||||||
|
svn_cmd up
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'fetch fails on modified hidden file' '
|
||||||
|
( cd g &&
|
||||||
|
git svn find-rev refs/remotes/git-svn > ../expect &&
|
||||||
|
! git svn fetch 2> ../errors &&
|
||||||
|
git svn find-rev refs/remotes/git-svn > ../expect2 ) &&
|
||||||
|
fgrep "not found in commit" errors &&
|
||||||
|
test_cmp expect expect2
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'reset unwinds back to r1' '
|
||||||
|
( cd g &&
|
||||||
|
git svn reset -r1 &&
|
||||||
|
git svn find-rev refs/remotes/git-svn > ../expect2 ) &&
|
||||||
|
echo 1 >expect &&
|
||||||
|
test_cmp expect expect2
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'refetch succeeds not ignoring any files' '
|
||||||
|
( cd g &&
|
||||||
|
git svn fetch &&
|
||||||
|
git svn rebase &&
|
||||||
|
fgrep "mod hidden" hid/hid.txt
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user