From dbc6c74d0858d77e61e092a48d467e725211f8e9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Jan 2009 16:51:10 -0800 Subject: [PATCH] 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 --- git-svn.perl | 55 +++++++++++++++++-- t/t9131-git-svn-empty-symlink.sh | 90 ++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 5 deletions(-) create mode 100755 t/t9131-git-svn-empty-symlink.sh diff --git a/git-svn.perl b/git-svn.perl index ad01e182d..1b87a6529 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3200,7 +3200,10 @@ sub new { my ($class, $git_svn) = @_; my $self = SVN::Delta::Editor->new; bless $self, $class; - $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; + if (exists $git_svn->{last_commit}) { + $self->{c} = $git_svn->{last_commit}; + $self->{empty_symlinks} = _mark_empty_symlinks($git_svn); + } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -3210,6 +3213,34 @@ sub new { $self; } +# this uses the Ra object, so it must be called before do_{switch,update}, +# not inside them (when the Git::SVN::Fetcher object is passed) to +# do_{switch,update} +sub _mark_empty_symlinks { + my ($git_svn) = @_; + my %ret; + my ($rev, $cmt) = $git_svn->last_rev_commit; + return {} unless ($rev && $cmt); + + chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`); + my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt); + local $/ = "\0"; + my $pfx = $git_svn->{path}; + $pfx .= '/' if length($pfx); + while (<$ls>) { + chomp; + s/\A100644 blob $empty_blob\t//o or next; + my $path = $_; + my (undef, $props) = + $git_svn->ra->get_file($pfx.$path, $rev, undef); + if ($props->{'svn:special'}) { + $ret{$path} = 1; + } + } + command_close_pipe($ls, $ctx); + \%ret; +} + sub set_path_strip { my ($self, $path) = @_; $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path; @@ -3268,6 +3299,9 @@ sub open_file { unless (defined $mode && defined $blob) { die "$path was not found in commit $self->{c} (r$rev)\n"; } + if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) { + $mode = '120000'; + } { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob, pool => SVN::Pool->new, action => 'M' }; } @@ -3346,7 +3380,10 @@ sub apply_textdelta { open my $dup, '<&', $fh or croak $!; my $base = $::_repository->temp_acquire('git_blob'); if ($fb->{blob}) { - print $base 'link ' if ($fb->{mode_a} == 120000); + if ($fb->{mode_a} eq '120000' && + ! $self->{empty_symlinks}->{$fb->{path}}) { + print $base 'link ' or die "print $!\n"; + } my $size = $::_repository->cat_blob($fb->{blob}, $base); die "Failed to read object $fb->{blob}" if ($size < 0); @@ -3379,11 +3416,19 @@ sub close_file { } if ($fb->{mode_b} == 120000) { sysseek($fh, 0, 0) or croak $!; - sysread($fh, my $buf, 5) == 5 or croak $!; + my $rd = sysread($fh, my $buf, 5); - unless ($buf eq 'link ') { + if (!defined $rd) { + croak "sysread: $!\n"; + } elsif ($rd == 0) { + warn "$path has mode 120000", + " but it points to nothing\n", + "converting to an empty file with mode", + " 100644\n"; + $fb->{mode_b} = '100644'; + } elsif ($buf ne 'link ') { warn "$path has mode 120000", - " but is not a link\n"; + " but is not a link\n"; } else { my $tmp_fh = $::_repository->temp_acquire( 'svn_hash'); diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh new file mode 100755 index 000000000..704a4f857 --- /dev/null +++ b/t/t9131-git-svn-empty-symlink.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +test_description='test that git handles an svn repository with empty symlinks' + +. ./lib-git-svn.sh +test_expect_success 'load svn dumpfile' ' + svnadmin load "$rawsvnrepo" < doink + +K 10 +svn:author +V 12 +normalperson +K 8 +svn:date +V 27 +2008-11-27T03:55:31.601672Z +PROPS-END + +Node-path: bar +Node-kind: file +Node-action: change +Text-content-length: 10 +Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9 +Content-length: 10 + +link doink + +EOF +' + +test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x' +test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar' +test_expect_success 'get "bar" => symlink fix from svn' \ + '(cd x && git svn rebase)' +test_expect_success '"bar" becomes a symlink' 'test -L x/bar' +test_done -- 2.26.2