ossp-pkg/cvsfusion/cvsfusion.pl
#!/usr/opkg/bin/perl
##
## OSSP cvsfusion - CVS Repository Fusion
## Copyright (c) 2004 Ralf S. Engelschall <rse@engelschall.com>
## Copyright (c) 2004 The OSSP Project <http://www.ossp.org/>
## Copyright (c) 2004 Cable & Wireless <http://www.cw.com/>
##
## This file is part of OSSP cvsfusion, a CVS repository fusion
## utility which can be found at http://www.ossp.org/pkg/tool/cvsfusion/.
##
## Permission to use, copy, modify, and distribute this software for
## any purpose with or without fee is hereby granted, provided that
## the above copyright notice and this permission notice appear in all
## copies.
##
## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
## SUCH DAMAGE.
##
## cvsfusion.pl: main program
##
require 5;
use lib ".";
use Getopt::Long;
use IO::File;
use File::Temp qw(tempfile tempdir);
use File::Find;
use File::Path;
use File::Copy;
use File::Basename;
use Cwd;
use RCS;
use strict;
use warnings;
no warnings 'File::Find';
## _________________________________________________________________________
##
## Prolog
## _________________________________________________________________________
##
# program information
my $prog = {
'name' => "cvsfusion",
'vers' => "0.0.1",
'date' => "20-Apr-2004"
};
# program parameters (defaults)
my $opt = {
'version' => 0,
'verbose' => 0,
'help' => 0,
'tmpdir' => '',
'cvsroot-source' => '',
'cvsroot-target' => '',
'cvs-module' => [],
'cvs-branch' => '',
'prog-rcs' => "rcs",
'prog-co' => "co",
'prog-diff' => "diff"
};
# exception handling support
$SIG{__DIE__} = sub {
my ($err) = @_;
$err =~ s|\s+at\s+.*||s if (not $opt->{'verbose'});
print STDERR $prog->{'name'} . ":ERROR: $err ". ($! ? "($!)" : "") . "\n";
exit(1);
};
# command line parsing
Getopt::Long::Configure("bundling");
my $result = GetOptions(
'V|version' => \$opt->{'version'},
'v|verbose' => \$opt->{'verbose'},
'h|help' => \$opt->{'help'},
't|tmpdir=s' => \$opt->{'tmpdir'},
'f|cvsroot-source=s' => \$opt->{'cvsroot-source'},
'l|cvsroot-target=s' => \$opt->{'cvsroot-target'},
'm|cvs-module=s@' => $opt->{'cvs-module'},
'b|cvs-branch=s' => \$opt->{'cvs-branch'},
'R|prog-rcs=s' => \$opt->{'prog-rcs'},
'C|prog-co=s' => \$opt->{'prog-co'},
'D|prog-diff=s' => \$opt->{'prog-diff'},
) || die "option parsing failed";
if ($opt->{'help'}) {
print "Usage: ".$prog->{'name'}." [options]\n" .
"Available options:\n" .
" -V,--version print program version\n" .
" -v,--verbose enable verbose run-time mode\n" .
" -h,--help print out this usage page\n" .
" -t,--tmpdir=DIR filesystem path to temporary directory\n" .
" -f,--cvsroot-source=DIR filesystem path to source CVS repository\n" .
" -l,--cvsroot-target=DIR filesystem path to target CVS repository\n" .
" -m,--cvs-module=SUBDIR selects the CVS repository module(s)\n" .
" -b,--cvs-branch=TAG:REV selects the CVS branch tag/revision to use\n" .
" -R,--prog-rcs=FILE filesystem path to GNU RCS' rcs(1) program\n" .
" -C,--prog-co=FILE filesystem path to GNU RCS' co(1) program\n" .
" -D,--prog-diff=FILE filesystem path to GNU DiffUtils' diff(1) program\n";
exit(0);
}
if ($opt->{'version'}) {
print "OSSP ".$prog->{'name'}." ".$prog->{'vers'}." (".$prog->{'date'}.")\n";
exit(0);
}
# verbose message printing
sub msg_verbose {
my ($msg) = @_;
print STDERR "$msg\n" if ($opt->{'verbose'});
}
# warning message printing
sub msg_warning {
my ($msg) = @_;
print STDERR $prog->{'name'}.":WARNING: $msg\n";
}
# error message printing
sub msg_error {
my ($msg) = @_;
print STDERR $prog->{'name'}.":ERROR: $msg\n";
}
## _________________________________________________________________________
##
## Main Procedure
## _________________________________________________________________________
##
# sanity check parameters
my $error = 0;
if ($opt->{'cvsroot-source'} eq '') {
&msg_error("no source CVSROOT specified (use option --cvsroot-source=DIR)");
$error++;
}
elsif (not -d $opt->{'cvsroot-source'}) {
&msg_error("source CVSROOT not a valid directory: ".$opt->{'cvsroot-source'});
$error++;
}
elsif (not -d $opt->{'cvsroot-source'}."/CVSROOT") {
&msg_error("source CVSROOT not a valid CVS repository: ".$opt->{'cvsroot-source'});
$error++;
}
if ($opt->{'cvsroot-target'} eq '') {
&msg_error("no target CVSROOT specified (use option --cvsroot-target=DIR)");
$error++;
}
elsif (not -d $opt->{'cvsroot-target'}) {
&msg_error("target CVSROOT not a valid directory: ".$opt->{'cvsroot-target'});
$error++;
}
elsif (not -d $opt->{'cvsroot-target'}."/CVSROOT") {
&msg_error("target CVSROOT not a valid CVS repository: ".$opt->{'cvsroot-target'});
$error++;
}
if (@{$opt->{'cvs-module'}} == 0) {
&msg_error("no source CVS module(s) specified (use option --cvs-module=SUBDIR)");
$error++;
}
else {
my $modules = [];
foreach my $module (@{$opt->{'cvs-module'}}) {
foreach my $m (split(/,/, $module)) {
push(@{$modules}, $m);
}
}
$opt->{'cvs-module'} = $modules;
foreach my $module (@{$opt->{'cvs-module'}}) {
if (not -d $opt->{'cvsroot-source'}."/".$module) {
&msg_error("invalid source CVS module: $module");
$error++;
}
}
}
if ($opt->{'cvs-branch'} eq '') {
&msg_error("no target CVS branch tag/revision specified (use option --cvs-branch=TAG:REV)");
$error++;
}
elsif ($opt->{'cvs-branch'} !~ m/^[A-Z][A-Z0-9_]+:\d+(\.\d+\.\d+)+$/) {
&msg_error("invalid target CVS branch tag/revision: ". $opt->{'cvs-branch'});
$error++;
}
exit(1) if ($error > 0);
# determine temporary directory
if ($opt->{'tmpdir'} eq '') {
$opt->{'tmpdir'} = tempdir($prog->{'name'}.".XXXXXXXXXX", TMPDIR => 1, CLEANUP => 1);
}
#my ($fh, $filename) = tempfile(DIR => $opt->{'tmpdir'}, SUFFIX => ',v');
# determine information about RCS files in source CVS repository
my $cvs = {};
$cvs->{'source'} = [];
my $cwd = getcwd();
chdir($opt->{'cvsroot-source'});
sub find_cb {
push(@{$cvs->{'source'}}, $File::Find::name) if (-f $_);
}
find(\&find_cb, @{$opt->{'cvs-module'}});
chdir($cwd);
# determine branch tag and revision
my ($branch_tag, $branch_rev) = ($opt->{'cvs-branch'} =~ m/^(.+):(.+)$/);
# iterate over all RCS files in source CVS repository
foreach my $source (sort @{$cvs->{'source'}}) {
print " $source\n";
# load source file
my $rcs = new RCS;
$rcs->load($opt->{'cvsroot-source'} . "/". $source);
my @revs = $rcs->lookup();
printf(" (%d revisions)\n", scalar(@revs));
# move all source revisions onto target branch
foreach my $rev (@revs) {
my $num = $rev->revision();
$rev->revision($branch_rev.".".$num);
}
# transform source trunk into regular branch
# FIXME
# merge all source revisions into target
# FIXME
# attach merged revisions onto source revision 1.1
# (and create a dead revision 1.1 if none exists)
# FIXME
# save target file
my $dirname = dirname($opt->{'cvsroot-target'} . "/". $source);
mkpath($dirname) if (not -d $dirname);
$rcs->save($opt->{'cvsroot-target'} . "/". $source);
$rcs->destroy;
}
## _________________________________________________________________________
##
## Utility Functions
## _________________________________________________________________________
##
## TEST
#my $rcs = new RCS;
#my $file = "sample/openpkg-bash.spec,v";
#$rcs->load("$file");
##$rcs->revapply(sub { my ($rev) = @_; $rev =~ s/^1\./1.42.1./; return $rev; });
#$rcs->save("$file.new");
##exit(0);
#foreach my $file (glob("sample/*,v")) {
#print "loading $file\n";
#$rcs->load("$file");
#print "saving $file\n";
#$rcs->save("$file.new");
#}
#undef $rcs;
__END__
## _________________________________________________________________________
##
## Manual Page
## _________________________________________________________________________
##
=pod
=head1 NAME
B<OSSP cvsfusion> - CVS Repository Fusion
=head1 SYNOPSIS
B<cvsfusion>
[B<--verbose>]
[B<--tmpdir=>I<dir>]
[B<--cvsroot-source=>I<dir>]
[B<--cvsroot-target=>I<dir>]
[B<--cvs-module=>I<subdir>]
[B<--cvs-branch=>I<tag>B<:>I<rev>]
[B<--prog-rcs=>I<file>]
[B<--prog-co=>I<file>]
[B<--prog-diff=>I<file>]
B<cvsfusion>
[B<--version>]
[B<--help>]
=head1 DESCRIPTION
B<OSSP cvsfusion> is a tool for merging two Concurrent Versions Systems
(CVS) repositories by attaching the trunk (and all its branches) of the
source repository as a regular branch onto the target repository. It
achieves this by directly operating on the level of the CVS underlying
I<Revision Control System> (RCS) files. The intention is to have the
(usually foreign vendor) source repository available as a whole in
the (usually own local) target repository for convenient comparisons,
merges, etc. It is considered a higher-level form of the CVS vendor
branch functionality.
=head1 OPTIONS
=over 4
=item B<--verbose>
...
=item B<--tmpdir=>I<dir>
...
=item B<--cvsroot-source=>I<dir>
...
=item B<--cvsroot-target=>I<dir>
...
=item B<--cvs-module=>I<subdir>
...
=item B<--cvs-branch=>I<tag>B<:>I<rev>
...
=item B<--prog-rcs=>I<file>
...
=item B<--prog-co=>I<file>
...
=item B<--prog-diff=>I<file>
...
=item B<--version>
...
=item B<--help>
...
=back
=head1 SEE ALSO
cvs(1).
=head1 HISTORY
B<OSSP cvsfusion> was implemented in April 2004 for use in B<OpenPKG>
project in order to provide a more powerful contributor environment
where the B<OpenPKG> CVS repository is regularily merged into the local
CVS repository of the contributor.
=head1 AUTHOR
Ralf S. Engelschall E<lt>rse@engelschall.comE<gt>
=cut