@matheusbsbs / matheus-bagliano / commits / dbc6c74d08
Fork 0

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