Skip to content

Commit dbc6c74

Browse files
author
Eric Wong
committed
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>
1 parent a83c885 commit dbc6c74

File tree

2 files changed

+140
-5
lines changed

2 files changed

+140
-5
lines changed

git-svn.perl

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3200,7 +3200,10 @@ sub new {
32003200
my ($class, $git_svn) = @_;
32013201
my $self = SVN::Delta::Editor->new;
32023202
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+
}
32043207
$self->{empty} = {};
32053208
$self->{dir_prop} = {};
32063209
$self->{file_prop} = {};
@@ -3210,6 +3213,34 @@ sub new {
32103213
$self;
32113214
}
32123215

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+
32133244
sub set_path_strip {
32143245
my ($self, $path) = @_;
32153246
$self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
@@ -3268,6 +3299,9 @@ sub open_file {
32683299
unless (defined $mode && defined $blob) {
32693300
die "$path was not found in commit $self->{c} (r$rev)\n";
32703301
}
3302+
if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) {
3303+
$mode = '120000';
3304+
}
32713305
{ path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
32723306
pool => SVN::Pool->new, action => 'M' };
32733307
}
@@ -3346,7 +3380,10 @@ sub apply_textdelta {
33463380
open my $dup, '<&', $fh or croak $!;
33473381
my $base = $::_repository->temp_acquire('git_blob');
33483382
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+
}
33503387
my $size = $::_repository->cat_blob($fb->{blob}, $base);
33513388
die "Failed to read object $fb->{blob}" if ($size < 0);
33523389

@@ -3379,11 +3416,19 @@ sub close_file {
33793416
}
33803417
if ($fb->{mode_b} == 120000) {
33813418
sysseek($fh, 0, 0) or croak $!;
3382-
sysread($fh, my $buf, 5) == 5 or croak $!;
3419+
my $rd = sysread($fh, my $buf, 5);
33833420

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 ') {
33853430
warn "$path has mode 120000",
3386-
" but is not a link\n";
3431+
" but is not a link\n";
33873432
} else {
33883433
my $tmp_fh = $::_repository->temp_acquire(
33893434
'svn_hash');

t/t9131-git-svn-empty-symlink.sh

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)