Code

git-svn: allow dcommit to retain local merge information
authorEric Wong <normalperson@yhbt.net>
Wed, 13 Jun 2007 09:23:28 +0000 (02:23 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 13 Jun 2007 18:24:23 +0000 (11:24 -0700)
dcommit will still rewrite the HEAD commit and the history of the first
parents of each HEAD~1, HEAD~2, HEAD~3 as it always has.

However, any merge parents (HEAD^2, HEAD^^2, HEAD~2^2) will now be
preserved when the new HEAD and HEAD~[0-9]+ commits are rewritten to SVN
with dcommit.  Commits written to SVN will still not have any merge
information besides anything in the commit message.

Thanks to Joakim Tjernlund, Junio C Hamano and Steven Grimm
for explanations, feedback, examples and test case.

Signed-off-by: Eric Wong <normalperson@yhbt.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
git-svn.perl
t/t9114-git-svn-dcommit-merge.sh [new file with mode: 0755]

index 0ae8d70de1eb884b0d51879e35e2aad79a56c296..42906767a10e848031146c4f7eb862c40b67385c 100755 (executable)
@@ -372,16 +372,9 @@ sub cmd_dcommit {
                die "Unable to determine upstream SVN information from ",
                    "$head history\n";
        }
-       my $c = $refs[-1];
        my $last_rev;
-       foreach my $d (@refs) {
-               if (!verify_ref("$d~1")) {
-                       fatal "Commit $d\n",
-                             "has no parent commit, and therefore ",
-                             "nothing to diff against.\n",
-                             "You should be working from a repository ",
-                             "originally created by git-svn\n";
-               }
+       my ($linear_refs, $parents) = linearize_history($gs, \@refs);
+       foreach my $d (@$linear_refs) {
                unless (defined $last_rev) {
                        (undef, $last_rev, undef) = cmt_metadata("$d~1");
                        unless (defined $last_rev) {
@@ -403,6 +396,9 @@ sub cmd_dcommit {
                                        svn_path => '');
                        if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
                                print "No changes\n$d~1 == $d\n";
+                       } elsif ($parents->{$d} && @{$parents->{$d}}) {
+                               $gs->{inject_parents_dcommit}->{$last_rev} =
+                                                              $parents->{$d};
                        }
                }
        }
@@ -821,6 +817,59 @@ sub working_head_info {
        (undef, undef, undef, undef);
 }
 
+sub read_commit_parents {
+       my ($parents, $c) = @_;
+       my ($fh, $ctx) = command_output_pipe(qw/cat-file commit/, $c);
+       while (<$fh>) {
+               chomp;
+               last if '';
+               /^parent ($sha1)/ or next;
+               push @{$parents->{$c}}, $1;
+       }
+       close $fh; # break the pipe
+}
+
+sub linearize_history {
+       my ($gs, $refs) = @_;
+       my %parents;
+       foreach my $c (@$refs) {
+               read_commit_parents(\%parents, $c);
+       }
+
+       my @linear_refs;
+       my %skip = ();
+       my $last_svn_commit = $gs->last_commit;
+       foreach my $c (reverse @$refs) {
+               next if $c eq $last_svn_commit;
+               last if $skip{$c};
+
+               unshift @linear_refs, $c;
+               $skip{$c} = 1;
+
+               # we only want the first parent to diff against for linear
+               # history, we save the rest to inject when we finalize the
+               # svn commit
+               my $fp_a = verify_ref("$c~1");
+               my $fp_b = shift @{$parents{$c}} if $parents{$c};
+               if (!$fp_a || !$fp_b) {
+                       die "Commit $c\n",
+                           "has no parent commit, and therefore ",
+                           "nothing to diff against.\n",
+                           "You should be working from a repository ",
+                           "originally created by git-svn\n";
+               }
+               if ($fp_a ne $fp_b) {
+                       die "$c~1 = $fp_a, however parsing commit $c ",
+                           "revealed that:\n$c~1 = $fp_b\nBUG!\n";
+               }
+
+               foreach my $p (@{$parents{$c}}) {
+                       $skip{$p} = 1;
+               }
+       }
+       (\@linear_refs, \%parents);
+}
+
 package Git::SVN;
 use strict;
 use warnings;
@@ -1541,6 +1590,11 @@ sub get_commit_parents {
        if (my $cur = ::verify_ref($self->refname.'^0')) {
                push @tmp, $cur;
        }
+       if (my $ipd = $self->{inject_parents_dcommit}) {
+               if (my $commit = delete $ipd->{$log_entry->{revision}}) {
+                       push @tmp, @$commit;
+               }
+       }
        push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp);
        while (my $p = shift @tmp) {
                next if $seen{$p};
diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh
new file mode 100755 (executable)
index 0000000..d6ca955
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+# Based on a script by Joakim Tjernlund <joakim.tjernlund@transmode.se>
+
+test_description='git-svn dcommit handles merges'
+
+. ./lib-git-svn.sh
+
+big_text_block () {
+cat << EOF
+#
+# (C) Copyright 2000 - 2005
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+#
+# See file CREDITS for list of people who contributed to this
+# project.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307 USA
+#
+EOF
+}
+
+test_expect_success 'setup svn repository' "
+       svn co $svnrepo mysvnwork &&
+       mkdir -p mysvnwork/trunk &&
+       cd mysvnwork &&
+               big_text_block >> trunk/README &&
+               svn add trunk &&
+               svn ci -m 'first commit' trunk &&
+               cd ..
+       "
+
+test_expect_success 'setup git mirror and merge' "
+       git svn init $svnrepo -t tags -T trunk -b branches &&
+       git svn fetch &&
+       git checkout --track -b svn remotes/trunk &&
+       git checkout -b merge &&
+       echo new file > new_file &&
+       git add new_file &&
+       git commit -a -m 'New file' &&
+       echo hello >> README &&
+       git commit -a -m 'hello' &&
+       echo add some stuff >> new_file &&
+       git commit -a -m 'add some stuff' &&
+       git checkout svn &&
+       mv -f README tmp &&
+       echo friend > README &&
+       cat tmp >> README &&
+       git commit -a -m 'friend' &&
+       git pull . merge
+       "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'verify pre-merge ancestry' "
+       test x\`git rev-parse --verify refs/heads/svn^2\` = \
+            x\`git rev-parse --verify refs/heads/merge\` &&
+       git cat-file commit refs/heads/svn^ | grep '^friend$'
+       "
+
+test_expect_success 'git svn dcommit merges' "
+       git svn dcommit
+       "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'verify post-merge ancestry' "
+       test x\`git rev-parse --verify refs/heads/svn\` = \
+            x\`git rev-parse --verify refs/remotes/trunk \` &&
+       test x\`git rev-parse --verify refs/heads/svn^2\` = \
+            x\`git rev-parse --verify refs/heads/merge\` &&
+       git cat-file commit refs/heads/svn^ | grep '^friend$'
+       "
+
+test_done