package SVN::Mirror::Git;
our $VERSION = '0.62';
use strict;
use warnings;
use base 'SVN::Mirror';
use base 'Class::Accessor';
use File::Spec::Functions 'catfile';
use File::Path 'mkpath';
use SVK::Util 'read_file';
use SVK::Command::Commit;
use Date::Format qw(time2str);
use File::chdir;
use Time::HiRes 'time';
sub load_commits {
my ($self) = @_;
my $head = read_file(catfile($ENV{GIT_DIR}, 'HEAD'));
my $commit = $head;
chomp $commit;
my @history;
while ($commit) {
last if $self->{fromrev} && $commit eq $self->{fromrev};
my $cobj = git_get_commit($commit);
$commit = $cobj->{parent};
unshift @history, $cobj
}
return \@history;
}
sub gitdir {
my $self = shift;
my $subdir = $self->{target_path};
$subdir =~ s{/}{_}g; # XXX!
return catfile($self->{repospath}, $subdir.".git");
}
sub run {
my $self = shift;
my $subdir = $self->{target_path};
my $gitdir = $self->gitdir;
mkpath [$gitdir] or die "Can't mkdir: $!"
unless -e $gitdir;
system("rsync -az --progress '$self->{source}/' $gitdir/");
die if $?;
local $ENV{'GIT_DIR'} = $gitdir;
$subdir =~ s{/}{_}g; # XXX!
my $git_checkout = catfile($self->{repospath}, 'git-tmp', $subdir, 'checkout');
mkpath [$git_checkout];
local $ENV{'GIT_INDEX_FILE'} = catfile($self->{repospath}, 'git-tmp', $subdir, 'index');
#die $self->{fromrev};
my $commits = $self->load_commits;
return if $#{$commits} < 0;
if (!$commits->[0]{parent} && $self->{fromrev}) {
die "something is wrong";
}
my $svk_output = '';
my $fs = $self->{repos}->fs;
my $svk = SVK->new ( output => \$svk_output,
xd => SVK::XD->new
( depotmap => {'' => $self->{repospath}},
svkpath => $self->{repospath},
checkout => Data::Hierarchy->new ));
my $yrev = $fs->youngest_rev;
$svk->{xd}{checkout}->store
($git_checkout, { depotpath => '/'.$self->{target_path}, revision => $yrev});
my $pool = SVN::Pool->new_default;
my $i;
my ($time_svk, $time_git) = (0, 0);
for my $commit (@$commits) {
$pool->clear;
my $t = time;
print ++$i."/".($#{$commits}+1).": $commit->{id}\r";
git_update_to($commit->{id}, $git_checkout);
my $changed = git_changed($commit->{id});
my $nt = time;
# print('git: '.($nt-$t)."sec\n");
$time_git += $nt-$t;
my ($author, $time, $tz) = $commit->{author} =~ m/^(.*?)\s(\d+)\s([-+\d]+)$/;
local $SIG{INT} = 'IGNORE';
{
no warnings 'redefine';
local *SVK::Command::Commit::loc = sub { $_[0] }; # XXX for the output match
local $CWD = $git_checkout;
$svk->commit(-m => $commit->{log}, '--import', '--direct',
@$changed);
}
die $svk_output
unless $svk_output =~ m'Committed revision';
$svk->up($git_checkout); # just to make sure..
$yrev = $svk->{xd}{checkout}->get($git_checkout)->{revision};
$fs->change_rev_prop($yrev, 'svm:headrev', "$self->{source_uuid}:$commit->{id}\n");
$time = time2str("%Y-%m-%dT%H:%M:%S.00000Z", $time);
$fs->change_rev_prop($yrev, 'svn:date', $time);
$fs->change_rev_prop($yrev, 'svn:author', $author);
$t = time;
# print('svk: '.($t-$nt)."sec\n");
$time_svk += $t-$nt;
}
print "git: $time_git\nsvk: $time_svk\n";
}
# git functions
sub git_get_commit {
my $c = shift;
my $ret = { id => $c };
open my $fh, "git-cat-file commit $c|";
while (<$fh>) {
chomp;
unless (length $_) {
local $/;
$ret->{log} = <$fh>;
last;
}
my ($what, $value) = split (/ /, $_, 2);
if (exists $ret->{$what}) {
if ($what eq 'parent') {
push @{$ret->{merge}}, $value;
}
else {
die "duplicated key $what";
}
}
else {
$ret->{$what} = $value;
}
}
return $ret;
}
sub git_changed {
my $c = shift;
open my $fh, "git-diff-tree -r $c|cut -f2|";
<$fh>;
my $changed = [];
while (<$fh>) {
chomp;
push @$changed, $_;
}
return $changed;
}
sub git_update_to {
my ($c, $dir) = @_;
local $CWD = $dir;
# with --prefix it is slow
system ("git-read-tree -m $c && git-checkout-cache -f -u -a");
die if $?;
# git-ls-files doens't handle fsck dir yet
open my $fh, "git-ls-files --others|";
while (<$fh>) {
chomp;
unlink $_;
}
}
# svn::mirror glue
use URI;
sub load_state {
my ($self) = @_;
$self->{source_uuid} = $self->{root}->node_prop ($self->{target_path}, 'svm:uuid');
$self->load_source;
$self->load_fromrev;
}
sub load_source {
my $self = shift;
$self->{source} =~ s{^git://}{};
$self->{source} =~ s{/$}{};
my $uri = URI->new($self->{source});
$self->{source_root} = $uri->scheme.'://'.$uri->host;
$self->{source_path} = $uri->path;
}
sub init_state {
my ($self) = @_;
use Sys::Hostname;
$self->load_source;
my $uuid_src = $self->{source};
$self->{source_uuid} = lc($self->make_uuid($uuid_src));
warn "If you already have the git archive, symlink its .git it to ".$self->gitdir."\n";
return 'git://'.$self->{source};
}
sub make_uuid {
return Win32API::GUID::CreateGuid() if ($^O eq 'MSWin32');
Data::UUID->new->create_from_name_str(&Data::UUID::NameSpace_DNS, $_[0]);
}
1;