#!/usr/bin/perl # vi:tw=0: # $Id: git_replay 229.5379.tsZx3g90yqu0wWMVDj0czqB4Mk0 2007-06-13 10:28:46 -0400 $ use strict; use DBM::Deep; use File::Spec; use File::Find; use Term::GentooFunctions qw(:all); use Tie::File; use Date::Parse; # dates in (Date::Manip will not handle git/date-R time...) use POSIX; # dates out use Getopt::Std; my %o; getopts("q", \%o); &q if $o{q}; &HELP_MESSAGE unless @ARGV == 3; my $gitrepo = shift @ARGV; my $dbfile = shift @ARGV; my $svnco = File::Spec->rel2abs( shift @ARGV ); my $TOP = new DBM::Deep($dbfile); $TOP->{$svnco} = {} unless $TOP->{$svnco}; my $dbm = $TOP->{$svnco}; &setup; &run; # setup {{{ sub setup { chdir $svnco or die "couldn't chdir into svnco($svnco): $!"; if( not -d "$svnco/.git/" ) { ebegin "cloning $gitrepo"; system(qw(git clone --no-checkout), $gitrepo, "temp") == 0 or die; system(qw(mv temp/.git .)) == 0 or die; rmdir "temp" or die $!; eend 1; } ebegin "pulling updates from $gitrepo"; system(qw(git checkout master)) == 0 or die; system(qw(git pull)) == 0 or die; eend 1; } # }}} # run {{{ sub run { open my $in, "git rev-list --all --parents | tac |" or die "error with revlist popen(): $!"; while(<$in>) { my ($commit, $parent) = split /\s+/; next if $dbm->{already_replayed}{$commit}; if( &replay($commit, $parent) and &inform_svn($commit, $parent) ) { $dbm->{already_replayed}{$commit} = 1; } else { last; } } close $in; } # }}} # replay {{{ sub replay { my ($commit, $parent) = @_; einfo "REPLAY $commit"; einfon "git checkout -- "; system(qw(git checkout), $commit) == 0 or die "checkout error"; eend 1; if( $parent ) { ebegin "dumping commit log to .msg"; system("git log -r $parent..$commit > .msg") == 0 or die; } else { ebegin "dumping initialization log to .msg"; system("git log > .msg") == 0 or die; } eend 1; tie my @a, 'Tie::File', ".msg" or die "error tieing .msg: $!"; if( @a and @a > 4) { ebegin "reversing commit detail location"; my $max = 1500; push @a, "- :: $gitrepo commit info :: -------------------"; while(1) { last if $a[0] =~ m/^\s*$/; push @a, shift @a; if( --$max < 0 ) { die "we seem to have hit an infinite loop... @a"; } } eend 1; } untie @a; return 1; } # }}} # inform_svn {{{ sub inform_svn { my ($commit, $parent) = @_; einfo "INFORM $commit"; my @files; my @dirs; &File::Find::find({wanted => sub { if( -f $_ ) { unless( $_ eq ".msg" ) { push @files, $File::Find::name; } } elsif( -d _ ) { if( m/^\.(?:git|svn)\z/ ) { $File::Find::prune = 1; } elsif( not m/^\.{1,2}\z/ ) { push @dirs, $File::Find::name; } } }}, '.' ); if( $parent ) { for my $f (@{ $dbm->{last_files}{$parent} }) { unless( -f $f ) { einfon "removing file \"$f\" from svn: "; system(qw(svn rm), $f) == 0 or die; eend 1; $dbm->{already_tracking_file}{$f} = 0; } } for my $d (@{ $dbm->{last_dirs}{$parent} }) { unless( -d $d ) { einfon "removing directory \"$d\" from svn: "; system(qw(svn rm), $d) == 0 or die; eend 1; $dbm->{already_tracking_dir}{$d} = 0; } } } for my $d (@dirs) { next if $dbm->{already_tracking_dir}{$d}; einfon "adding directory \"$d\" to svn: "; system(qw(svn add), $d) == 0 or die; eend 1; $dbm->{already_tracking_dir}{$d} = 1; } for my $f (@files) { next if $dbm->{already_tracking_file}{$f}; einfon "adding file \"$f\" to svn: "; system(qw(svn add), $f) == 0 or die; eend 1; $dbm->{already_tracking_file}{$f} = 1; } ebegin "comitting changes to svn"; system(qw(svn commit -F .msg)) == 0 or die; eend 1; my $date; my $mdate; open my $in, ".msg" or die "error opening commit message: $!"; while(<$in>) { # Date: Sat Mar 24 14:42:29 2007 -0400 # 2007-06-11T23:38:06.463364Z if( m/^Date:\s+(\w{3}\s+\w{3}.+[+-]\d{4})\s*$/ ) { $mdate = $1; if( my $s = str2time($mdate) ) { $date = strftime('%Y-%m-%dT%H:%M:%S.000000Z', gmtime($s)); } last; } } close $in; if( $date ) { ebegin "changing commit date to $date ($mdate)"; system(qw(svn propset --revprop -r HEAD svn:date), $date) == 0 or die; eend 1; } else { ewarn "date not found for $commit"; } $dbm->{last_dirs}{$commit} = \@dirs; $dbm->{last_files}{$commit} = \@files; # svn commits sometimes alters things causing git merge problems (very rare). # this resets everything that's tracked by git system(qw(git reset --hard HEAD)) == 0; return 1; } # }}} # HELP_MESSAGE {{{ sub HELP_MESSAGE { print "\n\n"; print "git_replay gitrepo replaydb svnco\n\n"; print " -q: quiet the gentoo-ish messages\n"; print "\n"; exit 0; } sub nop { print @_, "\n" } sub q { *eend = *nop; *einfo = *nop; *ebegin = *nop; *einfon = *nop; *ewarn = *nop; } # }}}