contrib/git-svn: add -b/--branch switch for branch detection
authorEric Wong <normalperson@yhbt.net>
Fri, 3 Mar 2006 09:20:07 +0000 (01:20 -0800)
committerJunio C Hamano <junkio@cox.net>
Fri, 3 Mar 2006 19:27:10 +0000 (11:27 -0800)
I've said I don't like branches in Subversion, and I still don't.
This is a bit more flexible, though, as the argument for -b is any
arbitrary git head/tag reference.

This makes some things easier:
 * Importing git history into a brand new SVN branch.
 * Tracking multiple SVN branches via GIT_SVN_ID, even from multiple
   repositories.
 * Adding tags from SVN (still need to use GIT_SVN_ID, though).
 * Even merge tracking is supported, if and only the heads end up with
   100% equivalent tree objects.  This is more stricter but more robust
   and foolproof than parsing commit messages, imho.

Signed-off-by: Eric Wong <normalperson@yhbt.net>
Signed-off-by: Junio C Hamano <junkio@cox.net>
contrib/git-svn/git-svn.perl
contrib/git-svn/git-svn.txt

index 0e092c5d3fb326fb37a48c4199880386281a4bd3..1f9a470f0c2eb75cf23ca85f2e56b483552daa89 100755 (executable)
@@ -35,6 +35,7 @@ my $sha1 = qr/[a-f\d]{40}/;
 my $sha1_short = qr/[a-f\d]{6,40}/;
 my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
        $_find_copies_harder, $_l, $_version, $_upgrade);
+my (@_branch_from, %tree_map);
 
 GetOptions(    'revision|r=s' => \$_revision,
                'no-ignore-externals' => \$_no_ignore_ext,
@@ -43,6 +44,7 @@ GetOptions(   'revision|r=s' => \$_revision,
                'rmdir' => \$_rmdir,
                'upgrade' => \$_upgrade,
                'help|H|h' => \$_help,
+               'branch|b=s' => \@_branch_from,
                'find-copies-harder' => \$_find_copies_harder,
                'l=i' => \$_l,
                'version|V' => \$_version,
@@ -831,6 +833,8 @@ sub git_commit {
        my $uuid = $info->{'Repository UUID'};
        defined $uuid or croak "Unable to get Repository UUID\n";
 
+       map_tree_joins() if (@_branch_from && !%tree_map);
+
        # commit parents can be conditionally bound to a particular
        # svn revision via: "svn_revno=commit_sha1", filter them out here:
        my @exec_parents;
@@ -852,6 +856,17 @@ sub git_commit {
                git_addremove();
                chomp(my $tree = `git-write-tree`);
                croak if $?;
+               if (exists $tree_map{$tree}) {
+                       my %seen_parent = map { $_ => 1 } @exec_parents;
+                       foreach (@{$tree_map{$tree}}) {
+                               # MAXPARENT is defined to 16 in commit-tree.c:
+                               if ($seen_parent{$_} || @exec_parents > 16) {
+                                       next;
+                               }
+                               push @exec_parents, $_;
+                               $seen_parent{$_} = 1;
+                       }
+               }
                my $msg_fh = IO::File->new_tmpfile or croak $!;
                print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ",
                                        "$SVN_URL\@$log_msg->{revision}",
@@ -975,6 +990,29 @@ sub check_upgrade_needed {
        }
 }
 
+# fills %tree_map with a reverse mapping of trees to commits.  Useful
+# for finding parents to commit on.
+sub map_tree_joins {
+       foreach my $br (@_branch_from) {
+               my $pid = open my $pipe, '-|';
+               defined $pid or croak $!;
+               if ($pid == 0) {
+                       exec(qw(git-rev-list --pretty=raw), $br) or croak $?;
+               }
+               while (<$pipe>) {
+                       if (/^commit ($sha1)$/o) {
+                               my $commit = $1;
+                               my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
+                               unless (defined $tree) {
+                                       die "Failed to parse commit $commit\n";
+                               }
+                               push @{$tree_map{$tree}}, $commit;
+                       }
+               }
+               close $pipe or croak $?;
+       }
+}
+
 __END__
 
 Data structures:
index 4102deb381201ea17370f22b1f14165a0fcb93bb..7306048bff8f6c496b48d1988677a2beb08dedab 100644 (file)
@@ -27,7 +27,7 @@ For importing svn, git-svnimport is potentially more powerful when
 operating on repositories organized under the recommended
 trunk/branch/tags structure, and should be faster, too.
 
-git-svn completely ignores the very limited view of branching that
+git-svn mostly ignores the very limited view of branching that
 Subversion has.  This allows git-svn to be much easier to use,
 especially on repositories that are not organized in a manner that
 git-svnimport is designed for.
@@ -116,6 +116,21 @@ OPTIONS
        They are both passed directly to git-diff-tree see
        git-diff-tree(1) for more information.
 
+-b<refname>::
+--branch <refname>::
+       Used with 'fetch' or 'commit'.
+
+       This can be used to join arbitrary git branches to remotes/git-svn
+       on new commits where the tree object is equivalent.
+
+       When used with different GIT_SVN_ID values, tags and branches in
+       SVN can be tracked this way, as can some merges where the heads
+       end up having completely equivalent content.  This can even be
+       used to track branches across multiple SVN _repositories_.
+
+       This option may be specified multiple times, once for each
+       branch.
+
 COMPATIBILITY OPTIONS
 ---------------------
 --no-ignore-externals::