#!/usr/bin/env perl
# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
# License: GPL v2 or later
use warnings;
use strict;
use vars qw/ $AUTHOR $VERSION
$SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
$GIT_SVN_INDEX $GIT_SVN
$GIT_DIR $REV_DIR/;
$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
$VERSION = '0.10.0';
$GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git";
# make sure the svn binary gives consistent output between locales and TZs:
$ENV{TZ} = 'UTC';
$ENV{LC_ALL} = 'C';
# If SVN:: library support is added, please make the dependencies
# optional and preserve the capability to use the command-line client.
# use eval { require SVN::... } to make it lazy load
# We don't use any modules not in the standard Perl distribution:
use Carp qw/croak/;
use IO::File qw//;
use File::Basename qw/dirname basename/;
use File::Path qw/mkpath/;
use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
use File::Spec qw//;
use POSIX qw/strftime/;
my $sha1 = qr/[a-f\d]{40}/;
my $sha1_short = qr/[a-f\d]{4,40}/;
my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
$_find_copies_harder, $_l, $_version, $_upgrade, $_authors);
my (@_branch_from, %tree_map, %users);
my $_svn_co_url_revs;
my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
'branch|b=s' => \@_branch_from,
'authors-file|A=s' => \$_authors );
my %cmd = (
fetch => [ \&fetch, "Download new revisions from SVN",
{ 'revision|r=s' => \$_revision, %fc_opts } ],
init => [ \&init, "Initialize and fetch (import)", { } ],
commit => [ \&commit, "Commit git revisions to SVN",
{ 'stdin|' => \$_stdin,
'edit|e' => \$_edit,
'rmdir' => \$_rmdir,
'find-copies-harder' => \$_find_copies_harder,
'l=i' => \$_l,
%fc_opts,
} ],
'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { } ],
rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
{ 'no-ignore-externals' => \$_no_ignore_ext,
'upgrade' => \$_upgrade } ],
);
my $cmd;
for (my $i = 0; $i < @ARGV; $i++) {
if (defined $cmd{$ARGV[$i]}) {
$cmd = $ARGV[$i];
splice @ARGV, $i, 1;
last;
}
};
my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
GetOptions(%opts, 'help|H|h' => \$_help,
'version|V' => \$_version,
'id|i=s' => \$GIT_SVN) or exit 1;
$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
$ENV{GIT_DIR} ||= $GIT_DIR;
$SVN_URL = undef;
$REV_DIR = "$GIT_DIR/$GIT_SVN/revs";
$SVN_WC = "$GIT_DIR/$GIT_SVN/tree";
usage(0) if $_help;
version() if $_version;
usage(1) unless defined $cmd;
load_authors() if $_authors;
svn_compat_check();
$cmd{$cmd}->[0]->(@ARGV);
exit 0;
####################### primary functions ######################
sub usage {
my $exit = shift || 0;
my $fd = $exit ? \*STDERR : \*STDOUT;
print $fd <<"";
git-svn - bidirectional operations between a single Subversion tree and git
Usage: $0 <command> [options] [arguments]\n
print $fd "Available commands:\n" unless $cmd;
foreach (sort keys %cmd) {
next if $cmd && $cmd ne $_;
print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n";
foreach (keys %{$cmd{$_}->[2]}) {
# prints out arguments as they should be passed:
my $x = s#=s$## ? '<arg>' : s#=i$## ? '<num>' : '';
print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
"--$_" : "-$_" }
split /\|/,$_)," $x\n";
}
}
print $fd <<"";
\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an
arbitrary identifier if you're tracking multiple SVN branches/repositories in
one git repository and want to keep them separate. See git-svn(1) for more
information.
exit $exit;
}
sub version {
print "git-svn version $VERSION\n";
exit 0;
}
sub rebuild {
$SVN_URL = shift or undef;
my $newest_rev = 0;
if ($_upgrade) {
sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
} else {
check_upgrade_needed();
}
my $pid = open(my $rev_list,'-|');
defined $pid or croak $!;
if ($pid == 0) {
exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
}
my $latest;
while (<$rev_list>) {
chomp;
my $c = $_;
croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
next if (!@commit); # skip merges
my $id = $commit[$#commit];
my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
\s([a-f\d\-]+)$/x);
if (!$rev || !$uuid || !$url) {
# some of the original repositories I made had
# indentifiers like this:
($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)
\@([a-f\d\-]+)/x);
if (!$rev || !$uuid) {
croak "Unable to extract revision or UUID from ",
"$c, $id\n";
}
}
# if we merged or otherwise started elsewhere, this is
# how we break out of it
next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
next if (defined $SVN_URL && ($url ne $SVN_URL));
print "r$rev = $c\n";
unless (defined $latest) {
if (!$SVN_URL && !$url) {
croak "SVN repository location required: $url\n";
}
$SVN_URL ||= $url;
$SVN_UUID ||= $uuid;
setup_git_svn();
$latest = $rev;
}
assert_revision_eq_or_unknown($rev, $c);
sys('git-update-ref',"$GIT_SVN/revs/$rev",$c);
$newest_rev = $rev if ($rev > $newest_rev);
}
close $rev_list or croak $?;
if (!chdir $SVN_WC) {
svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
chdir $SVN_WC or croak $!;
}
$pid = fork;
defined $pid or croak $!;
if ($pid == 0) {
my @svn_up = qw(svn up);
push @svn_up, '--ignore-externals'
|