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>
Ben Jackson committed
Jun 3, 2009 at 20:45 UTC
195643f2fc80b4d06a75b954b9a8ef2300976755
3 files changed
+164
-6
Documentation/git-svn.txt
+58
-1
@@ -216,7 +216,7 @@ config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options)
216
The following features from `svn log' are supported:
217
+
218
--
219
---revision=<n>[:<n>];;
219
+-r/--revision=<n>[:<n>];;
220
is supported, non-numeric args are not:
221
HEAD, NEXT, BASE, PREV, etc ...
222
-v/--verbose;;
@@ -314,6 +314,63 @@ Any other arguments are passed directly to 'git-log'
314
Shows the Subversion externals. Use -r/--revision to specify a
315
specific revision.
316
317
+'reset'::
318
+ Undoes the effects of 'fetch' back to the specified revision.
319
+ This allows you to re-'fetch' an SVN revision. Normally the
320
+ contents of an SVN revision should never change and 'reset'
321
+ should not be necessary. However, if SVN permissions change,
322
+ or if you alter your --ignore-paths option, a 'fetch' may fail
323
+ with "not found in commit" (file not previously visible) or
324
+ "checksum mismatch" (missed a modification). If the problem
325
+ file cannot be ignored forever (with --ignore-paths) the only
326
+ way to repair the repo is to use 'reset'.
327
+
328
+Only the rev_map and refs/remotes/git-svn are changed. Follow 'reset'
329
+with a 'fetch' and then 'git-reset' or 'git-rebase' to move local
330
+branches onto the new tree.
331
+
332
+-r/--revision=<n>;;
333
+ Specify the most recent revision to keep. All later revisions
334
+ are discarded.
335
+-p/--parent;;
336
+ Discard the specified revision as well, keeping the nearest
337
+ parent instead.
338
+Example:;;
339
+Assume you have local changes in "master", but you need to refetch "r2".
340
+
341
+------------
342
+ r1---r2---r3 remotes/git-svn
343
+ \
344
+ A---B master
345
+------------
346
+
347
+Fix the ignore-paths or SVN permissions problem that caused "r2" to
348
+be incomplete in the first place. Then:
349
+
350
+[verse]
351
+git svn reset -r2 -p
352
+git svn fetch
353
+
354
+------------
355
+ r1---r2'--r3' remotes/git-svn
356
+ \
357
+ r2---r3---A---B master
358
+------------
359
+
360
+Then fixup "master" with 'git-rebase'.
361
+Do NOT use 'git-merge' or your history will not be compatible with a
362
+future 'dcommit'!
363
+
364
+[verse]
365
+git rebase --onto remotes/git-svn A^ master
366
+
367
+------------
368
+ r1---r2'--r3' remotes/git-svn
369
+ \
370
+ A'--B' master
371
+------------
372
+
373
+
374
--
375
376
OPTIONS
git-svn.perl
+40
-5
@@ -211,6 +211,10 @@ my %cmd = (
211
'blame' => [ \&Git::SVN::Log::cmd_blame,
212
"Show what revision and author last modified each line of a file",
213
{ 'git-format' => \$_git_format } ],
214
+ 'reset' => [ \&cmd_reset,
215
+ "Undo fetches back to the specified SVN revision",
216
+ { 'revision|r=s' => \$_revision,
217
+ 'parent|p' => \$_fetch_parent } ],
218
);
219
220
my $cmd;
@@ -1054,6 +1058,20 @@ sub cmd_info {
1058
print $result, "\n";
1059
}
1060
1061
+sub cmd_reset {
1062
+ my $target = shift || $_revision or die "SVN revision required\n";
1063
+ $target = $1 if $target =~ /^r(\d+)$/;
1064
+ $target =~ /^\d+$/ or die "Numeric SVN revision expected\n";
1065
+ my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
1066
+ unless ($gs) {
1067
+ die "Unable to determine upstream SVN information from ".
1068
+ "history\n";
1069
+ }
1070
+ my ($r, $c) = $gs->find_rev_before($target, not $_fetch_parent);
1071
+ $gs->rev_map_set($r, $c, 'reset', $uuid);
1072
+ print "r$r = $c ($gs->{ref_id})\n";
1073
+}
1074
+
1075
########################### utility functions #########################
1076
1077
sub rebase_cmd {
@@ -3023,6 +3041,14 @@ sub _rev_map_set {
3041
croak "write: $!";
3042
}
3043
3044
+sub _rev_map_reset {
3045
+ my ($fh, $rev, $commit) = @_;
3046
+ my $c = _rev_map_get($fh, $rev);
3047
+ $c eq $commit or die "_rev_map_reset(@_) commit $c does not match!\n";
3048
+ my $offset = sysseek($fh, 0, SEEK_CUR) or croak "seek: $!";
3049
+ truncate $fh, $offset or croak "truncate: $!";
3050
+}
3051
+
3052
sub mkfile {
3053
my ($path) = @_;
3054
unless (-e $path) {
@@ -3039,6 +3065,7 @@ sub rev_map_set {
3065
my $db = $self->map_path($uuid);
3066
my $db_lock = "$db.lock";
3067
my $sig;
3068
+ $update_ref ||= 0;
3069
if ($update_ref) {
3070
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
3071
$SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
@@ -3062,7 +3089,8 @@ sub rev_map_set {
3089
3090
sysopen(my $fh, $db_lock, O_RDWR | O_CREAT)
3091
or croak "Couldn't open $db_lock: $!\n";
3065
- _rev_map_set($fh, $rev, $commit);
3092
+ $update_ref eq 'reset' ? _rev_map_reset($fh, $rev, $commit) :
3093
+ _rev_map_set($fh, $rev, $commit);
3094
if ($sync) {
3095
$fh->flush or die "Couldn't flush $db_lock: $!\n";
3096
$fh->sync or die "Couldn't sync $db_lock: $!\n";
@@ -3070,7 +3098,9 @@ sub rev_map_set {
3098
close $fh or croak $!;
3099
if ($update_ref) {
3100
$_head = $self;
3073
- command_noisy('update-ref', '-m', "r$rev",
3101
+ my $note = "";
3102
+ $note = " ($update_ref)" if ($update_ref !~ /^\d*$/);
3103
+ command_noisy('update-ref', '-m', "r$rev$note",
3104
$self->refname, $commit);
3105
}
3106
rename $db_lock, $db or die "rev_map_set(@_): ", "Failed to rename: ",
@@ -3132,12 +3162,19 @@ sub rev_map_get {
3162
return undef unless -e $map_path;
3163
3164
sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!";
3165
+ my $c = _rev_map_get($fh, $rev);
3166
+ close($fh) or croak "close: $!";
3167
+ $c
3168
+}
3169
+
3170
+sub _rev_map_get {
3171
+ my ($fh, $rev) = @_;
3172
+
3173
binmode $fh or croak "binmode: $!";
3174
my $size = (stat($fh))[7];
3175
($size % 24) == 0 or croak "inconsistent size: $size";
3176
3177
if ($size == 0) {
3140
- close $fh or croak "close: $fh";
3178
return undef;
3179
}
3180
@@ -3155,11 +3192,9 @@ sub rev_map_get {
3192
} elsif ($r > $rev) {
3193
$u = $i - 24;
3194
} else { # $r == $rev
3158
- close($fh) or croak "close: $!";
3195
return $c eq ('0' x 40) ? undef : $c;
3196
}
3197
}
3162
- close($fh) or croak "close: $!";
3198
undef;
3199
}
3200
t/t9139-git-svn-reset.sh
new
+66
@@ -0,0 +1,66 @@
1
+#!/bin/sh
2
+#
3
+# Copyright (c) 2009 Ben Jackson
4
+#
5
+
6
+test_description='git svn reset'
7
+. ./lib-git-svn.sh
8
+
9
+test_expect_success 'setup test repository' '
10
+ svn_cmd co "$svnrepo" s &&
11
+ (
12
+ cd s &&
13
+ mkdir vis &&
14
+ echo always visible > vis/vis.txt &&
15
+ svn_cmd add vis &&
16
+ svn_cmd commit -m "create visible files" &&
17
+ mkdir hid &&
18
+ echo initially hidden > hid/hid.txt &&
19
+ svn_cmd add hid &&
20
+ svn_cmd commit -m "create initially hidden files" &&
21
+ svn_cmd up &&
22
+ echo mod >> vis/vis.txt &&
23
+ svn_cmd commit -m "modify vis" &&
24
+ svn_cmd up
25
+ )
26
+'
27
+
28
+test_expect_success 'clone SVN repository with hidden directory' '
29
+ git svn init "$svnrepo" g &&
30
+ ( cd g && git svn fetch --ignore-paths="^hid" )
31
+'
32
+
33
+test_expect_success 'modify hidden file in SVN repo' '
34
+ ( cd s &&
35
+ echo mod hidden >> hid/hid.txt &&
36
+ svn_cmd commit -m "modify hid" &&
37
+ svn_cmd up
38
+ )
39
+'
40
+
41
+test_expect_success 'fetch fails on modified hidden file' '
42
+ ( cd g &&
43
+ git svn find-rev refs/remotes/git-svn > ../expect &&
44
+ ! git svn fetch 2> ../errors &&
45
+ git svn find-rev refs/remotes/git-svn > ../expect2 ) &&
46
+ fgrep "not found in commit" errors &&
47
+ test_cmp expect expect2
48
+'
49
+
50
+test_expect_success 'reset unwinds back to r1' '
51
+ ( cd g &&
52
+ git svn reset -r1 &&
53
+ git svn find-rev refs/remotes/git-svn > ../expect2 ) &&
54
+ echo 1 >expect &&
55
+ test_cmp expect expect2
56
+'
57
+
58
+test_expect_success 'refetch succeeds not ignoring any files' '
59
+ ( cd g &&
60
+ git svn fetch &&
61
+ git svn rebase &&
62
+ fgrep "mod hidden" hid/hid.txt
63
+ )
64
+'
65
+
66
+test_done