#!/usr/bin/perl
# This tool is copyright (c) 2005, Matthias Urlichs.
# It is released under the Gnu Public License, version 2.
#
# The basic idea is to aggregate CVS check-ins into related changes.
# Fortunately, "cvsps" does that for us; all we have to do is to parse
# its output.
#
# Checking out the files is done by a single long-running CVS connection
# / server process.
#
# The head revision is on branch "origin" by default.
# You can change that with the '-o' option.
use 5.008;
use strict;
use warnings;
use Getopt::Long;
use File::Spec;
use File::Temp qw(tempfile tmpnam);
use File::Path qw(mkpath);
use File::Basename qw(basename dirname);
use Time::Local;
use IO::Socket;
use IO::Pipe;
use POSIX qw(strftime tzset dup2 ENOENT);
use IPC::Open2;
$SIG{'PIPE'}="IGNORE";
set_timezone('UTC');
our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R);
my (%conv_author_name, %conv_author_email, %conv_author_tz);
sub usage(;$) {
my $msg = shift;
print(STDERR "Error: $msg\n") if $msg;
print STDERR <<END;
Usage: git cvsimport # fetch/update GIT from CVS
[-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
[-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
[-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
[-r remote] [-R] [CVS_module]
END
exit(1);
}
sub read_author_info($) {
my ($file) = @_;
my $user;
open my $f, '<', "$file" or die("Failed to open $file: $!\n");
while (<$f>) {
# Expected format is this:
# exon=Andreas Ericsson <ae@op5.se>
if (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/) {
$user = $1;
$conv_author_name{$user} = $2;
$conv_author_email{$user} = $3;
}
# or with an optional timezone:
# spawn=Simon Pawn <spawn@frog-pond.org> America/Chicago
elsif (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*(\S+?)\s*$/) {
$user = $1;
$conv_author_name{$user} = $2;
$conv_author_email{$user} = $3;
$conv_author_tz{$user} = $4;
}
# However, we also read from CVSROOT/users format
# to ease migration.
elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) {
my $mapped;
($user, $mapped) = ($1, $3);
if ($mapped =~ /^\s*(.*?)\s*<(.*)>\s*$/) {
$conv_author_name{$user} = $1;
$conv_author_email{$user} = $2;
}
elsif ($mapped =~ /^<?(.*)>?$/) {
$conv_author_name{$user} = $user;
$conv_author_email{$user} = $1;
}
}
# NEEDSWORK: Maybe warn on unrecognized lines?
}
close ($f);
}
sub write_author_info($) {
my ($file) = @_;
open my $f, '>', $file or
die("Failed to open $file for writing: $!");
foreach (keys %conv_author_name) {
print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>";
print $f " $conv_author_tz{$_}" if ($conv_author_tz{$_});
print $f "\n";
}
close ($f);
}
# Versions of perl before 5.10.0 may not automatically check $TZ each
# time localtime is run (most platforms will do so only the first time).
# We can work around this by using tzset() to update the internal
# variable whenever we change the environment.
sub set_timezone {
$ENV{TZ} = shift;
tzset();
}
# convert getopts specs for use by git config
my %longmap = (
'A:' => 'authors-file',
'M:' => 'merge-regex',
'P:' => undef,
'R' => 'track-revisions',
'S:' => 'ignore-paths',
);
sub read_repo_config {
# Split the string between characters, unless there is a ':'
# So "abc:de" becomes ["a", "b", "c:", "d", "e"]
my @opts = split(/ *(?!:)/, shift);
foreach my $o (@opts) {
my $key = $o;
$key =~ s/://g;
my $arg = 'git config';
$arg .= ' --bool' if ($o !~ /:$/);
my $ckey = $key;
if (exists $longmap{$o}) {
# An uppercase option like -R cannot be
# expressed in the configuration, as the
# variable names are downcased.
$ckey = $longmap{$o};
next if (! defined $ckey);
$ckey =~ s/-//g;
}
chomp(my $tmp = `$arg --get cvsimport.$ckey`);
if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
no strict 'refs';
my $opt_name = "opt_" . $key;
if (!$$opt_name) {
$$opt_name = $tmp;
}
}
}
}
my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R";
read_repo_config($opts);
Getopt::Long::Configure( 'no_ignore_case', 'bundling' );
# turn the Getopt::Std specification in a Getopt::Long one,
# with support for multiple -M options
GetOptions( map { s/:/=s/; /M/ ? "$_\@" : $_ } split( /(?!:)/, $opts ) )
or usage();
usage if $opt_h;
if (@ARGV == 0) {
chomp(my $module = `git config --get cvsimport.module`);
push(@ARGV, $module) if $? == 0;
}
@ARGV <= 1 or usage("You can't specify more than one CVS module");
if ($opt_d) {
$ENV{"CVSROOT"} = $opt_d;
} elsif (-f 'CVS/Root') {
open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
$opt_d = <$f>;
chomp $opt_d;
close $f;
$ENV{"CVSROOT"} = $opt_d;
} elsif ($ENV{"CVSROOT"}) {
$opt_d = $ENV{"CVSROOT"};
} else {
usage("CVSROOT needs to be set");
}
$opt_s ||= "-";
$opt_a ||= 0;
my $git_tree = $opt_C;
$git_tree ||= ".";
my $remote;
<
|