# The head revision is on branch "origin" by default.
# You can change that with the '-o' option.
-require 5.008; # for shell-safe open("-|",LIST)
use strict;
use warnings;
use Getopt::Std;
+use File::Copy;
use File::Spec;
use File::Temp qw(tempfile);
use File::Path qw(mkpath);
$SIG{'PIPE'}="IGNORE";
$ENV{'TZ'}="UTC";
-our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_s,$opt_l,$opt_d,$opt_D);
+our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
+ $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D);
sub usage() {
print STDERR <<END;
Usage: ${\basename $0} # fetch/update GIT from SVN
- [-o branch-for-HEAD] [-h] [-v] [-l max_num_changes]
+ [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
[-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
- [-d|-D] [-i] [-u] [-s start_chg] [-m] [-M regex] [SVN_URL]
+ [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
+ [-m] [-M regex] [-A author_file] [SVN_URL]
END
exit(1);
}
-getopts("b:C:dDhil:mM:o:s:t:T:uv") or usage();
+getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage();
usage if $opt_h;
my $tag_name = $opt_t || "tags";
our @mergerx = ();
if ($opt_m) {
- @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+ my $branch_esc = quotemeta ($branch_name);
+ my $trunk_esc = quotemeta ($trunk_name);
+ @mergerx =
+ (
+ qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+ qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+ qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i
+ );
}
if ($opt_M) {
- push (@mergerx, qr/$opt_M/);
+ unshift (@mergerx, qr/$opt_M/);
+}
+
+# Absolutize filename now, since we will have chdir'ed by the time we
+# get around to opening it.
+$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
+
+our %users = ();
+our $users_file = undef;
+sub read_users($) {
+ $users_file = File::Spec->rel2abs(@_);
+ die "Cannot open $users_file\n" unless -f $users_file;
+ open(my $authors,$users_file);
+ while(<$authors>) {
+ chomp;
+ next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+ (my $user,my $name,my $email) = ($1,$2,$3);
+ $users{$user} = [$name,$email];
+ }
+ close($authors);
}
select(STDERR); $|=1; select(STDOUT);
use File::Spec;
use File::Temp qw(tempfile);
use POSIX qw(strftime dup2);
+use Fcntl qw(SEEK_SET);
sub new {
my($what,$repo) = @_;
sub conn {
my $self = shift;
my $repo = $self->{'fullrep'};
- my $s = SVN::Ra->new($repo);
-
+ my $auth = SVN::Core::auth_open ([SVN::Client::get_simple_provider,
+ SVN::Client::get_ssl_server_trust_file_provider,
+ SVN::Client::get_username_provider]);
+ my $s = SVN::Ra->new(url => $repo, auth => $auth);
die "SVN connection to $repo: $!\n" unless defined $s;
$self->{'svn'} = $s;
$self->{'repo'} = $repo;
DIR => File::Spec->tmpdir(), UNLINK => 1);
print "... $rev $path ...\n" if $opt_v;
+ my (undef, $properties);
my $pool = SVN::Pool->new();
- eval { $self->{'svn'}->get_file($path,$rev,$fh,$pool); };
+ eval { (undef, $properties)
+ = $self->{'svn'}->get_file($path,$rev,$fh,$pool); };
$pool->clear;
if($@) {
return undef if $@ =~ /Attempted to get checksum/;
die $@;
}
+ my $mode;
+ if (exists $properties->{'svn:executable'}) {
+ $mode = '100755';
+ } elsif (exists $properties->{'svn:special'}) {
+ my ($special_content, $filesize);
+ $filesize = tell $fh;
+ seek $fh, 0, SEEK_SET;
+ read $fh, $special_content, $filesize;
+ if ($special_content =~ s/^link //) {
+ $mode = '120000';
+ seek $fh, 0, SEEK_SET;
+ truncate $fh, 0;
+ print $fh $special_content;
+ } else {
+ die "unexpected svn:special file encountered";
+ }
+ } else {
+ $mode = '100644';
+ }
close ($fh);
- return $name;
+ return ($name, $mode);
+}
+
+sub ignore {
+ my($self,$path,$rev) = @_;
+
+ print "... $rev $path ...\n" if $opt_v;
+ my (undef,undef,$properties)
+ = $self->{'svn'}->get_dir($path,$rev,undef);
+ if (exists $properties->{'svn:ignore'}) {
+ my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+ DIR => File::Spec->tmpdir(),
+ UNLINK => 1);
+ print $fh $properties->{'svn:ignore'};
+ close($fh);
+ return $name;
+ } else {
+ return undef;
+ }
}
package main;
use URI;
-my $svn = $svn_url;
+our $svn = $svn_url;
$svn .= "/$svn_dir" if defined $svn_dir;
+my $svn2 = SVNconn->new($svn);
$svn = SVNconn->new($svn);
my $lwp_ua;
my $maxnum = 0;
my $last_rev = "";
my $last_branch;
-my $current_rev = $opt_s-1;
+my $current_rev = $opt_s || 1;
unless(-d $git_dir) {
system("git-init-db");
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
-f "$git_dir/svn2git"
or die "'$git_dir/svn2git' does not exist.\n".
"You need that file for incremental imports.\n";
- $last_branch = basename(readlink("$git_dir/HEAD"));
+ open(F, "git-symbolic-ref HEAD |") or
+ die "Cannot run git-symbolic-ref: $!\n";
+ chomp ($last_branch = <F>);
+ $last_branch = basename($last_branch);
+ close(F);
unless($last_branch) {
warn "Cannot read the last branch name: $! -- assuming 'master'\n";
$last_branch = "master";
my($num,$branch,$ref) = split;
$branches{$branch}{$num} = $ref;
$branches{$branch}{"LAST"} = $ref;
- $current_rev = $num if $current_rev < $num;
+ $current_rev = $num+1 if $current_rev <= $num;
}
close($B);
}
-d $git_dir
or die "Could not create git subdir ($git_dir).\n";
+my $default_authors = "$git_dir/svn-authors";
+if ($opt_A) {
+ read_users($opt_A);
+ copy($opt_A,$default_authors) or die "Copy failed: $!";
+} else {
+ read_users($default_authors) if -f $default_authors;
+}
+
open BRANCHES,">>", "$git_dir/svn2git";
sub node_kind($$$) {
my $svnpath = revert_split_path($branch,$path);
# now get it
- my $name;
+ my ($name,$mode);
if($opt_d) {
my($req,$res);
return undef if $res->code == 301; # directory?
die $res->status_line." at $url\n";
}
+ $mode = '0644'; # can't obtain mode via direct http request?
} else {
- $name = $svn->file("/$svnpath",$rev);
+ ($name,$mode) = $svn->file("$svnpath",$rev);
return undef unless defined $name;
}
- open my $F, '-|', "git-hash-object", "-w", $name
+ my $pid = open(my $F, '-|');
+ die $! unless defined $pid;
+ if (!$pid) {
+ exec("git-hash-object", "-w", $name)
or die "Cannot create object: $!\n";
+ }
my $sha = <$F>;
chomp $sha;
close $F;
unlink $name;
- my $mode = "0644"; # SV does not seem to store any file modes
return [$mode, $sha, $path];
}
+sub get_ignore($$$$$) {
+ my($new,$old,$rev,$branch,$path) = @_;
+
+ return unless $opt_I;
+ my $svnpath = revert_split_path($branch,$path);
+ my $name = $svn->ignore("$svnpath",$rev);
+ if ($path eq '/') {
+ $path = $opt_I;
+ } else {
+ $path = File::Spec->catfile($path,$opt_I);
+ }
+ if (defined $name) {
+ my $pid = open(my $F, '-|');
+ die $! unless defined $pid;
+ if (!$pid) {
+ exec("git-hash-object", "-w", $name)
+ or die "Cannot create object: $!\n";
+ }
+ my $sha = <$F>;
+ chomp $sha;
+ close $F;
+ unlink $name;
+ push(@$new,['0644',$sha,$path]);
+ } else {
+ push(@$old,$path);
+ }
+}
+
sub split_path($$) {
my($rev,$path) = @_;
my $branch;
$srcpath =~ s#/*$#/#;
}
- open my $f,"-|","git-ls-tree","-r","-z",$gitrev,$srcpath;
+ my $pid = open my $f,'-|';
+ die $! unless defined $pid;
+ if (!$pid) {
+ exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+ or die $!;
+ }
local $/ = "\0";
while(<$f>) {
chomp;
if (not defined $author) {
$author_name = $author_email = "unknown";
+ } elsif (defined $users_file) {
+ die "User $author is not listed in $users_file\n"
+ unless exists $users{$author};
+ ($author_name,$author_email) = @{$users{$author}};
} elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
($author_name, $author_email) = ($1, $2);
} else {
}
if(($action->[0] eq "A") || ($action->[0] eq "R")) {
my $node_kind = node_kind($branch,$path,$revision);
- if($action->[1]) {
- copy_path($revision,$branch,$path,$action->[1],$action->[2],$node_kind,\@new,\@parents);
- } elsif ($node_kind eq $SVN::Node::file) {
+ if ($node_kind eq $SVN::Node::file) {
my $f = get_file($revision,$branch,$path);
if ($f) {
push(@new,$f) if $f;
my $opath = $action->[3];
print STDERR "$revision: $branch: could not fetch '$opath'\n";
}
+ } elsif ($node_kind eq $SVN::Node::dir) {
+ if($action->[1]) {
+ copy_path($revision, $branch,
+ $path, $action->[1],
+ $action->[2], $node_kind,
+ \@new, \@parents);
+ } else {
+ get_ignore(\@new, \@old, $revision,
+ $branch, $path);
+ }
}
} elsif ($action->[0] eq "D") {
push(@old,$path);
if ($node_kind eq $SVN::Node::file) {
my $f = get_file($revision,$branch,$path);
push(@new,$f) if $f;
+ } elsif ($node_kind eq $SVN::Node::dir) {
+ get_ignore(\@new, \@old, $revision,
+ $branch,$path);
}
} else {
die "$revision: unknown action '".$action->[0]."' for $path\n";
}
}
- if(@old) {
- open my $F, "-|", "git-ls-files", "-z", @old or die $!;
- @old = ();
+ while(@old) {
+ my @o1;
+ if(@old > 55) {
+ @o1 = splice(@old,0,50);
+ } else {
+ @o1 = @old;
+ @old = ();
+ }
+ my $pid = open my $F, "-|";
+ die "$!" unless defined $pid;
+ if (!$pid) {
+ exec("git-ls-files", "-z", @o1) or die $!;
+ }
+ @o1 = ();
local $/ = "\0";
while(<$F>) {
chomp;
- push(@old,$_);
+ push(@o1,$_);
}
close($F);
- while(@old) {
+ while(@o1) {
my @o2;
- if(@old > 55) {
- @o2 = splice(@old,0,50);
+ if(@o1 > 55) {
+ @o2 = splice(@o1,0,50);
} else {
- @o2 = @old;
- @old = ();
+ @o2 = @o1;
+ @o1 = ();
}
system("git-update-index","--force-remove","--",@o2);
die "Cannot remove files: $?\n" if $?;
$pr->reader();
$message =~ s/[\s\n]+\z//;
+ $message = "r$revision: $message" if $opt_r;
print $pw "$message\n"
or die "Error writing to git-commit-tree: $!\n";
print "DONE: $revision $dest $cid\n" if $opt_v;
}
-my ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
-sub _commit_all {
- ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+sub commit_all {
+ # Recursive use of the SVN connection does not work
+ local $svn = $svn2;
+
+ my ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
my %p;
while(my($path,$action) = each %$changed_paths) {
$p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
}
$changed_paths = \%p;
-}
-sub commit_all {
my %done;
my @col;
my $pref;
}
}
-while(++$current_rev <= $svn->{'maxrev'}) {
- if (defined $opt_l) {
- $opt_l--;
- if ($opt_l < 0) {
- last;
- }
- }
- my $pool=SVN::Pool->new;
- $svn->{'svn'}->get_log("/",$current_rev,$current_rev,1,1,1,\&_commit_all,$pool);
- $pool->clear;
- commit_all();
+$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'};
+
+if ($opt_l < $current_rev) {
+ print "Up to date: no new revisions to fetch!\n" if $opt_v;
+ unlink("$git_dir/SVN2GIT_HEAD");
+ exit;
}
+print "Fetching from $current_rev to $opt_l ...\n" if $opt_v;
+
+my $pool=SVN::Pool->new;
+$svn->{'svn'}->get_log("/",$current_rev,$opt_l,0,1,1,\&commit_all,$pool);
+$pool->clear;
+
unlink($git_index);
print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
unless -f "$git_dir/refs/heads/master";
- unlink("$git_dir/HEAD");
- symlink("refs/heads/$orig_branch","$git_dir/HEAD");
+ system('git-update-ref', 'HEAD', "$orig_branch");
unless ($opt_i) {
system('git checkout');
die "checkout failed: $?\n" if $?;