@matheusbsbs / matheus-bagliano / commits / 195643f2fc
Fork 0

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