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;