diff options
| author | fukachan <fukachan> | 2001-03-25 14:31:08 +0000 |
|---|---|---|
| committer | fukachan <fukachan> | 2001-03-25 14:31:08 +0000 |
| commit | bac6fcd72d2c54dd4ba78af90fcb2023a1a8e004 (patch) | |
| tree | 0f1abc0e03e2998ceea82bba39a8d7e53dffb5bd /cpan | |
| parent | c31b61a2958dbfe2c6510684ba1d8e0be727ae56 (diff) | |
| download | fml8-bac6fcd72d2c54dd4ba78af90fcb2023a1a8e004.tar.gz fml8-bac6fcd72d2c54dd4ba78af90fcb2023a1a8e004.tar.bz2 fml8-bac6fcd72d2c54dd4ba78af90fcb2023a1a8e004.zip | |
Initial revision
Diffstat (limited to 'cpan')
44 files changed, 9533 insertions, 0 deletions
diff --git a/cpan/dist/Rcs/ANNOUNCE b/cpan/dist/Rcs/ANNOUNCE new file mode 100644 index 00000000..a0469142 --- /dev/null +++ b/cpan/dist/Rcs/ANNOUNCE @@ -0,0 +1,35 @@ +A recent programming project required the creation of a Revision Control +System (RCS) front end. I started with inline system calls to the RCS +utilities, and by creating multiple parsing functions to extract information +from an RCS archive file. + +I then decided to put all my functions in one place, and created Perl module +Rcs.pm. + +The next step was to change my functions into object methods, and Rcs.pm +became object oriented. + +This Perl module should be of interest to anyone creating an RCS front end. + +Here's a quick and dirty example of how to check in a file. + +use Rcs; + +$obj = Rcs->new; +$obj->file('testfile.pl'); + +# Set RCS archive directory, is './RCS' by default +$obj->rcsdir("./project_tree/archive"); + +# Set working directory, is '.' by default +$obj->workdir("./project_tree/src"); + +$obj->ci('-mRevision Comment'); + +Full documentation may be found at http://www.freter.com/perl/Rcs/Rcs.html + +If there's any interest, I would like to make Rcs.pm publicly available. +Please let me know is you have any suggestions or comments. + +Craig Freter +<craig@freter.com> diff --git a/cpan/dist/Rcs/Changes b/cpan/dist/Rcs/Changes new file mode 100644 index 00000000..4907cae2 --- /dev/null +++ b/cpan/dist/Rcs/Changes @@ -0,0 +1,5 @@ +Revision history for Perl extension Rcs. + +0.01 Mon Oct 19 14:09:04 1998 + - original version; created by h2xs 1.18 + diff --git a/cpan/dist/Rcs/MANIFEST b/cpan/dist/Rcs/MANIFEST new file mode 100644 index 00000000..d770d7bb --- /dev/null +++ b/cpan/dist/Rcs/MANIFEST @@ -0,0 +1,36 @@ +ANNOUNCE +Changes +MANIFEST +Makefile.PL +README +Rcs.html +Rcs.pm +examples/project/RCS/testfile,v +examples/project/RCS/Rcs.pm,v +examples/project/src/Rcs.pm +examples/project/src/testfile +examples/access.pl +examples/ci-tree.pl +examples/ci.pl +examples/co-tree.pl +examples/co.pl +examples/comments.pl +examples/comments2.pl +examples/daterev.pl +examples/dates.pl +examples/green.txt +examples/mark.pl +examples/parse.pl +examples/pause +examples/rcsclean.pl +examples/rcsdiff.pl +examples/revdate.pl +examples/rlog.pl +examples/symbols.pl +examples/symrev.pl +examples/test1.pl +examples/test2.pl +examples/test3.pl +examples/type +examples/unlock.pl +test.pl diff --git a/cpan/dist/Rcs/Makefile.PL b/cpan/dist/Rcs/Makefile.PL new file mode 100644 index 00000000..62b7f389 --- /dev/null +++ b/cpan/dist/Rcs/Makefile.PL @@ -0,0 +1,7 @@ +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. +WriteMakefile( + 'NAME' => 'Rcs', + 'VERSION_FROM' => 'Rcs.pm', # finds $VERSION +); diff --git a/cpan/dist/Rcs/README b/cpan/dist/Rcs/README new file mode 100644 index 00000000..c4aa6b2c --- /dev/null +++ b/cpan/dist/Rcs/README @@ -0,0 +1,30 @@ +This is Perl Object Class Rcs.pm 0.09, a front-end to Revision Contron System (RCS) +Utilities. + +New in Version 0.07: +NT port by Jamie O'Shaughnessy, <jamie@thanatar.demon.co.uk> +Added methods 'daterev', 'symrev', and 'symbols' contributed by Jamie O'Shaughnessy. +Added method 'comments'. + +New in Version 0.08: +Fixed bug parsing comments. Bug found by David Green <greendjf@cvhp152.gpt.co.uk> +Modified daterev method to accept 1 or 6 arguments. + +New in Version 0.09: +Fixed bug parsing comments. Bug found by David Green <greendjf@cvhp152.gpt.co.uk> + +Install Rcs.pm as you would any Perl 5 module: + + perl Makefile.PL + make + make install + +The documentation is contained in Rcs.pm in pod format. The man pages +will install automatically, or may be extracted manually by: + + pod2man Rcs.pm > Rcs.man + +Please let me know is you have any suggestions or comments. + +Craig Freter +<craig@freter.com> diff --git a/cpan/dist/Rcs/Rcs.html b/cpan/dist/Rcs/Rcs.html new file mode 100644 index 00000000..c576665c --- /dev/null +++ b/cpan/dist/Rcs/Rcs.html @@ -0,0 +1,658 @@ +<HTML> +<HEAD> +<TITLE>Rcs - Perl Object Class for Revision Control System.</TITLE> +<LINK REV="made" HREF="mailto:freter@postal.pcy.mci.net"> +</HEAD> + +<BODY> + +<!-- INDEX BEGIN --> + +<UL> + + <LI><A HREF="#NAME">NAME</A> + <LI><A HREF="#SYNOPSIS">SYNOPSIS</A> + <LI><A HREF="#DESCRIPTION">DESCRIPTION</A> + <UL> + + <LI><A HREF="#OBJECT_CONSTRUCTOR">OBJECT CONSTRUCTOR</A> + <LI><A HREF="#CLASS_METHODS">CLASS METHODS</A> + <LI><A HREF="#OBJECT_ATTRIBUTE_METHODS">OBJECT ATTRIBUTE METHODS</A> + <LI><A HREF="#RCS_PARSE_METHODS">RCS PARSE METHODS</A> + <LI><A HREF="#RCS_SYSTEM_METHODS">RCS SYSTEM METHODS</A> + </UL> + + <LI><A HREF="#EXAMPLES">EXAMPLES</A> + <UL> + + <LI><A HREF="#CREATE_ACCESS_LIST">CREATE ACCESS LIST</A> + <LI><A HREF="#PARSE_RCS_ARCHIVE_FILE">PARSE RCS ARCHIVE FILE</A> + <LI><A HREF="#CHECK_IN_FILE">CHECK-IN FILE</A> + <LI><A HREF="#CHECK_OUT_FILE">CHECK-OUT FILE</A> + <LI><A HREF="#RCSDIFF">RCSDIFF</A> + <LI><A HREF="#RCSCLEAN">RCSCLEAN</A> + </UL> + + <LI><A HREF="#AUTHOR">AUTHOR</A> + <LI><A HREF="#CONTRIBUTORS">CONTRIBUTORS</A> + <LI><A HREF="#COPYRIGHT">COPYRIGHT</A> +</UL> +<!-- INDEX END --> + +<HR> +<P> +<H1><A NAME="NAME">NAME</A></H1> +<P> +Rcs - Perl Object Class for Revision Control System (RCS). + +<P> +<HR> +<H1><A NAME="SYNOPSIS">SYNOPSIS</A></H1> +<P> +<PRE> use Rcs; +</PRE> +<P> +<HR> +<H1><A NAME="DESCRIPTION">DESCRIPTION</A></H1> +<P> +This Perl module provides an object oriented interface to access +<STRONG>Revision Control System (RCS)</STRONG> utilities. RCS must be installed on the system prior to using this module. +This module should simplify the creation of an RCS front-end. + +<P> +<HR> +<H2><A NAME="OBJECT_CONSTRUCTOR">OBJECT CONSTRUCTOR</A></H2> +<P> +The <STRONG>new</STRONG> method may be used as either a class method or an object method to create a +new object. + +<P> +<PRE> # called as class method + $obj = Rcs->new; +</PRE> +<P> +<PRE> # called as object method + $newobj = $obj->new; +</PRE> +<P> +<HR> +<H2><A NAME="CLASS_METHODS">CLASS METHODS</A></H2> +<P> +Besides the object constructor, there are three class methods provided +which effect any newly created objects. + +<P> +The <STRONG>arcext</STRONG> method sets the RCS archive extension, which is ',v' by default. + +<P> +<PRE> # set/unset RCS archive extension + Rcs->arcext(''); # set no archive extension + Rcs->arcext(',v'); # set archive extension to ',v' + $arc_ext = Rcs->arcext(); # get current archive extension +</PRE> +<P> +The <STRONG>bindir</STRONG> method sets the directory path where the RCS executables (i.e. rcs, ci, co) +are located. The default location is '/usr/local/bin'. + +<P> +<PRE> # set RCS bin directory + Rcs->bindir('/usr/bin'); +</PRE> +<P> +<PRE> # access RCS bin directory + $bin_dir = Rcs->bindir; +</PRE> +<P> +The <STRONG>quiet</STRONG> method sets/unsets the quiet mode for the RCS executables. Quiet mode is +set by default. + +<P> +<PRE> # set/unset RCS quiet mode + Rcs->quiet(0); # unset quiet mode + Rcs->quiet(1); # set quiet mode +</PRE> +<P> +<PRE> # access RCS quiet mode + $quiet_mode = Rcs->quiet; +</PRE> +<P> +These methods may also be called as object methods. + +<P> +<PRE> $obj->arcext(''); + $obj->bindir('/usr/bin'); + $obj->quiet(0); +</PRE> +<P> +<HR> +<H2><A NAME="OBJECT_ATTRIBUTE_METHODS">OBJECT ATTRIBUTE METHODS</A></H2> +<P> +These methods set the attributes of the RCS object. + +<P> +The <STRONG>file</STRONG> method is used to set the name of the RCS working file. The filename must +be set before invoking any access of modifier methods on the object. + +<P> +<PRE> $obj->file('mr_anderson.pl'); +</PRE> +<P> +The <STRONG>arcfile</STRONG> method is used to set the name of the RCS archive file. Using this method +is optional, as the other methods will assume the archive filename is the +same as the working file unless specified otherwise. The RCS archive +extension (default ',v') is automatically added to the filename. + +<P> +<PRE> $obj->arcfile('principle_mcvicker.pl'); +</PRE> +<P> +The <STRONG>workdir</STRONG> methods set the path of the RCS working directory. If not specified, +default path is '.' (current working directory). + +<P> +<PRE> $obj->workdir('/usr/local/source'); +</PRE> +<P> +The <STRONG>rcsdir</STRONG> methods set the path of the RCS archive directory. If not specified, +default path is './RCS'. + +<P> +<PRE> $obj->rcsdir('/usr/local/archive'); +</PRE> +<P> +<HR> +<H2><A NAME="RCS_PARSE_METHODS">RCS PARSE METHODS</A></H2> +<P> +This class provides methods to directly parse the RCS archive file. + +<P> +The <STRONG>access</STRONG> method returns a list of all user on the access list. + +<P> +<PRE> @access_list = $obj->access; +</PRE> +<P> +The <STRONG>author</STRONG> method returns the author of the revision. The head revision is used if no +revision argument is passed to method. + +<P> +<PRE> # returns the author of revision '1.3' + $author = $obj->author('1.3'); +</PRE> +<P> +<PRE> # returns the authos of the head revision + $author = $obj->author; +</PRE> +<P> +The <STRONG>head</STRONG> method returns the head revision. + +<P> +<PRE> $head = $obj->head; +</PRE> +<P> +The <STRONG>lock</STRONG> method returns the locker of the revision. The method returns null if the +revision is unlocked. The head revision is used if no revision argument is +passed to method. + +<P> +<PRE> # returns locker of revision '1.3' + $locker = $obj->lock('1.3'); +</PRE> +<P> +<PRE> # returns locker of head revision + $locker = $obj->lock; +</PRE> +<P> +The <STRONG>revisions</STRONG> method returns a list of all revisions of archive file. + +<P> +<PRE> @revisions = $obj->revisions; +</PRE> +<P> +The <STRONG>state</STRONG> method returns the state of the revision. The head revision is used if no +revision argument is passed to method. + +<P> +<PRE> # returns state of revision '1.3' + $state = $obj->state('1.3'); +</PRE> +<P> +<PRE> # returns state of head revision + $state = $obj->state; +</PRE> +<P> +The <STRONG>symbol</STRONG> method returns the <CODE>symbol(s)</CODE> associated with a revision. If +called in list context, method returns all symbols associated with +revision. If called in scalar context, method returns last symbol assciated +with a revision. The head revision is used if no revision argument is +passed to method. + +<P> +<PRE> # list context, returns all symbols associated with revision 1.3 + @symbols = $obj->symbol('1.3'); +</PRE> +<P> +<PRE> # list context, returns all symbols associated with head revision + @symbols = $obj->symbol; +</PRE> +<P> +<PRE> # scalar context, returns last symbol associated with revision 1.3 + $symbol = $obj->symbol('1.3'); +</PRE> +<P> +<PRE> # scalar context, returns last symbol associated with head revision + $symbol = $obj->symbol; +</PRE> +<P> +The <STRONG>symbols</STRONG> method returns a hash, keyed by symbol, of all of the revisions associated +with the file. + +<P> +<PRE> %symbols = $obj->symbols; + foreach $sym (keys %symbols) { + $rev = $symbols{$sym}; + } +</PRE> +<P> +The <STRONG>revdate</STRONG> method returns the date of a revision. The returned date format is the same +as the localtime format. When called as a scalar, it returns the system +date number. If called is list context, the list +($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned. + +<P> +<PRE> # scalar mode + $scalar_date = $obj->revdate; + print "Scalar date number = $scalar_date\n"; + $date_str = localtime($scalar_date); + print "Scalar date string = $date_str\n"; +</PRE> +<P> +<PRE> # list mode + @list_date = $obj->revdate; + print "List date = @list_date\n"; +</PRE> +<P> +The <STRONG>dates</STRONG> method returns a hash of revision dates, keyed on revision. The hash values +are system date numbers. When called in scalar mode, the method returns the +most recent revision date. + +<P> +<PRE> # list mode + %DatesHash = obj->dates; + @dates_list = sort {$b<=>$a} values %DatesHash; + $MostRecent = $dates_list[0]; +</PRE> +<P> +<PRE> # scalar mode + $most_recent = $obj->dates; + print "Most recent date = $most_recent\n"; + $most_recent_str = localtime($most_recent); + print "Most recent date string = $most_recent_str\n"; +</PRE> +<P> +The <STRONG>symrev</STRONG> method returns the revision against which a specified symbol was defined. +If the symbol was not defined against any version of this file, 0 is +returned. + +<P> +<PRE> # gets revision that has 'MY_SYMBOL' defined against it + $rev = $obj->symrev('MY_SYMBOL'); +</PRE> +<P> +The <STRONG>daterev</STRONG> method returns revisions which were created before a specified date. Method +may take one or six arguments. If one arguments is passed, then the +argument is a date number. If six arguments are passed, then they represent +a date string. + +<P> +<PRE> # one argument, date number + # gets revisions created before Sun Sep 6 22:23:47 1998 + @revs = $obj->daterev(841436420); +</PRE> +<P> +<PRE> # six argument + # gets revisions created before 25th June 1998 16:45:30 + @revs = $obj->daterev(1998, 6, 25, 16, 45, 30); +</PRE> +<P> +The <STRONG>comments</STRONG> method returns a hash of revision comments, keyed on revision. A key value +of 0 returns the description. + +<P> +<PRE> %comments = $obj->comments; + $description = $comments{0}; + $comment_1_3 = $comments{'1.3'}; +</PRE> +<P> +<HR> +<H2><A NAME="RCS_SYSTEM_METHODS">RCS SYSTEM METHODS</A></H2> +<P> +These methods invoke the RCS system utilities. + +<P> +The <STRONG>ci</STRONG> method calls the RCS ci program. + +<P> +<PRE> # check in, and then check out in unlocked state + $obj->ci('-u'); +</PRE> +<P> +The <STRONG>co</STRONG> method calls the RCS co program. + +<P> +<PRE> # check out in locked state + $obj->co('-l'); +</PRE> +<P> +The <STRONG>rcs</STRONG> method calls the RCS rcs program. + +<P> +<PRE> # lock file + $obj->rcs('-l'); +</PRE> +<P> +The <STRONG>rcsdiff</STRONG> method calls the RCS rcsdiff program. When called in list context, this +method returns the outpout of the rcsdiff program. When called in scalar +context, this method returns the return status of the rcsdiff program. The +return status is 0 for the same, 1 for some differences, and 2 for error +condition. + +<P> +When called without parameters, rcsdiff does a diff between the current +working file, and the last revision checked in. + +<P> +<PRE> # call in list context + @diff_output = $obj->rcsdiff; +</PRE> +<P> +<PRE> # call in scalar context + $changed = $obj->rcsdiff; + if ($changed) { + print "Working file has changed\n"; + } +</PRE> +<P> +Call rcsdiff with parameters to do a diff between any two revisions. + +<P> +<PRE> @diff_output = $obj->rcsdiff('-r1.2', '-r1.1'); +</PRE> +<P> +The <STRONG>rlog</STRONG> method calls the RCS rlog program. This method returns the output of the +rlog program. + +<P> +<PRE> # get complete log output + @rlog_complete = $obj->rlog; +</PRE> +<P> +<PRE> # called with '-h' switch outputs only header information + @rlog_header = $obj->rlog('-h'); + print @rlog_header; +</PRE> +<P> +The <STRONG>rcsclean</STRONG> method calls the RCS rcsclean program. + +<P> +<PRE> # remove working file + $obj->rcsclean; +</PRE> +<P> +<HR> +<H1><A NAME="EXAMPLES">EXAMPLES</A></H1> +<P> +<HR> +<H2><A NAME="CREATE_ACCESS_LIST">CREATE ACCESS LIST</A></H2> +<P> +Using method <STRONG>rcs</STRONG> with the <STRONG>-a</STRONG> switch allows you to add users to the access list of an RCS archive file. + +<P> +<PRE> use Rcs; + $obj = Rcs->new; +</PRE> +<P> +<PRE> $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); +</PRE> +<P> +Methos <STRONG>rcs</STRONG> invokes the RCS utility rcs with the same parameters. + +<P> +<PRE> @users = qw(beavis butthead); + $obj->rcs("-a@users"); +</PRE> +<P> +Calling method <STRONG>access</STRONG> returns list of users on access list. + +<P> +<PRE> $filename = $obj->file; + @access_list = $obj->access; + print "Users @access_list are on the access list of $filename\n"; +</PRE> +<P> +<HR> +<H2><A NAME="PARSE_RCS_ARCHIVE_FILE">PARSE RCS ARCHIVE FILE</A></H2> +<P> +Set class variables and create 'RCS' object. Set bin directory where RCS +programs (e.g. rcs, ci, co) reside. The default is '/usr/local/bin'. This +sets the bin directory for all objects. + +<P> +<PRE> use Rcs; + Rcs->bindir('/usr/bin'); + $obj = Rcs->new; +</PRE> +<P> +Set information regarding RCS object. This information includes name of the +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + +<P> +<PRE> $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); +</PRE> +<P> +<PRE> $head_rev = $obj->head; + $locker = $obj->lock; + $author = $obj->author; + @access = $obj->access; + @revisions = $obj->revisions; +</PRE> +<P> +<PRE> $filename = $obj->file; +</PRE> +<P> +<PRE> if ($locker) { + print "Head revision $head_rev is locked by $locker\n"; + } + else { + print "Head revision $head_rev is unlocked\n"; + } +</PRE> +<P> +<PRE> if (@access) { + print "\nThe following users are on the access list of file $filename\n"; + map { print "User: $_\n"} @access; + } +</PRE> +<P> +<PRE> print "\nList of all revisions of $filename\n"; + foreach $rev (@revisions) { + print "Revision: $rev\n"; + } +</PRE> +<P> +<HR> +<H2><A NAME="CHECK_IN_FILE">CHECK-IN FILE</A></H2> +<P> +Set class variables and create 'RCS' object. Set bin directory where RCS +programs (e.g. rcs, ci, co) reside. The default is '/usr/local/bin'. This +sets the bin directory for all objects. + +<P> +<PRE> use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; +</PRE> +<P> +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + +<P> +<PRE> $obj->file('cornholio.pl'); +</PRE> +<P> +<PRE> # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); +</PRE> +<P> +<PRE> # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); +</PRE> +<P> +Check in file using <STRONG>-u</STRONG> switch. This will check in the file, and will then check out the file in an +unlocked state. The <STRONG>-m</STRONG> switch is used to set the revision comment. + +<P> +Command: + +<P> +<PRE> $obj->ci('-u', '-mRevision Comment'); +</PRE> +<P> +is equivalent to commands: + +<P> +<PRE> $obj->ci('-mRevision Comment'); + $obj->co; +</PRE> +<P> +<HR> +<H2><A NAME="CHECK_OUT_FILE">CHECK-OUT FILE</A></H2> +<P> +Set class variables and create 'RCS' object. Set bin directory where RCS +programs (e.g. rcs, ci, co) reside. The default is '/usr/local/bin'. This +sets the bin directory for all objects. + +<P> +<PRE> use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; +</PRE> +<P> +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + +<P> +<PRE> $obj->file('cornholio.pl'); +</PRE> +<P> +<PRE> # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); +</PRE> +<P> +<PRE> # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); +</PRE> +<P> +Check out file read-only: + +<P> +<PRE> $obj->co; +</PRE> +<P> +or check out and lock file: + +<P> +<PRE> $obj->co('-l'); +</PRE> +<P> +<HR> +<H2><A NAME="RCSDIFF">RCSDIFF</A></H2> +<P> +Method <STRONG>rcsdiff</STRONG> does an diff between revisions. + +<P> +<PRE> $obj = Rcs->new; + $obj->bindir('/usr/bin'); +</PRE> +<P> +<PRE> $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); +</PRE> +<P> +<PRE> print "Diff of current working file\n"; + if ($obj->rcsdiff) { # scalar context + print $obj->rcsdiff; # list context + } + else { + print "Versions are Equal\n"; + } +</PRE> +<P> +<PRE> print "\n\nDiff of revisions 1.2 and 1.1\n"; + print $obj->rcsdiff('-r1.2', '-r1.1'); +</PRE> +<P> +<HR> +<H2><A NAME="RCSCLEAN">RCSCLEAN</A></H2> +<P> +Method <STRONG>rcsclean</STRONG> will remove an unlocked working file. + +<P> +<PRE> use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; +</PRE> +<P> +<PRE> $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); +</PRE> +<P> +<PRE> print "Quiet mode NOT set\n" unless Rcs->quiet; +</PRE> +<P> +<PRE> $obj->rcsclean; +</PRE> +<P> +<HR> +<H1><A NAME="AUTHOR">AUTHOR</A></H1> +<P> +Craig Freter, <<EM>craig@freter.com</EM>> + + + +<P> +<HR> +<H1><A NAME="CONTRIBUTORS">CONTRIBUTORS</A></H1> +<P> +David Green, <<EM>greendjf@cvhp152.gpt.co.uk</EM>> + + + +<P> +Jamie O'Shaughnessy, <<EM>jamie@thanatar.demon.co.uk</EM>> + + + +<P> +<HR> +<H1><A NAME="COPYRIGHT">COPYRIGHT</A></H1> +<P> +Copyright (C) 1997,1998 Craig Freter. All rights reserved. This program is +free software; you can redistribute it and/or modify it under the same +terms as Perl itself. + +</BODY> + +</HTML> diff --git a/cpan/dist/Rcs/Rcs.pm b/cpan/dist/Rcs/Rcs.pm new file mode 100644 index 00000000..1c6e2f4b --- /dev/null +++ b/cpan/dist/Rcs/Rcs.pm @@ -0,0 +1,1343 @@ +package Rcs; +require 5.001; +use strict; +use Carp; +use Time::Local; +use vars qw($VERSION $revision); + +#------------------------------------------------------------------ +# global stuff +#------------------------------------------------------------------ +$VERSION = '0.09'; +$revision = '$Id: Rcs.pm,v 1.14.1.3 1998/10/19 18:04:48 freter Exp $'; +my $Dir_Sep = ($^O eq 'MSWin32') ? '\\' : '/'; +my $Exe_Ext = ($^O eq 'MSWin32') ? '.exe' : ''; +my $Rcs_Bin_Dir = '/usr/local/bin'; +my $Rcs_Dir = '.' . $Dir_Sep . 'RCS'; +my $Work_Dir = '.'; +my $Quiet = 1; # RCS quiet mode +my $Arc_Ext = ',v'; + +#------------------------------------------------------------------ +# RCS object constructor +#------------------------------------------------------------------ +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + + # provide default values for system stuff + $self->{"_BINDIR"} = \$Rcs_Bin_Dir; + $self->{"_QUIET"} = \$Quiet; + $self->{"_RCSDIR"} = \$Rcs_Dir; + $self->{"_WORKDIR"} = \$Work_Dir; + $self->{"_ARCEXT"} = \$Arc_Ext; + + $self->{FILE} = undef; + $self->{ARCFILE} = undef; + $self->{AUTHOR} = undef; + $self->{COMMENTS} = undef; + $self->{DATE} = undef; + $self->{LOCK} = undef; + $self->{ACCESS} = []; + $self->{REVISIONS} = []; + $self->{REVINFO} = undef; + $self->{STATE} = undef; + $self->{SYMBOLS} = undef; + bless($self, $class); + return $self; +} + +#------------------------------------------------------------------ +# access +# Access list of archive file. +#------------------------------------------------------------------ +sub access { + my $self = shift; + + if (not @{ $self->{ACCESS} }) { + _parse_rcs_header($self); + } + + # dereference revisions list + my @access = @{ $self->{ACCESS} }; + + return @access; +} + +#------------------------------------------------------------------ +# arcext +# Set the RCS archive file extension (default is ',v'). +#------------------------------------------------------------------ +sub arcext { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_ARCEXT"} } = shift }; + return ${ $self->{"_ARCEXT"} }; + } + + # called as class method + else { + if (@_) { $Arc_Ext = shift; } + return $Arc_Ext; + } +} + +#------------------------------------------------------------------ +# arcfile +# Name of RCS archive file. +# If not set then return name of working file with RCS +# extension (',v'). +#------------------------------------------------------------------ +sub arcfile { + my $self = shift; + if (@_) { $self->{ARCFILE} = shift } + return $self->{ARCFILE} || $self->{FILE} . ${ $self->{"_ARCEXT"} }; +} + +#------------------------------------------------------------------ +# author +# Return the author of an RCS revision. +# If revision is not provided, default to 'head' revision. +#------------------------------------------------------------------ +sub author { + my $self = shift; + + if (not defined $self->{AUTHOR}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference author hash + my %author_array = %{ $self->{AUTHOR} }; + + return $author_array{$revision}; +} + +#------------------------------------------------------------------ +# bindir +# Set the bin directory in which the RCS distribution programs +# reside. +#------------------------------------------------------------------ +sub bindir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_BINDIR"} } = shift }; + return ${ $self->{"_BINDIR"} }; + } + + # called as class method + else { + if (@_) { $Rcs_Bin_Dir = shift }; + return $Rcs_Bin_Dir; + } +} + +#------------------------------------------------------------------ +# ci +# Execute RCS 'ci' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub ci { + my $self = shift; + my @param = @_; + + my $ciprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'ci' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "ci program $ciprog not found" unless -e $ciprog; + croak "ci program $ciprog not executable" unless -x $ciprog; + system($ciprog, @param) == 0 or croak "$!"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# co +# Execute RCS 'co' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub co { + my $self = shift; + my @param = @_; + + my $coprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'co' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "co program $coprog not found" unless -e $coprog; + croak "co program $coprog not executable" unless -x $coprog; + system($coprog, @param) == 0 or croak "$!"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# comments +#------------------------------------------------------------------ +sub comments { + my $self = shift; + + if (not defined $self->{COMMENTS}) { + _parse_rcs_body($self); + } + + return %{$self->{COMMENTS}}; +} + +#------------------------------------------------------------------ +# daterev +# +# Returns revisions which were created before a specified date. +# +# Method takes one or six arguments. +# +# If one argument, then argument is date number. +# +# If six arguments, then year (4 digit year), month (1-12), day +# of month (1-31), hour (0-23), minute (0-59) and second (0-59). +#------------------------------------------------------------------ +sub daterev { + + my $self = shift; + my $target_time; + + # validate arguments + unless (@_ == 1 or @_ == 6) { + croak "daterev must have either 1 or 6 arguments"; + } + + # string date passed + if (@_ == 6) { + my($year, $mon, $mday, $hour, $min, $sec) = @_; + + if($year !~ /^\d{4}$/) { + croak "year (1st param) must be 4 digit number"; + } + + $mon--; # convert to 0-11 range + $target_time = timegm($sec, $min, $hour, $mday, $mon, $year); + } + + # system date passed + else { + $target_time = shift; + + if ($target_time !~ /^\d+$/) { + croak "system date must be an integer"; + } + } + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + + my @revisions = (); + my %dates; + my %dates_hash = %{$self->{DATE}}; + + my $revision; + foreach $revision (keys %dates_hash) { + my $date = $dates_hash{$revision}; + $dates{$date}{$revision} = 1; + } + + my $date; + foreach $date (reverse sort keys %dates) { + foreach $revision (keys %{ $dates{$date} }) { + push @revisions, $revision if $date <= $target_time; + } + } + + return wantarray ? @revisions : $revisions[0]; +} + +#------------------------------------------------------------------ +# dates +# Return a hash of revision dates, keyed on revision, when called +# in list mode. +# Return the most recent date when called in scalar mode. +# +# RCS stores dates in GMT. +# The date values are system dates. +#------------------------------------------------------------------ +sub dates { + my $self = shift; + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + + my %DatesHash = %{$self->{DATE}}; + my @dates_list = sort {$b<=>$a} values %DatesHash; + my $MostRecent = $dates_list[0]; + + return wantarray ? %DatesHash : $MostRecent; +} + +#------------------------------------------------------------------ +# file +# Name of working file. +#------------------------------------------------------------------ +sub file { + my $self = shift; + if (@_) { $self->{FILE} = shift } + return $self->{FILE}; +} + +#------------------------------------------------------------------ +# head +# Return the head revision. +#------------------------------------------------------------------ +sub head { + my $self = shift; + + if (not defined $self->{HEAD}) { + _parse_rcs_header($self); + } + return $self->{HEAD}; +} + +#------------------------------------------------------------------ +# lock +# Return user who has file locked. +#------------------------------------------------------------------ +sub lock { + my $self = shift; + + if (not defined $self->{LOCK}) { + _parse_rcs_header($self); + } + return $self->{LOCK}; +} + +#------------------------------------------------------------------ +# quiet +# Set or un-set RCS quiet mode. +#------------------------------------------------------------------ +sub quiet { + my $self = shift; + + # called as object method + if (ref $self) { + + # set/un-set quiet mode + if (@_) { + my $mode = shift; + croak "Passed parameter must be either '0' or '1'" + unless $mode == 0 or $mode == 1; + ${ $self->{"_QUIET"} } = $mode; + return ${ $self->{"_QUIET"} }; + } + + # access quiet mode + else { + return ${ $self->{"_QUIET"} }; + } + } + + # called as class method + else { + + # set/un-set quiet mode + if (@_) { + my $mode = shift; + croak "Passed parameter must be either '0' or '1'" + unless $mode == 0 or $mode == 1; + $Quiet = $mode; + return $Quiet; + } + + # access quiet mode + else { + return $Quiet; + } + } +} + +#------------------------------------------------------------------ +# rcs +# Execute RCS 'rcs' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub rcs { + my $self = shift; + my @param = @_; + + my $rcsprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcs' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "rcs program $rcsprog not found" unless -e $rcsprog; + croak "rcs program $rcsprog not executable" unless -x $rcsprog; + system($rcsprog, @param) == 0 or croak "$?"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# rcsclean +# Execute RCS 'rcsclean' program. +#------------------------------------------------------------------ +sub rcsclean { + my $self = shift; + my @param = @_; + + my $rcscleanprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsclean' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + + # run program + croak "rcsclean program $rcscleanprog not found" unless -e $rcscleanprog; + croak "rcsclean program $rcscleanprog not executable" unless -x $rcscleanprog; + system($rcscleanprog, @param) == 0 or croak "$?"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# rcsdiff +# Execute RCS 'rcsdiff' program. +# Calling in list context returns the output of rcsdiff, while +# calling in scalar context returns the return status of the +# rcsdiff program. +#------------------------------------------------------------------ +sub rcsdiff { + my $self = shift; + my @param = @_; + + my $rcsdiff_prog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsdiff' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $arcfile = $self->{ARCFILE} || $self->{FILE}; + $arcfile = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $self->workdir . $Dir_Sep . $self->file; + + # un-taint parameter string + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + my $param_str = join(' ', @param); + $param_str =~ s/([\w-]+)/$1/g; + + croak "rcsdiff program $rcsdiff_prog not found" unless -e $rcsdiff_prog; + croak "rcsdiff program $rcsdiff_prog not executable" unless -x $rcsdiff_prog; + open(DIFF, "$rcsdiff_prog $param_str $arcfile $workfile |"); + my @diff_output = <DIFF>; + + # rcsdiff returns exit status 0 for no differences, 1 for differences, + # and 2 for error condition. + close DIFF; + my $status = $?; + croak "$rcsdiff_prog failed" if $status == 2; + return wantarray ? @diff_output : $status; +} + +#------------------------------------------------------------------ +# rcsdir +# Location of 'RCS' archive directory. +#------------------------------------------------------------------ +sub rcsdir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_RCSDIR"} } = shift } + return ${ $self->{"_RCSDIR"} }; + } + + # called as class method + else { + if (@_) { $Rcs_Dir = shift } + return $Rcs_Dir; + } +} + +#------------------------------------------------------------------ +# revdate +# Return the revision date of an RCS revision. +# If revision is not provided, default to 'head' revision. +# +# RCS stores dates in GMT. This method will return dates relative +# to the local time zone. +#------------------------------------------------------------------ +sub revdate { + my $self = shift; + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference date hash + my %date_array = %{ $self->{DATE} }; + my $date_str = $date_array{$revision}; + + return wantarray ? localtime($date_str) : $date_str; +} + +#------------------------------------------------------------------ +# revisions +#------------------------------------------------------------------ +sub revisions { + my $self = shift; + + if (not @{ $self->{REVISIONS} }) { + _parse_rcs_header($self); + } + + # dereference revisions list + my @revisions = @{ $self->{REVISIONS} }; + + @revisions; +} + +#------------------------------------------------------------------ +# rlog +# Execute RCS 'rlog' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub rlog { + my $self = shift; + my @param = @_; + + my $rlogprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rlog' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $arcfile = $self->{ARCFILE} || $self->{FILE}; + + # un-taint parameter string + my $param_str = join(' ', @param); + $param_str =~ s/([\w-]+)/$1/g; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + croak "rlog program $rlogprog not found" unless -e $rlogprog; + croak "rlog program $rlogprog not executable" unless -x $rlogprog; + open(RLOG, "$rlogprog $param_str $archive_file |"); + + my @logoutput = <RLOG>; + close RLOG; + croak "$rlogprog failed" if $?; + @logoutput; +} + +#------------------------------------------------------------------ +# state +# If revision is not provided, default to 'head' revision +#------------------------------------------------------------------ +sub state { + my $self = shift; + + if (not defined $self->{STATE}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference author hash + my %state_array = %{ $self->{STATE} }; + + return $state_array{$revision}; +} + +#------------------------------------------------------------------ +# symbol +# If revision is not provided, default to 'head' revision +#------------------------------------------------------------------ +sub symbol { + my $self = shift; + + if (not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference symbols hash + my %sym_array = %{ $self->{SYMBOLS} }; + + return '' if not defined $sym_array{$revision}; + + my @symbols = @{ $sym_array{$revision} }; + + # return only first array element if user wants scalar + return wantarray ? @symbols : $symbols[0]; +} + +#------------------------------------------------------------------ +# symbols +# Returns hash of all revisions keyed on symbol defined against file. +#------------------------------------------------------------------ +sub symbols { + my $self = shift; + + if(not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + + my %symbols; + + # loop through each revision + my $rev; + foreach $rev (@{ $self->{REVISIONS} }) { + my $sym; + foreach $sym (@{ $self->{SYMBOLS}->{$rev} }) { + $symbols{$sym} = $rev; + } + } + return %symbols; +} + +#------------------------------------------------------------------ +# symrev +# Returns the revision against which a specified symbol was +# defined. If the symbol was not defined against any version +# of this file, 0 is returned. +#------------------------------------------------------------------ +sub symrev { + my $self = shift; + my $sym = shift; + if(! defined $sym) { + croak "You must supply a symbol to symrev"; + } + + if (not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + + my $ret_rev = 0; + my %symbols; + + # loop through each revision + my $rev; + REV_LOOP: + foreach $rev (@{ $self->{REVISIONS} }) { + # loop through each symbol defined against + # this revision + my $s; + foreach $s (@{ $self->{SYMBOLS}->{$rev} }) { + + # store each revision matching the pattern + if (wantarray) { + $symbols{$s} = $rev if $s =~ /$sym/; + } + + # if it's the one we're looking for, we can + # quit as we've found the revision we want + else { + if($s eq $sym) { + $ret_rev = $rev; + last REV_LOOP; + } + } + } + } + + return wantarray ? %symbols : $ret_rev; +} + +#------------------------------------------------------------------ +# workdir +# Location of working directory. +#------------------------------------------------------------------ +sub workdir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_WORKDIR"} } = shift } + return ${ $self->{"_WORKDIR"} }; + } + + # called as class method + else { + if (@_) { $Work_Dir = shift } + return $Work_Dir; + } +} + +#------------------------------------------------------------------ +# _parse_rcs_body +# Private function +#------------------------------------------------------------------ +sub _parse_rcs_body { + + my $self = shift; + local $_; + + my %comments; + + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $file = $self->{FILE}; + my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} }; + + # parse RCS archive file + open RCS_FILE, $rcs_file or croak "Unable to open $rcs_file"; + + # skip header info and get description + DESC: while (<RCS_FILE>) { + if (/^desc$/) { + $comments{0} = ''; + $_ = <RCS_FILE>; # read first line + s/^\@//; # remove leading '@' + while (1) { + last DESC if /^\@$/; + s/\@\@/\@/g; # RCS replaces single '@' with '@@' + $comments{0} .= $_; + $_ = <RCS_FILE>; + } + } + } + + # parse revision comments + my $revision; + REVISION: while (<RCS_FILE>) { + if (/^[\d\.]+$/) { + chomp($revision = $_); + $_ = <RCS_FILE>; + if (/^log$/) { + $comments{$revision} = ''; + $_ = <RCS_FILE>; # read first line + s/^\@//; # remove leading '@' + while (1) { + next REVISION if /^\@$/; + s/\@\@/\@/g; # RCS replaces single '@' with '@@' + $comments{$revision} .= $_; + $_ = <RCS_FILE>; + } + } + } + } + + # loop through 'text' section to avoid capturing bogus info + continue { + if (/^text$/) { # 'text' tag should always be there, but check anyway + while (<RCS_FILE>) { + s/\@\@//g; # RCS replaces single '@' with '@@' + last if /\@$/ + } + } + } + + close RCS_FILE; + $self->{COMMENTS} = \%comments; +} + +#------------------------------------------------------------------ +# _parse_rcs_header +# Private function +# Directly parse the RCS archive file. +#------------------------------------------------------------------ +sub _parse_rcs_header { + + my $self = shift; + local $_; + + my ($head, $lock); + my (@access_list, @revisions); + my (%author, %date, %state, %symbols); + + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $file = $self->{FILE}; + my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} }; + + # parse RCS archive file + open RCS_FILE, $rcs_file + or croak "Unable to open $rcs_file"; + while (<RCS_FILE>) { + next if /^\s*$/; # skip blank lines + last if /^desc$/; # end of header info + + # get head revision + if (/^head\s/) { + ($head) = /^head\s+(.*?);$/; + next; + } + + # get access list + if (/^access$/) { + while (<RCS_FILE>) { + chomp; + s/\s//g; # remove all whitespace + push @access_list, (split(/;/))[0]; + last if /;$/; + } + next; + } + + # get locker + # get symbols + if (/^symbols$/) { + while (<RCS_FILE>) { + chomp; + s/\s//g; # remove all whitespace + my ($sym, $rev) = split(/:/); + $rev =~ s/;$//; + push @{ $symbols{$rev} }, $sym; + last if /;$/; + } + next; + } + + # get locker + if (/^locks/) { + + # file not locked + if (/strict/) { + $lock = ''; + next; + } + + # get user who has file locked + my $next_line = <RCS_FILE>; # read next line + ($lock) = $next_line =~ m/^\s*(\w+):/; + next; + } + + # get all revisions + if (/^\d+\.\d+/) { + chomp; + push @revisions, $_; + + # get author, state and date of each revision + my $next_line = <RCS_FILE>; + chop(my $author = (split(/\s+/, $next_line))[3]); + chop(my $state = (split(/\s+/, $next_line))[5]); + chop(my $date = (split(/\s+/, $next_line))[1]); + + # store date as date number + my ($year, $mon, $mday, $hour, $min, $sec) = split(/\./, $date); + $mon--; # convert to 0-11 range + my @date = ($sec,$min,$hour,$mday,$mon,$year); + + # store value in hash using revision as key + $author{$_} = $author; + $state{$_} = $state; + $date{$_} = timegm(@date); + } + } + close RCS_FILE; + + $self->{HEAD} = $head; + $self->{LOCK} = $lock; + $self->{ACCESS} = \@access_list; + $self->{REVISIONS} = \@revisions; + $self->{AUTHOR} = \%author; + $self->{DATE} = \%date; + $self->{STATE} = \%state; + $self->{SYMBOLS} = \%symbols; +} + +1; + +__END__ + +=head1 NAME + +Rcs - Perl Object Class for Revision Control System (RCS). + +=head1 SYNOPSIS + + use Rcs; + +=head1 DESCRIPTION + +This Perl module provides an object oriented interface to access +B<Revision Control System (RCS)> utilities. RCS must be installed on +the system prior to using this module. This module should simplify +the creation of an RCS front-end. + +=head2 OBJECT CONSTRUCTOR + +The B<new> method may be used as either a class method or an object +method to create a new object. + + # called as class method + $obj = Rcs->new; + + # called as object method + $newobj = $obj->new; + +=head2 CLASS METHODS + +Besides the object constructor, there are three class methods provided +which effect any newly created objects. + +The B<arcext> method sets the RCS archive extension, which is ',v' by +default. + + # set/unset RCS archive extension + Rcs->arcext(''); # set no archive extension + Rcs->arcext(',v'); # set archive extension to ',v' + $arc_ext = Rcs->arcext(); # get current archive extension + +The B<bindir> method sets the directory path where the RCS executables +(i.e. rcs, ci, co) are located. The default location is '/usr/local/bin'. + + # set RCS bin directory + Rcs->bindir('/usr/bin'); + + # access RCS bin directory + $bin_dir = Rcs->bindir; + +The B<quiet> method sets/unsets the quiet mode for the RCS executables. +Quiet mode is set by default. + + # set/unset RCS quiet mode + Rcs->quiet(0); # unset quiet mode + Rcs->quiet(1); # set quiet mode + + # access RCS quiet mode + $quiet_mode = Rcs->quiet; + +These methods may also be called as object methods. + + $obj->arcext(''); + $obj->bindir('/usr/bin'); + $obj->quiet(0); + +=head2 OBJECT ATTRIBUTE METHODS + +These methods set the attributes of the RCS object. + +The B<file> method is used to set the name of the RCS working file. The +filename must be set before invoking any access of modifier methods on the +object. + + $obj->file('mr_anderson.pl'); + +The B<arcfile> method is used to set the name of the RCS archive file. +Using this method is optional, as the other methods will assume the archive +filename is the same as the working file unless specified otherwise. The +RCS archive extension (default ',v') is automatically added to the filename. + + $obj->arcfile('principle_mcvicker.pl'); + +The B<workdir> methods set the path of the RCS working directory. If not +specified, default path is '.' (current working directory). + + $obj->workdir('/usr/local/source'); + +The B<rcsdir> methods set the path of the RCS archive directory. If not +specified, default path is './RCS'. + + $obj->rcsdir('/usr/local/archive'); + +=head2 RCS PARSE METHODS + +This class provides methods to directly parse the RCS archive file. + +The B<access> method returns a list of all user on the access list. + + @access_list = $obj->access; + +The B<author> method returns the author of the revision. The head revision +is used if no revision argument is passed to method. + + # returns the author of revision '1.3' + $author = $obj->author('1.3'); + + # returns the authos of the head revision + $author = $obj->author; + +The B<head> method returns the head revision. + + $head = $obj->head; + +The B<lock> method returns the locker of the revision. The method returns +null if the revision is unlocked. The head revision is used if no revision +argument is passed to method. + + # returns locker of revision '1.3' + $locker = $obj->lock('1.3'); + + # returns locker of head revision + $locker = $obj->lock; + +The B<revisions> method returns a list of all revisions of archive file. + + @revisions = $obj->revisions; + +The B<state> method returns the state of the revision. The head revision +is used if no revision argument is passed to method. + + # returns state of revision '1.3' + $state = $obj->state('1.3'); + + # returns state of head revision + $state = $obj->state; + +The B<symbol> method returns the symbol(s) associated with a revision. +If called in list context, method returns all symbols associated with +revision. If called in scalar context, method returns last symbol +assciated with a revision. The head revision is used if no revision argument +is passed to method. + + # list context, returns all symbols associated with revision 1.3 + @symbols = $obj->symbol('1.3'); + + # list context, returns all symbols associated with head revision + @symbols = $obj->symbol; + + # scalar context, returns last symbol associated with revision 1.3 + $symbol = $obj->symbol('1.3'); + + # scalar context, returns last symbol associated with head revision + $symbol = $obj->symbol; + +The B<symbols> method returns a hash, keyed by symbol, of all of the revisions +associated with the file. + + %symbols = $obj->symbols; + foreach $sym (keys %symbols) { + $rev = $symbols{$sym}; + } + +The B<revdate> method returns the date of a revision. The returned date format +is the same as the localtime format. When called as a scalar, it returns the +system date number. If called is list context, the list +($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned. + + # scalar mode + $scalar_date = $obj->revdate; + print "Scalar date number = $scalar_date\n"; + $date_str = localtime($scalar_date); + print "Scalar date string = $date_str\n"; + + # list mode + @list_date = $obj->revdate; + print "List date = @list_date\n"; + +The B<dates> method returns a hash of revision dates, keyed on revision. The +hash values are system date numbers. When called in scalar mode, the method +returns the most recent revision date. + + # list mode + %DatesHash = obj->dates; + @dates_list = sort {$b<=>$a} values %DatesHash; + $MostRecent = $dates_list[0]; + + # scalar mode + $most_recent = $obj->dates; + print "Most recent date = $most_recent\n"; + $most_recent_str = localtime($most_recent); + print "Most recent date string = $most_recent_str\n"; + +The B<symrev> method returns the revision against which a specified symbol was +defined. If the symbol was not defined against any version of this file, 0 is +returned. + + # gets revision that has 'MY_SYMBOL' defined against it + $rev = $obj->symrev('MY_SYMBOL'); + +The B<daterev> method returns revisions which were created before a specified +date. Method may take one or six arguments. If one arguments is passed, then +the argument is a date number. If six arguments are passed, then they represent +a date string. + + # one argument, date number + # gets revisions created before Sun Sep 6 22:23:47 1998 + @revs = $obj->daterev(841436420); + + # six argument + # gets revisions created before 25th June 1998 16:45:30 + @revs = $obj->daterev(1998, 6, 25, 16, 45, 30); + +The B<comments> method returns a hash of revision comments, keyed on revision. +A key value of 0 returns the description. + + %comments = $obj->comments; + $description = $comments{0}; + $comment_1_3 = $comments{'1.3'}; + +=head2 RCS SYSTEM METHODS + +These methods invoke the RCS system utilities. + +The B<ci> method calls the RCS ci program. + + # check in, and then check out in unlocked state + $obj->ci('-u'); + +The B<co> method calls the RCS co program. + + # check out in locked state + $obj->co('-l'); + +The B<rcs> method calls the RCS rcs program. + + # lock file + $obj->rcs('-l'); + +The B<rcsdiff> method calls the RCS rcsdiff program. When called in +list context, this method returns the outpout of the rcsdiff program. +When called in scalar context, this method returns the return status of +the rcsdiff program. The return status is 0 for the same, 1 for some +differences, and 2 for error condition. + +When called without parameters, rcsdiff does a diff between the current +working file, and the last revision checked in. + + # call in list context + @diff_output = $obj->rcsdiff; + + # call in scalar context + $changed = $obj->rcsdiff; + if ($changed) { + print "Working file has changed\n"; + } + +Call rcsdiff with parameters to do a diff between any two revisions. + + @diff_output = $obj->rcsdiff('-r1.2', '-r1.1'); + +The B<rlog> method calls the RCS rlog program. This method returns the +output of the rlog program. + + # get complete log output + @rlog_complete = $obj->rlog; + + # called with '-h' switch outputs only header information + @rlog_header = $obj->rlog('-h'); + print @rlog_header; + +The B<rcsclean> method calls the RCS rcsclean program. + + # remove working file + $obj->rcsclean; + + +=head1 EXAMPLES + +=head2 CREATE ACCESS LIST + +Using method B<rcs> with the B<-a> switch allows you to add users to +the access list of an RCS archive file. + + use Rcs; + $obj = Rcs->new; + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + +Methos B<rcs> invokes the RCS utility rcs with the same parameters. + + @users = qw(beavis butthead); + $obj->rcs("-a@users"); + +Calling method B<access> returns list of users on access list. + + $filename = $obj->file; + @access_list = $obj->access; + print "Users @access_list are on the access list of $filename\n"; + + +=head2 PARSE RCS ARCHIVE FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of the +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + $head_rev = $obj->head; + $locker = $obj->lock; + $author = $obj->author; + @access = $obj->access; + @revisions = $obj->revisions; + + $filename = $obj->file; + + if ($locker) { + print "Head revision $head_rev is locked by $locker\n"; + } + else { + print "Head revision $head_rev is unlocked\n"; + } + + if (@access) { + print "\nThe following users are on the access list of file $filename\n"; + map { print "User: $_\n"} @access; + } + + print "\nList of all revisions of $filename\n"; + foreach $rev (@revisions) { + print "Revision: $rev\n"; + } + +=head2 CHECK-IN FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->file('cornholio.pl'); + + # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); + + # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); + +Check in file using B<-u> switch. This will check in the file, and will then +check out the file in an unlocked state. The B<-m> switch is used to set the +revision comment. + +Command: + + $obj->ci('-u', '-mRevision Comment'); + +is equivalent to commands: + + $obj->ci('-mRevision Comment'); + $obj->co; + +=head2 CHECK-OUT FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->file('cornholio.pl'); + + # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); + + # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); + +Check out file read-only: + + $obj->co; + +or check out and lock file: + + $obj->co('-l'); + +=head2 RCSDIFF + +Method B<rcsdiff> does an diff between revisions. + + $obj = Rcs->new; + $obj->bindir('/usr/bin'); + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + print "Diff of current working file\n"; + if ($obj->rcsdiff) { # scalar context + print $obj->rcsdiff; # list context + } + else { + print "Versions are Equal\n"; + } + + print "\n\nDiff of revisions 1.2 and 1.1\n"; + print $obj->rcsdiff('-r1.2', '-r1.1'); + +=head2 RCSCLEAN + +Method B<rcsclean> will remove an unlocked working file. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + print "Quiet mode NOT set\n" unless Rcs->quiet; + + $obj->rcsclean; + +=head1 AUTHOR + +Craig Freter, E<lt>F<craig@freter.com>E<gt> + +=head1 CONTRIBUTORS + +David Green, E<lt>F<greendjf@cvhp152.gpt.co.uk>E<gt> + +Jamie O'Shaughnessy, E<lt>F<jamie@thanatar.demon.co.uk>E<gt> + +=head1 COPYRIGHT + +Copyright (C) 1997,1998 Craig Freter. All rights reserved. +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut + diff --git a/cpan/dist/Rcs/examples/access.pl b/cpan/dist/Rcs/examples/access.pl new file mode 100755 index 00000000..96597e5c --- /dev/null +++ b/cpan/dist/Rcs/examples/access.pl @@ -0,0 +1,21 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Add users to access list. +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +Rcs->quiet(0); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +my @users = qw(beavis butthead); +$obj->rcs("-a@users"); + +my $filename = $obj->file; +my @access_list = $obj->access; +print "Users @access_list are on the access list of $filename\n"; diff --git a/cpan/dist/Rcs/examples/ci-tree.pl b/cpan/dist/Rcs/examples/ci-tree.pl new file mode 100755 index 00000000..4917ba1a --- /dev/null +++ b/cpan/dist/Rcs/examples/ci-tree.pl @@ -0,0 +1,46 @@ +#!/usr/bin/perl -w + +use strict; +use File::Find; +use File::Path; +use Rcs; + +Rcs->bindir("/usr/bin"); + +my $comment = shift; + +# Traverse desired filesystems + +my $tree_root = '/home/freter/tmp'; +my $rcs_path = '/RCS'; +my $src_path = '/src'; + +find(\&wanted, $tree_root . $src_path); + +exit; + +sub wanted { + my $relative_path = $File::Find::dir; + ($relative_path) =~ s{^$tree_root$src_path}{}; + print $relative_path; + print "\n"; + mkpath([$tree_root . $rcs_path . $relative_path], 1, 0755); + + return unless -f; + my $obj = Rcs->new; + $obj->file($_); + $obj->rcsdir($tree_root . $rcs_path . $relative_path); + $obj->workdir($tree_root . $src_path . $relative_path); + + # archive file exists + if (! -e $obj->rcsdir . '/' . $obj->arcfile) { + print "Initial Check-in\n"; + $obj->ci("-l", "-t-$comment"); + } + + # create archive file + else { + print "Check-in\n"; + $obj->ci("-l", "-m$comment"); + } +} diff --git a/cpan/dist/Rcs/examples/ci.pl b/cpan/dist/Rcs/examples/ci.pl new file mode 100755 index 00000000..7d1ae42f --- /dev/null +++ b/cpan/dist/Rcs/examples/ci.pl @@ -0,0 +1,28 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Check-in source file. +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +Rcs->quiet(0); # turn off quiet mode +my $obj = Rcs->new; + +print "Quiet mode set\n" if Rcs->quiet; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +# archive file exists +if (! -e $obj->rcsdir . '/' . $obj->arcfile) { + print "Initial Check-in\n"; + $obj->ci("-u"); +} + +# create archive file +else { + print "Check-in\n"; + $obj->ci("-l"); +} diff --git a/cpan/dist/Rcs/examples/co-tree.pl b/cpan/dist/Rcs/examples/co-tree.pl new file mode 100755 index 00000000..bf35b4f2 --- /dev/null +++ b/cpan/dist/Rcs/examples/co-tree.pl @@ -0,0 +1,45 @@ +#!/usr/bin/perl -w + +use strict; +use File::Find; +use File::Path; +use Rcs; + +Rcs->bindir("/usr/bin"); + +my $lock = 0; + +# Traverse desired filesystems + +my $tree_root = '/home/freter/tmp'; +my $rcs_path = '/RCS'; +my $chkpt_path = '/chkpt'; + +find(\&wanted, $tree_root . $rcs_path); + +exit; + +sub wanted { + my $relative_path = $File::Find::dir; + ($relative_path) =~ s{^$tree_root$rcs_path}{}; + print $relative_path; + print "\n"; + mkpath([$tree_root . $chkpt_path . $relative_path], 1, 0755); + + return unless -f; + my $obj = Rcs->new; + s/,v$//; + $obj->file($_); + $obj->rcsdir($tree_root . $rcs_path . $relative_path); + $obj->workdir($tree_root . $chkpt_path . $relative_path); + + # check out and lock + if ($lock) { + $obj->co("-l"); + } + + # check out read only + else { + $obj->co; + } +} diff --git a/cpan/dist/Rcs/examples/co.pl b/cpan/dist/Rcs/examples/co.pl new file mode 100755 index 00000000..de0208db --- /dev/null +++ b/cpan/dist/Rcs/examples/co.pl @@ -0,0 +1,21 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Check-out source file. +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +Rcs->quiet(0); # turn off quiet mode +my $obj = Rcs->new; + +print "Quiet mode set\n" if Rcs->quiet; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); +my $revision = shift || $obj->head; +die "Revision $revision does not exist\n" + unless grep /^$revision$/, $obj->revisions; + +$obj->co("-l${revision}"); diff --git a/cpan/dist/Rcs/examples/comments.pl b/cpan/dist/Rcs/examples/comments.pl new file mode 100755 index 00000000..55b0c055 --- /dev/null +++ b/cpan/dist/Rcs/examples/comments.pl @@ -0,0 +1,24 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Access comments hash +#------------------------------------------ +use strict; +use lib '.'; +use Rcs; + +Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +my %comments = $obj->comments; +my $revision; +foreach $revision (keys %comments) { + #my $comments = $comments{$revision} ? $comments{$revision} : 'GGG'; + my $comments = $comments{$revision}; + print "======\n"; + print "Revision: $revision\n"; + print "$comments###\n"; +} diff --git a/cpan/dist/Rcs/examples/comments2.pl b/cpan/dist/Rcs/examples/comments2.pl new file mode 100755 index 00000000..9a2b7dae --- /dev/null +++ b/cpan/dist/Rcs/examples/comments2.pl @@ -0,0 +1,24 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Access comments hash +#------------------------------------------ +use strict; +use lib '.'; +use Rcs; + +Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("Rcs.pm"); + +my %comments = $obj->comments; +my $revision; +foreach $revision (sort keys %comments) { + #my $comments = $comments{$revision} ? $comments{$revision} : 'GGG'; + my $comments = $comments{$revision}; + print "======\n"; + print "Revision: $revision\n"; + print "$comments###\n"; +} diff --git a/cpan/dist/Rcs/examples/daterev.pl b/cpan/dist/Rcs/examples/daterev.pl new file mode 100755 index 00000000..f6b7c6ac --- /dev/null +++ b/cpan/dist/Rcs/examples/daterev.pl @@ -0,0 +1,53 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Test daterev method +#------------------------------------------ +use strict; +use Time::Local; +use lib '.'; +use Rcs; + +Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +my @date_array = @ARGV; +my($year, $mon, $mday, $hour, $min, $sec) = @date_array; +$mon--; # convert to 0-11 range +my $target_time = timegm($sec, $min, $hour, $mday, $mon, $year); + +print "Called as 6 argument method\n"; +# scalar mode +my $revision = $obj->daterev(@date_array); +my $date_str = gmtime($obj->revdate($revision)); +print "Date : Revision = $date_str : $revision\n\n"; + + +# list mode +print "List mode\n"; +my @revisions = $obj->daterev(@date_array); +foreach (@revisions) { + $date_str = gmtime($obj->revdate($_)); + print "Date : Revision = $date_str : $_\n"; +} + + +print "\n\n\n"; +print "Called as 1 argument method\n"; +print "Time number is $target_time\n"; +$revision = $obj->daterev($target_time); +$date_str = gmtime($obj->revdate($revision)); +print "Date : Revision = $date_str : $revision\n\n"; + + +# list mode +print "List mode\n"; +@revisions = $obj->daterev($target_time); +foreach (@revisions) { + $date_str = gmtime($obj->revdate($_)); + print "Date : Revision = $date_str : $_\n"; +} + diff --git a/cpan/dist/Rcs/examples/dates.pl b/cpan/dist/Rcs/examples/dates.pl new file mode 100755 index 00000000..2d82a800 --- /dev/null +++ b/cpan/dist/Rcs/examples/dates.pl @@ -0,0 +1,35 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Access dates hash +#------------------------------------------ +use strict; +use Rcs; + +#Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +# sort by date +my %dates_hash = $obj->dates; +my $revision; +my %dates; +foreach $revision (keys %dates_hash) { + my $date = $dates_hash{$revision}; + $dates{$date}{$revision} = 1; +} + +my $date; +foreach $date (reverse sort keys %dates) { + foreach $revision (keys %{ $dates{$date} }) { + my $date_str = localtime($date); + print "Revision : Date = $revision : $date_str\n"; + } +} + +# scalar mode returns most recent date +print "\n"; +my $most_recent = localtime($obj->dates); +print "Most recent revision date = $most_recent\n"; diff --git a/cpan/dist/Rcs/examples/green.txt b/cpan/dist/Rcs/examples/green.txt new file mode 100644 index 00000000..8c5b40d2 --- /dev/null +++ b/cpan/dist/Rcs/examples/green.txt @@ -0,0 +1,85 @@ +From - Fri Sep 4 19:24:30 1998 +Return-Path: <greendjf@cvhp152.gpt.co.uk> +Received: from cvis01.gpt.co.uk (cvis01.gpt.co.uk [195.99.244.34]) + by onella.admc.com (8.9.0/8.9.0) with SMTP id DAA00464 + for <craig@admc.com>; Thu, 3 Sep 1998 03:45:15 -0400 +Received: from cvfw01 by cvis01.gpt.co.uk with SMTP + (SMI-8.6/GPT-01b5) id IAA03221; Thu, 3 Sep 1998 08:34:39 +0100 +Received: from cvhp152 (cvhp152.gpt.co.uk) by cvhp99.gpt.co.uk with SMTP + (1.37.109.17/99-21) id AA007258250; Thu, 3 Sep 1998 08:37:30 +0100 +Received: from cvhp267.gpt.co.uk by cvhp152 with SMTP + (1.38.193.4/MAIL-15) id AA12641; Thu, 3 Sep 1998 08:37:30 +0100 +Sender: greendjf@cvhp152.gpt.co.uk +Message-Id: <35EE4739.A3A04AD1@cvhp152.gpt.co.uk> +Date: Thu, 03 Sep 1998 08:37:29 +0100 +From: David Green <greendjf@cvhp152.gpt.co.uk> +Organization: GPT Ltd, U.K. +X-Mailer: Mozilla 4.06 [en] (X11; I; HP-UX B.10.20 9000/715) +Mime-Version: 1.0 +To: Craig <craig@admc.com> +Subject: Re: Rcs.pm 0.07 +References: <359A81C3.3AD5@freter.com> <359B3C03.EF608041@cvhp152.gpt.co.uk> <359B6955.CCA0F605@freter.com> <35E67CD9.19DEDCE1@cvhp152.gpt.co.uk> <35EAA99F.471156A@freter.com> <35EC0CD8.C955E86B@cvhp152.gpt.co.uk> <35EDCEF8.1FB4517D@admc.com> +Content-Type: text/plain; charset=us-ascii +Content-Transfer-Encoding: 7bit + +Craig wrote: +Craig, + +I've tried out your new code for daterev and it works ok except that my +version of perl, 5.004_04, complained that $target_time was not defined +(out of scope) a few lines up from the bottom of the method. I had to +define $target_time before the if (@_ == 6) test. + +sub daterev { + my $self = shift; + + # ensure date has all the elements + if (@_ != 1 and @_ != 6) { + croak "daterev must either 1 or 6 arguments"; + } + + my $target_time; + if (@_ == 6) { # string date passed + my($year, $mon, $mday, $hour, $min, $sec) = @_; + + if($year !~ /^\d{4}$/) { + croak "year (1st param) must be 4 digit number"; + } + + $mon--; # convert to 0-11 range + $target_time = timegm($sec, $min, $hour, $mday, $mon, $year); + } + else { # system date passed + $target_time = shift; + } + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + + + my @revisions; + my %dates; + + my %dates_hash = %{$self->{DATE}}; + foreach $revision (keys %dates_hash) { + my $date = $dates_hash{$revision}; + $dates{$date}{$revision} = 1; + } + + my $date; + foreach $date (reverse sort keys %dates) { + foreach $revision (keys %{ $dates{$date} }) { + push @revisions, $revision if $date <= $target_time; + } + } + + return wantarray ? @revisions : $revisions[0]; +} + + +Regards, + +David. +____________________________________________________________________ +David Green, AD4541, GPT Coventry, (740) 2821 greendjf@ncp.gpt.co.uk diff --git a/cpan/dist/Rcs/examples/mark.pl b/cpan/dist/Rcs/examples/mark.pl new file mode 100755 index 00000000..01f4f98e --- /dev/null +++ b/cpan/dist/Rcs/examples/mark.pl @@ -0,0 +1,21 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Unlock RCS file +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +Rcs->quiet(0); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); +(my $symbol = shift) or die "Usage: $0 symbol [revision]\n"; +my $revision = shift || $obj->head; +die "Revision $revision does not exist\n" + unless grep /^$revision$/, $obj->revisions; + +$obj->rcs("-n${symbol}:${revision}"); + diff --git a/cpan/dist/Rcs/examples/parse.pl b/cpan/dist/Rcs/examples/parse.pl new file mode 100755 index 00000000..50fb67c1 --- /dev/null +++ b/cpan/dist/Rcs/examples/parse.pl @@ -0,0 +1,38 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Parse RCS archive file. +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +my $head_rev = $obj->head; +my $locker = $obj->lock; +my $author = $obj->author; +my @access = $obj->access; +my @revisions = $obj->revisions; + +my $filename = $obj->file; + +if ($locker) { + print "Head revision $head_rev is locked by $locker\n"; +} +else { + print "Head revision $head_rev is unlocked\n"; +} + +if (@access) { + print "\nThe following users are on the access list of file $filename\n"; + map { print "User: $_\n"} @access; +} + +print "\nList of all revisions of $filename\n"; +foreach (@revisions) { + print "Revision: $_\n"; +} diff --git a/cpan/dist/Rcs/examples/pause b/cpan/dist/Rcs/examples/pause new file mode 100644 index 00000000..4141ffd6 --- /dev/null +++ b/cpan/dist/Rcs/examples/pause @@ -0,0 +1 @@ +http://www.perl.com/CPAN-local/modules/04pause.html diff --git a/cpan/dist/Rcs/examples/project/src/Rcs.pm b/cpan/dist/Rcs/examples/project/src/Rcs.pm new file mode 100644 index 00000000..03241de1 --- /dev/null +++ b/cpan/dist/Rcs/examples/project/src/Rcs.pm @@ -0,0 +1,1320 @@ +package Rcs; +require 5.001; +use strict; +use Carp; +use Time::Local; +use vars qw($VERSION $revision); + +#------------------------------------------------------------------ +# global stuff +#------------------------------------------------------------------ +$VERSION = '0.08'; +$revision = '$Id: Rcs.pm,v 1.14 1998/07/23 01:00:23 freter Exp freter $'; +my $Dir_Sep = ($^O eq 'MSWin32') ? '\\' : '/'; +my $Exe_Ext = ($^O eq 'MSWin32') ? '.exe' : ''; +my $Rcs_Bin_Dir = '/usr/local/bin'; +my $Rcs_Dir = '.' . $Dir_Sep . 'RCS'; +my $Work_Dir = '.'; +my $Quiet = 1; # RCS quiet mode +my $Arc_Ext = ',v'; + +#------------------------------------------------------------------ +# RCS object constructor +#------------------------------------------------------------------ +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + + # provide default values for system stuff + $self->{"_BINDIR"} = \$Rcs_Bin_Dir; + $self->{"_QUIET"} = \$Quiet; + $self->{"_RCSDIR"} = \$Rcs_Dir; + $self->{"_WORKDIR"} = \$Work_Dir; + $self->{"_ARCEXT"} = \$Arc_Ext; + + $self->{FILE} = undef; + $self->{ARCFILE} = undef; + $self->{AUTHOR} = undef; + $self->{COMMENTS} = undef; + $self->{DATE} = undef; + $self->{LOCK} = undef; + $self->{ACCESS} = []; + $self->{REVISIONS} = []; + $self->{REVINFO} = undef; + $self->{STATE} = undef; + $self->{SYMBOLS} = undef; + bless($self, $class); + return $self; +} + +#------------------------------------------------------------------ +# access +# Access list of archive file. +#------------------------------------------------------------------ +sub access { + my $self = shift; + + if (not @{ $self->{ACCESS} }) { + _parse_rcs_header($self); + } + + # dereference revisions list + my @access = @{ $self->{ACCESS} }; + + return @access; +} + +#------------------------------------------------------------------ +# arcext +# Set the RCS archive file extension (default is ',v'). +#------------------------------------------------------------------ +sub arcext { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_ARCEXT"} } = shift }; + return ${ $self->{"_ARCEXT"} }; + } + + # called as class method + else { + if (@_) { $Arc_Ext = shift; } + return $Arc_Ext; + } +} + +#------------------------------------------------------------------ +# arcfile +# Name of RCS archive file. +# If not set then return name of working file with RCS +# extension (',v'). +#------------------------------------------------------------------ +sub arcfile { + my $self = shift; + if (@_) { $self->{ARCFILE} = shift } + return $self->{ARCFILE} || $self->{FILE} . ${ $self->{"_ARCEXT"} }; +} + +#------------------------------------------------------------------ +# author +# Return the author of an RCS revision. +# If revision is not provided, default to 'head' revision. +#------------------------------------------------------------------ +sub author { + my $self = shift; + + if (not defined $self->{AUTHOR}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference author hash + my %author_array = %{ $self->{AUTHOR} }; + + return $author_array{$revision}; +} + +#------------------------------------------------------------------ +# bindir +# Set the bin directory in which the RCS distribution programs +# reside. +#------------------------------------------------------------------ +sub bindir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_BINDIR"} } = shift }; + return ${ $self->{"_BINDIR"} }; + } + + # called as class method + else { + if (@_) { $Rcs_Bin_Dir = shift }; + return $Rcs_Bin_Dir; + } +} + +#------------------------------------------------------------------ +# ci +# Execute RCS 'ci' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub ci { + my $self = shift; + my @param = @_; + + my $ciprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'ci' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "ci program $ciprog not found" unless -e $ciprog; + croak "ci program $ciprog not executable" unless -x $ciprog; + system($ciprog, @param) == 0 or croak "$!"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# co +# Execute RCS 'co' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub co { + my $self = shift; + my @param = @_; + + my $coprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'co' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "co program $coprog not found" unless -e $coprog; + croak "co program $coprog not executable" unless -x $coprog; + system($coprog, @param) == 0 or croak "$!"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# comments +#------------------------------------------------------------------ +sub comments { + my $self = shift; + + if (not defined $self->{COMMENTS}) { + _parse_rcs_body($self); + } + + return %{$self->{COMMENTS}}; +} + +#------------------------------------------------------------------ +# daterev +# Returns a revision which was current at a specified date/time. +# 0 is returned if all revisions are newer than the date +# specified. This usually means the file did not exist on that +# date. +# This takes 6 parameters, year (4 digit year), month (1-12), day +# of month (1-31), hour (0-23), minute (0-59) and second (0-59). +#------------------------------------------------------------------ +sub daterev { + my $self = shift; + my($year, $mon, $mday, $hour, $min, $sec) = @_; + + # ensure date has all the elements + if(@_ != 6) { + croak "daterev must have 6 element date/time (year, month, day, hour, min, sec)"; + } + + if($year !~ /^\d{4}$/) { + croak "year (1st param) must be 4 digit number"; + } + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + + $mon--; # convert to 0-11 range + my $target_time = timegm($sec, $min, $hour, $mday, $mon, $year); + my @revisions; + my %dates; + + my %dates_hash = %{$self->{DATE}}; + foreach $revision (keys %dates_hash) { + my $date = $dates_hash{$revision}; + $dates{$date}{$revision} = 1; + } + + my $date; + foreach $date (reverse sort keys %dates) { + foreach $revision (keys %{ $dates{$date} }) { + push @revisions, $revision if $date <= $target_time; + } + } + + return wantarray ? @revisions : $revisions[0]; +} + +#------------------------------------------------------------------ +# dates +# Return a hash of revision dates, keyed on revision, when called +# in list mode. +# Return the most recent date when called in scalar mode. +# +# RCS stores dates in GMT. +# The date values are system dates. +#------------------------------------------------------------------ +sub dates { + my $self = shift; + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + + my %DatesHash = %{$self->{DATE}}; + my @dates_list = sort {$b<=>$a} values %DatesHash; + my $MostRecent = $dates_list[0]; + + return wantarray ? %DatesHash : $MostRecent; +} + +#------------------------------------------------------------------ +# file +# Name of working file. +#------------------------------------------------------------------ +sub file { + my $self = shift; + if (@_) { $self->{FILE} = shift } + return $self->{FILE}; +} + +#------------------------------------------------------------------ +# head +# Return the head revision. +#------------------------------------------------------------------ +sub head { + my $self = shift; + + if (not defined $self->{HEAD}) { + _parse_rcs_header($self); + } + return $self->{HEAD}; +} + +#------------------------------------------------------------------ +# lock +# Return user who has file locked. +#------------------------------------------------------------------ +sub lock { + my $self = shift; + + if (not defined $self->{LOCK}) { + _parse_rcs_header($self); + } + return $self->{LOCK}; +} + +#------------------------------------------------------------------ +# quiet +# Set or un-set RCS quiet mode. +#------------------------------------------------------------------ +sub quiet { + my $self = shift; + + # called as object method + if (ref $self) { + + # set/un-set quiet mode + if (@_) { + my $mode = shift; + croak "Passed parameter must be either '0' or '1'" + unless $mode == 0 or $mode == 1; + ${ $self->{"_QUIET"} } = $mode; + return ${ $self->{"_QUIET"} }; + } + + # access quiet mode + else { + return ${ $self->{"_QUIET"} }; + } + } + + # called as class method + else { + + # set/un-set quiet mode + if (@_) { + my $mode = shift; + croak "Passed parameter must be either '0' or '1'" + unless $mode == 0 or $mode == 1; + $Quiet = $mode; + return $Quiet; + } + + # access quiet mode + else { + return $Quiet; + } + } +} + +#------------------------------------------------------------------ +# rcs +# Execute RCS 'rcs' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub rcs { + my $self = shift; + my @param = @_; + + my $rcsprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcs' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "rcs program $rcsprog not found" unless -e $rcsprog; + croak "rcs program $rcsprog not executable" unless -x $rcsprog; + system($rcsprog, @param) == 0 or croak "$?"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# rcsclean +# Execute RCS 'rcsclean' program. +#------------------------------------------------------------------ +sub rcsclean { + my $self = shift; + my @param = @_; + + my $rcscleanprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsclean' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + + # run program + croak "rcsclean program $rcscleanprog not found" unless -e $rcscleanprog; + croak "rcsclean program $rcscleanprog not executable" unless -x $rcscleanprog; + system($rcscleanprog, @param) == 0 or croak "$?"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# rcsdiff +# Execute RCS 'rcsdiff' program. +# Calling in list context returns the output of rcsdiff, while +# calling in scalar context returns the return status of the +# rcsdiff program. +#------------------------------------------------------------------ +sub rcsdiff { + my $self = shift; + my @param = @_; + + my $rcsdiff_prog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsdiff' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $arcfile = $self->{ARCFILE} || $self->{FILE}; + $arcfile = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $self->workdir . $Dir_Sep . $self->file; + + # un-taint parameter string + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + my $param_str = join(' ', @param); + $param_str =~ s/([\w-]+)/$1/g; + + croak "rcsdiff program $rcsdiff_prog not found" unless -e $rcsdiff_prog; + croak "rcsdiff program $rcsdiff_prog not executable" unless -x $rcsdiff_prog; + open(DIFF, "$rcsdiff_prog $param_str $arcfile $workfile |"); + my @diff_output = <DIFF>; + + # rcsdiff returns exit status 0 for no differences, 1 for differences, + # and 2 for error condition. + close DIFF; + my $status = $?; + croak "$rcsdiff_prog failed" if $status == 2; + return wantarray ? @diff_output : $status; +} + +#------------------------------------------------------------------ +# rcsdir +# Location of 'RCS' archive directory. +#------------------------------------------------------------------ +sub rcsdir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_RCSDIR"} } = shift } + return ${ $self->{"_RCSDIR"} }; + } + + # called as class method + else { + if (@_) { $Rcs_Dir = shift } + return $Rcs_Dir; + } +} + +#------------------------------------------------------------------ +# revdate +# Return the revision date of an RCS revision. +# If revision is not provided, default to 'head' revision. +# +# RCS stores dates in GMT. This method will return dates relative +# to the local time zone. +#------------------------------------------------------------------ +sub revdate { + my $self = shift; + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference date hash + my %date_array = %{ $self->{DATE} }; + my $date_str = $date_array{$revision}; + + return wantarray ? localtime($date_str) : $date_str; +} + +#------------------------------------------------------------------ +# revisions +#------------------------------------------------------------------ +sub revisions { + my $self = shift; + + if (not @{ $self->{REVISIONS} }) { + _parse_rcs_header($self); + } + + # dereference revisions list + my @revisions = @{ $self->{REVISIONS} }; + + @revisions; +} + +#------------------------------------------------------------------ +# rlog +# Execute RCS 'rlog' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub rlog { + my $self = shift; + my @param = @_; + + my $rlogprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rlog' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $arcfile = $self->{ARCFILE} || $self->{FILE}; + + # un-taint parameter string + my $param_str = join(' ', @param); + $param_str =~ s/([\w-]+)/$1/g; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + croak "rlog program $rlogprog not found" unless -e $rlogprog; + croak "rlog program $rlogprog not executable" unless -x $rlogprog; + open(RLOG, "$rlogprog $param_str $archive_file |"); + + my @logoutput = <RLOG>; + close RLOG; + croak "$rlogprog failed" if $?; + @logoutput; +} + +#------------------------------------------------------------------ +# state +# If revision is not provided, default to 'head' revision +#------------------------------------------------------------------ +sub state { + my $self = shift; + + if (not defined $self->{STATE}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference author hash + my %state_array = %{ $self->{STATE} }; + + return $state_array{$revision}; +} + +#------------------------------------------------------------------ +# symbol +# If revision is not provided, default to 'head' revision +#------------------------------------------------------------------ +sub symbol { + my $self = shift; + + if (not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference symbols hash + my %sym_array = %{ $self->{SYMBOLS} }; + + return '' if not defined $sym_array{$revision}; + + my @symbols = @{ $sym_array{$revision} }; + + # return only first array element if user wants scalar + return wantarray ? @symbols : $symbols[0]; +} + +#------------------------------------------------------------------ +# symbols +# Returns hash of all revisions keyed on symbol defined against file. +#------------------------------------------------------------------ +sub symbols { + my $self = shift; + + if(not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + + my %symbols; + + # loop through each revision + my $rev; + foreach $rev (@{ $self->{REVISIONS} }) { + my $sym; + foreach $sym (@{ $self->{SYMBOLS}->{$rev} }) { + $symbols{$sym} = $rev; + } + } + return %symbols; +} + +#------------------------------------------------------------------ +# symrev +# Returns the revision against which a specified symbol was +# defined. If the symbol was not defined against any version +# of this file, 0 is returned. +#------------------------------------------------------------------ +sub symrev { + my $self = shift; + my $sym = shift; + if(! defined $sym) { + croak "You must supply a symbol to symrev"; + } + + if (not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + + my $ret_rev = 0; + my %symbols; + + # loop through each revision + my $rev; + REV_LOOP: + foreach $rev (@{ $self->{REVISIONS} }) { + # loop through each symbol defined against + # this revision + my $s; + foreach $s (@{ $self->{SYMBOLS}->{$rev} }) { + + # store each revision matching the pattern + if (wantarray) { + $symbols{$s} = $rev if $s =~ /$sym/; + } + + # if it's the one we're looking for, we can + # quit as we've found the revision we want + else { + if($s eq $sym) { + $ret_rev = $rev; + last REV_LOOP; + } + } + } + } + + return wantarray ? %symbols : $ret_rev; +} + +#------------------------------------------------------------------ +# workdir +# Location of working directory. +#------------------------------------------------------------------ +sub workdir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_WORKDIR"} } = shift } + return ${ $self->{"_WORKDIR"} }; + } + + # called as class method + else { + if (@_) { $Work_Dir = shift } + return $Work_Dir; + } +} + +#------------------------------------------------------------------ +# _parse_rcs_body +# Private function +#------------------------------------------------------------------ +sub _parse_rcs_body { + + my $self = shift; + local $_; + + my %comments; + + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $file = $self->{FILE}; + my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} }; + + # parse RCS archive file + open RCS_FILE, $rcs_file or croak "Unable to open $rcs_file"; + + # skip header info and get description + DESC: while (<RCS_FILE>) { + if (/^desc$/) { + $comments{0} = ''; + $_ = <RCS_FILE>; + s/^\@//; # remove leading '@' + while (1) { + last DESC if /^\@$/; + s/\@\@/\@/g; # RCS replaces single '@' with '@@' + $comments{0} .= $_; + $_ = <RCS_FILE>; + } + } + } + + # parse revision comments + my $revision; + REVISION: while (<RCS_FILE>) { + if (/^[\d\.]+$/) { + chomp($revision = $_); + $_ = <RCS_FILE>; + if (/^log$/) { + $comments{$revision} = ''; + $_ = <RCS_FILE>; + s/^\@//; # remove leading '@' + while (1) { + next REVISION if /^\@$/; + s/\@\@/\@/g; # RCS replaces single '@' with '@@' + $comments{$revision} .= $_; + $_ = <RCS_FILE>; + } + } + } + } + + # loop through 'text' section to avoid capturing false comments + continue { + if (/^text$/) { + while (<RCS_FILE>) {last if /^\@$/} + } + } + + close RCS_FILE; + + $self->{COMMENTS} = \%comments; +} + +#------------------------------------------------------------------ +# _parse_rcs_header +# Private function +# Directly parse the RCS archive file. +#------------------------------------------------------------------ +sub _parse_rcs_header { + + my $self = shift; + local $_; + + my ($head, $lock); + my (@access_list, @revisions); + my (%author, %date, %state, %symbols); + + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $file = $self->{FILE}; + my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} }; + + # parse RCS archive file + open RCS_FILE, $rcs_file + or croak "Unable to open $rcs_file"; + while (<RCS_FILE>) { + next if /^\s*$/; # skip blank lines + last if /^desc$/; # end of header info + + # get head revision + if (/^head\s/) { + ($head) = /^head\s+(.*?);$/; + next; + } + + # get access list + if (/^access$/) { + while (<RCS_FILE>) { + chomp; + s/\s//g; # remove all whitespace + push @access_list, (split(/;/))[0]; + last if /;$/; + } + next; + } + + # get locker + # get symbols + if (/^symbols$/) { + while (<RCS_FILE>) { + chomp; + s/\s//g; # remove all whitespace + my ($sym, $rev) = split(/:/); + $rev =~ s/;$//; + push @{ $symbols{$rev} }, $sym; + last if /;$/; + } + next; + } + + # get locker + if (/^locks/) { + + # file not locked + if (/strict/) { + $lock = ''; + next; + } + + # get user who has file locked + my $next_line = <RCS_FILE>; # read next line + ($lock) = $next_line =~ m/^\s*(\w+):/; + next; + } + + # get all revisions + if (/^\d+\.\d+/) { + chomp; + push @revisions, $_; + + # get author, state and date of each revision + my $next_line = <RCS_FILE>; + chop(my $author = (split(/\s+/, $next_line))[3]); + chop(my $state = (split(/\s+/, $next_line))[5]); + chop(my $date = (split(/\s+/, $next_line))[1]); + + # store date as date number + my ($year, $mon, $mday, $hour, $min, $sec) = split(/\./, $date); + $mon--; # convert to 0-11 range + my @date = ($sec,$min,$hour,$mday,$mon,$year); + + # store value in hash using revision as key + $author{$_} = $author; + $state{$_} = $state; + $date{$_} = timegm(@date); + } + } + close RCS_FILE; + + $self->{HEAD} = $head; + $self->{LOCK} = $lock; + $self->{ACCESS} = \@access_list; + $self->{REVISIONS} = \@revisions; + $self->{AUTHOR} = \%author; + $self->{DATE} = \%date; + $self->{STATE} = \%state; + $self->{SYMBOLS} = \%symbols; +} + +1; + +__END__ + +=head1 NAME + +Rcs - Perl Object Class for Revision Control System (RCS). + +=head1 SYNOPSIS + + use Rcs; + +=head1 DESCRIPTION + +This Perl module provides an object oriented interface to access +B<Revision Control System (RCS)> utilities. RCS must be installed on +the system prior to using this module. This module should simplify +the creation of an RCS front-end. + +=head2 OBJECT CONSTRUCTOR + +The B<new> method may be used as either a class method or an object +method to create a new object. + + # called as class method + $obj = Rcs->new; + + # called as object method + $newobj = $obj->new; + +=head2 CLASS METHODS + +Besides the object constructor, there are three class methods provided +which effect any newly created objects. + +The B<arcext> method sets the RCS archive extension, which is ',v' by +default. + + # set/unset RCS archive extension + Rcs->arcext(''); # set no archive extension + Rcs->arcext(',v'); # set archive extension to ',v' + $arc_ext = Rcs->arcext(); # get current archive extension + +The B<bindir> method sets the directory path where the RCS executables +(i.e. rcs, ci, co) are located. The default location is '/usr/local/bin'. + + # set RCS bin directory + Rcs->bindir('/usr/bin'); + + # access RCS bin directory + $bin_dir = Rcs->bindir; + +The B<quiet> method sets/unsets the quiet mode for the RCS executables. +Quiet mode is set by default. + + # set/unset RCS quiet mode + Rcs->quiet(0); # unset quiet mode + Rcs->quiet(1); # set quiet mode + + # access RCS quiet mode + $quiet_mode = Rcs->quiet; + +These methods may also be called as object methods. + + $obj->arcext(''); + $obj->bindir('/usr/bin'); + $obj->quiet(0); + +=head2 OBJECT ATTRIBUTE METHODS + +These methods set the attributes of the RCS object. + +The B<file> method is used to set the name of the RCS working file. The +filename must be set before invoking any access of modifier methods on the +object. + + $obj->file('mr_anderson.pl'); + +The B<arcfile> method is used to set the name of the RCS archive file. +Using this method is optional, as the other methods will assume the archive +filename is the same as the working file unless specified otherwise. The +RCS archive extension (default ',v') is automatically added to the filename. + + $obj->arcfile('principle_mcvicker.pl'); + +The B<workdir> methods set the path of the RCS working directory. If not +specified, default path is '.' (current working directory). + + $obj->workdir('/usr/local/source'); + +The B<rcsdir> methods set the path of the RCS archive directory. If not +specified, default path is './RCS'. + + $obj->rcsdir('/usr/local/archive'); + +=head2 RCS PARSE METHODS + +This class provides methods to directly parse the RCS archive file. + +The B<access> method returns a list of all user on the access list. + + @access_list = $obj->access; + +The B<author> method returns the author of the revision. The head revision +is used if no revision argument is passed to method. + + # returns the author of revision '1.3' + $author = $obj->author('1.3'); + + # returns the authos of the head revision + $author = $obj->author; + +The B<head> method returns the head revision. + + $head = $obj->head; + +The B<lock> method returns the locker of the revision. The method returns +null if the revision is unlocked. The head revision is used if no revision +argument is passed to method. + + # returns locker of revision '1.3' + $locker = $obj->lock('1.3'); + + # returns locker of head revision + $locker = $obj->lock; + +The B<revisions> method returns a list of all revisions of archive file. + + @revisions = $obj->revisions; + +The B<state> method returns the state of the revision. The head revision +is used if no revision argument is passed to method. + + # returns state of revision '1.3' + $state = $obj->state('1.3'); + + # returns state of head revision + $state = $obj->state; + +The B<symbol> method returns the symbol(s) associated with a revision. +If called in list context, method returns all symbols associated with +revision. If called in scalar context, method returns last symbol +assciated with a revision. The head revision is used if no revision argument +is passed to method. + + # list context, returns all symbols associated with revision 1.3 + @symbols = $obj->symbol('1.3'); + + # list context, returns all symbols associated with head revision + @symbols = $obj->symbol; + + # scalar context, returns last symbol associated with revision 1.3 + $symbol = $obj->symbol('1.3'); + + # scalar context, returns last symbol associated with head revision + $symbol = $obj->symbol; + +The B<symbols> method returns a hash, keyed by symbol, of all of the revisions +associated with the file. + + %symbols = $obj->symbols; + foreach $sym (keys %symbols) { + $rev = $symbols{$sym}; + } + +The B<revdate> method returns the date of a revision. The returned date format +is the same as the localtime format. When called as a scalar, it returns the +system date number. If called is list context, the list +($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned. + + # scalar mode + $scalar_date = $obj->revdate; + print "Scalar date number = $scalar_date\n"; + $date_str = localtime($scalar_date); + print "Scalar date string = $date_str\n"; + + # list mode + @list_date = $obj->revdate; + print "List date = @list_date\n"; + +The B<dates> method returns a hash of revision dates, keyed on revision. The +hash values are system date numbers. When called in scalar mode, the method +returns the most recent revision date. + + # list mode + %DatesHash = obj->dates; + @dates_list = sort {$b<=>$a} values %DatesHash; + $MostRecent = $dates_list[0]; + + # scalar mode + $most_recent = $obj->dates; + print "Most recent date = $most_recent\n"; + $most_recent_str = localtime($most_recent); + print "Most recent date string = $most_recent_str\n"; + +The B<symrev> method returns the revision against which a specified symbol was +defined. If the symbol was not defined against any version of this file, 0 is +returned. + + # gets revision that has 'MY_SYMBOL' defined against it + $rev = symrev('MY_SYMBOL'); + +The B<daterev> method returns a revision which was current at a specified +date/time. If all revisions are newer than the specified date/time, i.e. the +file did not exist then, 0 is returned. + + # gets revision that was active on 25th June 1998 16:45:30 + $rev = daterev(1998, 6, 25, 16, 45, 30); + +The B<comments> method returns a hash of revision comments, keyed on revision. +A key value of 0 returns the description. + + %comments = $obj->comments; + $description = $comments{0}; + $comment_1_3 = $comments{'1.3'}; + +=head2 RCS SYSTEM METHODS + +These methods invoke the RCS system utilities. + +The B<ci> method calls the RCS ci program. + + # check in, and then check out in unlocked state + $obj->ci('-u'); + +The B<co> method calls the RCS co program. + + # check out in locked state + $obj->co('-l'); + +The B<rcs> method calls the RCS rcs program. + + # lock file + $obj->rcs('-l'); + +The B<rcsdiff> method calls the RCS rcsdiff program. When called in +list context, this method returns the outpout of the rcsdiff program. +When called in scalar context, this method returns the return status of +the rcsdiff program. The return status is 0 for the same, 1 for some +differences, and 2 for error condition. + +When called without parameters, rcsdiff does a diff between the current +working file, and the last revision checked in. + + # call in list context + @diff_output = $obj->rcsdiff; + + # call in scalar context + $changed = $obj->rcsdiff; + if ($changed) { + print "Working file has changed\n"; + } + +Call rcsdiff with parameters to do a diff between any two revisions. + + @diff_output = $obj->rcsdiff('-r1.2', '-r1.1'); + +The B<rlog> method calls the RCS rlog program. This method returns the +output of the rlog program. + + # get complete log output + @rlog_complete = $obj->rlog; + + # called with '-h' switch outputs only header information + @rlog_header = $obj->rlog('-h'); + print @rlog_header; + +The B<rcsclean> method calls the RCS rcsclean program. + + # remove working file + $obj->rcsclean; + + +=head1 EXAMPLES + +=head2 CREATE ACCESS LIST + +Using method B<rcs> with the B<-a> switch allows you to add users to +the access list of an RCS archive file. + + use Rcs; + $obj = Rcs->new; + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + +Methos B<rcs> invokes the RCS utility rcs with the same parameters. + + @users = qw(beavis butthead); + $obj->rcs("-a@users"); + +Calling method B<access> returns list of users on access list. + + $filename = $obj->file; + @access_list = $obj->access; + print "Users @access_list are on the access list of $filename\n"; + + +=head2 PARSE RCS ARCHIVE FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of the +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + $head_rev = $obj->head; + $locker = $obj->lock; + $author = $obj->author; + @access = $obj->access; + @revisions = $obj->revisions; + + $filename = $obj->file; + + if ($locker) { + print "Head revision $head_rev is locked by $locker\n"; + } + else { + print "Head revision $head_rev is unlocked\n"; + } + + if (@access) { + print "\nThe following users are on the access list of file $filename\n"; + map { print "User: $_\n"} @access; + } + + print "\nList of all revisions of $filename\n"; + foreach $rev (@revisions) { + print "Revision: $rev\n"; + } + +=head2 CHECK-IN FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->file('cornholio.pl'); + + # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); + + # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); + +Check in file using B<-u> switch. This will check in the file, and will then +check out the file in an unlocked state. The B<-m> switch is used to set the +revision comment. + +Command: + + $obj->ci('-u', '-mRevision Comment'); + +is equivalent to commands: + + $obj->ci('-mRevision Comment'); + $obj->co; + +=head2 CHECK-OUT FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->file('cornholio.pl'); + + # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); + + # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); + +Check out file read-only: + + $obj->co; + +or check out and lock file: + + $obj->co('-l'); + +=head2 RCSDIFF + +Method B<rcsdiff> does an diff between revisions. + + $obj = Rcs->new; + $obj->bindir('/usr/bin'); + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + print "Diff of current working file\n"; + if ($obj->rcsdiff) { # scalar context + print $obj->rcsdiff; # list context + } + else { + print "Versions are Equal\n"; + } + + print "\n\nDiff of revisions 1.2 and 1.1\n"; + print $obj->rcsdiff('-r1.2', '-r1.1'); + +=head2 RCSCLEAN + +Method B<rcsclean> will remove an unlocked working file. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + print "Quiet mode NOT set\n" unless Rcs->quiet; + + $obj->rcsclean; + +=head1 AUTHOR + +Craig Freter, E<lt>F<craig@freter.com>E<gt> + +=head1 CONTRIBUTORS + +David Green, E<lt>F<greendjf@cvhp152.gpt.co.uk>E<gt> + + David Green contributed the B<dates> method. + +Jamie O'Shaughnessy, E<lt>F<jamie@thanatar.demon.co.uk>E<gt> + + Contributed NT port. + Contributed methods B<daterev>, B<symrev>, and B<symbols>. + +=head1 COPYRIGHT + +Copyright (C) 1997,1998 Craig Freter. All rights reserved. +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut + diff --git a/cpan/dist/Rcs/examples/project/src/testfile b/cpan/dist/Rcs/examples/project/src/testfile new file mode 100644 index 00000000..59d119de --- /dev/null +++ b/cpan/dist/Rcs/examples/project/src/testfile @@ -0,0 +1,2 @@ +3.14 +hear again diff --git a/cpan/dist/Rcs/examples/rcsclean.pl b/cpan/dist/Rcs/examples/rcsclean.pl new file mode 100755 index 00000000..957cfc73 --- /dev/null +++ b/cpan/dist/Rcs/examples/rcsclean.pl @@ -0,0 +1,18 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# rcsclean utility +#------------------------------------------ +use strict; +use Rcs; + +Rcs->quiet(0); # turn off quiet mode +Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +print "Quiet mode NOT set\n" unless Rcs->quiet; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +$obj->rcsclean; diff --git a/cpan/dist/Rcs/examples/rcsdiff.pl b/cpan/dist/Rcs/examples/rcsdiff.pl new file mode 100755 index 00000000..7bd343f9 --- /dev/null +++ b/cpan/dist/Rcs/examples/rcsdiff.pl @@ -0,0 +1,27 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Use rcsdiff utility. +#------------------------------------------ +use strict; +use Rcs; + +Rcs->quiet(1); + +my $obj = Rcs->new; +$obj->bindir('/usr/bin'); +print "Quiet mode set\n" if Rcs->quiet; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +print "Diff of current working file\n"; +if ($obj->rcsdiff) { # scalar context + print $obj->rcsdiff; # list context +} +else { + print "Versions are Equal\n"; +} + +print "\n\nDiff of revisions 1.2 and 1.1\n"; +print $obj->rcsdiff('-r1.2', '-r1.1'); diff --git a/cpan/dist/Rcs/examples/revdate.pl b/cpan/dist/Rcs/examples/revdate.pl new file mode 100755 index 00000000..b20669f8 --- /dev/null +++ b/cpan/dist/Rcs/examples/revdate.pl @@ -0,0 +1,28 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Get revision date +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); +my $revision = shift || $obj->head; +die "Revision $revision does not exist\n" + unless grep /^$revision$/, $obj->revisions; + +# scalar mode +my $date_num = $obj->revdate($revision); +print "Revision : Date number = $revision : $date_num\n"; + +my $date_str = localtime($date_num); +print "Revision : Date string = $revision : $date_str\n"; + +# list mode +my @list_date = $obj->revdate($revision); +print "Revision : Date array = $revision : @list_date\n"; + diff --git a/cpan/dist/Rcs/examples/rlog.pl b/cpan/dist/Rcs/examples/rlog.pl new file mode 100755 index 00000000..14b66886 --- /dev/null +++ b/cpan/dist/Rcs/examples/rlog.pl @@ -0,0 +1,20 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Use rlog utility. +#------------------------------------------ +use strict; +use Rcs; + +my $obj = Rcs->new; + +# call quiet and bindir as objest methods +$obj->quiet(1); +$obj->bindir('/usr/bin'); + +print "Quiet mode set\n" if Rcs->quiet; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +print $obj->rlog; diff --git a/cpan/dist/Rcs/examples/symbols.pl b/cpan/dist/Rcs/examples/symbols.pl new file mode 100755 index 00000000..ffcafc32 --- /dev/null +++ b/cpan/dist/Rcs/examples/symbols.pl @@ -0,0 +1,23 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Test symrev method +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); + +my %symbols = $obj->symbols; +my $sym; +foreach $sym (keys %symbols) { + my $rev = $symbols{$sym}; + print "Symbol : Revision = $sym : $rev\n"; +} + +my @syms = keys %symbols; +print "@syms\n"; diff --git a/cpan/dist/Rcs/examples/symrev.pl b/cpan/dist/Rcs/examples/symrev.pl new file mode 100755 index 00000000..20af459c --- /dev/null +++ b/cpan/dist/Rcs/examples/symrev.pl @@ -0,0 +1,27 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Test symrev method +#------------------------------------------ +use strict; +use Rcs; + +#Rcs->bindir('/usr/bin'); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); +(my $symbol = shift) or die "Usage: $0 symbol\n"; + +# scalar mode +print "Scalar mode:\n"; +my $revision = $obj->symrev($symbol); +print "Symbol : Revision = $symbol : $revision\n"; + + +# list mode +print "\nList mode:\n"; +my %symbols = $obj->symrev($symbol); +foreach (keys %symbols) { + print "Symbol : Revision = $_ : $symbols{$_}\n"; +} diff --git a/cpan/dist/Rcs/examples/test1.pl b/cpan/dist/Rcs/examples/test1.pl new file mode 100755 index 00000000..209f9930 --- /dev/null +++ b/cpan/dist/Rcs/examples/test1.pl @@ -0,0 +1,14 @@ +#!/usr/local/bin/perl -w + +use strict; +use lib '.'; +use Rcs; + +my $p = new Rcs(); +$p->workdir("foo"); + +my $n = new Rcs(); +$n->workdir("bar"); + +print $p->workdir, "\n"; +print $n->workdir, "\n"; diff --git a/cpan/dist/Rcs/examples/test2.pl b/cpan/dist/Rcs/examples/test2.pl new file mode 100755 index 00000000..4992cc9b --- /dev/null +++ b/cpan/dist/Rcs/examples/test2.pl @@ -0,0 +1,15 @@ +#!/usr/local/bin/perl -w + +use strict; +use lib '.'; +use Rcs; + +Rcs->workdir("foo"); + +my $p = new Rcs(); +my $n = new Rcs(); + +Rcs->workdir("bar"); + +print $p->workdir, "\n"; +print $n->workdir, "\n"; diff --git a/cpan/dist/Rcs/examples/test3.pl b/cpan/dist/Rcs/examples/test3.pl new file mode 100755 index 00000000..4d0c36ed --- /dev/null +++ b/cpan/dist/Rcs/examples/test3.pl @@ -0,0 +1,17 @@ +#!/usr/local/bin/perl -w + +use strict; +use lib '.'; +use Rcs; + +my $p = new Rcs(); +my $n = new Rcs(); + +my $m = new Rcs(); +$m->workdir("foo"); + +Rcs->workdir("bar"); + +print $p->workdir, "\n"; +print $n->workdir, "\n"; +print $m->workdir, "\n"; diff --git a/cpan/dist/Rcs/examples/type b/cpan/dist/Rcs/examples/type new file mode 100644 index 00000000..29a4ee24 --- /dev/null +++ b/cpan/dist/Rcs/examples/type @@ -0,0 +1,13 @@ +Check-in +./project/RCS/testfile,v <-- ./project/src/testfile +new revision: 1.4; previous revision: 1.3 +enter log message, terminated with single '.' or end of file: +>> @test multi-line comment@@@@@@ +>> @ +>> @@ +>> @@@ +>> @@@@ +>> multiline comment +>> @ +>> . +done diff --git a/cpan/dist/Rcs/examples/unlock.pl b/cpan/dist/Rcs/examples/unlock.pl new file mode 100755 index 00000000..8d60137c --- /dev/null +++ b/cpan/dist/Rcs/examples/unlock.pl @@ -0,0 +1,20 @@ +#!/usr/local/bin/perl -w +#------------------------------------------ +# Unlock RCS file +#------------------------------------------ +use strict; +use Rcs; + +Rcs->bindir('/usr/bin'); +Rcs->quiet(0); +my $obj = Rcs->new; + +$obj->rcsdir("./project/RCS"); +$obj->workdir("./project/src"); +$obj->file("testfile"); +my $revision = shift || $obj->head; +die "Revision $revision does not exist\n" + unless grep /^$revision$/, $obj->revisions; + +$obj->rcs("-u${revision}"); + diff --git a/cpan/dist/Rcs/test.pl b/cpan/dist/Rcs/test.pl new file mode 100644 index 00000000..41c2ffc9 --- /dev/null +++ b/cpan/dist/Rcs/test.pl @@ -0,0 +1,20 @@ +# Before `make install' is performed this script should be runnable with +# `make test'. After `make install' it should work as `perl test.pl' + +######################### We start with some black magic to print on failure. + +# Change 1..1 below to 1..last_test_to_print . +# (It may become useful if the test is moved to ./t subdirectory.) + +BEGIN { $| = 1; print "1..1\n"; } +END {print "not ok 1\n" unless $loaded;} +use Rcs; +$loaded = 1; +print "ok 1\n"; + +######################### End of black magic. + +# Insert your test code below (better if it prints "ok 13" +# (correspondingly "not ok 13") depending on the success of chunk 13 +# of the test code): + diff --git a/cpan/dist/VCS-CVS/CVS.pm b/cpan/dist/VCS-CVS/CVS.pm new file mode 100644 index 00000000..3204e1b3 --- /dev/null +++ b/cpan/dist/VCS-CVS/CVS.pm @@ -0,0 +1,1474 @@ +package VCS::CVS; + +# Name: +# VCS::CVS. +# +# Documentation: +# POD-style documentation is at the end. Extract it with pod2html. +# +# Tabs: +# 4 spaces || die. +# +# -------------------------------------------------------------------------- + +use strict; +no strict 'refs'; + +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); + +use Carp; +use Cwd; +use File::Find; +use File::Path; + +require Exporter; + +@ISA = qw(Exporter); + +# Items to export into callers namespace by default. Note: do not export +# names by default without a very good reason. Use EXPORT_OK instead. +# Do not simply export all your public functions/methods/constants. + +@EXPORT = qw(); + +@EXPORT_OK = qw(); + +$VERSION = '2.00'; + +# Preloaded methods go here. +# -------------------------------------------------------------------------- +# Add an existing directory to the project. +# $dir can be a full path, or relative to the CWD. + +sub addDirectory +{ + my($self, $dir, $subDir, $message) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): \nFailure: $!"); + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + # $subDir Add this directory. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'add'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + push(@args, $subDir); + + $self -> runOrCroak(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of addDirectory. + +# -------------------------------------------------------------------------- +# Add an existing file to the project. +# $dir can be a full path, or relative to the CWD. + +sub addFile +{ + my($self, $dir, $file, $message) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): \nFailure: $!"); + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + # $file Add this file. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'add'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + push(@args, $file); + + $self -> runOrCroak(@args); + + $self -> commit($message); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of addFile. + +# -------------------------------------------------------------------------- +# Prepare & perform 'cvs checkout'. +# You call checkOut, and it calls _checkOutDontCallMe. +# $readOnly Interpretation +# 0 Check out files as read-write +# 1 Check out files as read-only +# $tag Interpretation +# Null Do not call upToDate; ie check out repository as is +# ! Null Call upToDate; Croak if repository is not up-to-date +# If you called new with $raw == 1, your tag is passed as is to CVS. +# If you called new with $raw == 0, your tag is assumed to be of the +# form release_1.23, and is converted to CVS's form release_1_23. +# $dir can be a full path, or relative to the CWD. + +sub checkOut +{ + my($self, $readOnly, $tag, $dir) = @_; + + $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'}); + + $self -> _validateObject($self -> {'project'}, 'modules', 0); + $self -> _validateObject($tag, 'val-tags', 0); + + croak("Failure: Move directory $dir out of the way") if (-d $dir); + + # Ensure the repository is up-to-date. + croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'") + if ($tag && (! $self -> upToDate() ) ); + + # Zap previous copy of work directory. + rmtree($dir, $self -> {'verbose'}); + + # Checkout a current copy of the project. + $self -> _checkOutDontCallMe($readOnly, $tag, $dir); + +} # End of checkOut. + +# -------------------------------------------------------------------------- +# Commit changes. +# Called as appropriate by addFile, removeFile and removeDirectory, +# so you don't need to call it. + +sub commit +{ + my($self, $message) = @_; + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'commit'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + $self -> runOrCroak(@args); + +} # End of commit. + +# -------------------------------------------------------------------------- +# Create a repository, using the current $CVSROOT. + +sub createRepository +{ + my($self) = @_; + + croak("Failure: Move directory $ENV{'CVSROOT'} out of the way") if (-d $ENV{'CVSROOT'}); + + # Create the repository and its files. + $self -> _mkpathOrCroak($ENV{'CVSROOT'}); + $self -> _mkpathOrCroak("$ENV{'CVSROOT'}/CVSROOT"); + + # Create the modules file. + my(@args) = (); + push(@args, "CVSROOT\t\tCVSROOT"); + push(@args, "modules\t\tCVSROOT\tmodules"); + push(@args, "$self->{'project'}\t\t$self->{'project'}"); + + my($file) = "$ENV{'CVSROOT'}/CVSROOT/modules"; + open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!"); + print OUT join("\n", @args), "\n"; + close(OUT); + + $file = "$ENV{'CVSROOT'}/CVSROOT/val-tags"; + open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!"); + # Write nothing. + close(OUT); + + if ($self -> {'history'}) + { + $file = "$ENV{'CVSROOT'}/CVSROOT/history"; + open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!"); + # Write nothing. + close(OUT); + } + +} # End of createRepository. + +# -------------------------------------------------------------------------- +# Return a reference to a list of tags. +# See also: the $raw option to new(). + +sub getTags +{ + my($self) = @_; + + my($line) = []; + + if (-e "$ENV{'CVSROOT'}/CVSROOT/val-tags") + { + $line = $self -> _readFile("$ENV{'CVSROOT'}/CVSROOT/val-tags"); + + for (@$line) + { + $_ = (split)[0]; + + # Convert tag_1_23 into tag_1.23, if requested. + s/([-a-zA-Z]+_\d\d?)_(\d\d)/$1\.$2/ if (! $self -> {'raw'}); + } + + } + + $line; + +} # End of getTags. + +# -------------------------------------------------------------------------- +# Run cvs history [-options]. +# Return a reference to a list of lines. +# +# The default option is -c. + +sub history +{ + my($self, $optionRef) = @_; + + # Preserve the caller's current working directory. + # cvs status only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir("$ENV{'HOME'}/$self->{'project'}") || + croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!"); + + # CVS history options: + # -c Report commits, ie -xARM. + + if (ref($optionRef) ne 'HASH') + { + $optionRef = {'-c' => ''}; + } + + my(@args) = ('cvs'); + push(@args, 'history'); + push(@args, join(' ', %$optionRef) ); + @args = `@args`; + chomp(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + \@args; + +} # End of history. + +# -------------------------------------------------------------------------- +# These are the options in the anonymous hash of parameters you pass in to 'new'. +# +# 'project' +# 'killerApp' The name of the project. No default +# +# 'history' +# 0 Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default +# 1 Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff +# +# 'permissions' +# 0775 Unix-specific. Default. Do not use '0775' +# +# 'raw' +# 0 Convert tags from CVS format to real format. Eg: release_1.23. Default +# 1 Set/Get tags in raw CVS format. Eg: release_1_23 +# +# 'verbose' +# 0 Run quietly +# 1 Report progress. Default + +sub new +{ + my($class, $optionRef) = @_; + $class = ref($class) || $class; + my($self) = (ref($optionRef) eq 'HASH') ? $optionRef : {}; + + my(%default) = + ( + 'history' => 0, + 'permissions' => 0775, # But not '0775'! + 'project' => '', + 'raw' => 0, + 'verbose' => 1, + ); + + my($option); + + for $option (keys(%default) ) + { + $self -> {$option} = $default{$option} if (! defined($self -> {$option}) ); + } + + $ENV{'HOME'} = '' if (! defined($ENV{'HOME'}) ); + $ENV{'CVSROOT'} = '' if (! defined($ENV{'CVSROOT'}) ); + + croak("Failure: No project name specified") if (! $self -> {'project'}); + croak("Failure: Env. var HOME not set") if (! $ENV{'HOME'}); + croak("Failure: Env. var CVSROOT not set") if (! $ENV{'CVSROOT'}); + + return bless $self, $class; + +} # End of new. + +# -------------------------------------------------------------------------- +# Import an existing directory structure. But, (sub) import is a reserved word. +# Use this to populate a repository for the first time. +# The value used for $vendorTag is not important; CVS discards it. +# The value used to $releaseTag is important; CVS discards it (why?) but I +# force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you +# should supply a meaningful value. Thus 'release_0_00' is strongly, repeat +# strongly, recommended. +# If you called new with $raw == 1, $releaseTag is passed as is to CVS. +# If you called new with $raw == 0, $releaseTag is assumed to be of the +# form release_1.23, and is converted to CVS's form release_1_23. + +# $sourceDir can be a full path, or relative to the CWD. + +sub populate +{ + my($self, $sourceDir, $vendorTag, $releaseTag, $message) = @_; + + $vendorTag = 'vendorTag' if ( ($#_ < 2) || (length($_[2]) == 0) ); + $releaseTag = 'release_0_00' if ( ($#_ < 3) || (length($_[3]) == 0) ); + $message = 'Initial version' if ($#_ < 4); + + $releaseTag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'}); + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($sourceDir) || croak("Can't chdir($sourceDir): \nFailure: $!"); + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'import'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + push(@args, $self -> {'project'}, $vendorTag, $releaseTag); + + $self -> runOrCroak(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + # Compensate for yet another CVS bug. + $self -> _fixTag($releaseTag); + +} # End of populate. + +# -------------------------------------------------------------------------- +# Remove a directory from the project. +# This deletes the directory (and all its files) from your working copy +# of the repository, as well as deleting them from the repository. +# Warning: $dir will have $CVSROOT and $HOME prepended by this code. +# Ie: $dir starts from - but excludes - your home directory +# (assuming, of course, you've checked out into your home directory...). +# You can't remove the current directory, or a parent thereof. + +sub removeDirectory +{ + my($self, $dir) = @_; + + my($cvsDir) = "$ENV{'CVSROOT'}/$dir/"; + my($workDir) = "$ENV{'HOME'}/$dir/"; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + + # Move into the work directory. + chdir($workDir) || croak("Can't chdir($workDir): \nFailure: $!"); + my($thisCwd) = cwd(); + + # Sanity check. + croak("Failure: You can't remove the current directory, or a parent") if ($cwd =~ /^$thisCwd/); + + # Ensure the repository is up-to-date. + croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'") + if (! $self -> upToDate() ); + + # Read the CVS entries. + my($cvsEntries) = 'CVS/Entries'; + my($entry) = $self -> _readFile($cvsEntries); + + # Remove each file, using CVS. + for (@$entry) + { + next if (/^D/); + + my($file); + + $file = $1 if (/^\/(.+?)\//); + + $self -> removeFile($workDir, $file, 'Whole directory removed'); + } + + $self -> commit('Whole directory removed'); + + # Move up, and remove the directory. + chdir('..') || croak("Can't chdir('..'): \nFailure: $!"); + my($directory) = $workDir; + my($index) = rindex($directory, '/', (length($directory) - 2) ); + substr($directory, 0, ($index + 1) ) = ''; + rmtree($directory, $self -> {'verbose'}); + + # Edit the CVS entries file to remove the dir. + if (-f $cvsEntries) + { + $entry = $self -> _readFile($cvsEntries); + @$entry = grep(! /^D\/$directory\//, @$entry); + open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!"); + print OUT join("\n", @$entry), "\n"; + close(OUT); + } + + # Remove the directory from CVS. + rmtree($cvsDir, $self -> {'verbose'}); + + # Remove the directory from the modules list. + if ($dir !~ /\//) + { + $cvsEntries = "$ENV{'CVSROOT'}/CVSROOT/modules"; + $entry = $self -> _readFile($cvsEntries); + + my($i); + + for ($i = 0; $i <= $#{$entry}; $i++) + { + my(@field) = split(/\s+/, $$entry[$i]); + splice(@$entry, $i, 1) if ($field[1] =~ /^$dir$/); + } + + open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!"); + print OUT join("\n", @$entry), "\n"; + close(OUT); + } + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of removeDirectory. + +# -------------------------------------------------------------------------- +# Remove a file from the project. +# This deletes the file from your working copy of the repository, +# as well as deleting it from the repository. +# $dir can be a full path, or relative to the CWD. +# $file is relative to $dir. + +sub removeFile +{ + my($self, $dir, $file, $message) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): \nFailure: $!"); + + unlink($file) || croak("Can't unlink($file): $!"); + + # CVS options: + # -Q Really quiet. + # -f Remove the file first. + # -l Do not recurse. + # $file Checkout this module. + + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'remove', '-f', '-l', $file); + + $self -> runOrCroak(@args); + + $self -> commit($message); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of removeFile. + +# -------------------------------------------------------------------------- +# The standard way to run a system command and report on the result. + +sub runOrCroak +{ + my($self, @args) = @_; + + my($result) = 0xffff & system(@args); + + print "Command: @args\n"; + + if ($result == 0) + { + print 'Success. '; + } + elsif ($result == 0xff00) + { + print "Failure: $!. "; + } + elsif ($result > 0x80) + { + $result >>= 8; + print "Exit status: $result. "; + } + else + { + if ($result & 0x80) + { + $result &= ~0x80; + print 'Coredump from '; + } + + print "Signal $result. "; + } + + printf("Result: %#04x\n", $result); + + croak("Failure: Can't run '@args'") if ($result); + +} # End of runOrCroak. + +# -------------------------------------------------------------------------- +# Tag the repository. +# You call setTag, and it calls _setTag. +# If you called new with $raw == 1, your tag is passed as is to CVS. +# If you called new with $raw == 0, your tag is assumed to be of the +# form release_1.23, and is converted to CVS's form release_1_23. + +sub setTag +{ + my($self, $tag) = @_; + + $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'}); + + $self -> _validateObject($self -> {'project'}, 'modules', 0); + $self -> _validateObject($tag, 'val-tags', 1); + + croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'") + if ($self -> upToDate() == 0); + + $self -> _setTag($tag); + +} # End of setTag. + +# -------------------------------------------------------------------------- +# Run cvs status. +# Return a reference to a list of lines. +# Only called by upToDate(), but you may call it. + +sub status +{ + my($self) = @_; + + # Preserve the caller's current working directory. + # cvs status only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir("$ENV{'HOME'}/$self->{'project'}") || + croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!"); + + # CVS options: + # -Q Really quiet. + + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'status'); + @args = `@args`; + chomp(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + \@args; + +} # End of status. + +# -------------------------------------------------------------------------- +# Delete all CVS directories and files from a copy of the repository. + +sub stripCVSDirs +{ + my($self, $dir) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): $!"); + + my(%dirStack); + + find + ( + sub + { + $dirStack{$File::Find::dir} = 1 if ($File::Find::dir =~ /\/CVS$/); + }, + cwd() + ); + + for (keys(%dirStack) ) + { + rmtree($_, $self -> {'verbose'}); + } + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of stripCVSDirs. + +# -------------------------------------------------------------------------- +# Run cvs -q [-n] update. +# Return a reference to a list of lines. +# Each line will start with one of [UARMC?], as per the CVS docs. +# +# Parameters Interpretation +# $n 0 -> Do not add -n to the cvs update command +# 1 -> Add -n to the command + +sub update +{ + my($self, $n) = @_; + + $n = 0 if (! defined($n) ); + + # Preserve the caller's current working directory. + # cvs status only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir("$ENV{'HOME'}/$self->{'project'}") || + croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!"); + + # CVS options: + # -q Quiet + # -n Do not change any files + + my(@args) = ('cvs'); + push(@args, '-q') if (! $self -> {'verbose'}); + push(@args, '-n') if ($n); + push(@args, 'update'); + @args = `@args`; + chomp(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + \@args; + +} # End of update. + +# -------------------------------------------------------------------------- +# Return Interpretation +# 0 Repository not up-to-date. +# 1 Up-to-date. + +sub upToDate +{ + my($self) = @_; + + # Get the status of the repository. + my($status) = $self -> status(); + @$status = grep(/Status/ && ! /Up-to-date/, @$status); + my($result) = 1; # Up-to-date. + $result = 0 if ($#{$status} >= 0); # Not, because log contains something. + + $result; + +} # End of upToDate. + +# -------------------------------------------------------------------------- +# Checkout a current copy of the project. +# You call checkOut, and it calls this. + +sub _checkOutDontCallMe +{ + my($self, $readOnly, $tag, $dir) = @_; + + # CVS options: + # -Q Really quiet. + # -r Read-only. Make the new working files read-only. + # -d$dir Use $dir, not $project, as the directory name. + # -r <tag> Check out files tagged with <tag>. Optional. + # + # $project Checkout this module. + + # CVS bug. Remove trailing '/', if any. + $dir = $1 if ($dir =~ /^(.+)\/$/); + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, '-r') if ($readOnly); + push(@args, 'checkout', '-A', '-P', "-d$dir"); + push(@args, '-r', $tag) if ($tag); + push(@args, $self -> {'project'}); + + $self -> runOrCroak(@args); + +} # End of _checkOutDontCallMe. + +# -------------------------------------------------------------------------- +# Fix a tag CVS failed to add. +# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23. + +sub _fixTag +{ + my($self, $tag) = @_; + + my($file) = "$ENV{'CVSROOT'}/CVSROOT/val-tags"; + + open(INX, $file) || croak("Can't open($file): \nFailure: $!"); + + my($found) = 0; + + while (<INX>) + { + $found = 1 if (/^$tag/); + } + + close(INX); + + if (! $found) + { + print "Warning: CVS bug. Tag $tag not in file $file\n" if ($self -> {'verbose'}); + print "Fixing... " if ($self -> {'verbose'}); + + open(OUT, ">> $file") || croak("Can't open(>>$file): \nFailure: $!"); + print OUT "$tag y\n"; + close(OUT); + + print "Success\n" if ($self -> {'verbose'}); + } + +} # End of _fixTag. + +# -------------------------------------------------------------------------- + +sub _mkpathOrCroak +{ + my($self, $dir) = @_; + + my($result) = mkpath($dir, $self -> {'verbose'}, $self -> {'permissions'}); + + croak("Can't mkpath($dir, $self->{'verbose'}, $self->{'permissions'}): \nFailure: $!") + if ( (! $result) && ($! !~ /No such file/) ); + +} # End of _mkpathOrCroak. + +# -------------------------------------------------------------------------- +# Return a reference to a list of lines. + +sub _readFile +{ + my($self, $file) = @_; + + open(INX, $file) || croak("Can't open($file): $!"); + my(@line) = <INX>; + close(INX); + chomp(@line); + + \@line; + +} # end of _readFile. + +# -------------------------------------------------------------------------- +# Tag the current version of the project. +# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23. +# You call setTag and it calls this. + +sub _setTag +{ + my($self, $tag) = @_; + + # Preserve the caller's current working directory. + # cvs tag only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir($ENV{'HOME'}) || croak("Can't chdir($ENV{'HOME'}): $!"); + + # CVS options: + # -Q Really quiet. + # -r <tag> Tag files with <tag>. + # $project Tag this module. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'tag', $tag, $self -> {'project'}); + + $self -> runOrCroak(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + # Compensate for yet another CVS bug. + $self -> _fixTag($tag); + +} # End of _setTag. + +# -------------------------------------------------------------------------- +# Validate an entry in one of the CVS files 'module' or 'val-tags'. +# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23. + +sub _validateObject +{ + my($self, $tag, $file, $mustBeAbsent) = @_; + + $file = "$ENV{'CVSROOT'}/CVSROOT/$file"; + + open(INX, $file) || croak("Can't open($file): \nFailure: $!"); + + my($found) = 0; + + while (<INX>) + { + $found = 1 if (/^$tag/); + } + + close(INX); + + croak("Failure: Tag not found: $tag in file $file") + if ( (! $found) && (! $mustBeAbsent) ); + + croak("Failure: Tag already present: $tag in file $file") + if ($found && $mustBeAbsent); + +} # End of _validateObject. + +# -------------------------------------------------------------------------- + +# Autoload methods go after =cut, and are processed by the autosplit program. + +1; + +__END__ + +=head1 NAME + +C<VCS::CVS> - Provide a simple interface to CVS (the Concurrent Versions System). + +You need to be clear in your mind about the 4 directories involved: + +=over 4 + +=item * + +The directory where your source code resides before you import it into CVS. +It is used only once - during the import phase. Call this $projectSource. + +=item * + +The directory into which you check out a read-write copy of the repository, +in order to edit that copy. Call this $project. You will spend up to 100% of +your time working within this directory structure. + +=item * + +The directory in which the repository resides. This is $CVSROOT. Thus +$projectSource will be imported into $CVSROOT/$project. + +=item * + +The directory into which you get a read-only copy of the repository, in order to, +say, make and ship that copy. Call this $someDir. It must not be $project. + +=back + +Note: You cannot have a directory called CVS in your home directory. That's +just asking for trouble. + +=head1 SYNOPSIS + + #!/usr/gnu/bin/perl -w + + use integer; + use strict; + + use VCS::CVS; + + my($history) = 1; + my($initialMsg) = 'Initial version'; + my($noChange) = 1; + my($nullTag) = ''; + my($permissions) = 0775; # But not '0775'! + my($project) = 'project'; + my($projectSource) = 'projectSource'; + my($raw) = 0; + my($readOnly) = 0; + my($releaseTag) = 'release_0.00'; + my($vendorTag) = 'vendorTag'; + my($verbose) = 1; + + # Note the anonymous hash in the next line, new as of V 1.10. + + my($cvs) = VCS::CVS -> new({ + 'project' => $project, + 'raw' => $raw, + 'verbose' => $verbose, + 'permissions' => $permissions, + 'history' => $history}); + + $cvs -> createRepository(); + $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg); + $cvs -> checkOut($readOnly, $nullTag, $project); + + print join("\n", @{$cvs -> update($noChange)}); + print "\n"; + print join("\n", @{$cvs -> history()}); + + exit(0); + +=head1 DESCRIPTION + +The C<VCS::CVS> module provides an OO interface to CVS. + +VCS - Version Control System - is the prefix given to each Perl module which +deals with some sort of source code control system. + +I have seen CVS corrupt binary files, even when run with CVS's binary option -kb. +So, since CVS doesn't support binary files, neither does VCS::CVS. + +Stop press: CVS V 1.10 (with RCS 5.7) supports binary files. + +Subroutines whose names start with a '_' are not normally called by you. + +There is a test program included, but I have not yet worked out exactly how to +set it up for make test. Stay tuned. + +=head1 INSTALLATION + +You install C<VCS::CVS>, as you would install any perl module library, +by running these commands: + + perl Makefile.PL + make + make test + make install + +If you want to install a private copy of C<VCS::CVS> in your home +directory, then you should try to produce the initial Makefile with +something like this command: + + perl Makefile.PL LIB=~/perl + or + perl Makefile.PL LIB=C:/Perl/Site/Lib + +If, like me, you don't have permission to write man pages into unix system +directories, use: + + make pure_install + +instead of make install. This option is secreted in the middle of p 414 of the +second edition of the dromedary book. + +=head1 WARNING re CVS bugs + +The following are my ideas as to what constitutes a bug in CVS: + +=over 4 + +=item * + +The initial revision tag, supplied when populating the repository with +'cvs import', is not saved into $CVSROOT/CVSROOT/val-tags. + +=item * + +The 'cvs tag' command does not always put the tag into 'val-tags'. + +=item * + +C<'cvs checkout -dNameOfDir'> fails if NameOfDir =~ /\/$/. + +=item * + +C<'cvs checkout -d NameOfDir'> inserts a leading space into the name of +the directory it creates. + +=back + +=head1 WARNING re test environment + +This code has only been tested under Unix. Sorry. + +=head1 WARNING re project names 'v' directory names + +I assume your copy of the repository was checked out into a directory with +the same name as the project, since I do a 'cd $HOME/$project' before running +'cvs status', to see if your copy is up-to-date. This is because some activity is +forbibben unless your copy is up-to-date. Typical cases of this include: + +=over 4 + +=item * + +C<checkOut> + +=item * + +C<removeDirectory> + +=item * + +C<setTag> + +=back + +=head1 WARNING re shell intervention + +Some commands cause the shell to become involved, which, under Unix, will read your +.cshrc or whatever, which in turn may set CVSROOT to something other than what you +set it to before running your script. If this happens, panic... + +Actually, I think I've eliminated such cases. You hope so. + +=head1 WARNING re Perl bug + +As always, be aware that these 2 lines mean the same thing, sometimes: + +=over 4 + +=item * + +$self -> {'thing'} + +=item * + +$self->{'thing'} + +=back + +The problem is the spaces around the ->. Inside double quotes, "...", the +first space stops the dereference taking place. Outside double quotes the +scanner correctly associates the $self token with the {'thing'} token. + +I regard this as a bug. + +=head1 addDirectory($dir, $subDir, $message) + +Add an existing directory to the project. + +$dir can be a full path, or relative to the CWD. + +=head1 addFile($dir, $file, $message) + +Add an existing file to the project. + +$dir can be a full path, or relative to the CWD. + +=head1 checkOut($readOnly, $tag, $dir) + +Prepare & perform 'cvs checkout'. + +You call checkOut, and it calls _checkOutDontCallMe. + +=over 4 + +=item * + +$readOnly == 0 -> Check out files as read-write. + +=item * + +$readOnly == 1 -> Check out files as read-only. + +=back + +=over 4 + +=item * + +$tag is Null -> Do not call upToDate; ie check out repository as is. + +=item * + +$tag is not Null -> Call upToDate; Croak if repository is not up-to-date. + +=back + +The value of $raw used in the call to new influences the handling of $tag: + +=over 4 + +=item * + +$raw == 1 -> Your tag is passed as is to CVS. + +=item * + +$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is +converted to CVS's form release_1_23. + +=back + +$dir can be a full path, or relative to the CWD. + +=head1 commit($message) + +Commit changes. + +Called as appropriate by addFile, removeFile and removeDirectory, +so you don't need to call it. + +=head1 createRepository() + +Create a repository, using the current $CVSROOT. + +This involves creating these files: + +=over 4 + +=item * + +$ENV{'CVSROOT'}/CVSROOT/modules + +=item * + +$ENV{'CVSROOT'}/CVSROOT/val-tags + +=item * + +$ENV{'CVSROOT'}/CVSROOT/history + +=back + +Notes: + +=over 4 + +=item * + +The 'modules' file contains these lines: + + CVSROOT CVSROOT + modules CVSROOT modules + $self -> {'project'} $self -> {'project'} + +where $self -> {'project'} comes from the 'project' parameter to new() + +=item * + +The 'val-tags' file is initially empty + +=item * + +The 'history' file is only created if the 'history' parameter to new() is set. +The file is initially empty + +=back + +=head1 getTags() + +Return a reference to a list of tags. + +See also: the $raw option to new(). + +C<getTags> does not take a project name because tags belong to the repository +as a whole, not to a project. + +=head1 history({}) + +Report details from the history log, $CVSROOT/CVSROOT/history. + +You must have used new({'history' => 1}), or some other mechanism, to create +the history file, before CVS starts logging changes into the history file. + +The anonymous hash takes any parameters 'cvs history' takes, and joins them +with a single space. Eg: + + $cvs -> history(); + + $cvs -> history({'-e' => ''}); + + $cvs -> history({'-xARM' => ''}); + + $cvs -> history({'-u' => $ENV{'LOGNAME'}, '-x' => 'A'}); + +but not + + $cvs -> history({'-xA' => 'M'}); + +because it doesn't work. + +=head1 new({}) + +Create a new object. See the synopsis. + +The anonymous hash takes these parameters, of which 'project' is the +only required one. + +=over 4 + +=item * + +'project' => 'killerApp'. The required name of the project. No default + +=back + +=over 4 + +=item * + +'permissions' => 0775. Unix-specific stuff. Default. Do not use '0775'. + +=back + +=over 4 + +=item * + +'history' => 0. Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default + +=item * + +'history' => 1. Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff + +=back + +=over 4 + +=item * + +'raw' => 0. Convert tags from CVS format to real format. Eg: release_1.23. Default. + +=item * + +'raw' => 1. Return tags in raw CVS format. Eg: release_1_23. + +=back + +=over 4 + +=item * + +'verbose' => 0. Do not report on the progress of mkpath/rmtree + +=item * + +'verbose' => 1. Report on the progress of mkpath/rmtree. Default + +=back + +=head1 populate($sourceDir, $vendorTag, $releaseTag, $message) + +Import an existing directory structure. But, (sub) import is a reserved word. + +Use this to populate a repository for the first time. + +The value used for $vendorTag is not important; CVS discards it. + +The value used to $releaseTag is important; CVS discards it (why?) but I +force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you +should supply a meaningful value. Thus 'release_0_00' is strongly, repeat +strongly, recommended. + +The value of $raw used in the call to new influences the handling of $tag: + +=over 4 + +=item * + +$raw == 1 -> Your tag is passed as is to CVS. + +=item * + +$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is +converted to CVS's form release_1_23. + +=back + +=head1 removeDirectory($dir) + +Remove a directory from the project. + +This deletes the directory (and all its files) from your working copy +of the repository, as well as deleting them from the repository. + +Warning: $dir will have $CVSROOT and $HOME prepended by this code. +Ie: $dir starts from - but excludes - your home directory +(assuming, of course, you've checked out into your home directory...). + +You can't remove the current directory, or a parent. + +=head1 removeFile($dir, $file, $message) + +Remove a file from the project. + +This deletes the file from your working copy of the repository, +as well as deleting it from the repository. + +$dir can be a full path, or relative to the CWD. +$file is relative to $dir. + +=head1 runOrCroak() + +The standard way to run a system command and report on the result. + +=head1 setTag($tag) + +Tag the repository. + +You call setTag, and it calls _setTag. + +The value of $raw used in the call to new influences the handling of $tag: + +=over 4 + +=item * + +$raw == 1 -> Your tag is passed as is to CVS. + +=item * + +$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is +converted to CVS's form release_1_23. + +=back + +=head1 stripCVSDirs($dir) + +Delete all CVS directories and files from a copy of the repository. + +Each user directory contains a CVS sub-directory, which holds 3 files: + +=over 4 + +=item * + +Entries + +=item * + +Repository + +=item * + +Root + +=back + +Zap 'em. + +=head1 status() + +Run cvs status. + +Return a reference to a list of lines. + +Only called by upToDate(), but you may call it. + +=head1 update($noChange) + +Run 'cvs C<-q> [C<-n>] update', returning a reference to a list of lines. +Each line will start with one of [UARMC?], as per the CVS docs. + +$cvs -> update(1) is a good way to get a list of uncommited changes, etc. + +=over 4 + +=item * + +$noChange == 0 -> Do not add C<-n> to the cvs command. Ie update your working copy + +=item * + +$noChange == 1 -> Add C<-n> to the cvs command. Do not change any files + +=back + +=head1 upToDate() + +=over 4 + +=item * + +return == 0 -> Repository not up-to-date. + +=item * + +return == 1 -> Up-to-date. + +=back + +=head1 _checkOutDontCallMe($readOnly, $tag, $dir) + +Checkout a current copy of the project. + +You call checkOut, and it calls this. + +=over 4 + +=item * + +$readOnly == 0 -> Check out files as read-write. + +=item * + +$readOnly == 1 -> Check out files as read-only. + +=back + +=head1 _fixTag($tag) + +Fix a tag which CVS failed to add. + +Warning: $tag must be in CVS format: release_1_23, not release_1.23. + +=head1 _mkpathOrCroak($self, $dir) + +There is no need for you to call this. + +=head1 _readFile($file) + +Return a reference to a list of lines. + +There is no need for you to call this. + +=head1 _setTag($tag) + +Tag the current version of the project. + +Warning: $tag must be in CVS format: release_1_23, not release_1.23. + +You call setTag and it calls this. + +=head1 _validateObject($tag, $file, $mustBeAbsent) + +Validate an entry in one of the CVS files 'module' or 'val-tags'. + +Warning: $tag must be in CVS format: release_1_23, not release_1.23. + +=head1 AUTHOR + +C<VCS::CVS> was written by Ron Savage I<E<lt>rpsavage@ozemail.com.auE<gt>> in 1998. + +=head1 LICENCE + +This program is free software; you can redistribute it and/or modify it under +the same terms as Perl itself. diff --git a/cpan/dist/VCS-CVS/Changes.txt b/cpan/dist/VCS-CVS/Changes.txt new file mode 100644 index 00000000..1ac65c5c --- /dev/null +++ b/cpan/dist/VCS-CVS/Changes.txt @@ -0,0 +1,29 @@ +Revision history for Perl extension VCS::CVS.
+
+2.00 17-Jun-99
+--------------
+o Change parameters to new(). It now accepts an anonymous hash
+o Add history parameter to new()
+o Add history(). It accepts an anonymous hash of 'cvs history' parameters,
+ and returns a ref to a list
+o Add update(). It accepts an optional boolean to active the -n in
+ 'cvs update -n', and returns a ref to a list
+o Fix 2 bugs whereby chdir() was called at the wrong time
+
+1.04 26-May-99
+--------------
+o Ensure POD survives buggy pod2man
+o Ship Readme.txt, the output of pod2text
+
+1.03 19-Apr-99
+--------------
+o Patch Makefile.PL to support ActivePerl's ppm.
+
+1.02 30-Mar-99
+--------------
+o Original version; created by h2xs 1.18
+
+Use h2xs to create the skeleton for CVS.pm.
+
+Fix populate & _setTag so they do a chdir
+back before calling _fixTag.
diff --git a/cpan/dist/VCS-CVS/MANIFEST b/cpan/dist/VCS-CVS/MANIFEST new file mode 100644 index 00000000..1aeb7df0 --- /dev/null +++ b/cpan/dist/VCS-CVS/MANIFEST @@ -0,0 +1,8 @@ +Changes.txt +CVS.pm +MANIFEST +MANIFEST.SKIP +Makefile.PL +Readme.txt +test.pl +t/base.t diff --git a/cpan/dist/VCS-CVS/MANIFEST.SKIP b/cpan/dist/VCS-CVS/MANIFEST.SKIP new file mode 100644 index 00000000..06c7fa7b --- /dev/null +++ b/cpan/dist/VCS-CVS/MANIFEST.SKIP @@ -0,0 +1,3 @@ +Makefile$ +^blib +^pm_to_blib diff --git a/cpan/dist/VCS-CVS/Makefile.PL b/cpan/dist/VCS-CVS/Makefile.PL new file mode 100644 index 00000000..87b9d0b2 --- /dev/null +++ b/cpan/dist/VCS-CVS/Makefile.PL @@ -0,0 +1,29 @@ +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. + +WriteMakefile +( + ($] ge '5.005') ? + ( + 'AUTHOR' => 'Ron Savage (rpsavage@ozemail.com.au)', + 'ABSTRACT' => "Interface to GNU's CVS", + ) : (), +'clean' => + { + 'FILES' => 'blib/* Makefile VCS-CVS-*' + }, +'dist' => + { + 'COMPRESS' => 'gzip', + 'SUFFIX' => 'gz' + }, +'DISTNAME' => 'VCS-CVS', +'NAME' => 'VCS::CVS', +'PM' => + { + 'CVS.pm' => '$(INST_LIBDIR)/CVS.pm', + }, +'PREREQ_PM' => {}, +'VERSION_FROM' => 'CVS.pm' +); diff --git a/cpan/dist/VCS-CVS/Readme.txt b/cpan/dist/VCS-CVS/Readme.txt new file mode 100644 index 00000000..6ac40487 --- /dev/null +++ b/cpan/dist/VCS-CVS/Readme.txt @@ -0,0 +1,739 @@ +.rn '' }` +''' $RCSfile$$Revision$$Date$ +''' +''' $Log$ +''' +.de Sh +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp +.if t .sp .5v +.if n .sp +.. +.de Ip +.br +.ie \\n(.$>=3 .ne \\$3 +.el .ne 3 +.IP "\\$1" \\$2 +.. +.de Vb +.ft CW +.nf +.ne \\$1 +.. +.de Ve +.ft R + +.fi +.. +''' +''' +''' Set up \*(-- to give an unbreakable dash; +''' string Tr holds user defined translation string. +''' Bell System Logo is used as a dummy character. +''' +.tr \(*W-|\(bv\*(Tr +.ie n \{\ +.ds -- \(*W- +.ds PI pi +.if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +.if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +.ds L" "" +.ds R" "" +''' \*(M", \*(S", \*(N" and \*(T" are the equivalent of +''' \*(L" and \*(R", except that they are used on ".xx" lines, +''' such as .IP and .SH, which do another additional levels of +''' double-quote interpretation +.ds M" """ +.ds S" """ +.ds N" """"" +.ds T" """"" +.ds L' ' +.ds R' ' +.ds M' ' +.ds S' ' +.ds N' ' +.ds T' ' +'br\} +.el\{\ +.ds -- \(em\| +.tr \*(Tr +.ds L" `` +.ds R" '' +.ds M" `` +.ds S" '' +.ds N" `` +.ds T" '' +.ds L' ` +.ds R' ' +.ds M' ` +.ds S' ' +.ds N' ` +.ds T' ' +.ds PI \(*p +'br\} +.\" If the F register is turned on, we'll generate +.\" index entries out stderr for the following things: +.\" TH Title +.\" SH Header +.\" Sh Subsection +.\" Ip Item +.\" X<> Xref (embedded +.\" Of course, you have to process the output yourself +.\" in some meaninful fashion. +.if \nF \{ +.de IX +.tm Index:\\$1\t\\n%\t"\\$2" +.. +.nr % 0 +.rr F +.\} +.TH CVS 3 "perl 5.005, patch 02" "17/Jun/99" "User Contributed Perl Documentation" +.UC +.if n .hy 0 +.if n .na +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.de CQ \" put $1 in typewriter font +.ft CW +'if n "\c +'if t \\&\\$1\c +'if n \\&\\$1\c +'if n \&" +\\&\\$2 \\$3 \\$4 \\$5 \\$6 \\$7 +'.ft R +.. +.\" @(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2 +. \" AM - accent mark definitions +.bd B 3 +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds ? ? +. ds ! ! +. ds / +. ds q +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds ? \s-2c\h'-\w'c'u*7/10'\u\h'\*(#H'\zi\d\s+2\h'\w'c'u*8/10' +. ds ! \s-2\(or\s+2\h'-\w'\(or'u'\v'-.8m'.\v'.8m' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +. ds q o\h'-\w'o'u*8/10'\s-4\v'.4m'\z\(*i\v'-.4m'\s+4\h'\w'o'u*8/10' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds v \\k:\h'-(\\n(.wu*9/10-\*(#H)'\v'-\*(#V'\*(#[\s-4v\s0\v'\*(#V'\h'|\\n:u'\*(#] +.ds _ \\k:\h'-(\\n(.wu*9/10-\*(#H+(\*(#F*2/3))'\v'-.4m'\z\(hy\v'.4m'\h'|\\n:u' +.ds . \\k:\h'-(\\n(.wu*8/10)'\v'\*(#V*4/10'\z.\v'-\*(#V*4/10'\h'|\\n:u' +.ds 3 \*(#[\v'.2m'\s-2\&3\s0\v'-.2m'\*(#] +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +.ds oe o\h'-(\w'o'u*4/10)'e +.ds Oe O\h'-(\w'O'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds v \h'-1'\o'\(aa\(ga' +. ds _ \h'-1'^ +. ds . \h'-1'. +. ds 3 3 +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +. ds oe oe +. ds Oe OE +.\} +.rm #[ #] #H #V #F C +.SH "NAME" +\f(CWVCS::CVS\fR \- Provide a simple interface to CVS (the Concurrent Versions System). +.PP +You need to be clear in your mind about the 4 directories involved: +.Ip "\(bu" 4 +The directory where your source code resides before you import it into \s-1CVS\s0. +It is used only once \- during the import phase. Call this \f(CW$projectSource\fR. +.Ip "\(bu" 4 +The directory into which you check out a read-write copy of the repository, +in order to edit that copy. Call this \f(CW$project\fR. You will spend up to 100% of +your time working within this directory structure. +.Ip "\(bu" 4 +The directory in which the repository resides. This is \f(CW$CVSROOT\fR. Thus +\f(CW$projectSource\fR will be imported into \f(CW$CVSROOT\fR/$project. +.Ip "\(bu" 4 +The directory into which you get a read-only copy of the repository, in order to, +say, make and ship that copy. Call this \f(CW$someDir\fR. It must not be \f(CW$project\fR. +.PP +Note: You cannot have a directory called \s-1CVS\s0 in your home directory. That's +just asking for trouble. +.SH "SYNOPSIS" +.PP +.Vb 1 +\& #!/usr/gnu/bin/perl -w +.Ve +.Vb 2 +\& use integer; +\& use strict; +.Ve +.Vb 1 +\& use VCS::CVS; +.Ve +.Vb 12 +\& my($history) = 1; +\& my($initialMsg) = 'Initial version'; +\& my($noChange) = 1; +\& my($nullTag) = ''; +\& my($permissions) = 0775; # But not '0775'! +\& my($project) = 'project'; +\& my($projectSource) = 'projectSource'; +\& my($raw) = 0; +\& my($readOnly) = 0; +\& my($releaseTag) = 'release_0.00'; +\& my($vendorTag) = 'vendorTag'; +\& my($verbose) = 1; +.Ve +.Vb 1 +\& # Note the anonymous hash in the next line, new as of V 1.10. +.Ve +.Vb 6 +\& my($cvs) = VCS::CVS -> new({ +\& 'project' => $project, +\& 'raw' => $raw, +\& 'verbose' => $verbose, +\& 'permissions' => $permissions, +\& 'history' => $history}); +.Ve +.Vb 3 +\& $cvs -> createRepository(); +\& $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg); +\& $cvs -> checkOut($readOnly, $nullTag, $project); +.Ve +.Vb 3 +\& print join("\en", @{$cvs -> update($noChange)}); +\& print "\en"; +\& print join("\en", @{$cvs -> history()}); +.Ve +.Vb 1 +\& exit(0); +.Ve +.SH "DESCRIPTION" +The \f(CWVCS::CVS\fR module provides an OO interface to CVS. +.PP +VCS \- Version Control System \- is the prefix given to each Perl module which +deals with some sort of source code control system. +.PP +I have seen CVS corrupt binary files, even when run with CVS's binary option \-kb. +So, since CVS doesn't support binary files, neither does VCS::CVS. +.PP +Stop press: CVS V 1.10 (with RCS 5.7) supports binary files. +.PP +Subroutines whose names start with a \*(L'_\*(R' are not normally called by you. +.PP +There is a test program included, but I have not yet worked out exactly how to +set it up for make test. Stay tuned. +.SH "INSTALLATION" +You install \f(CWVCS::CVS\fR, as you would install any perl module library, +by running these commands: +.PP +.Vb 4 +\& perl Makefile.PL +\& make +\& make test +\& make install +.Ve +If you want to install a private copy of \f(CWVCS::CVS\fR in your home +directory, then you should try to produce the initial Makefile with +something like this command: +.PP +.Vb 3 +\& perl Makefile.PL LIB=~/perl +\& or +\& perl Makefile.PL LIB=C:/Perl/Site/Lib +.Ve +If, like me, you don't have permission to write man pages into unix system +directories, use: +.PP +.Vb 1 +\& make pure_install +.Ve +instead of make install. This option is secreted in the middle of p 414 of the +second edition of the dromedary book. +.SH "WARNING re CVS bugs" +The following are my ideas as to what constitutes a bug in CVS: +.Ip "\(bu" 4 +The initial revision tag, supplied when populating the repository with +\&'cvs import\*(R', is not saved into \f(CW$CVSROOT\fR/\s-1CVSROOT/\s0val-tags. +.Ip "\(bu" 4 +The \*(L'cvs tag\*(R' command does not always put the tag into \*(L'val-tags\*(R'. +.Ip "\(bu" 4 +\&\f(CW'cvs checkout -dNameOfDir'\fR fails if NameOfDir =~ /\e/$/. +.Ip "\(bu" 4 +\&\f(CW'cvs checkout -d NameOfDir'\fR inserts a leading space into the name of +the directory it creates. +.SH "WARNING re test environment" +This code has only been tested under Unix. Sorry. +.SH "WARNING re project names \*(M'v\*(S' directory names" +I assume your copy of the repository was checked out into a directory with +the same name as the project, since I do a \*(L'cd \f(CW$HOME\fR/$project\*(R' before running +\&'cvs status\*(R', to see if your copy is up-to-date. This is because some activity is +forbibben unless your copy is up-to-date. Typical cases of this include: +.Ip "\(bu" 4 +\f(CWcheckOut\fR +.Ip "\(bu" 4 +\f(CWremoveDirectory\fR +.Ip "\(bu" 4 +\f(CWsetTag\fR +.SH "WARNING re shell intervention" +Some commands cause the shell to become involved, which, under Unix, will read your +\&.cshrc or whatever, which in turn may set CVSROOT to something other than what you +set it to before running your script. If this happens, panic... +.PP +Actually, I think I've eliminated such cases. You hope so. +.SH "WARNING re Perl bug" +As always, be aware that these 2 lines mean the same thing, sometimes: +.Ip "\(bu" 4 +$self \-> {'thing'} +.Ip "\(bu" 4 +$self->{'thing'} +.PP +The problem is the spaces around the \->. Inside double quotes, \*(L"...\*(R", the +first space stops the dereference taking place. Outside double quotes the +scanner correctly associates the \f(CW$self\fR token with the {'thing'} token. +.PP +I regard this as a bug. +.SH "\fIaddDirectory\fR\|($dir, \f(CW$subDir\fR, \f(CW$message\fR)" +Add an existing directory to the project. +.PP +$dir can be a full path, or relative to the CWD. +.SH "\fIaddFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)" +Add an existing file to the project. +.PP +$dir can be a full path, or relative to the CWD. +.SH "\fIcheckOut\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)" +Prepare & perform \*(L'cvs checkout\*(R'. +.PP +You call checkOut, and it calls _checkOutDontCallMe. +.Ip "\(bu" 4 +$readOnly == 0 \-> Check out files as read-write. +.Ip "\(bu" 4 +$readOnly == 1 \-> Check out files as read-only. +.Ip "\(bu" 4 +$tag is Null \-> Do not call upToDate; ie check out repository as is. +.Ip "\(bu" 4 +$tag is not Null \-> Call upToDate; Croak if repository is not up-to-date. +.PP +The value of \f(CW$raw\fR used in the call to new influences the handling of \f(CW$tag:\fR +.Ip "\(bu" 4 +$raw == 1 \-> Your tag is passed as is to \s-1CVS\s0. +.Ip "\(bu" 4 +$raw == 0 \-> Your tag is assumed to be of the form release_1.23, and is +converted to \s-1CVS\s0's form release_1_23. +.PP +$dir can be a full path, or relative to the \s-1CWD\s0. +.SH "\fIcommit\fR\|($message)" +Commit changes. +.PP +Called as appropriate by addFile, removeFile and removeDirectory, +so you don't need to call it. +.SH "\fIcreateRepository()\fR" +Create a repository, using the current \f(CW$CVSROOT\fR. +.PP +This involves creating these files: +.Ip "\(bu" 4 +$\s-1ENV\s0{'\s-1CVSROOT\s0'}/\s-1CVSROOT/\s0modules +.Ip "\(bu" 4 +$\s-1ENV\s0{'\s-1CVSROOT\s0'}/\s-1CVSROOT/\s0val-tags +.Ip "\(bu" 4 +$\s-1ENV\s0{'\s-1CVSROOT\s0'}/\s-1CVSROOT/\s0history +.PP +Notes: +.Ip "\(bu" 4 +The \*(L'modules\*(R' file contains these lines: +.Sp +.Vb 3 +\& CVSROOT CVSROOT +\& modules CVSROOT modules +\& $self -> {'project'} $self -> {'project'} +.Ve +where \f(CW$self\fR \-> {'project'} comes from the \*(L'project\*(R' parameter to \fInew()\fR +.Ip "\(bu" 4 +The \*(L'val-tags\*(R' file is initially empty +.Ip "\(bu" 4 +The \*(L'history\*(R' file is only created if the \*(L'history\*(R' parameter to \fInew()\fR is set. +The file is initially empty +.SH "\fIgetTags()\fR" +Return a reference to a list of tags. +.PP +See also: the \f(CW$raw\fR option to \fInew()\fR. +.PP +\f(CWgetTags\fR does not take a project name because tags belong to the repository +as a whole, not to a project. +.SH "\fIhistory\fR\|({})" +Report details from the history log, \f(CW$CVSROOT\fR/CVSROOT/history. +.PP +You must have used \fInew\fR\|({'history\*(R' => 1}), or some other mechanism, to create +the history file, before CVS starts logging changes into the history file. +.PP +The anonymous hash takes any parameters \*(L'cvs history\*(R' takes, and joins them +with a single space. Eg: +.PP +.Vb 1 +\& $cvs -> history(); +.Ve +.Vb 1 +\& $cvs -> history({'-e' => ''}); +.Ve +.Vb 1 +\& $cvs -> history({'-xARM' => ''}); +.Ve +.Vb 1 +\& $cvs -> history({'-u' => $ENV{'LOGNAME'}, '-x' => 'A'}); +.Ve +but not +.PP +.Vb 1 +\& $cvs -> history({'-xA' => 'M'}); +.Ve +because it doesn't work. +.SH "\fInew\fR\|({})" +Create a new object. See the synopsis. +.PP +The anonymous hash takes these parameters, of which \*(L'project\*(R' is the +only required one. +.Ip "\(bu" 4 +\&'project\*(R' => \*(L'killerApp\*(R'. The required name of the project. No default +.Ip "\(bu" 4 +\&'permissions\*(R' => 0775. Unix-specific stuff. Default. Do not use \*(L'0775\*(R'. +.Ip "\(bu" 4 +\&'history\*(R' => 0. Do not create \f(CW$CVSROOT\fR/\s-1CVSROOT/\s0history when \fIcreateRepository()\fR is called. Default +.Ip "\(bu" 4 +\&'history\*(R' => 1. Create \f(CW$CVSROOT\fR/\s-1CVSROOT/\s0history, which initiates \*(L'cvs history\*(R' stuff +.Ip "\(bu" 4 +\&'raw\*(R' => 0. Convert tags from \s-1CVS\s0 format to real format. Eg: release_1.23. Default. +.Ip "\(bu" 4 +\&'raw\*(R' => 1. Return tags in raw \s-1CVS\s0 format. Eg: release_1_23. +.Ip "\(bu" 4 +\&'verbose\*(R' => 0. Do not report on the progress of mkpath/rmtree +.Ip "\(bu" 4 +\&'verbose\*(R' => 1. Report on the progress of mkpath/rmtree. Default +.SH "\fIpopulate\fR\|($sourceDir, \f(CW$vendorTag\fR, \f(CW$releaseTag\fR, \f(CW$message\fR)" +Import an existing directory structure. But, (sub) import is a reserved word. +.PP +Use this to populate a repository for the first time. +.PP +The value used for \f(CW$vendorTag\fR is not important; CVS discards it. +.PP +The value used to \f(CW$releaseTag\fR is important; CVS discards it (why?) but I +force it to be the first tag in \f(CW$CVSROOT\fR/CVSROOT/val-tags. Thus you +should supply a meaningful value. Thus \*(L'release_0_00\*(R' is strongly, repeat +strongly, recommended. +.PP +The value of \f(CW$raw\fR used in the call to new influences the handling of \f(CW$tag:\fR +.Ip "\(bu" 4 +$raw == 1 \-> Your tag is passed as is to \s-1CVS\s0. +.Ip "\(bu" 4 +$raw == 0 \-> Your tag is assumed to be of the form release_1.23, and is +converted to \s-1CVS\s0's form release_1_23. +.SH "\fIremoveDirectory\fR\|($dir)" +Remove a directory from the project. +.PP +This deletes the directory (and all its files) from your working copy +of the repository, as well as deleting them from the repository. +.PP +Warning: \f(CW$dir\fR will have \f(CW$CVSROOT\fR and \f(CW$HOME\fR prepended by this code. +Ie: \f(CW$dir\fR starts from \- but excludes \- your home directory +(assuming, of course, you've checked out into your home directory...). +.PP +You can't remove the current directory, or a parent. +.SH "\fIremoveFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)" +Remove a file from the project. +.PP +This deletes the file from your working copy of the repository, +as well as deleting it from the repository. +.PP +$dir can be a full path, or relative to the CWD. +\f(CW$file\fR is relative to \f(CW$dir\fR. +.SH "\fIrunOrCroak()\fR" +The standard way to run a system command and report on the result. +.SH "\fIsetTag\fR\|($tag)" +Tag the repository. +.PP +You call setTag, and it calls _setTag. +.PP +The value of \f(CW$raw\fR used in the call to new influences the handling of \f(CW$tag:\fR +.Ip "\(bu" 4 +$raw == 1 \-> Your tag is passed as is to \s-1CVS\s0. +.Ip "\(bu" 4 +$raw == 0 \-> Your tag is assumed to be of the form release_1.23, and is +converted to \s-1CVS\s0's form release_1_23. +.SH "\fIstripCVSDirs\fR\|($dir)" +Delete all CVS directories and files from a copy of the repository. +.PP +Each user directory contains a CVS sub-directory, which holds 3 files: +.Ip "\(bu" 4 +Entries +.Ip "\(bu" 4 +Repository +.Ip "\(bu" 4 +Root +.PP +Zap \*(L'em. +.SH "\fIstatus()\fR" +Run cvs status. +.PP +Return a reference to a list of lines. +.PP +Only called by \fIupToDate()\fR, but you may call it. +.SH "\fIupdate\fR\|($noChange)" +Run \*(L'cvs \f(CW-q\fR [\f(CW-n\fR] update\*(R', returning a reference to a list of lines. +Each line will start with one of [UARMC?], as per the CVS docs. +.PP +$cvs \-> \fIupdate\fR\|(1) is a good way to get a list of uncommited changes, etc. +.Ip "\(bu" 4 +$noChange == 0 \-> Do not add \f(CW-n\fR to the cvs command. Ie update your working copy +.Ip "\(bu" 4 +$noChange == 1 \-> Add \f(CW-n\fR to the cvs command. Do not change any files +.SH "\fIupToDate()\fR" +.Ip "\(bu" 4 +return == 0 \-> Repository not up-to-date. +.Ip "\(bu" 4 +return == 1 \-> Up-to-date. +.SH "\fI_checkOutDontCallMe\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)" +Checkout a current copy of the project. +.PP +You call checkOut, and it calls this. +.Ip "\(bu" 4 +$readOnly == 0 \-> Check out files as read-write. +.Ip "\(bu" 4 +$readOnly == 1 \-> Check out files as read-only. +.SH "\fI_fixTag\fR\|($tag)" +Fix a tag which CVS failed to add. +.PP +Warning: \f(CW$tag\fR must be in CVS format: release_1_23, not release_1.23. +.SH "\fI_mkpathOrCroak\fR\|($self, \f(CW$dir\fR)" +There is no need for you to call this. +.SH "\fI_readFile\fR\|($file)" +Return a reference to a list of lines. +.PP +There is no need for you to call this. +.SH "\fI_setTag\fR\|($tag)" +Tag the current version of the project. +.PP +Warning: \f(CW$tag\fR must be in CVS format: release_1_23, not release_1.23. +.PP +You call setTag and it calls this. +.SH "\fI_validateObject\fR\|($tag, \f(CW$file\fR, \f(CW$mustBeAbsent\fR)" +Validate an entry in one of the CVS files \*(L'module\*(R' or \*(L'val-tags\*(R'. +.PP +Warning: \f(CW$tag\fR must be in CVS format: release_1_23, not release_1.23. +.SH "AUTHOR" +\f(CWVCS::CVS\fR was written by Ron Savage \fI<rpsavage@ozemail.com.au>\fR in 1998. +.SH "LICENCE" +This program is free software; you can redistribute it and/or modify it under +the same terms as Perl itself. + +.rn }` '' +.IX Title "CVS 3" +.IX Name "C<VCS::CVS> - Provide a simple interface to CVS (the Concurrent Versions System)." + +.IX Header "NAME" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "SYNOPSIS" + +.IX Header "DESCRIPTION" + +.IX Header "INSTALLATION" + +.IX Header "WARNING re CVS bugs" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "WARNING re test environment" + +.IX Header "WARNING re project names \*(M'v\*(S' directory names" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "WARNING re shell intervention" + +.IX Header "WARNING re Perl bug" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIaddDirectory\fR\|($dir, \f(CW$subDir\fR, \f(CW$message\fR)" + +.IX Header "\fIaddFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)" + +.IX Header "\fIcheckOut\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIcommit\fR\|($message)" + +.IX Header "\fIcreateRepository()\fR" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIgetTags()\fR" + +.IX Header "\fIhistory\fR\|({})" + +.IX Header "\fInew\fR\|({})" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIpopulate\fR\|($sourceDir, \f(CW$vendorTag\fR, \f(CW$releaseTag\fR, \f(CW$message\fR)" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIremoveDirectory\fR\|($dir)" + +.IX Header "\fIremoveFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)" + +.IX Header "\fIrunOrCroak()\fR" + +.IX Header "\fIsetTag\fR\|($tag)" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIstripCVSDirs\fR\|($dir)" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIstatus()\fR" + +.IX Header "\fIupdate\fR\|($noChange)" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fIupToDate()\fR" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fI_checkOutDontCallMe\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)" + +.IX Item "\(bu" + +.IX Item "\(bu" + +.IX Header "\fI_fixTag\fR\|($tag)" + +.IX Header "\fI_mkpathOrCroak\fR\|($self, \f(CW$dir\fR)" + +.IX Header "\fI_readFile\fR\|($file)" + +.IX Header "\fI_setTag\fR\|($tag)" + +.IX Header "\fI_validateObject\fR\|($tag, \f(CW$file\fR, \f(CW$mustBeAbsent\fR)" + +.IX Header "AUTHOR" + +.IX Header "LICENCE" + diff --git a/cpan/dist/VCS-CVS/t/base.t b/cpan/dist/VCS-CVS/t/base.t new file mode 100644 index 00000000..b690dc50 --- /dev/null +++ b/cpan/dist/VCS-CVS/t/base.t @@ -0,0 +1,38 @@ +# -*- perl -*- + +use integer; +use strict; + +use vars qw($loaded); + +BEGIN +{ + $| = 1; + print "1..2\n"; +} + +END +{ + print "not ok 1\n" if (! $loaded); +} + +use VCS::CVS; + +$loaded = 1; + +print "ok 1\n"; + +my($testNum) = 1; + +sub Test($) +{ + my($result) = shift; + $testNum++; + print ( ($result ? "" : "not "), "ok $testNum\n"); + $result; +} + +$ENV{'CVSROOT'} = '.'; +my($spell) = VCS::CVS -> new({'project' => 'test'}); + +Test($spell); # or print "Error...\n"; diff --git a/cpan/dist/VCS-CVS/test.pl b/cpan/dist/VCS-CVS/test.pl new file mode 100755 index 00000000..f88e4bf8 --- /dev/null +++ b/cpan/dist/VCS-CVS/test.pl @@ -0,0 +1,276 @@ +#!/usr/gnu/bin/perl -w +# +# Name: +# test.pl. +# +# Purpose: +# To test $PERL5LIB/VCS/CVS.pm. +# +# Warning: +# setenv CVSROOT <somethingHarmless> during this. + +use integer; +use strict; + +use Cwd; +use File::Basename; +use File::Copy; +use File::Path; +use VCS::CVS; + +#------------------------------------------------------------------ + +sub addDirectory +{ + my($cvs, $projectName, $subDirName, $fileName, $addDirMsg, + $addFileMsg, $verbose, $permissions) = @_; + + &init("$projectName/$subDirName", $fileName, $verbose, $permissions); + + &heading('addDirectory'); + $cvs -> addDirectory($projectName, $subDirName, $addDirMsg); + + print "\n"; + + # We can only add a file if we haven't used a sticky tag. + if ($projectName !~ /Strip/) + { + $fileName = fileparse($fileName, ''); + + &heading('addFile'); + $cvs -> addFile("$projectName/$subDirName", $fileName, $addFileMsg); + + print "\n"; + } + +} # End of addDirectory. + +#------------------------------------------------------------------ + +sub checkOut +{ + my($cvs, $readOnly, $dirName, $oldTag) = @_; + + &heading('checkOut'); + $cvs -> checkOut($readOnly, $oldTag, $dirName); + + &printDir($dirName); + + print "\n"; + +} # End of checkOut. + +#------------------------------------------------------------------ + +sub createRepository +{ + my($cvs, $projectSource, $vendorTag, $releaseTag, $initialMsg) = @_; + + &heading('createRepository'); + $cvs -> createRepository(); + + print "\n"; + + &heading('populate'); + $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg); + + print "\n"; + +} # End of creatRepository. + +#------------------------------------------------------------------ + +sub getTags +{ + my($cvs) = @_; + + &heading('getTags'); + my($tagRef) = $cvs -> getTags(); + + print "Tags: \n"; + + for (sort(@$tagRef) ) + { + print "$_\n"; + } + +} # End of getTags. + +#------------------------------------------------------------------ + +sub heading +{ + my($heading) = @_; + + print "$heading\n"; + print '-' x (length($heading) ), "\n"; + +} # End of heading. + +#------------------------------------------------------------------ + +sub init +{ + my($projectSource, $fileName, $verbose, $permissions) = @_; + + my($destination) = "$ENV{'HOME'}/$projectSource"; + + &heading("rmtree+mkpath($destination)"); + rmtree($destination, $verbose); + mkpath($destination, $verbose, $permissions); + + copy($fileName, $destination); + + &printDir($destination); + + print "\n"; + +} # End of init. + +#------------------------------------------------------------------ + +sub printDir +{ + my($dirName) = @_; + + opendir(INX, $dirName) || die("Can't opendir($dirName): $!"); + my(@file) = readdir(INX); + closedir(INX); + + print "Directory: $dirName. Files: \n"; + + for (@file) + { + print "$_\n"; + } + +} # End of printDir. + +#------------------------------------------------------------------ + +sub setTag +{ + my($cvs, $newTag) = @_; + + # my($cvs, $dirName, $fileName, $newTag) = @_; + # + # Edit file, to cause failure of upToDate call within setTag. + # chdir($dirName) || die(Can't chdir($dirName): $!"); + # my($line) = &readFile($fileName); + # splice(@$line, 5, 2); + # &writeFile($fileName, $line); + + &heading('setTag'); + $cvs -> setTag($newTag); + + print "\n"; + + &getTags($cvs); + + print "\n"; + +} # End of setTag. + +#------------------------------------------------------------------ + +sub strip +{ + my($cvs, $dirName) = @_; + + &heading('stripCVSDirs'); + $cvs -> stripCVSDirs($dirName); + + print "\n"; + +} # End of strip. + +#------------------------------------------------------------------ + +sub upToDate +{ + my($cvs) = @_; + + &heading('status'); + my($status) = $cvs -> status(); + + print "Status: \n"; + for (@$status) + { + print "$_\n"; + } + + print "\n"; + + &heading('upToDate'); + my($upToDate) = $cvs -> upToDate(); + + print 'The repository is ', ($upToDate ? '' : 'not '), "up-to-date\n"; + print "\n"; + +} # End of upToDate. + +#------------------------------------------------------------------ + +my($addDirMsg) = 'Add directory'; +my($addFileMsg) = 'Add file'; +my($dirName) = 'project'; +my($fileName) = fileparse($0, ''); +my($history) = 1; +my($initialMsg) = 'Initial version'; +my($myself) = cwd() . "/$fileName"; +my($newTag) = 'release_0.01'; +my($noChange) = 1; +my($nullTag) = ''; +my($permissions) = 0775; # But not '0775'! +my($projectName) = 'project'; +my($projectSource) = 'projectSource'; +my($raw) = 0; +my($readOnly) = 0; +my($releaseTag) = 'release_0.00'; +my($removeFileMsg) = 'Remove file'; +my($repository) = 'repository'; +my($roDirName) = 'projectReadOnly'; +my($stripDirName) = 'projectStrip'; +my($subDirName) = 'subDir'; +my($vendorTag) = 'vendorTag'; +my($verbose) = 1; + +$ENV{'HOME'} = cwd(); + +$ENV{'CVSROOT'} = "$ENV{'HOME'}/VCS-CVS-test/$repository"; + +my($cvs) = VCS::CVS -> new({ + 'project' => $projectName, + 'raw' => $raw, + 'history' => $history, + 'permissions' => $permissions, + 'verbose' => $verbose}); + +&init($projectSource, $myself, $verbose, $permissions); + +chdir($ENV{'HOME'}) || die("Can't chdir($ENV{'HOME'}): $!"); + +&createRepository($cvs, $projectSource, $vendorTag, $releaseTag, $initialMsg); + +&checkOut($cvs, $readOnly, $projectName, $nullTag); +&checkOut($cvs, $readOnly, $stripDirName, $releaseTag); +&checkOut($cvs, (! $readOnly), $roDirName, $releaseTag); + +&addDirectory($cvs, $projectName, $subDirName, $myself, $addDirMsg, + $addFileMsg, $verbose, $permissions); +&addDirectory($cvs, $stripDirName, $subDirName, $myself, $addDirMsg, + $addFileMsg, $verbose, $permissions); + +#&setTag($cvs, $projectName, $fileName, $newTag); +&setTag($cvs, $newTag); + +&upToDate($cvs); + +print "Update returned: \n", join("\n", @{$cvs -> update($noChange)}), "\n"; +print "\n"; +print "History returned: \n", join("\n", @{$cvs -> history({'-e' => ''})}), "\n"; + +&strip($cvs, $stripDirName); + +# Success. +exit(0); diff --git a/cpan/lib/Rcs.pm b/cpan/lib/Rcs.pm new file mode 100644 index 00000000..1c6e2f4b --- /dev/null +++ b/cpan/lib/Rcs.pm @@ -0,0 +1,1343 @@ +package Rcs; +require 5.001; +use strict; +use Carp; +use Time::Local; +use vars qw($VERSION $revision); + +#------------------------------------------------------------------ +# global stuff +#------------------------------------------------------------------ +$VERSION = '0.09'; +$revision = '$Id: Rcs.pm,v 1.14.1.3 1998/10/19 18:04:48 freter Exp $'; +my $Dir_Sep = ($^O eq 'MSWin32') ? '\\' : '/'; +my $Exe_Ext = ($^O eq 'MSWin32') ? '.exe' : ''; +my $Rcs_Bin_Dir = '/usr/local/bin'; +my $Rcs_Dir = '.' . $Dir_Sep . 'RCS'; +my $Work_Dir = '.'; +my $Quiet = 1; # RCS quiet mode +my $Arc_Ext = ',v'; + +#------------------------------------------------------------------ +# RCS object constructor +#------------------------------------------------------------------ +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + + # provide default values for system stuff + $self->{"_BINDIR"} = \$Rcs_Bin_Dir; + $self->{"_QUIET"} = \$Quiet; + $self->{"_RCSDIR"} = \$Rcs_Dir; + $self->{"_WORKDIR"} = \$Work_Dir; + $self->{"_ARCEXT"} = \$Arc_Ext; + + $self->{FILE} = undef; + $self->{ARCFILE} = undef; + $self->{AUTHOR} = undef; + $self->{COMMENTS} = undef; + $self->{DATE} = undef; + $self->{LOCK} = undef; + $self->{ACCESS} = []; + $self->{REVISIONS} = []; + $self->{REVINFO} = undef; + $self->{STATE} = undef; + $self->{SYMBOLS} = undef; + bless($self, $class); + return $self; +} + +#------------------------------------------------------------------ +# access +# Access list of archive file. +#------------------------------------------------------------------ +sub access { + my $self = shift; + + if (not @{ $self->{ACCESS} }) { + _parse_rcs_header($self); + } + + # dereference revisions list + my @access = @{ $self->{ACCESS} }; + + return @access; +} + +#------------------------------------------------------------------ +# arcext +# Set the RCS archive file extension (default is ',v'). +#------------------------------------------------------------------ +sub arcext { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_ARCEXT"} } = shift }; + return ${ $self->{"_ARCEXT"} }; + } + + # called as class method + else { + if (@_) { $Arc_Ext = shift; } + return $Arc_Ext; + } +} + +#------------------------------------------------------------------ +# arcfile +# Name of RCS archive file. +# If not set then return name of working file with RCS +# extension (',v'). +#------------------------------------------------------------------ +sub arcfile { + my $self = shift; + if (@_) { $self->{ARCFILE} = shift } + return $self->{ARCFILE} || $self->{FILE} . ${ $self->{"_ARCEXT"} }; +} + +#------------------------------------------------------------------ +# author +# Return the author of an RCS revision. +# If revision is not provided, default to 'head' revision. +#------------------------------------------------------------------ +sub author { + my $self = shift; + + if (not defined $self->{AUTHOR}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference author hash + my %author_array = %{ $self->{AUTHOR} }; + + return $author_array{$revision}; +} + +#------------------------------------------------------------------ +# bindir +# Set the bin directory in which the RCS distribution programs +# reside. +#------------------------------------------------------------------ +sub bindir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_BINDIR"} } = shift }; + return ${ $self->{"_BINDIR"} }; + } + + # called as class method + else { + if (@_) { $Rcs_Bin_Dir = shift }; + return $Rcs_Bin_Dir; + } +} + +#------------------------------------------------------------------ +# ci +# Execute RCS 'ci' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub ci { + my $self = shift; + my @param = @_; + + my $ciprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'ci' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "ci program $ciprog not found" unless -e $ciprog; + croak "ci program $ciprog not executable" unless -x $ciprog; + system($ciprog, @param) == 0 or croak "$!"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# co +# Execute RCS 'co' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub co { + my $self = shift; + my @param = @_; + + my $coprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'co' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "co program $coprog not found" unless -e $coprog; + croak "co program $coprog not executable" unless -x $coprog; + system($coprog, @param) == 0 or croak "$!"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# comments +#------------------------------------------------------------------ +sub comments { + my $self = shift; + + if (not defined $self->{COMMENTS}) { + _parse_rcs_body($self); + } + + return %{$self->{COMMENTS}}; +} + +#------------------------------------------------------------------ +# daterev +# +# Returns revisions which were created before a specified date. +# +# Method takes one or six arguments. +# +# If one argument, then argument is date number. +# +# If six arguments, then year (4 digit year), month (1-12), day +# of month (1-31), hour (0-23), minute (0-59) and second (0-59). +#------------------------------------------------------------------ +sub daterev { + + my $self = shift; + my $target_time; + + # validate arguments + unless (@_ == 1 or @_ == 6) { + croak "daterev must have either 1 or 6 arguments"; + } + + # string date passed + if (@_ == 6) { + my($year, $mon, $mday, $hour, $min, $sec) = @_; + + if($year !~ /^\d{4}$/) { + croak "year (1st param) must be 4 digit number"; + } + + $mon--; # convert to 0-11 range + $target_time = timegm($sec, $min, $hour, $mday, $mon, $year); + } + + # system date passed + else { + $target_time = shift; + + if ($target_time !~ /^\d+$/) { + croak "system date must be an integer"; + } + } + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + + my @revisions = (); + my %dates; + my %dates_hash = %{$self->{DATE}}; + + my $revision; + foreach $revision (keys %dates_hash) { + my $date = $dates_hash{$revision}; + $dates{$date}{$revision} = 1; + } + + my $date; + foreach $date (reverse sort keys %dates) { + foreach $revision (keys %{ $dates{$date} }) { + push @revisions, $revision if $date <= $target_time; + } + } + + return wantarray ? @revisions : $revisions[0]; +} + +#------------------------------------------------------------------ +# dates +# Return a hash of revision dates, keyed on revision, when called +# in list mode. +# Return the most recent date when called in scalar mode. +# +# RCS stores dates in GMT. +# The date values are system dates. +#------------------------------------------------------------------ +sub dates { + my $self = shift; + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + + my %DatesHash = %{$self->{DATE}}; + my @dates_list = sort {$b<=>$a} values %DatesHash; + my $MostRecent = $dates_list[0]; + + return wantarray ? %DatesHash : $MostRecent; +} + +#------------------------------------------------------------------ +# file +# Name of working file. +#------------------------------------------------------------------ +sub file { + my $self = shift; + if (@_) { $self->{FILE} = shift } + return $self->{FILE}; +} + +#------------------------------------------------------------------ +# head +# Return the head revision. +#------------------------------------------------------------------ +sub head { + my $self = shift; + + if (not defined $self->{HEAD}) { + _parse_rcs_header($self); + } + return $self->{HEAD}; +} + +#------------------------------------------------------------------ +# lock +# Return user who has file locked. +#------------------------------------------------------------------ +sub lock { + my $self = shift; + + if (not defined $self->{LOCK}) { + _parse_rcs_header($self); + } + return $self->{LOCK}; +} + +#------------------------------------------------------------------ +# quiet +# Set or un-set RCS quiet mode. +#------------------------------------------------------------------ +sub quiet { + my $self = shift; + + # called as object method + if (ref $self) { + + # set/un-set quiet mode + if (@_) { + my $mode = shift; + croak "Passed parameter must be either '0' or '1'" + unless $mode == 0 or $mode == 1; + ${ $self->{"_QUIET"} } = $mode; + return ${ $self->{"_QUIET"} }; + } + + # access quiet mode + else { + return ${ $self->{"_QUIET"} }; + } + } + + # called as class method + else { + + # set/un-set quiet mode + if (@_) { + my $mode = shift; + croak "Passed parameter must be either '0' or '1'" + unless $mode == 0 or $mode == 1; + $Quiet = $mode; + return $Quiet; + } + + # access quiet mode + else { + return $Quiet; + } + } +} + +#------------------------------------------------------------------ +# rcs +# Execute RCS 'rcs' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub rcs { + my $self = shift; + my @param = @_; + + my $rcsprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcs' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + + # run program + croak "rcs program $rcsprog not found" unless -e $rcsprog; + croak "rcs program $rcsprog not executable" unless -x $rcsprog; + system($rcsprog, @param) == 0 or croak "$?"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# rcsclean +# Execute RCS 'rcsclean' program. +#------------------------------------------------------------------ +sub rcsclean { + my $self = shift; + my @param = @_; + + my $rcscleanprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsclean' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $workdir = ${ $self->{"_WORKDIR"} }; + my $file = $self->{FILE}; + my $arcfile = $self->{ARCFILE} || $file; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $workdir . $Dir_Sep . $file; + push @param, $archive_file, $workfile; + + # run program + croak "rcsclean program $rcscleanprog not found" unless -e $rcscleanprog; + croak "rcsclean program $rcscleanprog not executable" unless -x $rcscleanprog; + system($rcscleanprog, @param) == 0 or croak "$?"; + + # re-parse RCS file and clear comments hash + _parse_rcs_header($self); + $self->{COMMENTS} = undef; +} + +#------------------------------------------------------------------ +# rcsdiff +# Execute RCS 'rcsdiff' program. +# Calling in list context returns the output of rcsdiff, while +# calling in scalar context returns the return status of the +# rcsdiff program. +#------------------------------------------------------------------ +sub rcsdiff { + my $self = shift; + my @param = @_; + + my $rcsdiff_prog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsdiff' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $arcfile = $self->{ARCFILE} || $self->{FILE}; + $arcfile = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + my $workfile = $self->workdir . $Dir_Sep . $self->file; + + # un-taint parameter string + unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode + my $param_str = join(' ', @param); + $param_str =~ s/([\w-]+)/$1/g; + + croak "rcsdiff program $rcsdiff_prog not found" unless -e $rcsdiff_prog; + croak "rcsdiff program $rcsdiff_prog not executable" unless -x $rcsdiff_prog; + open(DIFF, "$rcsdiff_prog $param_str $arcfile $workfile |"); + my @diff_output = <DIFF>; + + # rcsdiff returns exit status 0 for no differences, 1 for differences, + # and 2 for error condition. + close DIFF; + my $status = $?; + croak "$rcsdiff_prog failed" if $status == 2; + return wantarray ? @diff_output : $status; +} + +#------------------------------------------------------------------ +# rcsdir +# Location of 'RCS' archive directory. +#------------------------------------------------------------------ +sub rcsdir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_RCSDIR"} } = shift } + return ${ $self->{"_RCSDIR"} }; + } + + # called as class method + else { + if (@_) { $Rcs_Dir = shift } + return $Rcs_Dir; + } +} + +#------------------------------------------------------------------ +# revdate +# Return the revision date of an RCS revision. +# If revision is not provided, default to 'head' revision. +# +# RCS stores dates in GMT. This method will return dates relative +# to the local time zone. +#------------------------------------------------------------------ +sub revdate { + my $self = shift; + + if (not defined $self->{DATE}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference date hash + my %date_array = %{ $self->{DATE} }; + my $date_str = $date_array{$revision}; + + return wantarray ? localtime($date_str) : $date_str; +} + +#------------------------------------------------------------------ +# revisions +#------------------------------------------------------------------ +sub revisions { + my $self = shift; + + if (not @{ $self->{REVISIONS} }) { + _parse_rcs_header($self); + } + + # dereference revisions list + my @revisions = @{ $self->{REVISIONS} }; + + @revisions; +} + +#------------------------------------------------------------------ +# rlog +# Execute RCS 'rlog' program. +# Make archive filename same as working filename unless +# specifically set. +#------------------------------------------------------------------ +sub rlog { + my $self = shift; + my @param = @_; + + my $rlogprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rlog' . $Exe_Ext; + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $arcfile = $self->{ARCFILE} || $self->{FILE}; + + # un-taint parameter string + my $param_str = join(' ', @param); + $param_str =~ s/([\w-]+)/$1/g; + + my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} }; + croak "rlog program $rlogprog not found" unless -e $rlogprog; + croak "rlog program $rlogprog not executable" unless -x $rlogprog; + open(RLOG, "$rlogprog $param_str $archive_file |"); + + my @logoutput = <RLOG>; + close RLOG; + croak "$rlogprog failed" if $?; + @logoutput; +} + +#------------------------------------------------------------------ +# state +# If revision is not provided, default to 'head' revision +#------------------------------------------------------------------ +sub state { + my $self = shift; + + if (not defined $self->{STATE}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference author hash + my %state_array = %{ $self->{STATE} }; + + return $state_array{$revision}; +} + +#------------------------------------------------------------------ +# symbol +# If revision is not provided, default to 'head' revision +#------------------------------------------------------------------ +sub symbol { + my $self = shift; + + if (not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + my $revision = shift || $self->{HEAD}; + + # dereference symbols hash + my %sym_array = %{ $self->{SYMBOLS} }; + + return '' if not defined $sym_array{$revision}; + + my @symbols = @{ $sym_array{$revision} }; + + # return only first array element if user wants scalar + return wantarray ? @symbols : $symbols[0]; +} + +#------------------------------------------------------------------ +# symbols +# Returns hash of all revisions keyed on symbol defined against file. +#------------------------------------------------------------------ +sub symbols { + my $self = shift; + + if(not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + + my %symbols; + + # loop through each revision + my $rev; + foreach $rev (@{ $self->{REVISIONS} }) { + my $sym; + foreach $sym (@{ $self->{SYMBOLS}->{$rev} }) { + $symbols{$sym} = $rev; + } + } + return %symbols; +} + +#------------------------------------------------------------------ +# symrev +# Returns the revision against which a specified symbol was +# defined. If the symbol was not defined against any version +# of this file, 0 is returned. +#------------------------------------------------------------------ +sub symrev { + my $self = shift; + my $sym = shift; + if(! defined $sym) { + croak "You must supply a symbol to symrev"; + } + + if (not defined $self->{SYMBOLS}) { + _parse_rcs_header($self); + } + + my $ret_rev = 0; + my %symbols; + + # loop through each revision + my $rev; + REV_LOOP: + foreach $rev (@{ $self->{REVISIONS} }) { + # loop through each symbol defined against + # this revision + my $s; + foreach $s (@{ $self->{SYMBOLS}->{$rev} }) { + + # store each revision matching the pattern + if (wantarray) { + $symbols{$s} = $rev if $s =~ /$sym/; + } + + # if it's the one we're looking for, we can + # quit as we've found the revision we want + else { + if($s eq $sym) { + $ret_rev = $rev; + last REV_LOOP; + } + } + } + } + + return wantarray ? %symbols : $ret_rev; +} + +#------------------------------------------------------------------ +# workdir +# Location of working directory. +#------------------------------------------------------------------ +sub workdir { + my $self = shift; + + # called as object method + if (ref $self) { + if (@_) { ${ $self->{"_WORKDIR"} } = shift } + return ${ $self->{"_WORKDIR"} }; + } + + # called as class method + else { + if (@_) { $Work_Dir = shift } + return $Work_Dir; + } +} + +#------------------------------------------------------------------ +# _parse_rcs_body +# Private function +#------------------------------------------------------------------ +sub _parse_rcs_body { + + my $self = shift; + local $_; + + my %comments; + + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $file = $self->{FILE}; + my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} }; + + # parse RCS archive file + open RCS_FILE, $rcs_file or croak "Unable to open $rcs_file"; + + # skip header info and get description + DESC: while (<RCS_FILE>) { + if (/^desc$/) { + $comments{0} = ''; + $_ = <RCS_FILE>; # read first line + s/^\@//; # remove leading '@' + while (1) { + last DESC if /^\@$/; + s/\@\@/\@/g; # RCS replaces single '@' with '@@' + $comments{0} .= $_; + $_ = <RCS_FILE>; + } + } + } + + # parse revision comments + my $revision; + REVISION: while (<RCS_FILE>) { + if (/^[\d\.]+$/) { + chomp($revision = $_); + $_ = <RCS_FILE>; + if (/^log$/) { + $comments{$revision} = ''; + $_ = <RCS_FILE>; # read first line + s/^\@//; # remove leading '@' + while (1) { + next REVISION if /^\@$/; + s/\@\@/\@/g; # RCS replaces single '@' with '@@' + $comments{$revision} .= $_; + $_ = <RCS_FILE>; + } + } + } + } + + # loop through 'text' section to avoid capturing bogus info + continue { + if (/^text$/) { # 'text' tag should always be there, but check anyway + while (<RCS_FILE>) { + s/\@\@//g; # RCS replaces single '@' with '@@' + last if /\@$/ + } + } + } + + close RCS_FILE; + $self->{COMMENTS} = \%comments; +} + +#------------------------------------------------------------------ +# _parse_rcs_header +# Private function +# Directly parse the RCS archive file. +#------------------------------------------------------------------ +sub _parse_rcs_header { + + my $self = shift; + local $_; + + my ($head, $lock); + my (@access_list, @revisions); + my (%author, %date, %state, %symbols); + + my $rcsdir = ${ $self->{"_RCSDIR"} }; + my $file = $self->{FILE}; + my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} }; + + # parse RCS archive file + open RCS_FILE, $rcs_file + or croak "Unable to open $rcs_file"; + while (<RCS_FILE>) { + next if /^\s*$/; # skip blank lines + last if /^desc$/; # end of header info + + # get head revision + if (/^head\s/) { + ($head) = /^head\s+(.*?);$/; + next; + } + + # get access list + if (/^access$/) { + while (<RCS_FILE>) { + chomp; + s/\s//g; # remove all whitespace + push @access_list, (split(/;/))[0]; + last if /;$/; + } + next; + } + + # get locker + # get symbols + if (/^symbols$/) { + while (<RCS_FILE>) { + chomp; + s/\s//g; # remove all whitespace + my ($sym, $rev) = split(/:/); + $rev =~ s/;$//; + push @{ $symbols{$rev} }, $sym; + last if /;$/; + } + next; + } + + # get locker + if (/^locks/) { + + # file not locked + if (/strict/) { + $lock = ''; + next; + } + + # get user who has file locked + my $next_line = <RCS_FILE>; # read next line + ($lock) = $next_line =~ m/^\s*(\w+):/; + next; + } + + # get all revisions + if (/^\d+\.\d+/) { + chomp; + push @revisions, $_; + + # get author, state and date of each revision + my $next_line = <RCS_FILE>; + chop(my $author = (split(/\s+/, $next_line))[3]); + chop(my $state = (split(/\s+/, $next_line))[5]); + chop(my $date = (split(/\s+/, $next_line))[1]); + + # store date as date number + my ($year, $mon, $mday, $hour, $min, $sec) = split(/\./, $date); + $mon--; # convert to 0-11 range + my @date = ($sec,$min,$hour,$mday,$mon,$year); + + # store value in hash using revision as key + $author{$_} = $author; + $state{$_} = $state; + $date{$_} = timegm(@date); + } + } + close RCS_FILE; + + $self->{HEAD} = $head; + $self->{LOCK} = $lock; + $self->{ACCESS} = \@access_list; + $self->{REVISIONS} = \@revisions; + $self->{AUTHOR} = \%author; + $self->{DATE} = \%date; + $self->{STATE} = \%state; + $self->{SYMBOLS} = \%symbols; +} + +1; + +__END__ + +=head1 NAME + +Rcs - Perl Object Class for Revision Control System (RCS). + +=head1 SYNOPSIS + + use Rcs; + +=head1 DESCRIPTION + +This Perl module provides an object oriented interface to access +B<Revision Control System (RCS)> utilities. RCS must be installed on +the system prior to using this module. This module should simplify +the creation of an RCS front-end. + +=head2 OBJECT CONSTRUCTOR + +The B<new> method may be used as either a class method or an object +method to create a new object. + + # called as class method + $obj = Rcs->new; + + # called as object method + $newobj = $obj->new; + +=head2 CLASS METHODS + +Besides the object constructor, there are three class methods provided +which effect any newly created objects. + +The B<arcext> method sets the RCS archive extension, which is ',v' by +default. + + # set/unset RCS archive extension + Rcs->arcext(''); # set no archive extension + Rcs->arcext(',v'); # set archive extension to ',v' + $arc_ext = Rcs->arcext(); # get current archive extension + +The B<bindir> method sets the directory path where the RCS executables +(i.e. rcs, ci, co) are located. The default location is '/usr/local/bin'. + + # set RCS bin directory + Rcs->bindir('/usr/bin'); + + # access RCS bin directory + $bin_dir = Rcs->bindir; + +The B<quiet> method sets/unsets the quiet mode for the RCS executables. +Quiet mode is set by default. + + # set/unset RCS quiet mode + Rcs->quiet(0); # unset quiet mode + Rcs->quiet(1); # set quiet mode + + # access RCS quiet mode + $quiet_mode = Rcs->quiet; + +These methods may also be called as object methods. + + $obj->arcext(''); + $obj->bindir('/usr/bin'); + $obj->quiet(0); + +=head2 OBJECT ATTRIBUTE METHODS + +These methods set the attributes of the RCS object. + +The B<file> method is used to set the name of the RCS working file. The +filename must be set before invoking any access of modifier methods on the +object. + + $obj->file('mr_anderson.pl'); + +The B<arcfile> method is used to set the name of the RCS archive file. +Using this method is optional, as the other methods will assume the archive +filename is the same as the working file unless specified otherwise. The +RCS archive extension (default ',v') is automatically added to the filename. + + $obj->arcfile('principle_mcvicker.pl'); + +The B<workdir> methods set the path of the RCS working directory. If not +specified, default path is '.' (current working directory). + + $obj->workdir('/usr/local/source'); + +The B<rcsdir> methods set the path of the RCS archive directory. If not +specified, default path is './RCS'. + + $obj->rcsdir('/usr/local/archive'); + +=head2 RCS PARSE METHODS + +This class provides methods to directly parse the RCS archive file. + +The B<access> method returns a list of all user on the access list. + + @access_list = $obj->access; + +The B<author> method returns the author of the revision. The head revision +is used if no revision argument is passed to method. + + # returns the author of revision '1.3' + $author = $obj->author('1.3'); + + # returns the authos of the head revision + $author = $obj->author; + +The B<head> method returns the head revision. + + $head = $obj->head; + +The B<lock> method returns the locker of the revision. The method returns +null if the revision is unlocked. The head revision is used if no revision +argument is passed to method. + + # returns locker of revision '1.3' + $locker = $obj->lock('1.3'); + + # returns locker of head revision + $locker = $obj->lock; + +The B<revisions> method returns a list of all revisions of archive file. + + @revisions = $obj->revisions; + +The B<state> method returns the state of the revision. The head revision +is used if no revision argument is passed to method. + + # returns state of revision '1.3' + $state = $obj->state('1.3'); + + # returns state of head revision + $state = $obj->state; + +The B<symbol> method returns the symbol(s) associated with a revision. +If called in list context, method returns all symbols associated with +revision. If called in scalar context, method returns last symbol +assciated with a revision. The head revision is used if no revision argument +is passed to method. + + # list context, returns all symbols associated with revision 1.3 + @symbols = $obj->symbol('1.3'); + + # list context, returns all symbols associated with head revision + @symbols = $obj->symbol; + + # scalar context, returns last symbol associated with revision 1.3 + $symbol = $obj->symbol('1.3'); + + # scalar context, returns last symbol associated with head revision + $symbol = $obj->symbol; + +The B<symbols> method returns a hash, keyed by symbol, of all of the revisions +associated with the file. + + %symbols = $obj->symbols; + foreach $sym (keys %symbols) { + $rev = $symbols{$sym}; + } + +The B<revdate> method returns the date of a revision. The returned date format +is the same as the localtime format. When called as a scalar, it returns the +system date number. If called is list context, the list +($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned. + + # scalar mode + $scalar_date = $obj->revdate; + print "Scalar date number = $scalar_date\n"; + $date_str = localtime($scalar_date); + print "Scalar date string = $date_str\n"; + + # list mode + @list_date = $obj->revdate; + print "List date = @list_date\n"; + +The B<dates> method returns a hash of revision dates, keyed on revision. The +hash values are system date numbers. When called in scalar mode, the method +returns the most recent revision date. + + # list mode + %DatesHash = obj->dates; + @dates_list = sort {$b<=>$a} values %DatesHash; + $MostRecent = $dates_list[0]; + + # scalar mode + $most_recent = $obj->dates; + print "Most recent date = $most_recent\n"; + $most_recent_str = localtime($most_recent); + print "Most recent date string = $most_recent_str\n"; + +The B<symrev> method returns the revision against which a specified symbol was +defined. If the symbol was not defined against any version of this file, 0 is +returned. + + # gets revision that has 'MY_SYMBOL' defined against it + $rev = $obj->symrev('MY_SYMBOL'); + +The B<daterev> method returns revisions which were created before a specified +date. Method may take one or six arguments. If one arguments is passed, then +the argument is a date number. If six arguments are passed, then they represent +a date string. + + # one argument, date number + # gets revisions created before Sun Sep 6 22:23:47 1998 + @revs = $obj->daterev(841436420); + + # six argument + # gets revisions created before 25th June 1998 16:45:30 + @revs = $obj->daterev(1998, 6, 25, 16, 45, 30); + +The B<comments> method returns a hash of revision comments, keyed on revision. +A key value of 0 returns the description. + + %comments = $obj->comments; + $description = $comments{0}; + $comment_1_3 = $comments{'1.3'}; + +=head2 RCS SYSTEM METHODS + +These methods invoke the RCS system utilities. + +The B<ci> method calls the RCS ci program. + + # check in, and then check out in unlocked state + $obj->ci('-u'); + +The B<co> method calls the RCS co program. + + # check out in locked state + $obj->co('-l'); + +The B<rcs> method calls the RCS rcs program. + + # lock file + $obj->rcs('-l'); + +The B<rcsdiff> method calls the RCS rcsdiff program. When called in +list context, this method returns the outpout of the rcsdiff program. +When called in scalar context, this method returns the return status of +the rcsdiff program. The return status is 0 for the same, 1 for some +differences, and 2 for error condition. + +When called without parameters, rcsdiff does a diff between the current +working file, and the last revision checked in. + + # call in list context + @diff_output = $obj->rcsdiff; + + # call in scalar context + $changed = $obj->rcsdiff; + if ($changed) { + print "Working file has changed\n"; + } + +Call rcsdiff with parameters to do a diff between any two revisions. + + @diff_output = $obj->rcsdiff('-r1.2', '-r1.1'); + +The B<rlog> method calls the RCS rlog program. This method returns the +output of the rlog program. + + # get complete log output + @rlog_complete = $obj->rlog; + + # called with '-h' switch outputs only header information + @rlog_header = $obj->rlog('-h'); + print @rlog_header; + +The B<rcsclean> method calls the RCS rcsclean program. + + # remove working file + $obj->rcsclean; + + +=head1 EXAMPLES + +=head2 CREATE ACCESS LIST + +Using method B<rcs> with the B<-a> switch allows you to add users to +the access list of an RCS archive file. + + use Rcs; + $obj = Rcs->new; + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + +Methos B<rcs> invokes the RCS utility rcs with the same parameters. + + @users = qw(beavis butthead); + $obj->rcs("-a@users"); + +Calling method B<access> returns list of users on access list. + + $filename = $obj->file; + @access_list = $obj->access; + print "Users @access_list are on the access list of $filename\n"; + + +=head2 PARSE RCS ARCHIVE FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of the +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + $head_rev = $obj->head; + $locker = $obj->lock; + $author = $obj->author; + @access = $obj->access; + @revisions = $obj->revisions; + + $filename = $obj->file; + + if ($locker) { + print "Head revision $head_rev is locked by $locker\n"; + } + else { + print "Head revision $head_rev is unlocked\n"; + } + + if (@access) { + print "\nThe following users are on the access list of file $filename\n"; + map { print "User: $_\n"} @access; + } + + print "\nList of all revisions of $filename\n"; + foreach $rev (@revisions) { + print "Revision: $rev\n"; + } + +=head2 CHECK-IN FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->file('cornholio.pl'); + + # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); + + # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); + +Check in file using B<-u> switch. This will check in the file, and will then +check out the file in an unlocked state. The B<-m> switch is used to set the +revision comment. + +Command: + + $obj->ci('-u', '-mRevision Comment'); + +is equivalent to commands: + + $obj->ci('-mRevision Comment'); + $obj->co; + +=head2 CHECK-OUT FILE + +Set class variables and create 'RCS' object. +Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The +default is '/usr/local/bin'. This sets the bin directory for all objects. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + +Set information regarding RCS object. This information includes name of +working file, directory of working file ('.' by default), and RCS archive +directory ('./RCS' by default). + + $obj->file('cornholio.pl'); + + # Set RCS archive directory, is './RCS' by default + $obj->rcsdir("./project_tree/archive"); + + # Set working directory, is '.' by default + $obj->workdir("./project_tree/src"); + +Check out file read-only: + + $obj->co; + +or check out and lock file: + + $obj->co('-l'); + +=head2 RCSDIFF + +Method B<rcsdiff> does an diff between revisions. + + $obj = Rcs->new; + $obj->bindir('/usr/bin'); + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + print "Diff of current working file\n"; + if ($obj->rcsdiff) { # scalar context + print $obj->rcsdiff; # list context + } + else { + print "Versions are Equal\n"; + } + + print "\n\nDiff of revisions 1.2 and 1.1\n"; + print $obj->rcsdiff('-r1.2', '-r1.1'); + +=head2 RCSCLEAN + +Method B<rcsclean> will remove an unlocked working file. + + use Rcs; + Rcs->bindir('/usr/bin'); + Rcs->quiet(0); # turn off quiet mode + $obj = Rcs->new; + + $obj->rcsdir("./project_tree/archive"); + $obj->workdir("./project_tree/src"); + $obj->file("cornholio.pl"); + + print "Quiet mode NOT set\n" unless Rcs->quiet; + + $obj->rcsclean; + +=head1 AUTHOR + +Craig Freter, E<lt>F<craig@freter.com>E<gt> + +=head1 CONTRIBUTORS + +David Green, E<lt>F<greendjf@cvhp152.gpt.co.uk>E<gt> + +Jamie O'Shaughnessy, E<lt>F<jamie@thanatar.demon.co.uk>E<gt> + +=head1 COPYRIGHT + +Copyright (C) 1997,1998 Craig Freter. All rights reserved. +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut + diff --git a/cpan/lib/VCS/CVS.pm b/cpan/lib/VCS/CVS.pm new file mode 100644 index 00000000..3204e1b3 --- /dev/null +++ b/cpan/lib/VCS/CVS.pm @@ -0,0 +1,1474 @@ +package VCS::CVS; + +# Name: +# VCS::CVS. +# +# Documentation: +# POD-style documentation is at the end. Extract it with pod2html. +# +# Tabs: +# 4 spaces || die. +# +# -------------------------------------------------------------------------- + +use strict; +no strict 'refs'; + +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); + +use Carp; +use Cwd; +use File::Find; +use File::Path; + +require Exporter; + +@ISA = qw(Exporter); + +# Items to export into callers namespace by default. Note: do not export +# names by default without a very good reason. Use EXPORT_OK instead. +# Do not simply export all your public functions/methods/constants. + +@EXPORT = qw(); + +@EXPORT_OK = qw(); + +$VERSION = '2.00'; + +# Preloaded methods go here. +# -------------------------------------------------------------------------- +# Add an existing directory to the project. +# $dir can be a full path, or relative to the CWD. + +sub addDirectory +{ + my($self, $dir, $subDir, $message) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): \nFailure: $!"); + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + # $subDir Add this directory. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'add'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + push(@args, $subDir); + + $self -> runOrCroak(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of addDirectory. + +# -------------------------------------------------------------------------- +# Add an existing file to the project. +# $dir can be a full path, or relative to the CWD. + +sub addFile +{ + my($self, $dir, $file, $message) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): \nFailure: $!"); + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + # $file Add this file. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'add'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + push(@args, $file); + + $self -> runOrCroak(@args); + + $self -> commit($message); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of addFile. + +# -------------------------------------------------------------------------- +# Prepare & perform 'cvs checkout'. +# You call checkOut, and it calls _checkOutDontCallMe. +# $readOnly Interpretation +# 0 Check out files as read-write +# 1 Check out files as read-only +# $tag Interpretation +# Null Do not call upToDate; ie check out repository as is +# ! Null Call upToDate; Croak if repository is not up-to-date +# If you called new with $raw == 1, your tag is passed as is to CVS. +# If you called new with $raw == 0, your tag is assumed to be of the +# form release_1.23, and is converted to CVS's form release_1_23. +# $dir can be a full path, or relative to the CWD. + +sub checkOut +{ + my($self, $readOnly, $tag, $dir) = @_; + + $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'}); + + $self -> _validateObject($self -> {'project'}, 'modules', 0); + $self -> _validateObject($tag, 'val-tags', 0); + + croak("Failure: Move directory $dir out of the way") if (-d $dir); + + # Ensure the repository is up-to-date. + croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'") + if ($tag && (! $self -> upToDate() ) ); + + # Zap previous copy of work directory. + rmtree($dir, $self -> {'verbose'}); + + # Checkout a current copy of the project. + $self -> _checkOutDontCallMe($readOnly, $tag, $dir); + +} # End of checkOut. + +# -------------------------------------------------------------------------- +# Commit changes. +# Called as appropriate by addFile, removeFile and removeDirectory, +# so you don't need to call it. + +sub commit +{ + my($self, $message) = @_; + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'commit'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + $self -> runOrCroak(@args); + +} # End of commit. + +# -------------------------------------------------------------------------- +# Create a repository, using the current $CVSROOT. + +sub createRepository +{ + my($self) = @_; + + croak("Failure: Move directory $ENV{'CVSROOT'} out of the way") if (-d $ENV{'CVSROOT'}); + + # Create the repository and its files. + $self -> _mkpathOrCroak($ENV{'CVSROOT'}); + $self -> _mkpathOrCroak("$ENV{'CVSROOT'}/CVSROOT"); + + # Create the modules file. + my(@args) = (); + push(@args, "CVSROOT\t\tCVSROOT"); + push(@args, "modules\t\tCVSROOT\tmodules"); + push(@args, "$self->{'project'}\t\t$self->{'project'}"); + + my($file) = "$ENV{'CVSROOT'}/CVSROOT/modules"; + open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!"); + print OUT join("\n", @args), "\n"; + close(OUT); + + $file = "$ENV{'CVSROOT'}/CVSROOT/val-tags"; + open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!"); + # Write nothing. + close(OUT); + + if ($self -> {'history'}) + { + $file = "$ENV{'CVSROOT'}/CVSROOT/history"; + open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!"); + # Write nothing. + close(OUT); + } + +} # End of createRepository. + +# -------------------------------------------------------------------------- +# Return a reference to a list of tags. +# See also: the $raw option to new(). + +sub getTags +{ + my($self) = @_; + + my($line) = []; + + if (-e "$ENV{'CVSROOT'}/CVSROOT/val-tags") + { + $line = $self -> _readFile("$ENV{'CVSROOT'}/CVSROOT/val-tags"); + + for (@$line) + { + $_ = (split)[0]; + + # Convert tag_1_23 into tag_1.23, if requested. + s/([-a-zA-Z]+_\d\d?)_(\d\d)/$1\.$2/ if (! $self -> {'raw'}); + } + + } + + $line; + +} # End of getTags. + +# -------------------------------------------------------------------------- +# Run cvs history [-options]. +# Return a reference to a list of lines. +# +# The default option is -c. + +sub history +{ + my($self, $optionRef) = @_; + + # Preserve the caller's current working directory. + # cvs status only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir("$ENV{'HOME'}/$self->{'project'}") || + croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!"); + + # CVS history options: + # -c Report commits, ie -xARM. + + if (ref($optionRef) ne 'HASH') + { + $optionRef = {'-c' => ''}; + } + + my(@args) = ('cvs'); + push(@args, 'history'); + push(@args, join(' ', %$optionRef) ); + @args = `@args`; + chomp(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + \@args; + +} # End of history. + +# -------------------------------------------------------------------------- +# These are the options in the anonymous hash of parameters you pass in to 'new'. +# +# 'project' +# 'killerApp' The name of the project. No default +# +# 'history' +# 0 Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default +# 1 Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff +# +# 'permissions' +# 0775 Unix-specific. Default. Do not use '0775' +# +# 'raw' +# 0 Convert tags from CVS format to real format. Eg: release_1.23. Default +# 1 Set/Get tags in raw CVS format. Eg: release_1_23 +# +# 'verbose' +# 0 Run quietly +# 1 Report progress. Default + +sub new +{ + my($class, $optionRef) = @_; + $class = ref($class) || $class; + my($self) = (ref($optionRef) eq 'HASH') ? $optionRef : {}; + + my(%default) = + ( + 'history' => 0, + 'permissions' => 0775, # But not '0775'! + 'project' => '', + 'raw' => 0, + 'verbose' => 1, + ); + + my($option); + + for $option (keys(%default) ) + { + $self -> {$option} = $default{$option} if (! defined($self -> {$option}) ); + } + + $ENV{'HOME'} = '' if (! defined($ENV{'HOME'}) ); + $ENV{'CVSROOT'} = '' if (! defined($ENV{'CVSROOT'}) ); + + croak("Failure: No project name specified") if (! $self -> {'project'}); + croak("Failure: Env. var HOME not set") if (! $ENV{'HOME'}); + croak("Failure: Env. var CVSROOT not set") if (! $ENV{'CVSROOT'}); + + return bless $self, $class; + +} # End of new. + +# -------------------------------------------------------------------------- +# Import an existing directory structure. But, (sub) import is a reserved word. +# Use this to populate a repository for the first time. +# The value used for $vendorTag is not important; CVS discards it. +# The value used to $releaseTag is important; CVS discards it (why?) but I +# force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you +# should supply a meaningful value. Thus 'release_0_00' is strongly, repeat +# strongly, recommended. +# If you called new with $raw == 1, $releaseTag is passed as is to CVS. +# If you called new with $raw == 0, $releaseTag is assumed to be of the +# form release_1.23, and is converted to CVS's form release_1_23. + +# $sourceDir can be a full path, or relative to the CWD. + +sub populate +{ + my($self, $sourceDir, $vendorTag, $releaseTag, $message) = @_; + + $vendorTag = 'vendorTag' if ( ($#_ < 2) || (length($_[2]) == 0) ); + $releaseTag = 'release_0_00' if ( ($#_ < 3) || (length($_[3]) == 0) ); + $message = 'Initial version' if ($#_ < 4); + + $releaseTag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'}); + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($sourceDir) || croak("Can't chdir($sourceDir): \nFailure: $!"); + + # CVS options: + # -Q Really quiet. + # -m message Use this log message. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'import'); + + if ($message) + { + $message = '"' . $message . '"' if ($message !~ /^".*"$/); + push(@args, '-m', $message); + } + + push(@args, $self -> {'project'}, $vendorTag, $releaseTag); + + $self -> runOrCroak(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + # Compensate for yet another CVS bug. + $self -> _fixTag($releaseTag); + +} # End of populate. + +# -------------------------------------------------------------------------- +# Remove a directory from the project. +# This deletes the directory (and all its files) from your working copy +# of the repository, as well as deleting them from the repository. +# Warning: $dir will have $CVSROOT and $HOME prepended by this code. +# Ie: $dir starts from - but excludes - your home directory +# (assuming, of course, you've checked out into your home directory...). +# You can't remove the current directory, or a parent thereof. + +sub removeDirectory +{ + my($self, $dir) = @_; + + my($cvsDir) = "$ENV{'CVSROOT'}/$dir/"; + my($workDir) = "$ENV{'HOME'}/$dir/"; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + + # Move into the work directory. + chdir($workDir) || croak("Can't chdir($workDir): \nFailure: $!"); + my($thisCwd) = cwd(); + + # Sanity check. + croak("Failure: You can't remove the current directory, or a parent") if ($cwd =~ /^$thisCwd/); + + # Ensure the repository is up-to-date. + croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'") + if (! $self -> upToDate() ); + + # Read the CVS entries. + my($cvsEntries) = 'CVS/Entries'; + my($entry) = $self -> _readFile($cvsEntries); + + # Remove each file, using CVS. + for (@$entry) + { + next if (/^D/); + + my($file); + + $file = $1 if (/^\/(.+?)\//); + + $self -> removeFile($workDir, $file, 'Whole directory removed'); + } + + $self -> commit('Whole directory removed'); + + # Move up, and remove the directory. + chdir('..') || croak("Can't chdir('..'): \nFailure: $!"); + my($directory) = $workDir; + my($index) = rindex($directory, '/', (length($directory) - 2) ); + substr($directory, 0, ($index + 1) ) = ''; + rmtree($directory, $self -> {'verbose'}); + + # Edit the CVS entries file to remove the dir. + if (-f $cvsEntries) + { + $entry = $self -> _readFile($cvsEntries); + @$entry = grep(! /^D\/$directory\//, @$entry); + open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!"); + print OUT join("\n", @$entry), "\n"; + close(OUT); + } + + # Remove the directory from CVS. + rmtree($cvsDir, $self -> {'verbose'}); + + # Remove the directory from the modules list. + if ($dir !~ /\//) + { + $cvsEntries = "$ENV{'CVSROOT'}/CVSROOT/modules"; + $entry = $self -> _readFile($cvsEntries); + + my($i); + + for ($i = 0; $i <= $#{$entry}; $i++) + { + my(@field) = split(/\s+/, $$entry[$i]); + splice(@$entry, $i, 1) if ($field[1] =~ /^$dir$/); + } + + open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!"); + print OUT join("\n", @$entry), "\n"; + close(OUT); + } + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of removeDirectory. + +# -------------------------------------------------------------------------- +# Remove a file from the project. +# This deletes the file from your working copy of the repository, +# as well as deleting it from the repository. +# $dir can be a full path, or relative to the CWD. +# $file is relative to $dir. + +sub removeFile +{ + my($self, $dir, $file, $message) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): \nFailure: $!"); + + unlink($file) || croak("Can't unlink($file): $!"); + + # CVS options: + # -Q Really quiet. + # -f Remove the file first. + # -l Do not recurse. + # $file Checkout this module. + + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'remove', '-f', '-l', $file); + + $self -> runOrCroak(@args); + + $self -> commit($message); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of removeFile. + +# -------------------------------------------------------------------------- +# The standard way to run a system command and report on the result. + +sub runOrCroak +{ + my($self, @args) = @_; + + my($result) = 0xffff & system(@args); + + print "Command: @args\n"; + + if ($result == 0) + { + print 'Success. '; + } + elsif ($result == 0xff00) + { + print "Failure: $!. "; + } + elsif ($result > 0x80) + { + $result >>= 8; + print "Exit status: $result. "; + } + else + { + if ($result & 0x80) + { + $result &= ~0x80; + print 'Coredump from '; + } + + print "Signal $result. "; + } + + printf("Result: %#04x\n", $result); + + croak("Failure: Can't run '@args'") if ($result); + +} # End of runOrCroak. + +# -------------------------------------------------------------------------- +# Tag the repository. +# You call setTag, and it calls _setTag. +# If you called new with $raw == 1, your tag is passed as is to CVS. +# If you called new with $raw == 0, your tag is assumed to be of the +# form release_1.23, and is converted to CVS's form release_1_23. + +sub setTag +{ + my($self, $tag) = @_; + + $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'}); + + $self -> _validateObject($self -> {'project'}, 'modules', 0); + $self -> _validateObject($tag, 'val-tags', 1); + + croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'") + if ($self -> upToDate() == 0); + + $self -> _setTag($tag); + +} # End of setTag. + +# -------------------------------------------------------------------------- +# Run cvs status. +# Return a reference to a list of lines. +# Only called by upToDate(), but you may call it. + +sub status +{ + my($self) = @_; + + # Preserve the caller's current working directory. + # cvs status only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir("$ENV{'HOME'}/$self->{'project'}") || + croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!"); + + # CVS options: + # -Q Really quiet. + + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'status'); + @args = `@args`; + chomp(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + \@args; + +} # End of status. + +# -------------------------------------------------------------------------- +# Delete all CVS directories and files from a copy of the repository. + +sub stripCVSDirs +{ + my($self, $dir) = @_; + + # Preserve the caller's current working directory. + my($cwd) = cwd(); + chdir($dir) || croak("Can't chdir($dir): $!"); + + my(%dirStack); + + find + ( + sub + { + $dirStack{$File::Find::dir} = 1 if ($File::Find::dir =~ /\/CVS$/); + }, + cwd() + ); + + for (keys(%dirStack) ) + { + rmtree($_, $self -> {'verbose'}); + } + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + +} # End of stripCVSDirs. + +# -------------------------------------------------------------------------- +# Run cvs -q [-n] update. +# Return a reference to a list of lines. +# Each line will start with one of [UARMC?], as per the CVS docs. +# +# Parameters Interpretation +# $n 0 -> Do not add -n to the cvs update command +# 1 -> Add -n to the command + +sub update +{ + my($self, $n) = @_; + + $n = 0 if (! defined($n) ); + + # Preserve the caller's current working directory. + # cvs status only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir("$ENV{'HOME'}/$self->{'project'}") || + croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!"); + + # CVS options: + # -q Quiet + # -n Do not change any files + + my(@args) = ('cvs'); + push(@args, '-q') if (! $self -> {'verbose'}); + push(@args, '-n') if ($n); + push(@args, 'update'); + @args = `@args`; + chomp(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + \@args; + +} # End of update. + +# -------------------------------------------------------------------------- +# Return Interpretation +# 0 Repository not up-to-date. +# 1 Up-to-date. + +sub upToDate +{ + my($self) = @_; + + # Get the status of the repository. + my($status) = $self -> status(); + @$status = grep(/Status/ && ! /Up-to-date/, @$status); + my($result) = 1; # Up-to-date. + $result = 0 if ($#{$status} >= 0); # Not, because log contains something. + + $result; + +} # End of upToDate. + +# -------------------------------------------------------------------------- +# Checkout a current copy of the project. +# You call checkOut, and it calls this. + +sub _checkOutDontCallMe +{ + my($self, $readOnly, $tag, $dir) = @_; + + # CVS options: + # -Q Really quiet. + # -r Read-only. Make the new working files read-only. + # -d$dir Use $dir, not $project, as the directory name. + # -r <tag> Check out files tagged with <tag>. Optional. + # + # $project Checkout this module. + + # CVS bug. Remove trailing '/', if any. + $dir = $1 if ($dir =~ /^(.+)\/$/); + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, '-r') if ($readOnly); + push(@args, 'checkout', '-A', '-P', "-d$dir"); + push(@args, '-r', $tag) if ($tag); + push(@args, $self -> {'project'}); + + $self -> runOrCroak(@args); + +} # End of _checkOutDontCallMe. + +# -------------------------------------------------------------------------- +# Fix a tag CVS failed to add. +# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23. + +sub _fixTag +{ + my($self, $tag) = @_; + + my($file) = "$ENV{'CVSROOT'}/CVSROOT/val-tags"; + + open(INX, $file) || croak("Can't open($file): \nFailure: $!"); + + my($found) = 0; + + while (<INX>) + { + $found = 1 if (/^$tag/); + } + + close(INX); + + if (! $found) + { + print "Warning: CVS bug. Tag $tag not in file $file\n" if ($self -> {'verbose'}); + print "Fixing... " if ($self -> {'verbose'}); + + open(OUT, ">> $file") || croak("Can't open(>>$file): \nFailure: $!"); + print OUT "$tag y\n"; + close(OUT); + + print "Success\n" if ($self -> {'verbose'}); + } + +} # End of _fixTag. + +# -------------------------------------------------------------------------- + +sub _mkpathOrCroak +{ + my($self, $dir) = @_; + + my($result) = mkpath($dir, $self -> {'verbose'}, $self -> {'permissions'}); + + croak("Can't mkpath($dir, $self->{'verbose'}, $self->{'permissions'}): \nFailure: $!") + if ( (! $result) && ($! !~ /No such file/) ); + +} # End of _mkpathOrCroak. + +# -------------------------------------------------------------------------- +# Return a reference to a list of lines. + +sub _readFile +{ + my($self, $file) = @_; + + open(INX, $file) || croak("Can't open($file): $!"); + my(@line) = <INX>; + close(INX); + chomp(@line); + + \@line; + +} # end of _readFile. + +# -------------------------------------------------------------------------- +# Tag the current version of the project. +# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23. +# You call setTag and it calls this. + +sub _setTag +{ + my($self, $tag) = @_; + + # Preserve the caller's current working directory. + # cvs tag only works on the whole repository when run from your project dir + # (assuming, of course, you've checked out into your home directory...). + my($cwd) = cwd(); + chdir($ENV{'HOME'}) || croak("Can't chdir($ENV{'HOME'}): $!"); + + # CVS options: + # -Q Really quiet. + # -r <tag> Tag files with <tag>. + # $project Tag this module. + + # Warning: Do not try to combine these lines under any circumstances... + # Perl can't handle null list elements in a call to system. + my(@args) = ('cvs'); + push(@args, '-Q') if (! $self -> {'verbose'}); + push(@args, 'tag', $tag, $self -> {'project'}); + + $self -> runOrCroak(@args); + + chdir($cwd) || croak("Can't chdir($cwd): $!"); + + # Compensate for yet another CVS bug. + $self -> _fixTag($tag); + +} # End of _setTag. + +# -------------------------------------------------------------------------- +# Validate an entry in one of the CVS files 'module' or 'val-tags'. +# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23. + +sub _validateObject +{ + my($self, $tag, $file, $mustBeAbsent) = @_; + + $file = "$ENV{'CVSROOT'}/CVSROOT/$file"; + + open(INX, $file) || croak("Can't open($file): \nFailure: $!"); + + my($found) = 0; + + while (<INX>) + { + $found = 1 if (/^$tag/); + } + + close(INX); + + croak("Failure: Tag not found: $tag in file $file") + if ( (! $found) && (! $mustBeAbsent) ); + + croak("Failure: Tag already present: $tag in file $file") + if ($found && $mustBeAbsent); + +} # End of _validateObject. + +# -------------------------------------------------------------------------- + +# Autoload methods go after =cut, and are processed by the autosplit program. + +1; + +__END__ + +=head1 NAME + +C<VCS::CVS> - Provide a simple interface to CVS (the Concurrent Versions System). + +You need to be clear in your mind about the 4 directories involved: + +=over 4 + +=item * + +The directory where your source code resides before you import it into CVS. +It is used only once - during the import phase. Call this $projectSource. + +=item * + +The directory into which you check out a read-write copy of the repository, +in order to edit that copy. Call this $project. You will spend up to 100% of +your time working within this directory structure. + +=item * + +The directory in which the repository resides. This is $CVSROOT. Thus +$projectSource will be imported into $CVSROOT/$project. + +=item * + +The directory into which you get a read-only copy of the repository, in order to, +say, make and ship that copy. Call this $someDir. It must not be $project. + +=back + +Note: You cannot have a directory called CVS in your home directory. That's +just asking for trouble. + +=head1 SYNOPSIS + + #!/usr/gnu/bin/perl -w + + use integer; + use strict; + + use VCS::CVS; + + my($history) = 1; + my($initialMsg) = 'Initial version'; + my($noChange) = 1; + my($nullTag) = ''; + my($permissions) = 0775; # But not '0775'! + my($project) = 'project'; + my($projectSource) = 'projectSource'; + my($raw) = 0; + my($readOnly) = 0; + my($releaseTag) = 'release_0.00'; + my($vendorTag) = 'vendorTag'; + my($verbose) = 1; + + # Note the anonymous hash in the next line, new as of V 1.10. + + my($cvs) = VCS::CVS -> new({ + 'project' => $project, + 'raw' => $raw, + 'verbose' => $verbose, + 'permissions' => $permissions, + 'history' => $history}); + + $cvs -> createRepository(); + $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg); + $cvs -> checkOut($readOnly, $nullTag, $project); + + print join("\n", @{$cvs -> update($noChange)}); + print "\n"; + print join("\n", @{$cvs -> history()}); + + exit(0); + +=head1 DESCRIPTION + +The C<VCS::CVS> module provides an OO interface to CVS. + +VCS - Version Control System - is the prefix given to each Perl module which +deals with some sort of source code control system. + +I have seen CVS corrupt binary files, even when run with CVS's binary option -kb. +So, since CVS doesn't support binary files, neither does VCS::CVS. + +Stop press: CVS V 1.10 (with RCS 5.7) supports binary files. + +Subroutines whose names start with a '_' are not normally called by you. + +There is a test program included, but I have not yet worked out exactly how to +set it up for make test. Stay tuned. + +=head1 INSTALLATION + +You install C<VCS::CVS>, as you would install any perl module library, +by running these commands: + + perl Makefile.PL + make + make test + make install + +If you want to install a private copy of C<VCS::CVS> in your home +directory, then you should try to produce the initial Makefile with +something like this command: + + perl Makefile.PL LIB=~/perl + or + perl Makefile.PL LIB=C:/Perl/Site/Lib + +If, like me, you don't have permission to write man pages into unix system +directories, use: + + make pure_install + +instead of make install. This option is secreted in the middle of p 414 of the +second edition of the dromedary book. + +=head1 WARNING re CVS bugs + +The following are my ideas as to what constitutes a bug in CVS: + +=over 4 + +=item * + +The initial revision tag, supplied when populating the repository with +'cvs import', is not saved into $CVSROOT/CVSROOT/val-tags. + +=item * + +The 'cvs tag' command does not always put the tag into 'val-tags'. + +=item * + +C<'cvs checkout -dNameOfDir'> fails if NameOfDir =~ /\/$/. + +=item * + +C<'cvs checkout -d NameOfDir'> inserts a leading space into the name of +the directory it creates. + +=back + +=head1 WARNING re test environment + +This code has only been tested under Unix. Sorry. + +=head1 WARNING re project names 'v' directory names + +I assume your copy of the repository was checked out into a directory with +the same name as the project, since I do a 'cd $HOME/$project' before running +'cvs status', to see if your copy is up-to-date. This is because some activity is +forbibben unless your copy is up-to-date. Typical cases of this include: + +=over 4 + +=item * + +C<checkOut> + +=item * + +C<removeDirectory> + +=item * + +C<setTag> + +=back + +=head1 WARNING re shell intervention + +Some commands cause the shell to become involved, which, under Unix, will read your +.cshrc or whatever, which in turn may set CVSROOT to something other than what you +set it to before running your script. If this happens, panic... + +Actually, I think I've eliminated such cases. You hope so. + +=head1 WARNING re Perl bug + +As always, be aware that these 2 lines mean the same thing, sometimes: + +=over 4 + +=item * + +$self -> {'thing'} + +=item * + +$self->{'thing'} + +=back + +The problem is the spaces around the ->. Inside double quotes, "...", the +first space stops the dereference taking place. Outside double quotes the +scanner correctly associates the $self token with the {'thing'} token. + +I regard this as a bug. + +=head1 addDirectory($dir, $subDir, $message) + +Add an existing directory to the project. + +$dir can be a full path, or relative to the CWD. + +=head1 addFile($dir, $file, $message) + +Add an existing file to the project. + +$dir can be a full path, or relative to the CWD. + +=head1 checkOut($readOnly, $tag, $dir) + +Prepare & perform 'cvs checkout'. + +You call checkOut, and it calls _checkOutDontCallMe. + +=over 4 + +=item * + +$readOnly == 0 -> Check out files as read-write. + +=item * + +$readOnly == 1 -> Check out files as read-only. + +=back + +=over 4 + +=item * + +$tag is Null -> Do not call upToDate; ie check out repository as is. + +=item * + +$tag is not Null -> Call upToDate; Croak if repository is not up-to-date. + +=back + +The value of $raw used in the call to new influences the handling of $tag: + +=over 4 + +=item * + +$raw == 1 -> Your tag is passed as is to CVS. + +=item * + +$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is +converted to CVS's form release_1_23. + +=back + +$dir can be a full path, or relative to the CWD. + +=head1 commit($message) + +Commit changes. + +Called as appropriate by addFile, removeFile and removeDirectory, +so you don't need to call it. + +=head1 createRepository() + +Create a repository, using the current $CVSROOT. + +This involves creating these files: + +=over 4 + +=item * + +$ENV{'CVSROOT'}/CVSROOT/modules + +=item * + +$ENV{'CVSROOT'}/CVSROOT/val-tags + +=item * + +$ENV{'CVSROOT'}/CVSROOT/history + +=back + +Notes: + +=over 4 + +=item * + +The 'modules' file contains these lines: + + CVSROOT CVSROOT + modules CVSROOT modules + $self -> {'project'} $self -> {'project'} + +where $self -> {'project'} comes from the 'project' parameter to new() + +=item * + +The 'val-tags' file is initially empty + +=item * + +The 'history' file is only created if the 'history' parameter to new() is set. +The file is initially empty + +=back + +=head1 getTags() + +Return a reference to a list of tags. + +See also: the $raw option to new(). + +C<getTags> does not take a project name because tags belong to the repository +as a whole, not to a project. + +=head1 history({}) + +Report details from the history log, $CVSROOT/CVSROOT/history. + +You must have used new({'history' => 1}), or some other mechanism, to create +the history file, before CVS starts logging changes into the history file. + +The anonymous hash takes any parameters 'cvs history' takes, and joins them +with a single space. Eg: + + $cvs -> history(); + + $cvs -> history({'-e' => ''}); + + $cvs -> history({'-xARM' => ''}); + + $cvs -> history({'-u' => $ENV{'LOGNAME'}, '-x' => 'A'}); + +but not + + $cvs -> history({'-xA' => 'M'}); + +because it doesn't work. + +=head1 new({}) + +Create a new object. See the synopsis. + +The anonymous hash takes these parameters, of which 'project' is the +only required one. + +=over 4 + +=item * + +'project' => 'killerApp'. The required name of the project. No default + +=back + +=over 4 + +=item * + +'permissions' => 0775. Unix-specific stuff. Default. Do not use '0775'. + +=back + +=over 4 + +=item * + +'history' => 0. Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default + +=item * + +'history' => 1. Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff + +=back + +=over 4 + +=item * + +'raw' => 0. Convert tags from CVS format to real format. Eg: release_1.23. Default. + +=item * + +'raw' => 1. Return tags in raw CVS format. Eg: release_1_23. + +=back + +=over 4 + +=item * + +'verbose' => 0. Do not report on the progress of mkpath/rmtree + +=item * + +'verbose' => 1. Report on the progress of mkpath/rmtree. Default + +=back + +=head1 populate($sourceDir, $vendorTag, $releaseTag, $message) + +Import an existing directory structure. But, (sub) import is a reserved word. + +Use this to populate a repository for the first time. + +The value used for $vendorTag is not important; CVS discards it. + +The value used to $releaseTag is important; CVS discards it (why?) but I +force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you +should supply a meaningful value. Thus 'release_0_00' is strongly, repeat +strongly, recommended. + +The value of $raw used in the call to new influences the handling of $tag: + +=over 4 + +=item * + +$raw == 1 -> Your tag is passed as is to CVS. + +=item * + +$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is +converted to CVS's form release_1_23. + +=back + +=head1 removeDirectory($dir) + +Remove a directory from the project. + +This deletes the directory (and all its files) from your working copy +of the repository, as well as deleting them from the repository. + +Warning: $dir will have $CVSROOT and $HOME prepended by this code. +Ie: $dir starts from - but excludes - your home directory +(assuming, of course, you've checked out into your home directory...). + +You can't remove the current directory, or a parent. + +=head1 removeFile($dir, $file, $message) + +Remove a file from the project. + +This deletes the file from your working copy of the repository, +as well as deleting it from the repository. + +$dir can be a full path, or relative to the CWD. +$file is relative to $dir. + +=head1 runOrCroak() + +The standard way to run a system command and report on the result. + +=head1 setTag($tag) + +Tag the repository. + +You call setTag, and it calls _setTag. + +The value of $raw used in the call to new influences the handling of $tag: + +=over 4 + +=item * + +$raw == 1 -> Your tag is passed as is to CVS. + +=item * + +$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is +converted to CVS's form release_1_23. + +=back + +=head1 stripCVSDirs($dir) + +Delete all CVS directories and files from a copy of the repository. + +Each user directory contains a CVS sub-directory, which holds 3 files: + +=over 4 + +=item * + +Entries + +=item * + +Repository + +=item * + +Root + +=back + +Zap 'em. + +=head1 status() + +Run cvs status. + +Return a reference to a list of lines. + +Only called by upToDate(), but you may call it. + +=head1 update($noChange) + +Run 'cvs C<-q> [C<-n>] update', returning a reference to a list of lines. +Each line will start with one of [UARMC?], as per the CVS docs. + +$cvs -> update(1) is a good way to get a list of uncommited changes, etc. + +=over 4 + +=item * + +$noChange == 0 -> Do not add C<-n> to the cvs command. Ie update your working copy + +=item * + +$noChange == 1 -> Add C<-n> to the cvs command. Do not change any files + +=back + +=head1 upToDate() + +=over 4 + +=item * + +return == 0 -> Repository not up-to-date. + +=item * + +return == 1 -> Up-to-date. + +=back + +=head1 _checkOutDontCallMe($readOnly, $tag, $dir) + +Checkout a current copy of the project. + +You call checkOut, and it calls this. + +=over 4 + +=item * + +$readOnly == 0 -> Check out files as read-write. + +=item * + +$readOnly == 1 -> Check out files as read-only. + +=back + +=head1 _fixTag($tag) + +Fix a tag which CVS failed to add. + +Warning: $tag must be in CVS format: release_1_23, not release_1.23. + +=head1 _mkpathOrCroak($self, $dir) + +There is no need for you to call this. + +=head1 _readFile($file) + +Return a reference to a list of lines. + +There is no need for you to call this. + +=head1 _setTag($tag) + +Tag the current version of the project. + +Warning: $tag must be in CVS format: release_1_23, not release_1.23. + +You call setTag and it calls this. + +=head1 _validateObject($tag, $file, $mustBeAbsent) + +Validate an entry in one of the CVS files 'module' or 'val-tags'. + +Warning: $tag must be in CVS format: release_1_23, not release_1.23. + +=head1 AUTHOR + +C<VCS::CVS> was written by Ron Savage I<E<lt>rpsavage@ozemail.com.auE<gt>> in 1998. + +=head1 LICENCE + +This program is free software; you can redistribute it and/or modify it under +the same terms as Perl itself. |
