add code based on SVN and SVK perl bindings to get
authorStefan Metzmacher <metze@samba.org>
Thu, 20 Dec 2007 18:15:53 +0000 (19:15 +0100)
committerStefan Metzmacher <metze@samba.org>
Thu, 20 Dec 2007 18:15:53 +0000 (19:15 +0100)
the full diffs out of subversion even for deleted subdirs

metze

SVN2GitEditor.pm [new file with mode: 0644]
SVN2GitPatch.pm [new file with mode: 0644]
sync-v4-0-test.pl [new file with mode: 0755]

diff --git a/SVN2GitEditor.pm b/SVN2GitEditor.pm
new file mode 100644 (file)
index 0000000..c3416c4
--- /dev/null
@@ -0,0 +1,316 @@
+
+package SVN2GitEditor;
+use strict;
+
+require SVN::Delta;
+our @ISA = qw(SVN::Delta::Editor);
+
+use SVK::I18N;
+use SVK::XD;
+use autouse 'SVK::Util' => qw( slurp_fh tmpfile mimetype_is_text catfile );
+
+sub set_target_revision {
+    my ($self, $revision) = @_;
+}
+
+sub open_root {
+    my ($self, $baserev) = @_;
+    return '';
+}
+
+sub add_file {
+    my ($self, $path, $pdir, $from_path, $from_rev, $pool) = @_;
+    $self->{info}{$path}{fpool} = $pool;
+    if (defined $from_path) {
+       $self->{info}{$path}{from_path} = $from_path;
+       $self->{info}{$path}{copied} = 1;
+    } else {
+       $self->{info}{$path}{added} = 1;
+    }
+    return $path;
+}
+
+sub open_file {
+    my ($self, $path, $pdir, $rev, $pool) = @_;
+    $self->{info}{$path}{from_path} = $path;
+    $self->{info}{$path}{fpool} = $pool;
+
+    return $path;
+}
+
+sub apply_textdelta {
+    my ($self, $path, $checksum, $pool) = @_;
+    return unless $path;
+    my $info = $self->{info}{$path};
+    $info->{base} = $self->{cb_basecontent}($info->{from_path}, $info->{fpool})
+       unless $info->{added};
+
+    unless ($self->{external}) {
+       my $newtype = $info->{prop} && $info->{prop}{'svn:mime-type'};
+       my $is_text = !$newtype || mimetype_is_text ($newtype);
+       if ($is_text && !$info->{added}) {
+           my $basetype = $self->{cb_baseprop}->($info->{from_path}, 'svn:mime-type', $pool);
+           $is_text = !$basetype || mimetype_is_text ($basetype);
+       }
+       unless ($is_text) {
+            confess("Cannot display: file marked as a binary type.\n");
+       }
+    }
+    my $new;
+    if ($self->{external}) {
+       my $tmp = tmpfile ('diff');
+       slurp_fh ($info->{base}, $tmp)
+           if $info->{base};
+       seek $tmp, 0, 0;
+       $info->{base} = $tmp;
+       $info->{new} = $new = tmpfile ('diff');
+    }
+    else {
+       $info->{new} = '';
+       open $new, '>', \$info->{new};
+    }
+
+    return [SVN::TxDelta::apply ($info->{base}, $new,
+                                undef, undef, $pool)];
+}
+
+sub close_file {
+    my ($self, $path, $checksum, $pool) = @_;
+    return unless $path;
+    my $info = $self->{info}{$path};
+
+    if (exists $info->{new}) {
+       no warnings 'uninitialized';
+       my $rpath = $self->{report} ? catfile($self->{report}, $path) : $path;
+       my @label = map { $self->{$_} || $self->{"cb_$_"}->($path) } qw/llabel rlabel/;
+       my $showpath = ($self->{lpath} ne $self->{rpath});
+       my @showpath = map { $showpath ? $self->{$_} : undef } qw/lpath rpath/;
+       if ($self->{external}) {
+           # XXX: the 2nd file could be - and save some disk IO
+           my @content = map { ($info->{$_}->filename) } qw/base new/;
+           @content = reverse @content if $self->{reverse};
+           (system (split (/ /, $self->{external}),
+                   '-L', _full_label ($rpath, $showpath[0], $label[0]),
+                   $content[0],
+                   '-L', _full_label ($rpath, $showpath[1], $label[1]),
+                   $content[1]) >= 0) or die loc("Could not run %1: %2", $self->{external}, $?);
+       }
+       else {
+           $info->{base} = '';
+           $info->{base} = $self->{cb_basecontent}($info->{from_path}, $info->{fpool})
+               unless $info->{added};
+           my @content = ($info->{base}, \$self->{info}{$path}{new});
+           @content = reverse @content if $self->{reverse};
+           $self->output_diff ($rpath, @label, @showpath, @content);
+       }
+    }
+
+#    $self->output_prop_diff ($path, $pool);
+    delete $self->{info}{$path};
+}
+
+sub oldfilemode
+{
+       my ($self, $path) = @_;
+       my $pool = $self->{info}{$path}{fpool};
+       my $name = "svn:executable";
+
+       return undef if $self->{info}{$path}{added};
+
+       my $prop = $self->{cb_baseprop}->($path, $name, $pool);
+
+       return "100644" unless defined($prop);
+
+       return "100755" if (length($prop) > 0);
+
+       return "100644";
+}
+
+sub newfilemode
+{
+       my ($self, $path, $oldmode) = @_;
+       my $name = "svn:executable";
+
+       return undef if $self->{info}{$path}{deleted};
+
+       my $prop = $self->{info}{$path}{prop}{$name};
+
+       my $changed = 1;
+       $changed = 0 unless defined($prop);
+       $changed = 1 if $self->{info}{$path}{added};
+
+       return $oldmode unless $changed;
+
+       return "100755" if (length($prop) > 0);
+
+       return "100644";
+}
+
+sub output_diff {
+    my ($self, $path, $llabel, $rlabel, $lpath, $rpath) = splice(@_, 0, 6);
+    my $fh = $self->_output_fh;
+
+    my $ofile = $self->{info}{$path}{added} ? "/dev/null": "a/$path";
+    my $nfile = $self->{info}{$path}{deleted} ? "/dev/null": "b/$path";
+
+    my $osha1 = $self->{info}{$path}{added} ? "0000000": "1234567";
+    my $nsha1 = $self->{info}{$path}{deleted} ? "0000000": "7654321";
+
+    my $omode = $self->oldfilemode($path);
+    my $nmode = $self->newfilemode($path, $omode);
+
+    my $name = "";
+    my $mode = "";
+
+    if (defined($omode) and defined($nmode)) {
+       if ($omode ne $nmode) {
+           $mode = "old mode $omode\nnew mode $nmode\n";
+       }
+    } elsif (defined($omode)) {
+       $mode = "deleted file mode $omode\n";
+    } elsif (defined($nmode)) {
+       $mode = "new file mode $nmode\n";
+    }
+
+    print $fh (
+       "diff --git $ofile $nfile\n",
+       $name,
+       $mode,
+       "index $osha1..$nsha1\n"
+    );
+
+    unshift @_, $self->_output_fh;
+    push @_, $ofile, $nfile;
+
+    goto &{$self->can('_output_diff_content')};
+}
+
+# _output_diff_content($fh, $ltext, $rtext, $llabel, $rlabel)
+sub _output_diff_content {
+    my ($fh, $ltext, $rtext, $llabel, $rlabel) = @_;
+
+    my ($lfh, $lfn) = tmpfile ('diff');
+    my ($rfh, $rfn) = tmpfile ('diff');
+
+    slurp_fh ($ltext => $lfh); close ($lfh);
+    slurp_fh ($rtext => $rfh); close ($rfh);
+
+    my $diff = SVN::Core::diff_file_diff( $lfn, $rfn );
+
+    SVN::Core::diff_file_output_unified(
+        $fh, $diff, $lfn, $rfn, $llabel, $rlabel
+    );
+
+    unlink ($lfn, $rfn);
+}
+
+sub output_prop_diff {
+    my ($self, $path, $pool) = @_;
+    if ($self->{info}{$path}{prop}) {
+       my $rpath = $self->{report} ? catfile($self->{report}, $path) : $path;
+       $self->_print("\n", loc("Property changes on: %1\n", $rpath), ('_' x 67), "\n");
+       for (sort keys %{$self->{info}{$path}{prop}}) {
+           $self->_print(loc("Name: %1\n", $_));
+           my $baseprop;
+           $baseprop = $self->{cb_baseprop}->($path, $_, $pool)
+               unless $self->{info}{$path}{added};
+            my @args =
+                map \$_,
+                map { (length || /\n$/) ? "$_\n" : $_ }
+                    ($baseprop||''), ($self->{info}{$path}{prop}{$_}||'');
+            @args = reverse @args if $self->{reverse};
+
+            my $diff = '';
+            open my $fh, '>', \$diff;
+            _output_diff_content($fh, @args, '', '');
+            $diff =~ s/.*\n.*\n//;
+            $diff =~ s/^\@.*\n//mg;
+            $diff =~ s/^/ /mg;
+            $self->_print($diff);
+       }
+       $self->_print("\n");
+    }
+}
+
+sub add_directory {
+    my ($self, $path, $pdir, @arg) = @_;
+#    $self->{info}{$path}{added} = 1;
+    return $path;
+}
+
+sub open_directory {
+    my ($self, $path, $pdir, $rev, @arg) = @_;
+    return $path;
+}
+
+sub close_directory {
+    my ($self, $path, $pool) = @_;
+#    $self->output_prop_diff ($path, $pool);
+    delete $self->{info}{$path};
+}
+
+sub delete_entry {
+    my ($self, $path, $revision, $pdir, $pool) = @_;
+
+    my $fullpath = "$self->{branch}/$path";
+
+    if ($self->{oldroot}->is_file($fullpath)) {
+       $self->{info}{$path}{from_path} = $path;
+       $self->{info}{$path}{deleted} = 1;
+       $self->{info}{$path}{fpool} = $pool;
+       $self->{info}{$path}{new} = '';
+       $self->close_file($path, undef, $pool);
+       return;
+    }
+
+    my $entries = $self->{oldroot}->dir_entries($fullpath);
+    foreach my $c (keys %{$entries}) {
+       $self->delete_entry("$path/$c", $revision, $path, $pool);
+    }
+}
+
+sub change_file_prop {
+    my ($self, $path, $name, $value) = @_;
+    $self->{info}{$path}{prop}{$name} = $value;
+}
+
+sub change_dir_prop {
+    my ($self, $path, $name, $value) = @_;
+}
+
+sub close_edit {
+    my ($self, @arg) = @_;
+}
+
+sub _print {
+    my $self = shift;
+    $self->{output} or return print @_;
+    ${ $self->{output} } .= $_ for @_;
+}
+
+sub _output_fh {
+    my $self = shift;
+
+    no strict 'refs';
+    $self->{output} or return \*{select()};
+
+    open my $fh, '>>', $self->{output};
+    return $fh;
+}
+
+=head1 AUTHORS
+
+Chia-liang Kao E<lt>clkao@clkao.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2003-2005 by Chia-liang Kao E<lt>clkao@clkao.orgE<gt>.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
+
+1;
diff --git a/SVN2GitPatch.pm b/SVN2GitPatch.pm
new file mode 100644 (file)
index 0000000..d3f668a
--- /dev/null
@@ -0,0 +1,200 @@
+#!/usr/bin/perl
+#
+
+use strict;
+
+use util;
+
+use Time::Local;
+use Time::gmtime;
+
+use SVN::Core;
+use SVN::Repos;
+use SVN::Fs;
+use SVN::Delta;
+use SVN::Ra;
+use SVK::Editor::Diff;
+use SVN2GitEditor;
+
+package SVN2GitPatch;
+
+sub confess($)
+{
+       # TODO use confess from 'use Carp'
+       my ($str) = @_;
+       die($str);
+}
+
+sub new($$;$) {
+       my ($this, $repo_path, $authors_file) = @_;
+
+       my $self = undef;
+
+       $self->{repos} = SVN::Repos::open($repo_path);
+       $self->{authors_file} = $authors_file;
+       $self->{authors} = undef;
+
+       if (defined($self->{authors_file})) {
+               my $f = util::FileLoad($self->{authors_file});
+               my @lines = split("\n", $f);
+
+               foreach my $l (@lines) {
+                       if ($l =~ /^([\w\-]+) = (.*)$/) {
+                               $self->{authors}->{$1} = $2;
+                               next;
+                       }
+
+                       confess "line: $l: invalid";
+               }
+       }
+
+       bless $self;
+       return $self;
+}
+
+sub get_last_svn_rev($$)
+{
+       my ($self, $branch) = @_;
+
+       return $self->{repos}->fs->youngest_rev();
+}
+
+sub get_missing_svn_revs($$$)
+{
+       my ($self, $branch, $start_rev) = @_;
+       my $last_rev = $self->get_last_svn_rev($branch);
+       my @ret = ();
+
+       my $nroot = $self->{repos}->fs->revision_root($last_rev);
+       my $hist = $nroot->node_history($branch);
+
+       while ($hist = $hist->prev(0)) {
+               my $crev = $hist->location();
+               last unless defined($crev);
+               last unless $crev > 0;
+               last unless $crev > $start_rev;
+
+               unshift(@ret, $crev);
+       }
+
+       return @ret;
+}
+
+sub svn2git_author($$)
+{
+       my ($self, $in) = @_;
+
+       my $out = $self->{authors}->{$in};
+
+       confess "author: $in:not found" unless defined($out);
+
+       return $out;
+}
+
+sub svn2git_date($$)
+{
+       my ($self, $in) = @_;
+
+
+       my $tmp = $in;
+       my $out = undef;
+
+       if ($tmp =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\./) {
+               my $year = $1;
+               my $month = $2-1;
+               my $day = $3;
+               my $hour = $4;
+               my $min = $5;
+               my $sec = $6;
+
+               use Time::Local;
+               use Time::gmtime;
+               my $time = timegm($sec, $min, $hour, $day, $month, $year);
+               $out = gmctime($time);
+       }
+
+       confess "time: $in: invalid" unless defined($out);
+
+       return $out;
+}
+
+sub svn2git_log($$)
+{
+       my ($self, $in) = @_;
+
+       my $out = $in;
+
+       return $out;
+}
+
+sub get_git_patch($$$)
+{
+       my ($self, $branch, $rev) = @_;
+       my $orev = $rev-1;
+       my $nrev = $rev;
+       my $oroot = $self->{repos}->fs->revision_root($orev);
+       my $nroot = $self->{repos}->fs->revision_root($nrev);
+
+       my $p = undef;
+
+       $p->{svn_author} = $self->{repos}->fs->revision_prop($nrev, "svn:author");
+       $p->{svn_date} = $self->{repos}->fs->revision_prop($nrev, "svn:date");
+       $p->{svn_log} = $self->{repos}->fs->revision_prop($nrev, "svn:log");
+       $p->{svn_rev} = $rev;
+
+       $p->{git_diff} = "";
+       my $editor = SVN2GitEditor->new(
+               cb_basecontent => sub {
+                       my ($path, $pool) = @_;
+                       my $base = $oroot->file_contents("$branch/$path", $pool);
+                       return $base;
+               },
+               cb_baseprop => sub {
+                       my ($path, $pname, $pool) = @_;
+                       my $prop = $oroot->node_prop("$branch/$path", $pname, $pool);
+                       return $prop;
+               },
+               oldrepos => $self->{repos},
+               oldroot => $oroot,
+               oldrev => $orev,
+               newrev => $nrev,
+               branch => $branch,
+               llabel => "revision $orev",
+               rlabel => "revision $nrev",
+               external => undef,
+               output => \$p->{git_diff});
+
+       SVN::Repos::dir_delta($oroot,
+                             $branch, '',
+                             $nroot,
+                             $branch,
+                             $editor,
+                             undef, 1, 1, 1, 1);
+
+       $p->{git_author} = $self->svn2git_author($p->{svn_author});
+       $p->{git_date} = $self->svn2git_date($p->{svn_date});
+       $p->{git_log} = $self->svn2git_log($p->{svn_log});
+
+       my @log = split("\n", $p->{git_log});
+       $p->{git_log_subject} = shift @log;
+       $p->{git_log_body} = join("\n", @log);
+
+       if ($p->{git_diff} eq "") {
+               $p->{git_patch} = undef;
+               return $p;
+       }
+
+       $p->{git_patch} = "";
+       $p->{git_patch} .= "From 123456789abcdef\n";
+       $p->{git_patch} .= "From: $p->{git_author}\n";
+       $p->{git_patch} .= "Date: $p->{git_date}\n";
+       $p->{git_patch} .= "Subject: [PATCH] r$p->{svn_rev}: $p->{git_log_subject}\n";
+       $p->{git_patch} .= "$p->{git_log_body}\n";
+       $p->{git_patch} .= "\n";
+       $p->{git_patch} .= $p->{git_diff};
+       $p->{git_patch} .= "\n---\nsvn-sync script\n\n";
+
+       return $p;
+}
+
+1;
diff --git a/sync-v4-0-test.pl b/sync-v4-0-test.pl
new file mode 100755 (executable)
index 0000000..8aa68de
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+#
+
+use strict;
+
+use SVN2GitPatch;
+
+my $svn_repo_path = "/tmp/svn/samba.repo";
+my $authors_file = "svn-authors";
+
+my $svn_branch = "branches/SAMBA_4_0";
+my $svn_start_rev = 25600;
+my $basepath = "/tmp/svn/v4-0-test";
+
+my $patch_path = "$basepath/patches";
+my $last_svn_rev_file = "$patch_path/latest.svnrev";
+my $git_repo_path = "$basepath/git";
+
+$ENV{LANG} = "en_US.UTF-8";
+
+my $r = SVN2GitPatch->new($svn_repo_path, $authors_file);
+
+sub get_last_svn_rev($;$)
+{
+       my ($file, $default_rev) = @_;
+
+       $default_rev = 25600 if defined($default_rev);
+
+       my $v = util::FileLoad($file);
+
+       $v = $default_rev if $v eq "";
+       $v *= 1;
+       $v = $default_rev if $v == 0;
+
+       print "get_last_svn_rev: $v\n";
+       return $v;
+}
+
+sub set_last_svn_rev($$)
+{
+       my ($file, $rev) = @_;
+       util::FileSave($file, $rev);
+       print "set_last_svn_rev: $rev\n";
+}
+
+my $last_rev = $r->get_last_svn_rev($svn_branch);
+my $start_rev = get_last_svn_rev($last_svn_rev_file, $svn_start_rev);
+my @revs = $r->get_missing_svn_revs($svn_branch, $start_rev);
+
+print "start: $start_rev last: $last_rev\n";
+
+foreach my $rev (@revs) {
+
+       print "Get patch for rev: $rev\n";
+       my $p = $r->get_git_patch($svn_branch, $rev);
+
+       next unless defined($p->{git_patch});
+
+       my $patch_path = "$patch_path/$rev.patch";
+       open(PATCH, ">$patch_path") or die ("failed to $patch_path for write");
+       print PATCH $p->{git_patch}."\n";
+       close PATCH;
+
+       my $applycmd = "cd $git_repo_path && git am --whitespace=nowarn --binary $patch_path";
+
+       my $apply = `$applycmd` or die "$applycmd: failed";
+
+       set_last_svn_rev($last_svn_rev_file, $rev);
+}