git-svn: handle empty files marked as symlinks in SVN
Broken SVN clients generate empty files with the svn:special set to '*'. This attempts to denote a symlink pointing to a file with an empty path (""), which cannot be generated on a POSIX system. Thus, we mimic the behavior of svn(1) and create a zero-byte file in our tree. Signed-off-by: Eric Wong <normalperson@yhbt.net>
Eric Wong committed
Jan 11, 2009 at 16:51 UTC
dbc6c74d0858d77e61e092a48d467e725211f8e9
2 files changed
+140
-5
git-svn.perl
+50
-5
@@ -3200,7 +3200,10 @@ sub new {
3200
my ($class, $git_svn) = @_;
3201
my $self = SVN::Delta::Editor->new;
3202
bless $self, $class;
3203
- $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit};
3203
+ if (exists $git_svn->{last_commit}) {
3204
+ $self->{c} = $git_svn->{last_commit};
3205
+ $self->{empty_symlinks} = _mark_empty_symlinks($git_svn);
3206
+ }
3207
$self->{empty} = {};
3208
$self->{dir_prop} = {};
3209
$self->{file_prop} = {};
@@ -3210,6 +3213,34 @@ sub new {
3213
$self;
3214
}
3215
3216
+# this uses the Ra object, so it must be called before do_{switch,update},
3217
+# not inside them (when the Git::SVN::Fetcher object is passed) to
3218
+# do_{switch,update}
3219
+sub _mark_empty_symlinks {
3220
+ my ($git_svn) = @_;
3221
+ my %ret;
3222
+ my ($rev, $cmt) = $git_svn->last_rev_commit;
3223
+ return {} unless ($rev && $cmt);
3224
+
3225
+ chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`);
3226
+ my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt);
3227
+ local $/ = "\0";
3228
+ my $pfx = $git_svn->{path};
3229
+ $pfx .= '/' if length($pfx);
3230
+ while (<$ls>) {
3231
+ chomp;
3232
+ s/\A100644 blob $empty_blob\t//o or next;
3233
+ my $path = $_;
3234
+ my (undef, $props) =
3235
+ $git_svn->ra->get_file($pfx.$path, $rev, undef);
3236
+ if ($props->{'svn:special'}) {
3237
+ $ret{$path} = 1;
3238
+ }
3239
+ }
3240
+ command_close_pipe($ls, $ctx);
3241
+ \%ret;
3242
+}
3243
+
3244
sub set_path_strip {
3245
my ($self, $path) = @_;
3246
$self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
@@ -3268,6 +3299,9 @@ sub open_file {
3299
unless (defined $mode && defined $blob) {
3300
die "$path was not found in commit $self->{c} (r$rev)\n";
3301
}
3302
+ if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {
3303
+ $mode = '120000';
3304
+ }
3305
{ path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
3306
pool => SVN::Pool->new, action => 'M' };
3307
}
@@ -3346,7 +3380,10 @@ sub apply_textdelta {
3380
open my $dup, '<&', $fh or croak $!;
3381
my $base = $::_repository->temp_acquire('git_blob');
3382
if ($fb->{blob}) {
3349
- print $base 'link ' if ($fb->{mode_a} == 120000);
3383
+ if ($fb->{mode_a} eq '120000' &&
3384
+ ! $self->{empty_symlinks}->{$fb->{path}}) {
3385
+ print $base 'link ' or die "print $!\n";
3386
+ }
3387
my $size = $::_repository->cat_blob($fb->{blob}, $base);
3388
die "Failed to read object $fb->{blob}" if ($size < 0);
3389
@@ -3379,11 +3416,19 @@ sub close_file {
3416
}
3417
if ($fb->{mode_b} == 120000) {
3418
sysseek($fh, 0, 0) or croak $!;
3382
- sysread($fh, my $buf, 5) == 5 or croak $!;
3419
+ my $rd = sysread($fh, my $buf, 5);
3420
3384
- unless ($buf eq 'link ') {
3421
+ if (!defined $rd) {
3422
+ croak "sysread: $!\n";
3423
+ } elsif ($rd == 0) {
3424
+ warn "$path has mode 120000",
3425
+ " but it points to nothing\n",
3426
+ "converting to an empty file with mode",
3427
+ " 100644\n";
3428
+ $fb->{mode_b} = '100644';
3429
+ } elsif ($buf ne 'link ') {
3430
warn "$path has mode 120000",
3386
- " but is not a link\n";
3431
+ " but is not a link\n";
3432
} else {
3433
my $tmp_fh = $::_repository->temp_acquire(
3434
'svn_hash');
t/t9131-git-svn-empty-symlink.sh
new
+90
@@ -0,0 +1,90 @@
1
+#!/bin/sh
2
+
3
+test_description='test that git handles an svn repository with empty symlinks'
4
+
5
+. ./lib-git-svn.sh
6
+test_expect_success 'load svn dumpfile' '
7
+ svnadmin load "$rawsvnrepo" <<EOF
8
+SVN-fs-dump-format-version: 2
9
+
10
+UUID: 60780f9a-7df5-43b4-83ab-60e2c0673ef7
11
+
12
+Revision-number: 0
13
+Prop-content-length: 56
14
+Content-length: 56
15
+
16
+K 8
17
+svn:date
18
+V 27
19
+2008-11-26T07:17:27.590577Z
20
+PROPS-END
21
+
22
+Revision-number: 1
23
+Prop-content-length: 111
24
+Content-length: 111
25
+
26
+K 7
27
+svn:log
28
+V 4
29
+test
30
+K 10
31
+svn:author
32
+V 12
33
+normalperson
34
+K 8
35
+svn:date
36
+V 27
37
+2008-11-26T07:18:03.511836Z
38
+PROPS-END
39
+
40
+Node-path: bar
41
+Node-kind: file
42
+Node-action: add
43
+Prop-content-length: 33
44
+Text-content-length: 0
45
+Text-content-md5: d41d8cd98f00b204e9800998ecf8427e
46
+Content-length: 33
47
+
48
+K 11
49
+svn:special
50
+V 1
51
+*
52
+PROPS-END
53
+
54
+Revision-number: 2
55
+Prop-content-length: 121
56
+Content-length: 121
57
+
58
+K 7
59
+svn:log
60
+V 13
61
+bar => doink
62
+
63
+K 10
64
+svn:author
65
+V 12
66
+normalperson
67
+K 8
68
+svn:date
69
+V 27
70
+2008-11-27T03:55:31.601672Z
71
+PROPS-END
72
+
73
+Node-path: bar
74
+Node-kind: file
75
+Node-action: change
76
+Text-content-length: 10
77
+Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9
78
+Content-length: 10
79
+
80
+link doink
81
+
82
+EOF
83
+'
84
+
85
+test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x'
86
+test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar'
87
+test_expect_success 'get "bar" => symlink fix from svn' \
88
+ '(cd x && git svn rebase)'
89
+test_expect_success '"bar" becomes a symlink' 'test -L x/bar'
90
+test_done