#!/usr/opkg/bin/perl ## ## OSSP cvsfusion - CVS Repository Fusion ## Copyright (c) 2004 Ralf S. Engelschall ## Copyright (c) 2004 The OSSP Project ## Copyright (c) 2004 Cable & Wireless ## ## 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 - CVS Repository Fusion =head1 SYNOPSIS B [B<--verbose>] [B<--tmpdir=>I] [B<--cvsroot-source=>I] [B<--cvsroot-target=>I] [B<--cvs-module=>I] [B<--cvs-branch=>IB<:>I] [B<--prog-rcs=>I] [B<--prog-co=>I] [B<--prog-diff=>I] B [B<--version>] [B<--help>] =head1 DESCRIPTION B 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 (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 ... =item B<--cvsroot-source=>I ... =item B<--cvsroot-target=>I ... =item B<--cvs-module=>I ... =item B<--cvs-branch=>IB<:>I ... =item B<--prog-rcs=>I ... =item B<--prog-co=>I ... =item B<--prog-diff=>I ... =item B<--version> ... =item B<--help> ... =back =head1 SEE ALSO cvs(1). =head1 HISTORY B was implemented in April 2004 for use in B project in order to provide a more powerful contributor environment where the B CVS repository is regularily merged into the local CVS repository of the contributor. =head1 AUTHOR Ralf S. Engelschall Erse@engelschall.comE =cut