diff options
author | jkh <jkh@FreeBSD.org> | 1993-06-18 05:46:17 +0000 |
---|---|---|
committer | jkh <jkh@FreeBSD.org> | 1993-06-18 05:46:17 +0000 |
commit | 77629e160d558a58cca8b90f395ec5eba88b6bf0 (patch) | |
tree | 3595c61e4f85a1d3c1fb6ea6c1ede9ea8397ced7 /gnu | |
download | FreeBSD-src-77629e160d558a58cca8b90f395ec5eba88b6bf0.zip FreeBSD-src-77629e160d558a58cca8b90f395ec5eba88b6bf0.tar.gz |
Updated CVS
Diffstat (limited to 'gnu')
110 files changed, 45398 insertions, 0 deletions
diff --git a/gnu/usr.bin/cvs/Makefile b/gnu/usr.bin/cvs/Makefile new file mode 100644 index 0000000..d75858b --- /dev/null +++ b/gnu/usr.bin/cvs/Makefile @@ -0,0 +1,3 @@ +SUBDIR = lib cvs mkmodules + +.include <bsd.subdir.mk> diff --git a/gnu/usr.bin/cvs/contrib/README b/gnu/usr.bin/cvs/contrib/README new file mode 100644 index 0000000..412be26 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/README @@ -0,0 +1,68 @@ +@(#)README 1.8 92/04/10 + +This "contrib" directory is a place holder for code/scripts sent to +me by contributors around the world. This READM file will be kept +up-to-date from release to release. BUT, I must point out that these +contributions are really, REALLY UNSSUPPORTED. In fact, I probably +don't even know what they do. Nor do I guarantee to have tried them, +or ported them to work with this CVS distribution. If you have questions, +you might contact the author, but you should not necessarily expect +a reply. USE AT YOUR OWN RISK -- and all that stuff. + +Contents of this directory: + + README This file. + log.pl A perl script suitable for including in your + $CVSROOT/CVSROOT/loginfo file for logging commit + changes. Includes the RCS revision of the change + as part of the log. + Contributed by Kevin Samborn <samborn@sunrise.com>. + pcl-cvs A directory that contains GNU Emacs lisp code which + implements a CVS-mode for emacs. + Contributed by Per Cederqvist <ceder@lysator.liu.se>. + commit_prep.pl A perl script, to be combined with log_accum.pl, to + log_accum.pl provide for a way to combine the individual log + messages of a multi-directory "commit" into a + single log message, and mail the result somewhere. + Also does other checks for $Id and that you are + committing the correct revision of the file. + Read the comments carefully. + Contributed by David Hampton <hampton@cisco.com>. + mfpipe.pl Another perl script for logging. Allows you to + pipe the log message to a file and/or send mail + to some alias. + Contributed by John Clyne <clyne@niwot.scd.ucar.edu>. + rcs-to-cvs Script to import sources that may have been under + RCS control already. + Contributed by Per Cederqvist <ceder@lysator.liu.se>. + cvscheck Identifies files added, changed, or removed in a + cvscheck.man checked out CVS tree; also notices unknown files. + Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> + cvshelp.man An introductory manual page written by Lowell Skoog + <fluke!lowell@uunet.uu.net>. It is most likely + out-of-date relative to CVS 1.3, but still may be + useful. + dirfns A shar file which contains some code that might + help your system support opendir/readdir/closedir, + if it does not already. + Copied from the C-News distribution. + rcslock.pl A perl script that can be added to your commitinfo + file that tries to determine if your RCS file is + currently locked by someone else, as might be the + case for a binary file. + Contributed by John Rouillard <rouilj@cs.umb.edu>. + cvs_acls.pl A perl script that implements Access Control Lists + by using the "commitinfo" hook provided with the + "cvs commit" command. + Contributed by David G. Grubbs <dgg@ksr.com>. + descend A shell script that can be used to recursively + descend.man descend through a directory. In CVS 1.2, this was + very useful, since many of the commands were not + recursive. In CVS 1.3 (and later), however, most of + the commands are recursive. However, this may still + come in handy. + Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> + cln_hist.pl A perl script to compress your + $CVSROOT/CVSROOT/history file, as it can grow quite + large after extended use. + Contributed by David G. Grubbs <dgg@ksr.com> diff --git a/gnu/usr.bin/cvs/contrib/cln_hist.pl b/gnu/usr.bin/cvs/contrib/cln_hist.pl new file mode 100644 index 0000000..b353edc --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/cln_hist.pl @@ -0,0 +1,91 @@ +#!/usr/bin/perl -- # -*-Perl-*- +# +# cln_hist.pl,v 1.1 1992/04/10 03:04:15 berliner Exp +# Contributed by David G. Grubbs <dgg@ksr.com> +# +# Clean up the history file. 10 Record types: MAR OFT WUCG +# +# WUCG records are thrown out. +# MAR records are retained. +# T records: retain only last tag with same combined tag/module. +# +# Two passes: Walk through the first time and remember the +# 1. Last Tag record with same "tag" and "module" names. +# 2. Last O record with unique user/module/directory, unless followed +# by a matching F record. +# + +$r = $ENV{"CVSROOT"}; +$c = "$r/CVSROOT"; +$h = "$c/history"; + +eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';" + while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV)); +exit 255 if $die; # process any variable=value switches + +%tags = (); +%outs = (); + +# +# Move history file to safe place and re-initialize a new one. +# +rename($h, "$h.bak"); +open(XX, ">$h"); +close(XX); + +# +# Pass1 -- remember last tag and checkout. +# +open(HIST, "$h.bak"); +while (<HIST>) { + next if /^[MARWUCG]/; + + # Save whole line keyed by tag|module + if (/^T/) { + @tmp = split(/\|/, $_); + $tags{$tmp[4] . '|' . $tmp[5]} = $_; + } + # Save whole line + if (/^[OF]/) { + @tmp = split(/\|/, $_); + $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} = $_; + } +} + +# +# Pass2 -- print out what we want to save. +# +open(SAVE, ">$h.work"); +open(HIST, "$h.bak"); +while (<HIST>) { + next if /^[FWUCG]/; + + # If whole line matches saved (i.e. "last") one, print it. + if (/^T/) { + @tmp = split(/\|/, $_); + next if $tags{$tmp[4] . '|' . $tmp[5]} ne $_; + } + # Save whole line + if (/^O/) { + @tmp = split(/\|/, $_); + next if $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} ne $_; + } + + print SAVE $_; +} + +# +# Put back the saved stuff +# +system "cat $h >> $h.work"; + +if (-s $h) { + rename ($h, "$h.interim"); + print "history.interim has non-zero size.\n"; +} else { + unlink($h); +} + +rename ("$h.work", $h); + +exit(0); diff --git a/gnu/usr.bin/cvs/contrib/commit_prep.pl b/gnu/usr.bin/cvs/contrib/commit_prep.pl new file mode 100644 index 0000000..b3f7e9a --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/commit_prep.pl @@ -0,0 +1,168 @@ +#!/usr/local/bin/perl -w +# +# +# Perl filter to handle pre-commit checking of files. This program +# records the last directory where commits will be taking place for +# use by the log_accumulate script. For new file, it forcing the +# existence of a RCS "Id" keyword in the first ten lines of the file. +# For existing files, it checks version number in the "Id" line to +# prevent losing changes because an old version of a file was copied +# into the direcory. +# +# Possible future enhancements: +# +# +# Check for cruft left by unresolved conflicts. Search for +# "^<<<<<<<$", "^-------$", and "^>>>>>>>$". +# +# Look for a copyright and automagically update it to the +# current year. +# +# Contributed by David Hampton <hampton@cisco.com> +# + +############################################################ +# +# Configurable options +# +############################################################ +# +# Check each file (except dot files) for an RCS "Id" keyword. +# +$check_id = 1; + +# +# Record the directory for later use by the log_accumulate stript. +# +$record_directory = 1; + +############################################################ +# +# Constants +# +############################################################ +$LAST_FILE = "/tmp/#cvs.lastdir"; +$ENTRIES = "CVS/Entries"; + +$NoId = " +%s - Does not contain a line with the keyword \"Id:\". + Please see the template files for an example.\n"; + +# Protect string from substitution by RCS. +$NoName = " +%s - The ID line should contain only \"\$\I\d\:\ \$\" for a newly created file.\n"; + +$BadName = " +%s - The file name '%s' in the ID line does not match + the actual filename.\n"; + +$BadVersion = " +%s - How dare you!! You replaced your copy of the file '%s', + which was based upon version %s, with an %s version based + upon %s. Please move your '%s' out of the way, perform an + update to get the current version, and them merge your changes + into that file.\n"; + +############################################################ +# +# Subroutines +# +############################################################ + +sub write_line { + local($filename, $line) = @_; + open(FILE, ">$filename") || die("Cannot open $filename, stopped"); + print(FILE $line, "\n"); + close(FILE); +} + +sub check_version { + local($i, $id, $rname, $version); + local($filename, $cvsversion) = @_; + + open(FILE, $filename) || die("Cannot open $filename, stopped"); + for ($i = 1; $i < 10; $i++) { + $pos = -1; + last if eof(FILE); + $line = <FILE>; + $pos = index($line, "Id: "); + last if ($pos >= 0); + } + + if ($pos == -1) { + printf($NoId, $filename); + return(1); + } + + ($id, $rname, $version) = split(' ', substr($line, $pos)); + if ($cvsversion{$filename} == 0) { + if ($rname ne "\$") { + printf($NoName, $filename); + return(1); + } + return(0); + } + + if ($rname ne "$filename,v") { + printf($BadName, $filename, substr($rname, 0, length($rname)-2)); + return(1); + } + if ($cvsversion{$filename} < $version) { + printf($BadVersion, $filename, $filename, $cvsversion{$filename}, + "newer", $version, $filename); + return(1); + } + if ($cvsversion{$filename} > $version) { + printf($BadVersion, $filename, $filename, $cvsversion{$filename}, + "older", $version, $filename); + return(1); + } + return(0); +} + +############################################################# +# +# Main Body +# +############################################################ + +$id = getpgrp(); +#print("ARGV - ", join(":", @ARGV), "\n"); +#print("id - ", id, "\n"); + +# +# Suck in the Entries file +# +open(ENTRIES, $ENTRIES) || die("Cannot open $ENTRIES.\n"); +while (<ENTRIES>) { + local($filename, $version) = split('/', substr($_, 1)); + $cvsversion{$filename} = $version; +} + +# +# Now check each file name passed in, except for dot files. Dot files +# are considered to be administrative files by this script. +# +if ($check_id != 0) { + $failed = 0; + $directory = $ARGV[0]; + shift @ARGV; + foreach $arg (@ARGV) { + next if (index($arg, ".") == 0); + $failed += &check_version($arg); + } + if ($failed) { + print "\n"; + exit(1); + } +} + +# +# Record this directory as the last one checked. This will be used +# by the log_accumulate script to determine when it is processing +# the final directory of a multi-directory commit. +# +if ($record_directory != 0) { + &write_line("$LAST_FILE.$id", $directory); +} +exit(0); diff --git a/gnu/usr.bin/cvs/contrib/cvs_acls.pl b/gnu/usr.bin/cvs/contrib/cvs_acls.pl new file mode 100644 index 0000000..1a0096a --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/cvs_acls.pl @@ -0,0 +1,142 @@ +#!/usr/bin/perl -- # -*-Perl-*- +# +# cvs_acls.pl,v 1.2 1992/04/11 16:01:24 berliner Exp +# +# Access control lists for CVS. dgg@ksr.com (David G. Grubbs) +# +# CVS "commitinfo" for matching repository names, running the program it finds +# on the same line. More information is available in the CVS man pages. +# +# ==== INSTALLATION: +# +# To use this program as I intended, do the following four things: +# +# 0. Install PERL. :-) +# +# 1. Put one line, as the *only* non-comment line, in your commitinfo file: +# +# DEFAULT /usr/local/bin/cvs_acls +# +# 2. Install this file as /usr/local/bin/cvs_acls and make it executable. +# +# 3. Create a file named $CVSROOT/CVSROOT/avail. +# +# ==== FORMAT OF THE avail FILE: +# +# The avail file determines whether you may commit files. It contains lines +# read from top to bottom, keeping track of a single "bit". The "bit" +# defaults to "on". It can be turned "off" by "unavail" lines and "on" by +# "avail" lines. ==> Last one counts. +# +# Any line not beginning with "avail" or "unavail" is ignored. +# +# Lines beginning with "avail" or "unavail" are assumed to be '|'-separated +# triples: (All spaces and tabs are ignored in a line.) +# +# {avail.*,unavail.*} [| user,user,... [| repos,repos,...]] +# +# 1. String starting with "avail" or "unavail". +# 2. Optional, comma-separated list of usernames. +# 3. Optional, comma-separated list of repository pathnames. +# These are pathnames relative to $CVSROOT. They can be directories or +# filenames. A directory name allows access to all files and +# directories below it. +# +# Example: (Text from the ';;' rightward may not appear in the file.) +# +# unavail ;; Make whole repository unavailable. +# avail|dgg ;; Except for user "dgg". +# avail|fred, john|bin/ls ;; Except when "fred" or "john" commit to +# ;; the module whose repository is "bin/ls" +# +# PROGRAM LOGIC: +# +# CVS passes to @ARGV an absolute directory pathname (the repository +# appended to your $CVSROOT variable), followed by a list of filenames +# within that directory. +# +# We walk through the avail file looking for a line that matches both +# the username and repository. +# +# A username match is simply the user's name appearing in the second +# column of the avail line in a space-or-comma separate list. +# +# A repository match is either: +# - One element of the third column matches $ARGV[0], or some +# parent directory of $ARGV[0]. +# - Otherwise *all* file arguments ($ARGV[1..$#ARGV]) must be +# in the file list in one avail line. +# - In other words, using directory names in the third column of +# the avail file allows committing of any file (or group of +# files in a single commit) in the tree below that directory. +# - If individual file names are used in the third column of +# the avail file, then files must be committed individually or +# all files specified in a single commit must all appear in +# third column of a single avail line. +# + +$debug = 0; +$cvsroot = $ENV{'CVSROOT'}; +$availfile = $cvsroot . "/CVSROOT/avail"; +$myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"}); + +eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';" + while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV)); +exit 255 if $die; # process any variable=value switches + +die "Must set CVSROOT\n" if !$cvsroot; +($repos = shift) =~ s:^$cvsroot/::; +grep($_ = $repos . '/' . $_, @ARGV); + +print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug; + +$exit_val = 0; # Good Exit value + +$universal_off = 0; +open (AVAIL, $availfile) || exit(0); # It is ok for avail file not to exist +while (<AVAIL>) { + chop; + next if /^\s*\#/; + next if /^\s*$/; + ($flagstr, $u, $m) = split(/[\s,]*\|[\s,]*/, $_); + + # Skip anything not starting with "avail" or "unavail" and complain. + (print "Bad avail line: $_\n"), next + if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/); + + # Set which bit we are playing with. ('0' is OK == Available). + $flag = (($& eq "avail") ? 0 : 1); + + # If we find a "universal off" flag (i.e. a simple "unavail") remember it + $universal_off = 1 if ($flag && !$u && !$m); + + # $myname considered "in user list" if actually in list or is NULL + $in_user = (!$u || grep ($_ eq $myname, split(/[\s,]+/,$u))); + print "$$ \$myname($myname) in user list: $_\n" if $debug && $in_user; + + # Module matches if it is a NULL module list in the avail line. If module + # list is not null, we check every argument combination. + if (!($in_repo = !$m)) { + @tmp = split(/[\s,]+/,$m); + for $j (@tmp) { + # If the repos from avail is a parent(or equal) dir of $repos, OK + $in_repo = 1, last if ($repos eq $j || $repos =~ /^$j\//); + } + if (!$in_repo) { + $in_repo = 1; + for $j (@ARGV) { + last if !($in_repo = grep ($_ eq $j, @tmp)); + } + } + } + print "$$ \$repos($repos) in repository list: $_\n" if $debug && $in_repo; + + $exit_val = $flag if ($in_user && $in_repo); + print "$$ ==== \$exit_val = $exit_val\n$$ ==== \$flag = $flag\n" if $debug; +} +close(AVAIL); +print "$$ ==== \$exit_val = $exit_val\n" if $debug; +print "**** Access denied: Insufficient Karma ($myname|$repos)\n" if $exit_val; +print "**** Access allowed: Personal Karma exceeds Environmental Karma.\n" + if $universal_off && !$exit_val; +exit($exit_val); diff --git a/gnu/usr.bin/cvs/contrib/cvscheck b/gnu/usr.bin/cvs/contrib/cvscheck new file mode 100644 index 0000000..67fdbbd --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/cvscheck @@ -0,0 +1,84 @@ +#! /bin/sh +# cvscheck,v 1.2 1992/04/10 03:04:19 berliner Exp +# +# cvscheck - identify files added, changed, or removed +# in CVS working directory +# +# Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> +# +# This program should be run in a working directory that has been +# checked out using CVS. It identifies files that have been added, +# changed, or removed in the working directory, but not "cvs +# committed". It also determines whether the files have been "cvs +# added" or "cvs removed". For directories, it is only practical to +# determine whether they have been added. + +name=cvscheck +changes=0 + +# If we can't run CVS commands in this directory +cvs status . > /dev/null 2>&1 +if [ $? != 0 ] ; then + + # Bail out + echo "$name: there is no version here; bailing out" 1>&2 + exit 1 +fi + +# Identify files added to working directory +for file in .* * ; do + + # Skip '.' and '..' + if [ $file = '.' -o $file = '..' ] ; then + continue + fi + + # If a regular file + if [ -f $file ] ; then + if cvs status $file | grep -s '^From:[ ]*New file' ; then + echo "file added: $file - not CVS committed" + changes=`expr $changes + 1` + elif cvs status $file | grep -s '^From:[ ]*no entry for' ; then + echo "file added: $file - not CVS added, not CVS committed" + changes=`expr $changes + 1` + fi + + # Else if a directory + elif [ -d $file -a $file != CVS.adm ] ; then + + # Move into it + cd $file + + # If CVS commands don't work inside + cvs status . > /dev/null 2>&1 + if [ $? != 0 ] ; then + echo "directory added: $file - not CVS added" + changes=`expr $changes + 1` + fi + + # Move back up + cd .. + fi +done + +# Identify changed files +changedfiles=`cvs diff | egrep '^diff' | awk '{print $3}'` +for file in $changedfiles ; do + echo "file changed: $file - not CVS committed" + changes=`expr $changes + 1` +done + +# Identify files removed from working directory +removedfiles=`cvs status | egrep '^File:[ ]*no file' | awk '{print $4}'` + +# Determine whether each file has been cvs removed +for file in $removedfiles ; do + if cvs status $file | grep -s '^From:[ ]*-' ; then + echo "file removed: $file - not CVS committed" + else + echo "file removed: $file - not CVS removed, not CVS committed" + fi + changes=`expr $changes + 1` +done + +exit $changes diff --git a/gnu/usr.bin/cvs/contrib/cvscheck.man b/gnu/usr.bin/cvs/contrib/cvscheck.man new file mode 100644 index 0000000..10d47f5 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/cvscheck.man @@ -0,0 +1,53 @@ +.\" cvscheck.man,v 1.1 1992/04/10 03:04:20 berliner Exp +.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> +.TH CVSCHECK LOCAL "4 March 1991" FLUKE +.SH NAME +cvscheck \- identify files added, changed, or removed in a CVS working +directory +.SH SYNOPSIS +.B cvscheck +.SH DESCRIPTION +This command is a housekeeping aid. It should be run in a working +directory that has been checked out using CVS. It identifies files +that have been added, changed, or removed in the working directory, but +not CVS +.BR commit ted. +It also determines whether the files have been CVS +.BR add ed +or CVS +.BR remove d. +For directories, this command determines only whether they have been +.BR add ed. +It operates in the current directory only. +.LP +This command provides information that is available using CVS +.B status +and CVS +.BR diff . +The advantage of +.B cvscheck +is that its output is very concise. It saves you the strain (and +potential error) of interpreting the output of CVS +.B status +and +.BR diff . +.LP +See +.BR cvs (local) +or +.BR cvshelp (local) +for instructions on how to add or remove a file or directory in a +CVS-controlled package. +.SH DIAGNOSTICS +The exit status is 0 if no files have been added, changed, or removed +from the current directory. Otherwise, the command returns a count of +the adds, changes, and deletes. +.SH SEE ALSO +.BR cvs (local), +.BR cvshelp (local) +.SH AUTHOR +Lowell Skoog +.br +Software Technology Group +.br +Technical Computing diff --git a/gnu/usr.bin/cvs/contrib/cvshelp.man b/gnu/usr.bin/cvs/contrib/cvshelp.man new file mode 100644 index 0000000..a7128a8 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/cvshelp.man @@ -0,0 +1,562 @@ +.\" cvshelp.man,v 1.1 1992/04/10 03:04:21 berliner Exp +.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> +.\" Full space in nroff; half space in troff +.de SP +.if n .sp +.if t .sp .5 +.. +.\" Start a command example +.de XS +.SP +.in +.5i +.ft B +.nf +.. +.\" End a command example +.de XE +.fi +.ft P +.in -.5i +.SP +.. +.TH CVSHELP LOCAL "17 March 1991" FLUKE +.SH NAME +cvshelp \- advice on using the Concurrent Versions System +.SH DESCRIPTION +This man page is based on experience using CVS. +It is bound to change as we gain more experience. +If you come up with better advice than is found here, +contact the Software Technology +Group and we will add it to this page. +.SS "Getting Started" +Use the following steps to prepare to use CVS: +.TP +\(bu +Take a look at the CVS manual page to see what it can do for you, and +if it fits your environment (or can possibly be made to fit your +environment). +.XS +man cvs +.XE +If things look good, continue on... +.TP +\(bu +Setup the master source repository. Choose a directory with +ample disk space available for source files. This is where the RCS +`,v' files will be stored. Say you choose +.B /src/master +as the root +of your source repository. Make the +.SB CVSROOT.adm +directory in the root of the source repository: +.XS +mkdir /src/master/CVSROOT.adm +.XE +.TP +\(bu +Populate this directory with the +.I loginfo +and +.I modules +files from the +.B "/usr/doc/local/cvs" +directory. Edit these files to reflect your local source repository +environment \- they may be quite small initially, but will grow as +sources are added to your source repository. Turn these files into +RCS controlled files: +.XS +cd /src/master/CVSROOT.adm +ci \-m'Initial loginfo file' loginfo +ci \-m'Initial modules file' modules +.XE +.TP +\(bu +Run the command: +.XS +mkmodules /src/master/CVSROOT.adm +.XE +This will build the +.BR ndbm (3) +file for the modules database. +.TP +\(bu +Remember to edit the +.I modules +file manually when sources are checked +in with +.B checkin +or CVS +.BR add . +A copy of the +.I modules +file for editing can be retrieved with the command: +.XS +cvs checkout CVSROOT.adm +.XE +.TP +\(bu +Have all users of the CVS system set the +.SM CVSROOT +environment variable appropriately to reflect the placement of your +source repository. If the above example is used, the following +commands can be placed in a +.I .login +or +.I .profile +file: +.XS +setenv CVSROOT /src/master +.XE +for csh users, and +.XS +CVSROOT=/src/master; export CVSROOT +.XE +for sh users. +.SS "Placing Locally Written Sources Under CVS Control" +Say you want to place the `whizbang' sources under +CVS control. Say further that the sources have never +been under revision control before. +.TP +\(bu +Move the source hierarchy (lock, stock, and barrel) +into the master source repository: +.XS +mv ~/whizbang $CVSROOT +.XE +.TP +\(bu +Clean out unwanted object files: +.XS +cd $CVSROOT/whizbang +make clean +.XE +.TP +\(bu +Turn every file in the hierarchy into an RCS controlled file: +.XS +descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-nV\fR\fIx\fR\fB_\fR\fIy\fR\fB *' +.XE +In this example, the initial release tag is \fBV\fIx\fB_\fIy\fR, +representing version \fIx\fR.\fIy\fR. +.LP +You can use CVS on sources that are already under RCS control. +The following example shows how. +In this example, the source package is called `skunkworks'. +.TP +\(bu +Move the source hierarchy into the master source +repository: +.XS +mv ~/skunkworks $CVSROOT +.XE +.TP +\(bu +Clean out unwanted object files: +.XS +cd $CVSROOT/skunkworks +make clean +.XE +.TP +\(bu +Clean out unwanted working files, leaving only the RCS `,v' files: +.XS +descend \-r rcsclean +.XE +Note: If any working files have been checked out and changed, +.B rcsclean +will fail. Check in the modified working files +and run the command again. +.TP +\(bu +Get rid of +.SB RCS +subdirectories. CVS does not use them. +.XS +descend \-r \-f 'mv RCS/*,v .' +descend \-r \-f 'rmdir RCS' +.XE +.TP +\(bu +Delete any unwanted files that remain in the source hierarchy. Then +make sure all files are under RCS control: +.XS +descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-n\fR\fItag\fR\fB *' +.XE +.I tag +is the latest symbolic revision tag that you applied to your package +(if any). Note: This command will probably generate lots of error +messages (for directories and existing RCS files) that you can +ignore. +.SS "Placing a Third-Party Source Distribution Under CVS Control" +The +.B checkin +command checks third-party sources into CVS. The +difference between third-party sources and locally +written sources is that third-party sources must be checked into a +separate branch (called the +.IR "vendor branch" ) +of the RCS tree. This makes it possible to merge local changes to +the sources with later releases from the vendor. +.TP +\(bu +Save the original distribution kit somewhere. For example, if the +master source repository is +.B /src/master +the distribution kit could be saved in +.BR /src/dist . +Organize the distribution directory so that each release +is clearly identifiable. +.TP +\(bu +Unpack the package in a scratch directory, for example +.BR ~/scratch . +.TP +\(bu +Create a repository for the package. +In this example, the package is called `Bugs-R-Us 4.3'. +.XS +mkdir $CVSROOT/bugs +.XE +.TP +\(bu +Check in the unpacked files: +.XS +cd ~/scratch +checkin \-m 'Bugs-R-Us 4.3 distribution' bugs VENDOR V4_3 +.XE +There is nothing magic about the tag `VENDOR', which is applied to +the vendor branch. You can use whatever tag you want. `VENDOR' is a +useful convention. +.TP +\(bu +Never modify vendor files before checking them in. +Check in the files +.I exactly +as you unpacked them. +If you check in locally modified files, future vendor releases may +wipe out your local changes. +.SS "Working With CVS-Controlled Sources" +To use or edit the sources, you must check out a private copy. +For the following examples, the master files are assumed to reside in +.BR "$CVSROOT/behemoth" . +The working directory is +.BR "~/work" . +See +.BR cvs (local) +for more details on the commands mentioned below. +.TP +.I "To Check Out Working Files +Use CVS +.BR checkout : +.XS +cd ~/work +cvs checkout behemoth +.XE +There is nothing magic about the working directory. CVS will check +out sources anywhere you like. Once you have a working copy of the +sources, you can compile or edit them as desired. +.TP +.I "To Display Changes You Have Made" +Use CVS +.BR diff +to display detailed changes, equivalent to +.BR rcsdiff (local). +You can also use +.BR cvscheck (local) +to list files added, changed, and removed in +the directory, but not yet +.BR commit ted. +You must be in a directory containing working files. +.TP +.I "To Display Revision Information" +Use CVS +.BR log , +which is equivalent to +.BR rlog (local). +You must be in a directory containing working files. +.TP +.I "To Update Working Files" +Use CVS +.BR update +in a directory containing working files. +This command brings your working files up +to date with changes checked into the +master repository since you last checked out or updated +your files. +.TP +.I "To Check In Your Changes" +Use CVS +.BR commit +in a directory containing working files. +This command checks your changes into the master repository. +You can specify files by name or use +.XS +cvs commit \-a +.XE +to +.B commit +all the files you have changed. +.TP +.I "To Add a File" +Add the file to the working directory. +Use CVS +.B add +to mark the file as added. +Use CVS +.B commit +to add the file to the master repository. +.TP +.I "To Remove a File" +Remove the file from the working directory. +Use CVS +.B remove +to mark the file as removed. +Use CVS +.B commit +to move the file from its current location in the master repository +to the CVS +.IR Attic +directory. +.TP +.I "To Add a Directory" +Add the directory to the working directory. +Use CVS +.B add +to add the directory to the master repository. +.TP +.I "To Remove a Directory" +.br +You shouldn't remove directories under CVS. You should instead remove +their contents and then prune them (using the +.B \-f +and +.B \-p +options) when you +.B checkout +or +.B update +your working files. +.TP +.I "To Tag a Release" +Use CVS +.B tag +to apply a symbolic tag to the latest revision of each file in the +master repository. For example: +.XS +cvs tag V2_1 behemoth +.XE +.TP +.I "To Retrieve an Exact Copy of a Previous Release" +During a CVS +.B checkout +or +.BR update , +use the +.B \-r +option to retrieve revisions associated with a symbolic tag. +Use the +.B \-f +option to ignore all RCS files that do not contain the +tag. +Use the +.B \-p +option to prune directories that wind up empty because none +of their files matched the tag. Example: +.XS +cd ~/work +cvs checkout \-r V2_1 \-f \-p behemoth +.XE +.SS "Logging Changes" +It is a good idea to keep a change log together with the +sources. As a minimum, the change log should name and describe each +tagged release. The change log should also be under CVS control and +should be tagged along with the sources. +.LP +.BR cvslog (local) +can help. This command logs +changes reported during CVS +.B commit +operations. It automatically +updates a change log file in your working directory. When you are +finished making changes, you (optionally) edit the change log file and +then commit it to the master repository. +.LP +Note: You must edit the change log to describe a new release +and +.B commit +it to the master repository +.I before +.BR tag ging +the release using CVS. Otherwise, the release description will not be +included in the tagged package. +.LP +See +.BR cvslog (local) +for more information. +.SS "Merging a Subsequent Third-Party Distribution" +The initial steps in this process are identical to placing a +third-party distribution under CVS for the first time: save the +distribution kit and unpack the package in a scratch directory. From +that point the steps diverge. +The following example considers release 5.0 of the +Bugs-R-Us package. +.TP +\(bu +Check in the sources after unpacking them: +.XS +cd ~/scratch +checkin \-m 'Bugs-R-Us 5.0 distribution' bugs VENDOR V5_0 \\ + | tee ~/WARNINGS +.XE +It is important to save the output of +.B checkin +in a file +because it lists the sources that have been locally modified. +It is best to save the file in a different directory (for example, +your home directory). Otherwise, +.B checkin +will try to check it into the master repository. +.TP +\(bu +In your usual working directory, check out a fresh copy of the +distribution that you just checked in. +.XS +cd ~/work +cvs checkout \-r VENDOR bugs +.XE +The +.B checkout +command shown above retrieves the latest revision on the vendor branch. +.TP +\(bu +See the `WARNINGS' file for a list of all locally modified +sources. +For each locally modified source, +look at the differences between +the new distribution and the latest local revision: +.XS +cvs diff \-r \fR\fILocalRev file\fR\fB +.XE +In this command, +.I LocalRev +is the latest +numeric or symbolic revision +on the RCS trunk of +.IR file . +You can use CVS +.B log +to get the revision history. +.TP +\(bu +If your local modifications to a file have been incorporated into +the vendor's distribution, then you should reset the default RCS +branch for that file to the vendor branch. CVS doesn't provide a +mechanism to do this. You have to do it by hand in the master +repository: +.XS +rcs \-bVENDOR \fR\fIfile\fR\fB,v +.XE +.TP +\(bu +If your local modifications need to be merged with the +new distribution, use CVS +.B join +to do it: +.XS +cvs join \-r VENDOR \fR\fIfile\fR\fB +.XE +The resulting file will be placed in your working directory. +Edit it to resolve any overlaps. +.TP +\(bu +Test the merged package. +.TP +\(bu +Commit all modified files to the repository: +.XS +cvs commit \-a +.XE +.TP +\(bu +Tag the repository with a new local tag. +.SS "Applying Patches to Third-Party Sources" +Patches are handled in a manner very similar to complete +third-party distributions. This example considers patches applied to +Bugs-R-Us release 5.0. +.TP +\(bu +Save the patch files together with the distribution kit +to which they apply. +The patch file names should clearly indicate the patch +level. +.TP +\(bu +In a scratch directory, check out the last `clean' vendor copy \- the +highest revision on the vendor branch with +.IR "no local changes" : +.XS +cd ~/scratch +cvs checkout \-r VENDOR bugs +.XE +.TP +\(bu +Use +.BR patch (local) +to apply the patches. You should now have an image of the +vendor's software just as though you had received a complete, +new release. +.TP +\(bu +Proceed with the steps described for merging a subsequent third-party +distribution. +.TP +\(bu +Note: When you get to the step that requires you +to check out the new distribution after you have +checked it into the vendor branch, you should move to a different +directory. Do not attempt to +.B checkout +files in the directory in +which you applied the patches. If you do, CVS will try to merge the +changes that you made during patching with the version being checked +out and things will get very confusing. Instead, +go to a different directory (like your working directory) and +check out the files there. +.SS "Advice to Third-Party Source Hackers" +As you can see from the preceding sections, merging local changes +into third-party distributions remains difficult, and probably +always will. This fact suggests some guidelines: +.TP +\(bu +Minimize local changes. +.I Never +make stylistic changes. +Change makefiles only as much as needed for installation. Avoid +overhauling anything. Pray that the vendor does the same. +.TP +\(bu +Avoid renaming files or moving them around. +.TP +\(bu +Put independent, locally written files like help documents, local +tools, or man pages in a sub-directory called `local-additions'. +Locally written files that are linked into an existing executable +should be added right in with the vendor's sources (not in a +`local-additions' directory). +If, in the future, +the vendor distributes something +equivalent to your locally written files +you can CVS +.B remove +the files from the `local-additions' directory at that time. +.SH SEE ALSO +.BR cvs (local), +.BR checkin (local), +.BR cvslog (local), +.BR cvscheck (local) +.SH AUTHOR +Lowell Skoog +.br +Software Technology Group +.br +Technical Computing diff --git a/gnu/usr.bin/cvs/contrib/descend b/gnu/usr.bin/cvs/contrib/descend new file mode 100644 index 0000000..b63e4a7 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/descend @@ -0,0 +1,116 @@ +#! /bin/sh +# descend,v 1.1 1992/04/03 05:22:52 berliner Exp +# +# descend - walk down a directory tree and execute a command at each node + +fullname=$0 +name=descend +usage="Usage: $name [-afqrv] command [directory ...]\n +\040\040-a\040\040All: descend into directories starting with '.'\n +\040\040-f\040\040Force: ignore errors during descent\n +\040\040-q\040\040Quiet: don't print directory names\n +\040\040-r\040\040Restricted: don't descend into RCS, CVS.adm, SCCS directories\n +\040\040-v\040\040Verbose: print command before executing it" + +# Scan for options +while getopts afqrv option; do + case $option in + a) + alldirs=$option + options=$options" "-$option + ;; + f) + force=$option + options=$options" "-$option + ;; + q) + verbose= + quiet=$option + options=$options" "-$option + ;; + r) + restricted=$option + options=$options" "-$option + ;; + v) + verbose=$option + quiet= + options=$options" "-$option + ;; + \?) + /usr/5bin/echo $usage 1>&2 + exit 1 + ;; + esac +done +shift `expr $OPTIND - 1` + +# Get command to execute +if [ $# -lt 1 ] ; then + /usr/5bin/echo $usage 1>&2 + exit 1 +else + command=$1 + shift +fi + +# If no directory specified, use '.' +if [ $# -lt 1 ] ; then + default_dir=. +fi + +# For each directory specified +for dir in $default_dir "$@" ; do + + # Spawn sub-shell so we return to starting directory afterward + (cd $dir + + # Execute specified command + if [ -z "$quiet" ] ; then + echo In directory `hostname`:`pwd` + fi + if [ -n "$verbose" ] ; then + echo $command + fi + eval "$command" || if [ -z "$force" ] ; then exit 1; fi + + # Collect dot file names if necessary + if [ -n "$alldirs" ] ; then + dotfiles=.* + else + dotfiles= + fi + + # For each file in current directory + for file in $dotfiles * ; do + + # Skip '.' and '..' + if [ "$file" = "." -o "$file" = ".." ] ; then + continue + fi + + # If a directory but not a symbolic link + if [ -d "$file" -a ! -h "$file" ] ; then + + # If not skipping this type of directory + if [ \( "$file" != "RCS" -a \ + "$file" != "SCCS" -a \ + "$file" != "CVS" -a \ + "$file" != "CVS.adm" \) \ + -o -z "$restricted" ] ; then + + # Recursively descend into it + $fullname $options "$command" "$file" \ + || if [ -z "$force" ] ; then exit 1; fi + fi + + # Else if a directory AND a symbolic link + elif [ -d "$file" -a -h "$file" ] ; then + + if [ -z "$quiet" ] ; then + echo In directory `hostname`:`pwd`/$file: symbolic link: skipping + fi + fi + done + ) || if [ -z "$force" ] ; then exit 1; fi +done diff --git a/gnu/usr.bin/cvs/contrib/descend.man b/gnu/usr.bin/cvs/contrib/descend.man new file mode 100644 index 0000000..adeab3b --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/descend.man @@ -0,0 +1,115 @@ +.\" descend.man,v 1.1 1992/04/03 05:22:53 berliner Exp +.TH DESCEND 1 "31 March 1992" +.SH NAME +descend \- walk directory tree and execute a command at each node +.SH SYNOPSIS +.B descend +[ +.B \-afqrv +] +.I command +[ +.I directory +\&.\|.\|. +] +.SH DESCRIPTION +.B descend +walks down a directory tree and executes a command at each node. It +is not as versatile as +.BR find (1), +but it has a simpler syntax. If no +.I directory +is specified, +.B descend +starts at the current one. +.LP +Unlike +.BR find , +.B descend +can be told to skip the special directories associated with RCS, +CVS, and SCCS. This makes +.B descend +especially handy for use with these packages. It can be used with +other commands too, of course. +.LP +.B descend +is a poor man's way to make any command recursive. Note: +.B descend +does not follow symbolic links to directories unless they are +specified on the command line. +.SH OPTIONS +.TP 15 +.B \-a +.I All. +Descend into directories that begin with '.'. +.TP +.B \-f +.I Force. +Ignore errors during descent. Normally, +.B descend +quits when an error occurs. +.TP +.B \-q +.I Quiet. +Suppress the message `In directory +.IR directory ' +that is normally printed during the descent. +.TP +.B \-r +.I Restricted. +Don't descend into the special directories +.SB RCS, +.SB CVS, +.SB CVS.adm, +and +.SB SCCS. +.TP +.B \-v +.I Verbose. +Print +.I command +before executing it. +.SH EXAMPLES +.TP 15 +.B "descend ls" +Cheap substitute for `ls -R'. +.TP 15 +.B "descend -f 'rm *' tree" +Strip `tree' of its leaves. This command descends the `tree' +directory, removing all regular files. Since +.BR rm (1) +does not remove directories, this command leaves the directory +structure of `tree' intact, but denuded. The +.B \-f +option is required to keep +.B descend +from quitting. You could use `rm \-f' instead. +.TP +.B "descend -r 'co RCS/*'" /project/src/ +Check out every RCS file under the directory +.BR "/project/src" . +.TP +.B "descend -r 'cvs diff'" +Perform CVS `diff' operation on every directory below (and including) +the current one. +.SH DIAGNOSTICS +Returns 1 if errors occur (and the +.B \-f +option is not used). Otherwise returns 0. +.SH SEE ALSO +.BR find (1), +.BR rcsintro (1), +.BR cvs (1), +.BR sccs (1) +.SH AUTHOR +Lowell Skoog +.br +Software Technology Group +.br +John Fluke Mfg. Co., Inc. +.SH BUGS +Shell metacharacters in +.I command +may have bizarre effects. In particular, compound commands +(containing ';', '[', and ']' characters) will not work. It is best +to enclose complicated commands in single quotes \(aa\ \(aa. diff --git a/gnu/usr.bin/cvs/contrib/dirfns b/gnu/usr.bin/cvs/contrib/dirfns new file mode 100644 index 0000000..8324c41 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/dirfns @@ -0,0 +1,481 @@ +echo 'directory.3': +sed 's/^X//' >'directory.3' <<'!' +X.TH DIRECTORY 3 imported +X.DA 9 Oct 1985 +X.SH NAME +Xopendir, readdir, telldir, seekdir, rewinddir, closedir \- high-level directory operations +X.SH SYNOPSIS +X.B #include <sys/types.h> +X.br +X.B #include <ndir.h> +X.PP +X.SM +X.B DIR +X.B *opendir(filename) +X.br +X.B char *filename; +X.PP +X.SM +X.B struct direct +X.B *readdir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B long +X.B telldir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B seekdir(dirp, loc) +X.br +X.B DIR *dirp; +X.br +X.B long loc; +X.PP +X.SM +X.B rewinddir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B closedir(dirp) +X.br +X.B DIR *dirp; +X.SH DESCRIPTION +XThis library provides high-level primitives for directory scanning, +Xsimilar to those available for 4.2BSD's (very different) directory system. +X.\"The purpose of this library is to simulate +X.\"the new flexible length directory names of 4.2bsd UNIX +X.\"on top of the old directory structure of v7. +XIt incidentally provides easy portability to and from 4.2BSD (insofar +Xas such portability is not compromised by other 4.2/VAX dependencies). +X.\"It allows programs to be converted immediately +X.\"to the new directory access interface, +X.\"so that they need only be relinked +X.\"when moved to 4.2bsd. +X.\"It is obtained with the loader option +X.\".BR \-lndir . +X.PP +X.I Opendir +Xopens the directory named by +X.I filename +Xand associates a +X.I directory stream +Xwith it. +X.I Opendir +Xreturns a pointer to be used to identify the +X.I directory stream +Xin subsequent operations. +XThe pointer +X.SM +X.B NULL +Xis returned if +X.I filename +Xcannot be accessed or is not a directory. +X.PP +X.I Readdir +Xreturns a pointer to the next directory entry. +XIt returns +X.B NULL +Xupon reaching the end of the directory or detecting +Xan invalid +X.I seekdir +Xoperation. +X.PP +X.I Telldir +Xreturns the current location associated with the named +X.I directory stream. +X.PP +X.I Seekdir +Xsets the position of the next +X.I readdir +Xoperation on the +X.I directory stream. +XThe new position reverts to the one associated with the +X.I directory stream +Xwhen the +X.I telldir +Xoperation was performed. +XValues returned by +X.I telldir +Xare good only for the lifetime of the DIR pointer from +Xwhich they are derived. +XIf the directory is closed and then reopened, +Xthe +X.I telldir +Xvalue may be invalidated +Xdue to undetected directory compaction in 4.2BSD. +XIt is safe to use a previous +X.I telldir +Xvalue immediately after a call to +X.I opendir +Xand before any calls to +X.I readdir. +X.PP +X.I Rewinddir +Xresets the position of the named +X.I directory stream +Xto the beginning of the directory. +X.PP +X.I Closedir +Xcauses the named +X.I directory stream +Xto be closed, +Xand the structure associated with the DIR pointer to be freed. +X.PP +XA +X.I direct +Xstructure is as follows: +X.PP +X.RS +X.nf +Xstruct direct { +X /* unsigned */ long d_ino; /* inode number of entry */ +X unsigned short d_reclen; /* length of this record */ +X unsigned short d_namlen; /* length of string in d_name */ +X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ +X}; +X.fi +X.RE +X.PP +XThe +X.I d_reclen +Xfield is meaningless in non-4.2BSD systems and should be ignored. +XThe use of a +X.I long +Xfor +X.I d_ino +Xis also a 4.2BSDism; +X.I ino_t +X(see +X.IR types (5)) +Xshould be used elsewhere. +XThe macro +X.I DIRSIZ(dp) +Xgives the minimum memory size needed to hold the +X.I direct +Xvalue pointed to by +X.IR dp , +Xwith the minimum necessary allocation for +X.IR d_name . +X.PP +XThe preferred way to search the current directory for entry ``name'' is: +X.PP +X.RS +X.nf +X len = strlen(name); +X dirp = opendir("."); +X if (dirp == NULL) { +X fprintf(stderr, "%s: can't read directory .\\n", argv[0]); +X return NOT_FOUND; +X } +X while ((dp = readdir(dirp)) != NULL) +X if (dp->d_namlen == len && strcmp(dp->d_name, name) == 0) { +X closedir(dirp); +X return FOUND; +X } +X closedir(dirp); +X return NOT_FOUND; +X.RE +X.\".SH LINKING +X.\"This library is accessed by specifying ``-lndir'' as the +X.\"last argument to the compile line, e.g.: +X.\".PP +X.\" cc -I/usr/include/ndir -o prog prog.c -lndir +X.SH "SEE ALSO" +Xopen(2), +Xclose(2), +Xread(2), +Xlseek(2) +X.SH HISTORY +XWritten by +XKirk McKusick at Berkeley (ucbvax!mckusick). +XMiscellaneous bug fixes from elsewhere. +XThe size of the data structure has been decreased to avoid excessive +Xspace waste under V7 (where filenames are 14 characters at most). +XFor obscure historical reasons, the include file is also available +Xas +X.IR <ndir/sys/dir.h> . +XThe Berkeley version lived in a separate library (\fI\-lndir\fR), +Xwhereas ours is +Xpart of the C library, although the separate library is retained to +Xmaximize compatibility. +X.PP +XThis manual page has been substantially rewritten to be informative in +Xthe absence of a 4.2BSD manual. +X.SH BUGS +XThe +X.I DIRSIZ +Xmacro actually wastes a bit of space due to some padding requirements +Xthat are an artifact of 4.2BSD. +X.PP +XThe returned value of +X.I readdir +Xpoints to a static area that will be overwritten by subsequent calls. +X.PP +XThere are some unfortunate name conflicts with the \fIreal\fR V7 +Xdirectory structure definitions. +! +echo 'dir.h': +sed 's/^X//' >'dir.h' <<'!' +X/* dir.h 4.4 82/07/25 */ +X +X/* +X * A directory consists of some number of blocks of DIRBLKSIZ +X * bytes, where DIRBLKSIZ is chosen such that it can be transferred +X * to disk in a single atomic operation (e.g. 512 bytes on most machines). +X * +X * Each DIRBLKSIZ byte block contains some number of directory entry +X * structures, which are of variable length. Each directory entry has +X * a struct direct at the front of it, containing its inode number, +X * the length of the entry, and the length of the name contained in +X * the entry. These are followed by the name padded to a 4 byte boundary +X * with null bytes. All names are guaranteed null terminated. +X * The maximum length of a name in a directory is MAXNAMLEN. +X * +X * The macro DIRSIZ(dp) gives the amount of space required to represent +X * a directory entry. Free space in a directory is represented by +X * entries which have dp->d_reclen >= DIRSIZ(dp). All DIRBLKSIZ bytes +X * in a directory block are claimed by the directory entries. This +X * usually results in the last entry in a directory having a large +X * dp->d_reclen. When entries are deleted from a directory, the +X * space is returned to the previous entry in the same directory +X * block by increasing its dp->d_reclen. If the first entry of +X * a directory block is free, then its dp->d_ino is set to 0. +X * Entries other than the first in a directory do not normally have +X * dp->d_ino set to 0. +X */ +X#define DIRBLKSIZ 512 +X#ifdef VMUNIX +X#define MAXNAMLEN 255 +X#else +X#define MAXNAMLEN 14 +X#endif +X +Xstruct direct { +X /* unsigned */ long d_ino; /* inode number of entry */ +X unsigned short d_reclen; /* length of this record */ +X unsigned short d_namlen; /* length of string in d_name */ +X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ +X}; +X +X/* +X * The DIRSIZ macro gives the minimum record length which will hold +X * the directory entry. This requires the amount of space in struct direct +X * without the d_name field, plus enough space for the name with a terminating +X * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary. +X */ +X#undef DIRSIZ +X#define DIRSIZ(dp) \ +X ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3)) +X +X#ifndef KERNEL +X/* +X * Definitions for library routines operating on directories. +X */ +Xtypedef struct _dirdesc { +X int dd_fd; +X long dd_loc; +X long dd_size; +X char dd_buf[DIRBLKSIZ]; +X} DIR; +X#ifndef NULL +X#define NULL 0 +X#endif +Xextern DIR *opendir(); +Xextern struct direct *readdir(); +Xextern long telldir(); +X#ifdef void +Xextern void seekdir(); +Xextern void closedir(); +X#endif +X#define rewinddir(dirp) seekdir((dirp), (long)0) +X#endif KERNEL +! +echo 'makefile': +sed 's/^X//' >'makefile' <<'!' +XDIR = closedir.o opendir.o readdir.o seekdir.o telldir.o +XCFLAGS=-O -I. -Dvoid=int +XDEST=.. +X +Xall: $(DIR) +X +Xmv: $(DIR) +X mv $(DIR) $(DEST) +X +Xcpif: dir.h +X cp dir.h /usr/include/ndir.h +X +Xclean: +X rm -f *.o +! +echo 'closedir.c': +sed 's/^X//' >'closedir.c' <<'!' +Xstatic char sccsid[] = "@(#)closedir.c 4.2 3/10/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * close a directory. +X */ +Xvoid +Xclosedir(dirp) +X register DIR *dirp; +X{ +X close(dirp->dd_fd); +X dirp->dd_fd = -1; +X dirp->dd_loc = 0; +X free((char *)dirp); +X} +! +echo 'opendir.c': +sed 's/^X//' >'opendir.c' <<'!' +X/* Copyright (c) 1982 Regents of the University of California */ +X +Xstatic char sccsid[] = "@(#)opendir.c 4.4 11/12/82"; +X +X#include <sys/types.h> +X#include <sys/stat.h> +X#include <dir.h> +X +X/* +X * open a directory. +X */ +XDIR * +Xopendir(name) +X char *name; +X{ +X register DIR *dirp; +X register int fd; +X struct stat statbuf; +X char *malloc(); +X +X if ((fd = open(name, 0)) == -1) +X return NULL; +X if (fstat(fd, &statbuf) == -1 || !(statbuf.st_mode & S_IFDIR)) { +X close(fd); +X return NULL; +X } +X if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) { +X close (fd); +X return NULL; +X } +X dirp->dd_fd = fd; +X dirp->dd_loc = 0; +X dirp->dd_size = 0; /* so that telldir will work before readdir */ +X return dirp; +X} +! +echo 'readdir.c': +sed 's/^X//' >'readdir.c' <<'!' +X/* Copyright (c) 1982 Regents of the University of California */ +X +Xstatic char sccsid[] = "@(#)readdir.c 4.3 8/8/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * read an old stlye directory entry and present it as a new one +X */ +X#define ODIRSIZ 14 +X +Xstruct olddirect { +X ino_t od_ino; +X char od_name[ODIRSIZ]; +X}; +X +X/* +X * get next entry in a directory. +X */ +Xstruct direct * +Xreaddir(dirp) +X register DIR *dirp; +X{ +X register struct olddirect *dp; +X static struct direct dir; +X +X for (;;) { +X if (dirp->dd_loc == 0) { +X dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, +X DIRBLKSIZ); +X if (dirp->dd_size <= 0) { +X dirp->dd_size = 0; +X return NULL; +X } +X } +X if (dirp->dd_loc >= dirp->dd_size) { +X dirp->dd_loc = 0; +X continue; +X } +X dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc); +X dirp->dd_loc += sizeof(struct olddirect); +X if (dp->od_ino == 0) +X continue; +X dir.d_ino = dp->od_ino; +X strncpy(dir.d_name, dp->od_name, ODIRSIZ); +X dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */ +X dir.d_namlen = strlen(dir.d_name); +X dir.d_reclen = DIRBLKSIZ; +X return (&dir); +X } +X} +! +echo 'seekdir.c': +sed 's/^X//' >'seekdir.c' <<'!' +Xstatic char sccsid[] = "@(#)seekdir.c 4.9 3/25/83"; +X +X#include <sys/param.h> +X#include <dir.h> +X +X/* +X * seek to an entry in a directory. +X * Only values returned by "telldir" should be passed to seekdir. +X */ +Xvoid +Xseekdir(dirp, loc) +X register DIR *dirp; +X long loc; +X{ +X long curloc, base, offset; +X struct direct *dp; +X extern long lseek(); +X +X curloc = telldir(dirp); +X if (loc == curloc) +X return; +X base = loc & ~(DIRBLKSIZ - 1); +X offset = loc & (DIRBLKSIZ - 1); +X (void) lseek(dirp->dd_fd, base, 0); +X dirp->dd_size = 0; +X dirp->dd_loc = 0; +X while (dirp->dd_loc < offset) { +X dp = readdir(dirp); +X if (dp == NULL) +X return; +X } +X} +! +echo 'telldir.c': +sed 's/^X//' >'telldir.c' <<'!' +Xstatic char sccsid[] = "@(#)telldir.c 4.1 2/21/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * return a pointer into a directory +X */ +Xlong +Xtelldir(dirp) +X DIR *dirp; +X{ +X long lseek(); +X +X return (lseek(dirp->dd_fd, 0L, 1) - dirp->dd_size + dirp->dd_loc); +X} +! +echo done diff --git a/gnu/usr.bin/cvs/contrib/log.pl b/gnu/usr.bin/cvs/contrib/log.pl new file mode 100644 index 0000000..a6c75f6 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/log.pl @@ -0,0 +1,104 @@ +#!/usr/bin/perl + +# Modified by berliner@Sun.COM to add support for CVS 1.3 2/27/92 +# +# Date: Tue, 6 Aug 91 13:27 EDT +# From: samborn@sunrise.com (Kevin Samborn) +# +# I revised the perl script I sent you yesterday to use the info you +# send in on stdin. (I am appending the newer script to the end) +# +# now the output looks like this: +# +# ************************************** +# date: Tuesday, August 6, 1991 @ 13:17 +# author: samborn +# Update of /elmer/cvs/CVSROOT.adm +# In directory astro:/home/samborn/CVSROOT.adm +# +# Modified Files: +# test3 +# +# Added Files: +# test6 +# +# Removed Files: +# test4 +# +# Log Message: +# wow, what a test +# +# RCS: 1.4 /elmer/cvs/CVSROOT.adm/test3,v +# RCS: 1.1 /elmer/cvs/CVSROOT.adm/test6,v +# RCS: 1.1 /elmer/cvs/CVSROOT.adm/Attic/test4,v +# + +# +# turn off setgid +# +$) = $(; + +# +# parse command line arguments +# +@files = split(/ /,$ARGV[0]); +$logfile = $ARGV[1]; +$cvsroot = $ENV{'CVSROOT'}; + +# +# Some date and time arrays +# +@mos = (January,February,March,April,May,June,July,August,September, + October,November,December); +@days = (Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday); +($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; + +# +# get login name +# +$login = getlogin || (getpwuid($<))[0] || "nobody"; + +# +# open log file for appending +# +if ((open(OUT, ">>" . $logfile)) != 1) { + die "Could not open logfile " . $logfile . "\n"; +} + +# +# Header +# +print OUT "\n"; +print OUT "**************************************\n"; +print OUT "date: " . $days[$wday] . ", " . $mos[$mon] . " " . $mday . ", 19" . $year . + " @ " . $hour . ":" . sprintf("%02d", $min) . "\n"; +print OUT "author: " . $login . "\n"; + +# +#print the stuff on stdin to the logfile +# +open(IN, "-"); +while(<IN>) { + print OUT $_; +} +close(IN); + +print OUT "\n"; + +# +# after log information, do an 'cvs -Qn status' on each file in the arguments. +# +for $file (@files[1..$#files]) { + if ($file eq "-") { + last; + } + open(RCS,"-|") || exec 'cvs', '-Qn', 'status', $file; + while (<RCS>) { + if (substr($_, 0, 7) eq " RCS") { + print OUT; + } + } + close (RCS); +} + +close (OUT); diff --git a/gnu/usr.bin/cvs/contrib/log_accum.pl b/gnu/usr.bin/cvs/contrib/log_accum.pl new file mode 100644 index 0000000..798e25f --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/log_accum.pl @@ -0,0 +1,331 @@ +#!/usr/local/bin/perl -w +# +# Perl filter to handle the log messages from the checkin of files in +# a directory. This script will group the lists of files by log +# message, and mail a single consolidated log message at the end of +# the commit. +# +# This file assumes a pre-commit checking program that leaves the +# names of the first and last commit directories in a temporary file. +# +# Contributed by David Hampton <hampton@cisco.com> +# + +############################################################ +# +# Configurable options +# +############################################################ +# +# Do cisco Systems, Inc. specific nonsense. +# +$cisco_systems = 1; + +# +# Recipient of all mail messages +# +$mailto = "sw-notification@cisco.com"; + +############################################################ +# +# Constants +# +############################################################ +$STATE_NONE = 0; +$STATE_CHANGED = 1; +$STATE_ADDED = 2; +$STATE_REMOVED = 3; +$STATE_LOG = 4; + +$LAST_FILE = "/tmp/#cvs.lastdir"; +$CHANGED_FILE = "/tmp/#cvs.files.changed"; +$ADDED_FILE = "/tmp/#cvs.files.added"; +$REMOVED_FILE = "/tmp/#cvs.files.removed"; +$LOG_FILE = "/tmp/#cvs.files.log"; +$FILE_PREFIX = "#cvs.files"; + +$VERSION_FILE = "version"; +$TRUNKREV_FILE = "TrunkRev"; +$CHANGES_FILE = "Changes"; +$CHANGES_TEMP = "Changes.tmp"; + +############################################################ +# +# Subroutines +# +############################################################ + +sub format_names { + local($dir, @files) = @_; + local(@lines); + $lines[0] = sprintf(" %-08s", $dir); + foreach $file (@files) { + if (length($lines[$#lines]) + length($file) > 60) { + $lines[++$#lines] = sprintf(" %8s", " "); + } + $lines[$#lines] .= " ".$file; + } + @lines; +} + +sub cleanup_tmpfiles { + local($all) = @_; + local($wd, @files); + + $wd = `pwd`; + chdir("/tmp"); + opendir(DIR, "."); + if ($all == 1) { + push(@files, grep(/$id$/, readdir(DIR))); + } else { + push(@files, grep(/^$FILE_PREFIX.*$id$/, readdir(DIR))); + } + closedir(DIR); + foreach (@files) { + unlink $_; + } + chdir($wd); +} + +sub write_logfile { + local($filename, @lines) = @_; + open(FILE, ">$filename") || die ("Cannot open log file $filename.\n"); + print(FILE join("\n", @lines), "\n"); + close(FILE); +} + +sub append_to_file { + local($filename, $dir, @files) = @_; + if (@files) { + local(@lines) = &format_names($dir, @files); + open(FILE, ">>$filename") || die ("Cannot open file $filename.\n"); + print(FILE join("\n", @lines), "\n"); + close(FILE); + } +} + +sub write_line { + local($filename, $line) = @_; + open(FILE, ">$filename") || die("Cannot open file $filename.\n"); + print(FILE $line, "\n"); + close(FILE); +} + +sub read_line { + local($line); + local($filename) = @_; + open(FILE, "<$filename") || die("Cannot open file $filename.\n"); + $line = <FILE>; + close(FILE); + chop($line); + $line; +} + +sub read_file { + local(@text); + local($filename, $leader) = @_; + open(FILE, "<$filename") || return (); + while (<FILE>) { + chop; + push(@text, sprintf(" %-10s %s", $leader, $_)); + $leader = ""; + } + close(FILE); + @text; +} + +sub read_logfile { + local(@text); + local($filename, $leader) = @_; + open(FILE, "<$filename") || die ("Cannot open log file $filename.\n"); + while (<FILE>) { + chop; + push(@text, $leader.$_); + } + close(FILE); + @text; +} + +sub bump_version { + local($trunkrev, $editnum, $version); + + $trunkrev = &read_line("$ENV{'CVSROOT'}/$repository/$TRUNKREV_FILE"); + $editnum = &read_line("$ENV{'CVSROOT'}/$repository/$VERSION_FILE"); + &write_line("$ENV{'CVSROOT'}/$repository/$VERSION_FILE", $editnum+1); + $version = $trunkrev . "(" . $editnum . ")"; +} + +sub build_header { + local($version) = @_; + local($header); + local($sec,$min,$hour,$mday,$mon,$year) = localtime(time); + $header = sprintf("%-8s %s %02d/%02d/%02d %02d:%02d:%02d", + $login, $version, $year%100, $mon+1, $mday, + $hour, $min, $sec); +} + +sub do_changes_file { + local($changes, $tmpchanges); + local(@text) = @_; + + $changes = "$ENV{'CVSROOT'}/$repository/$CHANGES_FILE"; + $tmpchanges = "$ENV{'CVSROOT'}/$repository/$CHANGES_TEMP"; + if (rename($changes, $tmpchanges) != 1) { + die("Cannot rename $changes to $tmpchanges.\n"); + } + open(CHANGES, ">$changes") || die("Cannot open $changes.\n"); + open(TMPCHANGES, "<$tmpchanges") || die("Cannot open $tmpchanges.\n"); + print(CHANGES join("\n", @text), "\n\n"); + print(CHANGES <TMPCHANGES>); + close(CHANGES); + close(TMPCHANGES); + unlink($tmpchanges); +} + +sub mail_notification { + local($name, @text) = @_; + open(MAIL, "| mail -s \"Source Repository Modification\" $name"); + print(MAIL join("\n", @text)); + close(MAIL); +} + +############################################################# +# +# Main Body +# +############################################################ + +# +# Initialize basic variables +# +$id = getpgrp(); +$state = $STATE_NONE; +$login = getlogin || (getpwuid($<))[0] || die("Unknown user $<.\n"); +@files = split(' ', $ARGV[0]); +@path = split('/', $files[0]); +$repository = @path[0]; +if ($#path == 0) { + $dir = "."; +} else { + $dir = join('/', @path[1..$#path]); +} +#print("ARGV - ", join(":", @ARGV), "\n"); +#print("files - ", join(":", @files), "\n"); +#print("path - ", join(":", @path), "\n"); +#print("dir - ", $dir, "\n"); +#print("id - ", $id, "\n"); + +# +# Check for a new directory first. This will always appear as a +# single item in the argument list, and an empty log message. +# +if ($ARGV[0] =~ /New directory/) { + $version = &bump_version if ($cisco_systems != 0); + $header = &build_header($version); + @text = (); + push(@text, $header); + push(@text, ""); + push(@text, " ".$ARGV[0]); + &do_changes_file(@text) if ($cisco_systems != 0); + &mail_notification($mailto, @text); + exit 0; +} + +# +# Iterate over the body of the message collecting information. +# +while (<STDIN>) { + chop; # Drop the newline + if (/^Modified Files/) { $state = $STATE_CHANGED; next; } + if (/^Added Files/) { $state = $STATE_ADDED; next; } + if (/^Removed Files/) { $state = $STATE_REMOVED; next; } + if (/^Log Message/) { $state = $STATE_LOG; next; } + s/^[ \t\n]+//; # delete leading space + s/[ \t\n]+$//; # delete trailing space + + push (@changed_files, split) if ($state == $STATE_CHANGED); + push (@added_files, split) if ($state == $STATE_ADDED); + push (@removed_files, split) if ($state == $STATE_REMOVED); + push (@log_lines, $_) if ($state == $STATE_LOG); +} + +# +# Strip leading and trailing blank lines from the log message. Also +# compress multiple blank lines in the body of the message down to a +# single blank line. +# +while ($#log_lines > -1) { + last if ($log_lines[0] ne ""); + shift(@log_lines); +} +while ($#log_lines > -1) { + last if ($log_lines[$#log_lines] ne ""); + pop(@log_lines); +} +for ($i = $#log_lines; $i > 0; $i--) { + if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) { + splice(@log_lines, $i, 1); + } +} + +# +# Find the log file that matches this log message +# +for ($i = 0; ; $i++) { + last if (! -e "$LOG_FILE.$i.$id"); + @text = &read_logfile("$LOG_FILE.$i.$id", ""); + last if ($#text == -1); + last if (join(" ", @log_lines) eq join(" ", @text)); +} + +# +# Spit out the information gathered in this pass. +# +&write_logfile("$LOG_FILE.$i.$id", @log_lines); +&append_to_file("$ADDED_FILE.$i.$id", $dir, @added_files); +&append_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files); +&append_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files); + +# +# Check whether this is the last directory. If not, quit. +# +$_ = &read_line("$LAST_FILE.$id"); +exit 0 if (! grep(/$files[0]$/, $_)); + +# +# This is it. The commits are all finished. Lump everything together +# into a single message, fire a copy off to the mailing list, and drop +# it on the end of the Changes file. +# +# Get the full version number +# +$version = &bump_version if ($cisco_systems != 0); +$header = &build_header($version); + +# +# Produce the final compilation of the log messages +# +@text = (); +push(@text, $header); +push(@text, ""); +for ($i = 0; ; $i++) { + last if (! -e "$LOG_FILE.$i.$id"); + push(@text, &read_file("$CHANGED_FILE.$i.$id", "Modified:")); + push(@text, &read_file("$ADDED_FILE.$i.$id", "Added:")); + push(@text, &read_file("$REMOVED_FILE.$i.$id", "Removed:")); + push(@text, " Log:"); + push(@text, &read_logfile("$LOG_FILE.$i.$id", " ")); + push(@text, ""); +} +if ($cisco_systems != 0) { + @ddts = grep(/^CSCdi/, split(' ', join(" ", @text))); + $text[0] .= " " . join(" ", @ddts); +} +# +# Put the log message at the beginning of the Changes file and mail +# out the notification. +# +&do_changes_file(@text) if ($cisco_systems != 0); +&mail_notification($mailto, @text); +&cleanup_tmpfiles(1); +exit 0; diff --git a/gnu/usr.bin/cvs/contrib/mfpipe.pl b/gnu/usr.bin/cvs/contrib/mfpipe.pl new file mode 100644 index 0000000..74cc5e1 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/mfpipe.pl @@ -0,0 +1,87 @@ +#!/usr/bin/perl +# +# From: clyne@niwot.scd.ucar.EDU (John Clyne) +# Date: Fri, 28 Feb 92 09:54:21 MST +# +# BTW, i wrote a perl script that is similar to 'nfpipe' except that in +# addition to logging to a file it provides a command line option for mailing +# change notices to a group of users. Obviously you probably wouldn't want +# to mail every change. But there may be certain directories that are commonly +# accessed by a group of users who would benefit from an email notice. +# Especially if they regularly beat on the same directory. Anyway if you +# think anyone would be interested here it is. +# +# mfpipe.pl,v 1.1 1992/03/02 01:22:41 berliner Exp +# +# +# File: mfpipe +# +# Author: John Clyne +# National Center for Atmospheric Research +# PO 3000, Boulder, Colorado +# +# Date: Wed Feb 26 18:34:53 MST 1992 +# +# Description: Tee standard input to mail a list of users and to +# a file. Used by CVS logging. +# +# Usage: mfpipe [-f file] [user@host...] +# +# Environment: CVSROOT +# Path to CVS root. +# +# Files: +# +# +# Options: -f file +# Capture output to 'file' +# + +$header = "Log Message:\n"; + +$mailcmd = "| mail -s 'CVS update notice'"; +$whoami = `whoami`; +chop $whoami; +$date = `date`; +chop $date; + +$cvsroot = $ENV{'CVSROOT'}; + +while (@ARGV) { + $arg = shift @ARGV; + + if ($arg eq '-f') { + $file = shift @ARGV; + } + else { + $users = "$users $arg"; + } +} + +if ($users) { + $mailcmd = "$mailcmd $users"; + open(MAIL, $mailcmd) || die "Execing $mail: $!\n"; +} + +if ($file) { + $logfile = "$cvsroot/LOG/$file"; + open(FILE, ">> $logfile") || die "Opening $logfile: $!\n"; +} + +print FILE "$whoami $date--------BEGIN LOG ENTRY-------------\n" if ($logfile); + +while (<>) { + print FILE $log if ($log && $logfile); + + print FILE $_ if ($logfile); + print MAIL $_ if ($users); + + $log = "log: " if ($_ eq $header); +} + +close FILE; +die "Write failed" if $?; +close MAIL; +die "Mail failed" if $?; + +exit 0; diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/ChangeLog b/gnu/usr.bin/cvs/contrib/pcl-cvs/ChangeLog new file mode 100644 index 0000000..fab9a7d --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/ChangeLog @@ -0,0 +1,119 @@ +Tue Apr 7 09:11:27 1992 Per Cederqvist (ceder@leopold) + + * Release 1.02. + + * pcl-cvs.el (cvs-diff-backup, cvs-edit-done, cvs-status): Call + save-some-buffers. + + * pcl-cvs.el (cvs-diff-backup-extractor): Fixed syntax error. + + * Makefile, README, compile-all.el, dist-makefile, pcl-cvs.el, + pcl-cvs.texinfo (XXRELEASEXX): A magic string that is substituted + for the current release number when a distribution is made. + (Release 1.01 says that it is release 1.00). + + * pcl-cvs.el (cvs-find-file): Added missing pair of parenthesis. + +Mon Mar 30 14:25:26 1992 Per Cederqvist (ceder@leopold) + + * Release 1.01. + + * pcl-cvs.el (cvs-parse-buffer): The message when waiting for a + lock has been changed. + +Sun Mar 29 05:29:57 1992 Per Cederqvist (ceder@leopold) + + * Release 1.00. + + * pcl-cvs.el (cvs-do-update, cvs-sentinel, cvs-parse-buffer): + Major rewrite of buffer and window selection and handling. + The *cvs* buffer is now killed whenever a new "cvs update" is + initiated. The -update buffer is replaced with the *cvs* + buffer when the update is completed. + +Sat Mar 28 21:03:05 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-delete-unused-temporary-buffers): Fixed it. + + * pcl-cvs.el (cvs-auto-remove-handled): New variable. + * pcl-cvs.el (cvs-edit-done): Use it. + * pcl-cvs.texinfo (Customization, Removing handled entries): + Document it. + + * pcl-cvs.el (cvs-mode): Turn of the undo feature. It really + isn't useful in a cookie buffer... + + * pcl-cvs.el (cvs-edit-done): Committing a file now looks more + like diffing a file. The window handling is better. + * pcl-cvs.el (cvs-use-temp-buffer): The &optional switch is no + longer needed. + +Mon Mar 23 00:20:33 1992 Per Cederqvist (ceder@robin) + + * Release 0.97. + + * pcl-cvs.el (default-directory): Make sure it always ends in a + slash. fileinfo->dir does NOT end in a slash, and I had forgotten + to call file-name-as-directory in various places. + + * pcl-cvs.el (cvs-diff-backup-extractor): Signal an error if a + fileinfo without backup file is given. + + * pcl-cvs.el (cvs-mode): Added documentation. + + * pcl-cvs.el (cvs-execute-list): Fix the order of files in the + same directory. + + * pcl-cvs.el (cvs-log-flags, cvs-status-flags): New variables. + * pcl-cvs.el (cvs-log, cvs-status): Use them. + * pcl-cvs.texinfo (Customization): Document them. + + * pcl-cvs.el (cvs-diff-backup): Filter non-backup-diffable files + at an earlier stage, like cvs-commit does. + + * pcl-cvs.el (cvs-diff-flags): New variable. + * pcl-cvs.el (cvs-diff-backup): Use it. + * pcl-cvs.texinfo (Customization): Document it. + + * pcl-cvs.el (cvs-execute-single-file-list): Remove &rest before + last argument. No callers needed updating. + + * pcl-cvs.el (cvs-execute-list): Remove the &rest before the last + argument (constant-args). Update all callers of cvs-execute-list + to use the new calling convention. + * pcl-cvs.el (cvs-cvs-diff-flags): Now a list of strings instead + of a string. + * pcl-cvs.texinfo (Customization): Document the change to + cvs-cvs-diff-flags. + + * Release 0.96. + + * pcl-cvs.el (cvs-cvs-diff-flags): New variable. + * pcl-cvs.el (cvs-diff-cvs): Use it. + * pcl-cvs.texinfo (Customization, Viewing differences): Document it. + + * pcl-cvs.el (cvs-use-temp-buffe): Don't switch to the temporary + buffer. Use display-buffer and set-buffer instead. This way + cvs-log, cvs-status, cvs-diff-cvs and friends don't select the + temporary buffer. The cursor will remain in the *cvs* buffer. + +Sun Mar 22 21:50:18 1992 Per Cederqvist (ceder@robin) + + * pcl-cvs.el (cvs-find-file, cvs-find-file-other-window): Don't + prompt when reading in a directory in dired. + + * Makefile (pcl-cvs-$(VER)): Include pcl-cvs-startup.el in the + distribution. + + * dist-makefile (pcl-cvs.dvi): Don't fail even if texindex does + not exist. + + * pcl-cvs.texinfo (@setchapternewpage): Changed from 'off' to 'on'. + * pcl-cvs.texinfo (Variable index): Joined into function index. + * pcl-cvs.texinfo (Key index): add a description about the key. + * pcl-cvs.texinfo: Many other small changes. + +Wed Mar 18 01:58:38 1992 Per Cederqvist (ceder@leopold) + + * Use GNU General Public License version 2. + diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/INSTALL b/gnu/usr.bin/cvs/contrib/pcl-cvs/INSTALL new file mode 100644 index 0000000..8c89053 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/INSTALL @@ -0,0 +1,83 @@ +This text is copied from the TeXinfo manual for pcl-cvs. + +Installation of the pcl-cvs program +=================================== + + 1. Edit the file `Makefile' to reflect the situation at your site. + The only things you have to change is the definition of + `lispdir' and `infodir'. The elisp files will be copied to + `lispdir', and the info file to `infodir'. + + 2. Configure pcl-cvs.el + + There are a couple of paths that you have to check to make + sure that they match you system. They appear early in the file + pcl-cvs.el. + + *NOTE:* If your system is running emacs 18.57 or earlier + you MUST uncomment the line that says: + + (setq delete-exited-processes nil) + + Setting `delete-exited-processes' to `nil' works around a bug + in emacs that causes it to dump core. The bug was fixed in + emacs 18.58. + + 3. Type `make install' in the source directory. This will + byte-compile all `.el' files and copy both the `.el' and the + `.elc' into the directory you specified in step 1. + + If you don't want to install the `.el' files but only the + `.elc' files (the byte-compiled files), you can type ``make + install_elc'' instead of ``make install''. + + If you only want to create the compiled elisp files, but + don't want to install them, you can type `make elcfiles' + instead. This is what happens if you only type `make' without + parameters. + + 4. Edit the file `default.el' in your emacs lisp directory (usually + `/usr/gnu/emacs/lisp' or something similar) and enter the + contents of the file `pcl-cvs-startup.el' into it. It contains + a couple of `auto-load's that facilitates the use of pcl-cvs. + + + + +Installation of the on-line manual. +=================================== + + 1. Create the info file `pcl-cvs' from `pcl-cvs.texinfo' by typing + `make info'. If you don't have the program `makeinfo' you can + get it by anonymous ftp from e.g. `ftp.gnu.ai.mit.edu' as + `pub/gnu/texinfo-2.14.tar.Z' (there might be a newer version + there when you read this), or you could use the preformatted + info file `pcl-cvs.info' that is included in the distribution + (type `cp pcl-cvs.info pcl-cvs'). + + 2. Move the info file `pcl-cvs' to your standard info directory. + This might be called something like `/usr/gnu/emacs/info'. + + 3. Edit the file `dir' in the info directory and enter one line to + contain a pointer to the info file `pcl-cvs'. The line can, for + instance, look like this: + + * Pcl-cvs: (pcl-cvs). An Emacs front-end to CVS. + + + + +How to make typeset documentation from pcl-cvs.texinfo +====================================================== + + If you have TeX installed at your site, you can make a typeset +manual from `pcl-cvs.texinfo'. + + 1. Run TeX by typing ``make pcl-cvs.dvi''. You will not get the + indices unless you have the `texindex' program. + + 2. Convert the resulting device independent file `pcl-cvs.dvi' to a + form which your printer can output and print it. If you have a + postscript printer there is a program, `dvi2ps', which does. + There is also a program which comes together with TeX, `dvips', + which you can use. diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/Makefile b/gnu/usr.bin/cvs/contrib/pcl-cvs/Makefile new file mode 100644 index 0000000..f0ded69 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/Makefile @@ -0,0 +1,78 @@ +# Makefile,v 1.2 1992/04/07 20:49:07 berliner Exp +# Makefile for pcl-cvs release 1.02. +# Copyright (C) 1992 Per Cederqvist +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# This is the directory in which the ELFILES and ELCFILES will be +# installed. + +lispdir = /usr/local/lib/elisp + +# Where to install the info file. + +prefix=/usr/local +infodir = $(prefix)/info + +# +# The rest of this file should not need to be modified. +# + +# Just in case... +SHELL = /bin/sh + +ELFILES = pcl-cvs.el cookie.el elib-dll.el elib-node.el +ELCFILES = pcl-cvs.elc cookie.elc elib-dll.elc elib-node.elc +INFOFILES = pcl-cvs +TEXTMPS = pcl-cvs.aux pcl-cvs.log pcl-cvs.toc pcl-cvs.dvi pcl-cvs.cp \ + pcl-cvs.fn pcl-cvs.vr pcl-cvs.tp pcl-cvs.ky pcl-cvs.pg \ + pcl-cvs.cps pcl-cvs.fns pcl-cvs.kys pcl-cvs.pgs pcl-cvs.tps \ + pcl-cvs.vrs + +INSTALL = install +INSTALL_DATA = $(INSTALL) + +elcfiles: + emacs -batch -l ./compile-all.el -f compile-pcl-cvs + +all: elcfiles info + +# Don't install the info file yet, since it requires makeinfo +# version 2.something (and version 1.something is distributed with emacs). +# +# install: install_elc install_info +install: install_elc + for i in $(ELFILES); do $(INSTALL_DATA) $$i $(lispdir)/$$i; done + +install_elc: elcfiles + for i in $(ELCFILES); do $(INSTALL_DATA) $$i $(lispdir)/$$i; done + +install_info: pcl-cvs + $(INSTALL_DATA) pcl-cvs $(infodir)/pcl-cvs + +info pcl-cvs: pcl-cvs.texinfo + makeinfo +fill-column=70 pcl-cvs.texinfo + +pcl-cvs.dvi: pcl-cvs.texinfo + tex pcl-cvs.texinfo + -texindex pcl-cvs.cp pcl-cvs.fn pcl-cvs.vr pcl-cvs.tp pcl-cvs.ky \ + pcl-cvs.pg + tex pcl-cvs.texinfo + +mostlyclean clean realclean: + rm -f *~ core $(ELCFILES) $(INFOFILES) $(TEXTMPS) + +tags TAGS: + etags *.el diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/README b/gnu/usr.bin/cvs/contrib/pcl-cvs/README new file mode 100644 index 0000000..6f0a5fe --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/README @@ -0,0 +1,14 @@ +README,v 1.2 1992/04/07 20:49:09 berliner Exp + +This is the readme file for pcl-cvs, release 1.02. + +Pcl-cvs is a front-end to CVS version 1.3. It integrates the most +frequently used CVS commands into emacs. + +There is some configuration that needs to be done in pcl-cvs.el to get +it to work. See the instructions in file INSTALL. + +Full documentation is in pcl-cvs.texinfo. Since it requires makeinfo +2.14 a preformatted info file is also included (pcl-cvs.info). + + ceder@lysator.liu.se diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el new file mode 100644 index 0000000..74f1bca --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/compile-all.el @@ -0,0 +1,52 @@ +;;;; compile-all.el,v 1.2 1992/04/07 20:49:10 berliner Exp +;;;; This file byte-compiles all .el files in pcl-cvs release 1.02. +;;;; +;;;; Copyright (C) 1991 Inge Wallin +;;;; +;;;; This file is part of the GNU Emacs lisp library, Elib. +;;;; +;;;; GNU Elib is free software; you can redistribute it and/or modify +;;;; it under the terms of the GNU General Public License as published by +;;;; the Free Software Foundation; either version 1, or (at your option) +;;;; any later version. +;;;; +;;;; GNU Elib is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with GNU Emacs; see the file COPYING. If not, write to +;;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;;;; + + +(setq elib-files '("elib-node" + "elib-dll" + "cookie" + "pcl-cvs")) + + +(defun compile-file-if-necessary (file) + "Compile the Elib file FILE if necessary. + +This is done if FILE.el is newer than FILE.elc or if FILE.elc doesn't exist." + (let ((el-name (concat file ".el")) + (elc-name (concat file ".elc"))) + (if (or (not (file-exists-p elc-name)) + (file-newer-than-file-p el-name elc-name)) + (progn + (message (format "Byte-compiling %s..." el-name)) + (byte-compile-file el-name))))) + + +(defun compile-pcl-cvs () + "Byte-compile all uncompiled files of elib. +Be sure to have . in load-path since a number of files in elib +depend on other files and we always want the newer one even if +a previous version of elib exists." + + (interactive) + (setq load-path (append '(".") load-path)) + (mapcar (function compile-file-if-necessary) + elib-files)) diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/cookie.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/cookie.el new file mode 100644 index 0000000..8bd4bdf --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/cookie.el @@ -0,0 +1,884 @@ +;;; cookie.el,v 1.2 1992/04/07 20:49:12 berliner Exp +;;; cookie.el -- Utility to display cookies in buffers +;;; Copyright (C) 1991, 1992 Per Cederqvist +;;; +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2 of the License, or +;;; (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;;; TO-DO: Byt namn! tin -> wrapper (eller n}got b{ttre). + +;;; Note that this file is still under development. Comments, +;;; enhancements and bug fixes are welcome. +;;; Send them to ceder@lysator.liu.se. + +(defun impl nil (error "Not yet implemented!")) + +;;; Cookie is a package that imlements a connection between an +;;; elib-dll and the contents of a buffer. Possible uses are dired +;;; (have all files in a list, and show them), buffer-list, +;;; kom-prioritize (in the LysKOM elisp client) and others. pcl-cvs.el +;;; uses cookie.el. +;;; +;;; A cookie buffer contains a header, any number of cookies, and a +;;; footer. The header and footer are constant strings that are given +;;; to cookie-create when the buffer is placed under cookie. Each cookie +;;; is displayed in the buffer by calling a user-supplied function +;;; that takes a cookie and returns a string. The string may be +;;; empty, or contain any number of lines. An extra newline is always +;;; appended unless the string is empty. +;;; +;;; Cookie does not affect the mode of the buffer in any way. It +;;; merely makes it easy to connect an underlying data representation +;;; to the buffer contents. +;;; +;;; The cookie-node data type: +;;; start-marker +;;; ;; end-marker This field is no longer present. +;;; cookie The user-supplied element. +;;; +;;; A dll of cookie-nodes are held in the buffer local variable +;;; cake-tin. +;;; +;;; A tin is an object that contains one cookie. You can get the next +;;; and previous tin. +;;; + +(require 'elib-dll) +(provide 'cookie) + +(defvar cookies nil + "A doubly linked list that contains the underlying data representation +for the contents of a cookie buffer. The package elib-dll is used to +manipulate this list.") + +(defvar cookie-pretty-printer nil + "The function that is used to pretty-print a cookie in this buffer.") + +(defvar cookie-header nil + "The tin that holds the header cookie.") + +(defvar cookie-footer nil + "The tin that holds the footer cookie.") + +(defvar cookie-last-tin nil + "The tin the cursor was positioned at, the last time the cookie +package checked the cursor position. Buffer local in all buffers +the cookie package works on. You may set this if your package +thinks it knows where the cursor will be the next time this +package is called. It can speed things up. + +It must never be set to a tin that has been deleted.") + +;;; ================================================================ +;;; Internal functions for use in the cookie package + +(put 'cookie-set-buffer 'lisp-indent-hook 1) + +(defmacro cookie-set-buffer (buffer &rest forms) + + ;; Execute FORMS with BUFFER selected as current buffer. + ;; Return value of last form in FORMS. INTERNAL USE ONLY. + + (let ((old-buffer (make-symbol "old-buffer"))) + (` (let (((, old-buffer) (current-buffer))) + (set-buffer (get-buffer-create (, buffer))) + (unwind-protect + (progn (,@ forms)) + (set-buffer (, old-buffer))))))) + + +(defmacro cookie-filter-hf (tin) + + ;; Evaluate TIN once and return it. BUT if it is + ;; equal to cookie-header or cookie-footer return nil instead. + ;; INTERNAL USE ONLY. + + (let ((tempvar (make-symbol "tin"))) + (` (let (((, tempvar) (, tin))) + (if (or (eq (, tempvar) cookie-header) + (eq (, tempvar) cookie-footer)) + nil + (, tempvar)))))) + + +;;; cookie-tin +;;; Constructor: + +(defun cookie-create-tin (start-marker + cookie) + ;; Create a tin. INTERNAL USE ONLY. + (cons 'COOKIE-TIN (vector start-marker nil cookie))) + + +;;; Selectors: + +(defun cookie-tin-start-marker (cookie-tin) + ;; Get start-marker from cookie-tin. INTERNAL USE ONLY. + (elt (cdr cookie-tin) 0)) + +;(defun cookie-tin-end-marker (cookie-tin) +; ;;Get end-marker from cookie-tin. INTERNAL USE ONLY. +; (elt (cdr cookie-tin) 1)) + +(defun cookie-tin-cookie-safe (cookie-tin) + ;; Get cookie from cookie-tin. INTERNAL USE ONLY. + ;; Returns nil if given nil as input. + ;; This is the same as cookie-tin-cookie in version 18.57 + ;; of emacs, but elt should signal an error when given nil + ;; as input (according to the info files). + (elt (cdr cookie-tin) 2)) + +(defun cookie-tin-cookie (cookie-tin) + ;; Get cookie from cookie-tin. INTERNAL USE ONLY. + (elt (cdr cookie-tin) 2)) + + +;;; Modifiers: + +(defun set-cookie-tin-start-marker (cookie-tin newval) + ;; Set start-marker in cookie-tin to NEWVAL. INTERNAL USE ONLY. + (aset (cdr cookie-tin) 0 newval)) + +;(defun set-cookie-tin-end-marker (cookie-tin newval) +; ;; Set end-marker in cookie-tin to NEWVAL. INTERNAL USE ONLY. +; (aset (cdr cookie-tin) 1 newval)) + +(defun set-cookie-tin-cookie (cookie-tin newval) + ;; Set cookie in cookie-tin to NEWVAL. INTERNAL USE ONLY. + (aset (cdr cookie-tin) 2 newval)) + + + +;;; Predicate: + +(defun cookie-tin-p (object) + ;; Return t if OBJECT is a tin. INTERNAL USE ONLY. + (eq (car-safe object) 'COOKIE-TIN)) + +;;; end of cookie-tin data type. + + +(defun cookie-create-tin-and-insert (cookie string pos) + ;; Insert STRING at POS in current buffer. Remember start + ;; position. Create a tin containing them and the COOKIE. + ;; INTERNAL USE ONLY. + + (save-excursion + (goto-char pos) + ;; Remember the position as a number so that it doesn't move + ;; when we insert the string. + (let ((start (if (markerp pos) + (marker-position pos) + pos))) + ;; Use insert-before-markers so that the marker for the + ;; next cookie is updated. + (insert-before-markers string) + (insert-before-markers ?\n) + (cookie-create-tin (copy-marker start) cookie)))) + + +(defun cookie-delete-tin-internal (tin) + ;; Delete a cookie from the buffer. INTERNAL USE ONLY. + ;; Can not be used on the footer. + (delete-region (cookie-tin-start-marker (dll-element cookies tin)) + (cookie-tin-start-marker + (dll-element cookies + (dll-next cookies tin))))) + + + +(defun cookie-refresh-tin (tin) + ;; Redisplay the cookie represented by TIN. INTERNAL USE ONLY. + ;; Can not be used on the footer. + + (save-excursion + ;; First, remove the string: + (delete-region (cookie-tin-start-marker (dll-element cookies tin)) + (1- (marker-position + (cookie-tin-start-marker + (dll-element cookies + (dll-next cookies tin)))))) + + ;; Calculate and insert the string. + + (goto-char (cookie-tin-start-marker (dll-element cookies tin))) + (insert + (funcall cookie-pretty-printer + (cookie-tin-cookie (dll-element cookies tin)))))) + + +;;; ================================================================ +;;; The public members of the cookie package + + +(defun cookie-cookie (buffer tin) + "Get the cookie from a TIN. Args: BUFFER TIN." + (cookie-set-buffer buffer + (cookie-tin-cookie (dll-element cookies tin)))) + + + + +(defun cookie-create (buffer pretty-printer &optional header footer) + + "Start to use the cookie package in BUFFER. +BUFFER may be a buffer or a buffer name. It is created if it does not exist. +Beware that the entire contents of the buffer will be erased. +PRETTY-PRINTER is a function that takes one cookie and returns a string +to be displayed in the buffer. The string may be empty. If it is not +empty a newline will be added automatically. It may span several lines. +Optional third argument HEADER is a string that will always be present +at the top of the buffer. HEADER should end with a newline. Optionaly +fourth argument FOOTER is similar, and will always be inserted at the +bottom of the buffer." + + (cookie-set-buffer buffer + + (erase-buffer) + + (make-local-variable 'cookie-last-tin) + (make-local-variable 'cookie-pretty-printer) + (make-local-variable 'cookie-header) + (make-local-variable 'cookie-footer) + (make-local-variable 'cookies) + + (setq cookie-last-tin nil) + (setq cookie-pretty-printer pretty-printer) + (setq cookies (dll-create)) + + (dll-enter-first cookies + (cookie-create-tin-and-insert + header header 0)) + (setq cookie-header (dll-nth cookies 0)) + + (dll-enter-last cookies + (cookie-create-tin-and-insert + footer footer (point-max))) + (setq cookie-footer (dll-nth cookies -1)) + + (goto-char (point-min)) + (forward-line 1))) + + +(defun cookie-set-header (buffer header) + "Change the header. Args: BUFFER HEADER." + (impl)) + + +(defun cookie-set-footer (buffer header) + "Change the footer. Args: BUFFER FOOTER." + (impl)) + + + +(defun cookie-enter-first (buffer cookie) + "Enter a COOKIE first in BUFFER. +Args: BUFFER COOKIE." + + (cookie-set-buffer buffer + + ;; It is always safe to insert an element after the first element, + ;; because the header is always present. (dll-nth cookies 0) should + ;; never return nil. + + (dll-enter-after + cookies + (dll-nth cookies 0) + (cookie-create-tin-and-insert + cookie + (funcall cookie-pretty-printer cookie) + (cookie-tin-start-marker + (dll-element cookies (dll-nth cookies 1))))))) + + + +(defun cookie-enter-last (buffer cookie) + "Enter a COOKIE last in BUFFER. +Args: BUFFER COOKIE." + + (cookie-set-buffer buffer + + ;; Remember that the header and footer are always present. There + ;; is no need to check if (dll-nth cookies -2) returns nil. + + (dll-enter-before + cookies + (dll-nth cookies -1) + (cookie-create-tin-and-insert + cookie + (funcall cookie-pretty-printer cookie) + (cookie-tin-start-marker (dll-last cookies)))))) + + +(defun cookie-enter-after (buffer node cookie) + (impl)) + + +(defun cookie-enter-before (buffer node cookie) + (impl)) + + + +(defun cookie-next (buffer tin) + "Get the next tin. Args: BUFFER TIN. +Returns nil if TIN is nil or the last cookie." + (if tin + (cookie-set-buffer buffer + (cookie-filter-hf (dll-next cookies tin))))) + + + +(defun cookie-previous (buffer tin) + "Get the previous tin. Args: BUFFER TIN. +Returns nil if TIN is nil or the first cookie." + (if tin + (cookie-set-buffer buffer + (cookie-filter-hf (dll-previous cookies tin))))) + + +(defun cookie-nth (buffer n) + + "Return the Nth tin. Args: BUFFER N. +N counts from zero. Nil is returned if there is less than N cookies. +If N is negative, return the -(N+1)th last element. +Thus, (cookie-nth dll 0) returns the first node, +and (cookie-nth dll -1) returns the last node. + +Use cookie-cookie to extract the cookie from the tin." + + (cookie-set-buffer buffer + + ;; Skip the header (or footer, if n is negative). + (if (< n 0) + (setq n (1- n)) + (setq n (1+ n))) + + (cookie-filter-hf (dll-nth cookies n)))) + + + +(defun cookie-delete (buffer tin) + "Delete a cookie. Args: BUFFER TIN." + + (cookie-set-buffer buffer + (if (eq cookie-last-tin tin) + (setq cookie-last-tin nil)) + + (cookie-delete-tin-internal tin) + (dll-delete cookies tin))) + + + +(defun cookie-delete-first (buffer) + "Delete first cookie and return it. Args: BUFFER. +Returns nil if there is no cookie left." + + (cookie-set-buffer buffer + + ;; We have to check that we do not try to delete the footer. + + (let ((tin (dll-nth cookies 1))) ;Skip the header. + (if (eq tin cookie-footer) + nil + (cookie-delete-tin-internal tin) + (cookie-tin-cookie (dll-delete cookies tin)))))) + + + +(defun cookie-delete-last (buffer) + "Delete last cookie and return it. Args: BUFFER. +Returns nil if there is no cookie left." + + (cookie-set-buffer buffer + + ;; We have to check that we do not try to delete the header. + + (let ((tin (dll-nth cookies -2))) ;Skip the footer. + (if (eq tin cookie-header) + nil + (cookie-delete-tin-internal tin) + (cookie-tin-cookie (dll-delete cookies tin)))))) + + + +(defun cookie-first (buffer) + + "Return the first cookie in BUFFER. The cookie is not removed." + + (cookie-set-buffer buffer + (let ((tin (cookie-filter-hf (dll-nth cookies -1)))) + (if tin + (cookie-tin-cookie-safe + (dll-element cookies tin)))))) + + +(defun cookie-last (buffer) + + "Return the last cookie in BUFFER. The cookie is not removed." + + (cookie-set-buffer buffer + (let ((tin (cookie-filter-hf (dll-nth cookies -2)))) + (if tin + (cookie-tin-cookie-safe + (dll-element cookies tin)))))) + + +(defun cookie-empty (buffer) + + "Return true if there are no cookies in BUFFER." + + (cookie-set-buffer buffer + (eq (dll-nth cookies 1) cookie-footer))) + + +(defun cookie-length (buffer) + + "Return number of cookies in BUFFER." + + ;; Don't count the footer and header. + + (cookie-set-buffer buffer + (- (dll-length cookies) 2))) + + +(defun cookie-all (buffer) + + "Return a list of all cookies in BUFFER." + + (cookie-set-buffer buffer + (let (result + (tin (dll-nth cookies -2))) + (while (not (eq tin cookie-header)) + (setq result (cons (cookie-tin-cookie (dll-element cookies tin)) + result)) + (setq tin (dll-previous cookies tin))) + result))) + +(defun cookie-clear (buffer) + + "Remove all cookies in buffer." + + (cookie-set-buffer buffer + (cookie-create buffer cookie-pretty-printer + (cookie-tin-cookie (dll-element cookies cookie-header)) + (cookie-tin-cookie (dll-element cookies cookie-footer))))) + + + +(defun cookie-map (map-function buffer &rest map-args) + + "Apply MAP-FUNCTION to all cookies in BUFFER. +MAP-FUNCTION is applied to the first element first. +If MAP-FUNCTION returns non-nil the cookie will be refreshed. + +Note that BUFFER will be current buffer when MAP-FUNCTION is called. + +If more than two arguments are given to cookie-map, remaining +arguments will be passed to MAP-FUNCTION." + + (cookie-set-buffer buffer + (let ((tin (dll-nth cookies 1)) + result) + + (while (not (eq tin cookie-footer)) + + (if (apply map-function + (cookie-tin-cookie (dll-element cookies tin)) + map-args) + (cookie-refresh-tin tin)) + + (setq tin (dll-next cookies tin)))))) + + + +(defun cookie-map-reverse (map-function buffer &rest map-args) + + "Apply MAP-FUNCTION to all cookies in BUFFER. +MAP-FUNCTION is applied to the last cookie first. +If MAP-FUNCTION returns non-nil the cookie will be refreshed. + +Note that BUFFER will be current buffer when MAP-FUNCTION is called. + +If more than two arguments are given to cookie-map, remaining +arguments will be passed to MAP-FUNCTION." + + (cookie-set-buffer buffer + (let ((tin (dll-nth cookies -2)) + result) + + (while (not (eq tin cookie-header)) + + (if (apply map-function + (cookie-tin-cookie (dll-element cookies tin)) + map-args) + (cookie-refresh-tin tin)) + + (setq tin (dll-previous cookies tin)))))) + + + +(defun cookie-enter-cookies (buffer cookie-list) + + "Insert all cookies in the list COOKIE-LIST last in BUFFER. +Args: BUFFER COOKIE-LIST." + + (while cookie-list + (cookie-enter-last buffer (car cookie-list)) + (setq cookie-list (cdr cookie-list)))) + + +(defun cookie-filter (buffer predicate) + + "Remove all cookies in BUFFER for which PREDICATE returns nil. +Note that BUFFER will be current-buffer when PREDICATE is called. + +The PREDICATE is called with one argument, the cookie." + + (cookie-set-buffer buffer + (let ((tin (dll-nth cookies 1)) + next) + (while (not (eq tin cookie-footer)) + (setq next (dll-next cookies tin)) + (if (funcall predicate (cookie-tin-cookie (dll-element cookies tin))) + nil + (cookie-delete-tin-internal tin) + (dll-delete cookies tin)) + (setq tin next))))) + + +(defun cookie-filter-tins (buffer predicate) + + "Remove all cookies in BUFFER for which PREDICATE returns nil. +Note that BUFFER will be current-buffer when PREDICATE is called. + +The PREDICATE is called with one argument, the tin." + + (cookie-set-buffer buffer + (let ((tin (dll-nth cookies 1)) + next) + (while (not (eq tin cookie-footer)) + (setq next (dll-next cookies tin)) + (if (funcall predicate tin) + nil + (cookie-delete-tin-internal tin) + (dll-delete cookies tin)) + (setq tin next))))) + +(defun cookie-pos-before-middle-p (pos tin1 tin2) + + "Return true if POS is in the first half of the region defined by TIN1 and +TIN2." + + (< pos (/ (+ (cookie-tin-start-marker (dll-element cookeis tin1)) + (cookie-tin-start-marker (dll-element cookeis tin2))) + 2))) + + +(defun cookie-get-selection (buffer pos &optional guess force-guess) + + "Return the tin the POS is within. +Args: BUFFER POS &optional GUESS FORCE-GUESS. +GUESS should be a tin that it is likely that POS is near. If FORCE-GUESS +is non-nil GUESS is always used as a first guess, otherwise the first +guess is the first tin, last tin, or GUESS, whichever is nearest to +pos in the BUFFER. + +If pos points within the header, the first cookie is returned. +If pos points within the footer, the last cookie is returned. +Nil is returned if there is no cookie. + +It is often good to specify cookie-last-tin as GUESS, but remember +that cookie-last-tin is buffer local in all buffers that cookie +operates on." + + (cookie-set-buffer buffer + + (cond + ; No cookies present? + ((eq (dll-nth cookies 1) (dll-nth cookies -1)) + nil) + + ; Before first cookie? + ((< pos (cookie-tin-start-marker + (dll-element cookies (dll-nth cookies 1)))) + (dll-nth cookies 1)) + + ; After last cookie? + ((>= pos (cookie-tin-start-marker (dll-last cookies))) + (dll-nth cookies -2)) + + ; We now now that pos is within a cookie. + (t + ; Make an educated guess about which of the three known + ; cookies (the first, the last, or GUESS) is nearest. + (setq + guess + (cond + (force-guess guess) + (guess + (cond + ;; Closest to first cookie? + ((cookie-pos-before-middle-p + pos guess + (dll-nth cookies 1)) + (dll-nth cookies 1)) + ;; Closest to GUESS? + ((cookie-pos-before-middle-p + pos guess + cookie-footer) + guess) + ;; Closest to last cookie. + (t (dll-previous cookies cookie-footer)))) + (t + ;; No guess given. + (cond + ;; First half? + ((cookie-pos-before-middle-p + pos (dll-nth cookies 1) + cookie-footer) + (dll-nth cookies 1)) + (t (dll-previous cookies cookie-footer)))))) + + ;; GUESS is now a "best guess". + + ;; Find the correct cookie. First determine in which direction + ;; it lies, and then move in that direction until it is found. + + (cond + ;; Is pos after the guess? + ((>= pos (cookie-tin-start-marker (dll-element cookiess guess))) + + ;; Loop until we are exactly one cookie too far down... + (while (>= pos (cookie-tin-start-marker (dll-element cookiess guess))) + (setq guess (dll-next cookies guess))) + + ;; ...and return the previous cookie. + (dll-previous cookies guess)) + + ;; Pos is before guess + (t + + (while (< pos (cookie-tin-start-marker (dll-element cookiess guess))) + (setq guess (dll-previous cookies guess))) + + guess)))))) + + +(defun cookie-start-marker (buffer tin) + + "Return start-position of a cookie in BUFFER. +Args: BUFFER TIN. +The marker that is returned should not be modified in any way, +and is only valid until the contents of the cookie buffer changes." + + (cookie-set-buffer buffer + (cookie-tin-start-marker (dll-element cookies tin)))) + + +(defun cookie-end-marker (buffer tin) + + "Return end-position of a cookie in BUFFER. +Args: BUFFER TIN. +The marker that is returned should not be modified in any way, +and is only valid until the contents of the cookie buffer changes." + + (cookie-set-buffer buffer + (cookie-tin-start-marker + (dll-element cookies (dll-next cookies tin))))) + + + +(defun cookie-refresh (buffer) + + "Refresh all cookies in BUFFER. +Cookie-pretty-printer will be called for all cookies and the new result +displayed. + +See also cookie-invalidate-tins." + + (cookie-set-buffer buffer + + (erase-buffer) + + (set-marker (cookie-tin-start-marker (dll-element cookies cookie-header)) + (point) buffer) + (insert (cookie-tin-cookie (dll-element cookies cookie-header))) + (insert "\n") + + (let ((tin (dll-nth cookies 1))) + (while (not (eq tin cookie-footer)) + + (set-marker (cookie-tin-start-marker (dll-element cookies tin)) + (point) buffer) + (insert + (funcall cookie-pretty-printer + (cookie-tin-cookie (dll-element cookies tin)))) + (insert "\n") + (setq tin (dll-next cookies tin)))) + + (set-marker (cookie-tin-start-marker (dll-element cookies cookie-footer)) + (point) buffer) + (insert (cookie-tin-cookie (dll-element cookies cookie-footer))) + (insert "\n"))) + + +(defun cookie-invalidate-tins (buffer &rest tins) + + "Refresh some cookies. +Args: BUFFER &rest TINS." + + (cookie-set-buffer buffer + + (while tins + (cookie-refresh-tin (car tins)) + (setq tins (cdr tins))))) + + +;;; Cookie movement commands. + +(defun cookie-set-goal-column (buffer goal) + "Set goal-column for BUFFER. +Args: BUFFER GOAL. +goal-column is made buffer-local." + (cookie-set-buffer buffer + (make-local-variable 'goal-column) + (setq goal-column goal))) + + +(defun cookie-previous-cookie (buffer pos arg) + "Move point to the ARGth previous cookie. +Don't move if we are at the first cookie. +ARG is the prefix argument when called interactively. +Args: BUFFER POS ARG. +Sets cookie-last-tin to the cookie we move to." + + (interactive (list (current-buffer) (point) + (prefix-numeric-value current-prefix-arg))) + + (cookie-set-buffer buffer + (setq cookie-last-tin + (cookie-get-selection buffer pos cookie-last-tin)) + + (while (and cookie-last-tin (> arg 0)) + (setq arg (1- arg)) + (setq cookie-last-tin + (dll-previous cookies cookie-last-tin))) + + ;; Never step above the first cookie. + + (if (null (cookie-filter-hf cookie-last-tin)) + (setq cookie-last-tin (dll-nth cookies 1))) + + (goto-char + (cookie-tin-start-marker + (dll-element cookies cookie-last-tin))) + + (if goal-column + (move-to-column goal-column)))) + + + +(defun cookie-next-cookie (buffer pos arg) + "Move point to the ARGth next cookie. +Don't move if we are at the last cookie. +ARG is the prefix argument when called interactively. +Args: BUFFER POS ARG. +Sets cookie-last-tin to the cookie we move to." + + (interactive (list (current-buffer) (point) + (prefix-numeric-value current-prefix-arg))) + + (cookie-set-buffer buffer + (setq cookie-last-tin + (cookie-get-selection buffer pos cookie-last-tin)) + + (while (and cookie-last-tin (> arg 0)) + (setq arg (1- arg)) + (setq cookie-last-tin + (dll-next cookies cookie-last-tin))) + + (if (null (cookie-filter-hf cookie-last-tin)) + (setq cookie-last-tin (dll-nth cookies -2))) + + (goto-char + (cookie-tin-start-marker + (dll-element cookies cookie-last-tin))) + + (if goal-column + (move-to-column goal-column)))) + + +(defun cookie-collect-tins (buffer predicate &rest predicate-args) + + "Return a list of all tins in BUFFER whose cookie PREDICATE +returns true for. +PREDICATE is a function that takes a cookie as its argument. +The tins on the returned list will appear in the same order +as in the buffer. You should not rely on in which order PREDICATE +is called. Note that BUFFER is current-buffer when PREDICATE +is called. (If you call cookie-collect with another buffer set +as current-buffer and need to access buffer-local variables +from that buffer within PREDICATE you must send them via +PREDICATE-ARGS). + +If more than two arguments are given to cookie-collect the remaining +arguments will be passed to PREDICATE. + +Use cookie-cookie to get the cookie from the tin." + + (cookie-set-buffer buffer + (let ((tin (dll-nth cookies -2)) + result) + + (while (not (eq tin cookie-header)) + + (if (apply predicate + (cookie-tin-cookie (dll-element cookies tin)) + predicate-args) + (setq result (cons tin result))) + + (setq tin (dll-previous cookies tin))) + result))) + + +(defun cookie-collect-cookies (buffer predicate &rest predicate-args) + + "Return a list of all cookies in BUFFER that PREDICATE +returns true for. +PREDICATE is a function that takes a cookie as its argument. +The cookie on the returned list will appear in the same order +as in the buffer. You should not rely on in which order PREDICATE +is called. Note that BUFFER is current-buffer when PREDICATE +is called. (If you call cookie-collect with another buffer set +as current-buffer and need to access buffer-local variables +from that buffer within PREDICATE you must send them via +PREDICATE-ARGS). + +If more than two arguments are given to cookie-collect the remaining +arguments will be passed to PREDICATE." + + (cookie-set-buffer buffer + (let ((tin (dll-nth cookies -2)) + result) + + (while (not (eq tin cookie-header)) + + (if (apply predicate + (cookie-tin-cookie (dll-element cookies tin)) + predicate-args) + (setq result (cons (cookie-tin-cookie (dll-element cookies tin)) + result))) + + (setq tin (dll-previous cookies tin))) + result))) diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-dll-debug.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-dll-debug.el new file mode 100644 index 0000000..733ff86 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-dll-debug.el @@ -0,0 +1,298 @@ +;;; elib-dll-debug -- A slow implementation of elib-dll for debugging. +;;; elib-dll-debug.el,v 1.2 1992/04/07 20:49:13 berliner Exp +;;; Copyright (C) 1991,1992 Per Cederqvist +;;; +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2 of the License, or +;;; (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +;;; This is a plug-in replacement for elib-dll.el. It is dreadfully +;;; slow, but it facilitates debugging. Don't trust the comments in +;;; this file too much. +(provide 'elib-dll) + +;;; +;;; A doubly linked list consists of one cons cell which holds the tag +;;; 'DL-LIST in the car cell and the list in the cdr +;;; cell. The doubly linked list is implemented as a normal list. You +;;; should use elib-dll.el and not this package in debugged code. This +;;; package is not written for speed... +;;; + +;;; ================================================================ +;;; Internal functions for use in the doubly linked list package + +(defun dll-get-dummy-node (dll) + + ;; Return the dummy node. INTERNAL USE ONLY. + dll) + +(defun dll-list-nodes (dll) + + ;; Return a list of all nodes in DLL. INTERNAL USE ONLY. + + (cdr dll)) + +(defun dll-set-from-node-list (dll list) + + ;; Set the contents of DLL to the nodes in LIST. + ;; INTERNAL USE ONLY. + + (setcdr dll list)) + +(defun dll-get-node-before (dll node) + ;; Return the node in DLL that points to NODE. Use + ;; (dll-get-node-before some-list nil) to get the last node. + ;; INTERNAL USE ONLY. + (while (and dll (not (eq (cdr dll) node))) + (setq dll (cdr dll))) + (if (not dll) + (error "Node not on list")) + dll) + +(defmacro dll-insert-after (node element) + (let ((node-v (make-symbol "node")) + (element-v (make-symbol "element"))) + (` (let (((, node-v) (, node)) + ((, element-v) (, element))) + (setcdr (, node-v) (cons (, element-v) (cdr (, node-v)))))))) + +;;; =================================================================== +;;; The public functions which operate on doubly linked lists. + +(defmacro dll-element (dll node) + + "Get the element of a NODE in a doubly linked list DLL. +Args: DLL NODE." + + (` (car (, node)))) + + +(defun dll-create () + "Create an empty doubly linked list." + (cons 'DL-LIST nil)) + + +(defun dll-p (object) + "Return t if OBJECT is a doubly linked list, otherwise return nil." + (eq (car-safe object) 'DL-LIST)) + + +(defun dll-enter-first (dll element) + "Add an element first on a doubly linked list. +Args: DLL ELEMENT." + (setcdr dll (cons element (cdr dll)))) + + +(defun dll-enter-last (dll element) + "Add an element last on a doubly linked list. +Args: DLL ELEMENT." + (dll-insert-after (dll-get-node-before dll nil) element)) + + +(defun dll-enter-after (dll node element) + "In the doubly linked list DLL, insert a node containing ELEMENT after NODE. +Args: DLL NODE ELEMENT." + + (dll-get-node-before dll node) + (dll-insert-after node element)) + + +(defun dll-enter-before (dll node element) + "In the doubly linked list DLL, insert a node containing ELEMENT before NODE. +Args: DLL NODE ELEMENT." + + (dll-insert-after (dll-get-node-before dll node) element)) + + + +(defun dll-next (dll node) + "Return the node after NODE, or nil if NODE is the last node. +Args: DLL NODE." + + (dll-get-node-before dll node) + (cdr node)) + + +(defun dll-previous (dll node) + "Return the node before NODE, or nil if NODE is the first node. +Args: DLL NODE." + + (dll-get-node-before dll node)) + + +(defun dll-delete (dll node) + + "Delete NODE from the doubly linked list DLL. +Args: DLL NODE. Return the element of node." + + ;; This is a no-op when applied to the dummy node. This will return + ;; nil if applied to the dummy node since it always contains nil. + + (setcdr (dll-get-node-before dll node) (cdr node))) + + +(defun dll-delete-first (dll) + + "Delete the first NODE from the doubly linked list DLL. +Return the element. Args: DLL. Returns nil if the DLL was empty." + + ;; Relies on the fact that dll-delete does nothing and + ;; returns nil if given the dummy node. + + (setcdr dll (cdr (cdr dll)))) + + +(defun dll-delete-last (dll) + + "Delete the last NODE from the doubly linked list DLL. +Return the element. Args: DLL. Returns nil if the DLL was empty." + + ;; Relies on the fact that dll-delete does nothing and + ;; returns nil if given the dummy node. + + (setcdr dll (dll-get-node-before dll nil) nil)) + + +(defun dll-first (dll) + + "Return the first element on the doubly linked list DLL. +Return nil if the list is empty. The element is not removed." + + (car (cdr dll))) + + + + +(defun dll-last (dll) + + "Return the last element on the doubly linked list DLL. +Return nil if the list is empty. The element is not removed." + + (car (dll-get-node-before dll nil))) + + + +(defun dll-nth (dll n) + + "Return the Nth node from the doubly linked list DLL. + Args: DLL N +N counts from zero. If DLL is not that long, nil is returned. +If N is negative, return the -(N+1)th last element. +Thus, (dll-nth dll 0) returns the first node, +and (dll-nth dll -1) returns the last node." + + ;; Branch 0 ("follow left pointer") is used when n is negative. + ;; Branch 1 ("follow right pointer") is used otherwise. + + (if (>= n 0) + (nthcdr n (cdr dll)) + (unwind-protect + (progn (setcdr dll (nreverse (cdr dll))) + (nthcdr (- n) dll)) + (setcdr dll (nreverse (cdr dll)))))) + +(defun dll-empty (dll) + + "Return t if the doubly linked list DLL is empty, nil otherwise" + + (not (cdr dll))) + +(defun dll-length (dll) + + "Returns the number of elements in the doubly linked list DLL." + + (length (cdr dll))) + + + +(defun dll-copy (dll &optional element-copy-fnc) + + "Return a copy of the doubly linked list DLL. +If optional second argument ELEMENT-COPY-FNC is non-nil it should be +a function that takes one argument, an element, and returns a copy of it. +If ELEMENT-COPY-FNC is not given the elements are not copied." + + (if element-copy-fnc + (cons 'DL-LIST (mapcar element-copy-fnc (cdr dll))) + (copy-sequence dll))) + + +(defun dll-all (dll) + + "Return all elements on the double linked list DLL as an ordinary list." + + (cdr dll)) + + +(defun dll-clear (dll) + + "Clear the doubly linked list DLL, i.e. make it completely empty." + + (setcdr dll nil)) + + +(defun dll-map (map-function dll) + + "Apply MAP-FUNCTION to all elements in the doubly linked list DLL. +The function is applied to the first element first." + + (mapcar map-function (cdr dll))) + + +(defun dll-map-reverse (map-function dll) + + "Apply MAP-FUNCTION to all elements in the doubly linked list DLL. +The function is applied to the last element first." + + (unwind-protect + (setcdr dll (nreverse (cdr dll))) + (mapcar map-function (cdr dll)) + (setcdr dll (nreverse (cdr dll))))) + + +(defun dll-create-from-list (list) + + "Given an elisp LIST create a doubly linked list with the same elements." + + (cons 'DL-LIST list)) + + + +(defun dll-sort (dll predicate) + + "Sort the doubly linked list DLL, stably, comparing elements using PREDICATE. +Returns the sorted list. DLL is modified by side effects. +PREDICATE is called with two elements of DLL, and should return T +if the first element is \"less\" than the second." + + (setcdr dll (sort (cdr dll) predicate)) + dll) + + +(defun dll-filter (dll predicate) + + "Remove all elements in the doubly linked list DLL for which PREDICATE +return nil." + + (let* ((prev dll) + (node (cdr dll))) + + (while node + (cond + ((funcall predicate (car node)) + (setq prev node)) + (t + (setcdr prev (cdr node)))) + (setq node (cdr node))))) diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-dll.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-dll.el new file mode 100644 index 0000000..855bd19 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-dll.el @@ -0,0 +1,386 @@ +;;; elib-dll.el,v 1.2 1992/04/07 20:49:15 berliner Exp +;;; elib-dll.el -- Some primitives for Doubly linked lists. +;;; Copyright (C) 1991, 1992 Per Cederqvist +;;; +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2 of the License, or +;;; (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Mail bug reports to ceder@lysator.liu.se. + +(require 'elib-node) +(provide 'elib-dll) + +;;; +;;; A doubly linked list consists of one cons cell which holds the tag +;;; 'DL-LIST in the car cell and a pointer to a dummy node in the cdr +;;; cell. The doubly linked list is implemented as a circular list +;;; with the dummy node first and last. The dummy node is recognized +;;; by comparing it to the node which the cdr of the cons cell points +;;; to. +;;; + +;;; ================================================================ +;;; Internal functions for use in the doubly linked list package + +(defun dll-get-dummy-node (dll) + + ;; Return the dummy node. INTERNAL USE ONLY. + (cdr dll)) + +(defun dll-list-nodes (dll) + + ;; Return a list of all nodes in DLL. INTERNAL USE ONLY. + + (let* ((result nil) + (dummy (dll-get-dummy-node dll)) + (node (elib-node-left dummy))) + + (while (not (eq node dummy)) + (setq result (cons node result)) + (setq node (elib-node-left node))) + + result)) + +(defun dll-set-from-node-list (dll list) + + ;; Set the contents of DLL to the nodes in LIST. + ;; INTERNAL USE ONLY. + + (dll-clear dll) + (let* ((dummy (dll-get-dummy-node dll)) + (left dummy)) + (while list + (elib-node-set-left (car list) left) + (elib-node-set-right left (car list)) + (setq left (car list)) + (setq list (cdr list))) + + (elib-node-set-right left dummy) + (elib-node-set-left dummy left))) + + +;;; =================================================================== +;;; The public functions which operate on doubly linked lists. + +(defmacro dll-element (dll node) + + "Get the element of a NODE in a doubly linked list DLL. +Args: DLL NODE." + + (` (elib-node-data (, node)))) + + +(defun dll-create () + "Create an empty doubly linked list." + (let ((dummy-node (elib-node-create nil nil nil))) + (elib-node-set-right dummy-node dummy-node) + (elib-node-set-left dummy-node dummy-node) + (cons 'DL-LIST dummy-node))) + +(defun dll-p (object) + "Return t if OBJECT is a doubly linked list, otherwise return nil." + (eq (car-safe object) 'DL-LIST)) + +(defun dll-enter-first (dll element) + "Add an element first on a doubly linked list. +Args: DLL ELEMENT." + (dll-enter-after + dll + (dll-get-dummy-node dll) + element)) + + +(defun dll-enter-last (dll element) + "Add an element last on a doubly linked list. +Args: DLL ELEMENT." + (dll-enter-before + dll + (dll-get-dummy-node dll) + element)) + + +(defun dll-enter-after (dll node element) + "In the doubly linked list DLL, insert a node containing ELEMENT after NODE. +Args: DLL NODE ELEMENT." + + (let ((new-node (elib-node-create + node (elib-node-right node) + element))) + (elib-node-set-left (elib-node-right node) new-node) + (elib-node-set-right node new-node))) + + +(defun dll-enter-before (dll node element) + "In the doubly linked list DLL, insert a node containing ELEMENT before NODE. +Args: DLL NODE ELEMENT." + + (let ((new-node (elib-node-create + (elib-node-left node) node + element))) + (elib-node-set-right (elib-node-left node) new-node) + (elib-node-set-left node new-node))) + + + +(defun dll-next (dll node) + "Return the node after NODE, or nil if NODE is the last node. +Args: DLL NODE." + + (if (eq (elib-node-right node) (dll-get-dummy-node dll)) + nil + (elib-node-right node))) + + +(defun dll-previous (dll node) + "Return the node before NODE, or nil if NODE is the first node. +Args: DLL NODE." + + (if (eq (elib-node-left node) (dll-get-dummy-node dll)) + nil + (elib-node-left node))) + + +(defun dll-delete (dll node) + + "Delete NODE from the doubly linked list DLL. +Args: DLL NODE. Return the element of node." + + ;; This is a no-op when applied to the dummy node. This will return + ;; nil if applied to the dummy node since it always contains nil. + + (elib-node-set-right (elib-node-left node) (elib-node-right node)) + (elib-node-set-left (elib-node-right node) (elib-node-left node)) + (dll-element dll node)) + + + +(defun dll-delete-first (dll) + + "Delete the first NODE from the doubly linked list DLL. +Return the element. Args: DLL. Returns nil if the DLL was empty." + + ;; Relies on the fact that dll-delete does nothing and + ;; returns nil if given the dummy node. + + (dll-delete dll (elib-node-right (dll-get-dummy-node dll)))) + + +(defun dll-delete-last (dll) + + "Delete the last NODE from the doubly linked list DLL. +Return the element. Args: DLL. Returns nil if the DLL was empty." + + ;; Relies on the fact that dll-delete does nothing and + ;; returns nil if given the dummy node. + + (dll-delete dll (elib-node-left (dll-get-dummy-node dll)))) + + +(defun dll-first (dll) + + "Return the first element on the doubly linked list DLL. +Return nil if the list is empty. The element is not removed." + + (if (eq (elib-node-right (dll-get-dummy-node dll)) + (dll-get-dummy-node dll)) + nil + (elib-node-data (elib-node-right (dll-get-dummy-node dll))))) + + + + +(defun dll-last (dll) + + "Return the last element on the doubly linked list DLL. +Return nil if the list is empty. The element is not removed." + + (if (eq (elib-node-left (dll-get-dummy-node dll)) + (dll-get-dummy-node dll)) + nil + (elib-node-data (elib-node-left (dll-get-dummy-node dll))))) + + + +(defun dll-nth (dll n) + + "Return the Nth node from the doubly linked list DLL. + Args: DLL N +N counts from zero. If DLL is not that long, nil is returned. +If N is negative, return the -(N+1)th last element. +Thus, (dll-nth dll 0) returns the first node, +and (dll-nth dll -1) returns the last node." + + ;; Branch 0 ("follow left pointer") is used when n is negative. + ;; Branch 1 ("follow right pointer") is used otherwise. + + (let* ((dummy (dll-get-dummy-node dll)) + (branch (if (< n 0) 0 1)) + (node (elib-node-branch dummy branch))) + + (if (< n 0) + (setq n (- -1 n))) + + (while (and (not (eq dummy node)) + (> n 0)) + (setq node (elib-node-branch node branch)) + (setq n (1- n))) + + (if (eq dummy node) + nil + node))) + + +(defun dll-empty (dll) + + "Return t if the doubly linked list DLL is empty, nil otherwise" + + (eq (elib-node-left (dll-get-dummy-node dll)) + (dll-get-dummy-node dll))) + +(defun dll-length (dll) + + "Returns the number of elements in the doubly linked list DLL." + + (let* ((dummy (dll-get-dummy-node dll)) + (node (elib-node-right dummy)) + (n 0)) + + (while (not (eq node dummy)) + (setq node (elib-node-right node)) + (setq n (1+ n))) + + n)) + + + +(defun dll-copy (dll &optional element-copy-fnc) + + "Return a copy of the doubly linked list DLL. +If optional second argument ELEMENT-COPY-FNC is non-nil it should be +a function that takes one argument, an element, and returns a copy of it. +If ELEMENT-COPY-FNC is not given the elements are not copied." + + (let ((result (dll-create)) + (node (dll-nth dll 0))) + (if element-copy-fnc + + ;; Copy the elements with the user-supplied function. + (while node + (dll-enter-last result + (funcall element-copy-fnc + (dll-element dll node))) + (setq node (dll-next dll node))) + + ;; Don't try to copy the elements - they might be + ;; circular lists, or anything at all... + (while node + (dll-enter-last result (dll-element dll node)) + (setq node (dll-next dll node)))) + + result)) + + + +(defun dll-all (dll) + + "Return all elements on the double linked list DLL as an ordinary list." + + (let* ((result nil) + (dummy (dll-get-dummy-node dll)) + (node (elib-node-left dummy))) + + (while (not (eq node dummy)) + (setq result (cons (dll-element dll node) result)) + (setq node (elib-node-left node))) + + result)) + + +(defun dll-clear (dll) + + "Clear the doubly linked list DLL, i.e. make it completely empty." + + (elib-node-set-left (dll-get-dummy-node dll) (dll-get-dummy-node dll)) + (elib-node-set-right (dll-get-dummy-node dll) (dll-get-dummy-node dll))) + + +(defun dll-map (map-function dll) + + "Apply MAP-FUNCTION to all elements in the doubly linked list DLL. +The function is applied to the first element first." + + (let* ((dummy (dll-get-dummy-node dll)) + (node (elib-node-right dummy))) + + (while (not (eq node dummy)) + (funcall map-function (dll-element dll node)) + (setq node (elib-node-right node))))) + + +(defun dll-map-reverse (map-function dll) + + "Apply MAP-FUNCTION to all elements in the doubly linked list DLL. +The function is applied to the last element first." + + (let* ((dummy (dll-get-dummy-node dll)) + (node (elib-node-left dummy))) + + (while (not (eq node dummy)) + (funcall map-function (dll-element dll node)) + (setq node (elib-node-left node))))) + + +(defun dll-create-from-list (list) + + "Given an elisp LIST create a doubly linked list with the same elements." + + (let ((dll (dll-create))) + (while list + (dll-enter-last dll (car list)) + (setq list (cdr list))) + dll)) + + + +(defun dll-sort (dll predicate) + + "Sort the doubly linked list DLL, stably, comparing elements using PREDICATE. +Returns the sorted list. DLL is modified by side effects. +PREDICATE is called with two elements of DLL, and should return T +if the first element is \"less\" than the second." + + (dll-set-from-node-list + dll (sort (dll-list-nodes dll) + (function (lambda (x1 x2) + (funcall predicate + (dll-element dll x1) + (dll-element dll x2)))))) + dll) + + +(defun dll-filter (dll predicate) + + "Remove all elements in the doubly linked list DLL for which PREDICATE +return nil." + + (let* ((dummy (dll-get-dummy-node dll)) + (node (elib-node-right dummy)) + next) + + (while (not (eq node dummy)) + (setq next (elib-node-right node)) + (if (funcall predicate (dll-element dll node)) + nil + (dll-delete dll node)) + (setq node next)))) diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-node.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-node.el new file mode 100644 index 0000000..6c476a3 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/elib-node.el @@ -0,0 +1,89 @@ +;;;; elib-node.el,v 1.2 1992/04/07 20:49:16 berliner Exp +;;;; This file implements the nodes used in binary trees and +;;;; doubly linked lists +;;;; +;;;; Copyright (C) 1991 Inge Wallin +;;;; +;;;; This file is part of the GNU Emacs lisp library, Elib. +;;;; +;;;; GNU Elib is free software; you can redistribute it and/or modify +;;;; it under the terms of the GNU General Public License as published by +;;;; the Free Software Foundation; either version 1, or (at your option) +;;;; any later version. +;;;; +;;;; GNU Elib is distributed in the hope that it will be useful, +;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;;; GNU General Public License for more details. +;;;; +;;;; You should have received a copy of the GNU General Public License +;;;; along with GNU Emacs; see the file COPYING. If not, write to +;;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;;;; +;;;; Author: Inge Wallin +;;;; + +;;; +;;; A node is implemented as an array with three elements, using +;;; (elt node 0) as the left pointer +;;; (elt node 1) as the right pointer +;;; (elt node 2) as the data +;;; +;;; Some types of trees, e.g. AVL trees, need bigger nodes, but +;;; as long as the first three parts are the left pointer, the +;;; right pointer and the data field, these macros can be used. +;;; + + +(provide 'elib-node) + + +(defmacro elib-node-create (left right data) + "Create a tree node from LEFT, RIGHT and DATA." + (` (vector (, left) (, right) (, data)))) + + +(defmacro elib-node-left (node) + "Return the left pointer of NODE." + (` (aref (, node) 0))) + + +(defmacro elib-node-right (node) + "Return the right pointer of NODE." + (` (aref (, node) 1))) + + +(defmacro elib-node-data (node) + "Return the data of NODE." + (` (aref (, node) 2))) + + +(defmacro elib-node-set-left (node newleft) + "Set the left pointer of NODE to NEWLEFT." + (` (aset (, node) 0 (, newleft)))) + + +(defmacro elib-node-set-right (node newright) + "Set the right pointer of NODE to NEWRIGHT." + (` (aset (, node) 1 (, newright)))) + + +(defmacro elib-node-set-data (node newdata) + "Set the data of NODE to NEWDATA." + (` (aset (, node) 2 (, newdata)))) + + + +(defmacro elib-node-branch (node branch) + "Get value of a branch of a node. +NODE is the node, and BRANCH is the branch. +0 for left pointer, 1 for right pointer and 2 for the data." + (` (aref (, node) (, branch)))) + + +(defmacro elib-node-set-branch (node branch newval) + "Set value of a branch of a node. +NODE is the node, and BRANCH is the branch. +0 for left pointer, 1 for the right pointer and 2 for the data. +NEWVAL is new value of the branch." + (` (aset (, node) (, branch) (, newval)))) diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs-startup.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs-startup.el new file mode 100644 index 0000000..27bb57c --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs-startup.el @@ -0,0 +1,6 @@ +;;; pcl-cvs-startup.el,v 1.2 1992/04/07 20:49:17 berliner Exp +(autoload 'cvs-update "pcl-cvs" + "Run a 'cvs update' in the current working directory. Feed the +output to a *cvs* buffer and run cvs-mode on it. +If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run." + t) diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.el b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.el new file mode 100644 index 0000000..99da369 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.el @@ -0,0 +1,1476 @@ +;;; pcl-cvs.el,v 1.2 1992/04/07 20:49:19 berliner Exp +;;; pcl-cvs.el -- A Front-end to CVS 1.3 or later. Release 1.02. +;;; Copyright (C) 1991, 1992 Per Cederqvist +;;; +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2 of the License, or +;;; (at your option) any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program; if not, write to the Free Software +;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;;; See below for installation instructions. +;;;; +;;;; There is an TeXinfo file that describes this package. The GNU +;;;; General Public License is included in that file. You should read +;;;; it to get the most from this package. + +;;; Don't try to use this with CVS 1.2 or earlier. It won't work. Get +;;; CVS 1.3. + +;;; Mail questions and bug reports to ceder@lysator.liu.se. + +(require 'cookie) +(provide 'pcl-cvs) + +;;; ------------------------------------------------------- +;;; START OF THINGS TO CHECK WHEN INSTALLING + +(defvar cvs-program "/usr/gnu/bin/cvs" + "*Full path to the cvs executable.") + +(defvar cvs-diff-program "/usr/gnu/bin/diff" + "*Full path to the diff program.") + +(defvar cvs-rm-program "/usr/gnu/bin/rm" + "*Full path to the rm program. Typically /bin/rm.") + +;; Uncomment the following line if you are running on 18.57 or earlier. +;(setq delete-exited-processes nil) +;; Emacs version 18.57 and earlier is likely to crash if +;; delete-exited-processes is t, since the sentinel uses lots of +;; memory, and 18.57 forgets to GCPROT a variable if +;; delete-exited-processes is t. + +;;; END OF THINGS TO CHECK WHEN INSTALLING +;;; -------------------------------------------------------- + +(defvar cvs-bakprefix ".#" + "The prefix that CVS prepends to files when rcsmerge'ing.") + +(defvar cvs-erase-input-buffer nil + "*Non-nil if input buffers should be cleared before asking for new info.") + +(defvar cvs-auto-remove-handled nil + "*Non-nil if cvs-remove-handled should be called automatically. +If this is set to any non-nil value entries that does not need to be +checked in will be removed from the *cvs* buffer after every cvs-commit +command.") + +(defconst cvs-cursor-column 14 + "Column to position cursor in in cvs-mode. +Column 0 is left-most column.") + +(defvar cvs-mode-map nil + "Keymap for the cvs mode.") + +(defvar cvs-edit-mode-map nil + "Keymap for the cvs edit mode (used when editing cvs log messages).") + +(defvar cvs-buffer-name "*cvs*" + "Name of the cvs buffer.") + +(defvar cvs-commit-prompt-buffer "*cvs-commit-message*" + "Name of buffer in which the user is prompted for a log message when +committing files.") + +(defvar cvs-temp-buffer-name "*cvs-tmp*" + "*Name of the cvs temporary buffer. +Output from cvs is placed here by synchronous commands.") + +(defvar cvs-cvs-diff-flags nil + "*List of strings to use as flags to pass to ``cvs diff''. +Used by cvs-diff-cvs. +Set this to '("-u") to get a Unidiff format, or '("-c") to get context diffs.") + +(defvar cvs-status-flags nil + "*List of strings to pass to ``cvs status''.") + +(defvar cvs-log-flags nil + "*List of strings to pass to ``cvs log''.") + +(defvar cvs-diff-flags nil + "*List of strings to use as flags to pass to ``diff''. +Do not confuse with cvs-cvs-diff-flags. Used by cvs-diff-backup.") + +(defvar cvs-buffers-to-delete nil + "List of temporary buffers that should be discarded as soon as possible. +Due to a bug in emacs 18.57 the sentinel can't discard them reliably.") + +;; You are NOT allowed to disable this message by default. However, you +;; are encouraged to inform your users that by adding +;; (setq cvs-inhibit-copyright-message t) +;; to their .emacs they can get rid of it. Just don't add that line +;; to your default.el! +(defvar cvs-inhibit-copyright-message nil + "*Don't display a Copyright message in the ``*cvs*'' buffer.") + +(defvar cvs-startup-message + (if cvs-inhibit-copyright-message + "PCL-CVS release 1.02" + "PCL-CVS release 1.02. Copyright (C) 1992 Per Cederqvist +Pcl-cvs comes with absolutely no warranty; for details consult the manual. +This is free software, and you are welcome to redistribute it under certain +conditions; again, consult the TeXinfo manual for details.") + "*Startup message for CVS.") + +(defvar cvs-cvs-buffer nil + "Internal to pcl-cvs.el. +This variable exists in the *cvs-commit-message* buffer and names +the *cvs* buffer.") + +;;; The cvs data structure: +;;; +;;; When the `cvs update' is ready we parse the output. Every file +;;; that is affected in some way is added as a cookie of fileinfo +;;; (as defined below). +;;; + +;;; cvs-fileinfo +;;; +;;; marked t/nil +;;; type One of +;;; UPDATED - file copied from repository +;;; MODIFIED - modified by you, unchanged in +;;; repository +;;; ADDED - added by you, not yet committed +;;; REMOVED - removed by you, not yet committed +;;; CVS-REMOVED- removed, since file no longer exists +;;; in the repository. +;;; MERGED - successful merge +;;; CONFLICT - conflict when merging +;;; REM-CONFLICT-removed in repository, changed locally. +;;; MOD-CONFLICT-removed locally, changed in repository. +;;; DIRCHANGE - A change of directory. +;;; UNKNOWN - An unknown file. +;;; MOVE-AWAY - A file that is in the way. +;;; REPOS-MISSING- The directory is removed from the +;;; repository. Go fetch a backup. +;;; dir Directory the file resides in. Should not end with +;;; slash. +;;; file-name The file name. +;;; backup-file Name of the backup file if MERGED or CONFLICT. +;;; cvs-diff-buffer A buffer that contains a 'cvs diff file'. +;;; backup-diff-buffer A buffer that contains a 'diff file backup-file'. +;;; full-log The output from cvs, unparsed. +;;; mod-time Modification time of file used for *-diff-buffer. +;;; handled True if this file doesn't require further action. +;;; +;;; Constructor: + +;;; cvs-fileinfo + +;;; Constructor: + +(defun cvs-create-fileinfo (type + dir + file-name + full-log) + "Create a fileinfo from all parameters. +Arguments: TYPE DIR FILE-NAME FULL-LOG. +A fileinfo has the following fields: + + marked t/nil + type One of + UPDATED - file copied from repository + MODIFIED - modified by you, unchanged in + repository + ADDED - added by you, not yet committed + REMOVED - removed by you, not yet committed + CVS-REMOVED- removed, since file no longer exists + in the repository. + MERGED - successful merge + CONFLICT - conflict when merging + REM-CONFLICT-removed in repository, but altered + locally. + MOD-CONFLICT-removed locally, changed in repository. + DIRCHANGE - A change of directory. + UNKNOWN - An unknown file. + MOVE-AWAY - A file that is in the way. + REPOS-MISSING- The directory has vanished from the + repository. + dir Directory the file resides in. Should not end with slash. + file-name The file name. + backup-file Name of the backup file if MERGED or CONFLICT. + cvs-diff-buffer A buffer that contains a 'cvs diff file'. + backup-diff-buffer A buffer that contains a 'diff file backup-file'. + full-log The output from cvs, unparsed. + mod-time Modification time of file used for *-diff-buffer. + handled True if this file doesn't require further action." + (cons + 'CVS-FILEINFO + (vector nil nil type dir file-name nil nil nil full-log nil))) + + +;;; Selectors: + +(defun cvs-fileinfo->handled (cvs-fileinfo) + "Get the `handled' field from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 0)) + +(defun cvs-fileinfo->marked (cvs-fileinfo) + "Check if CVS-FILEINFO is marked." + (elt (cdr cvs-fileinfo) 1)) + +(defun cvs-fileinfo->type (cvs-fileinfo) + "Get type from CVS-FILEINFO. +Type is one of UPDATED, MODIFIED, ADDED, REMOVED, CVS-REMOVED, MERGED, +CONFLICT, REM-CONFLICT, MOD-CONFLICT, DIRCHANGE, UNKNOWN, MOVE-AWAY +or REPOS-MISSING." + (elt (cdr cvs-fileinfo) 2)) + +(defun cvs-fileinfo->dir (cvs-fileinfo) + "Get dir from CVS-FILEINFO. +The directory name does not end with a slash. " + (elt (cdr cvs-fileinfo) 3)) + +(defun cvs-fileinfo->file-name (cvs-fileinfo) + "Get file-name from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 4)) + +(defun cvs-fileinfo->backup-file (cvs-fileinfo) + "Get backup-file from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 5)) + +(defun cvs-fileinfo->cvs-diff-buffer (cvs-fileinfo) + "Get cvs-diff-buffer from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 6)) + +(defun cvs-fileinfo->backup-diff-buffer (cvs-fileinfo) + "Get backup-diff-buffer from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 7)) + +(defun cvs-fileinfo->full-log (cvs-fileinfo) + "Get full-log from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 8)) + +(defun cvs-fileinfo->mod-time (cvs-fileinfo) + "Get mod-time from CVS-FILEINFO." + (elt (cdr cvs-fileinfo) 9)) + +;;; Modifiers: + +(defun cvs-set-fileinfo->handled (cvs-fileinfo newval) + "Set handled in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 0 newval)) + +(defun cvs-set-fileinfo->marked (cvs-fileinfo newval) + "Set marked in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 1 newval)) + +(defun cvs-set-fileinfo->type (cvs-fileinfo newval) + "Set type in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 2 newval)) + +(defun cvs-set-fileinfo->dir (cvs-fileinfo newval) + "Set dir in CVS-FILEINFO to NEWVAL. +The directory should now end with a slash." + (aset (cdr cvs-fileinfo) 3 newval)) + +(defun cvs-set-fileinfo->file-name (cvs-fileinfo newval) + "Set file-name in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 4 newval)) + +(defun cvs-set-fileinfo->backup-file (cvs-fileinfo newval) + "Set backup-file in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 5 newval)) + +(defun cvs-set-fileinfo->cvs-diff-buffer (cvs-fileinfo newval) + "Set cvs-diff-buffer in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 6 newval)) + +(defun cvs-set-fileinfo->backup-diff-buffer (cvs-fileinfo newval) + "Set backup-diff-buffer in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 7 newval)) + +(defun cvs-set-fileinfo->full-log (cvs-fileinfo newval) + "Set full-log in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 8 newval)) + +(defun cvs-set-fileinfo->mod-time (cvs-fileinfo newval) + "Set full-log in CVS-FILEINFO to NEWVAL." + (aset (cdr cvs-fileinfo) 9 newval)) + + + +;;; Predicate: + +(defun cvs-fileinfo-p (object) + "Return t if OBJECT is a cvs-fileinfo." + (eq (car-safe object) 'CVS-FILEINFO)) + +;;;; End of types. + +(defun cvs-use-temp-buffer () + "Display a temporary buffer in another window and select it. +The selected window will not be changed. The temporary buffer will +be erased and writable." + + (display-buffer (get-buffer-create cvs-temp-buffer-name)) + (set-buffer cvs-temp-buffer-name) + (setq buffer-read-only nil) + (erase-buffer)) + +; Too complicated to handle all the cases that are generated. +; Maybe later. +;(defun cvs-examine (directory &optional local) +; "Run a 'cvs -n update' in the current working directory. +;That is, check what needs to be done, but don't change the disc. +;Feed the output to a *cvs* buffer and run cvs-mode on it. +;If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run." +; (interactive (list (read-file-name "CVS Update (directory): " +; nil default-directory nil) +; current-prefix-arg)) +; (cvs-do-update directory local 'noupdate)) + +(defun cvs-update (directory &optional local) + "Run a 'cvs update' in the current working directory. Feed the +output to a *cvs* buffer and run cvs-mode on it. +If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run." + (interactive (list (read-file-name "CVS Update (directory): " + nil default-directory nil) + current-prefix-arg)) + (cvs-do-update directory local nil)) + +(defun cvs-filter (predicate list &rest extra-args) + "Apply PREDICATE to each element on LIST. +Args: PREDICATE LIST &rest EXTRA-ARGS. +Return a new list consisting of those elements that PREDICATE +returns non-nil for. + +If more than two arguments are given the remaining args are +passed to PREDICATE." + ;; Avoid recursion - this should work for LONG lists also! + (let* ((head (cons 'dummy-header nil)) + (tail head)) + (while list + (if (apply predicate (car list) extra-args) + (setq tail (setcdr tail (list (car list))))) + (setq list (cdr list))) + (cdr head))) + +(defun cvs-update-no-prompt () + "Run cvs update in current directory." + (interactive) + (cvs-do-update default-directory nil nil)) + +(defun cvs-do-update (directory local dont-change-disc) + "Do a 'cvs update' in DIRECTORY. +If LOCAL is non-nil 'cvs update -l' is executed. +If DONT-CHANGE-DISC is non-nil 'cvs -n update' is executed. +Both LOCAL and DONT-CHANGE-DISC may be non-nil simultaneously. + +*Note*: DONT-CHANGE-DISC does not yet work. The parser gets confused." + (save-some-buffers) + (let* ((this-dir (file-name-as-directory (expand-file-name directory))) + (use-this-window (equal (buffer-name (current-buffer)) + cvs-buffer-name)) + (update-buffer (generate-new-buffer + (concat (file-name-nondirectory + (substring this-dir 0 -1)) + "-update"))) + cvs-process args) + + ;; The *cvs* buffer is killed to avoid confusion - is the update ready + ;; or not? + (if (get-buffer cvs-buffer-name) + (kill-buffer cvs-buffer-name)) + + ;; Generate "-n update -l". + (if local (setq args (list "-l"))) + (setq args (cons "update" args)) + (if dont-change-disc (setq args (cons "-n" args))) + + ;; Set up the buffer that receives the output from "cvs update". + (if use-this-window + (switch-to-buffer update-buffer) + (set-buffer update-buffer) + (display-buffer update-buffer)) + + (setq default-directory this-dir) + (setq cvs-process + (let ((process-connection-type nil)) ; Use a pipe, not a pty. + (apply 'start-process "cvs" update-buffer cvs-program args))) + + (setq mode-line-process + (concat ": " + (symbol-name (process-status cvs-process)))) + (set-buffer-modified-p (buffer-modified-p)) ; Update the mode line. + (set-process-sentinel cvs-process 'cvs-sentinel) + + ;; Work around a bug in emacs 18.57 and earlier. + (setq cvs-buffers-to-delete + (cvs-delete-unused-temporary-buffers cvs-buffers-to-delete)))) + +(defun cvs-delete-unused-temporary-buffers (list) + "Delete all buffers on LIST that is not visible. +Return a list of all buffers that still is alive." + + (cond + ((null list) nil) + ((get-buffer-window (car list)) + (cons (car list) + (cvs-delete-unused-temporary-buffers (cdr list)))) + (t + (kill-buffer (car list)) + (cvs-delete-unused-temporary-buffers (cdr list))))) + + +(put 'cvs-mode 'mode-class 'special) + +(defun cvs-mode () + "\\<cvs-mode-map>Mode used for pcl-cvs, a frontend to CVS. + +To get the *cvs* buffer you should use ``\\[cvs-update]''. + +Full documentation is in the TeXinfo file. These are the most useful commands: + +\\[cookie-previous-cookie] Move up. \\[cookie-next-cookie] Move down. +\\[cvs-commit] Commit file. \\[cvs-update-no-prompt] Reupdate directory. +\\[cvs-mark] Mark file/dir. \\[cvs-unmark] Unmark file/dir. +\\[cvs-mark-all-files] Mark all files. \\[cvs-unmark-all-files] Unmark all files. +\\[cvs-find-file] Edit file/run Dired. \\[cvs-find-file-other-window] Find file or run Dired in other window. +\\[cvs-remove-handled] Remove processed entries. \\[cvs-add-change-log-entry-other-window] Write ChangeLog in other window. +\\[cvs-add] Add to repository. \\[cvs-remove-file] Remove file. +\\[cvs-diff-cvs] Diff between base revision. \\[cvs-diff-backup] Diff backup file. +\\[cvs-acknowledge] Delete line from buffer. \\[cvs-ignore] Add file to the .cvsignore file. +\\[cvs-log] Run ``cvs log''. \\[cvs-status] Run ``cvs status''. + +Entry to this mode runs cvs-mode-hook. +This description is updated for release 1.02 of pcl-cvs. +All bindings: +\\{cvs-mode-map}" + (interactive) + (setq major-mode 'cvs-mode) + (setq mode-name "CVS") + (setq buffer-read-only nil) + (buffer-flush-undo (current-buffer)) + (make-local-variable 'goal-column) + (setq goal-column cvs-cursor-column) + (use-local-map cvs-mode-map) + (run-hooks 'cvs-mode-hook)) + +(defun cvs-sentinel (proc msg) + "Sentinel for the cvs update process. +This is responsible for parsing the output from the cvs update when +it is finished." + (cond + ((null (buffer-name (process-buffer proc))) + ;; buffer killed + (set-process-buffer proc nil)) + ((memq (process-status proc) '(signal exit)) + (let* ((obuf (current-buffer)) + (omax (point-max)) + (opoint (point))) + ;; save-excursion isn't the right thing if + ;; process-buffer is current-buffer + (unwind-protect + (progn + (set-buffer (process-buffer proc)) + (setq mode-line-process + (concat ": " + (symbol-name (process-status proc)))) + (cvs-parse-buffer) + (setq cvs-buffers-to-delete + (cons (process-buffer proc) cvs-buffers-to-delete))) + (set-buffer-modified-p (buffer-modified-p))) + (if (equal obuf (process-buffer proc)) + nil + (set-buffer (process-buffer proc)) + (if (< opoint omax) + (goto-char opoint)) + (set-buffer obuf)))))) + +(defun cvs-skip-line (regexp errormsg &optional arg) + "Like forward-line, but check that the skipped line matches REGEXP. +If it doesn't match REGEXP (error ERRORMSG) is called. +If optional ARG, a number, is given the ARGth parenthesized expression +in the REGEXP is returned as a string. +Point should be in column 1 when this function is called." + (cond + ((looking-at regexp) + (forward-line 1) + (if arg + (buffer-substring (match-beginning arg) + (match-end arg)))) + (t + (error errormsg)))) + +(defun cvs-get-current-dir (dirname) + "Return current working directory, suitable for cvs-parse-buffer. +Args: DIRNAME. +Concatenates default-directory and DIRNAME to form an absolute path." + (if (string= "." dirname) + (substring default-directory 0 -1) + (concat default-directory dirname))) + + +(defun cvs-parse-buffer () + "Parse the current buffer and select a *cvs* buffer. +Signals an error if unexpected output was detected in the buffer." + (goto-char (point-min)) + (let ((buf (get-buffer-create cvs-buffer-name)) + (current-dir default-directory) + (root-dir default-directory) + (parse-buf (current-buffer))) + + (cookie-create + buf 'cvs-pp cvs-startup-message ;Se comment above cvs-startup-message. + "---------- End -----") + + (cookie-enter-first + buf + (cvs-create-fileinfo + 'DIRCHANGE current-dir + nil "")) + + (while (< (point) (point-max)) + (cond + + ;; CVS is descending a subdirectory. + + ((looking-at "cvs update: Updating \\(.*\\)$") + (setq current-dir + (cvs-get-current-dir + (buffer-substring (match-beginning 1) (match-end 1)))) + + ;; Omit empty directories. + (if (eq (cvs-fileinfo->type (cookie-last buf)) + 'DIRCHANGE) + (cookie-delete-last buf)) + + (cookie-enter-last + buf + (cvs-create-fileinfo + 'DIRCHANGE current-dir + nil (buffer-substring (match-beginning 0) + (match-end 0)))) + (forward-line 1)) + + ;; File removed, since it is removed (by third party) in repository. + + ((or (looking-at "cvs update: warning: \\(.*\\) is not (any longer) \ +pertinent") + (looking-at "cvs update: \\(.*\\) is no longer in the repository")) + (cookie-enter-last + buf + (cvs-create-fileinfo + 'CVS-REMOVED current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) + (match-end 0)))) + (forward-line 1)) + + ;; File removed by you, but recreated by cvs. Ignored. + + ((looking-at "cvs update: warning: .* was lost$") + (forward-line 1)) + + ;; A file that has been created by you, but added to the cvs + ;; repository by another. + + ((looking-at "^cvs update: move away \\(.*\\); it is in the way$") + (cookie-enter-last + buf + (cvs-create-fileinfo + 'MOVE-AWAY current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) + (match-end 0)))) + (forward-line 1)) + + ;; Empty line. Probably inserted by mistake by user (or developer :-) + ;; Ignore. + + ((looking-at "^$") + (forward-line 1)) + + ;; Cvs waits for a lock. Ignore. + + ((looking-at + "^cvs update: \\[..:..:..\\] waiting for .*lock in ") + (forward-line 1)) + + ;; File removed in repository, but edited by you. + + ((looking-at + "cvs update: conflict: \\(.*\\) is modified but no longer \ +in the repository$") + (cookie-enter-last + buf + (cvs-create-fileinfo + 'REM-CONFLICT current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) + (match-end 0)))) + (forward-line 1)) + + ((looking-at + "cvs update: conflict: removed \\(.*\\) was modified by second party") + (cvs-create-fileinfo + 'MOD-CONFLICT current-dir + (buffer-substring (match-beginning 1) (match-end 1)) + (buffer-substring (match-beginning 0) (match-end 0))) + (forward-line 1)) + + ((looking-at "cvs update: in directory ") + (let ((start (point))) + (forward-line 1) + (cvs-skip-line + (regexp-quote "cvs [update aborted]: there is no repository ") + "Unexpected cvs output.") + (cookie-enter-last + buf + (cvs-create-fileinfo + 'REPOS-MISSING current-dir + nil + (buffer-substring start (point)))))) + + ;; The file is copied from the repository. + + ((looking-at "U \\(.*\\)$") + (cookie-enter-last + buf + (let ((fileinfo + (cvs-create-fileinfo + 'UPDATED current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) (match-end 0))))) + (cvs-set-fileinfo->handled fileinfo t) + fileinfo)) + (forward-line 1)) + + ;; The file is modified by the user, and untouched in the repository. + + ((looking-at "M \\(.*\\)$") + (cookie-enter-last + buf + (cvs-create-fileinfo + 'MODIFIED current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) (match-end 0)))) + (forward-line 1)) + + ;; The file is "cvs add"ed, but not "cvs ci"ed. + + ((looking-at "A \\(.*\\)$") + (cookie-enter-last + buf + (cvs-create-fileinfo + 'ADDED current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) (match-end 0)))) + (forward-line 1)) + + ;; The file is "cvs remove"ed, but not "cvs ci"ed. + + ((looking-at "R \\(.*\\)$") + (cookie-enter-last + buf + (cvs-create-fileinfo + 'REMOVED current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) (match-end 0)))) + (forward-line 1)) + + ;; Unknown file. + + ((looking-at "? \\(.*\\)$") + (cookie-enter-last + buf + (cvs-create-fileinfo + 'UNKNOWN current-dir + (file-name-nondirectory + (buffer-substring (match-beginning 1) (match-end 1))) + (buffer-substring (match-beginning 0) (match-end 0)))) + (forward-line 1)) + (t + + ;; CVS has decided to merge someone elses changes into this + ;; document. This leads to a lot of garbage being printed. + ;; First there is two lines that contains no information + ;; that we skip (but we check that we recognize them). + + (let ((complex-start (point)) + initial-revision filename) + + (cvs-skip-line "^RCS file: .*$" "Parse error.") + (setq initial-revision + (cvs-skip-line "^retrieving revision \\(.*\\)$" + "Unexpected output from cvs." 1)) + (cvs-skip-line "^retrieving revision .*$" + "Unexpected output from cvs.") + + ;; Get the file name from the next line. + + (setq + filename + (cvs-skip-line + "^Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$" + "Unexpected output from cvs." + 1)) + + (cond + + ;; The file was successfully merged. + + ((looking-at "^M ") + (forward-line 1) + (let ((fileinfo + (cvs-create-fileinfo + 'MERGED current-dir + filename + (buffer-substring complex-start (point))))) + (cvs-set-fileinfo->backup-file + fileinfo + (concat cvs-bakprefix filename "." initial-revision)) + (cookie-enter-last + buf fileinfo))) + + ;; A conflicting merge. + + (t + (cvs-skip-line "^merge: overlaps during merge$" + "Unexpected output from cvs.") + (cvs-skip-line "^cvs update: conflicts found in " + "Unexpected output from cvs.") + (cvs-skip-line "^C " "Unexpected cvs output.") + (let ((fileinfo + (cvs-create-fileinfo + 'CONFLICT current-dir + filename + (buffer-substring complex-start (point))))) + + (cvs-set-fileinfo->backup-file + fileinfo + (concat cvs-bakprefix filename "." initial-revision)) + + (cookie-enter-last buf fileinfo)))))))) + + ;; All parsing is done. + + ;; If the last entry is a directory, remove it. + (if (eq (cvs-fileinfo->type (cookie-last buf)) + 'DIRCHANGE) + (cookie-delete-last buf)) + + (set-buffer buf) + (cvs-mode) + (setq cookie-last-tin (cookie-nth buf 0)) + (goto-char (point-min)) + (cookie-previous-cookie buf (point-min) 1) + (setq default-directory root-dir) + (if (get-buffer-window parse-buf) + (set-window-buffer (get-buffer-window parse-buf) buf) + (display-buffer buf)))) + + +(defun cvs-pp (fileinfo) + "Pretty print FILEINFO into a string." + + (let ((a (cvs-fileinfo->type fileinfo)) + (s (if (cvs-fileinfo->marked fileinfo) + "*" " ")) + (f (cvs-fileinfo->file-name fileinfo)) + (ci (if (cvs-fileinfo->handled fileinfo) + " " "ci"))) + (cond + ((eq a 'UPDATED) + (format "%s Updated %s" s f)) + ((eq a 'MODIFIED) + (format "%s Modified %s %s" s ci f)) + ((eq a 'MERGED) + (format "%s Merged %s %s" s ci f)) + ((eq a 'CONFLICT) + (format "%s Conflict %s" s f)) + ((eq a 'ADDED) + (format "%s Added %s %s" s ci f)) + ((eq a 'REMOVED) + (format "%s Removed %s %s" s ci f)) + ((eq a 'UNKNOWN) + (format "%s Unknown %s" s f)) + ((eq a 'CVS-REMOVED) + (format "%s Removed from repository: %s" s f)) + ((eq a 'REM-CONFLICT) + (format "%s Conflict: Removed from repository, changed by you: %s" s f)) + ((eq a 'MOD-CONFLICT) + (format "%s Conflict: Removed by you, changed in repository: %s" s f)) + ((eq a 'DIRCHANGE) + (format "\nIn directory %s:" + (cvs-fileinfo->dir fileinfo))) + ((eq a 'MOVE-AWAY) + (format "%s Move away %s - it is in the way" s f)) + ((eq a 'REPOS-MISSING) + (format " This repository is missing! Remove this dir manually.")) + (t + (format "%s Internal error! %s" s f))))) + + +;;; You can define your own keymap in .emacs. pcl-cvs.el won't overwrite it. + +(if cvs-mode-map + nil + (setq cvs-mode-map (make-keymap)) + (suppress-keymap cvs-mode-map) + (define-key cvs-mode-map " " 'cookie-next-cookie) + (define-key cvs-mode-map "?" 'describe-mode) + (define-key cvs-mode-map "A" 'cvs-add-change-log-entry-other-window) + (define-key cvs-mode-map "M" 'cvs-mark-all-files) + (define-key cvs-mode-map "U" 'cvs-unmark-all-files) + (define-key cvs-mode-map "\C-?" 'cvs-unmark-up) + (define-key cvs-mode-map "\C-n" 'cookie-next-cookie) + (define-key cvs-mode-map "\C-p" 'cookie-previous-cookie) + (define-key cvs-mode-map "a" 'cvs-add) + (define-key cvs-mode-map "b" 'cvs-diff-backup) + (define-key cvs-mode-map "c" 'cvs-commit) + (define-key cvs-mode-map "d" 'cvs-diff-cvs) + (define-key cvs-mode-map "f" 'cvs-find-file) + (define-key cvs-mode-map "g" 'cvs-update-no-prompt) + (define-key cvs-mode-map "i" 'cvs-ignore) + (define-key cvs-mode-map "l" 'cvs-log) + (define-key cvs-mode-map "m" 'cvs-mark) + (define-key cvs-mode-map "n" 'cookie-next-cookie) + (define-key cvs-mode-map "o" 'cvs-find-file-other-window) + (define-key cvs-mode-map "p" 'cookie-previous-cookie) + (define-key cvs-mode-map "r" 'cvs-remove-file) + (define-key cvs-mode-map "s" 'cvs-status) + (define-key cvs-mode-map "\C-k" 'cvs-acknowledge) + (define-key cvs-mode-map "x" 'cvs-remove-handled) + (define-key cvs-mode-map "u" 'cvs-unmark)) + + +(defun cvs-get-marked () + "Return a list of all selected tins. +If there are any marked tins, return them. +Otherwise, if the cursor selects a directory, return all files in it. +Otherwise return (a list containing) the file the cursor points to, or +an empty list if it doesn't point to a file at all." + + (cond + ;; Any marked cookies? + ((cookie-collect-tins (current-buffer) + 'cvs-fileinfo->marked)) + ;; Nope. + (t + (let ((sel (cookie-get-selection + (current-buffer) (point) cookie-last-tin))) + (cond + ;; If a directory is selected, all it members are returned. + ((and sel (eq (cvs-fileinfo->type + (cookie-cookie (current-buffer) sel)) + 'DIRCHANGE)) + (cookie-collect-tins + (current-buffer) 'cvs-dir-member-p + (cvs-fileinfo->dir (cookie-cookie (current-buffer) sel)))) + (t + (list sel))))))) + + +(defun cvs-dir-member-p (fileinfo dir) + "Return true if FILEINFO represents a file in directory DIR." + (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)) + (string= (cvs-fileinfo->dir fileinfo) dir))) + +(defun cvs-dir-empty-p (cvs-buf tin) + "Return non-nil if TIN is a directory that is empty. +Args: CVS-BUF TIN." + (and (eq (cvs-fileinfo->type (cookie-cookie cvs-buf tin)) 'DIRCHANGE) + (or (not (cookie-next cvs-buf tin)) + (eq (cvs-fileinfo->type (cookie-cookie cvs-buf + (cookie-next cvs-buf tin))) + 'DIRCHANGE)))) + +(defun cvs-remove-handled () + "Remove all lines that are handled. +Empty directories are removed." + (interactive) + ;; Pass one: remove files that are handled. + (cookie-filter (current-buffer) + (function + (lambda (fileinfo) (not (cvs-fileinfo->handled fileinfo))))) + ;; Pass two: remove empty directories. + (cookie-filter-tins (current-buffer) + (function + (lambda (tin) + (not (cvs-dir-empty-p (current-buffer) tin)))))) + +(defun cvs-mark (pos) + "Mark a fileinfo. Args: POS. +If the fileinfo is a directory, all the contents of that directory are +marked instead. A directory can never be marked. +POS is a buffer position." + + (interactive "d") + + (let* ((tin (cookie-get-selection + (current-buffer) pos cookie-last-tin)) + (sel (cookie-cookie (current-buffer) tin))) + + (cond + ;; Does POS point to a directory? If so, mark all files in that directory. + ((eq (cvs-fileinfo->type sel) 'DIRCHANGE) + (cookie-map + (function (lambda (f dir) + (cond + ((cvs-dir-member-p f dir) + (cvs-set-fileinfo->marked f t) + t)))) ;Tell cookie to redisplay this cookie. + (current-buffer) + (cvs-fileinfo->dir sel))) + (t + (cvs-set-fileinfo->marked sel t) + (cookie-invalidate-tins (current-buffer) tin) + (cookie-next-cookie (current-buffer) pos 1))))) + + +(defun cvs-committable (tin cvs-buf) + "Check if the TIN is committable. +It is committable if it + a) is not handled and + b) is either MODIFIED, ADDED, REMOVED, MERGED or CONFLICT." + (let* ((fileinfo (cookie-cookie cvs-buf tin)) + (type (cvs-fileinfo->type fileinfo))) + (and (not (cvs-fileinfo->handled fileinfo)) + (or (eq type 'MODIFIED) + (eq type 'ADDED) + (eq type 'REMOVED) + (eq type 'MERGED) + (eq type 'CONFLICT))))) + +(defun cvs-commit () + + "Check in all marked files, or the current file. +The user will be asked for a log message in a buffer. +If cvs-erase-input-buffer is non-nil that buffer will be erased. +Otherwise mark and point will be set around the entire contents of the +buffer so that it is easy to kill the contents of the buffer with \\[kill-region]." + + (interactive) + + (let* ((cvs-buf (current-buffer)) + (marked (cvs-filter (function cvs-committable) + (cvs-get-marked) + cvs-buf))) + (if (null marked) + (error "Nothing to commit!") + (pop-to-buffer (get-buffer-create cvs-commit-prompt-buffer)) + (goto-char (point-min)) + + (if cvs-erase-input-buffer + (erase-buffer) + (push-mark (point-max))) + (cvs-edit-mode) + (make-local-variable 'cvs-commit-list) + (setq cvs-commit-list marked) + (make-local-variable 'cvs-cvs-buffer) + (setq cvs-cvs-buffer cvs-buf) + (message "Press C-c C-c when you are done editing.")))) + + +(defun cvs-edit-done () + "Commit the files to the repository." + (interactive) + (save-some-buffers) + (let ((cc-list cvs-commit-list) + (cc-buffer cvs-cvs-buffer) + (msg-buffer (current-buffer)) + (msg (buffer-substring (point-min) (point-max)))) + (pop-to-buffer cc-buffer) + (bury-buffer msg-buffer) + (cvs-use-temp-buffer) + (message "Committing...") + (cvs-execute-list cc-list cvs-program (list "commit" "-m" msg)) + (mapcar (function + (lambda (tin) + (cvs-set-fileinfo->handled (cookie-cookie cc-buffer tin) t))) + cc-list) + (apply 'cookie-invalidate-tins cc-buffer cc-list) + (set-buffer cc-buffer) + (if cvs-auto-remove-handled + (cvs-remove-handled))) + + (message "Committing... Done.")) + + +(defun cvs-execute-list (tin-list program constant-args) + "Run PROGRAM on all elements on TIN-LIST. +Args: TIN-LIST PROGRAM CONSTANT-ARGS +The PROGRAM will be called with pwd set to the directory the +files reside in. CONSTANT-ARGS should be a list of strings. The +arguments given to the program will be CONSTANT-ARGS followed by all +the files (from TIN-LIST) that resides in that directory. If the files +in TIN-LIST resides in different directories the PROGRAM will be run +once for each directory (if all files in the same directory appears +after each other." + + (while tin-list + (let ((current-dir (cvs-fileinfo->dir + (cookie-cookie cvs-buffer-name + (car tin-list)))) + arg-list arg-str) + + ;; Collect all marked files in this directory. + + (while (and tin-list + (string= + current-dir + (cvs-fileinfo->dir + (cookie-cookie cvs-buffer-name (car tin-list))))) + (setq arg-list + (cons (cvs-fileinfo->file-name + (cookie-cookie cvs-buffer-name (car tin-list))) + arg-list)) + (setq tin-list (cdr tin-list))) + + (setq arg-list (nreverse arg-list)) + + ;; Execute the command on all the files that were collected. + + (setq default-directory (file-name-as-directory current-dir)) + (insert (format "=== cd %s\n" default-directory)) + (insert (format "=== %s %s\n\n" + program + (mapconcat '(lambda (foo) foo) + (nconc (copy-sequence constant-args) + arg-list) + " "))) + (apply 'call-process program nil t t + (nconc (copy-sequence constant-args) arg-list)) + (goto-char (point-max))))) + + +(defun cvs-execute-single-file-list (tin-list extractor program constant-args) + "Run PROGRAM on all elements on TIN-LIST. + +Args: TIN-LIST EXTRACTOR PROGRAM CONSTANT-ARGS + +The PROGRAM will be called with pwd set to the directory the files +reside in. CONSTANT-ARGS is a list of strings to pass as arguments to +PROGRAM. The arguments given to the program will be CONSTANT-ARGS +followed by the list that EXTRACTOR returns. + +EXTRACTOR will be called once for each file on TIN-LIST. It is given +one argument, the cvs-fileinfo. It can return t, which means ignore +this file, or a list of arguments to send to the program." + + (while tin-list + (let ((default-directory (file-name-as-directory + (cvs-fileinfo->dir + (cookie-cookie cvs-buffer-name + (car tin-list))))) + (arg-list + (funcall extractor + (cookie-cookie cvs-buffer-name (car tin-list))))) + + ;; Execute the command unless extractor returned t. + + (if (eq arg-list t) + nil + (insert (format "=== cd %s\n" default-directory)) + (insert (format "=== %s %s\n\n" + program + (mapconcat '(lambda (foo) foo) + (nconc (copy-sequence constant-args) + arg-list) + " "))) + (apply 'call-process program nil t t + (nconc (copy-sequence constant-args) arg-list)) + (goto-char (point-max)))) + (setq tin-list (cdr tin-list)))) + + +(defun cvs-edit-mode () + "\\<cvs-edit-mode-map>Mode for editing cvs log messages. +Commands: +\\[cvs-edit-done] checks in the file when you are ready. +This mode is based on fundamental mode." + (interactive) + (use-local-map cvs-edit-mode-map) + (setq major-mode 'cvs-edit-mode) + (setq mode-name "CVS Log") + (auto-fill-mode 1)) + + +(if cvs-edit-mode-map + nil + (setq cvs-edit-mode-map (make-sparse-keymap)) + (define-prefix-command 'cvs-control-c-prefix) + (define-key cvs-edit-mode-map "\C-c" 'cvs-control-c-prefix) + (define-key cvs-edit-mode-map "\C-c\C-c" 'cvs-edit-done)) + + +(defun cvs-diff-cvs () + "Diff the selected files against the repository. +The flags the variable cvs-cvs-diff-flags will be passed to ``cvs diff''." + (interactive) + + (save-some-buffers) + (let ((marked (cvs-get-marked))) + (cvs-use-temp-buffer) + (message "cvsdiffing...") + (cvs-execute-list marked cvs-program (cons "diff" cvs-cvs-diff-flags))) + (message "cvsdiffing... Done.")) + + +(defun cvs-backup-diffable (tin cvs-buf) + "Check if the TIN is backup-diffable. +It must have a backup file to be diffable." + (cvs-fileinfo->backup-file (cookie-cookie cvs-buf tin))) + +(defun cvs-diff-backup () + "Diff the files against the backup file. +This command can be used on files that are marked with \"Merged\" +or \"Conflict\" in the *cvs* buffer. + +The flags in cvs-diff-flags will be passed to ``diff''." + + (interactive) + (save-some-buffers) + (let ((marked (cvs-filter (function cvs-backup-diffable) + (cvs-get-marked) + (current-buffer)))) + (if (null marked) + (error "No ``Conflict'' or ``Merged'' file selected!")) + (cvs-use-temp-buffer) + (message "diffing...") + (cvs-execute-single-file-list + marked 'cvs-diff-backup-extractor cvs-diff-program cvs-diff-flags)) + (message "diffing... Done.")) + + +(defun cvs-diff-backup-extractor (fileinfo) + "Return the filename and the name of the backup file as a list. +Signal an error if there is no backup file." + (if (null (cvs-fileinfo->backup-file fileinfo)) + (error "%s has no backup file." + (concat + (file-name-as-directory (cvs-fileinfo->dir fileinfo)) + (cvs-fileinfo->file-name fileinfo)))) + (list (cvs-fileinfo->file-name fileinfo) + (cvs-fileinfo->backup-file fileinfo))) + +(defun cvs-find-file-other-window (pos) + "Select a buffer containing the file in another window. +Args: POS" + (interactive "d") + (save-some-buffers) + (let* ((cookie-last-tin + (cookie-get-selection (current-buffer) pos cookie-last-tin)) + (type (cvs-fileinfo->type (cookie-cookie (current-buffer) + cookie-last-tin)))) + (cond + ((or (eq type 'REMOVED) + (eq type 'CVS-REMOVED)) + (error "Can't visit a removed file.")) + ((eq type 'DIRCHANGE) + (let ((obuf (current-buffer)) + (odir default-directory)) + (setq default-directory + (file-name-as-directory + (cvs-fileinfo->dir + (cookie-cookie (current-buffer) cookie-last-tin)))) + (dired-other-window default-directory) + (set-buffer obuf) + (setq default-directory odir))) + (t + (find-file-other-window (cvs-full-path (current-buffer) + cookie-last-tin)))))) + +(defun cvs-full-path (buffer tin) + "Return the full path for the file that is described in TIN. +Args: BUFFER TIN." + (concat + (file-name-as-directory + (cvs-fileinfo->dir (cookie-cookie buffer tin))) + (cvs-fileinfo->file-name (cookie-cookie buffer tin)))) + +(defun cvs-find-file (pos) + "Select a buffer containing the file in another window. +Args: POS" + (interactive "d") + (let* ((cvs-buf (current-buffer)) + (cookie-last-tin (cookie-get-selection cvs-buf pos cookie-last-tin)) + (fileinfo (cookie-cookie cvs-buf cookie-last-tin)) + (type (cvs-fileinfo->type fileinfo))) + (cond + ((or (eq type 'REMOVED) + (eq type 'CVS-REMOVED)) + (error "Can't visit a removed file.")) + ((eq type 'DIRCHANGE) + (let ((odir default-directory)) + (setq default-directory + (file-name-as-directory (cvs-fileinfo->dir fileinfo))) + (dired default-directory) + (set-buffer cvs-buf) + (setq default-directory odir))) + (t + (find-file (cvs-full-path cvs-buf cookie-last-tin)))))) + +(defun cvs-mark-all-files () + "Mark all files. +Directories are not marked." + (interactive) + (cookie-map (function (lambda (cookie) + (cond + ((not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE)) + (cvs-set-fileinfo->marked cookie t) + t)))) + (current-buffer))) + + +(defun cvs-unmark (pos) + "Unmark a fileinfo. Args: POS." + (interactive "d") + + (let* ((tin (cookie-get-selection + (current-buffer) pos cookie-last-tin)) + (sel (cookie-cookie (current-buffer) tin))) + + (cond + ((eq (cvs-fileinfo->type sel) 'DIRCHANGE) + (cookie-map + (function (lambda (f dir) + (cond + ((cvs-dir-member-p f dir) + (cvs-set-fileinfo->marked f nil) + t)))) + (current-buffer) + (cvs-fileinfo->dir sel))) + (t + (cvs-set-fileinfo->marked sel nil) + (cookie-invalidate-tins (current-buffer) tin) + (cookie-next-cookie (current-buffer) pos 1))))) + +(defun cvs-unmark-all-files () + "Unmark all files. +Directories are also unmarked, but that doesn't matter, since +they should always be unmarked." + (interactive) + (cookie-map (function (lambda (cookie) + (cvs-set-fileinfo->marked cookie nil) + t)) + (current-buffer))) + + +(defun cvs-do-removal (cvs-buf tins) + "Remove files. +Args: CVS-BUF TINS. +CVS-BUF is the cvs buffer. TINS is a list of tins that the +user wants to delete. The files are deleted. If the type of +the tin is 'UNKNOWN the tin is removed from the buffer. If it +is anything else the file is added to a list that should be +`cvs remove'd and the tin is changed to be of type 'REMOVED. + +Returns a list of tins files that should be `cvs remove'd." + (cvs-use-temp-buffer) + (mapcar 'cvs-insert-full-path tins) + (cond + ((and tins (yes-or-no-p (format "Delete %d files? " (length tins)))) + (let (files-to-remove) + (while tins + (let* ((tin (car tins)) + (fileinfo (cookie-cookie cvs-buf tin)) + (type (cvs-fileinfo->type fileinfo))) + (if (not (or (eq type 'REMOVED) (eq type 'CVS-REMOVED))) + (progn + (delete-file (cvs-full-path cvs-buf tin)) + (cond + ((or (eq type 'UNKNOWN) (eq type 'MOVE-AWAY)) + (cookie-delete cvs-buf tin)) + (t + (setq files-to-remove (cons tin files-to-remove)) + (cvs-set-fileinfo->type fileinfo 'REMOVED) + (cvs-set-fileinfo->handled fileinfo nil) + (cookie-invalidate-tins cvs-buf tin)))))) + (setq tins (cdr tins))) + files-to-remove)) + (t nil))) + + + +(defun cvs-remove-file () + "Remove all marked files." + (interactive) + (let ((files-to-remove (cvs-do-removal (current-buffer) (cvs-get-marked)))) + (if (null files-to-remove) + nil + (cvs-use-temp-buffer) + (message "removing from repository...") + (cvs-execute-list files-to-remove cvs-program '("remove")) + (message "removing from repository... done.")))) + +(defun cvs-acknowledge () + "Remove all marked files from the buffer." + (interactive) + + (mapcar (function (lambda (tin) + (cookie-delete (current-buffer) tin))) + (cvs-get-marked)) + (setq cookie-last-tin nil)) + + +(defun cvs-unmark-up (pos) + "Unmark the file on the previous line. +Takes one argument POS, a buffer position." + (interactive "d") + (cookie-previous-cookie (current-buffer) pos 1) + (cvs-set-fileinfo->marked (cookie-cookie (current-buffer) cookie-last-tin) + nil) + (cookie-invalidate-tins (current-buffer) cookie-last-tin)) + +(defun cvs-add-file-update-buffer (cvs-buf tin) + "Subfunction to cvs-add. Internal use only. +Update the display. Return non-nil if `cvs add' should be called on this +file. Args: CVS-BUF TIN. +Returns 'ADD or 'RESURRECT." + (let ((fileinfo (cookie-cookie cvs-buf tin))) + (cond + ((eq (cvs-fileinfo->type fileinfo) 'UNKNOWN) + (cvs-set-fileinfo->type fileinfo 'ADDED) + (cookie-invalidate-tins cvs-buf tin) + 'ADD) + ((eq (cvs-fileinfo->type fileinfo) 'REMOVED) + (cvs-set-fileinfo->type fileinfo 'UPDATED) + (cvs-set-fileinfo->handled fileinfo t) + (cookie-invalidate-tins cvs-buf tin) + 'RESURRECT)))) + +(defun cvs-add-sub (cvs-buf candidates) + "Internal use only. +Args: CVS-BUF CANDIDATES. +CANDIDATES is a list of tins. Updates the CVS-BUF and returns a pair of lists. +The first list is unknown tins that shall be `cvs add -m msg'ed. +The second list is removed files that shall be `cvs add'ed (resurrected)." + (let (add resurrect) + (while candidates + (let ((type (cvs-add-file-update-buffer cvs-buf (car candidates)))) + (cond ((eq type 'ADD) + (setq add (cons (car candidates) add))) + ((eq type 'RESURRECT) + (setq resurrect (cons (car candidates) resurrect))))) + (setq candidates (cdr candidates))) + (cons add resurrect))) + +(defun cvs-add () + "Add marked files to the cvs repository." + (interactive) + + (let* ((buf (current-buffer)) + (result (cvs-add-sub buf (cvs-get-marked))) + (added (car result)) + (resurrect (cdr result)) + (msg (if added (read-from-minibuffer "Enter description: ")))) + + (if (or resurrect added) + (cvs-use-temp-buffer)) + + (cond (resurrect + (message "Resurrecting files from repository...") + (cvs-execute-list resurrect cvs-program '("add")) + (message "Done."))) + + (cond (added + (message "Adding new files to repository...") + (cvs-execute-list added cvs-program (list "add" "-m" msg)) + (message "Done."))))) + +(defun cvs-ignore () + "Arrange so that CVS ignores the selected files. +This command ignores files that are not flagged as `Unknown'." + (interactive) + + (mapcar (function (lambda (tin) + (cond + ((eq (cvs-fileinfo->type + (cookie-cookie (current-buffer) tin)) 'UNKNOWN) + (cvs-append-to-ignore + (cookie-cookie (current-buffer) tin)) + (cookie-delete (current-buffer) tin))))) + (cvs-get-marked)) + (setq cookie-last-tin nil)) + +(defun cvs-append-to-ignore (fileinfo) + "Append the file in fileinfo to the .cvsignore file" + (save-window-excursion + (set-buffer (find-file-noselect (concat (file-name-as-directory + (cvs-fileinfo->dir fileinfo)) + ".cvsignore"))) + (goto-char (point-max)) + (if (not (zerop (current-column))) + (insert "\n")) + (insert (cvs-fileinfo->file-name fileinfo) "\n") + (save-buffer))) + +(defun cvs-status () + "Show cvs status for all marked files." + (interactive) + + (save-some-buffers) + (let ((marked (cvs-get-marked))) + (cvs-use-temp-buffer) + (message "Running cvs status ...") + (cvs-execute-list marked cvs-program (cons "status" cvs-status-flags))) + (message "Running cvs status ... Done.")) + +(defun cvs-log () + "Display the cvs log of all selected files." + (interactive) + + (let ((marked (cvs-get-marked))) + (cvs-use-temp-buffer) + (message "Running cvs log ...") + (cvs-execute-list marked cvs-program (cons "log" cvs-log-flags))) + (message "Running cvs log ... Done.")) + + +(defun cvs-insert-full-path (tin) + "Insert full path to the file described in TIN." + (insert (format "%s\n" (cvs-full-path cvs-buffer-name tin)))) + + +(defun cvs-add-change-log-entry-other-window (pos) + "Add a ChangeLog entry in the ChangeLog of the current directory. +Args: POS." + (interactive "d") + (let* ((cvs-buf (current-buffer)) + (odir default-directory)) + (setq default-directory + (file-name-as-directory + (cvs-fileinfo->dir + (cookie-cookie + cvs-buf + (cookie-get-selection cvs-buf pos cookie-last-tin))))) + (if (not default-directory) ;In case there was no entries. + (setq default-directory odir)) + (add-change-log-entry-other-window) + (set-buffer cvs-buf) + (setq default-directory odir))) + + +(defun print-cvs-tin (foo) + "Debug utility." + (let ((cookie (cookie-cookie (current-buffer) foo)) + (stream (get-buffer-create "debug"))) + (princ "==============\n" stream) + (princ (cvs-fileinfo->file-name cookie) stream) + (princ "\n" stream) + (princ (cvs-fileinfo->dir cookie) stream) + (princ "\n" stream) + (princ (cvs-fileinfo->full-log cookie) stream) + (princ "\n" stream) + (princ (cvs-fileinfo->marked cookie) stream) + (princ "\n" stream))) diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.info b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.info new file mode 100644 index 0000000..3c0d3c0 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.info @@ -0,0 +1,1367 @@ +Info file pcl-cvs, produced by Makeinfo, -*- Text -*- from input +file pcl-cvs.texinfo. + + Copyright (C) 1992 Per Cederqvist + + Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + + Permission is granted to copy and distribute modified versions of +this manual under the conditions for verbatim copying, provided also +that the section entitled "GNU General Public License" is included +exactly as in the original, and provided that the entire resulting +derived work is distributed under the terms of a permission notice +identical to this one. + + Permission is granted to copy and distribute translations of this +manual into another language, under the above conditions for +modified versions, except that the section entitled "GNU General +Public License" and this permission notice may be included in +translations approved by the Free Software Foundation instead of in +the original English. + + +File: pcl-cvs, Node: Top, Next: Copying, Prev: (dir), Up: (dir) + + This info manual describes pcl-cvs which is a GNU Emacs front-end +to CVS. It works with CVS version 1.3. This manual is updated to +release 1.02 of pcl-cvs. + +* Menu: + +* Copying:: GNU General Public License +* Installation:: How to install pcl-cvs on your system. +* About pcl-cvs:: Authors and ftp sites. + +* Getting started:: An introduction with a walk-through example. +* Buffer contents:: An explanation of the buffer contents. +* Commands:: All commands, grouped by type. + +* Customization:: How you can tailor pcl-cvs to suit your needs. +* Future enhancements:: Future enhancements of pcl-cvs. +* Reporting bugs and ideas:: Where to report bugs. + +* Function and Variable Index:: List of functions and variables. +* Concept Index:: List of concepts. +* Key Index:: List of keystrokes. + + -- The Detailed Node Listing -- + +Installation + +* Pcl-cvs installation:: How to install pcl-cvs on your system. +* On-line manual installation:: How to install the on-line manual. +* Typeset manual installation:: How to create typeset documentation + about pcl-cvs. + +About pcl-cvs + +* Contributors:: Contributors to pcl-cvs. +* Archives:: Where can I get a copy of Pcl-Cvs? + +Buffer contents + +* File status:: The meaning of the second field. +* Selected files:: How selection works. + +Commands + +* Updating the directory:: Commands to update the local directory +* Movement commands:: How to move up and down in the buffer +* Marking files:: How to mark files that other commands + will later operate on. +* Committing changes:: Checking in your modifications to the + CVS repository. +* Editing files:: Loading files into Emacs. +* Getting info about files:: Display the log and status of files. +* Adding and removing files:: Adding and removing files +* Removing handled entries:: Uninteresting lines can easily be removed. +* Ignoring files:: Telling CVS to ignore generated files. +* Viewing differences:: Commands to `diff' different versions. + + +File: pcl-cvs, Node: Copying, Next: Installation, Prev: Top, Up: Top + +GNU GENERAL PUBLIC LICENSE +************************** + + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +Preamble +======== + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. +This General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit +to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can +apply it to your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and +charge for this service if you wish), that you receive source code +or can get it if you want it, that you can change the software or +use pieces of it in new free programs; and that you know you can do +these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the +rights. These restrictions translate to certain responsibilities +for you if you distribute copies of the software, or if you modify +it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the +software, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make +certain that everyone understands that there is no warranty for this +free software. If the software is modified by someone else and +passed on, we want its recipients to know that what they have is not +the original, so that any problems introduced by others will not +reflect on the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making +the program proprietary. To prevent this, we have made it clear +that any patent must be licensed for everyone's free use or not +licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 1. This License applies to any program or other work which contains + a notice placed by the copyright holder saying it may be + distributed under the terms of this General Public License. + The "Program", below, refers to any such program or work, and a + "work based on the Program" means either the Program or any + derivative work under copyright law: that is to say, a work + containing the Program or a portion of it, either verbatim or + with modifications and/or translated into another language. + (Hereinafter, translation is included without limitation in the + term "modification".) Each licensee is addressed as "you". + + Activities other than copying, distribution and modification + are not covered by this License; they are outside its scope. + The act of running the Program is not restricted, and the + output from the Program is covered only if its contents + constitute a work based on the Program (independent of having + been made by running the Program). Whether that is true + depends on what the Program does. + + 2. You may copy and distribute verbatim copies of the Program's + source code as you receive it, in any medium, provided that you + conspicuously and appropriately publish on each copy an + appropriate copyright notice and disclaimer of warranty; keep + intact all the notices that refer to this License and to the + absence of any warranty; and give any other recipients of the + Program a copy of this License along with the Program. + + You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + + 3. You may modify your copy or copies of the Program or any portion + of it, thus forming a work based on the Program, and copy and + distribute such modifications or work under the terms of + Section 1 above, provided that you also meet all of these + conditions: + + 1. You must cause the modified files to carry prominent + notices stating that you changed the files and the date of + any change. + + 2. You must cause any work that you distribute or publish, + that in whole or in part contains or is derived from the + Program or any part thereof, to be licensed as a whole at + no charge to all third parties under the terms of this + License. + + 3. If the modified program normally reads commands + interactively when run, you must cause it, when started + running for such interactive use in the most ordinary way, + to print or display an announcement including an + appropriate copyright notice and a notice that there is no + warranty (or else, saying that you provide a warranty) and + that users may redistribute the program under these + conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive + but does not normally print such an announcement, your + work based on the Program is not required to print an + announcement.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the + Program, and can be reasonably considered independent and + separate works in themselves, then this License, and its terms, + do not apply to those sections when you distribute them as + separate works. But when you distribute the same sections as + part of a whole which is a work based on the Program, the + distribution of the whole must be on the terms of this License, + whose permissions for other licensees extend to the entire + whole, and thus to each and every part regardless of who wrote + it. + + Thus, it is not the intent of this section to claim rights + or contest your rights to work written entirely by you; rather, + the intent is to exercise the right to control the distribution + of derivative or collective works based on the Program. + + In addition, mere aggregation of another work not based on + the Program with the Program (or with a work based on the + Program) on a volume of a storage or distribution medium does + not bring the other work under the scope of this License. + + 4. You may copy and distribute the Program (or a work based on it, + under Section 2) in object code or executable form under the + terms of Sections 1 and 2 above provided that you also do one + of the following: + + 1. Accompany it with the complete corresponding + machine-readable source code, which must be distributed + under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + 2. Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than + your cost of physically performing source distribution, a + complete machine-readable copy of the corresponding source + code, to be distributed under the terms of Sections 1 and + 2 above on a medium customarily used for software + interchange; or, + + 3. Accompany it with the information you received as to the + offer to distribute corresponding source code. (This + alternative is allowed only for noncommercial distribution + and only if you received the program in object code or + executable form with such an offer, in accord with + Subsection b above.) + + The source code for a work means the preferred form of the + work for making modifications to it. For an executable work, + complete source code means all the source code for all modules + it contains, plus any associated interface definition files, + plus the scripts used to control compilation and installation + of the executable. However, as a special exception, the source + code distributed need not include anything that is normally + distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating + system on which the executable runs, unless that component + itself accompanies the executable. + + If distribution of executable or object code is made by + offering access to copy from a designated place, then offering + equivalent access to copy the source code from the same place + counts as distribution of the source code, even though third + parties are not compelled to copy the source along with the + object code. + + 5. You may not copy, modify, sublicense, or distribute the Program + except as expressly provided under this License. Any attempt + otherwise to copy, modify, sublicense or distribute the Program + is void, and will automatically terminate your rights under + this License. However, parties who have received copies, or + rights, from you under this License will not have their + licenses terminated so long as such parties remain in full + compliance. + + 6. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to + modify or distribute the Program or its derivative works. + These actions are prohibited by law if you do not accept this + License. Therefore, by modifying or distributing the Program + (or any work based on the Program), you indicate your + acceptance of this License to do so, and all its terms and + conditions for copying, distributing or modifying the Program + or works based on it. + + 7. Each time you redistribute the Program (or any work based on the + Program), the recipient automatically receives a license from + the original licensor to copy, distribute or modify the Program + subject to these terms and conditions. You may not impose any + further restrictions on the recipients' exercise of the rights + granted herein. You are not responsible for enforcing + compliance by third parties to this License. + + 8. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent + issues), conditions are imposed on you (whether by court order, + agreement or otherwise) that contradict the conditions of this + License, they do not excuse you from the conditions of this + License. If you cannot distribute so as to satisfy + simultaneously your obligations under this License and any + other pertinent obligations, then as a consequence you may not + distribute the Program at all. For example, if a patent + license would not permit royalty-free redistribution of the + Program by all those who receive copies directly or indirectly + through you, then the only way you could satisfy both it and + this License would be to refrain entirely from distribution of + the Program. + + If any portion of this section is held invalid or + unenforceable under any particular circumstance, the balance of + the section is intended to apply and the section as a whole is + intended to apply in other circumstances. + + It is not the purpose of this section to induce you to + infringe any patents or other property right claims or to + contest validity of any such claims; this section has the sole + purpose of protecting the integrity of the free software + distribution system, which is implemented by public license + practices. Many people have made generous contributions to the + wide range of software distributed through that system in + reliance on consistent application of that system; it is up to + the author/donor to decide if he or she is willing to + distribute software through any other system and a licensee + cannot impose that choice. + + This section is intended to make thoroughly clear what is + believed to be a consequence of the rest of this License. + + 9. If the distribution and/or use of the Program is restricted in + certain countries either by patents or by copyrighted + interfaces, the original copyright holder who places the + Program under this License may add an explicit geographical + distribution limitation excluding those countries, so that + distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the + limitation as if written in the body of this License. + + 10. The Free Software Foundation may publish revised and/or new + versions of the General Public License from time to time. Such + new versions will be similar in spirit to the present version, + but may differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If + the Program specifies a version number of this License which + applies to it and "any later version", you have the option of + following the terms and conditions either of that version or of + any later version published by the Free Software Foundation. + If the Program does not specify a version number of this + License, you may choose any version ever published by the Free + Software Foundation. + + 11. If you wish to incorporate parts of the Program into other free + programs whose distribution conditions are different, write to + the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free + Software Foundation; we sometimes make exceptions for this. + Our decision will be guided by the two goals of preserving the + free status of all derivatives of our free software and of + promoting the sharing and reuse of software generally. + + NO WARRANTY + + 12. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE + LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS + WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE + COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 13. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY + MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE + LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, + INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR + INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS + OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY + YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE + WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + +Appendix: How to Apply These Terms to Your New Programs +======================================================= + + If you develop a new program, and you want it to be of the +greatest possible use to the public, the best way to achieve this is +to make it free software which everyone can redistribute and change +under these terms. + + To do so, attach the following notices to the program. It is +safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + ONE LINE TO GIVE THE PROGRAM'S NAME AND A BRIEF IDEA OF WHAT IT DOES. + Copyright (C) 19YY NAME OF AUTHOR + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + Also add information on how to contact you by electronic and +paper mail. + + If the program is interactive, make it output a short notice like +this when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19YY NAME OF AUTHOR + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and +`show c'; they could even be mouse-clicks or menu items--whatever +suits your program. + + You should also get your employer (if you work as a programmer) +or your school, if any, to sign a "copyright disclaimer" for the +program, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + SIGNATURE OF TY COON, 1 April 1989 + Ty Coon, President of Vice + + This General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking +proprietary applications with the library. If this is what you want +to do, use the GNU Library General Public License instead of this +License. + + +File: pcl-cvs, Node: Installation, Next: About pcl-cvs, Prev: Copying, Up: Top + +Installation +************ + + This section describes the installation of pcl-cvs, the GNU Emacs +CVS front-end. You should install not only the elisp files +themselves, but also the on-line documentation so that your users +will know how to use it. You can create typeset documentation from +the file `pcl-cvs.texinfo' as well as an on-line info file. The +following steps are also described in the file `INSTALL' in the +source directory. + +* Menu: + +* Pcl-cvs installation:: How to install pcl-cvs on your system. +* On-line manual installation:: How to install the on-line manual. +* Typeset manual installation:: How to create typeset documentation + about pcl-cvs. + + +File: pcl-cvs, Node: Pcl-cvs installation, Next: On-line manual installation, Prev: Installation, Up: Installation + +Installation of the pcl-cvs program +=================================== + + 1. Edit the file `Makefile' to reflect the situation at your site. + The only things you have to change is the definition of + `lispdir' and `infodir'. The elisp files will be copied to + `lispdir', and the info file to `infodir'. + + 2. Configure pcl-cvs.el + + There are a couple of paths that you have to check to make + sure that they match you system. They appear early in the file + pcl-cvs.el. + + *NOTE:* If your system is running emacs 18.57 or earlier + you MUST uncomment the line that says: + + (setq delete-exited-processes nil) + + Setting `delete-exited-processes' to `nil' works around a bug + in emacs that causes it to dump core. The bug was fixed in + emacs 18.58. + + 3. Type `make install' in the source directory. This will + byte-compile all `.el' files and copy both the `.el' and the + `.elc' into the directory you specified in step 1. + + If you don't want to install the `.el' files but only the + `.elc' files (the byte-compiled files), you can type ``make + install_elc'' instead of ``make install''. + + If you only want to create the compiled elisp files, but + don't want to install them, you can type `make elcfiles' + instead. This is what happens if you only type `make' without + parameters. + + 4. Edit the file `default.el' in your emacs lisp directory (usually + `/usr/gnu/emacs/lisp' or something similar) and enter the + contents of the file `pcl-cvs-startup.el' into it. It contains + a couple of `auto-load's that facilitates the use of pcl-cvs. + + +File: pcl-cvs, Node: On-line manual installation, Next: Typeset manual installation, Prev: Pcl-cvs installation, Up: Installation + +Installation of the on-line manual. +=================================== + + 1. Create the info file `pcl-cvs' from `pcl-cvs.texinfo' by typing + `make info'. If you don't have the program `makeinfo' you can + get it by anonymous ftp from e.g. `ftp.gnu.ai.mit.edu' as + `pub/gnu/texinfo-2.14.tar.Z' (there might be a newer version + there when you read this), or you could use the preformatted + info file `pcl-cvs.info' that is included in the distribution + (type `cp pcl-cvs.info pcl-cvs'). + + 2. Move the info file `pcl-cvs' to your standard info directory. + This might be called something like `/usr/gnu/emacs/info'. + + 3. Edit the file `dir' in the info directory and enter one line to + contain a pointer to the info file `pcl-cvs'. The line can, for + instance, look like this: + + * Pcl-cvs: (pcl-cvs). An Emacs front-end to CVS. + + +File: pcl-cvs, Node: Typeset manual installation, Prev: On-line manual installation, Up: Installation + +How to make typeset documentation from pcl-cvs.texinfo +====================================================== + + If you have TeX installed at your site, you can make a typeset +manual from `pcl-cvs.texinfo'. + + 1. Run TeX by typing ``make pcl-cvs.dvi''. You will not get the + indices unless you have the `texindex' program. + + 2. Convert the resulting device independent file `pcl-cvs.dvi' to a + form which your printer can output and print it. If you have a + postscript printer there is a program, `dvi2ps', which does. + There is also a program which comes together with TeX, `dvips', + which you can use. + + +File: pcl-cvs, Node: About pcl-cvs, Next: Getting started, Prev: Installation, Up: Top + +About pcl-cvs +************* + + Pcl-cvs is a front-end to CVS version 1.3. It integrates the most +frequently used CVS commands into emacs. + +* Menu: + +* Contributors:: Contributors to pcl-cvs. +* Archives:: Where can I get a copy of Pcl-Cvs? + + +File: pcl-cvs, Node: Contributors, Next: Archives, Prev: About pcl-cvs, Up: About pcl-cvs + +Contributors to pcl-cvs +======================= + + Contributions to the package are welcome. I have limited time to +work on this project, but I will gladly add any code that you +contribute to me to this package (*note Reporting bugs and ideas::.). + + The following persons have made contributions to pcl-cvs. + + * Brian Berliner wrote CVS, together with some other contributors. + Without his work on CVS this package would be useless... + + * Per Cederqvist wrote most of the otherwise unattributed + functions in pcl-cvs as well as all documentation. + + * Inge Wallin (`inge@lysator.liu.se') wrote the skeleton to + `pcl-cvs.texinfo', and gave useful comments on it. He also + wrote the files `elib-node.el' and `compile-all.el'. The file + `cookie.el' was inspired by Inge. + + * Linus Tolke (`linus@lysator.liu.se') contributed useful comments + on both the functionality and the documentation. + + +File: pcl-cvs, Node: Archives, Prev: Contributors, Up: About pcl-cvs + +Where can I get pcl-cvs? +======================== + + This release of pcl-cvs is included in the CVS 1.3 distribution. +However, since pcl-cvs has had less time to mature (the first line of +code was written less than a year ago) it is likely that there will +be a new release of pcl-cvs before the next release of CVS. + + The latest release of pcl-cvs can be fetched via anonymous ftp +from `ftp.lysator.liu.se', (IP no. 130.236.254.1) in the directory +`pub/emacs'. If you don't live in Scandinavia you should probably +check with archie to see if there is a site closer to you that +archives pcl-cvs. + + New releases will be announced to appropriate newsgroups. If you +send your email address to me I will add you to my list of people to +mail when I make a new release. + + +File: pcl-cvs, Node: Getting started, Next: Buffer contents, Prev: About pcl-cvs, Up: Top + +Getting started +*************** + + This document assumes that you know what CVS is, and that you at +least knows the fundamental concepts of CVS. If that is not the +case you should read the man page for CVS. + + Pcl-cvs is only useful once you have checked out a module. So +before you invoke it you must have a copy of a module somewhere in +the file system. + + You invoke pcl-cvs by typing `M-x pcl-cvs RET'. If your emacs +responds with `[No match]' your system administrator has not +installed pcl-cvs properly. Try `M-x load-library RET pcl-cvs RET'. +If that also fails - talk to your root. If it succeeds you might put +this line in your `.emacs' file so that you don't have to type the +`load-library' command every time you wish to use pcl-cvs: + + (autoload 'cvs-update "pcl-cvs" nil t) + + The function `cvs-update' will ask for a directory. The command +`cvs update' will be run in that directory. (It should contain +files that have been checked out from a CVS archive.) The output +from `cvs' will be parsed and presented in a table in a buffer called +`*cvs*'. It might look something like this: + + PCL-CVS release 1.02. + + In directory /users/ceder/FOO/test: + Updated bar + Updated file.txt + Modified ci namechange + Updated newer + + In directory /users/ceder/FOO/test/sub: + Modified ci ChangeLog + ---------- End ----- + + In this example the three files (`bar', `file.txt' and `newer') +that are marked with `Updated' have been copied from the CVS +repository to `/users/ceder/FOO/test/' since someone else have +checked in newer versions of them. Two files (`namechange' and +`sub/ChangeLog') have been modified locally, and needs to be checked +in. + + You can move the cursor up and down in the buffer with `C-n' and +`C-p' or `n' and `p'. If you press `c' on one of the `Modified' +files that file will be checked in to the CVS repository. *Note +Committing changes::. You can press `x' to get rid of the +"uninteresting" files that have only been `Updated' (and don't +require any further action from you). + + You can also easily get a `diff' between your modified file and +the base version that you started from, and you can get the output +from `cvs log' and `cvs status' on the listed files simply by +pressing a key (*note Getting info about files::.). + + +File: pcl-cvs, Node: Buffer contents, Next: Commands, Prev: Getting started, Up: Top + +Buffer contents +*************** + + The display contains four columns. They contain, from left to +right: + + * An asterisk when the file is "marked" (*note Selected files::.). + + * The status of the file. See *Note File status::, for more + information. + + * A "need to be checked in"-marker (`ci'). + + * The file name. + +* Menu: + +* File status:: The meaning of the second field. +* Selected files:: How selection works. + + +File: pcl-cvs, Node: File status, Next: Selected files, Prev: Buffer contents, Up: Buffer contents + +File status +=========== + + The `file status' field can have the following values: + +`Updated' + The file was brought up to date with respect to the repository. + This is done for any file that exists in the repository but + not in your source, and for files that you haven't changed but + are not the most recent versions available in the repository. + +`Modified' + The file is modified in your working directory, and there + was no modification to the same file in the repository. + +`Merged' + The file is modified in your working directory, and there were + modifications in the repository as well as in your copy, but + they were merged successfully, without conflict, in your + working directory. + +`Conflict' + A conflict was detected while trying to merge your changes to + FILE with changes from the source repository. FILE (the copy + in your working directory) is now the output of the `rcsmerge' + command on the two versions; an unmodified copy of your file is + also in your working directory, with the name `.#FILE.VERSION', + where VERSION is the RCS revision that your modified file + started from. *Note Viewing differences::, for more details. + +`Added' + The file has been added by you, but it still needs to be + checked in to the repository. + +`Removed' + The file has been removed by you, but it needs to be checked in + to the repository. You can resurrect it by typing `a' (*note + Adding and removing files::.). + +`Unknown' + A file that was detected in your directory, but that neither + appears in the repository, nor is present on the list of files + that CVS should ignore. + + There are also a few special cases, that rarely occur, which have +longer strings in the fields: + +`Removed from repository' + The file has been removed from your directory since someone has + removed it from the repository. (It is still present in the + Attic directory, so no permanent loss has occurred). This, + unlike the other entries in this table, is not an error + condition. + +`Removed from repository, changed by you' + You have modified a file that someone have removed from the + repository. You can correct this situation by removing the + file manually (see *note Adding and removing files::.). + +`Removed by you, changed in repository' + You have removed a file, and before you committed the removal + someone committed a change to that file. You could use `a' to + resurrect the file (see *note Adding and removing files::.). + +`Move away FILE - it is in the way' + For some reason CVS does not like the file FILE. Rename or + remove it. + +`This repository is missing! Remove this dir manually.' + It is impossible to remove a directory in the CVS repository in + a clean way. Someone have tried to remove one, and CVS gets + confused. Remove your copy of the directory. + + +File: pcl-cvs, Node: Selected files, Prev: File status, Up: Buffer contents + +Selected files +============== + + Many of the commands works on the current set of "selected" files. + + * If there are any files that are marked they constitute the set + of selected files. + + * Otherwise, if the cursor points to a file, that file is the + selected file. + + * Otherwise, if the cursor points to a directory, all the files + in that directory that appears in the buffer are the selected + files. + + This scheme might seem a little complicated, but once one get +used to it, it is quite powerful. + + *Note Marking files:: tells how you mark and unmark files. + + +File: pcl-cvs, Node: Commands, Next: Customization, Prev: Buffer contents, Up: Top + +Commands +******** + + The nodes in this menu contains explanations about all the +commands that you can use in pcl-cvs. They are grouped together by +type. + +* Menu: + +* Updating the directory:: Commands to update the local directory +* Movement commands:: How to move up and down in the buffer +* Marking files:: How to mark files that other commands + will later operate on. +* Committing changes:: Checking in your modifications to the + CVS repository. +* Editing files:: Loading files into Emacs. +* Getting info about files:: Display the log and status of files. +* Adding and removing files:: Adding and removing files +* Removing handled entries:: Uninteresting lines can easily be removed. +* Ignoring files:: Telling CVS to ignore generated files. +* Viewing differences:: Commands to `diff' different versions. + + +File: pcl-cvs, Node: Updating the directory, Next: Movement commands, Prev: Commands, Up: Commands + +Updating the directory +====================== + +`M-x cvs-update' + Run a `cvs update' command. You will be asked for the + directory in which the `cvs update' will be run. The output + will be parsed by pcl-cvs, and the result printed in the + `*cvs*' buffer (see *note Buffer contents::. for a description + of the contents). + + By default, `cvs-update' will descend recursively into + subdirectories. You can avoid that behavior by giving a prefix + argument to it (e.g., by typing `C-u M-x cvs-update RET'). + + All other commands in pcl-cvs requires that you have a `*cvs*' + buffer. This is the command that you use to get one. + +`g' + This will run `cvs update' again. It will always use the same + buffer that was used with the previous `cvs update'. Give a + prefix argument to avoid descending into subdirectories. This + runs the command `cvs-update-no-prompt'. + + +File: pcl-cvs, Node: Movement commands, Next: Marking files, Prev: Updating the directory, Up: Commands + +Movement Commands +================= + + You can use most normal Emacs commands to move forward and +backward in the buffer. Some keys are rebound to functions that +take advantage of the fact that the buffer is a pcl-cvs buffer: + +`SPC' +`C-n' +`n' + These keys move the cursor one file forward, towards the end of + the buffer (`cookie-next-cookie'). + +`C-p' +`p' + These keys move one file backward, towards the beginning of the + buffer (`cookie-previous-cookie'). + + +File: pcl-cvs, Node: Marking files, Next: Committing changes, Prev: Movement commands, Up: Commands + +Marking files +============= + + Pcl-cvs works on a set of "selected files" (*note Selected +files::.). You can mark and unmark files with these commands: + +`m' + This marks the file that the cursor is positioned on. If the + cursor is positioned on a directory all files in that directory + will be marked. (`cvs-mark'). + +`u' + Unmark the file that the cursor is positioned on. If the cursor + is on a directory, all files in that directory will be unmarked. + (`cvs-unmark'). + +`M' + Mark *all* files in the buffer (`cvs-mark-all-files'). + +`U' + Unmark *all* files (`cvs-unmark-all-files'). + +`DEL' + Unmark the file on the previous line, and move point to that + line (`cvs-unmark-up'). + + +File: pcl-cvs, Node: Committing changes, Next: Editing files, Prev: Marking files, Up: Commands + +Committing changes +================== + +`c' + All files that have a "need to be checked in"-marker (*note + Buffer contents::.) can be checked in with the `c' command. It + checks in all selected files (*note Selected files::.) (except + those who lack the "ci"-marker - they are ignored). Pressing + `c' causes `cvs-commit' to be run. + + When you press `c' you will get a buffer called + `*cvs-commit-message*'. Enter the log message for the file(s) + in it. When you are ready you should press `C-c C-c' to + actually commit the files (using `cvs-edit-done'). + + Normally the `*cvs-commit-message*' buffer will retain the log + message from the previous commit, but if the variable + `cvs-erase-input-buffer' is set to a non-nil value the buffer + will be erased. Point and mark will always be located around + the entire buffer so that you can easily erase it with `C-w' + (`kill-region'). + + +File: pcl-cvs, Node: Editing files, Next: Getting info about files, Prev: Committing changes, Up: Commands + +Editing files +============= + + There are currently three commands that can be used to find a +file (that is, load it into a buffer and start editing it there). +These commands work on the line that the cursor is situated at. +They ignore any marked files. + +`f' + Find the file that the cursor points to. Run `dired' + + (*note Dired: (Emacs)Dired.) + + if the cursor points to a directory (`cvs-find-file'). + +`o' + Like `f', but use another window (`cvs-find-file-other-window'). + +`A' + Invoke `add-change-log-entry-other-window' to edit a + `ChangeLog' file. The `ChangeLog' will be found in the + directory of the file the cursor points to. + (`cvs-add-change-log-entry-other-window'). + + +File: pcl-cvs, Node: Getting info about files, Next: Adding and removing files, Prev: Editing files, Up: Commands + +Getting info about files +======================== + + Both of the following commands can be customized. *Note +Customization::. + +`l' + Run `cvs log' on all selected files, and show the result in a + temporary buffer (`cvs-log'). + +`s' + Run `cvs status' on all selected files, and show the result in a + temporary buffer (`cvs-status'). + + +File: pcl-cvs, Node: Adding and removing files, Next: Removing handled entries, Prev: Getting info about files, Up: Commands + +Adding and removing files +========================= + + The following commands are available to make it easy to add and +remove files from the CVS repository. + +`a' + Add all selected files. This command can be used on `Unknown' + files (see *note File status::.). The status of the file will + change to `Added', and you will have to use `c' (`cvs-commit', + see *note Committing changes::.) to really add the file to the + repository. + + This command can also be used on `Removed' files (before you + commit them) to resurrect them. + + Selected files that are neither `Unknown' nor `Removed' will be + ignored by this command. + + The command that is run is `cvs-add'. + +`r' + This command removes the selected files (after prompting for + confirmation). The files are `rm'ed from your directory and + (unless the status was `Unknown'; *note File status::.) they + will also be `cvs remove'd. If the files were `Unknown' they + will disappear from the buffer. Otherwise their status will + change to `Removed', and you must use `c' (`cvs-commit', *note + Committing changes::.) to commit the removal. + + The command that is run is `cvs-remove-file'. + + +File: pcl-cvs, Node: Removing handled entries, Next: Ignoring files, Prev: Adding and removing files, Up: Commands + +Removing handled entries +======================== + +`x' + This command allows you to remove all entries that you have + processed. More specifically, the lines for `Updated' files + (*note File status::. and files that have been checked in + (*note Committing changes::.) are removed from the buffer. If + a directory becomes empty the heading for that directory is + also removed. This makes it easier to get an overview of what + needs to be done. + + The command is called `cvs-remove-handled'. If + `cvs-auto-remove-handled' is set to non-`nil' this will + automatically be performed after every commit. + +`C-k' + This command can be used for lines that `cvs-remove-handled' + would not delete, but that you want to delete + (`cvs-acknowledge'). + + +File: pcl-cvs, Node: Ignoring files, Next: Viewing differences, Prev: Removing handled entries, Up: Commands + +Ignoring files +============== + +`i' + Arrange so that CVS will ignore the selected files. The file + names are added to the `.cvsignore' file in the corresponding + directory. If the `.cvsignore' doesn't exist it will be + created. + + The `.cvsignore' file should normally be added to the + repository, but you could ignore it also if you like it better + that way. + + This runs `cvs-ignore'. + + +File: pcl-cvs, Node: Viewing differences, Prev: Ignoring files, Up: Commands + +Viewing differences +=================== + +`d' + Display a `cvs diff' between the selected files and the RCS + version that they are based on. *Note Customization:: + describes how you can send flags to `cvs diff'. (The function + that does the job is `cvs-diff-cvs'). + +`b' + If CVS finds a conflict while merging two versions of a file + (during a `cvs update', *note Updating the directory::.) it + will save the original file in a file called `.#FILE.VERSION' + where FILE is the name of the file, and VERSION is the RCS + version number that your file was based on. + + With the `b' command you can run a `diff' on the files + `.#FILE.VERSION' and `FILE'. You can get a context- or Unidiff + by setting `cvs-diff-flags' - *note Customization::.. This + command only works on files that have status `Conflict' or + `Merged'. The name of the command is `cvs-diff-backup'. + + +File: pcl-cvs, Node: Customization, Next: Future enhancements, Prev: Commands, Up: Top + +Customization +************* + + If you have an idea about any customization that would be handy +but isn't present in this list, please tell me! *Note Reporting +bugs and ideas:: for info on how to reach me. + +`cvs-erase-input-buffer' + If set to anything else than `nil' the edit buffer will be + erased before you write the log message (*note Committing + changes::.). + +`cvs-inhibit-copyright-message' + The copyright message that is displayed on startup can be + annoying after a while. Set this variable to `t' if you want + to get rid of it. (But don't set this to `t' in the system + defaults file - new users should see this message at least + once). + +`cvs-cvs-diff-flags' + A list of strings to pass as arguments to the `cvs diff' + program. This is used by `cvs-diff-cvs' (key `d', *note + Viewing differences::.). If you prefer the Unidiff format you + could add this line to your `.emacs' file: + + (setq cvs-cvs-diff-flags '("-u")) + +`cvs-diff-flags' + Like `cvs-cvs-diff-flags', but passed to `diff'. This is used + by `cvs-diff-backup' (key `b', *note Viewing differences::.). + +`cvs-log-flags' + List of strings to send to `cvs log'. Used by `cvs-log' (key + `l', *note Getting info about files::.). + +`cvs-status-flags' + List of strings to send to `cvs status'. Used by `cvs-status' + (key `s', *note Getting info about files::.). + +`cvs-auto-remove-handled' + If this variable is set to any non-`nil' value + `cvs-remove-handled' will be called every time you check in + files, after the check-in is ready. *Note Removing handled + entries::. + + +File: pcl-cvs, Node: Future enhancements, Next: Reporting bugs and ideas, Prev: Customization, Up: Top + +Future enhancements +******************* + + Pcl-cvs is still under development and needs a number of +enhancements to be called complete. Here is my current wish-list +for future releases of pcl-cvs: + + * Dired support. I have an experimental `dired-cvs.el' that works + together with CVS 1.2. Unfortunately I wrote it on top of a + non-standard `dired.el', so it must be rewritten. + + * It should be possible to run commands such as `cvs log', `cvs + status' and `cvs commit' directly from a buffer containing a + file, instead of having to `cvs-update'. If the directory + contains many files the `cvs-update' can take quite some time, + especially on a slow machine. + + If you miss something in this wish-list, let me know! I don't +promise that I will write it, but I will at least try to coordinate +the efforts of making a good Emacs front end to CVS. See *Note +Reporting bugs and ideas:: for information about how to reach me. + + +File: pcl-cvs, Node: Reporting bugs and ideas, Next: Function and Variable Index, Prev: Future enhancements, Up: Top + +Reporting bugs and ideas +************************ + + If you find a bug or misfeature, don't hesitate to tell me! Send +email to `ceder@lysator.liu.se'. + + If you have ideas for improvements, or if you have written some +extensions to this package, I would like to hear from you. I hope +that you find this package useful! + + +File: pcl-cvs, Node: Function and Variable Index, Next: Concept Index, Prev: Reporting bugs and ideas, Up: Top + +Function and Variable Index +*************************** + +* Menu: + +* cookie-next-cookie: Movement commands. +* cookie-previous-cookie: Movement commands. +* cvs-acknowledge: Removing handled entries. +* cvs-add: Adding and removing files. +* cvs-add-change-log-entry-other-window: Editing files. +* cvs-auto-remove-handled (variable): Customization. +* cvs-commit: Committing changes. +* cvs-cvs-diff-flags (variable): Customization. +* cvs-diff-backup: Viewing differences. +* cvs-diff-cvs: Viewing differences. +* cvs-diff-flags (variable): Customization. +* cvs-erase-input-buffer (variable): Committing changes. +* cvs-erase-input-buffer (variable): Customization. +* cvs-find-file: Editing files. +* cvs-find-file-other-window: Editing files. +* cvs-inhibit-copyright-message (variable): Customization. +* cvs-log: Getting info about files. +* cvs-log-flags (variable): Customization. +* cvs-mark: Marking files. +* cvs-mark-all-files: Marking files. +* cvs-remove-file: Adding and removing files. +* cvs-remove-handled: Removing handled entries. +* cvs-status: Getting info about files. +* cvs-status-flags (variable): Customization. +* cvs-unmark: Marking files. +* cvs-unmark-all-files: Marking files. +* cvs-unmark-up: Marking files. +* cvs-update: Updating the directory. +* cvs-update-no-prompt: Updating the directory. + + +File: pcl-cvs, Node: Concept Index, Next: Key Index, Prev: Function and Variable Index, Up: Top + +Concept Index +************* + +* Menu: + +* About pcl-cvs: About pcl-cvs. +* Active files: Selected files. +* Added (file status): File status. +* Adding files: Adding and removing files. +* Archives: Archives. +* Author, how to reach: Reporting bugs and ideas. +* Authors: Contributors. +* Automatically remove handled files: Customization. +* Buffer contents: Buffer contents. +* Bugs, how to report them: Reporting bugs and ideas. +* Ci: Committing changes. +* Commit buffer: Committing changes. +* Committing changes: Committing changes. +* Conflict (file status): File status. +* Conflicts, how to resolve them: Viewing differences. +* Context diff, how to get: Customization. +* Contributors: Contributors. +* Copyright message, getting rid of it: Customization. +* Customization: Customization. +* Deleting files: Adding and removing files. +* Diff: Viewing differences. +* Dired: Editing files. +* Edit buffer: Committing changes. +* Editing files: Editing files. +* Email archives: Archives. +* Email to the author: Reporting bugs and ideas. +* Enhancements: Future enhancements. +* Erasing commit message: Committing changes. +* Erasing the input buffer: Customization. +* Example run: Getting started. +* Expunging uninteresting entries: Removing handled entries. +* File selection: Selected files. +* File status: File status. +* Finding files: Editing files. +* Ftp-sites: Archives. +* Generating a typeset manual: Typeset manual installation. +* Generating the on-line manual: On-line manual installation. +* Getting pcl-cvs: Archives. +* Getting rid of the Copyright message.: Customization. +* Getting rid of uninteresting lines: Removing handled entries. +* Getting status: Getting info about files. +* Handled lines, removing them: Removing handled entries. +* Info-file (how to generate): On-line manual installation. +* Inhibiting the Copyright message.: Customization. +* Installation: Installation. +* Installation of elisp files: Pcl-cvs installation. +* Installation of on-line manual: On-line manual installation. +* Installation of typeset manual: Typeset manual installation. +* Introduction: Getting started. +* Invoking dired: Editing files. +* Loading files: Editing files. +* Log (RCS/cvs command): Getting info about files. +* Manual installation (on-line): On-line manual installation. +* Manual installation (typeset): Typeset manual installation. +* Marked files: Selected files. +* Marking files: Marking files. +* Merged (file status): File status. +* Modified (file status): File status. +* Move away FILE - it is in the way (file status): File status. +* Movement Commands: Movement commands. +* On-line manual (how to generate): On-line manual installation. +* Printing a manual: Typeset manual installation. +* Putting files under CVS control: Adding and removing files. +* Removed (file status): File status. +* Removed by you, changed in repository (file status): File status. +* Removed from repository (file status): File status. +* Removed from repository, changed by you (file status): File status. +* Removing files: Adding and removing files. +* Removing uninteresting (processed) lines: Removing handled entries. +* Reporting bugs and ideas: Reporting bugs and ideas. +* Resurrecting files: Adding and removing files. +* Selected files: Selected files. +* Selecting files (commands to mark files): Marking files. +* Sites: Archives. +* Status (cvs command): Getting info about files. +* TeX - generating a typeset manual: Typeset manual installation. +* This repository is missing!... (file status): File status. +* Unidiff, how to get: Customization. +* Uninteresting entries, getting rid of them: Removing handled entries. +* Unknown (file status): File status. +* Updated (file status): File status. +* Variables, list of all: Customization. +* Viewing differences: Viewing differences. + + +File: pcl-cvs, Node: Key Index, Prev: Concept Index, Up: Top + +Key Index +********* + +* Menu: + +* A - add ChangeLog entry: Editing files. +* C-k - remove selected entries: Removing handled entries. +* C-n - Move down one file: Movement commands. +* C-p - Move up one file: Movement commands. +* DEL - unmark previous file: Marking files. +* M - marking all files: Marking files. +* SPC - Move down one file: Movement commands. +* U - unmark all files: Marking files. +* a - add a file: Adding and removing files. +* b - diff backup file: Viewing differences. +* c - commit files: Committing changes. +* d - run cvs diff: Viewing differences. +* f - find file or directory: Editing files. +* g - Rerun cvs update: Updating the directory. +* l - run cvs log: Getting info about files. +* m - marking a file: Marking files. +* n - Move down one file: Movement commands. +* o - find file in other window: Editing files. +* p - Move up on file: Movement commands. +* r - remove a file: Adding and removing files. +* s - run cvs status: Getting info about files. +* u - unmark a file: Marking files. +* x - remove processed entries: Removing handled entries. + + + +Tag Table: +Node: Top1004 +Node: Copying3396 +Node: Installation22716 +Node: Pcl-cvs installation23507 +Node: On-line manual installation25291 +Node: Typeset manual installation26310 +Node: About pcl-cvs27048 +Node: Contributors27417 +Node: Archives28440 +Node: Getting started29287 +Node: Buffer contents31728 +Node: File status32277 +Node: Selected files35303 +Node: Commands35976 +Node: Updating the directory37018 +Node: Movement commands38043 +Node: Marking files38629 +Node: Committing changes39456 +Node: Editing files40502 +Node: Getting info about files41335 +Node: Adding and removing files41805 +Node: Removing handled entries43145 +Node: Ignoring files44058 +Node: Viewing differences44593 +Node: Customization45595 +Node: Future enhancements47326 +Node: Reporting bugs and ideas48394 +Node: Function and Variable Index48842 +Node: Concept Index50743 +Node: Key Index55865 + +End Tag Table diff --git a/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.texinfo b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.texinfo new file mode 100644 index 0000000..5ad8db1 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/pcl-cvs/pcl-cvs.texinfo @@ -0,0 +1,1437 @@ +\input texinfo @c -*-texinfo-*- + +@comment pcl-cvs.texinfo,v 1.2 1992/04/07 20:49:23 berliner Exp +@comment Documentation for the GNU Emacs CVS mode. +@comment Copyright (C) 1992 Per Cederqvist + +@comment This file is part of the pcl-cvs distribution. + +@comment Pcl-cvs is free software; you can redistribute it and/or modify +@comment it under the terms of the GNU General Public License as published by +@comment the Free Software Foundation; either version 1, or (at your option) +@comment any later version. + +@comment Pcl-cvs is distributed in the hope that it will be useful, +@comment but WITHOUT ANY WARRANTY; without even the implied warranty of +@comment MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +@comment GNU General Public License for more details. + +@comment You should have received a copy of the GNU General Public License +@comment along with pcl-cvs; see the file COPYING. If not, write to +@comment the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +@setfilename pcl-cvs +@settitle Pcl-cvs - The Emacs Front-End to CVS +@setchapternewpage on + +@ifinfo +Copyright @copyright{} 1992 Per Cederqvist + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +@ignore +Permission is granted to process this file through Tex and print the +results, provided the printed document carries copying permission +notice identical to this one except for the removal of this paragraph +(this paragraph not being relevant to the printed manual). + +@end ignore +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +section entitled ``GNU General Public License'' is included exactly as +in the original, and provided that the entire resulting derived work is +distributed under the terms of a permission notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that the section entitled ``GNU General Public License'' and +this permission notice may be included in translations approved by the +Free Software Foundation instead of in the original English. +@end ifinfo + +@synindex vr fn +@comment The titlepage section does not appear in the Info file. +@titlepage +@sp 4 +@comment The title is printed in a large font. +@center @titlefont{User's Guide} +@sp +@center @titlefont{to} +@sp +@center @titlefont{pcl-cvs - the Emacs Front-End to CVS} +@sp 2 +@center release 1.02 +@comment -release- +@sp 3 +@center Per Cederqvist +@sp 3 +@center last updated 29 Mar 1992 +@comment -date- + +@comment The following two commands start the copyright page +@comment for the printed manual. This will not appear in the Info file. +@page +@vskip 0pt plus 1filll +Copyright @copyright{} 1992 Per Cederqvist + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +section entitled ``GNU General Public License'' is included exactly as +in the original, and provided that the entire resulting derived work is +distributed under the terms of a permission notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that the section entitled ``GNU General Public License'' and +this permission notice may be included in translations approved by the +Free Software Foundation instead of in the original English. +@end titlepage + +@comment ================================================================ +@comment The real text starts here +@comment ================================================================ + +@node Top, Copying, (dir), (dir) +@comment node-name, next, previous, up + + +@ifinfo +This info manual describes pcl-cvs which is a GNU Emacs front-end to +CVS. It works with CVS version 1.3. This manual is updated to release +1.02 of pcl-cvs. +@end ifinfo +@comment -release- + +@menu +* Copying:: GNU General Public License +* Installation:: How to install pcl-cvs on your system. +* About pcl-cvs:: Authors and ftp sites. + +* Getting started:: An introduction with a walk-through example. +* Buffer contents:: An explanation of the buffer contents. +* Commands:: All commands, grouped by type. + +* Customization:: How you can tailor pcl-cvs to suit your needs. +* Future enhancements:: Future enhancements of pcl-cvs. +* Reporting bugs and ideas:: Where to report bugs. + +* Function and Variable Index:: List of functions and variables. +* Concept Index:: List of concepts. +* Key Index:: List of keystrokes. + + --- The Detailed Node Listing --- + +Installation + +* Pcl-cvs installation:: How to install pcl-cvs on your system. +* On-line manual installation:: How to install the on-line manual. +* Typeset manual installation:: How to create typeset documentation + about pcl-cvs. + +About pcl-cvs + +* Contributors:: Contributors to pcl-cvs. +* Archives:: Where can I get a copy of Pcl-Cvs? + +Buffer contents + +* File status:: The meaning of the second field. +* Selected files:: How selection works. + +Commands + +* Updating the directory:: Commands to update the local directory +* Movement commands:: How to move up and down in the buffer +* Marking files:: How to mark files that other commands + will later operate on. +* Committing changes:: Checking in your modifications to the + CVS repository. +* Editing files:: Loading files into Emacs. +* Getting info about files:: Display the log and status of files. +* Adding and removing files:: Adding and removing files +* Removing handled entries:: Uninteresting lines can easily be removed. +* Ignoring files:: Telling CVS to ignore generated files. +* Viewing differences:: Commands to @samp{diff} different versions. +@end menu + +@node Copying, Installation, Top, Top +@unnumbered GNU GENERAL PUBLIC LICENSE +@center Version 2, June 1991 + +@display +Copyright @copyright{} 1989, 1991 Free Software Foundation, Inc. +675 Mass Ave, Cambridge, MA 02139, USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +@end display + +@unnumberedsec Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software---to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + +@iftex +@unnumberedsec TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +@end iftex +@ifinfo +@center TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +@end ifinfo + +@enumerate +@item +This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The ``Program'', below, +refers to any such program or work, and a ``work based on the Program'' +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term ``modification''.) Each licensee is addressed as ``you''. + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +@item +You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +@item +You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +@enumerate a +@item +You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + +@item +You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any +part thereof, to be licensed as a whole at no charge to all third +parties under the terms of this License. + +@item +If the modified program normally reads commands interactively +when run, you must cause it, when started running for such +interactive use in the most ordinary way, to print or display an +announcement including an appropriate copyright notice and a +notice that there is no warranty (or else, saying that you provide +a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this +License. (Exception: if the Program itself is interactive but +does not normally print such an announcement, your work based on +the Program is not required to print an announcement.) +@end enumerate + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +@item +You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +@enumerate a +@item +Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections +1 and 2 above on a medium customarily used for software interchange; or, + +@item +Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your +cost of physically performing source distribution, a complete +machine-readable copy of the corresponding source code, to be +distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +@item +Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is +allowed only for noncommercial distribution and only if you +received the program in object code or executable form with such +an offer, in accord with Subsection b above.) +@end enumerate + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +@item +You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +@item +You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +@item +Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +@item +If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +@item +If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +@item +The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and ``any +later version'', you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +@item +If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + +@iftex +@heading NO WARRANTY +@end iftex +@ifinfo +@center NO WARRANTY +@end ifinfo + +@item +BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM ``AS IS'' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +@item +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. +@end enumerate + +@iftex +@heading END OF TERMS AND CONDITIONS +@end iftex +@ifinfo +@center END OF TERMS AND CONDITIONS +@end ifinfo + +@page +@unnumberedsec Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the ``copyright'' line and a pointer to where the full notice is found. + +@smallexample +@var{one line to give the program's name and a brief idea of what it does.} +Copyright (C) 19@var{yy} @var{name of author} + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +@end smallexample + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + +@smallexample +Gnomovision version 69, Copyright (C) 19@var{yy} @var{name of author} +Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. +This is free software, and you are welcome to redistribute it +under certain conditions; type `show c' for details. +@end smallexample + +The hypothetical commands @samp{show w} and @samp{show c} should show +the appropriate parts of the General Public License. Of course, the +commands you use may be called something other than @samp{show w} and +@samp{show c}; they could even be mouse-clicks or menu items---whatever +suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a ``copyright disclaimer'' for the program, if +necessary. Here is a sample; alter the names: + +@example +Yoyodyne, Inc., hereby disclaims all copyright interest in the program +`Gnomovision' (which makes passes at compilers) written by James Hacker. + +@var{signature of Ty Coon}, 1 April 1989 +Ty Coon, President of Vice +@end example + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +@node Installation, About pcl-cvs, Copying, Top +@comment node-name, next, previous, up +@chapter Installation +@cindex Installation + +This section describes the installation of pcl-cvs, the GNU Emacs CVS +front-end. You should install not only the elisp files themselves, but +also the on-line documentation so that your users will know how to use +it. You can create typeset documentation from the file +@file{pcl-cvs.texinfo} as well as an on-line info file. The following +steps are also described in the file @file{INSTALL} in the source +directory. + +@menu +* Pcl-cvs installation:: How to install pcl-cvs on your system. +* On-line manual installation:: How to install the on-line manual. +* Typeset manual installation:: How to create typeset documentation + about pcl-cvs. +@end menu + +@node Pcl-cvs installation, On-line manual installation, Installation, Installation +@comment node-name, next, previous, up +@section Installation of the pcl-cvs program +@cindex Installation of elisp files + +@enumerate +@item +Edit the file @file{Makefile} to reflect the situation at your site. +The only things you have to change is the definition of @code{lispdir} +and @code{infodir}. The elisp files will be copied to @code{lispdir}, +and the info file to @code{infodir}. + +@item +Configure pcl-cvs.el + +There are a couple of paths that you have to check to make sure that +they match you system. They appear early in the file pcl-cvs.el. + +@strong{NOTE:} If your system is running emacs 18.57 or earlier you MUST +uncomment the line that says: + +@example +(setq delete-exited-processes nil) +@end example + +Setting @code{delete-exited-processes} to @code{nil} works around a bug +in emacs that causes it to dump core. The bug was fixed in emacs +18.58.@refill + +@item +Type @samp{make install} in the source directory. This will +byte-compile all @file{.el} files and copy both the @file{.el} and the +@file{.elc} into the directory you specified in step 1. + +If you don't want to install the @file{.el} files but only the +@file{.elc} files (the byte-compiled files), you can type `@samp{make +install_elc}' instead of `@samp{make install}'. + +If you only want to create the compiled elisp files, but don't want to +install them, you can type @samp{make elcfiles} instead. This is what +happens if you only type @samp{make} without parameters. + +@item +Edit the file @file{default.el} in your emacs lisp directory (usually +@file{/usr/gnu/emacs/lisp} or something similar) and enter the contents +of the file @file{pcl-cvs-startup.el} into it. It contains a couple of +@code{auto-load}s that facilitates the use of pcl-cvs. + +@end enumerate + +@node On-line manual installation, Typeset manual installation, Pcl-cvs installation, Installation +@comment node-name, next, previous, up +@section Installation of the on-line manual. +@cindex Manual installation (on-line) +@cindex Installation of on-line manual +@cindex Generating the on-line manual +@cindex On-line manual (how to generate) +@cindex Info-file (how to generate) + +@enumerate +@item +Create the info file @file{pcl-cvs} from @file{pcl-cvs.texinfo} by +typing @samp{make info}. If you don't have the program @samp{makeinfo} +you can get it by anonymous ftp from e.g. @samp{ftp.gnu.ai.mit.edu} as +@file{pub/gnu/texinfo-2.14.tar.Z} (there might be a newer version there +when you read this), or you could use the preformatted info file +@file{pcl-cvs.info} that is included in the distribution (type +@samp{cp pcl-cvs.info pcl-cvs}).@refill + +@item +Move the info file @file{pcl-cvs} to your standard info directory. +This might be called something like @file{/usr/gnu/emacs/info}.@refill + +@item +Edit the file @file{dir} in the info directory and enter one line to +contain a pointer to the info file @file{pcl-cvs}. The line can, for +instance, look like this:@refill + +@example +* Pcl-cvs: (pcl-cvs). An Emacs front-end to CVS. +@end example +@end enumerate + +@node Typeset manual installation, , On-line manual installation, Installation +@comment node-name, next, previous, up +@section How to make typeset documentation from pcl-cvs.texinfo +@cindex Manual installation (typeset) +@cindex Installation of typeset manual +@cindex Printing a manual +@cindex TeX - generating a typeset manual +@cindex Generating a typeset manual + +If you have @TeX{} installed at your site, you can make a typeset manual +from @file{pcl-cvs.texinfo}. + +@enumerate +@item +Run @TeX{} by typing `@samp{make pcl-cvs.dvi}'. You will not get the +indices unless you have the @code{texindex} program. + +@item +Convert the resulting device independent file @file{pcl-cvs.dvi} to a +form which your printer can output and print it. If you have a +postscript printer there is a program, @code{dvi2ps}, which does. There +is also a program which comes together with @TeX{}, @code{dvips}, which +you can use. + +@end enumerate + +@node About pcl-cvs, Getting started, Installation, Top +@comment node-name, next, previous, up +@chapter About pcl-cvs +@cindex About pcl-cvs + +Pcl-cvs is a front-end to CVS version 1.3. It integrates the most +frequently used CVS commands into emacs. + +@menu +* Contributors:: Contributors to pcl-cvs. +* Archives:: Where can I get a copy of Pcl-Cvs? +@end menu + +@node Contributors, Archives, About pcl-cvs, About pcl-cvs +@comment node-name, next, previous, up +@section Contributors to pcl-cvs +@cindex Contributors +@cindex Authors + +Contributions to the package are welcome. I have limited time to work +on this project, but I will gladly add any code that you contribute to +me to this package (@pxref{Reporting bugs and ideas}). + +The following persons have made contributions to pcl-cvs. + +@itemize @bullet +@item +Brian Berliner wrote CVS, together with some other contributors. +Without his work on CVS this package would be useless@dots{} + +@item +Per Cederqvist wrote most of the otherwise unattributed functions in +pcl-cvs as well as all documentation. + +@item +Inge Wallin (@samp{inge@@lysator.liu.se}) wrote the skeleton to +@file{pcl-cvs.texinfo}, and gave useful comments on it. He also wrote +the files @file{elib-node.el} and @file{compile-all.el}. The file +@file{cookie.el} was inspired by Inge.@refill + +@item +Linus Tolke (@samp{linus@@lysator.liu.se}) contributed useful comments +on both the functionality and the documentation.@refill + +@end itemize + + +@node Archives, , Contributors, About pcl-cvs +@comment node-name, next, previous, up +@section Where can I get pcl-cvs? +@cindex Sites +@cindex Archives +@cindex Ftp-sites +@cindex Getting pcl-cvs +@cindex Email archives + +This release of pcl-cvs is included in the CVS 1.3 distribution. +However, since pcl-cvs has had less time to mature (the first line of +code was written less than a year ago) it is likely that there will be a +new release of pcl-cvs before the next release of CVS. + +The latest release of pcl-cvs can be fetched via anonymous ftp from +@code{ftp.lysator.liu.se}, (IP no. 130.236.254.1) in the directory +@code{pub/emacs}. If you don't live in Scandinavia you should probably +check with archie to see if there is a site closer to you that archives +pcl-cvs. + +New releases will be announced to appropriate newsgroups. If you send +your email address to me I will add you to my list of people to mail +when I make a new release. + +@node Getting started, Buffer contents, About pcl-cvs, Top +@comment node-name, next, previous, up +@chapter Getting started +@cindex Introduction +@cindex Example run + +This document assumes that you know what CVS is, and that you at least +knows the fundamental concepts of CVS. If that is not the case you +should read the man page for CVS. + +Pcl-cvs is only useful once you have checked out a module. So before +you invoke it you must have a copy of a module somewhere in the file +system. + +You invoke pcl-cvs by typing @kbd{M-x pcl-cvs RET}. If your emacs +responds with @samp{[No match]} your system administrator has not +installed pcl-cvs properly. Try @kbd{M-x load-library RET pcl-cvs RET}. +If that also fails - talk to your root. If it succeeds you might put +this line in your @file{.emacs} file so that you don't have to type the +@samp{load-library} command every time you wish to use pcl-cvs: + +@example +(autoload 'cvs-update "pcl-cvs" nil t) +@end example + +The function @code{cvs-update} will ask for a directory. The command +@samp{cvs update} will be run in that directory. (It should contain +files that have been checked out from a CVS archive.) The output from +@code{cvs} will be parsed and presented in a table in a buffer called +@samp{*cvs*}. It might look something like this: + +@example +PCL-CVS release 1.02. +@comment -release- + +In directory /users/ceder/FOO/test: + Updated bar + Updated file.txt + Modified ci namechange + Updated newer + +In directory /users/ceder/FOO/test/sub: + Modified ci ChangeLog +---------- End ----- +@end example + +In this example the three files (@file{bar}, @file{file.txt} and +@file{newer}) that are marked with @samp{Updated} have been copied from +the CVS repository to @file{/users/ceder/FOO/test/} since someone else +have checked in newer versions of them. Two files (@file{namechange} +and @file{sub/ChangeLog}) have been modified locally, and needs to be +checked in. + +You can move the cursor up and down in the buffer with @kbd{C-n} and +@kbd{C-p} or @kbd{n} and @kbd{p}. If you press @kbd{c} on one of the +@samp{Modified} files that file will be checked in to the CVS +repository. @xref{Committing changes}. You can press @kbd{x} to get rid +of the "uninteresting" files that have only been @samp{Updated} (and +don't require any further action from you).@refill + +You can also easily get a @samp{diff} between your modified file and the +base version that you started from, and you can get the output from +@samp{cvs log} and @samp{cvs status} on the listed files simply by +pressing a key (@pxref{Getting info about files}). + +@node Buffer contents, Commands, Getting started, Top +@comment node-name, next, previous, up +@chapter Buffer contents +@cindex Buffer contents + +The display contains four columns. They contain, from left to right: + +@itemize @bullet +@item +An asterisk when the file is @dfn{marked} (@pxref{Selected +files}).@refill +@item +The status of the file. See @xref{File status}, for more information.@refill +@item +A "need to be checked in"-marker (@samp{ci}). +@item +The file name. +@end itemize + +@menu +* File status:: The meaning of the second field. +* Selected files:: How selection works. +@end menu + +@node File status, Selected files, Buffer contents, Buffer contents +@comment node-name, next, previous, up +@section File status +@cindex File status +@cindex Updated (file status) +@cindex Modified (file status) +@cindex Merged (file status) +@cindex Conflict (file status) +@cindex Added (file status) +@cindex Removed (file status) +@cindex Unknown (file status) +@cindex Removed from repository (file status) +@cindex Removed from repository, changed by you (file status) +@cindex Removed by you, changed in repository (file status) +@cindex Move away @var{file} - it is in the way (file status) +@cindex This repository is missing!@dots{} (file status) + +The @samp{file status} field can have the following values: + +@table @samp +@item Updated +The file was brought up to date with respect to the repository. This is +done for any file that exists in the repository but not in your source, +and for files that you haven't changed but are not the most recent +versions available in the repository.@refill + +@item Modified +The file is modified in your working directory, and there was no +modification to the same file in the repository.@refill + +@item Merged +The file is modified in your working directory, and there were +modifications in the repository as well as in your copy, but they were +merged successfully, without conflict, in your working directory.@refill + +@item Conflict +A conflict was detected while trying to merge your changes to @var{file} +with changes from the source repository. @var{file} (the copy in your +working directory) is now the output of the @samp{rcsmerge} command on +the two versions; an unmodified copy of your file is also in your +working directory, with the name @file{.#@var{file}.@var{version}}, +where @var{version} is the RCS revision that your modified file started +from. @xref{Viewing differences}, for more details.@refill + +@item Added +The file has been added by you, but it still needs to be checked in to +the repository.@refill + +@item Removed +The file has been removed by you, but it needs to be checked in to the +repository. You can resurrect it by typing @kbd{a} (@pxref{Adding and +removing files}).@refill + +@item Unknown +A file that was detected in your directory, but that neither appears in +the repository, nor is present on the list of files that CVS should +ignore.@refill + +@end table + +There are also a few special cases, that rarely occur, which have longer +strings in the fields: + +@table @samp +@item Removed from repository +The file has been removed from your directory since someone has removed +it from the repository. (It is still present in the Attic directory, so +no permanent loss has occurred). This, unlike the other entries in this +table, is not an error condition.@refill + +@item Removed from repository, changed by you +You have modified a file that someone have removed from the repository. +You can correct this situation by removing the file manually (see +@pxref{Adding and removing files}).@refill + +@item Removed by you, changed in repository +You have removed a file, and before you committed the removal someone +committed a change to that file. You could use @kbd{a} to resurrect the +file (see @pxref{Adding and removing files}).@refill + +@item Move away @var{file} - it is in the way +For some reason CVS does not like the file @var{file}. Rename or remove +it.@refill + +@item This repository is missing! Remove this dir manually. +It is impossible to remove a directory in the CVS repository in a clean +way. Someone have tried to remove one, and CVS gets confused. Remove +your copy of the directory.@refill +@end table + +@node Selected files, , File status, Buffer contents +@comment node-name, next, previous, up +@section Selected files +@cindex Selected files +@cindex Marked files +@cindex File selection +@cindex Active files + +Many of the commands works on the current set of @dfn{selected} files. + +@itemize @bullet +@item +If there are any files that are marked they constitute the set of +selected files.@refill +@item +Otherwise, if the cursor points to a file, that file is the selected +file.@refill +@item +Otherwise, if the cursor points to a directory, all the files in that +directory that appears in the buffer are the selected files. +@end itemize + +This scheme might seem a little complicated, but once one get used to +it, it is quite powerful. + +@xref{Marking files} tells how you mark and unmark files. + +@node Commands, Customization, Buffer contents, Top +@comment node-name, next, previous, up +@chapter Commands + +@iftex +This chapter describes all the commands that you can use in pcl-cvs. +@end iftex +@ifinfo +The nodes in this menu contains explanations about all the commands that +you can use in pcl-cvs. They are grouped together by type. +@end ifinfo + +@menu +* Updating the directory:: Commands to update the local directory +* Movement commands:: How to move up and down in the buffer +* Marking files:: How to mark files that other commands + will later operate on. +* Committing changes:: Checking in your modifications to the + CVS repository. +* Editing files:: Loading files into Emacs. +* Getting info about files:: Display the log and status of files. +* Adding and removing files:: Adding and removing files +* Removing handled entries:: Uninteresting lines can easily be removed. +* Ignoring files:: Telling CVS to ignore generated files. +* Viewing differences:: Commands to @samp{diff} different versions. +@end menu + +@node Updating the directory, Movement commands, Commands, Commands +@comment node-name, next, previous, up +@section Updating the directory +@findex cvs-update +@findex cvs-update-no-prompt +@kindex g - Rerun @samp{cvs update} + + +@table @kbd + +@item M-x cvs-update +Run a @samp{cvs update} command. You will be asked for the directory in +which the @samp{cvs update} will be run. The output will be parsed by +pcl-cvs, and the result printed in the @samp{*cvs*} buffer (see +@pxref{Buffer contents} for a description of the contents).@refill + +By default, @samp{cvs-update} will descend recursively into +subdirectories. You can avoid that behavior by giving a prefix +argument to it (e.g., by typing @kbd{C-u M-x cvs-update RET}).@refill + +All other commands in pcl-cvs requires that you have a @samp{*cvs*} +buffer. This is the command that you use to get one.@refill + +@item g +This will run @samp{cvs update} again. It will always use the same +buffer that was used with the previous @samp{cvs update}. Give a prefix +argument to avoid descending into subdirectories. This runs the command +@samp{cvs-update-no-prompt}.@refill +@end table +@node Movement commands, Marking files, Updating the directory, Commands +@comment node-name, next, previous, up +@section Movement Commands +@cindex Movement Commands +@findex cookie-next-cookie +@findex cookie-previous-cookie +@kindex SPC - Move down one file +@kindex C-n - Move down one file +@kindex n - Move down one file +@kindex C-p - Move up one file +@kindex p - Move up on file + +You can use most normal Emacs commands to move forward and backward in +the buffer. Some keys are rebound to functions that take advantage of +the fact that the buffer is a pcl-cvs buffer: + + +@table @kbd +@item SPC +@itemx C-n +@itemx n +These keys move the cursor one file forward, towards the end of the +buffer (@code{cookie-next-cookie}). + +@item C-p +@itemx p +These keys move one file backward, towards the beginning of the buffer +(@code{cookie-previous-cookie}). +@end table + +@node Marking files, Committing changes, Movement commands, Commands +@comment node-name, next, previous, up +@section Marking files +@cindex Selecting files (commands to mark files) +@cindex Marking files +@kindex m - marking a file +@kindex M - marking all files +@kindex u - unmark a file +@kindex U - unmark all files +@kindex DEL - unmark previous file +@findex cvs-mark +@findex cvs-unmark +@findex cvs-mark-all-files +@findex cvs-unmark-all-files +@findex cvs-unmark-up + +Pcl-cvs works on a set of @dfn{selected files} (@pxref{Selected files}). +You can mark and unmark files with these commands: + +@table @kbd +@item m +This marks the file that the cursor is positioned on. If the cursor is +positioned on a directory all files in that directory will be marked. +(@code{cvs-mark}). + +@item u +Unmark the file that the cursor is positioned on. If the cursor is on a +directory, all files in that directory will be unmarked. +(@code{cvs-unmark}).@refill + +@item M +Mark @emph{all} files in the buffer (@code{cvs-mark-all-files}). + +@item U +Unmark @emph{all} files (@code{cvs-unmark-all-files}). + +@item @key{DEL} +Unmark the file on the previous line, and move point to that line +(@code{cvs-unmark-up}). +@end table + +@node Committing changes, Editing files, Marking files, Commands +@comment node-name, next, previous, up +@section Committing changes +@cindex Committing changes +@cindex Ci +@findex cvs-commit +@kindex c - commit files +@vindex cvs-erase-input-buffer (variable) +@cindex Commit buffer +@cindex Edit buffer +@cindex Erasing commit message + +@table @kbd +@item c +All files that have a "need to be checked in"-marker (@pxref{Buffer +contents}) can be checked in with the @kbd{c} command. It checks in all +selected files (@pxref{Selected files}) (except those who lack the +"ci"-marker - they are ignored). Pressing @kbd{c} causes +@code{cvs-commit} to be run.@refill + +When you press @kbd{c} you will get a buffer called +@samp{*cvs-commit-message*}. Enter the log message for the file(s) in +it. When you are ready you should press @kbd{C-c C-c} to actually +commit the files (using @code{cvs-edit-done}). + +Normally the @samp{*cvs-commit-message*} buffer will retain the log +message from the previous commit, but if the variable +@code{cvs-erase-input-buffer} is set to a non-nil value the buffer will +be erased. Point and mark will always be located around the entire +buffer so that you can easily erase it with @kbd{C-w} +(@samp{kill-region}).@refill +@end table + +@node Editing files, Getting info about files, Committing changes, Commands +@comment node-name, next, previous, up +@section Editing files + +@cindex Editing files +@cindex Finding files +@cindex Loading files +@cindex Dired +@cindex Invoking dired +@findex cvs-find-file +@findex cvs-find-file-other-window +@findex cvs-add-change-log-entry-other-window +@kindex f - find file or directory +@kindex o - find file in other window +@kindex A - add ChangeLog entry + +There are currently three commands that can be used to find a file (that +is, load it into a buffer and start editing it there). These commands +work on the line that the cursor is situated at. They ignore any marked +files. + +@table @kbd +@item f +Find the file that the cursor points to. Run @samp{dired} +@ifinfo +(@pxref{Dired,,,Emacs}) +@end ifinfo +if the cursor points to a directory (@code{cvs-find-file}).@refill + +@item o +Like @kbd{f}, but use another window +(@code{cvs-find-file-other-window}).@refill + +@item A +Invoke @samp{add-change-log-entry-other-window} to edit a +@samp{ChangeLog} file. The @samp{ChangeLog} will be found in the +directory of the file the cursor points to. +(@code{cvs-add-change-log-entry-other-window}).@refill +@end table + +@node Getting info about files, Adding and removing files, Editing files, Commands +@comment node-name, next, previous, up +@section Getting info about files +@cindex Status (cvs command) +@cindex Log (RCS/cvs command) +@cindex Getting status +@kindex l - run @samp{cvs log} +@kindex s - run @samp{cvs status} +@findex cvs-log +@findex cvs-status + +Both of the following commands can be customized. +@xref{Customization}.@refill + +@table @kbd +@item l +Run @samp{cvs log} on all selected files, and show the result in a +temporary buffer (@code{cvs-log}). + +@item s +Run @samp{cvs status} on all selected files, and show the result in a +temporary buffer (@code{cvs-status}). +@end table + +@node Adding and removing files, Removing handled entries, Getting info about files, Commands +@comment node-name, next, previous, up +@section Adding and removing files +@cindex Adding files +@cindex Removing files +@cindex Resurrecting files +@cindex Deleting files +@cindex Putting files under CVS control +@kindex a - add a file +@kindex r - remove a file +@findex cvs-add +@findex cvs-remove-file + +The following commands are available to make it easy to add and remove +files from the CVS repository. + +@table @kbd +@item a +Add all selected files. This command can be used on @samp{Unknown} +files (see @pxref{File status}). The status of the file will change to +@samp{Added}, and you will have to use @kbd{c} (@samp{cvs-commit}, see +@pxref{Committing changes}) to really add the file to the +repository.@refill + +This command can also be used on @samp{Removed} files (before you commit +them) to resurrect them. + +Selected files that are neither @samp{Unknown} nor @samp{Removed} will +be ignored by this command. + +The command that is run is @code{cvs-add}. + +@item r +This command removes the selected files (after prompting for +confirmation). The files are @samp{rm}ed from your directory and +(unless the status was @samp{Unknown}; @pxref{File status}) they will +also be @samp{cvs remove}d. If the files were @samp{Unknown} they will +disappear from the buffer. Otherwise their status will change to +@samp{Removed}, and you must use @kbd{c} (@samp{cvs-commit}, +@pxref{Committing changes}) to commit the removal.@refill + +The command that is run is @code{cvs-remove-file}. +@end table + +@node Removing handled entries, Ignoring files, Adding and removing files, Commands +@comment node-name, next, previous, up +@section Removing handled entries +@cindex Expunging uninteresting entries +@cindex Uninteresting entries, getting rid of them +@cindex Getting rid of uninteresting lines +@cindex Removing uninteresting (processed) lines +@cindex Handled lines, removing them +@kindex x - remove processed entries +@kindex C-k - remove selected entries +@findex cvs-remove-handled +@findex cvs-acknowledge + +@table @kbd +@item x +This command allows you to remove all entries that you have processed. +More specifically, the lines for @samp{Updated} files (@pxref{File +status} and files that have been checked in (@pxref{Committing changes}) +are removed from the buffer. If a directory becomes empty the heading +for that directory is also removed. This makes it easier to get an +overview of what needs to be done. + +The command is called @code{cvs-remove-handled}. If +@samp{cvs-auto-remove-handled} is set to non-@samp{nil} this will +automatically be performed after every commit.@refill + +@item C-k +This command can be used for lines that @samp{cvs-remove-handled} would +not delete, but that you want to delete (@code{cvs-acknowledge}). +@end table + +@node Ignoring files, Viewing differences, Removing handled entries, Commands +@comment node-name, next, previous, up +@section Ignoring files + +@table @kbd +@item i +Arrange so that CVS will ignore the selected files. The file names are +added to the @file{.cvsignore} file in the corresponding directory. If +the @file{.cvsignore} doesn't exist it will be created. + +The @file{.cvsignore} file should normally be added to the repository, +but you could ignore it also if you like it better that way. + +This runs @code{cvs-ignore}. +@end table + +@node Viewing differences, , Ignoring files, Commands +@comment node-name, next, previous, up +@section Viewing differences +@cindex Diff +@cindex Conflicts, how to resolve them +@cindex Viewing differences +@kindex d - run @samp{cvs diff} +@kindex b - diff backup file +@findex cvs-diff-cvs +@findex cvs-diff-backup + +@table @kbd +@item d +Display a @samp{cvs diff} between the selected files and the RCS version +that they are based on. @xref{Customization} describes how you can send +flags to @samp{cvs diff}. (The function that does the job is +@code{cvs-diff-cvs}).@refill + +@item b +If CVS finds a conflict while merging two versions of a file (during a +@samp{cvs update}, @pxref{Updating the directory}) it will save the +original file in a file called @file{.#@var{FILE}.@var{VERSION}} where +@var{FILE} is the name of the file, and @var{VERSION} is the RCS version +number that your file was based on. + +With the @kbd{b} command you can run a @samp{diff} on the files +@file{.#@var{FILE}.@var{VERSION}} and @file{@var{FILE}}. You can get a +context- or Unidiff by setting @samp{cvs-diff-flags} - +@pxref{Customization}. This command only works on files that have +status @samp{Conflict} or @samp{Merged}. The name of the command is +@code{cvs-diff-backup}. @refill +@end table + +@node Customization, Future enhancements, Commands, Top +@comment node-name, next, previous, up +@chapter Customization +@vindex cvs-erase-input-buffer (variable) +@vindex cvs-inhibit-copyright-message (variable) +@vindex cvs-cvs-diff-flags (variable) +@vindex cvs-diff-flags (variable) +@vindex cvs-log-flags (variable) +@vindex cvs-status-flags (variable) +@vindex cvs-auto-remove-handled (variable) +@cindex Inhibiting the Copyright message. +@cindex Copyright message, getting rid of it +@cindex Getting rid of the Copyright message. +@cindex Customization +@cindex Variables, list of all +@cindex Erasing the input buffer +@cindex Context diff, how to get +@cindex Unidiff, how to get +@cindex Automatically remove handled files + +If you have an idea about any customization that would be handy but +isn't present in this list, please tell me! @xref{Reporting bugs and +ideas} for info on how to reach me.@refill + +@table @samp +@item cvs-erase-input-buffer +If set to anything else than @samp{nil} the edit buffer will be erased +before you write the log message (@pxref{Committing changes}). + +@item cvs-inhibit-copyright-message +The copyright message that is displayed on startup can be annoying after +a while. Set this variable to @samp{t} if you want to get rid of it. +(But don't set this to @samp{t} in the system defaults file - new users +should see this message at least once). + +@item cvs-cvs-diff-flags +A list of strings to pass as arguments to the @samp{cvs diff} program. +This is used by @samp{cvs-diff-cvs} (key @kbd{d}, @pxref{Viewing +differences}). If you prefer the Unidiff format you could add this line +to your @file{.emacs} file:@refill + +@example +(setq cvs-cvs-diff-flags '("-u")) +@end example + +@item cvs-diff-flags +Like @samp{cvs-cvs-diff-flags}, but passed to @samp{diff}. This is used +by @samp{cvs-diff-backup} (key @kbd{b}, @pxref{Viewing differences}). + +@item cvs-log-flags +List of strings to send to @samp{cvs log}. Used by @samp{cvs-log} (key +@kbd{l}, @pxref{Getting info about files}). + +@item cvs-status-flags +List of strings to send to @samp{cvs status}. Used by @samp{cvs-status} +(key @kbd{s}, @pxref{Getting info about files}). + +@item cvs-auto-remove-handled +If this variable is set to any non-@samp{nil} value +@samp{cvs-remove-handled} will be called every time you check in files, +after the check-in is ready. @xref{Removing handled entries}.@refill + +@end table +@node Future enhancements, Reporting bugs and ideas, Customization, Top +@comment node-name, next, previous, up +@chapter Future enhancements +@cindex Enhancements + +Pcl-cvs is still under development and needs a number of enhancements to +be called complete. Here is my current wish-list for future releases of +pcl-cvs: + +@itemize @bullet +@item +Dired support. I have an experimental @file{dired-cvs.el} that works +together with CVS 1.2. Unfortunately I wrote it on top of a +non-standard @file{dired.el}, so it must be rewritten.@refill + +@item +It should be possible to run commands such as @samp{cvs log}, @samp{cvs +status} and @samp{cvs commit} directly from a buffer containing a file, +instead of having to @samp{cvs-update}. If the directory contains many +files the @samp{cvs-update} can take quite some time, especially on a +slow machine. +@end itemize + + +If you miss something in this wish-list, let me know! I don't promise +that I will write it, but I will at least try to coordinate the efforts +of making a good Emacs front end to CVS. See @xref{Reporting bugs and +ideas} for information about how to reach me.@refill + + +@node Reporting bugs and ideas, Function and Variable Index, Future enhancements, Top +@comment node-name, next, previous, up +@chapter Reporting bugs and ideas +@cindex Reporting bugs and ideas +@cindex Bugs, how to report them +@cindex Author, how to reach +@cindex Email to the author + +If you find a bug or misfeature, don't hesitate to tell me! Send email +to @samp{ceder@@lysator.liu.se}. + +If you have ideas for improvements, or if you have written some +extensions to this package, I would like to hear from you. I hope that +you find this package useful! + + +@node Function and Variable Index, Concept Index, Reporting bugs and ideas, Top +@comment node-name, next, previous, up +@unnumbered Function and Variable Index + +@printindex fn + +@node Concept Index, Key Index, Function and Variable Index, Top +@comment node-name, next, previous, up +@unnumbered Concept Index + +@printindex cp + +@node Key Index, , Concept Index, Top +@comment node-name, next, previous, up +@unnumbered Key Index + +@printindex ky + +@summarycontents +@contents +@bye diff --git a/gnu/usr.bin/cvs/contrib/rcs-to-cvs b/gnu/usr.bin/cvs/contrib/rcs-to-cvs new file mode 100644 index 0000000..1a241b9 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/rcs-to-cvs @@ -0,0 +1,208 @@ +#!/bin/csh +# +# rcs-to-cvs,v 1.3 1992/04/10 03:04:25 berliner Exp +# Contributed by Per Cederqvist <ceder@lysator.liu.se>. +# +# Copyright (c) 1989, Brian Berliner +# +# You may distribute under the terms of the GNU General Public License +# as specified in the README file that comes with the CVS 1.0 kit. +# +############################################################################# +# # +# This script is used to check in sources that previously was under RCS or # +# no source control system. # +# # +# Usage: rcs-to-cvs repository # +# # +# The repository is the directory where the sources should # +# be deposited. +# # +# checkin traverses the current directory, ensuring that an # +# identical directory structure exists in the repository directory. It # +# then checks the files in in the following manner: # +# # +# 1) If the file doesn't yet exist, check it in # +# as revision 0.1 # +# # +# The script also is somewhat verbose in letting the user know what is # +# going on. It prints a diagnostic when it creates a new file, or updates # +# a file that has been modified on the trunk. # +# # +############################################################################# + +set vbose = 0 +set message = "" +set cvsbin = /usr/gnu/bin +set rcsbin = /usr/gnu/bin +set grep = /bin/grep +set message_file = /usr/tmp/checkin.$$ +set got_one = 0 + +if ( $#argv < 1 ) then + echo "Usage: rcs-to-cvs [-v] [-m message] [-f message_file] repository" + exit 1 +endif +while ( $#argv ) + switch ( $argv[1] ) + case -v: + set vbose = 1 + breaksw + case -m: + shift + echo $argv[1] > $message_file + set got_one = 1 + breaksw + case -f: + shift + set message_file = $argv[1] + set got_one = 2 + breaksw + default: + break + endsw + shift +end +if ( $#argv < 1 ) then + echo "Usage: rcs-to-cvs [-v] [-m message] [-f message_file] repository" + exit 1 +endif +set repository = $argv[1] +shift + +if ( ! $?CVSROOT ) then + echo "Please set the environmental variable CVSROOT to the root" + echo " of the tree you wish to update" + exit 1 +endif + +if ( $got_one == 0 ) then + echo "Please Edit this file to contain the RCS log information" >$message_file + echo "to be associated with this file (please remove these lines)">>$message_file + if ( $?EDITOR ) then + $EDITOR $message_file > /dev/tty + else + /usr/ucb/vi $message_file > /dev/tty + endif + set got_one = 1 +endif + +umask 22 + +set update_dir = ${CVSROOT}/${repository} +if ( -d SCCS ) then + echo SCCS files detected! + exit 1 +endif +if ( -d RCS ) then + $rcsbin/co RCS/* >& /dev/null +endif +foreach name ( * .[a-zA-Z0-9]* ) + echo $name + if ( "$name" == SCCS ) then + continue + endif + if ( "$name" == RCS ) then + continue + endif + if ( $vbose ) then + echo "Updating ${repository}/${name}" + endif + if ( -d "$name" ) then + if ( ! -d "${update_dir}/${name}" ) then + echo "WARNING: Creating new directory ${repository}/${name}" + mkdir "${update_dir}/${name}" + if ( $status ) then + echo "ERROR: mkdir failed - aborting" + exit 1 + endif + endif + chdir "$name" + if ( $status ) then + echo "ERROR: Couldn\'t chdir to "$name" - aborting" + exit 1 + endif + if ( $vbose ) then + rcs-to-cvs -v -f $message_file "${repository}/${name}" + else + rcs-to-cvs -f $message_file "${repository}/${name}" + endif + if ( $status ) then + exit 1 + endif + chdir .. + else # if not directory + if ( ! -f "$name" ) then + echo "WARNING: "$name" is neither a regular file" + echo " nor a directory - ignored" + continue + endif + set file = "${update_dir}/${name},v" + set new = 0 + set comment = "" + grep -s '\$Log.*\$' "${name}" + if ( $status == 0 ) then # If $Log keyword + set myext = ${name:e} + set knownext = 0 + foreach xx ( "c" "csh" "e" "f" "h" "l" "mac" "me" "mm" "ms" "p" "r" "red" "s" "sh" "sl" "cl" "ml" "el" "tex" "y" "ye" "yr" "" ) + if ( "${myext}" == "${xx}" ) then + set knownext = 1 + break + endif + end + if ( $knownext == 0 ) then + echo For file ${file}: + grep '\$Log.*\$' "${name}" + echo -n "Please insert a comment leader for file ${name} > " + set comment = $< + endif + endif + if ( ! -f "$file" ) then # If not exists in repository + if ( ! -f "${update_dir}/Attic/${name},v" ) then + echo "WARNING: Creating new file ${repository}/${name}" + if ( -f RCS/"${name}",v ) then + echo "MSG: Copying old rcs file." + cp RCS/"${name}",v "$file" + else + if ( "${comment}" != "" ) then + $rcsbin/rcs -q -i -c"${comment}" -t${message_file} -m'.' "$file" + endif + $rcsbin/ci -q -u0.1 -t${message_file} -m'.' "$file" + if ( $status ) then + echo "ERROR: Initial check-in of $file failed - aborting" + exit 1 + endif + set new = 1 + endif + else + set file = "${update_dir}/Attic/${name},v" + echo "WARNING: IGNORED: ${repository}/Attic/${name}" + continue + endif + else # File existed + echo ERROR: File exists: Ignored: "$file" + continue +# set headbranch = `sed -n '/^head/p; /^branch/p; 2q' $file` +# if ( $#headbranch != 2 && $#headbranch != 4 ) then +# echo "ERROR: corrupted RCS file $file - aborting" +# endif +# set head = "$headbranch[2]" +# set branch = "" +# if ( $#headbranch == 4 ) then +# set branch = "$headbranch[4]" +# endif +# if ( "$head" == "1.1;" && "$branch" != "1.1.1;" ) then +# ${rcsbin}/rcsdiff -q -r1.1 $file > /dev/null +# if ( ! $status ) then +# set new = 1 +# endif +# else +# if ( "$branch" != "1.1.1;" ) then +# echo -n "WARNING: Updating locally modified file " +# echo "${repository}/${name}" +# endif +# endif + endif + endif +end +if ( $got_one == 1 ) rm $message_file diff --git a/gnu/usr.bin/cvs/contrib/rcslock.pl b/gnu/usr.bin/cvs/contrib/rcslock.pl new file mode 100644 index 0000000..db09b4b --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/rcslock.pl @@ -0,0 +1,234 @@ +#!/usr/bin/perl + +# Author: John Rouillard (rouilj@cs.umb.edu) +# Supported: Yeah right. (Well what do you expect for 2 hours work?) +# Blame-to: rouilj@cs.umb.edu +# Complaints to: Anybody except Brian Berliner, he's blameless for +# this script. +# Acknowlegements: The base code for this script has been acquired +# from the log.pl script. + +# rcslock.pl - A program to prevent commits when a file to be ckecked +# in is locked in the repository. + +# There are times when you need exclusive access to a file. This +# often occurs when binaries are checked into the repository, since +# cvs's (actually rcs's) text based merging mechanism won't work. This +# script allows you to use the rcs lock mechanism (rcs -l) to make +# sure that no changes to a repository are able to be committed if +# those changes would result in a locked file being changed. + +# WARNING: +# This script will work only if locking is set to strict. +# + +# Setup: +# Add the following line to the commitinfo file: + +# ALL /local/location/for/script/lockcheck [options] + +# Where ALL is replaced by any suitable regular expression. +# Options are -v for verbose info, or -d for debugging info. +# The %s will provide the repository directory name and the names of +# all changed files. + +# Use: +# When a developer needs exclusive access to a version of a file, s/he +# should use "rcs -l" in the repository tree to lock the version they +# are working on. CVS will automagically release the lock when the +# commit is performed. + +# Method: +# An "rlog -h" is exec'ed to give info on all about to be +# committed files. This (header) information is parsed to determine +# if any locks are outstanding and what versions of the file are +# locked. This filename, version number info is used to index an +# associative array. All of the files to be committed are checked to +# see if any locks are outstanding. If locks are outstanding, the +# version number of the current file (taken from the CVS/Entries +# subdirectory) is used in the key to determine if that version is +# locked. If the file being checked in is locked by the person doing +# the checkin, the commit is allowed, but if the lock is held on that +# version of a file by another person, the commit is not allowed. + +$ext = ",v"; # The extension on your rcs files. + +$\="\n"; # I hate having to put \n's at the end of my print statements +$,=' '; # Spaces should occur between arguments to print when printed + +# turn off setgid +# +$) = $(; + +# +# parse command line arguments +# +require 'getopts.pl'; + +&Getopts("vd"); # verbose or debugging + +# Verbose is useful when debugging +$opt_v = $opt_d if defined $opt_d; + +# $files[0] is really the name of the subdirectory. +# @files = split(/ /,$ARGV[0]); +@files = @ARGV[0..$#ARGV]; +$cvsroot = $ENV{'CVSROOT'}; + +# +# get login name +# +$login = getlogin || (getpwuid($<))[0] || "nobody"; + +# +# save the current directory since we have to return here to parse the +# CVS/Entries file if a lock is found. +# +$pwd = `/bin/pwd`; +chop $pwd; + +print "Starting directory is $pwd" if defined $opt_d ; + +# +# cd to the repository directory and check on the files. +# +print "Checking directory ", $files[0] if defined $opt_v ; + +if ( $files[0] =~ /^\// ) +{ + print "Directory path is $files[0]" if defined $opt_d ; + chdir $files[0] || die "Can't change to repository directory $files[0]" ; +} +else +{ + print "Directory path is $cvsroot/$files[0]" if defined $opt_d ; + chdir ($cvsroot . "/" . $files[0]) || + die "Can't change to repository directory $files[0] in $cvsroot" ; +} + + +# Open the rlog process and apss all of the file names to that one +# process to cut down on exec overhead. This may backfire if there +# are too many files for the system buffer to handle, but if there are +# that many files, chances are that the cvs repository is not set up +# cleanly. + +print "opening rlog -h @files[1..$#files] |" if defined $opt_d; + +open( RLOG, "rlog -h @files[1..$#files] |") || die "Can't run rlog command" ; + +# Create the locks associative array. The elements in the array are +# of two types: +# +# The name of the RCS file with a value of the total number of locks found +# for that file, +# or +# +# The name of the rcs file concatenated with the version number of the lock. +# The value of this element is the name of the locker. + +# The regular expressions used to split the rcs info may have to be changed. +# The current ones work for rcs 5.6. + +$lock = 0; + +while (<RLOG>) +{ + chop; + next if /^$/; # ditch blank lines + + if ( $_ =~ /^RCS file: (.*)$/ ) + { + $curfile = $1; + next; + } + + if ( $_ =~ /^locks: strict$/ ) + { + $lock = 1 ; + next; + } + + if ( $lock ) + { + # access list: is the line immediately following the list of locks. + if ( /^access list:/ ) + { # we are done getting lock info for this file. + $lock = 0; + } + else + { # We are accumulating lock info. + + # increment the lock count + $locks{$curfile}++; + # save the info on the version that is locked. $2 is the + # version number $1 is the name of the locker. + $locks{"$curfile" . "$2"} = $1 + if /[ ]*([a-zA-Z._]*): ([0-9.]*)$/; + + print "lock by $1 found on $curfile version $2" if defined $opt_d; + + } + } +} + +# Lets go back to the starting directory and see if any locked files +# are ones we are interested in. + +chdir $pwd; + +# fo all of the file names (remember $files[0] is the directory name +foreach $i (@files[1..$#files]) +{ + if ( defined $locks{$i . $ext} ) + { # well the file has at least one lock outstanding + + # find the base version number of our file + &parse_cvs_entry($i,*entry); + + # is our version of this file locked? + if ( defined $locks{$i . $ext . $entry{"version"}} ) + { # if so, it is by us? + if ( $login ne ($by = $locks{$i . $ext . $entry{"version"}}) ) + {# crud somebody else has it locked. + $outstanding_lock++ ; + print "$by has file $i locked for version " , $entry{"version"}; + } + else + { # yeah I have it locked. + print "You have a lock on file $i for version " , $entry{"version"} + if defined $opt_v; + } + } + } +} + +exit $outstanding_lock; + + +### End of main program + +sub parse_cvs_entry +{ # a very simple minded hack at parsing an entries file. +local ( $file, *entry ) = @_; +local ( @pp ); + + +open(ENTRIES, "< CVS/Entries") || die "Can't open entries file"; + +while (<ENTRIES>) + { + if ( $_ =~ /^\/$file\// ) + { + @pp = split('/'); + + $entry{"name"} = $pp[1]; + $entry{"version"} = $pp[2]; + $entry{"dates"} = $pp[3]; + $entry{"name"} = $pp[4]; + $entry{"name"} = $pp[5]; + $entry{"sticky"} = $pp[6]; + return; + } + } +} diff --git a/gnu/usr.bin/cvs/contrib/sccs2rcs b/gnu/usr.bin/cvs/contrib/sccs2rcs new file mode 100644 index 0000000..a208645 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/sccs2rcs @@ -0,0 +1,277 @@ +#!/bin/csh -f +# +# Sccs2rcs is a script to convert an existing SCCS +# history into an RCS history without losing any of +# the information contained therein. +# It has been tested under the following OS's: +# SunOS 3.5, 4.0.3, 4.1 +# Ultrix-32 2.0, 3.1 +# +# Things to note: +# + It will NOT delete or alter your ./SCCS history under any circumstances. +# +# + Run in a directory where ./SCCS exists and where you can +# create ./RCS +# +# + /usr/local/bin is put in front of the default path. +# (SCCS under Ultrix is set-uid sccs, bad bad bad, so +# /usr/local/bin/sccs here fixes that) +# +# + Date, time, author, comments, branches, are all preserved. +# +# + If a command fails somewhere in the middle, it bombs with +# a message -- remove what it's done so far and try again. +# "rm -rf RCS; sccs unedit `sccs tell`; sccs clean" +# There is no recovery and exit is far from graceful. +# If a particular module is hanging you up, consider +# doing it separately; move it from the current area so that +# the next run will have a better chance or working. +# Also (for the brave only) you might consider hacking +# the s-file for simpler problems: I've successfully changed +# the date of a delta to be in sync, then run "sccs admin -z" +# on the thing. +# +# + After everything finishes, ./SCCS will be moved to ./old-SCCS. +# +# This file may be copied, processed, hacked, mutilated, and +# even destroyed as long as you don't tell anyone you wrote it. +# +# Ken Cox +# Viewlogic Systems, Inc. +# kenstir@viewlogic.com +# ...!harvard!cg-atla!viewlog!kenstir +# +# Various hacks made by Brian Berliner before inclusion in CVS contrib area. +# +# sccs2rcs,v 1.1 1992/04/10 03:04:26 berliner Exp + + +#we'll assume the user set up the path correctly +# for the Pmax, /usr/ucb/sccs is suid sccs, what a pain +# /usr/local/bin/sccs should override /usr/ucb/sccs there +set path = (/usr/local/bin $path) + + +############################################################ +# Error checking +# +if (! -w .) then + echo "Error: ./ not writeable by you." + exit 1 +endif +if (! -d SCCS) then + echo "Error: ./SCCS directory not found." + exit 1 +endif +set edits = (`sccs tell`) +if ($#edits) then + echo "Error: $#edits file(s) out for edit...clean up before converting." + exit 1 +endif +if (-d RCS) then + echo "Warning: RCS directory exists" + if (`ls -a RCS | wc -l` > 2) then + echo "Error: RCS directory not empty + exit 1 + endif +else + mkdir RCS +endif + +sccs clean + +set logfile = /tmp/sccs2rcs_$$_log +rm -f $logfile +set tmpfile = /tmp/sccs2rcs_$$_tmp +rm -f $tmpfile +set emptyfile = /tmp/sccs2rcs_$$_empty +echo -n "" > $emptyfile +set initialfile = /tmp/sccs2rcs_$$_init +echo "Initial revision" > $initialfile +set sedfile = /tmp/sccs2rcs_$$_sed +rm -f $sedfile +set revfile = /tmp/sccs2rcs_$$_rev +rm -f $revfile + +# the quotes surround the dollar signs to fool RCS when I check in this script +set sccs_keywords = (\ + '%W%[ ]*%G%'\ + '%W%[ ]*%E%'\ + '%W%'\ + '%Z%%M%[ ]*%I%[ ]*%G%'\ + '%Z%%M%[ ]*%I%[ ]*%E%'\ + '%M%[ ]*%I%[ ]*%G%'\ + '%M%[ ]*%I%[ ]*%E%'\ + '%M%'\ + '%I%'\ + '%G%'\ + '%E%'\ + '%U%') +set rcs_keywords = (\ + '$'Id'$'\ + '$'Id'$'\ + '$'Id'$'\ + '$'SunId'$'\ + '$'SunId'$'\ + '$'Id'$'\ + '$'Id'$'\ + '$'RCSfile'$'\ + '$'Revision'$'\ + '$'Date'$'\ + '$'Date'$'\ + '') + + +############################################################ +# Get some answers from user +# +echo "" +echo "Do you want to be prompted for a description of each" +echo "file as it is checked in to RCS initially?" +echo -n "(y=prompt for description, n=null description) [y] ?" +set ans = $< +if ((_$ans == _) || (_$ans == _y) || (_$ans == _Y)) then + set nodesc = 0 +else + set nodesc = 1 +endif +echo "" +echo "The default keyword substitutions are as follows and are" +echo "applied in the order specified:" +set i = 1 +while ($i <= $#sccs_keywords) +# echo ' '\"$sccs_keywords[$i]\"' ==> '\"$rcs_keywords[$i]\" + echo " $sccs_keywords[$i] ==> $rcs_keywords[$i]" + @ i = $i + 1 +end +echo "" +echo -n "Do you want to change them [n] ?" +set ans = $< +if ((_$ans != _) && (_$ans != _n) && (_$ans != _N)) then + echo "You can't always get what you want." + echo "Edit this script file and change the variables:" + echo ' $sccs_keywords' + echo ' $rcs_keywords' +else + echo "good idea." +endif + +# create the sed script +set i = 1 +while ($i <= $#sccs_keywords) + echo "s,$sccs_keywords[$i],$rcs_keywords[$i],g" >> $sedfile + @ i = $i + 1 +end + +onintr ERROR + +############################################################ +# Loop over every s-file in SCCS dir +# +foreach sfile (SCCS/s.*) + # get rid of the "s." at the beginning of the name + set file = `echo $sfile:t | sed -e "s/^..//"` + + # work on each rev of that file in ascending order + set firsttime = 1 + sccs prs $file | grep "^D " | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile + foreach rev (`cat $revfile`) + if ($status != 0) goto ERROR + + # get file into current dir and get stats + set date = `sccs prs -r$rev $file | grep "^D " | awk '{printf("19%s %s", $3, $4); exit}'` + set author = `sccs prs -r$rev $file | grep "^D " | awk '{print $5; exit}'` + echo "" + echo "==> file $file, rev=$rev, date=$date, author=$author" + sccs edit -r$rev $file >>& $logfile + if ($status != 0) goto ERROR + echo checked out of SCCS + + # add RCS keywords in place of SCCS keywords + sed -f $sedfile $file > $tmpfile + if ($status != 0) goto ERROR + echo performed keyword substitutions + cp $tmpfile $file + + # check file into RCS + if ($firsttime) then + set firsttime = 0 + if ($nodesc) then + echo about to do ci + echo ci -f -r$rev -d"$date" -w$author -t$emptyfile $file + ci -f -r$rev -d"$date" -w$author -t$emptyfile $file < $initialfile >>& $logfile + if ($status != 0) goto ERROR + echo initial rev checked into RCS without description + else + echo "" + echo Enter a brief description of the file $file \(end w/ Ctrl-D\): + cat > $tmpfile + ci -f -r$rev -d"$date" -w$author -t$tmpfile $file < $initialfile >>& $logfile + if ($status != 0) goto ERROR + echo initial rev checked into RCS + endif + else + # get RCS lock + set lckrev = `echo $rev | sed -e 's/\.[0-9]*$//'` + if ("$lckrev" =~ [0-9]*.*) then + # need to lock the brach -- it is OK if the lock fails + rcs -l$lckrev $file >>& $logfile + else + # need to lock the trunk -- must succeed + rcs -l $file >>& $logfile + if ($status != 0) goto ERROR + endif + echo got lock + sccs prs -r$rev $file | grep "." > $tmpfile + # it's OK if grep fails here and gives status == 1 + # put the delta message in $tmpfile + ed $tmpfile >>& $logfile <<EOF +/COMMENTS +1,.d +w +q +EOF + ci -f -r$rev -d"$date" -w$author $file < $tmpfile >>& $logfile + if ($status != 0) goto ERROR + echo checked into RCS + endif + sccs unedit $file >>& $logfile + if ($status != 0) goto ERROR + end + rm -f $file +end + + +############################################################ +# Clean up +# +echo cleaning up... +mv SCCS old-SCCS +rm -f $tmpfile $emptyfile $initialfile $sedfile +echo =================================================== +echo " Conversion Completed Successfully" +echo "" +echo " SCCS history now in old-SCCS/" +echo =================================================== +set exitval = 0 +goto cleanup + +ERROR: +foreach f (`sccs tell`) + sccs unedit $f +end +echo "" +echo "" +echo Danger\! Danger\! +echo Some command exited with a non-zero exit status. +echo Log file exists in $logfile. +echo "" +echo Incomplete history in ./RCS -- remove it +echo Original unchanged history in ./SCCS +set exitval = 1 + +cleanup: +# leave log file +rm -f $tmpfile $emptyfile $initialfile $sedfile $revfile + +exit $exitval diff --git a/gnu/usr.bin/cvs/cvs/Makefile b/gnu/usr.bin/cvs/cvs/Makefile new file mode 100644 index 0000000..5be0fbe --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/Makefile @@ -0,0 +1,18 @@ +PROG = cvs +CFLAGS += -I${.CURDIR}/../lib \ + -DDIRENT -DSTDC_HEADERS -DPOSIX -DBROKEN_SIGISMEMBER \ + -DFTIME_MISSING -DHAVE_TIMEZONE -DUTIME_NULL_MISSING + +LDADD= -L${.CURDIR}/../lib/obj -lcvs + +SRCS = add.c admin.c checkin.c checkout.c classify.c commit.c \ +create_adm.c diff.c entries.c find_names.c history.c ignore.c \ +import.c lock.c log.c logmsg.c main.c rcs.c modules.c \ +no_diff.c parseinfo.c patch.c recurse.c release.c remove.c repos.c rtag.c \ +status.c tag.c update.c vers_ts.c version.c + +MAN1= cvs.0 +MAN5= cvs.0 + +.include <bsd.prog.mk> +.include "../../Makefile.inc" diff --git a/gnu/usr.bin/cvs/cvs/add.c b/gnu/usr.bin/cvs/cvs/add.c new file mode 100644 index 0000000..b4925bf --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/add.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Add + * + * Adds a file or directory to the RCS source repository. For a file, + * the entry is marked as "needing to be added" in the user's own CVS + * directory, and really added to the repository when it is committed. + * For a directory, it is added at the appropriate place in the source + * repository and a CVS directory is generated within the directory. + * + * The -m option is currently the only supported option. Some may wish to + * supply standard "rcs" options here, but I've found that this causes more + * trouble than anything else. + * + * The user files or directories must already exist. For a directory, it must + * not already have a CVS file in it. + * + * An "add" on a file that has been "remove"d but not committed will cause the + * file to be resurrected. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)add.c 1.46 92/04/03"; +#endif + +#if __STDC__ +static int add_directory (char *repository, char *dir); +static int build_entry (char *repository, char *user, char *options, + char *message, List * entries); +#else +static int add_directory (); +static int build_entry (); +#endif /* __STDC__ */ + +static char *add_usage[] = +{ + "Usage: %s %s [-k rcs-kflag] [-m message] files...\n", + "\t-k\tUse \"rcs-kflag\" to add the file with the specified kflag.\n", + "\t-m\tUse \"message\" for the creation log.\n", + NULL +}; + +int +add (argc, argv) + int argc; + char *argv[]; +{ + char message[MAXMESGLEN]; + char *user; + int i; + char *repository; + int c; + int err = 0; + int added_files = 0; + char *options = NULL; + List *entries; + Vers_TS *vers; + + if (argc == 1 || argc == -1) + usage (add_usage); + + /* parse args */ + message[0] = '\0'; + optind = 1; + while ((c = gnu_getopt (argc, argv, "k:m:")) != -1) + { + switch (c) + { + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + + case 'm': + if (strlen (optarg) >= sizeof (message)) + { + error (0, 0, "warning: message too long; truncated!"); + (void) strncpy (message, optarg, sizeof (message)); + message[sizeof (message) - 1] = '\0'; + } + else + (void) strcpy (message, optarg); + break; + case '?': + default: + usage (add_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (argc <= 0) + usage (add_usage); + + /* find the repository associated with our current dir */ + repository = Name_Repository ((char *) NULL, (char *) NULL); + entries = ParseEntries (0); + + /* walk the arg list adding files/dirs */ + for (i = 0; i < argc; i++) + { + int begin_err = err; + + user = argv[i]; + if (index (user, '/') != NULL) + { + error (0, 0, + "cannot add files with '/' in their name; %s not added", user); + err++; + continue; + } + + vers = Version_TS (repository, options, (char *) NULL, (char *) NULL, + user, 0, 0, entries, (List *) NULL); + if (vers->vn_user == NULL) + { + /* No entry available, ts_rcs is invalid */ + if (vers->vn_rcs == NULL) + { + /* There is no RCS file either */ + if (vers->ts_user == NULL) + { + /* There is no user file either */ + error (0, 0, "nothing known about %s", user); + err++; + } + else if (!isdir (user)) + { + /* + * See if a directory exists in the repository with + * the same name. If so, blow this request off. + */ + char dname[PATH_MAX]; + (void) sprintf (dname, "%s/%s", repository, user); + if (isdir (dname)) + { + error (0, 0, + "cannot add file `%s' since the directory", + user); + error (0, 0, "`%s' already exists in the repository", + dname); + error (1, 0, "illegal filename overlap"); + } + + /* There is a user file, so build the entry for it */ + if (build_entry (repository, user, vers->options, + message, entries) != 0) + err++; + else if (!quiet) + { + added_files++; + error (0, 0, "scheduling file `%s' for addition", + user); + } + } + } + else + { + + /* + * There is an RCS file already, so somebody else must've + * added it + */ + error (0, 0, "%s added independently by second party", user); + err++; + } + } + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + { + + /* + * An entry for a new-born file, ts_rcs is dummy, but that is + * inappropriate here + */ + error (0, 0, "%s has already been entered", user); + err++; + } + else if (vers->vn_user[0] == '-') + { + /* An entry for a removed file, ts_rcs is invalid */ + if (vers->ts_user == NULL) + { + /* There is no user file (as it should be) */ + if (vers->vn_rcs == NULL) + { + + /* + * There is no RCS file, so somebody else must've removed + * it from under us + */ + error (0, 0, + "cannot resurrect %s; RCS file removed by second party", user); + err++; + } + else + { + + /* + * There is an RCS file, so remove the "-" from the + * version number and restore the file + */ + char *tmp = xmalloc (strlen (user) + 50); + + (void) strcpy (tmp, vers->vn_user + 1); + (void) strcpy (vers->vn_user, tmp); + (void) sprintf (tmp, "Resurrected %s", user); + Register (entries, user, vers->vn_user, tmp, vers->options, + vers->tag, vers->date); + free (tmp); + + /* XXX - bugs here; this really resurrect the head */ + if (update (2, argv + i - 1) == 0) + { + error (0, 0, "%s, version %s, resurrected", user, + vers->vn_user); + } + else + { + error (0, 0, "could not resurrect %s", user); + err++; + } + } + } + else + { + /* The user file shouldn't be there */ + error (0, 0, "%s should be removed and is still there (or is back again)", user); + err++; + } + } + else + { + /* A normal entry, ts_rcs is valid, so it must already be there */ + error (0, 0, "%s already exists, with version number %s", user, + vers->vn_user); + err++; + } + freevers_ts (&vers); + + /* passed all the checks. Go ahead and add it if its a directory */ + if (begin_err == err && isdir (user)) + { + err += add_directory (repository, user); + continue; + } + } + if (added_files) + error (0, 0, "use 'cvs commit' to add %s permanently", + (added_files == 1) ? "this file" : "these files"); + dellist (&entries); + return (err); +} + +/* + * The specified user file is really a directory. So, let's make sure that + * it is created in the RCS source repository, and that the user's directory + * is updated to include a CVS directory. + * + * Returns 1 on failure, 0 on success. + */ +static int +add_directory (repository, dir) + char *repository; + char *dir; +{ + char cwd[PATH_MAX], rcsdir[PATH_MAX]; + char message[PATH_MAX + 100]; + char *tag, *date; + + if (index (dir, '/') != NULL) + { + error (0, 0, + "directory %s not added; must be a direct sub-directory", dir); + return (1); + } + if (strcmp (dir, CVSADM) == 0 || strcmp (dir, OCVSADM) == 0) + { + error (0, 0, "cannot add a `%s' or a `%s' directory", CVSADM, OCVSADM); + return (1); + } + + /* before we do anything else, see if we have any per-directory tags */ + ParseTag (&tag, &date); + + /* now, remember where we were, so we can get back */ + if (getwd (cwd) == NULL) + { + error (0, 0, "cannot get working directory: %s", cwd); + return (1); + } + if (chdir (dir) < 0) + { + error (0, errno, "cannot chdir to %s", dir); + return (1); + } + if (isfile (CVSADM) || isfile (OCVSADM)) + { + error (0, 0, + "%s/%s (or %s/%s) already exists", dir, CVSADM, dir, OCVSADM); + goto out; + } + + (void) sprintf (rcsdir, "%s/%s", repository, dir); + if (isfile (rcsdir) && !isdir (rcsdir)) + { + error (0, 0, "%s is not a directory; %s not added", rcsdir, dir); + goto out; + } + + /* setup the log message */ + (void) sprintf (message, "Directory %s added to the repository\n", rcsdir); + if (tag) + { + (void) strcat (message, "--> Using per-directory sticky tag `"); + (void) strcat (message, tag); + (void) strcat (message, "'\n"); + } + if (date) + { + (void) strcat (message, "--> Using per-directory sticky date `"); + (void) strcat (message, date); + (void) strcat (message, "'\n"); + } + + if (!isdir (rcsdir)) + { + mode_t omask; + char line[MAXLINELEN]; + Node *p; + List *ulist; + + (void) printf ("Add directory %s to the repository (y/n) [n] ? ", + rcsdir); + (void) fflush (stdout); + clearerr (stdin); + if (fgets (line, sizeof (line), stdin) == NULL || + (line[0] != 'y' && line[0] != 'Y')) + { + error (0, 0, "directory %s not added", rcsdir); + goto out; + } + omask = umask (2); + if (mkdir (rcsdir, 0777) < 0) + { + error (0, errno, "cannot mkdir %s", rcsdir); + (void) umask ((int) omask); + goto out; + } + (void) umask ((int) omask); + + /* + * Set up an update list with a single title node for Update_Logfile + */ + ulist = getlist (); + p = getnode (); + p->type = UPDATE; + p->delproc = update_delproc; + p->key = xstrdup ("- New directory"); + p->data = (char *) T_TITLE; + (void) addnode (ulist, p); + Update_Logfile (rcsdir, message, (char *) NULL, (FILE *) NULL, ulist); + dellist (&ulist); + } + + Create_Admin (".", rcsdir, tag, date); + if (tag) + free (tag); + if (date) + free (date); + + (void) printf ("%s", message); +out: + if (chdir (cwd) < 0) + error (1, errno, "cannot chdir to %s", cwd); + return (0); +} + +/* + * Builds an entry for a new file and sets up "CVS/file",[pt] by + * interrogating the user. Returns non-zero on error. + */ +static int +build_entry (repository, user, options, message, entries) + char *repository; + char *user; + char *options; + char *message; + List *entries; +{ + char fname[PATH_MAX]; + char line[MAXLINELEN]; + FILE *fp; + + /* + * There may be an old file with the same name in the Attic! This is, + * perhaps, an awkward place to check for this, but other places are + * equally awkward. + */ + (void) sprintf (fname, "%s/%s/%s%s", repository, CVSATTIC, user, RCSEXT); + if (isreadable (fname)) + { + error (0, 0, "there is an old file %s already in %s/%s", user, + repository, CVSATTIC); + return (1); + } + + if (noexec) + return (0); + + /* + * The options for the "add" command are store in the file CVS/user,p + */ + (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_OPT); + fp = open_file (fname, "w+"); + if (fclose (fp) == EOF) + error(1, errno, "cannot close %s", fname); + + /* + * And the requested log is read directly from the user and stored in the + * file user,t. If the "message" argument is set, use it as the + * initial creation log (which typically describes the file). + */ + (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_LOG); + fp = open_file (fname, "w+"); + if (*message && fputs (message, fp) == EOF) + error (1, errno, "cannot write to %s", fname); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", fname); + + /* + * Create the entry now, since this allows the user to interrupt us above + * without needing to clean anything up (well, we could clean up the ,p + * and ,t files, but who cares). + */ + (void) sprintf (line, "Initial %s", user); + Register (entries, user, "0", line, options, (char *) 0, (char *) 0); + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/admin.c b/gnu/usr.bin/cvs/cvs/admin.c new file mode 100644 index 0000000..91d3929 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/admin.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Administration + * + * For now, this is basically a front end for rcs. All options are passed + * directly on. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)admin.c 1.17 92/03/31"; +#endif + +#if __STDC__ +static Dtype admin_dirproc (char *dir, char *repos, char *update_dir); +static int admin_fileproc (char *file, char *update_dir, + char *repository, List *entries, + List *srcfiles); +#else +static int admin_fileproc (); +static Dtype admin_dirproc (); +#endif /* __STDC__ */ + +static char *admin_usage[] = +{ + "Usage: %s %s rcs-options files...\n", + NULL +}; + +static int ac; +static char **av; + +int +admin (argc, argv) + int argc; + char *argv[]; +{ + int err; + + if (argc <= 1) + usage (admin_usage); + + /* skip all optional arguments to see if we have any file names */ + for (ac = 1; ac < argc; ac++) + if (argv[ac][0] != '-') + break; + argc -= ac; + av = argv + 1; + argv += ac; + ac--; + if (ac == 0 || argc == 0) + usage (admin_usage); + + /* start the recursion processor */ + err = start_recursion (admin_fileproc, (int (*) ()) NULL, admin_dirproc, + (int (*) ()) NULL, argc, argv, 0, + W_LOCAL, 0, 1, (char *) NULL, 1); + return (err); +} + +/* + * Called to run "rcs" on a particular file. + */ +/* ARGSUSED */ +static int +admin_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Vers_TS *vers; + char *version; + char **argv; + int argc; + int retcode = 0; + + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 0, 0, entries, srcfiles); + + version = vers->vn_user; + if (version == NULL) + return (0); + else if (strcmp (version, "0") == 0) + { + error (0, 0, "cannot admin newly added file `%s'", file); + return (0); + } + + run_setup ("%s%s", Rcsbin, RCS); + for (argc = ac, argv = av; argc; argc--, argv++) + run_arg (*argv); + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "%s failed for `%s'", RCS, file); + return (1); + } + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +admin_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Administrating %s", update_dir); + return (R_PROCESS); +} diff --git a/gnu/usr.bin/cvs/cvs/checkin.c b/gnu/usr.bin/cvs/cvs/checkin.c new file mode 100644 index 0000000..14f7c05 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/checkin.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Check In + * + * Does a very careful checkin of the file "user", and tries not to spoil its + * modification time (to avoid needless recompilations). When RCS ID keywords + * get expanded on checkout, however, the modification time is updated and + * there is no good way to get around this. + * + * Returns non-zero on error. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)checkin.c 1.40 92/03/31"; +#endif + +int +Checkin (type, file, repository, rcs, rev, tag, message, entries) + int type; + char *file; + char *repository; + char *rcs; + char *rev; + char *tag; + char *message; + List *entries; +{ + char fname[PATH_MAX]; + Vers_TS *vers; + + (void) printf ("Checking in %s;\n", file); + (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file); + + /* + * Move the user file to a backup file, so as to preserve its + * modification times, then place a copy back in the original file name + * for the checkin and checkout. + */ + if (!noexec) + copy_file (file, fname); + + run_setup ("%s%s -f %s%s", Rcsbin, RCS_CI, + rev ? "-r" : "", rev ? rev : ""); + run_args ("-m%s", message); + run_arg (rcs); + + switch (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) + { + case 0: /* everything normal */ + + /* + * The checkin succeeded, so now check the new file back out and + * see if it matches exactly with the one we checked in. If it + * does, just move the original user file back, thus preserving + * the modes; otherwise, we have no recourse but to leave the + * newly checkout file as the user file and remove the old + * original user file. + */ + + /* XXX - make sure -k options are used on the co; and tag/date? */ + run_setup ("%s%s -q %s%s", Rcsbin, RCS_CO, + rev ? "-r" : "", rev ? rev : ""); + run_arg (rcs); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + xchmod (file, 1); + if (xcmp (file, fname) == 0) + rename_file (fname, file); + else + (void) unlink_file (fname); + + /* + * If we want read-only files, muck the permissions here, before + * getting the file time-stamp. + */ + if (cvswrite == FALSE) + xchmod (file, 0); + + /* for added files with symbolic tags, need to add the tag too */ + if (type == 'A' && tag && !isdigit (*tag)) + { + run_setup ("%s%s -q -N%s:%s", Rcsbin, RCS, tag, rev); + run_arg (rcs); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + + /* re-register with the new data */ + vers = Version_TS (repository, (char *) NULL, tag, (char *) NULL, + file, 1, 1, entries, (List *) NULL); + if (strcmp (vers->options, "-V4") == 0) + vers->options[0] = '\0'; + Register (entries, file, vers->vn_rcs, vers->ts_user, vers->options, + vers->tag, vers->date); + history_write (type, (char *) 0, vers->vn_rcs, file, repository); + freevers_ts (&vers); + break; + + case -1: /* fork failed */ + if (!noexec) + error (1, errno, "could not check in %s -- fork failed", file); + return (1); + + default: /* ci failed */ + + /* + * The checkin failed, for some unknown reason, so we restore the + * original user file, print an error, and return an error + */ + if (!noexec) + { + rename_file (fname, file); + error (0, 0, "could not check in %s", file); + } + return (1); + } + + /* + * When checking in a specific revision, we may have locked the wrong + * branch, so to be sure, we do an extra unlock here before + * returning. + */ + if (rev) + { + run_setup ("%s%s -q -u", Rcsbin, RCS); + run_arg (rcs); + (void) run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL); + } + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/checkout.c b/gnu/usr.bin/cvs/cvs/checkout.c new file mode 100644 index 0000000..ad5a5b8 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/checkout.c @@ -0,0 +1,718 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Create Version + * + * "checkout" creates a "version" of an RCS repository. This version is owned + * totally by the user and is actually an independent copy, to be dealt with + * as seen fit. Once "checkout" has been called in a given directory, it + * never needs to be called again. The user can keep up-to-date by calling + * "update" when he feels like it; this will supply him with a merge of his + * own modifications and the changes made in the RCS original. See "update" + * for details. + * + * "checkout" can be given a list of directories or files to be updated and in + * the case of a directory, will recursivley create any sub-directories that + * exist in the repository. + * + * When the user is satisfied with his own modifications, the present version + * can be committed by "commit"; this keeps the present version in tact, + * usually. + * + * The call is cvs checkout [options] <module-name>... + * + * "checkout" creates a directory ./CVS, in which it keeps its administration, + * in two files, Repository and Entries. The first contains the name of the + * repository. The second contains one line for each registered file, + * consisting of the version number it derives from, its time stamp at + * derivation time and its name. Both files are normal files and can be + * edited by the user, if necessary (when the repository is moved, e.g.) + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)checkout.c 1.67 92/04/10"; +#endif + +#if __STDC__ +static char *findslash (char *start, char *p); +static int build_dirs_and_chdir (char *dir, char *prepath, char *realdir, + int sticky); +static int checkout_proc (int *pargc, char *argv[], char *where, + char *mwhere, char *mfile, int shorten, + int local_specified, char *omodule, + char *msg); +#else +static int checkout_proc (); +static char *findslash (); +static int build_dirs_and_chdir (); +#endif /* __STDC__ */ + +static char *checkout_usage[] = +{ + "Usage:\n %s %s [-ANPQcflnpqs] [-r rev | -D date] [-d dir] [-k kopt] modules...\n", + "\t-A\tReset any sticky tags/date/kopts.\n", + "\t-N\tDon't shorten module paths if -d specified.\n", + "\t-P\tPrune empty directories.\n", + "\t-Q\tReally quiet.\n", + "\t-c\t\"cat\" the module database.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-n\tDo not run module program (if any).\n", + "\t-p\tCheck out files to standard output.\n", + "\t-q\tSomewhat quiet.\n", + "\t-s\tLike -c, but include module status.\n", + "\t-r rev\tCheck out revision or tag. (implies -P)\n", + "\t-D date\tCheck out revisions as of date. (implies -P)\n", + "\t-d dir\tCheck out into dir instead of module name.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + "\t-j rev\tMerge in changes made between current revision and rev.\n", + NULL +}; + +static char *export_usage[] = +{ + "Usage: %s %s [-NPQflnq] [-r rev | -D date] [-d dir] module...\n", + "\t-N\tDon't shorten module paths if -d specified.\n", + "\t-Q\tReally quiet.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-n\tDo not run module program (if any).\n", + "\t-q\tSomewhat quiet.\n", + "\t-r rev\tCheck out revision or tag. (implies -P)\n", + "\t-D date\tCheck out revisions as of date. (implies -P)\n", + "\t-d dir\tCheck out into dir instead of module name.\n", + NULL +}; + +static int checkout_prune_dirs; +static int force_tag_match = 1; +static int pipeout; +static int aflag; +static char *options = NULL; +static char *tag = NULL; +static char *date = NULL; +static char *join_rev1 = NULL; +static char *join_rev2 = NULL; +static char *preload_update_dir = NULL; + +int +checkout (argc, argv) + int argc; + char *argv[]; +{ + register int i; + int c; + DBM *db; + int cat = 0, err = 0, status = 0; + int run_module_prog = 1; + int local = 0; + int shorten = -1; + char *where = NULL; + char *valid_options, **valid_usage; + + /* + * A smaller subset of options are allowed for the export command, which + * is essentially like checkout, except that it hard-codes certain + * options to be on (like -kv) and takes care to remove the CVS directory + * when it has done its duty + */ + if (strcmp (command_name, "export") == 0) + { + valid_options = "Nnd:flRQqr:D:"; + valid_usage = export_usage; + } + else + { + valid_options = "ANnk:d:flRpQqcsr:D:j:P"; + valid_usage = checkout_usage; + } + + if (argc == -1) + usage (valid_usage); + + ign_setup (); + + optind = 1; + while ((c = gnu_getopt (argc, argv, valid_options)) != -1) + { + switch (c) + { + case 'A': + aflag = 1; + break; + case 'N': + shorten = 0; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'n': + run_module_prog = 0; + break; + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'P': + checkout_prune_dirs = 1; + break; + case 'p': + pipeout = 1; + run_module_prog = 0; /* don't run module prog when piping */ + noexec = 1; /* so no locks will be created */ + break; + case 'c': + cat = 1; + break; + case 'd': + where = optarg; + if (shorten == -1) + shorten = 1; + break; + case 's': + status = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'r': + tag = optarg; + checkout_prune_dirs = 1; + break; + case 'D': + date = Make_Date (optarg); + checkout_prune_dirs = 1; + break; + case 'j': + if (join_rev2) + error (1, 0, "only two -j options can be specified"); + if (join_rev1) + join_rev2 = optarg; + else + join_rev1 = optarg; + break; + case '?': + default: + usage (valid_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (shorten == -1) + shorten = 0; + + if ((!(cat + status) && argc == 0) || ((cat + status) && argc != 0) + || (tag && date)) + usage (valid_usage); + + if (where && pipeout) + error (1, 0, "-d and -p are mutually exclusive"); + + if (strcmp (command_name, "export") == 0) + { + if (!tag && !date) + { + error (0, 0, "must specify a tag or date"); + usage (valid_usage); + } + if (tag && isdigit (tag[0])) + error (1, 0, "tag `%s' must be a symbolic tag", tag); + options = RCS_check_kflag ("v");/* -kv must be on */ + } + + if (cat || status) + { + cat_module (status); + return (0); + } + db = open_module (); + + /* + * if we have more than one argument and where was specified, we make the + * where, cd into it, and try to shorten names as much as possible. + * Otherwise, we pass the where as a single argument to do_module. + */ + if (argc > 1 && where != NULL) + { + char repository[PATH_MAX]; + + (void) mkdir (where, 0777); + if (chdir (where) < 0) + error (1, errno, "cannot chdir to %s", where); + preload_update_dir = xstrdup (where); + where = (char *) NULL; + if (!isfile (CVSADM) && !isfile (OCVSADM)) + { + (void) sprintf (repository, "%s/%s", CVSroot, CVSNULLREPOS); + if (!isfile (repository)) + (void) mkdir (repository, 0777); + Create_Admin (".", repository, (char *) NULL, (char *) NULL); + if (!noexec) + { + FILE *fp; + + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); + } + } + } + + /* + * if where was specified (-d) and we have not taken care of it already + * with the multiple arg stuff, and it was not a simple directory name + * but rather a path, we strip off everything but the last component and + * attempt to cd to the indicated place. where then becomes simply the + * last component + */ + if (where != NULL && index (where, '/') != NULL) + { + char *slash; + + slash = rindex (where, '/'); + *slash = '\0'; + + if (chdir (where) < 0) + error (1, errno, "cannot chdir to %s", where); + + preload_update_dir = xstrdup (where); + + where = slash + 1; + if (*where == '\0') + where = NULL; + } + + for (i = 0; i < argc; i++) + err += do_module (db, argv[i], CHECKOUT, "Updating", checkout_proc, + where, shorten, local, run_module_prog, + (char *) NULL); + close_module (db); + return (err); +} + +/* + * process_module calls us back here so we do the actual checkout stuff + */ +/* ARGSUSED */ +static int +checkout_proc (pargc, argv, where, mwhere, mfile, shorten, + local_specified, omodule, msg) + int *pargc; + char *argv[]; + char *where; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *omodule; + char *msg; +{ + int err = 0; + int which; + char *cp; + char *cp2; + char repository[PATH_MAX]; + char xwhere[PATH_MAX]; + char *oldupdate = NULL; + char *prepath; + char *realdirs; + + /* + * OK, so we're doing the checkout! Our args are as follows: + * argc,argv contain either dir or dir followed by a list of files + * where contains where to put it (if supplied by checkout) + * mwhere contains the module name or -d from module file + * mfile says do only that part of the module + * shorten = TRUE says shorten as much as possible + * omodule is the original arg to do_module() + */ + + /* set up the repository (maybe) for the bottom directory */ + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + + /* save the original value of preload_update_dir */ + if (preload_update_dir != NULL) + oldupdate = xstrdup (preload_update_dir); + + /* fix up argv[] for the case of partial modules */ + if (mfile != NULL) + { + char file[PATH_MAX]; + + /* if mfile is really a path, straighten it out first */ + if ((cp = rindex (mfile, '/')) != NULL) + { + *cp = 0; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + + /* + * Now we need to fill in the where correctly. if !shorten, tack + * the rest of the path onto where if where is filled in + * otherwise tack the rest of the path onto mwhere and make that + * the where + * + * If shorten is enabled, we might use mwhere to set where if + * nobody set it yet, so we'll need to setup mwhere as the last + * component of the path we are tacking onto repository + */ + if (!shorten) + { + if (where != NULL) + (void) sprintf (xwhere, "%s/%s", where, mfile); + else + (void) sprintf (xwhere, "%s/%s", mwhere, mfile); + where = xwhere; + } + else + { + char *slash; + + if ((slash = rindex (mfile, '/')) != NULL) + mwhere = slash + 1; + else + mwhere = mfile; + } + mfile = cp + 1; + } + + (void) sprintf (file, "%s/%s", repository, mfile); + if (isdir (file)) + { + + /* + * The portion of a module was a directory, so kludge up where to + * be the subdir, and fix up repository + */ + (void) strcpy (repository, file); + + /* + * At this point, if shorten is not enabled, we make where either + * where with mfile concatenated, or if where hadn't been set we + * set it to mwhere with mfile concatenated. + * + * If shorten is enabled and where hasn't been set yet, then where + * becomes mfile + */ + if (!shorten) + { + if (where != NULL) + (void) sprintf (xwhere, "%s/%s", where, mfile); + else + (void) sprintf (xwhere, "%s/%s", mwhere, mfile); + where = xwhere; + } + else if (where == NULL) + where = mfile; + } + else + { + int i; + + /* + * The portion of a module was a file, so kludge up argv to be + * correct + */ + for (i = 1; i < *pargc; i++)/* free the old ones */ + free (argv[i]); + argv[1] = xstrdup (mfile); /* set up the new one */ + *pargc = 2; + + /* where gets mwhere if where isn't set */ + if (where == NULL) + where = mwhere; + } + } + + /* + * if shorten is enabled and where isn't specified yet, we pluck the last + * directory component of argv[0] and make it the where + */ + if (shorten && where == NULL) + { + if ((cp = rindex (argv[0], '/')) != NULL) + { + (void) strcpy (xwhere, cp + 1); + where = xwhere; + } + } + + /* if where is still NULL, use mwhere if set or the argv[0] dir */ + if (where == NULL) + { + if (mwhere) + where = mwhere; + else + { + (void) strcpy (xwhere, argv[0]); + where = xwhere; + } + } + + if (preload_update_dir != NULL) + { + char tmp[PATH_MAX]; + + (void) sprintf (tmp, "%s/%s", preload_update_dir, where); + free (preload_update_dir); + preload_update_dir = xstrdup (tmp); + } + else + preload_update_dir = xstrdup (where); + + /* + * At this point, where is the directory we want to build, repository is + * the repository for the lowest level of the path. + */ + + /* + * If we are sending everything to stdout, we can skip a whole bunch of + * work from here + */ + if (!pipeout) + { + + /* + * We need to tell build_dirs not only the path we want it to build, + * but also the repositories we want it to populate the path with. To + * accomplish this, we pass build_dirs a ``real path'' with valid + * repositories and a string to pre-pend based on how many path + * elements exist in where. Big Black Magic + */ + prepath = xstrdup (repository); + cp = rindex (where, '/'); + cp2 = rindex (prepath, '/'); + while (cp != NULL) + { + cp = findslash (where, cp - 1); + cp2 = findslash (prepath, cp2 - 1); + } + *cp2 = '\0'; + realdirs = cp2 + 1; + + /* + * build dirs on the path if necessary and leave us in the bottom + * directory (where if where was specified) doesn't contain a CVS + * subdir yet, but all the others contain CVS and Entries.Static + * files + */ + if (build_dirs_and_chdir (where, prepath, realdirs, *pargc <= 1) != 0) + { + error (0, 0, "ignoring module %s", omodule); + free (prepath); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + + /* clean up */ + free (prepath); + + /* set up the repository (or make sure the old one matches) */ + if (!isfile (CVSADM) && !isfile (OCVSADM)) + { + FILE *fp; + + if (!noexec && *pargc > 1) + { + Create_Admin (".", repository, (char *) NULL, (char *) NULL); + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); + } + else + Create_Admin (".", repository, tag, date); + } + else + { + char *repos; + + /* get the contents of the previously existing repository */ + repos = Name_Repository ((char *) NULL, preload_update_dir); + if (strcmp (repository, repos) != 0) + { + error (0, 0, "existing repository %s does not match %s", + repos, repository); + error (0, 0, "ignoring module %s", omodule); + free (repos); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + free (repos); + } + } + + /* + * If we are going to be updating to stdout, we need to cd to the + * repository directory so the recursion processor can use the current + * directory as the place to find repository information + */ + if (pipeout) + { + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (1); + } + which = W_REPOS; + } + else + which = W_LOCAL | W_REPOS; + + if (tag != NULL || date != NULL) + which |= W_ATTIC; + + /* + * if we are going to be recursive (building dirs), go ahead and call the + * update recursion processor. We will be recursive unless either local + * only was specified, or we were passed arguments + */ + if (!(local_specified || *pargc > 1)) + { + if (strcmp (command_name, "export") != 0 && !pipeout) + history_write ('O', preload_update_dir, tag ? tag : date, where, + repository); + err += do_update (0, (char **) NULL, options, tag, date, + force_tag_match, 0 /* !local */ , + 1 /* update -d */ , aflag, checkout_prune_dirs, + pipeout, which, join_rev1, join_rev2, + preload_update_dir); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (err); + } + + if (!pipeout) + { + int i; + List *entries; + + /* we are only doing files, so register them */ + entries = ParseEntries (0); + for (i = 1; i < *pargc; i++) + { + char line[MAXLINELEN]; + char *user; + Vers_TS *vers; + + user = argv[i]; + vers = Version_TS (repository, options, tag, date, user, + force_tag_match, 0, entries, (List *) NULL); + if (vers->ts_user == NULL) + { + (void) sprintf (line, "Initial %s", user); + Register (entries, user, vers->vn_rcs, line, vers->options, + vers->tag, vers->date); + } + freevers_ts (&vers); + } + dellist (&entries); + } + + /* Don't log "export", just regular "checkouts" */ + if (strcmp (command_name, "export") != 0 && !pipeout) + history_write ('O', preload_update_dir, (tag ? tag : date), where, + repository); + + /* go ahead and call update now that everything is set */ + err += do_update (*pargc - 1, argv + 1, options, tag, date, + force_tag_match, local_specified, 1 /* update -d */, + aflag, checkout_prune_dirs, pipeout, which, join_rev1, + join_rev2, preload_update_dir); + free (preload_update_dir); + preload_update_dir = oldupdate; + return (err); +} + +static char * +findslash (start, p) + char *start; + char *p; +{ + while ((int) p >= (int) start && *p != '/') + p--; + if ((int) p < (int) start) + return (NULL); + else + return (p); +} + +/* + * build all the dirs along the path to dir with CVS subdirs with appropriate + * repositories and Entries.Static files + */ +static int +build_dirs_and_chdir (dir, prepath, realdir, sticky) + char *dir; + char *prepath; + char *realdir; + int sticky; +{ + FILE *fp; + char repository[PATH_MAX]; + char path[PATH_MAX]; + char path2[PATH_MAX]; + char *slash; + char *slash2; + char *cp; + char *cp2; + + (void) strcpy (path, dir); + (void) strcpy (path2, realdir); + for (cp = path, cp2 = path2; + (slash = index (cp, '/')) != NULL && (slash2 = index (cp2, '/')) != NULL; + cp = slash + 1, cp2 = slash2 + 1) + { + *slash = '\0'; + *slash2 = '\0'; + (void) mkdir (cp, 0777); + if (chdir (cp) < 0) + { + error (0, errno, "cannot chdir to %s", cp); + return (1); + } + if (!isfile (CVSADM) && !isfile (OCVSADM) && + strcmp (command_name, "export") != 0) + { + (void) sprintf (repository, "%s/%s", prepath, path2); + Create_Admin (".", repository, sticky ? (char *) NULL : tag, + sticky ? (char *) NULL : date); + if (!noexec) + { + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose(fp) == EOF) + error(1, errno, "cannot close %s", CVSADM_ENTSTAT); + } + } + *slash = '/'; + *slash2 = '/'; + } + (void) mkdir (cp, 0777); + if (chdir (cp) < 0) + { + error (0, errno, "cannot chdir to %s", cp); + return (1); + } + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/classify.c b/gnu/usr.bin/cvs/cvs/classify.c new file mode 100644 index 0000000..318fab8 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/classify.c @@ -0,0 +1,380 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)classify.c 1.11 92/03/31"; +#endif + +#if __STDC__ +static void sticky_ck (char *file, int aflag, Vers_TS * vers, List * entries); +#else +static void sticky_ck (); +#endif /* __STDC__ */ + +/* + * Classify the state of a file + */ +Ctype +Classify_File (file, tag, date, options, force_tag_match, aflag, repository, + entries, srcfiles, versp) + char *file; + char *tag; + char *date; + char *options; + int force_tag_match; + int aflag; + char *repository; + List *entries; + List *srcfiles; + Vers_TS **versp; +{ + Vers_TS *vers; + Ctype ret; + + /* get all kinds of good data about the file */ + vers = Version_TS (repository, options, tag, date, file, + force_tag_match, 0, entries, srcfiles); + + if (vers->vn_user == NULL) + { + /* No entry available, ts_rcs is invalid */ + if (vers->vn_rcs == NULL) + { + /* there is no RCS file either */ + if (vers->ts_user == NULL) + { + /* there is no user file */ + if (!force_tag_match || !(vers->tag || vers->date)) + if (!really_quiet) + error (0, 0, "nothing known about %s", file); + ret = T_UNKNOWN; + } + else + { + /* there is a user file */ + if (!force_tag_match || !(vers->tag || vers->date)) + if (!really_quiet) + error (0, 0, "use `cvs add' to create an entry for %s", + file); + ret = T_UNKNOWN; + } + } + else + { + /* there is an rcs file */ + + if (vers->ts_user == NULL) + { + /* There is no user file; needs checkout */ + ret = T_CHECKOUT; + } + else + { + /* + * There is a user file; print a warning and add it to the + * conflict list, only if it is indeed different from what we + * plan to extract + */ + if (No_Difference (file, vers, entries)) + { + /* the files were different so it is a conflict */ + if (!really_quiet) + error (0, 0, "move away %s; it is in the way", file); + ret = T_CONFLICT; + } + else + /* since there was no difference, still needs checkout */ + ret = T_CHECKOUT; + } + } + } + else if (strcmp (vers->vn_user, "0") == 0) + { + /* An entry for a new-born file; ts_rcs is dummy */ + + if (vers->ts_user == NULL) + { + /* + * There is no user file, but there should be one; remove the + * entry + */ + if (!really_quiet) + error (0, 0, "warning: new-born %s has disappeared", file); + ret = T_REMOVE_ENTRY; + } + else + { + /* There is a user file */ + + if (vers->vn_rcs == NULL) + /* There is no RCS file, added file */ + ret = T_ADDED; + else + { + /* + * There is an RCS file, so someone else must have checked + * one in behind our back; conflict + */ + if (!really_quiet) + error (0, 0, + "conflict: %s created independently by second party", + file); + ret = T_CONFLICT; + } + } + } + else if (vers->vn_user[0] == '-') + { + /* An entry for a removed file, ts_rcs is invalid */ + + if (vers->ts_user == NULL) + { + char tmp[PATH_MAX]; + + /* There is no user file (as it should be) */ + + (void) sprintf (tmp, "-%s", vers->vn_rcs ? vers->vn_rcs : ""); + + if (vers->vn_rcs == NULL) + { + + /* + * There is no RCS file; this is all-right, but it has been + * removed independently by a second party; remove the entry + */ + ret = T_REMOVE_ENTRY; + } + else if (strcmp (tmp, vers->vn_user) == 0) + + /* + * The RCS file is the same version as the user file was, and + * that's OK; remove it + */ + ret = T_REMOVED; + else + { + + /* + * The RCS file is a newer version than the removed user file + * and this is definitely not OK; make it a conflict. + */ + if (!really_quiet) + error (0, 0, + "conflict: removed %s was modified by second party", + file); + ret = T_CONFLICT; + } + } + else + { + /* The user file shouldn't be there */ + if (!really_quiet) + error (0, 0, "%s should be removed and is still there", file); + ret = T_REMOVED; + } + } + else + { + /* A normal entry, TS_Rcs is valid */ + if (vers->vn_rcs == NULL) + { + /* There is no RCS file */ + + if (vers->ts_user == NULL) + { + /* There is no user file, so just remove the entry */ + if (!really_quiet) + error (0, 0, "warning: %s is not (any longer) pertinent", + file); + ret = T_REMOVE_ENTRY; + } + else if (strcmp (vers->ts_user, vers->ts_rcs) == 0) + { + + /* + * The user file is still unmodified, so just remove it from + * the entry list + */ + if (!really_quiet) + error (0, 0, "%s is no longer in the repository", file); + ret = T_REMOVE_ENTRY; + } + else + { + /* + * The user file has been modified and since it is no longer + * in the repository, a conflict is raised + */ + if (No_Difference (file, vers, entries)) + { + /* they are different -> conflict */ + if (!really_quiet) + error (0, 0, + "conflict: %s is modified but no longer in the repository", + file); + ret = T_CONFLICT; + } + else + { + /* they weren't really different */ + if (!really_quiet) + error (0, 0, + "warning: %s is not (any longer) pertinent", + file); + ret = T_REMOVE_ENTRY; + } + } + } + else if (strcmp (vers->vn_rcs, vers->vn_user) == 0) + { + /* The RCS file is the same version as the user file */ + + if (vers->ts_user == NULL) + { + + /* + * There is no user file, so note that it was lost and + * extract a new version + */ + if (strcmp (command_name, "update") == 0) + if (!really_quiet) + error (0, 0, "warning: %s was lost", file); + ret = T_CHECKOUT; + } + else if (strcmp (vers->ts_user, vers->ts_rcs) == 0) + { + + /* + * The user file is still unmodified, so nothing special at + * all to do -- no lists updated, unless the sticky -k option + * has changed. If the sticky tag has changed, we just need + * to re-register the entry + */ + if (vers->entdata->options && + strcmp (vers->entdata->options, vers->options) != 0) + ret = T_CHECKOUT; + else + { + sticky_ck (file, aflag, vers, entries); + ret = T_UPTODATE; + } + } + else + { + + /* + * The user file appears to have been modified, but we call + * No_Difference to verify that it really has been modified + */ + if (No_Difference (file, vers, entries)) + { + + /* + * they really are different; modified if we aren't + * changing any sticky -k options, else needs merge + */ +#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED + if (strcmp (vers->entdata->options ? + vers->entdata->options : "", vers->options) == 0) + ret = T_MODIFIED; + else + ret = T_NEEDS_MERGE; +#else + ret = T_MODIFIED; + sticky_ck (file, aflag, vers, entries); +#endif + } + else + { + /* file has not changed; check out if -k changed */ + if (strcmp (vers->entdata->options ? + vers->entdata->options : "", vers->options) != 0) + { + ret = T_CHECKOUT; + } + else + { + + /* + * else -> note that No_Difference will Register the + * file already for us, using the new tag/date. This + * is the desired behaviour + */ + ret = T_UPTODATE; + } + } + } + } + else + { + /* The RCS file is a newer version than the user file */ + + if (vers->ts_user == NULL) + { + /* There is no user file, so just get it */ + + if (strcmp (command_name, "update") == 0) + if (!really_quiet) + error (0, 0, "warning: %s was lost", file); + ret = T_CHECKOUT; + } + else if (strcmp (vers->ts_user, vers->ts_rcs) == 0) + { + + /* + * The user file is still unmodified, so just get it as well + */ + ret = T_CHECKOUT; + } + else + { + if (No_Difference (file, vers, entries)) + /* really modified, needs to merge */ + ret = T_NEEDS_MERGE; + else + /* not really modified, check it out */ + ret = T_CHECKOUT; + } + } + } + + /* free up the vers struct, or just return it */ + if (versp != (Vers_TS **) NULL) + *versp = vers; + else + freevers_ts (&vers); + + /* return the status of the file */ + return (ret); +} + +static void +sticky_ck (file, aflag, vers, entries) + char *file; + int aflag; + Vers_TS *vers; + List *entries; +{ + if (aflag || vers->tag || vers->date) + { + char *enttag = vers->entdata->tag; + char *entdate = vers->entdata->date; + + if ((enttag && vers->tag && strcmp (enttag, vers->tag)) || + ((enttag && !vers->tag) || (!enttag && vers->tag)) || + (entdate && vers->date && strcmp (entdate, vers->date)) || + ((entdate && !vers->date) || (!entdate && vers->date))) + { + Register (entries, file, vers->vn_user, vers->ts_rcs, + vers->options, vers->tag, vers->date); + } + } +} diff --git a/gnu/usr.bin/cvs/cvs/commit.c b/gnu/usr.bin/cvs/cvs/commit.c new file mode 100644 index 0000000..1b2f3be --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/commit.c @@ -0,0 +1,1229 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Commit Files + * + * "commit" commits the present version to the RCS repository, AFTER + * having done a test on conflicts. + * + * The call is: cvs commit [options] files... + * + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)commit.c 1.84 92/03/31"; +#endif + +#if __STDC__ +static Dtype check_direntproc (char *dir, char *repos, char *update_dir); +static int check_fileproc (char *file, char *update_dir, char *repository, + List * entries, List * srcfiles); +static int check_filesdoneproc (int err, char *repos, char *update_dir); +static int checkaddfile (char *file, char *repository, char *tag); +static Dtype commit_direntproc (char *dir, char *repos, char *update_dir); +static int commit_dirleaveproc (char *dir, int err, char *update_dir); +static int commit_fileproc (char *file, char *update_dir, char *repository, + List * entries, List * srcfiles); +static int commit_filesdoneproc (int err, char *repository, char *update_dir); +static int finaladd (char *file, char *revision, char *tag, char *repository, + List *entries); +static int findmaxrev (Node * p); +static int fsortcmp (Node * p, Node * q); +static int lock_RCS (char *user, char *rcs, char *rev, char *repository); +static int lock_filesdoneproc (int err, char *repository, char *update_dir); +static int lockrcsfile (char *file, char *repository, char *rev); +static int precommit_list_proc (Node * p); +static int precommit_proc (char *repository, char *filter); +static int remove_file (char *file, char *repository, char *tag, + List *entries); +static void fix_rcs_modes (char *rcs, char *user); +static void fixaddfile (char *file, char *repository); +static void fixbranch (char *file, char *repository, char *branch); +static void unlockrcs (char *file, char *repository); +static void ci_delproc (Node *p); +static void locate_rcs (char *file, char *repository, char *rcs); +#else +static int fsortcmp (); +static int lock_filesdoneproc (); +static int check_fileproc (); +static Dtype check_direntproc (); +static int precommit_list_proc (); +static int precommit_proc (); +static int check_filesdoneproc (); +static int commit_fileproc (); +static int commit_filesdoneproc (); +static Dtype commit_direntproc (); +static int commit_dirleaveproc (); +static int findmaxrev (); +static int remove_file (); +static int finaladd (); +static void unlockrcs (); +static void fixaddfile (); +static void fixbranch (); +static int checkaddfile (); +static int lockrcsfile (); +static int lock_RCS (); +static void fix_rcs_modes (); +static void ci_delproc (); +static void locate_rcs (); +#endif /* __STDC__ */ + +struct commit_info +{ + Ctype status; /* as returned from Classify_File() */ + char *rev; /* a numeric rev, if we know it */ + char *tag; /* any sticky tag, or -r option */ +}; +struct master_lists +{ + List *ulist; /* list for Update_Logfile */ + List *cilist; /* list with commit_info structs */ +}; + +static int got_message; +static int run_module_prog = 1; +static int aflag; +static char *tag; +static char *write_dirtag; +static char *logfile; +static List *mulist; +static List *locklist; +static char *message; + +static char *commit_usage[] = +{ + "Usage: %s %s [-nRl] [-m msg | -f logfile] [-r rev] files...\n", + "\t-n\tDo not run the module program (if any).\n", + "\t-R\tProcess directories recursively.\n", + "\t-l\tLocal directory only (not recursive).\n", + "\t-f file\tRead the log message from file.\n", + "\t-m msg\tLog message.\n", + "\t-r rev\tCommit to this branch or trunk revision.\n", + NULL +}; + +int +commit (argc, argv) + int argc; + char *argv[]; +{ + int c; + int err = 0; + int local = 0; + + if (argc == -1) + usage (commit_usage); + +#ifdef CVS_BADROOT + /* + * For log purposes, do not allow "root" to commit files. If you look + * like root, but are really logged in as a non-root user, it's OK. + */ + if (geteuid () == (uid_t) 0) + { + struct passwd *pw; + + if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL) + error (1, 0, "you are unknown to this system"); + if (pw->pw_uid == (uid_t) 0) + error (1, 0, "cannot commit files as 'root'"); + } +#endif /* CVS_BADROOT */ + + message = xmalloc (MAXMESGLEN + 1); + message[0] = '\0'; /* Null message by default */ + optind = 1; + while ((c = gnu_getopt (argc, argv, "nlRm:f:r:")) != -1) + { + switch (c) + { + case 'n': + run_module_prog = 0; + break; + case 'm': +#ifdef FORCE_USE_EDITOR + use_editor = TRUE; +#else + use_editor = FALSE; +#endif + if (strlen (optarg) >= (size_t) MAXMESGLEN) + { + error (0, 0, "warning: message too long; truncated!"); + (void) strncpy (message, optarg, MAXMESGLEN); + message[MAXMESGLEN] = '\0'; + } + else + (void) strcpy (message, optarg); + break; + case 'r': + if (tag) + free (tag); + tag = xstrdup (optarg); + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'f': +#ifdef FORCE_USE_EDITOR + use_editor = TRUE; +#else + use_editor = FALSE; +#endif + logfile = optarg; + break; + case '?': + default: + usage (commit_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* numeric specified revision means we ignore sticky tags... */ + if (tag && isdigit (*tag)) + { + aflag = 1; + /* strip trailing dots */ + while (tag[strlen (tag) - 1] == '.') + tag[strlen (tag) - 1] = '\0'; + } + + /* some checks related to the "-f logfile" option */ + if (logfile) + { + int n, logfd; + + if (*message) + error (1, 0, "cannot specify both a message and a log file"); + + if ((logfd = open (logfile, O_RDONLY)) < 0 || + (n = read (logfd, message, MAXMESGLEN)) < 0) + { + error (1, errno, "cannot read log message from %s", logfile); + } + (void) close (logfd); + message[n] = '\0'; + } + + /* XXX - this is not the perfect check for this */ + if (argc <= 0) + write_dirtag = tag; + + /* + * Run the recursion processor to find all the dirs to lock and lock all + * the dirs + */ + locklist = getlist (); + err = start_recursion ((int (*) ()) NULL, lock_filesdoneproc, + (Dtype (*) ()) NULL, (int (*) ()) NULL, argc, + argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0); + sortlist (locklist, fsortcmp); + if (Writer_Lock (locklist) != 0) + error (1, 0, "lock failed - giving up"); + + /* + * Set up the master update list + */ + mulist = getlist (); + + /* + * Run the recursion processor to verify the files are all up-to-date + */ + err = start_recursion (check_fileproc, check_filesdoneproc, + check_direntproc, (int (*) ()) NULL, argc, + argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1); + if (err) + { + Lock_Cleanup (); + error (1, 0, "correct above errors first!"); + } + + /* + * Run the recursion processor to commit the files + */ + if (noexec == 0) + err = start_recursion (commit_fileproc, commit_filesdoneproc, + commit_direntproc, commit_dirleaveproc, + argc, argv, local, W_LOCAL, aflag, 0, + (char *) NULL, 1); + + /* + * Unlock all the dirs and clean up + */ + Lock_Cleanup (); + dellist (&mulist); + dellist (&locklist); + return (err); +} + +/* + * compare two lock list nodes (for sort) + */ +static int +fsortcmp (p, q) + Node *p, *q; +{ + return (strcmp (p->key, q->key)); +} + +/* + * Create a list of repositories to lock + */ +/* ARGSUSED */ +static int +lock_filesdoneproc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + Node *p; + + p = getnode (); + p->type = LOCK; + p->key = xstrdup (repository); + if (p->key == NULL || addnode (locklist, p) != 0) + freenode (p); + return (err); +} + +/* + * Check to see if a file is ok to commit and make sure all files are + * up-to-date + */ +/* ARGSUSED */ +static int +check_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Ctype status; + char *xdir; + Node *p; + List *ulist, *cilist; + Vers_TS *vers; + struct commit_info *ci; + int save_noexec, save_quiet, save_really_quiet; + + save_noexec = noexec; + save_quiet = quiet; + save_really_quiet = really_quiet; + noexec = quiet = really_quiet = 1; + + /* handle specified numeric revision specially */ + if (tag && isdigit (*tag)) + { + /* If the tag is for the trunk, make sure we're at the head */ + if (numdots (tag) < 2) + { + status = Classify_File (file, (char *) NULL, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers); + if (status == T_UPTODATE) + { + freevers_ts (&vers); + status = Classify_File (file, tag, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers); + if (status == T_REMOVE_ENTRY) + status = T_MODIFIED; + } + } + else + { + char *xtag, *cp; + + /* + * The revision is off the main trunk; make sure we're + * up-to-date with the head of the specified branch. + */ + xtag = xstrdup (tag); + if ((numdots (xtag) & 1) != 0) + { + cp = rindex (xtag, '.'); + *cp = '\0'; + } + status = Classify_File (file, xtag, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers); + if ((status == T_REMOVE_ENTRY || status == T_CONFLICT) + && (cp = rindex (xtag, '.')) != NULL) + { + /* pluck one more dot off the revision */ + *cp = '\0'; + freevers_ts (&vers); + status = Classify_File (file, xtag, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers); + if (status == T_UPTODATE || status == T_REMOVE_ENTRY) + status = T_MODIFIED; + } + /* now, muck with vers to make the tag correct */ + free (vers->tag); + vers->tag = xstrdup (tag); + free (xtag); + } + } + else + status = Classify_File (file, tag, (char *) NULL, (char *) NULL, + 1, 0, repository, entries, srcfiles, &vers); + noexec = save_noexec; + quiet = save_quiet; + really_quiet = save_really_quiet; + + switch (status) + { + case T_CHECKOUT: + case T_NEEDS_MERGE: + case T_CONFLICT: + case T_REMOVE_ENTRY: + error (0, 0, "Up-to-date check failed for `%s'", file); + freevers_ts (&vers); + return (1); + case T_MODIFIED: + case T_ADDED: + case T_REMOVED: + /* + * some quick sanity checks; if no numeric -r option specified: + * - can't have a sticky date + * - can't have a sticky tag that is not a branch + * Also, + * - if status is T_REMOVED, can't have a numeric tag + * - if status is T_ADDED, rcs file must not exist + * - if status is T_ADDED, can't have a non-trunk numeric rev + */ + if (!tag || !isdigit (*tag)) + { + if (vers->date) + { + error (0, 0, + "cannot commit with sticky date for file `%s'", + file); + freevers_ts (&vers); + return (1); + } + if (status == T_MODIFIED && vers->tag && + !RCS_isbranch (file, vers->tag, srcfiles)) + { + error (0, 0, + "sticky tag `%s' for file `%s' is not a branch", + vers->tag, file); + freevers_ts (&vers); + return (1); + } + } + if (status == T_REMOVED && vers->tag && isdigit (*vers->tag)) + { + error (0, 0, + "cannot remove file `%s' which has a numeric sticky tag of `%s'", + file, vers->tag); + freevers_ts (&vers); + return (1); + } + if (status == T_ADDED) + { + char rcs[PATH_MAX]; + + locate_rcs (file, repository, rcs); + if (isreadable (rcs)) + { + error (0, 0, + "cannot add file `%s' when RCS file `%s' already exists", + file, rcs); + freevers_ts (&vers); + return (1); + } + if (vers->tag && isdigit (*vers->tag) && + numdots (vers->tag) > 1) + { + error (0, 0, + "cannot add file `%s' with revision `%s'; must be on trunk", + file, vers->tag); + freevers_ts (&vers); + return (1); + } + } + + /* done with consistency checks; now, to get on with the commit */ + if (update_dir[0] == '\0') + xdir = "."; + else + xdir = update_dir; + if ((p = findnode (mulist, xdir)) != NULL) + { + ulist = ((struct master_lists *) p->data)->ulist; + cilist = ((struct master_lists *) p->data)->cilist; + } + else + { + struct master_lists *ml; + + ulist = getlist (); + cilist = getlist (); + p = getnode (); + p->key = xstrdup (xdir); + p->type = UPDATE; + ml = (struct master_lists *) + xmalloc (sizeof (struct master_lists)); + ml->ulist = ulist; + ml->cilist = cilist; + p->data = (char *) ml; + (void) addnode (mulist, p); + } + + /* first do ulist, then cilist */ + p = getnode (); + p->key = xstrdup (file); + p->type = UPDATE; + p->delproc = update_delproc; + p->data = (char *) status; + (void) addnode (ulist, p); + + p = getnode (); + p->key = xstrdup (file); + p->type = UPDATE; + p->delproc = ci_delproc; + ci = (struct commit_info *) xmalloc (sizeof (struct commit_info)); + ci->status = status; + if (vers->tag) + if (isdigit (*vers->tag)) + ci->rev = xstrdup (vers->tag); + else + ci->rev = RCS_whatbranch (file, vers->tag, srcfiles); + else + ci->rev = (char *) NULL; + ci->tag = xstrdup (vers->tag); + p->data = (char *) ci; + (void) addnode (cilist, p); + break; + case T_UNKNOWN: + error (0, 0, "nothing known about `%s'", file); + freevers_ts (&vers); + return (1); + case T_UPTODATE: + break; + default: + error (0, 0, "Unknown status 0x%x for `%s'", status, file); + break; + } + + freevers_ts (&vers); + return (0); +} + +/* + * Print warm fuzzies while examining the dirs + */ +/* ARGSUSED */ +static Dtype +check_direntproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Examining %s", update_dir); + + return (R_PROCESS); +} + +/* + * Walklist proc to run pre-commit checks + */ +static int +precommit_list_proc (p) + Node *p; +{ + if (p->data == (char *) T_ADDED || p->data == (char *) T_MODIFIED) + run_arg (p->key); + return (0); +} + +/* + * Callback proc for pre-commit checking + */ +static List *ulist; +static int +precommit_proc (repository, filter) + char *repository; + char *filter; +{ + /* see if the filter is there, only if it's a full path */ + if (filter[0] == '/' && !isfile (filter)) + { + error (0, errno, "cannot find pre-commit filter `%s'", filter); + return (1); /* so it fails! */ + } + + run_setup ("%s %s", filter, repository); + (void) walklist (ulist, precommit_list_proc); + return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY)); +} + +/* + * Run the pre-commit checks for the dir + */ +/* ARGSUSED */ +static int +check_filesdoneproc (err, repos, update_dir) + int err; + char *repos; + char *update_dir; +{ + int n; + Node *p; + + /* find the update list for this dir */ + p = findnode (mulist, update_dir); + if (p != NULL) + ulist = ((struct master_lists *) p->data)->ulist; + else + ulist = (List *) NULL; + + /* skip the checks if there's nothing to do */ + if (ulist == NULL || ulist->list->next == ulist->list) + return (err); + + /* run any pre-commit checks */ + if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0) + { + error (0, 0, "Pre-commit check failed"); + err += n; + } + + return (err); +} + +/* + * Do the work of committing a file + */ +static int maxrev; +static char sbranch[PATH_MAX]; + +/* ARGSUSED */ +static int +commit_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Node *p; + int err = 0; + List *ulist, *cilist; + struct commit_info *ci; + char rcs[PATH_MAX]; + + if (update_dir[0] == '\0') + p = findnode (mulist, "."); + else + p = findnode (mulist, update_dir); + + /* + * if p is null, there were file type command line args which were + * all up-to-date so nothing really needs to be done + */ + if (p == NULL) + return (0); + ulist = ((struct master_lists *) p->data)->ulist; + cilist = ((struct master_lists *) p->data)->cilist; + + /* + * At this point, we should have the commit message unless we were called + * with files as args from the command line. In that latter case, we + * need to get the commit message ourselves + */ + if (use_editor && !got_message) + { + got_message = 1; + do_editor (update_dir, message, repository, ulist); + } + + p = findnode (cilist, file); + if (p == NULL) + return (0); + + ci = (struct commit_info *) p->data; + if (ci->status == T_MODIFIED) + { + if (lockrcsfile (file, repository, ci->rev) != 0) + { + unlockrcs (file, repository); + return (1); + } + } + else if (ci->status == T_ADDED) + { + if (checkaddfile (file, repository, ci->tag) != 0) + { + fixaddfile (file, repository); + return (1); + } + } + + /* + * Add the file for real + */ + if (ci->status == T_ADDED) + { + char *xrev = (char *) NULL; + + if (ci->rev == NULL) + { + /* find the max major rev number in this directory */ + maxrev = 0; + (void) walklist (entries, findmaxrev); + if (maxrev == 0) + maxrev = 1; + xrev = xmalloc (20); + (void) sprintf (xrev, "%d", maxrev); + } + + /* XXX - an added file with symbolic -r should add tag as well */ + err = finaladd (file, ci->rev ? ci->rev : xrev, ci->tag, + repository, entries); + if (xrev) + free (xrev); + return (err); + } + + if (ci->status == T_MODIFIED) + { + locate_rcs (file, repository, rcs); + err = Checkin ('M', file, repository, rcs, ci->rev, ci->tag, + message, entries); + if (err != 0) + { + unlockrcs (file, repository); + fixbranch (file, repository, sbranch); + } + } + + if (ci->status == T_REMOVED) + err = remove_file (file, repository, ci->tag, entries); + + return (err); +} + +/* + * Log the commit and clean up the update list + */ +/* ARGSUSED */ +static int +commit_filesdoneproc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + List *ulist, *cilist; + char *xtag = (char *) NULL; + Node *p; + + p = findnode (mulist, update_dir); + if (p != NULL) + { + ulist = ((struct master_lists *) p->data)->ulist; + cilist = ((struct master_lists *) p->data)->cilist; + } + else + return (err); + + got_message = 0; + + /* see if we need to specify a per-directory or -r option tag */ + if (tag == NULL) + ParseTag (&xtag, (char **) NULL); + + Update_Logfile (repository, message, tag ? tag : xtag, (FILE *) 0, ulist); + dellist (&ulist); + dellist (&cilist); + if (xtag) + free (xtag); + + if (err == 0 && run_module_prog) + { + char *cp; + FILE *fp; + char line[MAXLINELEN]; + char *repository; + + /* It is not an error if Checkin.prog does not exist. */ + if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL) + { + if (fgets (line, sizeof (line), fp) != NULL) + { + if ((cp = rindex (line, '\n')) != NULL) + *cp = '\0'; + repository = Name_Repository ((char *) NULL, update_dir); + run_setup ("%s %s", line, repository); + (void) printf ("%s %s: Executing '", program_name, + command_name); + run_print (stdout); + (void) printf ("'\n"); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + free (repository); + } + (void) fclose (fp); + } + } + + return (err); +} + +/* + * Get the log message for a dir and print a warm fuzzy + */ +/* ARGSUSED */ +static Dtype +commit_direntproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + Node *p; + List *ulist; + char *real_repos; + + /* find the update list for this dir */ + p = findnode (mulist, update_dir); + if (p != NULL) + ulist = ((struct master_lists *) p->data)->ulist; + else + ulist = (List *) NULL; + + /* skip the files as an optimization */ + if (ulist == NULL || ulist->list->next == ulist->list) + return (R_SKIP_FILES); + + /* print the warm fuzzy */ + if (!quiet) + error (0, 0, "Committing %s", update_dir); + + /* get commit message */ + if (use_editor) + { + got_message = 1; + real_repos = Name_Repository (dir, update_dir); + do_editor (update_dir, message, real_repos, ulist); + free (real_repos); + } + return (R_PROCESS); +} + +/* + * Process the post-commit proc if necessary + */ +/* ARGSUSED */ +static int +commit_dirleaveproc (dir, err, update_dir) + char *dir; + int err; + char *update_dir; +{ + /* update the per-directory tag info */ + if (err == 0 && write_dirtag != NULL) + WriteTag ((char *) NULL, write_dirtag, (char *) NULL); + + return (err); +} + +/* + * find the maximum major rev number in an entries file + */ +static int +findmaxrev (p) + Node *p; +{ + char *cp; + int thisrev; + Entnode *entdata; + + entdata = (Entnode *) p->data; + cp = index (entdata->version, '.'); + if (cp != NULL) + *cp = '\0'; + thisrev = atoi (entdata->version); + if (cp != NULL) + *cp = '.'; + if (thisrev > maxrev) + maxrev = thisrev; + return (0); +} + +/* + * Actually remove a file by moving it to the attic + * XXX - if removing a ,v file that is a relative symbolic link to + * another ,v file, we probably should add a ".." component to the + * link to keep it relative after we move it into the attic. + */ +static int +remove_file (file, repository, tag, entries) + char *file; + char *repository; + char *tag; + List *entries; +{ + int omask; + int retcode; + char rcs[PATH_MAX]; + char tmp[PATH_MAX]; + + locate_rcs (file, repository, rcs); + if (tag) + { + /* a symbolic tag is specified; just remove the tag from the file */ + run_setup ("%s%s -q -N%s", Rcsbin, RCS, tag); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to remove tag `%s' from `%s'", tag, rcs); + return (1); + } + return (0); + } + else + { + /* no symbolic tag specified; really move it into the Attic */ + (void) sprintf (tmp, "%s/%s", repository, CVSATTIC); + omask = umask (2); + (void) mkdir (tmp, 0777); + (void) umask (omask); + (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); + + if ((strcmp (rcs, tmp) == 0 || rename (rcs, tmp) != -1) || + (!isreadable (rcs) && isreadable (tmp))) + { + Scratch_Entry (entries, file); + return (0); + } + } + return (1); +} + +/* + * Do the actual checkin for added files + */ +static int +finaladd (file, rev, tag, repository, entries) + char *file; + char *rev; + char *tag; + char *repository; + List *entries; +{ + int ret; + char tmp[PATH_MAX]; + char rcs[PATH_MAX]; + + locate_rcs (file, repository, rcs); + ret = Checkin ('A', file, repository, rcs, rev, tag, + message, entries); + if (ret == 0) + { + (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_OPT); + (void) unlink_file (tmp); + (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG); + (void) unlink_file (tmp); + } + else + fixaddfile (file, repository); + return (ret); +} + +/* + * Unlock an rcs file + */ +static void +unlockrcs (file, repository) + char *file; + char *repository; +{ + char rcs[PATH_MAX]; + int retcode = 0; + + locate_rcs (file, repository, rcs); + run_setup ("%s%s -q -u", Rcsbin, RCS); + run_arg (rcs); + + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "could not unlock %s", rcs); +} + +/* + * remove a partially added file. if we can parse it, leave it alone. + */ +static void +fixaddfile (file, repository) + char *file; + char *repository; +{ + RCSNode *rcsfile; + char rcs[PATH_MAX]; + int save_really_quiet; + + locate_rcs (file, repository, rcs); + save_really_quiet = really_quiet; + really_quiet = 1; + if ((rcsfile = RCS_parsercsfile (rcs)) == NULL) + (void) unlink_file (rcs); + else + freercsnode (&rcsfile); + really_quiet = save_really_quiet; +} + +/* + * put the branch back on an rcs file + */ +static void +fixbranch (file, repository, branch) + char *file; + char *repository; + char *branch; +{ + char rcs[PATH_MAX]; + int retcode = 0; + + if (branch != NULL && branch[0] != '\0') + { + locate_rcs (file, repository, rcs); + run_setup ("%s%s -q -b%s", Rcsbin, RCS, branch); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "cannot restore branch to %s for %s", branch, rcs); + } +} + +/* + * do the initial part of a file add for the named file. if adding + * with a tag, put the file in the Attic and point the symbolic tag + * at the committed revision. + */ +static int +checkaddfile (file, repository, tag) + char *file; + char *repository; + char *tag; +{ + FILE *fp; + char *cp; + char rcs[PATH_MAX]; + char fname[PATH_MAX]; + int omask; + int retcode = 0; + + if (tag) + { + (void) sprintf(rcs, "%s/%s", repository, CVSATTIC); + omask = umask (2); + (void) mkdir (rcs, 0777); + (void) umask (omask); + (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); + } + else + locate_rcs (file, repository, rcs); + + run_setup ("%s%s -i", Rcsbin, RCS); + run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG); + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT); + fp = open_file (fname, "r"); + while (fgets (fname, sizeof (fname), fp) != NULL) + { + if ((cp = rindex (fname, '\n')) != NULL) + *cp = '\0'; + if (*fname) + run_arg (fname); + } + (void) fclose (fp); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "could not create %s", rcs); + return (1); + } + fix_rcs_modes (rcs, file); + return (0); +} + +/* + * Lock the rcs file ``file'' + */ +static int +lockrcsfile (file, repository, rev) + char *file; + char *repository; + char *rev; +{ + char rcs[PATH_MAX]; + + locate_rcs (file, repository, rcs); + if (lock_RCS (file, rcs, rev, repository) != 0) + return (1); + else + return (0); +} + +/* + * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it + * couldn't. If the RCS file currently has a branch as the head, we must + * move the head back to the trunk before locking the file, and be sure to + * put the branch back as the head if there are any errors. + */ +static int +lock_RCS (user, rcs, rev, repository) + char *user; + char *rcs; + char *rev; + char *repository; +{ + RCSNode *rcsfile; + char *branch = NULL; + int err = 0; + + /* + * For a specified, numeric revision of the form "1" or "1.1", (or when + * no revision is specified ""), definitely move the branch to the trunk + * before locking the RCS file. + * + * The assumption is that if there is more than one revision on the trunk, + * the head points to the trunk, not a branch... and as such, it's not + * necessary to move the head in this case. + */ + if (rev == NULL || (rev && isdigit (*rev) && numdots (rev) < 2)) + { + if ((rcsfile = RCS_parsercsfile (rcs)) == NULL) + { + /* invalid rcs file? */ + err = 1; + } + else + { + /* rcsfile is valid */ + branch = xstrdup (rcsfile->branch); + freercsnode (&rcsfile); + if (branch != NULL) + { + run_setup ("%s%s -q -b", Rcsbin, RCS); + run_arg (rcs); + if (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0) + { + error (0, 0, "cannot change branch to default for %s", + rcs); + if (branch) + free (branch); + return (1); + } + } + run_setup ("%s%s -q -l", Rcsbin, RCS); + run_arg (rcs); + err = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + } + else + { + run_setup ("%s%s -q -l%s", Rcsbin, RCS, rev ? rev : ""); + run_arg (rcs); + (void) run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL); + } + + if (err == 0) + { + if (branch) + { + (void) strcpy (sbranch, branch); + free (branch); + } + else + sbranch[0] = '\0'; + return (0); + } + + /* try to restore the branch if we can on error */ + if (branch != NULL) + fixbranch (user, repository, branch); + + if (branch) + free (branch); + return (1); +} + +/* + * Called when "add"ing files to the RCS respository, as it is necessary to + * preserve the file modes in the same fashion that RCS does. This would be + * automatic except that we are placing the RCS ,v file very far away from + * the user file, and I can't seem to convince RCS of the location of the + * user file. So we munge it here, after the ,v file has been successfully + * initialized with "rcs -i". + */ +static void +fix_rcs_modes (rcs, user) + char *rcs; + char *user; +{ + struct stat sb; + + if (stat (user, &sb) != -1) + (void) chmod (rcs, (int) sb.st_mode & ~0222); +} + +/* + * free an UPDATE node's data (really nothing to do) + */ +void +update_delproc (p) + Node *p; +{ + p->data = (char *) NULL; +} + +/* + * Free the commit_info structure in p. + */ +static void +ci_delproc (p) + Node *p; +{ + struct commit_info *ci; + + ci = (struct commit_info *) p->data; + if (ci->rev) + free (ci->rev); + if (ci->tag) + free (ci->tag); + free (ci); +} + +/* + * Find an RCS file in the repository. + */ +static void +locate_rcs (file, repository, rcs) + char *file; + char *repository; + char *rcs; +{ + (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT); + if (!isreadable (rcs)) + { + (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); + if (!isreadable (rcs)) + (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT); + } +} diff --git a/gnu/usr.bin/cvs/cvs/config.h b/gnu/usr.bin/cvs/cvs/config.h new file mode 100644 index 0000000..b3bee5f --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/config.h @@ -0,0 +1,217 @@ +/* @(#)config.h 1.19 92/03/31 */ + +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * This file holds (most of) the configuration tweaks that can be made to + * customize CVS for your site. CVS comes configured for a typical SunOS 4.x + * environment. The comments for each configurable item are intended to be + * self-explanatory. All #defines are tested first to see if an over-riding + * option was specified on the "make" command line. + * + * If special libraries are needed, you will have to edit the Makefile.in file + * or the configure script directly. Sorry. + */ + +/* + * CVS provides the most features when used in conjunction with the Version-5 + * release of RCS. Thus, it is the default. This also assumes that GNU diff + * Version-1.15 is being used as well -- you will have to configure your RCS + * V5 release separately to make this the case. If you do not have RCS V5 and + * GNU diff V1.15, comment out this define. You should not try mixing and + * matching other combinations of these tools. + */ +#ifndef HAVE_RCS5 +#define HAVE_RCS5 +#endif + +/* + * If, before installing this version of CVS, you were running RCS V4 AND you + * are installing this CVS and RCS V5 and GNU diff 1.15 all at the same time, + * you should turn on the following define. It only exists to try to do + * reasonable things with your existing checked out files when you upgrade to + * RCS V5, since the keyword expansion formats have changed with RCS V5. + * + * If you already have been running with RCS5, or haven't been running with CVS + * yet at all, or are sticking with RCS V4 for now, leave the commented out. + */ +#ifndef HAD_RCS4 +/* #define HAD_RCS4 */ +#endif + +/* + * For portability and heterogeneity reasons, CVS is shipped by default using + * my own text-file version of the ndbm database library in the src/myndbm.c + * file. If you want better performance and are not concerned about + * heterogeneous hosts accessing your modules file, turn this option off. + */ +#ifndef MY_NDBM +#define MY_NDBM +#endif + +/* + * The "diff" program to execute when creating patch output. This "diff" + * must support the "-c" option for context diffing. Specify a full pathname + * if your site wants to use a particular diff. If you are using the GNU + * version of diff (version 1.15 or later), this should be "diff -a". + * + * NOTE: this program is only used for the ``patch'' sub-command. The other + * commands use rcsdiff which will use whatever version of diff was specified + * when rcsdiff was built on your system. + */ +#ifndef DIFF +#define DIFF "diff" +#endif + +/* + * The "grep" program to execute when checking to see if a merged file had + * any conflicts. This "grep" must support the "-s" option and a standard + * regular expression as an argument. Specify a full pathname if your site + * wants to use a particular grep. + */ +#ifndef GREP +#define GREP "grep" +#endif + +/* + * The "rm" program to execute when pruning directories that are not part of + * a release. This "rm" must support the "-fr" options. Specify a full + * pathname if your site wants to use a particular rm. + */ +#ifndef RM +#define RM "rm" +#endif + +/* + * The "sort" program to execute when displaying the module database. Specify + * a full pathname if your site wants to use a particular sort. + */ +#ifndef SORT +#define SORT "sort" +#endif + +/* + * By default, RCS programs are executed with the shell or through execlp(), + * so the user's PATH environment variable is searched. If you'd like to + * bind all RCS programs to a certain directory (perhaps one not in most + * people's PATH) then set the default in RCSBIN_DFLT. Note that setting + * this here will cause all RCS programs to be executed from this directory, + * unless the user overrides the default with the RCSBIN environment variable + * or the "-b" option to CVS. + * + * This define should be either the empty string ("") or a full pathname to the + * directory containing all the installed programs from the RCS distribution. + */ +#ifndef RCSBIN_DFLT +#define RCSBIN_DFLT "" +#endif + +/* + * The default editor to use, if one does not specify the "-e" option to cvs, + * or does not have an EDITOR environment variable. I set this to just "vi", + * and use the shell to find where "vi" actually is. This allows sites with + * /usr/bin/vi or /usr/ucb/vi to work equally well (assuming that your PATH + * is reasonable). + */ +#ifndef EDITOR_DFLT +#define EDITOR_DFLT "vi" +#endif + +/* + * The Repository file holds the path to the directory within the source + * repository that contains the RCS ,v files for each CVS working directory. + * This path is either a full-path or a path relative to CVSROOT. + * + * The only advantage that I can see to having a relative path is that One can + * change the physical location of the master source repository, change one's + * CVSROOT environment variable, and CVS will work without problems. I + * recommend using full-paths. + */ +#ifndef RELATIVE_REPOS +/* #define RELATIVE_REPOS */ +#endif + +/* + * When committing or importing files, you must enter a log message. + * Normally, you can do this either via the -m flag on the command line or an + * editor will be started for you. If you like to use logging templates (the + * rcsinfo file within the $CVSROOT/CVSROOT directory), you might want to + * force people to use the editor even if they specify a message with -m. + * Enabling FORCE_USE_EDITOR will cause the -m message to be appended to the + * temp file when the editor is started. + */ +#ifndef FORCE_USE_EDITOR +/* #define FORCE_USE_EDITOR */ +#endif + +/* + * When locking the repository, some sites like to remove locks and assume + * the program that created them went away if the lock has existed for a long + * time. This used to be the default for previous versions of CVS. CVS now + * attempts to be much more robust, so lock files should not be left around + * by mistake. The new behaviour will never remove old locks (they must now + * be removed by hand). Enabling CVS_FUDGELOCKS will cause CVS to remove + * locks that are older than CVSLCKAGE seconds. + * Use of this option is NOT recommended. + */ +#ifndef CVS_FUDGELOCKS +/* #define CVS_FUDGELOCKS */ +#endif + +/* + * When committing a permanent change, CVS and RCS make a log entry of + * who committed the change. If you are committing the change logged in + * as "root" (not under "su" or other root-priv giving program), CVS/RCS + * cannot determine who is actually making the change. + * + * As such, by default, CVS disallows changes to be committed by users + * logged in as "root". You can disable this option by commenting + * out the lines below. + */ +#ifndef CVS_BADROOT +#define CVS_BADROOT +#endif + +/* + * The "cvs diff" command accepts all the single-character options that GNU + * diff (1.15) accepts. Except -D. GNU diff uses -D as a way to put + * cpp-style #define's around the output differences. CVS, by default, uses + * -D to specify a free-form date (like "cvs diff -D '1 week ago'"). If + * you would prefer that the -D option of "cvs diff" work like the GNU diff + * option, then comment out this define. + */ +#ifndef CVS_DIFFDATE +#define CVS_DIFFDATE +#endif + +/* End of CVS configuration section */ + +/* + * Externs that are included in libc, but are used frequently enough to + * warrant defining here. + */ +#ifndef STDC_HEADERS +extern void exit (); +#endif + +#ifndef getwd +extern char *getwd (); +#endif + +/* + * Some UNIX distributions don't include these in their stat.h Defined here + * because "config.h" is always included last. + */ +#ifndef S_IWRITE +#define S_IWRITE 0000200 /* write permission, owner */ +#endif +#ifndef S_IWGRP +#define S_IWGRP 0000020 /* write permission, grougroup */ +#endif +#ifndef S_IWOTH +#define S_IWOTH 0000002 /* write permission, other */ +#endif diff --git a/gnu/usr.bin/cvs/cvs/create_adm.c b/gnu/usr.bin/cvs/cvs/create_adm.c new file mode 100644 index 0000000..911258e --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/create_adm.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Create Administration. + * + * Creates a CVS administration directory based on the argument repository; the + * "Entries" file is prefilled from the "initrecord" argument. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)create_adm.c 1.24 92/03/31"; +#endif + +void +Create_Admin (dir, repository, tag, date) + char *dir; + char *repository; + char *tag; + char *date; +{ + FILE *fout; + char *cp; + char tmp[PATH_MAX]; + + if (noexec) + return; + + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM); + else + (void) strcpy (tmp, CVSADM); + + if (isfile (tmp)) + error (1, 0, "there is a version here already"); + else + { + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, OCVSADM); + else + (void) strcpy (tmp, OCVSADM); + + if (isfile (tmp)) + error (1, 0, "there is a version here already"); + } + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM); + else + (void) strcpy (tmp, CVSADM); + make_directory (tmp); + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP); + else + (void) strcpy (tmp, CVSADM_REP); + fout = open_file (tmp, "w+"); + cp = repository; + strip_path (cp); + +#ifdef RELATIVE_REPOS + /* + * If the Repository file is to hold a relative path, try to strip off + * the leading CVSroot argument. + */ + if (CVSroot != NULL) + { + char path[PATH_MAX]; + + (void) sprintf (path, "%s/", CVSroot); + if (strncmp (repository, path, strlen (path)) == 0) + cp = repository + strlen (path); + } +#endif + + if (fprintf (fout, "%s\n", cp) == EOF) + error (1, errno, "write to %s failed", tmp); + if (fclose (fout) == EOF) + error (1, errno, "cannot close %s", tmp); + + /* now, do the Entries file */ + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT); + else + (void) strcpy (tmp, CVSADM_ENT); + fout = open_file (tmp, "w+"); + if (fclose (fout) == EOF) + error (1, errno, "cannot close %s", tmp); + + /* Create a new CVS/Tag file */ + WriteTag (dir, tag, date); +} diff --git a/gnu/usr.bin/cvs/cvs/cvs.1 b/gnu/usr.bin/cvs/cvs/cvs.1 new file mode 100644 index 0000000..f0c648f --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/cvs.1 @@ -0,0 +1,1991 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id cvs.1,v 1.12 1992/04/10 03:05:16 berliner Exp +.TH CVS 1 "\*(Dt" +.\" Full space in nroff; half space in troff +.de SP +.if n .sp +.if t .sp .5 +.. +.\" quoted command +.de ` +.RB ` "\|\\$1\|" '\\$2 +.. +.SH "NAME" +cvs \- Concurrent Versions System +.SH "SYNOPSIS" +.TP +\fBcvs\fP [ \fIcvs_options\fP ] +.I cvs_command +[ +.I command_options +] [ +.I command_args +] +.SH "DESCRIPTION" +.IX "revision control system" "\fLcvs\fR" +.IX cvs "" "\fLcvs\fP \- concurrent versions system" +.IX "concurrent versions system \- \fLcvs\fP" +.IX "release control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system" +.IX "source control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system" +.IX revisions "cvs command" "" "\fLcvs\fP \- source control" +.B cvs +is a front end to the +.BR rcs ( 1 ) +revision control system which extends +the notion of revision control from a collection of files in a single +directory to a hierarchical collection of directories consisting of +revision controlled files. +These directories and files can be combined together to form a software +release. +.B cvs +provides the functions necessary to manage these software releases and to +control the concurrent editing of source files among multiple software +developers. +.SP +.B cvs +keeps a single copy of the master sources. +This copy is called the source ``repository''; it contains all the +information to permit extracting previous software releases at any +time based on either a symbolic revision tag, or a date in the past. +.SH "ESSENTIAL COMMANDS" +.B cvs +provides a rich variety of commands (\fIcvs_command\fP in the +Synopsis), each of which often has a wealth of options, to satisfy the +many needs of source management in distributed environments. However, +you don't have to master every detail to do useful work with +.BR cvs ; +in fact, five commands are sufficient to use (and contribute to) +the source repository. +.TP +\fBcvs checkout\fP \fImodules\fP\|.\|.\|. +A necessary preliminary for most \fBcvs\fP work: creates your private +copy of the source for \fImodules\fP (named collections of source; you +can also use a path relative to the source repository here). You can +work with this copy without interfering with others' work. At least +one subdirectory level is always created. +.TP +.B cvs update +Execute this command from \fIwithin\fP your private source +directory when you wish to update your copies of source files from +changes that other developers have made to the source in the +repository. +.TP +\fBcvs add\fP \fIfile\fP\|.\|.\|. +Use this command to enroll new files in \fBcvs\fP records of your +working directory. The files will be added to the repository the next +time you run +.` "cvs commit". +Note: +You should use the +.` "cvs import" +command to bootstrap new sources into the source repository. +.` "cvs add" +is only used for new files to an already checked-out module. +.TP +\fBcvs remove\fP \fIfile\fP\|.\|.\|. +Use this command (after erasing any files listed) to declare that you +wish to eliminate files from the repository. The removal does not +affect others until you run +.` "cvs commit". +.TP +\fBcvs commit\fP \fIfile\fP\|.\|.\|. +Use this command when you wish to ``publish'' your changes to other +developers, by incorporating them in the source repository. +.SH "OPTIONS" +The +.B cvs +command line can include +.IR cvs_options , +which apply to the overall +.B cvs +program; a +.IR cvs_command , +which specifies a particular action on the source repository; and +.I command_options +and +.I command_arguments +to fully specify what the +.I cvs_command +will do. +.SP +.I Warning: +you must be careful of precisely where you place options relative to the +.IR cvs_command . +The same option can mean different things depending on whether it +is in the +.I cvs_options +position (to the left of a +.B cvs +command) or in the +.I command_options +position (to the right of a +.B cvs +command). +.SP +There are only two situations where you may omit +.IR cvs_command : +.` "cvs \-H" +elicits a list of available commands, and +.` "cvs \-v " +displays version information on \fBcvs\fP itself. +.SP +.SH "CVS OPTIONS" +Use these options to control the overall +.B cvs +program: +.TP +.B \-H +Display usage information about the specified +.I cvs_command +(but do not actually execute the command). If you don't specify a +command name, +.` "cvs \-H" +displays a summary of all the commands available. +.TP +.B \-Q +Causes the command to be +.I really +quiet; the command will generate output only for serious problems. +.TP +.B \-q +Causes the command to be somewhat quiet; informational messages, such +as reports of recursion through subdirectories, are suppressed. +.TP +\fB\-b\fP \fIbindir\fP +Use +.I bindir +as the directory where +.SM RCS +programs are located. +Overrides the setting of the +.SM RCSBIN +environment variable. +This value should be specified as an absolute pathname. +.TP +\fB\-d\fP \fICVS_root_directory\fP +Use +.I CVS_root_directory +as the root directory pathname of the master +.SM RCS +source repository. +Overrides the setting of the +.SM CVSROOT +environment variable. +This value should be specified as an absolute pathname. +.TP +\fB\-e\fP \fIeditor\fP +Use +.I editor +to enter revision log information. +Overrides the setting of the +.SM EDITOR +environment variable. +.TP +.B \-l +Do not log the +.I cvs_command +in the command history (but execute it anyway). See the description +of the +.B history +command for information on command history. +.TP +.B \-n +Do not change any files. Attempt to execute the +.IR cvs_command , +but only to issue reports; do not remove, update, or merge any +existing files, or create any new files. +.TP +.B \-t +Trace program execution; display messages showing the steps of +.B cvs +activity. Particularly useful with +.B \-n +to explore the potential impact of an unfamiliar command. +.TP +.B \-r +Makes new working files files read-only. +Same effect as if the +.SM CVSREAD +environment variable is set. +.TP +.B \-v +Displays version and copyright information for +.BR cvs . +.TP +.B \-w +Makes new working files read-write (default). +Overrides the setting of the +.SM CVSREAD +environment variable. +.SH "USAGE" +Except when requesting general help with +.` "cvs \-H", +you must specify a +.I cvs_command +to +.B cvs +to select a specific release control function to perform. +Each +.B cvs +command accepts its own collection of options and arguments. +However, many options are available across several commands. +You can display a usage summary for each command by specifying the +.B \-H +option with the command. +.SH "CVS COMMAND SUMMARY" +Here are brief descriptions of all the +.B cvs +commands: +.TP +.B add +Add a new file or directory to the repository, pending a +.` "cvs commit" +on the same file. +Can only be done from within sources created by a previous +.` "cvs checkout" +invocation. +Use +.` "cvs import" +to place whole new hierarchies of sources under +.B cvs +control. +(Does not directly affect repository; changes +working directory.) +.TP +.B admin +Execute +.SM RCS +control functions on the source repository. (Changes +repository directly; uses working directory without changing it.) +.TP +.B checkout +Make a working directory of source files for editing. (Creates or changes +working directory.) +.TP +.B commit +Apply to the source repository changes, additions, and deletions from your +working directory. (Changes repository.) +.TP +.B diff +Show differences between files in working directory and source +repository, or between two revisions in source repository. +(Does not change either repository or working directory.) +.TP +.B export +Prepare copies of a set of source files for shipment off site. +Differs from +.` "cvs checkout" +in that no +.B cvs +administrative directories are created (and therefore +.` "cvs commit" +cannot be executed from a directory prepared with +.` "cvs export"), +and a symbolic tag must be specified. +(Does not change repository; creates directory similar to working +directories). +.TP +.B history +Show reports on +.B cvs +commands that you or others have executed on a particular file or +directory in the source repository. (Does not change repository or +working directory.) History logs are kept only if enabled by creation +of the +.` "$CVSROOT/CVSROOT/history" +file; see +.BR cvs ( 5 ). +.TP +.B import +Incorporate a set of updates from off-site into the source repository, +as a ``vendor branch''. (Changes repository.) +.TP +.B log +Display +.SM RCS +log information. +(Does not change repository or working directory.) +.TP +.B rdiff +Prepare a collection of diffs as a patch file between two releases in +the repository. (Does not change repository or working directory.) +.TP +.B release +Cancel a +.` "cvs checkout", +abandoning any changes. +(Can delete working directory; no effect on repository.) +.TP +.B remove +Remove files from the source repository, pending a +.` "cvs commit" +on the same files. (Does not directly affect repository; +changes working directory.) +.TP +.B rtag +Explicitly specify a symbolic tag for particular revisions of files in the +source repository. See also +.` "cvs tag". +(Changes repository directly; does not require or affect +working directory.) +.TP +.B status +Show current status of files: latest version, version in working +directory, whether working version has been edited and, optionally, +symbolic tags in the +.SM RCS +file. (Does not change +repository or working directory.) +.TP +.B tag +Specify a symbolic tag for files in the repository. Tags the revisions +that were last synchronized with your working directory. (Changes +repository directly; uses working directory without changing it.) +.TP +.B update +Bring your working directory up to date with changes from the +repository. Merges are performed automatically when possible; a +warning is issued if manual resolution is required for conflicting +changes. (Changes working directory; does not change repository.) +.SH "COMMON COMMAND OPTIONS" +This section describes the +.I command_options +that are available across several +.B cvs +commands. Not all commands support all of these options; each option +is only supported for commands where it makes sense. However, when +a command has one of these options you can count on the same meaning +for the option as in other commands. (Other command +options, which are listed with the individual commands, may have +different meanings from one +.B cvs +command to another.) +.I "Warning:" +the +.B history +command is an exception; +it supports many options that conflict +even with these standard options. +.TP +\fB\-D\fP \fIdate_spec\fP +Use the most recent revision no later than \fIdate_spec\fP (a single +argument, date description specifying a date in the +past). A wide variety of date formats are supported by the underlying +.SM RCS +facilities, similar to those described in +.BR co ( 1 ), +but not exactly the same. +The \fIdate_spec\fP is interpreted as being in the local timezone, unless a +specific timezone is specified. +The specification is ``sticky'' when you use it to make a +private copy of a source file; that is, when you get a working file +using \fB\-D\fP, \fBcvs\fP records the date you +specified, so that further updates in the same directory will use the +same date (unless you explicitly override it; see the description of +the \fBupdate\fP command). +.B \-D +is available with the +.BR checkout ", " diff, ", " history ", " export ", " +.BR rdiff ", " rtag ", and " +.B update +commands. +Examples of valid date specifications include: +.in +1i +.ft B +.nf +1 month ago +2 hours ago +400000 seconds ago +last year +last Monday +yesterday +a fortnight ago +3/31/92 10:00:07 PST +January 23, 1987 10:05pm +22:00 GMT +.fi +.ft P +.in -1i +.TP +.B \-f +When you specify a particular date or tag to \fBcvs\fP commands, they +normally ignore files that do not contain the tag (or did not exist on +the date) that you specified. Use the \fB\-f\fP option if you want +files retrieved even when there is no match for the tag or date. (The +most recent version is used in this situation.) +.B \-f +is available with these commands: +.BR checkout ", " export ", " +.BR rdiff ", " rtag ", and " update . +.TP +.B \-H +Help; describe the options available for this command. This is the +only option supported for +.I all +.B cvs +commands. +.TP +\fB\-k\fP \fIkflag\fP +Alter the default +.SM RCS +processing of keywords; all the +.B \-k +options described in +.BR rcs ( 1 ) +are available. The \fB\-k\fP option is available with the +.BR add ", " checkout ", " diff ", " +.RB rdiff ", and " update +commands. Your \fIkflag\fP specification is ``sticky'' when you use +it to create a private copy of a source file; that is, when you use +this option with the \fBcheckout\fP or \fBupdate\fP commands, +\fBcvs\fP associates your selected \fIkflag\fP with the file, and +continues to use it with future \fBupdate\fP commands on the same file +until you specify otherwise. +.TP +.B \-l +Local; run only in current working directory, rather than recurring through +subdirectories. Available with the following commands: +.BR checkout ", " commit ", " diff ", " +.BR export ", " remove ", " rdiff ", " rtag ", " +.BR status ", " tag ", and " update . +.I Warning: +this is not the same +as the overall +.` "cvs \-l" +option, which you can specify to the +.I left +of a +.B cvs +command! +.TP +.B \-n +Do +.I not +run any +.BR checkout / commit / tag / update +program. (A program can be specified to run on each of these +activities, in the modules database; this option bypasses it.) +Available with the +.BR checkout ", " commit ", " export ", and " +.B rtag +commands. +.I Warning: +this is not the same +as the overall +.` "cvs \-n" +option, which you can specify to the +.I left +of a +.B cvs +command! +.TP +.B \-P +Prune (remove) directories that are empty after being updated, on +.BR checkout ", or " update . +Normally, an empty directory (one that is void of revision-controlled +files) is left alone. +Specifying +.B \-P +will cause these directories to be silently removed from your checked-out +sources. +This does not remove the directory from the repository, only from your +checked out copy. +Note that this option is implied by the +.B \-r +or +.B \-D +options of +.BR checkout " and " export . +.TP +.B \-p +Pipe the files retrieved from the repository to standard output, +rather than writing them in the current directory. Available with the +.BR checkout " and " update +commands. +.TP +.B \-Q +Causes the command to be +.I really +quiet; the command will generate output only for serious problems. +Available with the following commands: +.BR checkout ", " import ", " export ", " +.BR rdiff ", " rtag ", " +.BR tag ", and " update . +.TP +.B \-q +Causes the command to be somewhat quiet; informational messages, such +as reports of recursion through subdirectories, are suppressed. +Available with the following commands: +.BR checkout ", " import ", " export ", " +.BR rtag ", " +.BR tag ", and " update . +.TP +\fB\-r\fP \fItag\fP +Use the revision specified by the +.I tag +argument instead of the default ``head'' revision. As well as +arbitrary tags defined with the \fBtag\fP or \fBrtag\fP command, two +special tags are always available: +.` "HEAD" +refers to the most +recent version available in the repository, and +.` "BASE" +refers to the revision you last checked out into the current working +directory. +.SP +The \fItag\fP specification is ``sticky'' when you use +this option with +.` "cvs checkout" +or +.` "cvs update" +to +make your own copy of a file: \fBcvs\fP remembers the \fItag\fP and +continues to use it on future \fBupdate\fP commands, until you specify +otherwise. +.I tag +can be either a symbolic or numeric tag, in +.SM RCS +fashion. +Specifying the +.B \-q +option along with the +.B \-r +option is often useful, to suppress the warning messages when the +.SM RCS +file does not contain the specified tag. +.B \-r +is available with the +.BR checkout ", " commit ", " diff ", " +.BR history ", " export ", " +.BR rdiff ", " rtag ", and " update +commands. +.I Warning: +this is not the same +as the overall +.` "cvs \-r" +option, which you can specify to the +.I left +of a +.B cvs +command! +.SH "CVS COMMANDS" +Here (finally) are details on all the +.B cvs +commands and the options each accepts. The summary lines at the top +of each command's description highlight three kinds of things: +.TP 1i +\ \ \ \ Command Options and Arguments +Special options are described in detail below; common command options +may appear only in the summary line. +.TP 1i +\ \ \ \ Working Directory, or Repository? +Some \fBcvs\fP commands require a working directory to operate; some +require a repository. Also, some commands \fIchange\fP the +repository, some change the working directory, and some change +nothing. +.TP 1i +\ \ \ \ Synonyms +Many commands have synonyms, which you may find easier to +remember (or type) than the principal name. +.PP +.TP +\fBadd\fP [\fB\-k\fP \fIkflag\fP] [\fB\-m '\fP\fImessage\fP\fB'\fP] \fIfiles.\|.\|.\fP +.I Requires: +repository, working directory. +.br +.I Changes: +working directory. +.br +.I Synonym: +.B new +.br +Use the +.B add +command to create a new file or directory in the +.SM RCS +source repository. +The files or directories specified with +.B add +must already exist in the current directory (which must have been created +with the +.B checkout +command). +To add a whole new directory hierarchy to the source repository +(for example, files received from a third-party vendor), use the +.` "cvs import" +command instead. +.SP +If the argument to +.` "cvs add" +refers to an immediate sub-directory, the directory is +created at the correct place in the +.SM RCS +source repository, and the necessary +.B cvs +administration files are created in your working directory. +If the directory already exists in the source repository, +.` "cvs add" +still creates the administration files in your version of the directory. +This allows you to use +.` "cvs add" +to add a particular directory to your private sources even if +someone else created that directory after your +.B checkout +of the sources. You can do the following: +.SP +.in +1i +.ft B +.nf +example% mkdir new_directory +example% cvs add new_directory +example% cvs update new_directory +.fi +.ft P +.in -1i +.SP +An alternate approach using +.` "cvs update" +might be: +.SP +.in +1i +.ft B +.nf +example% cvs update -d new_directory +.fi +.ft P +.in -1i +.SP +(To add \fIany available\fP new directories to your working directory, it's +probably simpler to use +.` "cvs checkout" +or +.` "cvs update -d".) +.SP +The added files are not placed in the +.SM RCS +source repository until you use +.` "cvs commit" +to make the change permanent. +Doing a +.` "cvs add" +on a file that was removed with the +.` "cvs remove" +command will resurrect the file, if no +.` "cvs commit" +command intervened. +.SP +You will have the opportunity to specify a logging message, as usual, +when you use +.` "cvs commit" +to make the new file permanent. If you'd like to have another +logging message associated with just +.I creation +of the file (for example, to describe the file's purpose), you can +specify it with the +.` "\-m \fImessage\fP" +option to the +.B add +command. +.SP +The +.` "-k kflag" +option specifies the default way that this +file will be checked out. +The +.` "kflag" +argument is stored in the +.SM RCS +file and can be changed with +.` "cvs admin". +Specifying +.` "-ko" +is useful for checking in binaries that +shouldn't have the +.SM RCS +id strings expanded. +.TP +\fBadmin\fP [\fIrcs-options\fP] \fIfiles.\|.\|.\fP +.I Requires: +repository, working directory. +.br +.I Changes: +repository. +.br +.I Synonym: +.B rcs +.br +This is the +.B cvs +interface to assorted administrative +.SM RCS +facilities, documented in +.BR rcs ( 1 ). +.` "cvs admin" +simply passes all its options and arguments to the +.B rcs +command; it does no filtering or other processing. +This command does work recursively, however, so extreme care should be +used. +.TP +\fBcheckout\fP [\fBoptions\fP] \fImodules\fP.\|.\|. +.I Requires: +repository. +.br +.I Changes: +working directory. +.br +.I Synonyms: +.BR co ", " get +.br +Make a working directory containing copies of the source files specified by +.IR modules . +You must execute +.` "cvs checkout" +before using most of the other +.B cvs +commands, since most of them operate on your working directory. +.SP +\fImodules\fP are either symbolic names (themselves defined as the +module +.` "modules" +in the source repository; see +.BR cvs ( 5 )) +for some collection of source directories and files, or paths to +directories or files in the repository. +.SP +Depending on the +.I modules +you specify, +.B checkout +may recursively create directories and populate them with the appropriate +source files. +You can then edit these source files at any time (regardless of whether +other software developers are editing their own copies of the sources); +update them to include new changes applied by others to the source +repository; or commit your work as a permanent change to the +.SM RCS +repository. +.SP +Note that +.B checkout +is used to create directories. +The top-level directory created is always added to the directory +where +.B checkout +is invoked, and usually has the same name as the specified +.IR module . +In the case of a +.I module +alias, the created sub-directory may have a different name, but you can be +sure that it will be a sub-directory, and that +.B checkout +will show the relative path leading to each file as it is extracted into +your private work area (unless you specify the +.B \-Q +option). +.SP +Running +.` "cvs checkout" +on a directory that was already built by a prior +.B checkout +is also permitted, and +has the same effect as specifying the +.B \-d +option to the +.B update +command described below. +.SP +The +.I options +permitted with +.` "cvs checkout" +include the standard command options +.BR \-P ", " \-Q ", " \-f ", " +.BI \-k " kflag" +\&, +.BR \-l ", " \-n ", " \-p ", " +.BR \-q ", " \-r +.IR tag ", and" +.BI \-D " date"\c +\&. +.SP +In addition to those, you can use these special command options +with +.BR checkout : +.SP +Use the +.B \-A +option to reset any sticky tags, dates, or +.B \-k +options. (If you get a working file using one of the +\fB\-r\fP, \fB\-D\fP, or \fB\-k\fP options, \fBcvs\fP remembers the +corresponding tag, date, or \fIkflag\fP and continues using it on +future updates; use the \fB\-A\fP option to make \fBcvs\fP forget these +specifications, and retrieve the ``head'' version of the file). +.SP +The +.BI \-j " branch" +option merges the changes made between the +resulting revision and the revision that it is based on (e.g., if +the tag refers to a branch, +.B cvs +will merge all changes made in that branch into your working file). +.SP +With two \fB-j\fP options, +.B cvs +will merge in the changes between the two respective revisions. +This can be used to ``remove'' a certain delta from your working file. +.SP +In addition, each \fB-j\fP option can contain on optional date +specification which, when used with branches, can limit the chosen +revision to one within a specific date. +An optional date is specified by adding a colon (:) to the tag. +An example might be what +.` "cvs import" +tells you to do when you have +just imported sources that have conflicts with local changes: +.SP +.in +1i +.ft B +.nf +example% cvs checkout -jTAG:yesterday -jTAG module +.fi +.ft P +.in -1i +.SP +Use the +.B \-N +option with +.` "\-d \fIdir\fP" +to avoid shortening module paths in your working directory. (Normally, \fBcvs\fP shortens paths as much as possible when you specify an explicit target directory.) +.SP +Use the +.B \-c +option to copy the module file, sorted, to the standard output, +instead of creating or modifying any files or directories in your +working directory. +.SP +Use the +.BI \-d " dir" +option to create a directory called +.I dir +for the working files, instead of using the module name. Unless you +also use \fB\-N\fP, the paths created under \fIdir\fP will be as short +as possible. +.SP +Use the +.B \-s +option to display per-module status information stored with +the +.B \-s +option within the modules file. +.TP +\fBcommit\fP [\fB\-lnR\fP] [\fB\-m\fP '\fIlog_message\fP' | \fB\-f\fP \fIfile\fP] [\fB\-r\fP \fIrevision\fP] [\fIfiles.\|.\|.\fP] +.I Requires: +working directory, repository. +.br +.I Changes: +repository. +.br +.I Synonym: +.B ci +.br +Use +.` "cvs commit" +when you want to incorporate changes from your working source +files into the general source repository. +.SP +If you don't specify particular \fIfiles\fP to commit, all +of the files in your working current directory are examined. +.B commit +is careful to change in the repository only those files that you have +really changed. By default (or if you explicitly specify the +.B \-R +option), files +in subdirectories are also examined and committed if they have +changed; you can use the +.B \-l +option to limit +.B commit +to the current directory only. +.SP +.B commit +verifies that the selected files are up to date with the current revisions +in the source repository; it will notify you, and exit without +committing, if any of the specified files must be made current first +with +.` "cvs update". +.B commit +does not call the +.B update +command for you, but rather leaves that for you to do when +the time is right. +.SP +When all is well, an editor is invoked to allow you to enter a log +message that will be written to one or more logging programs and placed in the +.SM RCS +source repository file. +You can instead specify the log message on the command line with the +.B \-m +option, thus suppressing the editor invocation, or use the +.B \-f +option to specify that the argument \fIfile\fP contains the log message. +.SP +The +.B \-r +option can be used to commit to a particular symbolic or numeric revision +within the +.SM RCS +file. +For example, to bring all your files up to the +.SM RCS +revision ``3.0'' (including those that haven't changed), you might do: +.SP +.in +1i +.ft B +.nf +example% cvs commit -r3.0 +.fi +.ft P +.in -1i +.SP +.B cvs +will only allow you to commit to a revision that is on the main trunk (a +revision with a single dot). +However, you can also commit to a branch revision (one that has an even +number of dots) with the +.B \-r +option. +To create a branch revision, one typically use the +.B \-b option of the +.BR rtag " or " tag +commands. +Then, either +.BR checkout " or " update +can be used to base your sources on the newly created branch. +From that point on, all +.B commit +changes made within these working sources will be automatically added +to a branch revision, thereby not perturbing main-line development in any +way. +For example, if you had to create a patch to the 1.2 version of the +product, even though the 2.0 version is already under development, you +might do: +.SP +.in +1i +.ft B +.nf +example% cvs rtag -b -rFCS1_2 FCS1_2_Patch product_module +example% cvs checkout -rFCS1_2_Patch product_module +example% cd product_module +[[ hack away ]] +example% cvs commit +.fi +.ft P +.in -1i +.SP +Say you have been working on some extremely experimental software, based on +whatever revision you happened to checkout last week. +If others in your group would like to work on this software with you, but +without disturbing main-line development, you could commit your change to a +new branch. +Others can then checkout your experimental stuff and utilize the full +benefit of +.B cvs +conflict resolution. +The scenario might look like: +.SP +.in +1i +.ft B +.nf +example% cvs tag -b EXPR1 +example% cvs update -rEXPR1 +[[ hack away ]] +example% cvs commit +.fi +.ft P +.in -1i +.SP +Others would simply do +.` "cvs checkout -rEXPR1 whatever_module" +to work with you on the experimental change. +.TP +\fBdiff\fP [\fB\-kl\fP] [\fIrcsdiff_options\fP] [[\fB\-r\fP \fIrev1\fP | \fB\-D\fP \fIdate1\fP] [\fB\-r\fP \fIrev2\fP | \fB\-D\fP \fIdate2\fP]] [\fIfiles.\|.\|.\fP] +.I Requires: +working directory, repository. +.br +.I Changes: +nothing. +.br +You can compare your working files with revisions in the source +repository, with the +.` "cvs diff" +command. If you don't specify a particular revision, your files +are compared with the revisions they were based on. You can also use +the standard +.B cvs +command option +.B \-r +to specify a particular revision to compare your files with. Finally, +if you use +.B \-r +twice, you can see differences between two revisions in the +repository. +You can also specify +.B \-D +options to diff against a revision in the past. +The +.B \-r +and +.B \-D +options can be mixed together with at most two options ever specified. +.SP +See +.BR rcsdiff ( 1 ) +for a list of other accepted options. +.SP +If you don't specify any files, +.B diff +will display differences for all those files in the current directory +(and its subdirectories, unless you use the standard option +.BR \-l ) +that +differ from the corresponding revision in the source repository +(i.e. files that +.I you +have changed), or that differ from the revision specified. +.TP +\fBexport\fP [\-\fBf\|lNnQq\fP] \fB\-r\fP \fIrev\fP\||\|\fB\-D\fP \fIdate\fP [\fB\-d\fP \fIdir\fP] \fImodule\fP.\|.\|. +.I Requires: +repository. +.br +.I Changes: +current directory. +.br +This command is a variant of +.` "cvs checkout"; +use it when you want a copy of the source for \fImodule\fP +without the \fBcvs\fP administrative directories. For example, you +might use +.` "cvs export" +to prepare source for shipment +off-site. This command \fIrequires\fP that you specify a date or tag +(with \fB\-D\fP or \fB\-r\fP), so that you can count on reproducing +the source you ship to others. +.SP +The only non-standard options are +.` "\-d \fIdir\fP" +(write the +source into directory \fIdir\fP) and +.` "\-N" +(don't shorten +module paths). +These have the same meanings as the same options in +.` "cvs checkout". +.SP +The +.B \-kv +option is always set when +.B export +is used. +This causes any +.SM RCS +keywords to be expanded such that an +.B import +done at some other site will not lose the keyword revision information. +There is no way to override this. +.TP +\fBhistory\fP [\fB\-\fP\fIreport\fP] [\fB\-\fP\fIflags\fP] [\fB\-\fP\fIoptions args\fP] [\fIfiles\fP.\|.\|.] +.I Requires: +the file +.` "$CVSROOT/CVSROOT/history" +.br +.I Changes: +nothing. +.br +\fBcvs\fP keeps a history file that tracks each use of the +\fBcheckout\fP, \fBcommit\fP, \fBrtag\fP, \fBupdate\fP, and \fBrelease\fP +commands. You can use +.` "cvs history" +to display this +information in various formats. +.SP +.I Warning: +.` "cvs history" +uses +.` "\-f", +.` "\-l", +.` "\-n", +and +.` "\-p" +in ways that conflict with the +descriptions in +.SM +COMMON COMMAND OPTIONS\c +\&. +.SP +Several options (shown above as \fB\-\fP\fIreport\fP) control what +kind of report is generated: +.TP 1i +.B \ \ \ \ \ \ \-c +Report on each time \fBcommit\fP was used (i.e., each time the +repository was modified). +.TP 1i +\fB\ \ \ \ \ \ \-m\fP \fImodule\fP +Report on a particular \fImodule\fP. (You can meaningfully use +\fB\-m\fP more than once on the command line.) +.TP 1i +.B \ \ \ \ \ \ \-o +Report on checked-out modules. +.TP 1i +.B \ \ \ \ \ \ \-T +Report on all tags. +.TP 1i +\fB\ \ \ \ \ \ \-x\fP \fItyp\fP +Extract a particular set of record types \fIX\fP from the \fBcvs\fP +history. The types are indicated by single letters, which you may +specify in combination. +Certain commands have a single record type: \fBcheckout\fP (type `O'), +\fBrelease\fP (type `F'), and \fBrtag\fP (type `T'). One of four +record types may result from an \fBupdate\fP: `W', when the working copy +of a file is deleted during update (because it was gone from the +repository); `U', when a working file was copied from the +repository; `G', when a merge was necessary and it succeeded; and 'C', +when a merge was necessary but collisions were detected (requiring +manual merging). Finally, one of three record types results from +\fBcommit\fP: `M', when a file was modified; `A', when a file is first +added; and `R', when a file is removed. +.TP 1i +.B \ \ \ \ \ \ \-e +Everything (all record types); equivalent to specifying +.` "\-xMACFROGWUT". +.PP +.RS .5i +The options shown as \fB\-\fP\fIflags\fP constrain the report without +requiring option arguments: +.RE +.TP 1i +.B \ \ \ \ \ \ \-a +Show data for all users (the default is to show data only for the user +executing +.` "cvs history"). +.TP 1i +.B \ \ \ \ \ \ \-l +Show last modification only. +.TP 1i +.B \ \ \ \ \ \ \-w +Show only the records for modifications done from the same working +directory where +.` "cvs history" +is executing. +.PP +.RS .5i +The options shown as \fB\-\fP\fIoptions args\fP constrain the report +based on an argument: +.RE +.TP 1i +\fB\ \ \ \ \ \ \-b\fP \fIstr\fP +Show data back to a record containing the string \fIstr\fP in either +the module name, the file name, or the repository path. +.TP 1i +\fB\ \ \ \ \ \ \-D\fP \fIdate\fP +Show data since \fIdate\fP. +.TP 1i +\fB\ \ \ \ \ \ \-p\fP \fIrepository\fP +Show data for a particular source repository (you can specify several +\fB\-p\fP options on the same command line). +.TP 1i +\fB\ \ \ \ \ \ \-r\fP \fIrev\fP +Show records referring to revisions since the revision or tag +named \fIrev\fP appears in individual RCS files. +Each +.SM RCS +file is searched for the revision or tag. +.TP 1i +\fB\ \ \ \ \ \ \-t\fP \fItag\fP +Show records since tag \fItag\fP was last added to the the history file. +This differs from the \fB-r\fP flag above in that it reads +only the history file, not the +.SM RCS +files, and is much faster. +.TP 1i +\fB\ \ \ \ \ \ \-u\fP \fIname\fP +Show records for user \fIname\fP. +.PP +.TP +\fBimport\fP [\fB\-\fP\fIoptions\fP] \fIrepository vendortag releasetag\fP.\|.\|. +.I Requires: +Repository, source distribution directory. +.br +.I Changes: +repository. +.br +Use +.` "cvs import" +to incorporate an entire source +distribution from an outside source (e.g., a source vendor) into your +source repository directory. You can use this command both for +initial creation of a repository, and for wholesale updates to the +module form the outside source. +.SP +The \fIrepository\fP argument gives a directory name (or a path to a +directory) under the CVS root directory for repositories; if the +directory did not exist, \fBimport\fP creates it. +.SP +When you use \fBimport\fP for updates to source that has been modified in your +source repository (since a prior \fBimport\fP), it +will notify you of any files that conflict in the two branches of +development; use +.` "cvs checkout -j" +to reconcile the differences, as \fBimport\fP instructs you to do. +.SP +By default, certain file names are ignored during +.` "cvs import": +names associated with +.SM CVS +administration, or with other common source control systems; common +names for patch files, object files, archive files, and editor backup +files; and other names that are usually artifacts of assorted utilities. +Currently, the default list of ignored files includes files matching +these names: +.SP +.in +1i +.ft B +.nf +RCSLOG RCS SCCS +CVS* cvslog.* +tags TAGS +\&.make.state .nse_depinfo +*~ #* .#* ,* +*.old *.bak *.orig *.rej .del\-* +*.a *.o *.Z *.elc *.ln core +.fi +.ft P +.in -1i +.SP +The outside source is saved in a first-level +.SM RCS +branch, by default +.` "1.1.1". +Updates are leaves of this +branch; for example, files from the first imported collection of +source will be revision +.` "1.1.1.1", +then files from the first +imported update will be revision +.` "1.1.1.2", +and so on. +.SP +At least three arguments are required. \fIrepository\fP is needed to +identify the collection of source. \fIvendortag\fP is a tag for the +entire branch (e.g., for +.` "1.1.1"). +You must also specify at +least one \fIreleasetag\fP to identify the files at the leaves created +each time you execute +.` "cvs import". +.SP +Three of the standard +.B cvs +command options are available: \fB\-Q\fP, \fB\-q\fP, and \fB\-m\fP +\fImessage\fP. If you do not specify a logging message with +\fB\-m\fP, your editor is invoked (as with \fBcommit\fP) to allow you +to enter one. +.SP +There are two additional special options. +.SP +Use +.` "\-b \fIbranch\fP" +to specify a first-level branch other +than +.` "1.1.1". +.SP +Use +.` "\-I \fIname\fP" +to specify file names that should be +ignored during \fBimport\fP. You can use this option repeatedly. +To avoid ignoring any files at all (even those ignored by default), +specify +.` "\-I !". +.TP +\fBlog\fP [\fB\-l\fP] \fIrlog-options [files\fP\|.\|.\|.] +.I Requires: +repository, working directory. +.br +.I Changes: +nothing. +.br +.I Synonym: +.B rlog +.br +Display log information for \fIfiles\fP. +.` "cvs log" +calls +the +.SM RCS +utility \fBrlog\fP; all the options described in +.BR rlog ( 1 ) +are available. Among the more useful \fBrlog\fP options are \fB\-h\fP +to display only the header (including tag definitions, but omitting +most of the full log); \fB\-r\fP to select logs on particular +revisions or ranges of revisions; and \fB\-d\fP to select particular +dates or date ranges. See +.BR rlog ( 1 ) +for full explanations. +This command is recursive by default, unless the +.B \-l +option is specified. +.TP +\fBrdiff\fP [\fB\-\fP\fIflags\fP] [\fB\-V\fP \fIvn\fP] [\fB\-r\fP \fIt\fP|\fB\-D\fP \fId\fP [\fB\-r\fP \fIt2\fP|\fB\-D\fP \fId2\fP]] \fImodules\|.\|.\|.\fP +.I Requires: +repository. +.br +.I Changes: +nothing. +.br +.I Synonym: +.B patch +.br +Builds a Larry Wall format +.BR patch ( 1 ) +file between two releases, that can be fed directly into the +.B patch +program to bring an old release up-to-date with the new release. +(This is one of the few \fBcvs\fP commands that operates directly from +the repository, and doesn't require a prior +.BR checkout .) +The diff output is sent to the standard output device. +You can specify (using the standard \fB\-r\fP and \fB\-D\fP options) +any combination of one or two revisions or dates. +If only one revision or date is specified, the +patch file reflects differences between that revision or date and the +current ``head'' revisions in the +.SM RCS +file. +.SP +Note that if the software release affected +is contained in more than one directory, then it may be necessary to +specify the +.B \-p +option to the +.B patch +command when patching the old sources, so that +.B patch +is able to find the files that are located in other directories. +.SP +If you use the option \fB\-V\fP \fIvn\fP, +.SM RCS +keywords are expanded according to the rules current in +.SM RCS +version \fIvn\fP (the expansion format changed with +.SM RCS +version 5). +.SP +The standard option \fIflags\fP \fB\-f\fP, \fB\-l\fP, \fB\-Q\fP, and +\fB\-q\fP are available with this command. There are also several +special options flags: +.SP +If you use the +.B \-s +option, no patch output is produced. +Instead, a summary of the changed or added files between the two +releases is sent to the standard output device. +This is useful for finding out, for example, which files have changed +between two dates or revisions. +.SP +If you use the +.B \-t +option, a diff of the top two revisions is sent to the standard output device. +This is most useful for seeing what the last change to a file was. +.SP +If you use the +.B \-u +option, the patch output uses the newer ``unidiff'' format for context +diffs. +.SP +You can use +.B \-c +to explicitly specify the +.` "diff \-c" +form of context diffs +(which is the default), if you like. +.TP +\fBrelease\fP [\fB\-dQq\fP] \fImodules\fP\|.\|.\|. +.I Requires: +Working directory. +.br +.I Changes: +Working directory, history log. +.br +This command is meant to safely cancel the effect of +.` "cvs checkout'. +Since +.B cvs +doesn't lock files, it isn't strictly necessary to use this command. +You can always simply delete your working directory, if you +like; but you risk losing changes you may have forgotten, and you +leave no trace in the +.B cvs +history file that you've abandoned your checkout. +.SP +Use +.` "cvs release" +to avoid these problems. This command +checks that no un-committed changes are present; that you are +executing it from immediately above, or inside, a \fBcvs\fP working +directory; and that the repository recorded for your files is the same +as the repository defined in the module database. +.SP +If all these conditions are true, +.` "cvs release" +leaves a +record of its execution (attesting to your intentionally abandoning +your checkout) in the +.B cvs +history log. +.SP +You can use the \fB\-d\fP flag to request that your working copies of +the source files be deleted if the \fBrelease\fP succeeds. +.TP +\fBremove\fP [\fB\-lR\fP] [\fIfiles\|.\|.\|.\fP] +.I Requires: +Working directory. +.br +.I Changes: +Working directory. +.br +.I Synonyms: +.BR rm ", " delete +.br +Use this command to declare that you wish to remove \fIfiles\fP from +the source repository. Like most +.B cvs +commands, +.` "cvs remove" +works on files in your working +directory, not directly on the repository. As a safeguard, it also +requires that you first erase the specified files from your working +directory. +.SP +The files are not actually removed until you apply your changes to the +repository with +.BR commit ; +at that point, the corresponding +.SM RCS +files in the source repository are +.I moved +into the +.` "Attic" +directory (also within the source repository). +.SP +This command is recursive by default, scheduing all physically removed +files that it finds for removal by the next +.BR commit . +Use the +.B \-l +option to avoid this recursion, or just specify that actual files that you +wish remove to consider. +.TP +\fBrtag\fP [\fB\-f\|alnRQq\fP] [\fB\-b\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP | \fB\-D\fP \fIdate\fP] \fIsymbolic_tag\fP \fImodules\|.\|.\|.\fP +.I Requires: +repository. +.br +.I Changes: +repository. +.br +.I Synonym: +.B rfreeze +.br +You can use this command to assign symbolic tags to particular, +explicitly specified source versions in the repository. +.` "cvs rtag" +works directly on the repository contents (and requires no +prior +.BR checkout ). +Use +.` "cvs tag" +instead, to base the selection of +versions to tag on the contents of your working directory. +.SP +In general, tags (often the symbolic names of software distributions) +should not be removed, but the +.B \-d +option is available as a means to remove completely obsolete symbolic names +if necessary (as might be the case for an Alpha release, say). +.SP +The \fB-b\fP option makes the tag a ``branch'' tag, allowing +concurrent, isolated development. +This is most useful for creating a patch to a previously released software +distribution. +.SP +You can use the standard \fB\-r\fP and \fB\-D\fP options to tag only those +files that already contain a certain tag. This method would be used +to rename a tag: tag only the files identified by the old tag, then delete the +old tag, leaving the new tag on exactly the same files as the old tag. +.SP +.B rtag +executes recursively by default, tagging all subdirectories of +\fImodules\fP you specify in the argument. You can restrict its +operation to top-level directories with the standard \fB\-l\fP option; +or you can explicitly request recursion with \fB\-R\fP. +.SP +The modules database can specify a program to execute whenever a tag +is specified; a typical use is to send electronic mail to a group of +interested parties. If you want to bypass that program, use the +standard \fB\-n\fP option. +.SP +The standard options \fB\-Q\fP and \fB\-q\fP are also available with +this command. +.SP +Use the +.B \-a +option to have +.B rtag +look in the +.` "Attic" +for removed files that contin the specified tag. +The tag is removed from these files, which makes it convenient to re-use a +symbolic tag as development continues (and files get removed from the +up-coming distribution). +.TP +\fBstatus\fP [\fB\-lR\fP] [\fB\-v\fP] [\fIfiles\fP\|.\|.\|.] +.I Requires: +working directory, repository. +.br +.I Changes: +nothing. +.br +Display a brief report on the current status of \fIfiles\fP with +respect to the source repository, including any ``sticky'' tags, +dates, or \fB\-k\fP options. (``Sticky'' options will restrict how +.` "cvs update" +operates until you reset them; see the +description of +.` "cvs update \-A\|.\|.\|.".) +.SP +You can also use this command to anticipate the potential impact of a +.` "cvs update" +on your working source directory. If you do +not specify any \fIfiles\fP explicitly, reports are shown for all +files that \fBcvs\fP has placed in your working directory. You can +limit the scope of this search to the current directory itself (not +its subdirectories) with the standard \fB\-l\fP option flag; or you +can explicitly request recursive status reports with the \fB\-R\fP +option. +.SP +The +.B \-v +option causes the symbolic tags for the +.SM RCS +file to be displayed as well. +.TP +\fBtag\fP [\fB\-lQqR\fP] [\fB\-b\fP] [\fB\-d\fP] \fIsymbolic_tag\fP [\fIfiles\fP\|.\|.\|.\|] +.I Requires: +working directory, repository. +.br +.I Changes: +repository. +.br +.I Synonym: +.B freeze +.br +Use this command to assign symbolic tags to the nearest repository +versions to your working sources. The tags are applied immediately to +the repository, as with \fBrtag\fP, but the versions are supplied +implicitly by the \fBcvs\fP records of your working files' history +rather than applied explicitly. +.SP +One use for tags is to record a ``snapshot'' of the current sources +when the software freeze date of a project arrives. As bugs are fixed +after the freeze date, only those changed sources that are to be part +of the release need be re-tagged. +.SP +The symbolic tags are meant to permanently record which revisions of which +files were used in creating a software distribution. +The +.B checkout +and +.B update +commands allow you to extract an exact copy of a tagged release at any time in +the future, regardless of whether files have been changed, added, or removed +since the release was tagged. +.SP +If you use +.` "cvs tag \-d \fIsymbolic_tag\fP\|.\|.\|.", +the +symbolic tag you specify is +.I deleted +instead of being added. \fIWarning\fP: Be very certain of your ground +before you delete a tag; doing this effectively discards some +historical information, which may later turn out to have been valuable. +.SP +The \fB-b\fP option makes the tag a ``branch'' tag, allowing +concurrent, isolated development. +This is most useful for creating a patch to a previously released software +distribution. +.SP +Normally, +.B tag +executes recursively through subdirectories; you can prevent this by +using the standard \fB\-l\fP option, or specify the recursion +explicitly by using \fB\-R\fP. +.TP +\fBupdate\fP [\fB\-Adf\|lPpQqR\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP|\fB\-D\fP \fIdate\fP] \fIfiles\|.\|.\|.\fP +.I Requires: +repository, working directory. +.br +.I Changes: +working directory. +.br +After you've run +.B checkout +to create your private copy of source from the common repository, +other developers will continue changing the central source. From time +to time, when it is convenient in your development process, you can +use the +.B update +command +from within your working directory to reconcile your work with any +revisions applied to the source repository since your last +.B checkout +or +.BR update . +.SP +.B update +keeps you informed of its progress by printing a line for each file, +prefaced with one of the characters +.` "U A R M C ?" +to indicate the status of the file: +.TP 1i +\fBU\fP \fIfile\fP +The file was brought \fIup to date\fP with respect to the repository. +This is done for any file that exists in the repository but not in +your source, and for files that you haven't changed but are not the most +recent versions available in the repository. +.TP 1i +\fBA\fP \fIfile\fP +The file has been \fIadded\fP to your private copy of the sources, and +will be added to the +.SM RCS +source repository when you run +.` "cvs commit" +on the file. +This is a reminder to you that the file needs to be committed. +.TP 1i +\fBR\fP \fIfile\fP +The file has been \fIremoved\fP from your private copy of the sources, and +will be removed from the +.SM RCS +source repository when you run +.` "cvs commit" +on the file. +This is a reminder to you that the file needs to be committed. +.TP 1i +\fBM\fP \fIfile\fP +The file is \fImodified\fP in your working directory. +.` "M" +can indicate one of two states for a file you're working on: either +there were no modifications to the same file in the repository, so +that your file remains as you last saw it; or there were modifications +in the repository as well as in your copy, but they were +\fImerged\fP successfully, without conflict, in your working +directory. +.TP 1i +\fBC\fP \fIfile\fP +A \fIconflict\fP was detected while trying to merge your changes to +\fIfile\fP with changes from the source repository. \fIfile\fP (the +copy in your working directory) is now the output of the +.BR rcsmerge ( 1 ) +command on the two versions; an unmodified copy of your file is also +in your working directory, with the name `\fB.#\fP\fIfile\fP\fB.\fP\fIversion\fP', +where +.I version +is the +.SM RCS +revision that your modified file started from. +(Note that some systems automatically purge files that begin with +\& +.` ".#" +if they have not been accessed for a few days. +If you intend to keep a copy of your original file, it is a very good +idea to rename it.) +.TP 1i +\fB?\fP \fIfile\fP +\fIfile\fP is in your working directory, but does not correspond to +anything in the source repository, and is not in the list of files +for \fBcvs\fP to ignore (see the description of the \fB\-I\fP option). +.PP +.RS .5i +.SP +Use the +.B \-A +option to reset any sticky tags, dates, or +.B \-k +options. (If you get a working copy of a file by using one of the +\fB\-r\fP, \fB\-D\fP, or \fB\-k\fP options, \fBcvs\fP remembers the +corresponding tag, date, or \fIkflag\fP and continues using it on +future updates; use the \fB\-A\fP option to make \fBcvs\fP forget these +specifications, and retrieve the ``head'' version of the file). +.SP +The \fB\-j\fP\fIbranch\fP option +merges the changes made between the +resulting revision and the revision that it is based on (e.g., if +the tag refers to a branch, +.B cvs +will merge all changes made in +that branch into your working file). +.SP +With two \fB-j\fP options, +.B cvs +will merge in the changes between the two respective revisions. +This can be used to ``remove'' a certain delta from your working file. +E.g., If the file foo.c is based on +revision 1.6 and I want to remove the changes made between 1.3 and +1.5, I might do: +.SP +.in +1i +.ft B +.nf +example% cvs update -j1.5 -j1.3 foo.c # note the order... +.fi +.ft P +.in -1i +.SP +In addition, each \fB-j\fP option can contain on optional date +specification which, when used with branches, can limit the chosen +revision to one within a specific date. +An optional date is specified by adding a colon (:) to the tag. +.SP +.in +1i +.ft B +.nf +-jSymbolic_Tag:Date_Specifier +.fi +.ft P +.in -1i +.SP +Use the +.B \-d +option to create any directories that exist in the repository if they're +missing from the working directory. (Normally, update acts only on +directories and files that were already enrolled in your +working directory.) This is useful for updating directories +that were created in the repository since the initial +\fBcheckout\fP; but it has an unfortunate side effect. If you +deliberately avoided certain directories in the repository when you +created your working directory (either through use of a module name or by +listing explicitly the files and directories you wanted on the +command line), then updating with +.B \-d +will create those directories, which may not be what you want. +.SP +Use \fB\-I\fP \fIname\fP to ignore files whose names match \fIname\fP +(in your working directory) during the update. You can specify +\fB\-I\fP more than once on the command line to specify several files +to ignore. By default, +\fBupdate\fP ignores files whose names match any of the following: +.SP +.in +1i +.ft B +.nf +RCSLOG RCS SCCS +CVS* cvslog.* +tags TAGS +\&.make.state .nse_depinfo +*~ #* .#* ,* +*.old *.bak *.orig *.rej .del\-* +*.a *.o *.Z *.elc *.ln core +.fi +.ft P +.in -1i +.SP +Use +.` "\-I !" +to avoid ignoring any files at all. +.SP +The standard \fBcvs\fP command options \fB\-f\fP, \fB\-k\fP, +\fB\-l\fP, \fB\-P\fP, \fB\-p\fP, \fB\-Q\fP, \fB\-q\fP, and \fB\-r\fP +are also available with \fBupdate\fP. +.RE +.SH "FILES" +For more detailed information on +.B cvs +supporting files, see +.BR cvs ( 5 ). +.LP +.I +Files in working directories: +.TP +CVS +A directory of \fBcvs\fP administrative files. +.I +Do not delete. +.TP +CVS/Entries +List and status of files in your working directory. +.TP +CVS/Entries.Backup +A backup of +.` "CVS/Entries". +.TP +CVS/Entries.Static +Flag: do not add more entries on +.` "cvs update". +.TP +CVS/Repository +Pathname to the corresponding directory in the source repository. +.TP +CVS/Tag +Contains the per-directory ``sticky'' tag or date information. +This file is created/updated when you specify +.B \-r +or +.B \-D +to the +.B checkout +or +.B update +commands, and no files are specified. +.TP +CVS/Checkin.prog +Name of program to run on +.` "cvs commit". +.TP +CVS/Update.prog +Name of program to run on +.` "cvs update". +.LP +.I +Files in source repositories: +.TP +$CVSROOT/CVSROOT +Directory of global administrative files for repository. +.TP +CVSROOT/commitinfo,v +Records programs for filtering +.` "cvs commit" +requests. +.TP +CVSROOT/history +Log file of \fBcvs\fP transactions. +.TP +CVSROOT/modules,v +Definitions for modules in this repository. +.TP +CVSROOT/loginfo,v +Records programs for piping +.` "cvs commit" +log entries. +.TP +CVSROOT/rcsinfo,v +Records pathnames to templates used dueing a +.` "cvs commit" +operation. +.TP +CVSROOT/editinfo,v +Records programs for editing/validating +.` "cvs commit" +log entries. +.TP +Attic +Directory for removed source files. +.TP +#cvs.lock +A lock directory created by +.B cvs +when doing sensitive changes to the +.SM RCS +source repository. +.TP +#cvs.tfl.\fIpid\fP +Temporary lock file for repository. +.TP +#cvs.rfl.\fIpid\fP +A read lock. +.TP +#cvs.wfl.\fIpid\fP +A write lock. +.SH "ENVIRONMENT VARIABLES" +.TP +.SM CVSROOT +Should contain the full pathname to the root of the +.B cvs +source repository (where the +.SM RCS +files are kept). This information must be available to \fBcvs\fP for +most commands to execute; if +.SM CVSROOT +is not set, or if you wish to override it for one invocation, you can +supply it on the command line: +.` "cvs \-d \fIcvsroot cvs_command\fP\|.\|.\|." +You may not need to set +.SM CVSROOT +if your \fBcvs\fP binary has the right path compiled in; use +.` "cvs \-v" +to display all compiled-in paths. +.TP +.SM CVSREAD +If this is set, +.B checkout +and +.B update +will try hard to make the files in your working directory read-only. +When this is not set, the default behavior is to permit modification +of your working files. +.TP +.SM RCSBIN +Specifies the full pathname where to find +.SM RCS +programs, such as +.BR co ( 1 ) +and +.BR ci ( 1 ). +If not set, a compiled-in value is used; see the display from +.` "cvs \-v". +.TP +.SM EDITOR +Specifies the program to use for recording log messages during +.BR commit . +If not set, the default is +.BR /usr/ucb/vi . +.SH "AUTHORS" +.TP +Dick Grune +Original author of the +.B cvs +shell script version posted to +.B comp.sources.unix +in the volume6 release of December, 1986. +Credited with much of the +.B cvs +conflict resolution algorithms. +.TP +Brian Berliner +Coder and designer of the +.B cvs +program itself in April, 1989, based on the original work done by Dick. +.TP +Jeff Polk +Helped Brian with the design of the +.B cvs +module and vendor branch support and author of the +.BR checkin ( 1 ) +shell script (the ancestor of +.` "cvs import"). +.SH "SEE ALSO" +.BR ci ( 1 ), +.BR co ( 1 ), +.BR cvs ( 5 ), +.BR diff ( 1 ), +.BR grep ( 1 ), +.BR mkmodules ( 1 ), +.BR patch ( 1 ), +.BR rcs ( 1 ), +.BR rcsdiff ( 1 ), +.BR rcsmerge ( 1 ), +.BR rlog ( 1 ), +.BR rm ( 1 ), +.BR sort ( 1 ). diff --git a/gnu/usr.bin/cvs/cvs/cvs.5 b/gnu/usr.bin/cvs/cvs/cvs.5 new file mode 100644 index 0000000..49ca562 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/cvs.5 @@ -0,0 +1,326 @@ +.TH cvs 5 "12 February 1992" +.\" Full space in nroff; half space in troff +.de SP +.if n .sp +.if t .sp .5 +.. +.SH NAME +cvs \- Concurrent Versions System support files +.SH SYNOPSIS +.hy 0 +.na +.TP +.B $CVSROOT/CVSROOT/modules,v +.TP +.B $CVSROOT/CVSROOT/commitinfo,v +.TP +.B $CVSROOT/CVSROOT/loginfo,v +.TP +.B $CVSROOT/CVSROOT/rcsinfo,v +.TP +.B $CVSROOT/CVSROOT/editinfo,v +.TP +.B $CVSROOT/CVSROOT/cvsignore,v +.TP +.B $CVSROOT/CVSROOT/history +.ad b +.hy 1 +.SH DESCRIPTION +.B cvs +is a system for providing source control to hierarchical collections +of source directories. Commands and procedures for using \fBcvs\fP +are described in +.BR cvs ( 1 ). +.SP +.B cvs +manages \fIsource repositories\fP, the directories containing master +copies of the revision-controlled files, by copying particular +revisions of the files to (and modifications back from) developers' +private \fIworking directories\fP. In terms of file structure, each +individual source repository is an immediate subdirectory of +\fB$CVSROOT\fP. +.SP +The files described here are supporting files; they do not have to +exist for \fBcvs\fP to operate, but they allow you to make \fBcvs\fP +operation more flexible. +.SP +The +.BR cvsinit ( 1 ) +shell script included at the top-level of the +.B cvs +distribution can be used to setup an initial +.B $CVSROOT/CVSROOT +area, if you don't have one already. +.SP +You can use the `\|modules\|' file to define symbolic names for +collections of source maintained with \fBcvs\fP. If there is no +`\|modules\|' file, developers must specify complete path names +(absolute, or relative to \fB$CVSROOT\fP) for the files they wish to +manage with \fBcvs\fP commands. +.SP +You can use the `\|commitinfo\|' file to define programs to execute +whenever `\|\fBcvs commit\fP\|' is about to execute. +These programs are used for ``pre-commit'' checking to verify that the +modified, added, and removed files are really ready to be committed. +Some uses for this check might be to turn off a portion (or all) of the +source repository from a particular person or group. +Or, perhaps, to verify that the changed files conform to the site's +standards for coding practice. +.SP +You can use the `\|loginfo\|' file to define programs to execute after +any +.BR commit , +which writes a log entry for changes in the repository. +These logging programs might be used to append the log message to a file. +Or send the log message through electronic mail to a group of developers. +Or, perhaps, post the log message to a particular newsgroup. +.SP +You can use the `\|rcsinfo\|' file to define forms for log messages. +.SP +You can use the `\|editinfo\|' file to define a program to execute for +editing/validating `\|\fBcvs commit\fP\|' log entries. +This is most useful when used with a `\|rcsinfo\|' forms specification, as +it can verify that the proper fields of the form have been filled in by the +user committing the change. +.SP +You can use the `\|cvsignore\|' file to specify the default list of +files to ignore during \fBupdate\fP. +.SP +You can use the `\|history\|' file to record the \fBcvs\fP commands +that affect the repository. +The creation of this file enables history logging. +.SH FILES +.TP +.B modules +The `\|modules\|' file records your definitions of names for +collections of source code. \fBcvs\fP will use these definitions if +you create a file with the right format in +`\|\fB$CVSROOT/CVSROOT/modules,v\fP\|'. +The +.BR mkmodules ( 1 ) +command should be run whenever the modules file changes, so that the +appropriate files can be generated (depending on how you have configured +.B cvs +operation). +.SP +To allow convenient editing of the `\|modules\|' file itself, the file should +include an entry like the following (where \fIlocalbin\fP represents the +directory where your site installs programs like +.BR mkmodules ( 1 )): +.SP +.nf +\&\fBmodules \-i /\fP\fIlocalbin\fP\fB/mkmodules CVSROOT modules\fP +.fi +.SP +This defines the name `\|\fBmodules\fP\|' as the module name for the +file itself, so that you can use +.SP +.in +1i +.ft B +.nf +example% cvs checkout modules +.fi +.ft P +.in -1i +.SP +to get an editable copy of the file. You should define similar module +entries for the other configuration files described here (except +\&`\|history\|'). +The +.BR cvsinit ( 1 ) +script will setup a smilar `\|modules\|' file for you automatically. +.SP +The `\|modules\|' file may contain blank lines and comments (lines +beginning with `\|\fB#\fP\|') as well as module definitions. +Long lines can be continued on the next line by specifying a backslash +(``\e'') as the last character on the line. +.SP +A \fImodule definition\fP is a single line of the `\|modules\|' file, +in either of two formats. In both cases, \fImname\fP represents the +symbolic module name, and the remainder of the line is its definition. +.SP +\fImname\fP \fB\-a\fP \fIaliases\fP\|.\|.\|. +.br +This represents the simplest way of defining a module \fImname\fP. +The `\|\fB\-a\fP\|' flags the definition as a simple alias: \fBcvs\fP +will treat any use of \fImname\fP (as a command argument) as if the list +of names \fIaliases\fP had been specified instead. \fIaliases\fP may +contain either other module names or paths. When you use paths in +\fIaliases\fP, `\|\fBcvs checkout\fP\|' creates all intermediate +directories in the working directory, just as if the path had been +specified explicitly in the \fBcvs\fP arguments. +.SP +.nf +\fImname\fP [ \fIoptions\fP ] \fIdir\fP [ \fIfiles\fP\|.\|.\|. ] [ \fB&\fP\fImodule\fP\|.\|.\|. ] +.fi +.SP +In the simplest case, this form of module definition reduces to +`\|\fImname dir\fP\|'. This defines all the files in directory +\fIdir\fP as module \fImname\fP. \fIdir\fP is a relative path (from +\fB$CVSROOT\fP) to a directory of source in one of the source +repositories. In this case, on \fBcheckout\fP, a single directory +called \fImname\fP is created as a working directory; no intermediate +directory levels are used by default, even if \fIdir\fP was a path +involving several directory levels. +.SP +By explicitly specifying \fIfiles\fP in the module definition after +\fIdir\fP, you can select particular files from directory +\fIdir\fP. The sample definition for \fBmodules\fP is an example of +a module defined with a single file from a particular directory. Here +is another example: +.SP +.nf +.ft B +m4test unsupported/gnu/m4 foreach.m4 forloop.m4 +.ft P +.fi +.SP +With this definition, executing `\|\fBcvs checkout m4test\fP\|' +will create a single working directory `\|m4test\|' containing the two +files listed, which both come from a common directory several levels +deep in the \fBcvs\fP source repository. +.SP +A module definition can refer to other modules by including +`\|\fB&\fP\fImodule\fP\|' in its definition. \fBcheckout\fP creates +a subdirectory for each such \fImodule\fP, in your working directory. +.br +.I +New in \fBcvs\fP 1.3; +avoid this feature if sharing module definitions with older versions +of \fBcvs\fP. +.SP +Finally, you can use one or more of the following \fIoptions\fP in +module definitions: +.SP +\&`\|\fB\-d\fP \fIname\fP\|', to name the working directory something +other than the module name. +.br +.I +New in \fBcvs\fP 1.3; +avoid this feature if sharing module definitions with older versions +of \fBcvs\fP. +.SP +\&`\|\fB\-i\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever files in a module are committed. \fIprog\fP runs with a +single argument, the full pathname of the affected directory in a +source repository. The `\|commitinfo\|', `\|loginfo\|', and +`\|editinfo\|' files provide other ways to call a program on \fBcommit\fP. +.SP +`\|\fB\-o\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever files in a module are checked out. \fIprog\fP runs +with a single argument, the module name. +.SP +`\|\fB\-t\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever files in a module are tagged. \fIprog\fP runs with two +arguments: the module name and the symbolic tag specified to \fBrtag\fP. +.SP +`\|\fB\-u\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP +to run whenever `\|\fBcvs update\fP\|' is executed from the top-level +directory of the checked-out module. \fIprog\fP runs with a +single argument, the full path to the source repository for this module. +.TP +\&\fBcommitinfo\fP, \fBloginfo\fP, \fBrcsinfo\fP, \fBeditinfo\fP +These files all specify programs to call at different points in the +`\|\fBcvs commit\fP\|' process. They have a common structure. +Each line is a pair of fields: a regular expression, separated by +whitespace from a filename or command-line template. +Whenever one of the regular expression matches a directory name in the +repository, the rest of the line is used. +If the line begins with a \fB#\fP character, the entire line is considered +a comment and is ignored. +Whitespace between the fields is also ignored. +.SP +For `\|loginfo\|', the rest of the +line is a command-line template to execute. +The templates can include not only +a program name, but whatever list of arguments you wish. If you write +`\|\fB%s\fP\|' somewhere on the argument list, \fBcvs\fP supplies, at +that point, the list of files affected by the \fBcommit\fP. +The first entry in the list is the relative path within the source +repository where the change is being made. +The remaining arguments list the files that are being modified, added, or +removed by this \fBcommit\fP invocation. +.SP +For `\|commitinfo\|', the rest of the line is a command-line template to +execute. +The template can include can include not only a program name, but whatever +list of arguments you wish. +The full path to the current source repository is appended to the template, +followed by the file names of any files involved in the commit (added, +removed, and modified files). +.SP +For `\|rcsinfo\|', the rest of the line is the full path to a file that +should be loaded into the log message template. +.SP +For `\|editinfo\|', the rest of the line is a command-line template to +execute. +The template can include can include not only a program name, but whatever +list of arguments you wish. +The full path to the current log message template file is appended to the +template. +.SP +You can use one of two special strings instead of a regular +expression: `\|\fBALL\fP\|' specifies a command line template that +must always be executed, and `\|\fBDEFAULT\fP\|' specifies a command +line template to use if no regular expression is a match. +.SP +The `\|commitinfo\|' file contains commands to execute \fIbefore\fP any +other \fBcommit\fP activity, to allow you to check any conditions that +must be satisfied before \fBcommit\fP can proceed. The rest of the +\fBcommit\fP will execute only if all selected commands from this file +exit with exit status \fB0\fP. +.SP +The `\|rcsinfo\|' file allows you to specify \fIlog templates\fP for +the \fBcommit\fP logging session; you can use this to provide a form +to edit when filling out the \fBcommit\fP log. The field after the +regular expression, in this file, contains filenames (of files +containing the logging forms) rather than command templates. +.SP +The `\|editinfo\|' file allows you to execute a script \fIbefore the +commit starts\fP, but after the log information is recorded. These +"edit" scripts can verify information recorded in the log file. If +the edit script exits wth a non-zero exit status, the commit is aborted. +.SP +The `\|loginfo\|' file contains commands to execute \fIat the end\fP +of a commit. The text specified as a commit log message is piped +through the command; typical uses include sending mail, filing an +article in a newsgroup, or appending to a central file. +.TP +\&\fBcvsignore\fP, \fB.cvsignore\fP +The default list of files (or +.BR sh ( 1 ) +file name patterns) to ignore during `\|\fBcvs update\fP\|'. +At startup time, \fBcvs\fP loads the compiled in default list of file name +patterns (see +.BR cvs ( 1 )). +Then the per-repository list included in \fB$CVSROOT/CVSROOT/cvsignore\fP +is loaded, if it exists. +Then the per-user list is loaded from `\|$HOME/.cvsignore\|'. +Finally, as \fBcvs\fP traverses through your directories, it will load any +per-directory `\|.cvsignore\|' files whenever it finds one. +These per-directory files are only valid for exactly the directory that +contains them, not for any sub-directories. +.TP +.B history +Create this file in \fB$CVSROOT/CVSROOT\fP to enable history logging +(see the description of `\|\fBcvs history\fP\|'). +.SH "SEE ALSO" +.BR cvs ( 1 ), +.BR mkmodules ( 1 ). +.SH COPYING +Copyright \(co 1992 Cygnus Support, Brian Berliner, and Jeff Polk +.PP +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. +.PP +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. +.PP +Permission is granted to copy and distribute translations of this +manual into another language, under the above conditions for modified +versions, except that this permission notice may be included in +translations approved by the Free Software Foundation instead of in +the original English. diff --git a/gnu/usr.bin/cvs/cvs/cvs.h b/gnu/usr.bin/cvs/cvs/cvs.h new file mode 100644 index 0000000..ec47581 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/cvs.h @@ -0,0 +1,438 @@ +/* @(#)cvs.h 1.72 92/03/31 */ + +#include "system.h" +#include <stdio.h> +#include <ctype.h> +#include <pwd.h> +#include <signal.h> +#include "hash.h" +#include "rcs.h" +#include "regex.h" +#include "fnmatch.h" +#include "getopt.h" +#include "wait.h" +#include "config.h" +#ifdef MY_NDBM +#include "myndbm.h" +#else +#include <ndbm.h> +#endif /* !MY_NDBM */ + +/* XXX - for now this is static */ +#undef PATH_MAX +#ifdef MAXPATHLEN +#define PATH_MAX MAXPATHLEN+2 +#else +#define PATH_MAX 1024+2 +#endif + +/* just in case this implementation does not define this */ +#ifndef L_tmpnam +#define L_tmpnam 50 +#endif + +#if __STDC__ +#define CONST const +#define PTR void * +#else +#define CONST +#define PTR char * +#endif + +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Definitions for the CVS Administrative directory and the files it contains. + * Here as #define's to make changing the names a simple task. + */ +#define CVSADM "CVS" +#define CVSADM_ENT "CVS/Entries" +#define CVSADM_ENTBAK "CVS/Entries.Backup" +#define CVSADM_ENTSTAT "CVS/Entries.Static" +#define CVSADM_REP "CVS/Repository" +#define CVSADM_CIPROG "CVS/Checkin.prog" +#define CVSADM_UPROG "CVS/Update.prog" +#define CVSADM_TAG "CVS/Tag" + +/* + * The following are obsolete and are maintained here only so that they can be + * cleaned up during the transition + */ +#define OCVSADM "CVS.adm" /* for CVS 1.2 and earlier */ +#define CVSADM_FILE "CVS/Files" +#define CVSADM_MOD "CVS/Mod" + +/* + * Definitions for the CVSROOT Administrative directory and the files it + * contains. This directory is created as a sub-directory of the $CVSROOT + * environment variable, and holds global administration information for the + * entire source repository beginning at $CVSROOT. + */ +#define CVSROOTADM "CVSROOT" +#define CVSROOTADM_MODULES "modules" +#define CVSROOTADM_LOGINFO "loginfo" +#define CVSROOTADM_RCSINFO "rcsinfo" +#define CVSROOTADM_COMMITINFO "commitinfo" +#define CVSROOTADM_EDITINFO "editinfo" +#define CVSROOTADM_HISTORY "history" +#define CVSROOTADM_IGNORE "cvsignore" +#define CVSNULLREPOS "Emptydir" /* an empty directory */ + +/* support for the modules file (CVSROOTADM_MODULES) */ +#define CVSMODULE_OPTS "ad:i:lo:s:t:u:"/* options in modules file */ +#define CVSMODULE_SPEC '&' /* special delimiter */ + +/* + * The following are obsolete and are maintained here only so that they can be + * cleaned up during the transition + */ +#define OCVSROOTADM "CVSROOT.adm" /* for CVS 1.2 and earlier */ + +/* Other CVS file names */ +#define CVSATTIC "Attic" +#define CVSLCK "#cvs.lock" +#define CVSTFL "#cvs.tfl" +#define CVSRFL "#cvs.rfl" +#define CVSWFL "#cvs.wfl" +#define CVSEXT_OPT ",p" +#define CVSEXT_LOG ",t" +#define CVSPREFIX ",," +#define CVSDOTIGNORE ".cvsignore" + +/* miscellaneous CVS defines */ +#define CVSEDITPREFIX "CVS: " +#define CVSLCKAGE (60*60) /* 1-hour old lock files cleaned up */ +#define CVSLCKSLEEP 30 /* wait 30 seconds before retrying */ +#define CVSBRANCH "1.1.1" /* RCS branch used for vendor srcs */ +#define BAKPREFIX ".#" /* when rcsmerge'ing */ +#define DEVNULL "/dev/null" + +#define FALSE 0 +#define TRUE 1 + +/* + * Special tags. -rHEAD refers to the head of an RCS file, regardless of any + * sticky tags. -rBASE refers to the current revision the user has checked + * out This mimics the behaviour of RCS. + */ +#define TAG_HEAD "HEAD" +#define TAG_BASE "BASE" + +/* Environment variable used by CVS */ +#define CVSREAD_ENV "CVSREAD" /* make files read-only */ +#define CVSREAD_DFLT FALSE /* writable files by default */ + +#define RCSBIN_ENV "RCSBIN" /* RCS binary directory */ +/* #define RCSBIN_DFLT Set by config.h */ + +#define EDITOR_ENV "EDITOR" /* which editor to use */ +/* #define EDITOR_DFLT Set by config.h */ + +#define CVSROOT_ENV "CVSROOT" /* source directory root */ +#define CVSROOT_DFLT NULL /* No dflt; must set for checkout */ + +#define IGNORE_ENV "CVSIGNORE" /* More files to ignore */ + +/* + * If the beginning of the Repository matches the following string, strip it + * so that the output to the logfile does not contain a full pathname. + * + * If the CVSROOT environment variable is set, it overrides this define. + */ +#define REPOS_STRIP "/master/" + +/* + * The maximum number of files per each CVS directory. This is mainly for + * sizing arrays statically rather than dynamically. 3000 seems plenty for + * now. + */ +#define MAXFILEPERDIR 3000 +#define MAXLINELEN 5000 /* max input line from a file */ +#define MAXPROGLEN 30000 /* max program length to system() */ +#define MAXLISTLEN 40000 /* For [A-Z]list holders */ +#define MAXMESGLEN 10000 /* max RCS log message size */ +#define MAXDATELEN 50 /* max length for a date */ + +/* The type of request that is being done in do_module() */ +enum mtype +{ + CHECKOUT, TAG, PATCH +}; + +/* + * defines for Classify_File() to determine the current state of a file. + * These are also used as types in the data field for the list we make for + * Update_Logfile in commit, import, and add. + */ +enum classify_type +{ + T_UNKNOWN = 1, /* no old-style analog existed */ + T_CONFLICT, /* C (conflict) list */ + T_NEEDS_MERGE, /* G (needs merging) list */ + T_MODIFIED, /* M (needs checked in) list */ + T_CHECKOUT, /* O (needs checkout) list */ + T_ADDED, /* A (added file) list */ + T_REMOVED, /* R (removed file) list */ + T_REMOVE_ENTRY, /* W (removed entry) list */ + T_UPTODATE, /* File is up-to-date */ + T_TITLE /* title for node type */ +}; +typedef enum classify_type Ctype; + +/* + * a struct vers_ts contains all the information about a file including the + * user and rcs file names, and the version checked out and the head. + * + * this is usually obtained from a call to Version_TS which takes a tag argument + * for the RCS file if desired + */ +struct vers_ts +{ + char *vn_user; /* rcs version user file derives from + * it can have the following special + * values: + * empty = no user file + * 0 = user file is new + * -vers = user file to be removed */ + char *vn_rcs; /* the verion for the rcs file + * (tag version?) */ + char *ts_user; /* the timestamp for the user file */ + char *ts_rcs; /* the user timestamp from entries */ + char *options; /* opts from Entries file + * (keyword expansion) */ + char *tag; /* tag stored in the Entries file */ + char *date; /* date stored in the Entries file */ + Entnode *entdata; /* pointer to entries file node */ + RCSNode *srcfile; /* pointer to parsed src file info */ +}; +typedef struct vers_ts Vers_TS; + +/* + * structure used for list-private storage by ParseEntries() and + * Version_TS(). + */ +struct stickydirtag +{ + int aflag; + char *tag; + char *date; + char *options; +}; + +/* flags for run_exec(), the fast system() for CVS */ +#define RUN_NORMAL 0x0000 /* no special behaviour */ +#define RUN_COMBINED 0x0001 /* stdout is duped to stderr */ +#define RUN_REALLY 0x0002 /* do the exec, even if noexec is on */ +#define RUN_STDOUT_APPEND 0x0004 /* append to stdout, don't truncate */ +#define RUN_STDERR_APPEND 0x0008 /* append to stderr, don't truncate */ +#define RUN_SIGIGNORE 0x0010 /* ignore interrupts for command */ +#define RUN_TTY (char *)0 /* for the benefit of lint */ + +/* Flags for find_{names,dirs} routines */ +#define W_LOCAL 0x01 /* look for files locally */ +#define W_REPOS 0x02 /* look for files in the repository */ +#define W_ATTIC 0x04 /* look for files in the attic */ + +/* Flags for return values of direnter procs for the recursion processor */ +enum direnter_type +{ + R_PROCESS = 1, /* process files and maybe dirs */ + R_SKIP_FILES, /* don't process files in this dir */ + R_SKIP_DIRS, /* don't process sub-dirs */ + R_SKIP_ALL /* don't process files or dirs */ +}; +typedef enum direnter_type Dtype; + +extern char *program_name, *command_name; +extern char *Rcsbin, *Editor, *CVSroot; +extern char *CurDir; +extern int really_quiet, quiet; +extern int use_editor; +extern int cvswrite; + +extern int trace; /* Show all commands */ +extern int noexec; /* Don't modify disk anywhere */ +extern int logoff; /* Don't write history entry */ + +/* Externs that are included directly in the CVS sources */ +#if __STDC__ +int Reader_Lock (char *xrepository); +DBM *open_module (void); +FILE *Fopen (char *name, char *mode); +FILE *open_file (char *name, char *mode); +List *Find_Dirs (char *repository, int which); +List *ParseEntries (int aflag); +char *Make_Date (char *rawdate); +char *Name_Repository (char *dir, char *update_dir); +char *Short_Repository (char *repository); +char *getcaller (void); +char *time_stamp (char *file); +char *xmalloc (int bytes); +char *xrealloc (char *ptr, int bytes); +char *xstrdup (char *str); +int No_Difference (char *file, Vers_TS * vers, List * entries); +int Parse_Info (char *infofile, char *repository, int (*callproc) (), int all); +int Reader_Lock (char *xrepository); +int SIG_register (int sig, SIGTYPE (*fn) ()); +int Writer_Lock (List * list); +int gethostname (char *name, int namelen); +int ign_name (char *name); +int isdir (char *file); +int isfile (char *file); +int islink (char *file); +int isreadable (char *file); +int iswritable (char *file); +int link_file (char *from, char *to); +int numdots (char *s); +int run_exec (char *stin, char *stout, char *sterr, int flags); +int unlink_file (char *f); +int update (int argc, char *argv[]); +int xcmp (char *file1, char *file2); +int yesno (void); +time_t get_date (char *date, struct timeb *now); +void Create_Admin (char *dir, char *repository, char *tag, char *date); +void Lock_Cleanup (void); +void ParseTag (char **tagp, char **datep); +void Scratch_Entry (List * list, char *fname); +void WriteTag (char *dir, char *tag, char *date); +void cat_module (int status); +void check_entries (char *dir); +void close_module (DBM * db); +void copy_file (char *from, char *to); +void error (int status, int errnum, char *message,...); +void fperror (FILE * fp, int status, int errnum, char *message,...); +void free_names (int *pargc, char *argv[]); +void freevers_ts (Vers_TS ** versp); +void ign_add (char *ign, int hold); +void ign_add_file (char *file, int hold); +void ign_setup (void); +void line2argv (int *pargc, char *argv[], char *line); +void make_directories (char *name); +void make_directory (char *name); +void rename_file (char *from, char *to); +void run_arg (char *s); +void run_args (char *fmt,...); +void run_print (FILE * fp); +void run_setup (char *fmt,...); +void strip_path (char *path); +void update_delproc (Node * p); +void usage (char **cpp); +void xchmod (char *fname, int writable); +int Checkin (int type, char *file, char *repository, char *rcs, char *rev, + char *tag, char *message, List * entries); +Ctype Classify_File (char *file, char *tag, char *date, char *options, + int force_tag_match, int aflag, char *repository, + List *entries, List *srcfiles, Vers_TS **versp); +List *Find_Names (char *repository, int which, int aflag, + List ** optentries); +void Register (List * list, char *fname, char *vn, char *ts, + char *options, char *tag, char *date); +void Update_Logfile (char *repository, char *xmessage, char *xrevision, + FILE * xlogfp, List * xchanges); +Vers_TS *Version_TS (char *repository, char *options, char *tag, + char *date, char *user, int force_tag_match, + int set_time, List * entries, List * xfiles); +void do_editor (char *dir, char *message, char *repository, + List * changes); +int do_module (DBM * db, char *mname, enum mtype m_type, char *msg, + int (*callback_proc) (), char *where, int shorten, + int local_specified, int run_module_prog, char *extra_arg); +int do_recursion (int (*xfileproc) (), int (*xfilesdoneproc) (), + Dtype (*xdirentproc) (), int (*xdirleaveproc) (), + Dtype xflags, int xwhich, int xaflag, int xreadlock, + int xdosrcs); +int do_update (int argc, char *argv[], char *xoptions, char *xtag, + char *xdate, int xforce, int local, int xbuild, + int xaflag, int xprune, int xpipeout, int which, + char *xjoin_rev1, char *xjoin_rev2, char *preload_update_dir); +void history_write (int type, char *update_dir, char *revs, char *name, + char *repository); +int start_recursion (int (*fileproc) (), int (*filesdoneproc) (), + Dtype (*direntproc) (), int (*dirleaveproc) (), + int argc, char *argv[], int local, int which, + int aflag, int readlock, char *update_preload, + int dosrcs); +void SIG_beginCrSect (); +void SIG_endCrSect (); +#else /* !__STDC__ */ +DBM *open_module (); +FILE *Fopen (); +FILE *open_file (); +List *Find_Dirs (); +List *Find_Names (); +List *ParseEntries (); +Vers_TS *Version_TS (); +char *Make_Date (); +char *Name_Repository (); +char *Short_Repository (); +char *getcaller (); +char *time_stamp (); +char *xmalloc (); +char *xrealloc (); +char *xstrdup (); +int Checkin (); +Ctype Classify_File (); +int No_Difference (); +int Parse_Info (); +int Reader_Lock (); +int SIG_register (); +int Writer_Lock (); +int do_module (); +int do_recursion (); +int do_update (); +int gethostname (); +int ign_name (); +int isdir (); +int isfile (); +int islink (); +int isreadable (); +int iswritable (); +int link_file (); +int numdots (); +int run_exec (); +int start_recursion (); +int unlink_file (); +int update (); +int xcmp (); +int yesno (); +time_t get_date (); +void Create_Admin (); +void Lock_Cleanup (); +void ParseTag (); +void ParseTag (); +void Register (); +void Scratch_Entry (); +void Update_Logfile (); +void WriteTag (); +void cat_module (); +void check_entries (); +void close_module (); +void copy_file (); +void do_editor (); +void error (); +void fperror (); +void free_names (); +void freevers_ts (); +void history_write (); +void ign_add (); +void ign_add_file (); +void ign_setup (); +void line2argv (); +void make_directories (); +void make_directory (); +void rename_file (); +void run_arg (); +void run_args (); +void run_print (); +void run_setup (); +void strip_path (); +void update_delproc (); +void usage (); +void xchmod (); +void SIG_beginCrSect (); +void SIG_endCrSect (); +#endif /* __STDC__ */ diff --git a/gnu/usr.bin/cvs/cvs/diff.c b/gnu/usr.bin/cvs/cvs/diff.c new file mode 100644 index 0000000..db8b4b7 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/diff.c @@ -0,0 +1,407 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Difference + * + * Run diff against versions in the repository. Options that are specified are + * passed on directly to "rcsdiff". + * + * Without any file arguments, runs diff against all the currently modified + * files. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)diff.c 1.52 92/04/10"; +#endif + +#if __STDC__ +static Dtype diff_dirproc (char *dir, char *pos_repos, char *update_dir); +static int diff_dirleaveproc (char *dir, int err, char *update_dir); +static int diff_file_nodiff (char *file, char *repository, List *entries, + List *srcfiles, Vers_TS *vers); +static int diff_fileproc (char *file, char *update_dir, char *repository, + List * entries, List * srcfiles); +static void diff_mark_errors (int err); +#else +static int diff_fileproc (); +static Dtype diff_dirproc (); +static int diff_dirleaveproc (); +static int diff_file_nodiff (); +static void diff_mark_errors (); +#endif /* __STDC__ */ + +static char *diff_rev1, *diff_rev2; +static char *diff_date1, *diff_date2; +static char *use_rev1, *use_rev2; +static char *options; +static char opts[PATH_MAX]; +static int diff_errors; + +static char *diff_usage[] = +{ + "Usage: %s %s [-l] [rcsdiff-options]\n", +#ifdef CVS_DIFFDATE + " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n", +#else + " [-r rev1 [-r rev2]] [files...] \n", +#endif + "\t-l\tLocal directory only, not recursive\n", + "\t-D d1\tDiff revision for date against working file.\n", + "\t-D d2\tDiff rev1/date1 against date2.\n", + "\t-r rev1\tDiff revision for rev1 against working file.\n", + "\t-r rev2\tDiff rev1/date1 against rev2.\n", + NULL +}; + +int +diff (argc, argv) + int argc; + char *argv[]; +{ + char tmp[50]; + int c, err = 0; + int local = 0; + + if (argc == -1) + usage (diff_usage); + + /* + * Note that we catch all the valid arguments here, so that we can + * intercept the -r arguments for doing revision diffs; and -l/-R for a + * non-recursive/recursive diff. + */ + optind = 1; + while ((c = gnu_getopt (argc, argv, + "abcdefhilnpqtuw0123456789BHQRTC:D:F:I:L:V:k:r:")) != -1) + { + switch (c) + { + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'h': case 'i': case 'n': case 'p': case 't': case 'u': + case 'w': case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': case 'B': + case 'H': case 'T': case 'Q': + (void) sprintf (tmp, " -%c", (char) c); + (void) strcat (opts, tmp); + if (c == 'Q') + { + quiet = 1; + really_quiet = 1; + c = 'q'; + } + break; + case 'C': case 'F': case 'I': case 'L': case 'V': +#ifndef CVS_DIFFDATE + case 'D': +#endif + (void) sprintf (tmp, " -%c%s", (char) c, optarg); + (void) strcat (opts, tmp); + break; + case 'R': + local = 0; + break; + case 'l': + local = 1; + break; + case 'q': + quiet = 1; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'r': + if (diff_rev2 != NULL || diff_date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (diff_rev1 != NULL || diff_date1 != NULL) + diff_rev2 = optarg; + else + diff_rev1 = optarg; + break; +#ifdef CVS_DIFFDATE + case 'D': + if (diff_rev2 != NULL || diff_date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (diff_rev1 != NULL || diff_date1 != NULL) + diff_date2 = Make_Date (optarg); + else + diff_date1 = Make_Date (optarg); + break; +#endif + case '?': + default: + usage (diff_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* make sure options is non-null */ + if (!options) + options = xstrdup (""); + + /* start the recursion processor */ + err = start_recursion (diff_fileproc, (int (*) ()) NULL, diff_dirproc, + diff_dirleaveproc, argc, argv, local, + W_LOCAL, 0, 1, (char *) NULL, 1); + + /* clean up */ + free (options); + return (err); +} + +/* + * Do a file diff + */ +/* ARGSUSED */ +static int +diff_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + int status, err = 2; /* 2 == trouble, like rcsdiff */ + Vers_TS *vers; + + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 1, 0, entries, srcfiles); + + if (vers->vn_user == NULL) + { + error (0, 0, "I know nothing about %s", file); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + { + error (0, 0, "%s is a new entry, no comparison available", file); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + else if (vers->vn_user[0] == '-') + { + error (0, 0, "%s was removed, no comparison available", file); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + else + { + if (vers->vn_rcs == NULL && vers->srcfile == NULL) + { + error (0, 0, "cannot find revision control file for %s", file); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + else + { + if (vers->ts_user == NULL) + { + error (0, 0, "cannot find %s", file); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + } + } + + if (diff_file_nodiff (file, repository, entries, srcfiles, vers)) + { + freevers_ts (&vers); + return (0); + } + + (void) fflush (stdout); + if (use_rev2) + { + run_setup ("%s%s %s %s -r%s -r%s", Rcsbin, RCS_DIFF, + opts, *options ? options : vers->options, + use_rev1, use_rev2); + } + else + { + run_setup ("%s%s %s %s -r%s", Rcsbin, RCS_DIFF, opts, + *options ? options : vers->options, use_rev1); + } + run_arg (vers->srcfile->path); + + switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, + RUN_REALLY|RUN_COMBINED))) + { + case -1: /* fork failed */ + error (1, errno, "fork failed during rcsdiff of %s", + vers->srcfile->path); + case 0: /* everything ok */ + err = 0; + break; + default: /* other error */ + err = status; + break; + } + + (void) fflush (stdout); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); +} + +/* + * Remember the exit status for each file. + */ +static void +diff_mark_errors (err) + int err; +{ + if (err > diff_errors) + diff_errors = err; +} + +/* + * Print a warm fuzzy message when we enter a dir + */ +/* ARGSUSED */ +static Dtype +diff_dirproc (dir, pos_repos, update_dir) + char *dir; + char *pos_repos; + char *update_dir; +{ + /* XXX - check for dirs we don't want to process??? */ + if (!quiet) + error (0, 0, "Diffing %s", update_dir); + return (R_PROCESS); +} + +/* + * Concoct the proper exit status. + */ +/* ARGSUSED */ +static int +diff_dirleaveproc (dir, err, update_dir) + char *dir; + int err; + char *update_dir; +{ + return (diff_errors); +} + +/* + * verify that a file is different 0=same 1=different + */ +static int +diff_file_nodiff (file, repository, entries, srcfiles, vers) + char *file; + char *repository; + List *entries; + List *srcfiles; + Vers_TS *vers; +{ + Vers_TS *xvers; + char tmp[L_tmpnam+1]; + + /* free up any old use_rev* variables and reset 'em */ + if (use_rev1) + free (use_rev1); + if (use_rev2) + free (use_rev2); + use_rev1 = use_rev2 = (char *) NULL; + + if (diff_rev1 || diff_date1) + { + /* special handling for TAG_HEAD */ + if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) + use_rev1 = xstrdup (vers->vn_rcs); + else + { + xvers = Version_TS (repository, (char *) NULL, diff_rev1, + diff_date1, file, 1, 0, entries, srcfiles); + if (xvers->vn_rcs == NULL) + { + if (diff_rev1) + error (0, 0, "tag %s is not in file %s", diff_rev1, file); + else + error (0, 0, "no revision for date %s in file %s", + diff_date1, file); + return (1); + } + use_rev1 = xstrdup (xvers->vn_rcs); + freevers_ts (&xvers); + } + } + if (diff_rev2 || diff_date2) + { + /* special handling for TAG_HEAD */ + if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0) + use_rev2 = xstrdup (vers->vn_rcs); + else + { + xvers = Version_TS (repository, (char *) NULL, diff_rev2, + diff_date2, file, 1, 0, entries, srcfiles); + if (xvers->vn_rcs == NULL) + { + if (diff_rev1) + error (0, 0, "tag %s is not in file %s", diff_rev2, file); + else + error (0, 0, "no revision for date %s in file %s", + diff_date2, file); + return (1); + } + use_rev2 = xstrdup (xvers->vn_rcs); + freevers_ts (&xvers); + } + + /* now, see if we really need to do the diff */ + return (strcmp (use_rev1, use_rev2) == 0); + } + if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0) + { + if (strcmp (vers->ts_rcs, vers->ts_user) == 0 && + (!(*options) || strcmp (options, vers->options) == 0)) + { + return (1); + } + if (use_rev1 == NULL) + use_rev1 = xstrdup (vers->vn_user); + } + + /* + * with 0 or 1 -r option specified, run a quick diff to see if we + * should bother with it at all. + */ + run_setup ("%s%s -p -q %s -r%s", Rcsbin, RCS_CO, + *options ? options : vers->options, use_rev1); + run_arg (vers->srcfile->path); + switch (run_exec (RUN_TTY, tmpnam (tmp), RUN_TTY, RUN_REALLY)) + { + case 0: /* everything ok */ + if (xcmp (file, tmp) == 0) + { + (void) unlink (tmp); + return (1); + } + break; + case -1: /* fork failed */ + (void) unlink (tmp); + error (1, errno, "fork failed during checkout of %s", + vers->srcfile->path); + default: + break; + } + (void) unlink (tmp); + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/entries.c b/gnu/usr.bin/cvs/cvs/entries.c new file mode 100644 index 0000000..6453e4f --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/entries.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Entries file to Files file + * + * Creates the file Files containing the names that comprise the project, from + * the Entries file. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)entries.c 1.37 92/03/31"; +#endif + +#if __STDC__ +static Node *AddEntryNode (List * list, char *name, char *version, + char *timestamp, char *options, char *tag, + char *date); +#else +static Node *AddEntryNode (); +#endif /* __STDC__ */ + +static FILE *entfile; +static char *entfilename; /* for error messages */ + +/* + * Write out the line associated with a node of an entries file + */ +static int +write_ent_proc (node) + Node *node; +{ + Entnode *p; + + p = (Entnode *) node->data; + if (fprintf (entfile, "/%s/%s/%s/%s/", node->key, p->version, + p->timestamp, p->options) == EOF) + error (1, errno, "cannot write %s", entfilename); + if (p->tag) + { + if (fprintf (entfile, "T%s\n", p->tag) == EOF) + error (1, errno, "cannot write %s", entfilename); + } + else if (p->date) + { + if (fprintf (entfile, "D%s\n", p->date) == EOF) + error (1, errno, "cannot write %s", entfilename); + } + else if (fprintf (entfile, "\n") == EOF) + error (1, errno, "cannot write %s", entfilename); + return (0); +} + +/* + * write out the current entries file given a list, making a backup copy + * first of course + */ +static void +write_entries (list) + List *list; +{ + /* open the new one and walk the list writing entries */ + entfilename = CVSADM_ENTBAK; + entfile = open_file (entfilename, "w+"); + (void) walklist (list, write_ent_proc); + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + + /* now, atomically (on systems that support it) rename it */ + rename_file (entfilename, CVSADM_ENT); +} + +/* + * Removes the argument file from the Entries file if necessary. + */ +void +Scratch_Entry (list, fname) + List *list; + char *fname; +{ + Node *node; + + if (trace) + (void) fprintf (stderr, "-> Scratch_Entry(%s)\n", fname); + + /* hashlookup to see if it is there */ + if ((node = findnode (list, fname)) != NULL) + { + delnode (node); /* delete the node */ + if (!noexec) + write_entries (list); /* re-write the file */ + } +} + +/* + * Enters the given file name/version/time-stamp into the Entries file, + * removing the old entry first, if necessary. + */ +void +Register (list, fname, vn, ts, options, tag, date) + List *list; + char *fname; + char *vn; + char *ts; + char *options; + char *tag; + char *date; +{ + Node *node; + + if (trace) + (void) fprintf (stderr, "-> Register(%s, %s, %s, %s, %s %s)\n", + fname, vn, ts, options, tag ? tag : "", + date ? date : ""); + /* was it already there? */ + if ((node = findnode (list, fname)) != NULL) + { + /* take it out */ + delnode (node); + + /* add the new one and re-write the file */ + (void) AddEntryNode (list, fname, vn, ts, options, tag, date); + if (!noexec) + write_entries (list); + } + else + { + /* add the new one */ + node = AddEntryNode (list, fname, vn, ts, options, tag, date); + + if (!noexec) + { + /* append it to the end */ + entfilename = CVSADM_ENT; + entfile = open_file (entfilename, "a"); + (void) write_ent_proc (node); + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + } + } +} + +/* + * Node delete procedure for list-private sticky dir tag/date info + */ +static void +freesdt (p) + Node *p; +{ + struct stickydirtag *sdtp; + + sdtp = (struct stickydirtag *) p->data; + if (sdtp->tag) + free (sdtp->tag); + if (sdtp->date) + free (sdtp->date); + if (sdtp->options) + free (sdtp->options); + free ((char *) sdtp); +} + +/* + * Read the entries file into a list, hashing on the file name. + */ +List * +ParseEntries (aflag) + int aflag; +{ + List *entries; + char line[MAXLINELEN]; + char *cp, *user, *vn, *ts, *options; + char *tag_or_date, *tag, *date; + char *dirtag, *dirdate; + int lineno = 0; + FILE *fpin; + + /* get a fresh list... */ + entries = getlist (); + + /* + * Parse the CVS/Tag file, to get any default tag/date settings. Use + * list-private storage to tuck them away for Version_TS(). + */ + ParseTag (&dirtag, &dirdate); + if (aflag || dirtag || dirdate) + { + struct stickydirtag *sdtp; + + sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp)); + bzero ((char *) sdtp, sizeof (*sdtp)); + sdtp->aflag = aflag; + sdtp->tag = xstrdup (dirtag); + sdtp->date = xstrdup (dirdate); + + /* feed it into the list-private area */ + entries->list->data = (char *) sdtp; + entries->list->delproc = freesdt; + } + + again: + fpin = fopen (CVSADM_ENT, "r"); + if (fpin == NULL) + error (0, errno, "cannot open %s for reading", CVSADM_ENT); + else + { + while (fgets (line, sizeof (line), fpin) != NULL) + { + lineno++; + if (line[0] == '/') + { + user = line + 1; + if ((cp = index (user, '/')) == NULL) + continue; + *cp++ = '\0'; + vn = cp; + if ((cp = index (vn, '/')) == NULL) + continue; + *cp++ = '\0'; + ts = cp; + if ((cp = index (ts, '/')) == NULL) + continue; + *cp++ = '\0'; + options = cp; + if ((cp = index (options, '/')) == NULL) + continue; + *cp++ = '\0'; + tag_or_date = cp; + if ((cp = index (tag_or_date, '\n')) == NULL) + continue; + *cp = '\0'; + tag = (char *) NULL; + date = (char *) NULL; + if (*tag_or_date == 'T') + tag = tag_or_date + 1; + else if (*tag_or_date == 'D') + date = tag_or_date + 1; + (void) AddEntryNode (entries, user, vn, ts, options, tag, date); + } + else + { + /* try conversion only on first line */ + if (lineno == 1) + { + (void) fclose (fpin); + check_entries ((char *) NULL); + goto again; + } + } + } + } + + /* clean up and return */ + if (fpin) + (void) fclose (fpin); + if (dirtag) + free (dirtag); + if (dirdate) + free (dirdate); + return (entries); +} + +/* + * Look at the entries file to determine if it is in the old entries format. + * If so, convert it to the new format. + */ +void +check_entries (dir) + char *dir; +{ + FILE *fpin, *fpout; + char tmp[MAXLINELEN]; + char line[MAXLINELEN]; + char entname[MAXLINELEN]; + char entbak[MAXLINELEN]; + char *cp, *user, *rev, *ts, *opt; + + if (dir != NULL) + { + (void) sprintf (entname, "%s/%s", dir, CVSADM_ENT); + (void) sprintf (entbak, "%s/%s", dir, CVSADM_ENTBAK); + } + else + { + (void) strcpy (entname, CVSADM_ENT); + (void) strcpy (entbak, CVSADM_ENTBAK); + } + + fpin = open_file (entname, "r"); + if (fgets (line, sizeof (line), fpin) == NULL) + { + (void) fclose (fpin); + return; + } + (void) fclose (fpin); + if (line[0] != '/') + { + rename_file (entname, entbak); + fpin = open_file (entbak, "r"); + fpout = open_file (entname, "w+"); + while (fgets (line, sizeof (line), fpin) != NULL) + { + if (line[0] == '/') + { + if (fputs (line, fpout) == EOF) + error (1, errno, "cannot write %s", CVSADM_ENT); + continue; + } + rev = line; + if ((ts = index (line, '|')) == NULL) + continue; + *ts++ = '\0'; + if ((user = rindex (ts, ' ')) == NULL) + continue; + *user++ = '\0'; + if ((cp = index (user, '|')) == NULL) + continue; + *cp = '\0'; + opt = ""; +#ifdef HAVE_RCS5 +#ifdef HAD_RCS4 + opt = "-V4"; +#endif +#endif + if (fprintf (fpout, "/%s/%s/%s/%s/\n", user, rev, ts, opt) == EOF) + error (1, errno, "cannot write %s", CVSADM_ENT); + } + (void) fclose (fpin); + if (fclose (fpout) == EOF) + error (1, errno, "cannot close %s", entname); + + /* clean up any old Files or Mod files */ + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_FILE); + else + (void) strcpy (tmp, CVSADM_FILE); + if (isfile (tmp)) + (void) unlink (tmp); + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_MOD); + else + (void) strcpy (tmp, CVSADM_MOD); + if (isfile (tmp)) + (void) unlink (tmp); + } +} + +/* + * Free up the memory associated with the data section of an ENTRIES type + * node + */ +static void +Entries_delproc (node) + Node *node; +{ + Entnode *p; + + p = (Entnode *) node->data; + free (p->version); + free (p->timestamp); + free (p->options); + if (p->tag) + free (p->tag); + if (p->date) + free (p->date); + free ((char *) p); +} + +/* + * Get an Entries file list node, initialize it, and add it to the specified + * list + */ +static Node * +AddEntryNode (list, name, version, timestamp, options, tag, date) + List *list; + char *name; + char *version; + char *timestamp; + char *options; + char *tag; + char *date; +{ + Node *p; + Entnode *entdata; + + /* get a node and fill in the regular stuff */ + p = getnode (); + p->type = ENTRIES; + p->delproc = Entries_delproc; + + /* this one gets a key of the name for hashing */ + p->key = xstrdup (name); + + /* malloc the data parts and fill them in */ + p->data = xmalloc (sizeof (Entnode)); + entdata = (Entnode *) p->data; + entdata->version = xstrdup (version); + entdata->timestamp = xstrdup (timestamp); + entdata->options = xstrdup (options); + if (entdata->options == NULL) + entdata->options = xstrdup ("");/* must be non-NULL */ + entdata->tag = xstrdup (tag); + entdata->date = xstrdup (date); + + /* put the node into the list */ + if (addnode (list, p) != 0) + error (0, 0, "Duplicate filename in entries file (%s) -- ignored", + name); + + return (p); +} + +/* + * Write out/Clear the CVS/Tag file. + */ +void +WriteTag (dir, tag, date) + char *dir; + char *tag; + char *date; +{ + FILE *fout; + char tmp[PATH_MAX]; + + if (noexec) + return; + + if (dir == NULL) + (void) strcpy (tmp, CVSADM_TAG); + else + (void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG); + + if (tag || date) + { + fout = open_file (tmp, "w+"); + if (tag) + { + if (fprintf (fout, "T%s\n", tag) == EOF) + error (1, errno, "write to %s failed", tmp); + } + else + { + if (fprintf (fout, "D%s\n", date) == EOF) + error (1, errno, "write to %s failed", tmp); + } + if (fclose (fout) == EOF) + error (1, errno, "cannot close %s", tmp); + } + else + (void) unlink_file (tmp); +} + +/* + * Parse the CVS/Tag file for the current directory. + */ +void +ParseTag (tagp, datep) + char **tagp; + char **datep; +{ + FILE *fp; + char line[MAXLINELEN]; + char *cp; + + if (tagp) + *tagp = (char *) NULL; + if (datep) + *datep = (char *) NULL; + fp = fopen (CVSADM_TAG, "r"); + if (fp) + { + if (fgets (line, sizeof (line), fp) != NULL) + { + if ((cp = rindex (line, '\n')) != NULL) + *cp = '\0'; + if (*line == 'T' && tagp) + *tagp = xstrdup (line + 1); + else if (*line == 'D' && datep) + *datep = xstrdup (line + 1); + } + (void) fclose (fp); + } +} diff --git a/gnu/usr.bin/cvs/cvs/find_names.c b/gnu/usr.bin/cvs/cvs/find_names.c new file mode 100644 index 0000000..187bd23 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/find_names.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Find Names + * + * Finds all the pertinent file names, both from the administration and from the + * repository + * + * Find Dirs + * + * Finds all pertinent sub-directories of the checked out instantiation and the + * repository (and optionally the attic) + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)find_names.c 1.38 92/04/10"; +#endif + +#if __STDC__ +static int find_dirs (char *dir, List * list, int checkadm); +static int find_rcs (char *dir, List * list); +#else +static int find_rcs (); +static int find_dirs (); +#endif /* __STDC__ */ + +static List *filelist; + +/* + * add the key from entry on entries list to the files list + */ +static int +add_entries_proc (node) + Node *node; +{ + Node *fnode; + + fnode = getnode (); + fnode->type = FILES; + fnode->key = xstrdup (node->key); + if (addnode (filelist, fnode) != 0) + freenode (fnode); + return (0); +} + +/* + * compare two files list node (for sort) + */ +static int +fsortcmp (p, q) + Node *p, *q; +{ + return (strcmp (p->key, q->key)); +} + +List * +Find_Names (repository, which, aflag, optentries) + char *repository; + int which; + int aflag; + List **optentries; +{ + List *entries; + List *files; + char dir[PATH_MAX]; + + /* make a list for the files */ + files = filelist = getlist (); + + /* look at entries (if necessary) */ + if (which & W_LOCAL) + { + /* parse the entries file (if it exists) */ + entries = ParseEntries (aflag); + + if (entries != NULL) + { + /* walk the entries file adding elements to the files list */ + (void) walklist (entries, add_entries_proc); + + /* if our caller wanted the entries list, return it; else free it */ + if (optentries != NULL) + *optentries = entries; + else + dellist (&entries); + } + } + + if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT)) + { + /* search the repository */ + if (find_rcs (repository, files) != 0) + error (1, errno, "cannot open directory %s", repository); + + /* search the attic too */ + if (which & W_ATTIC) + { + (void) sprintf (dir, "%s/%s", repository, CVSATTIC); + (void) find_rcs (dir, files); + } + } + + /* sort the list into alphabetical order and return it */ + sortlist (files, fsortcmp); + return (files); +} + +/* + * create a list of directories to traverse from the current directory + */ +List * +Find_Dirs (repository, which) + char *repository; + int which; +{ + List *dirlist; + + /* make a list for the directories */ + dirlist = getlist (); + + /* find the local ones */ + if (which & W_LOCAL) + { + /* look only for CVS controlled sub-directories */ + if (find_dirs (".", dirlist, 1) != 0) + error (1, errno, "cannot open current directory"); + } + + /* look for sub-dirs in the repository */ + if ((which & W_REPOS) && repository) + { + /* search the repository */ + if (find_dirs (repository, dirlist, 0) != 0) + error (1, errno, "cannot open directory %s", repository); + +#ifdef ATTIC_DIR_SUPPORT /* XXX - FIXME */ + /* search the attic too */ + if (which & W_ATTIC) + { + char dir[PATH_MAX]; + + (void) sprintf (dir, "%s/%s", repository, CVSATTIC); + (void) find_dirs (dir, dirlist, 0); + } +#endif + } + + /* sort the list into alphabetical order and return it */ + sortlist (dirlist, fsortcmp); + return (dirlist); +} + +/* + * Finds all the ,v files in the argument directory, and adds them to the + * files list. Returns 0 for success and non-zero if the argument directory + * cannot be opened. + */ +static int +find_rcs (dir, list) + char *dir; + List *list; +{ + Node *p; + CONST char *regex_err; + char line[50]; + struct direct *dp; + DIR *dirp; + + /* set up to read the dir */ + if ((dirp = opendir (dir)) == NULL) + return (1); + + /* set up a regular expression to find the ,v files */ + (void) sprintf (line, ".*%s$", RCSEXT); + if ((regex_err = re_comp (line)) != NULL) + error (1, 0, "%s", regex_err); + + /* read the dir, grabbing the ,v files */ + while ((dp = readdir (dirp)) != NULL) + { + if (re_exec (dp->d_name)) + { + char *comma; + + comma = rindex (dp->d_name, ','); /* strip the ,v */ + *comma = '\0'; + p = getnode (); + p->type = FILES; + p->key = xstrdup (dp->d_name); + if (addnode (list, p) != 0) + freenode (p); + } + } + (void) closedir (dirp); + return (0); +} + +/* + * Finds all the subdirectories of the argument dir and adds them to the + * specified list. Sub-directories without a CVS administration directory + * are optionally ignored Returns 0 for success or 1 on error. + */ +static int +find_dirs (dir, list, checkadm) + char *dir; + List *list; + int checkadm; +{ + Node *p; + CONST char *regex_err; + char tmp[PATH_MAX]; + char admdir[PATH_MAX]; + struct direct *dp; + DIR *dirp; + + /* build a regex to blow off ,v files */ + (void) sprintf (tmp, ".*%s$", RCSEXT); + if ((regex_err = re_comp (tmp)) != NULL) + error (1, 0, "%s", regex_err); + + /* set up to read the dir */ + if ((dirp = opendir (dir)) == NULL) + return (1); + + /* read the dir, grabbing sub-dirs */ + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || + strcmp (dp->d_name, "..") == 0 || + strcmp (dp->d_name, CVSATTIC) == 0 || + strcmp (dp->d_name, CVSLCK) == 0 || + re_exec (dp->d_name)) /* don't bother stating ,v files */ + continue; + + (void) sprintf (tmp, "%s/%s", dir, dp->d_name); + if (isdir (tmp)) + { + /* check for administration directories (if needed) */ + if (checkadm) + { + /* blow off symbolic links to dirs in local dir */ + if (islink (tmp)) + continue; + + /* check for new style */ + (void) sprintf (admdir, "%s/%s", tmp, CVSADM); + if (!isdir (admdir)) + { + /* and old style */ + (void) sprintf (admdir, "%s/%s", tmp, OCVSADM); + if (!isdir (admdir)) + continue; + } + } + + /* put it in the list */ + p = getnode (); + p->type = DIRS; + p->key = xstrdup (dp->d_name); + if (addnode (list, p) != 0) + freenode (p); + } + } + (void) closedir (dirp); + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/history.c b/gnu/usr.bin/cvs/cvs/history.c new file mode 100644 index 0000000..9675c4c --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/history.c @@ -0,0 +1,1373 @@ +/* + * + * You may distribute under the terms of the GNU General Public License + * as specified in the README file that comes with the CVS 1.0 kit. + * + * **************** History of Users and Module **************** + * + * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY". + * + * On For each Tag, Add, Checkout, Commit, Update or Release command, + * one line of text is written to a History log. + * + * X date | user | CurDir | special | rev(s) | argument '\n' + * + * where: [The spaces in the example line above are not in the history file.] + * + * X is a single character showing the type of event: + * T "Tag" cmd. + * O "Checkout" cmd. + * F "Release" cmd. + * W "Update" cmd - No User file, Remove from Entries file. + * U "Update" cmd - File was checked out over User file. + * G "Update" cmd - File was merged successfully. + * C "Update" cmd - File was merged and shows overlaps. + * M "Commit" cmd - "Modified" file. + * A "Commit" cmd - "Added" file. + * R "Commit" cmd - "Removed" file. + * + * date is a fixed length 8-char hex representation of a Unix time_t. + * [Starting here, variable fields are delimited by '|' chars.] + * + * user is the username of the person who typed the command. + * + * CurDir The directory where the action occurred. This should be the + * absolute path of the directory which is at the same level as + * the "Repository" field (for W,U,G,C & M,A,R). + * + * Repository For record types [W,U,G,C,M,A,R] this field holds the + * repository read from the administrative data where the + * command was typed. + * T "A" --> New Tag, "D" --> Delete Tag + * Otherwise it is the Tag or Date to modify. + * O,F A "" (null field) + * + * rev(s) Revision number or tag. + * T The Tag to apply. + * O The Tag or Date, if specified, else "" (null field). + * F "" (null field) + * W The Tag or Date, if specified, else "" (null field). + * U The Revision checked out over the User file. + * G,C The Revision(s) involved in merge. + * M,A,R RCS Revision affected. + * + * argument The module (for [TOUF]) or file (for [WUGCMAR]) affected. + * + * + *** Report categories: "User" and "Since" modifiers apply to all reports. + * [For "sort" ordering see the "sort_order" routine.] + * + * Extract list of record types + * + * -e, -x [TOFWUGCMAR] + * + * Extracted records are simply printed, No analysis is performed. + * All "field" modifiers apply. -e chooses all types. + * + * Checked 'O'ut modules + * + * -o, -w + * Checked out modules. 'F' and 'O' records are examined and if + * the last record for a repository/file is an 'O', a line is + * printed. "-w" forces the "working dir" to be used in the + * comparison instead of the repository. + * + * Committed (Modified) files + * + * -c, -l, -w + * All 'M'odified, 'A'dded and 'R'emoved records are examined. + * "Field" modifiers apply. -l forces a sort by file within user + * and shows only the last modifier. -w works as in Checkout. + * + * Warning: Be careful with what you infer from the output of + * "cvs hi -c -l". It means the last time *you* + * changed the file, not the list of files for which + * you were the last changer!!! + * + * Module history for named modules. + * -m module, -l + * + * This is special. If one or more modules are specified, the + * module names are remembered and the files making up the + * modules are remembered. Only records matching exactly those + * files and repositories are shown. Sorting by "module", then + * filename, is implied. If -l ("last modified") is specified, + * then "update" records (types WUCG), tag and release records + * are ignored and the last (by date) "modified" record. + * + * TAG history + * + * -T All Tag records are displayed. + * + *** Modifiers. + * + * Since ... [All records contain a timestamp, so any report + * category can be limited by date.] + * + * -D date - The "date" is parsed into a Unix "time_t" and + * records with an earlier time stamp are ignored. + * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If + * you use this option, every file is searched for the + * indicated rev/tag. + * -t tag - The "tag" is searched for in the history file and no + * record is displayed before the tag is found. An + * error is printed if the tag is never found. + * -b string - Records are printed only back to the last reference + * to the string in the "module", "file" or + * "repository" fields. + * + * Field Selections [Simple comparisons on existing fields. All field + * selections are repeatable.] + * + * -a - All users. + * -u user - If no user is given and '-a' is not given, only + * records for the user typing the command are shown. + * ==> If -a or -u is not specified, just use "self". + * + * -f filematch - Only records in which the "file" field contains the + * string "filematch" are considered. + * + * -p repository - Only records in which the "repository" string is a + * prefix of the "repos" field are considered. + * + * -m modulename - Only records which contain "modulename" in the + * "module" field are considered. + * + * + * EXAMPLES: ("cvs history", "cvs his" or "cvs hi") + * + *** Checked out files for username. (default self, e.g. "dgg") + * cvs hi [equivalent to: "cvs hi -o -u dgg"] + * cvs hi -u user [equivalent to: "cvs hi -o -u user"] + * cvs hi -o [equivalent to: "cvs hi -o -u dgg"] + * + *** Committed (modified) files from the beginning of the file. + * cvs hi -c [-u user] + * + *** Committed (modified) files since Midnight, January 1, 1990: + * cvs hi -c -D 'Jan 1 1990' [-u user] + * + *** Committed (modified) files since tag "TAG" was stored in the history file: + * cvs hi -c -t TAG [-u user] + * + *** Committed (modified) files since tag "TAG" was placed on the files: + * cvs hi -c -r TAG [-u user] + * + *** Who last committed file/repository X? + * cvs hi -c -l -[fp] X + * + *** Modified files since tag/date/file/repos? + * cvs hi -c {-r TAG | -D Date | -b string} + * + *** Tag history + * cvs hi -T + * + *** History of file/repository/module X. + * cvs hi -[fpn] X + * + *** History of user "user". + * cvs hi -e -u user + * + *** Dump (eXtract) specified record types + * cvs hi -x [TOFWUGCMAR] + * + * + * FUTURE: J[Join], I[Import] (Not currently implemented.) + * + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)history.c 1.31 92/04/10"; +#endif + +static struct hrec +{ + char *type; /* Type of record (In history record) */ + char *user; /* Username (In history record) */ + char *dir; /* "Compressed" Working dir (In history record) */ + char *repos; /* (Tag is special.) Repository (In history record) */ + char *rev; /* Revision affected (In history record) */ + char *file; /* Filename (In history record) */ + char *end; /* Ptr into repository to copy at end of workdir */ + char *mod; /* The module within which the file is contained */ + time_t date; /* Calculated from date stored in record */ + int idx; /* Index of record, for "stable" sort. */ +} *hrec_head; + + +#if __STDC__ +static char *fill_hrec (char *line, struct hrec * hr); +static int accept_hrec (struct hrec * hr, struct hrec * lr); +static int select_hrec (struct hrec * hr); +static int sort_order (CONST PTR l, CONST PTR r); +static int within (char *find, char *string); +static time_t date_and_time (char *date_str); +static void expand_modules (void); +static void read_hrecs (char *fname); +static void report_hrecs (void); +static void save_file (char *dir, char *name, char *module); +static void save_module (char *module); +static void save_user (char *name); +#else +static int sort_order (); +static time_t date_and_time (); +static void save_user (); +static void save_file (); +static void save_module (); +static void expand_modules (); +static char *fill_hrec (); +static void read_hrecs (); +static int within (); +static int select_hrec (); +static void report_hrecs (); +static int accept_hrec (); +#endif /* __STDC__ */ + +#define ALL_REC_TYPES "TOFWUCGMAR" +#define USER_INCREMENT 2 +#define FILE_INCREMENT 128 +#define MODULE_INCREMENT 5 +#define HREC_INCREMENT 128 + +static short report_count; + +static short extract; +static short v_checkout; +static short modified; +static short tag_report; +static short module_report; +static short working; +static short last_entry; +static short all_users; + +static short user_sort; +static short repos_sort; +static short file_sort; +static short module_sort; + +static time_t since_date; +static char since_rev[20]; /* Maxrev ~= 99.99.99.999 */ +static char since_tag[64]; +static struct hrec *last_since_tag; +static char backto[128]; +static struct hrec *last_backto; +static char rec_types[20]; + +static int hrec_count; +static int hrec_max; + +static char **user_list; /* Ptr to array of ptrs to user names */ +static int user_max; /* Number of elements allocated */ +static int user_count; /* Number of elements used */ + +static struct file_list_str +{ + char *l_file; + char *l_module; +} *file_list; /* Ptr to array file name structs */ +static int file_max; /* Number of elements allocated */ +static int file_count; /* Number of elements used */ + +static char **mod_list; /* Ptr to array of ptrs to module names */ +static int mod_max; /* Number of elements allocated */ +static int mod_count; /* Number of elements used */ + +static int histsize; +static char *histdata; +static char *histfile; /* Ptr to the history file name */ + +static char *history_usg[] = +{ + "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n", + " Reports:\n", + " -T Produce report on all TAGs\n", + " -c Committed (Modified) files\n", + " -o Checked out modules\n", + " -m <module> Look for specified module (repeatable)\n", + " -x [TOFWUCGMAR] Extract by record type\n", + " Flags:\n", + " -a All users (Default is self)\n", + " -e Everything (same as -x, but all record types)\n", + " -l Last modified (committed or modified report)\n", + " -w Working directory must match\n", + " Options:\n", + " -D <date> Since date (Many formats)\n", + " -b <str> Back to record with str in module/file/repos field\n", + " -f <file> Specified file (same as command line) (repeatable)\n", + " -n <modulename> In module (repeatable)\n", + " -p <repos> In repository (repeatable)\n", + " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n", + " -t <tag> Since tag record placed in history file (by anyone).\n", + " -u <user> For user name (repeatable)\n", + NULL}; + +/* Sort routine for qsort: + - If a user is selected at all, sort it first. User-within-file is useless. + - If a module was selected explicitly, sort next on module. + - Then sort by file. "File" is "repository/file" unless "working" is set, + then it is "workdir/file". (Revision order should always track date.) + - Always sort timestamp last. +*/ +static int +sort_order (l, r) + CONST PTR l; + CONST PTR r; +{ + int i; + CONST struct hrec *left = (CONST struct hrec *) l; + CONST struct hrec *right = (CONST struct hrec *) r; + + if (user_sort) /* If Sort by username, compare users */ + { + if ((i = strcmp (left->user, right->user)) != 0) + return (i); + } + if (module_sort) /* If sort by modules, compare module names */ + { + if (left->mod && right->mod) + if ((i = strcmp (left->mod, right->mod)) != 0) + return (i); + } + if (repos_sort) /* If sort by repository, compare them. */ + { + if ((i = strcmp (left->repos, right->repos)) != 0) + return (i); + } + if (file_sort) /* If sort by filename, compare files, NOT dirs. */ + { + if ((i = strcmp (left->file, right->file)) != 0) + return (i); + + if (working) + { + if ((i = strcmp (left->dir, right->dir)) != 0) + return (i); + + if ((i = strcmp (left->end, right->end)) != 0) + return (i); + } + } + + /* + * By default, sort by date, time + * XXX: This fails after 2030 when date slides into sign bit + */ + if ((i = ((long) (left->date) - (long) (right->date))) != 0) + return (i); + + /* For matching dates, keep the sort stable by using record index */ + return (left->idx - right->idx); +} + +static time_t +date_and_time (date_str) + char *date_str; +{ + time_t t; + + t = get_date (date_str, (struct timeb *) NULL); + if (t == (time_t) - 1) + error (1, 0, "Can't parse date/time: %s", date_str); + return (t); +} + +int +history (argc, argv) + int argc; + char **argv; +{ + int i, c; + char fname[PATH_MAX]; + + if (argc == -1) + usage (history_usg); + + optind = 1; + while ((c = gnu_getopt (argc, argv, "Tacelow?D:b:f:m:n:p:r:t:u:x:X:")) != -1) + { + switch (c) + { + case 'T': /* Tag list */ + report_count++; + tag_report++; + break; + case 'a': /* For all usernames */ + all_users++; + break; + case 'c': + report_count++; + modified = 1; + break; + case 'e': + report_count++; + extract++; + (void) strcpy (rec_types, ALL_REC_TYPES); + break; + case 'l': /* Find Last file record */ + last_entry = 1; + break; + case 'o': + report_count++; + v_checkout = 1; + break; + case 'w': /* Match Working Dir (CurDir) fields */ + working = 1; + break; + case 'X': /* Undocumented debugging flag */ + histfile = optarg; + break; + case 'D': /* Since specified date */ + if (*since_rev || *since_tag || *backto) + { + error (0, 0, "date overriding rev/tag/backto"); + *since_rev = *since_tag = *backto = '\0'; + } + since_date = date_and_time (optarg); + break; + case 'b': /* Since specified file/Repos */ + if (since_date || *since_rev || *since_tag) + { + error (0, 0, "backto overriding date/rev/tag"); + *since_rev = *since_tag = '\0'; + since_date = 0; + } + if (strlen (optarg) >= sizeof (backto)) + { + error (0, 0, "backto truncated to %d bytes", + sizeof (backto) - 1); + optarg[sizeof (backto) - 1] = '\0'; + } + (void) strcpy (backto, optarg); + break; + case 'f': /* For specified file */ + save_file ("", optarg, (char *) NULL); + break; + case 'm': /* Full module report */ + report_count++; + module_report++; + case 'n': /* Look for specified module */ + save_module (optarg); + break; + case 'p': /* For specified directory */ + save_file (optarg, "", (char *) NULL); + break; + case 'r': /* Since specified Tag/Rev */ + if (since_date || *since_tag || *backto) + { + error (0, 0, "rev overriding date/tag/backto"); + *since_tag = *backto = '\0'; + since_date = 0; + } + (void) strcpy (since_rev, optarg); + break; + case 't': /* Since specified Tag/Rev */ + if (since_date || *since_rev || *backto) + { + error (0, 0, "tag overriding date/marker/file/repos"); + *since_rev = *backto = '\0'; + since_date = 0; + } + (void) strcpy (since_tag, optarg); /* tag */ + break; + case 'u': /* For specified username */ + save_user (optarg); + break; + case 'x': + report_count++; + extract++; + { + char *cp; + + for (cp = optarg; *cp; cp++) + if (!index (ALL_REC_TYPES, *cp)) + error (1, 0, "%c is not a valid report type", cp); + } + (void) strcpy (rec_types, optarg); + break; + case '?': + default: + usage (history_usg); + break; + } + } + c = optind; /* Save the handled option count */ + + /* ================ Now analyze the arguments a bit */ + if (!report_count) + v_checkout++; + else if (report_count > 1) + error (1, 0, "Only one report type allowed from: \"-Tcomx\"."); + + if (all_users) + save_user (""); + + if (mod_list) + expand_modules (); + + if (tag_report) + { + if (!index (rec_types, 'T')) + (void) strcat (rec_types, "T"); + } + else if (extract) + { + if (user_list) + user_sort++; + } + else if (modified) + { + (void) strcpy (rec_types, "MAR"); + /* + * If the user has not specified a date oriented flag ("Since"), sort + * by Repository/file before date. Default is "just" date. + */ + if (!since_date && !*since_rev && !*since_tag && !*backto) + { + repos_sort++; + file_sort++; + /* + * If we are not looking for last_modified and the user specified + * one or more users to look at, sort by user before filename. + */ + if (!last_entry && user_list) + user_sort++; + } + } + else if (module_report) + { + (void) strcpy (rec_types, last_entry ? "OMAR" : ALL_REC_TYPES); + module_sort++; + repos_sort++; + file_sort++; + working = 0; /* User's workdir doesn't count here */ + } + else + /* Must be "checkout" or default */ + { + (void) strcpy (rec_types, "OF"); + /* See comments in "modified" above */ + if (!last_entry && user_list) + user_sort++; + if (!since_date && !*since_rev && !*since_tag && !*backto) + file_sort++; + } + + /* If no users were specified, use self (-a saves a universal ("") user) */ + if (!user_list) + save_user (getcaller ()); + + /* If we're looking back to a Tag value, must consider "Tag" records */ + if (*since_tag && !index (rec_types, 'T')) + (void) strcat (rec_types, "T"); + + argc -= c; + argv += c; + for (i = 0; i < argc; i++) + save_file ("", argv[i], (char *) NULL); + + if (histfile) + (void) strcpy (fname, histfile); + else + (void) sprintf (fname, "%s/%s/%s", CVSroot, + CVSROOTADM, CVSROOTADM_HISTORY); + + read_hrecs (fname); + qsort ((PTR) hrec_head, hrec_count, sizeof (struct hrec), sort_order); + report_hrecs (); + + return (0); +} + +void +history_write (type, update_dir, revs, name, repository) + int type; + char *update_dir; + char *revs; + char *name; + char *repository; +{ + char fname[PATH_MAX], workdir[PATH_MAX], homedir[PATH_MAX]; + static char username[20]; /* !!! Should be global */ + FILE *fp; + char *slash = "", *cp, *cp2, *repos; + int i; + static char *tilde = ""; + static char *PrCurDir = NULL; + + if (logoff) /* History is turned off by cmd line switch */ + return; + (void) sprintf (fname, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_HISTORY); + + /* turn off history logging if the history file does not exist */ + if (!isfile (fname)) + { + logoff = 1; + return; + } + + if (!(fp = Fopen (fname, "a"))) /* Some directory not there! */ + return; + + repos = Short_Repository (repository); + + if (!PrCurDir) + { + struct passwd *pw; + + (void) strcpy (username, getcaller ()); + PrCurDir = CurDir; + if (!(pw = (struct passwd *) getpwnam (username))) + error (0, 0, "cannot find own username"); + else + { + /* Assumes neither CurDir nor pw->pw_dir ends in '/' */ + i = strlen (pw->pw_dir); + if (!strncmp (CurDir, pw->pw_dir, i)) + { + PrCurDir += i; /* Point to '/' separator */ + tilde = "~"; + } + else + { + /* Try harder to find a "homedir" */ + if (!getwd (workdir)) + error (1, errno, "can't getwd in history"); + if (chdir (pw->pw_dir) < 0) + error (1, errno, "can't chdir(%s)", pw->pw_dir); + if (!getwd (homedir)) + error (1, errno, "can't getwd in:", pw->pw_dir); + (void) chdir (workdir); + + i = strlen (homedir); + if (!strncmp (CurDir, homedir, i)) + { + PrCurDir += i; /* Point to '/' separator */ + tilde = "~"; + } + } + } + } + + if (type == 'T') + { + repos = update_dir; + update_dir = ""; + } + else if (update_dir && *update_dir) + slash = "/"; + else + update_dir = ""; + + (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir); + + /* + * "workdir" is the directory where the file "name" is. ("^~" == $HOME) + * "repos" is the Repository, relative to $CVSROOT where the RCS file is. + * + * "$workdir/$name" is the working file name. + * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository. + * + * First, note that the history format was intended to save space, not + * to be human readable. + * + * The working file directory ("workdir") and the Repository ("repos") + * usually end with the same one or more directory elements. To avoid + * duplication (and save space), the "workdir" field ends with + * an integer offset into the "repos" field. This offset indicates the + * beginning of the "tail" of "repos", after which all characters are + * duplicates. + * + * In other words, if the "workdir" field has a '*' (a very stupid thing + * to put in a filename) in it, then every thing following the last '*' + * is a hex offset into "repos" of the first character from "repos" to + * append to "workdir" to finish the pathname. + * + * It might be easier to look at an example: + * + * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo + * + * Indicates that the workdir is really "~/work/cvs/examples", saving + * 10 characters, where "~/work*d" would save 6 characters and mean that + * the workdir is really "~/work/examples". It will mean more on + * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term + * + * "workdir" is always an absolute pathname (~/xxx is an absolute path) + * "repos" is always a relative pathname. So we can assume that we will + * never run into the top of "workdir" -- there will always be a '/' or + * a '~' at the head of "workdir" that is not matched by anything in + * "repos". On the other hand, we *can* run off the top of "repos". + * + * Only "compress" if we save characters. + */ + + if (!repos) + repos = ""; + + cp = workdir + strlen (workdir) - 1; + cp2 = repos + strlen (repos) - 1; + for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--) + i++; + + if (i > 2) + { + i = strlen (repos) - i; + (void) sprintf ((cp + 1), "*%x", i); + } + + if (fprintf (fp, "%c%08x|%s|%s|%s|%s|%s\n", type, time ((time_t *) NULL), + username, workdir, repos, revs ? revs : "", name) == EOF) + error (1, errno, "cannot write to history file: %s", fname); + (void) fclose (fp); +} + +/* + * save_user() adds a user name to the user list to select. Zero-length + * username ("") matches any user. + */ +static void +save_user (name) + char *name; +{ + if (user_count == user_max) + { + user_max += USER_INCREMENT; + user_list = (char **) xrealloc ((char *) user_list, + (int) user_max * sizeof (char *)); + } + user_list[user_count++] = xstrdup (name); +} + +/* + * save_file() adds file name and associated module to the file list to select. + * + * If "dir" is null, store a file name as is. + * If "name" is null, store a directory name with a '*' on the front. + * Else, store concatenated "dir/name". + * + * Later, in the "select" stage: + * - if it starts with '*', it is prefix-matched against the repository. + * - if it has a '/' in it, it is matched against the repository/file. + * - else it is matched against the file name. + */ +static void +save_file (dir, name, module) + char *dir; + char *name; + char *module; +{ + char *cp; + struct file_list_str *fl; + + if (file_count == file_max) + { + file_max += FILE_INCREMENT; + file_list = (struct file_list_str *) xrealloc ((char *) file_list, + file_max * sizeof (*fl)); + } + fl = &file_list[file_count++]; + fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2); + fl->l_module = module; + + if (dir && *dir) + { + if (name && *name) + { + (void) strcpy (cp, dir); + (void) strcat (cp, "/"); + (void) strcat (cp, name); + } + else + { + *cp++ = '*'; + (void) strcpy (cp, dir); + } + } + else + { + if (name && *name) + { + (void) strcpy (cp, name); + } + else + { + error (0, 0, "save_file: null dir and file name"); + } + } +} + +static void +save_module (module) + char *module; +{ + if (mod_count == mod_max) + { + mod_max += MODULE_INCREMENT; + mod_list = (char **) xrealloc ((char *) mod_list, + mod_max * sizeof (char *)); + } + mod_list[mod_count++] = xstrdup (module); +} + +static void +expand_modules () +{ +} + +/* fill_hrec + * + * Take a ptr to 7-part history line, ending with a newline, for example: + * + * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo + * + * Split it into 7 parts and drop the parts into a "struct hrec". + * Return a pointer to the character following the newline. + */ + +#define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return(rtn); *(line - 1) = '\0'; } while (0) + +static char * +fill_hrec (line, hr) + char *line; + struct hrec *hr; +{ + char *cp, *rtn; + int c; + int off; + static int idx = 0; + + bzero ((char *) hr, sizeof (*hr)); + while (isspace (*line)) + line++; + if (!(rtn = index (line, '\n'))) + return (""); + *rtn++ = '\0'; + + hr->type = line++; + (void) sscanf (line, "%x", &hr->date); + while (*line && index ("0123456789abcdefABCDEF", *line)) + line++; + if (*line == '\0') + return (rtn); + + line++; + NEXT_BAR (user); + NEXT_BAR (dir); + if ((cp = rindex (hr->dir, '*')) != NULL) + { + *cp++ = '\0'; + (void) sscanf (cp, "%x", &off); + hr->end = line + off; + } + else + hr->end = line - 1; /* A handy pointer to '\0' */ + NEXT_BAR (repos); + NEXT_BAR (rev); + hr->idx = idx++; + if (index ("FOT", *(hr->type))) + hr->mod = line; + + NEXT_BAR (file); /* This returns ptr to next line or final '\0' */ + return (rtn); /* If it falls through, go on to next record */ +} + +/* read_hrecs's job is to read the history file and fill in all the "hrec" + * (history record) array elements with the ones we need to print. + * + * Logic: + * - Read the whole history file into a single buffer. + * - Walk through the buffer, parsing lines out of the buffer. + * 1. Split line into pointer and integer fields in the "next" hrec. + * 2. Apply tests to the hrec to see if it is wanted. + * 3. If it *is* wanted, bump the hrec pointer down by one. + */ +static void +read_hrecs (fname) + char *fname; +{ + char *cp, *cp2; + int i, fd; + struct hrec *hr; + struct stat st_buf; + + if ((fd = open (fname, O_RDONLY)) < 0) + error (1, errno, "cannot open history file: %s", fname); + + if (fstat (fd, &st_buf) < 0) + error (1, errno, "can't stat history file"); + + /* Exactly enough space for lines data */ + if (!(i = st_buf.st_size)) + error (1, 0, "history file is empty"); + histdata = cp = xmalloc (i + 2); + histsize = i; + + if (read (fd, cp, i) != i) + error (1, errno, "cannot read log file"); + (void) close (fd); + + if (*(cp + i - 1) != '\n') + { + *(cp + i) = '\n'; /* Make sure last line ends in '\n' */ + i++; + } + *(cp + i) = '\0'; + for (cp2 = cp; cp2 - cp < i; cp2++) + { + if (*cp2 != '\n' && !isprint (*cp2)) + *cp2 = ' '; + } + + hrec_max = HREC_INCREMENT; + hrec_head = (struct hrec *) xmalloc (hrec_max * sizeof (struct hrec)); + + while (*cp) + { + if (hrec_count == hrec_max) + { + struct hrec *old_head = hrec_head; + + hrec_max += HREC_INCREMENT; + hrec_head = (struct hrec *) xrealloc ((char *) hrec_head, + hrec_max * sizeof (struct hrec)); + if (hrec_head != old_head) + { + if (last_since_tag) + last_since_tag = hrec_head + (last_since_tag - old_head); + if (last_backto) + last_backto = hrec_head + (last_backto - old_head); + } + } + + hr = hrec_head + hrec_count; + cp = fill_hrec (cp, hr); /* cp == next line or '\0' at end of buffer */ + + if (select_hrec (hr)) + hrec_count++; + } + + /* Special selection problem: If "since_tag" is set, we have saved every + * record from the 1st occurrence of "since_tag", when we want to save + * records since the *last* occurrence of "since_tag". So what we have + * to do is bump hrec_head forward and reduce hrec_count accordingly. + */ + if (last_since_tag) + { + hrec_count -= (last_since_tag - hrec_head); + hrec_head = last_since_tag; + } + + /* Much the same thing is necessary for the "backto" option. */ + if (last_backto) + { + hrec_count -= (last_backto - hrec_head); + hrec_head = last_backto; + } +} + +/* Utility program for determining whether "find" is inside "string" */ +static int +within (find, string) + char *find, *string; +{ + int c, len; + + if (!find || !string) + return (0); + + c = *find++; + len = strlen (find); + + while (*string) + { + if (!(string = index (string, c))) + return (0); + string++; + if (!strncmp (find, string, len)) + return (1); + } + return (0); +} + +/* The purpose of "select_hrec" is to apply the selection criteria based on + * the command arguments and defaults and return a flag indicating whether + * this record should be remembered for printing. + */ +static int +select_hrec (hr) + struct hrec *hr; +{ + char **cpp, *cp, *cp2; + struct file_list_str *fl; + int count; + + /* "Since" checking: The argument parser guarantees that only one of the + * following four choices is set: + * + * 1. If "since_date" is set, it contains a Unix time_t specified on the + * command line. hr->date fields earlier than "since_date" are ignored. + * 2. If "since_rev" is set, it contains either an RCS "dotted" revision + * number (which is of limited use) or a symbolic TAG. Each RCS file + * is examined and the date on the specified revision (or the revision + * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is + * compared against hr->date as in 1. above. + * 3. If "since_tag" is set, matching tag records are saved. The field + * "last_since_tag" is set to the last one of these. Since we don't + * know where the last one will be, all records are saved from the + * first occurrence of the TAG. Later, at the end of "select_hrec" + * records before the last occurrence of "since_tag" are skipped. + * 4. If "backto" is set, all records with a module name or file name + * matching "backto" are saved. In addition, all records with a + * repository field with a *prefix* matching "backto" are saved. + * The field "last_backto" is set to the last one of these. As in + * 3. above, "select_hrec" adjusts to include the last one later on. + */ + if (since_date) + { + if (hr->date < since_date) + return (0); + } + else if (*since_rev) + { + Vers_TS *vers; + time_t t; + + vers = Version_TS (hr->repos, (char *) NULL, since_rev, (char *) NULL, + hr->file, 1, 0, (List *) NULL, (List *) NULL); + if (vers->vn_rcs) + { + if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0)) + != (time_t) 0) + { + if (hr->date < t) + { + freevers_ts (&vers); + return (0); + } + } + } + freevers_ts (&vers); + } + else if (*since_tag) + { + if (*(hr->type) == 'T') + { + /* + * A 'T'ag record, the "rev" field holds the tag to be set, + * while the "repos" field holds "D"elete, "A"dd or a rev. + */ + if (within (since_tag, hr->rev)) + { + last_since_tag = hr; + return (1); + } + else + return (0); + } + if (!last_since_tag) + return (0); + } + else if (*backto) + { + if (within (backto, hr->file) || within (backto, hr->mod) || + within (backto, hr->repos)) + last_backto = hr; + else + return (0); + } + + /* User checking: + * + * Run down "user_list", match username ("" matches anything) + * If "" is not there and actual username is not there, return failure. + */ + if (user_list && hr->user) + { + for (cpp = user_list, count = user_count; count; cpp++, count--) + { + if (!**cpp) + break; /* null user == accept */ + if (!strcmp (hr->user, *cpp)) /* found listed user */ + break; + } + if (!count) + return (0); /* Not this user */ + } + + /* Record type checking: + * + * 1. If Record type is not in rec_types field, skip it. + * 2. If mod_list is null, keep everything. Otherwise keep only modules + * on mod_list. + * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If + * file_list is null, keep everything. Otherwise, keep only files on + * file_list, matched appropriately. + */ + if (!index (rec_types, *(hr->type))) + return (0); + if (!index ("TFO", *(hr->type))) /* Don't bother with "file" if "TFO" */ + { + if (file_list) /* If file_list is null, accept all */ + { + for (fl = file_list, count = file_count; count; fl++, count--) + { + /* 1. If file_list entry starts with '*', skip the '*' and + * compare it against the repository in the hrec. + * 2. If file_list entry has a '/' in it, compare it against + * the concatenation of the repository and file from hrec. + * 3. Else compare the file_list entry against the hrec file. + */ + char cmpfile[PATH_MAX]; + + if (*(cp = fl->l_file) == '*') + { + cp++; + /* if argument to -p is a prefix of repository */ + if (!strncmp (cp, hr->repos, strlen (cp))) + { + hr->mod = fl->l_module; + break; + } + } + else + { + if (index (cp, '/')) + { + (void) sprintf (cp2 = cmpfile, "%s/%s", + hr->repos, hr->file); + } + else + { + cp2 = hr->file; + } + + /* if requested file is found within {repos}/file fields */ + if (within (cp, cp2)) + { + hr->mod = fl->l_module; + break; + } + } + } + if (!count) + return (0); /* String specified and no match */ + } + } + if (mod_list) + { + for (cpp = mod_list, count = mod_count; count; cpp++, count--) + { + if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */ + break; + } + if (!count) + return (0); /* Module specified & this record is not one of them. */ + } + + return (1); /* Select this record unless rejected above. */ +} + +/* The "sort_order" routine (when handed to qsort) has arranged for the + * hrecs files to be in the right order for the report. + * + * Most of the "selections" are done in the select_hrec routine, but some + * selections are more easily done after the qsort by "accept_hrec". + */ +static void +report_hrecs () +{ + struct hrec *hr, *lr; + struct tm *tm; + int i, count, ty; + char *cp; + int user_len, file_len, rev_len, mod_len, repos_len; + + if (*since_tag && !last_since_tag) + { + (void) printf ("No tag found: %s\n", since_tag); + return; + } + else if (*backto && !last_backto) + { + (void) printf ("No module, file or repository with: %s\n", backto); + return; + } + else if (hrec_count < 1) + { + (void) printf ("No records selected.\n"); + return; + } + + user_len = file_len = rev_len = mod_len = repos_len = 0; + + /* Run through lists and find maximum field widths */ + hr = lr = hrec_head; + hr++; + for (count = hrec_count; count--; lr = hr, hr++) + { + char repos[PATH_MAX]; + + if (!count) + hr = NULL; + if (!accept_hrec (lr, hr)) + continue; + + ty = *(lr->type); + (void) strcpy (repos, lr->repos); + if ((cp = rindex (repos, '/')) != NULL) + { + if (lr->mod && !strcmp (++cp, lr->mod)) + { + (void) strcpy (cp, "*"); + } + } + if ((i = strlen (lr->user)) > user_len) + user_len = i; + if ((i = strlen (lr->file)) > file_len) + file_len = i; + if (ty != 'T' && (i = strlen (repos)) > repos_len) + repos_len = i; + if (ty != 'T' && (i = strlen (lr->rev)) > rev_len) + rev_len = i; + if (lr->mod && (i = strlen (lr->mod)) > mod_len) + mod_len = i; + } + + /* Walk through hrec array setting "lr" (Last Record) to each element. + * "hr" points to the record following "lr" -- It is NULL in the last + * pass. + * + * There are two sections in the loop below: + * 1. Based on the report type (e.g. extract, checkout, tag, etc.), + * decide whether the record should be printed. + * 2. Based on the record type, format and print the data. + */ + for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++) + { + char workdir[PATH_MAX], repos[PATH_MAX]; + + if (!hrec_count) + hr = NULL; + if (!accept_hrec (lr, hr)) + continue; + + ty = *(lr->type); + tm = localtime (&(lr->date)); + (void) printf ("%c %02d/%02d %02d:%02d %-*s", ty, tm->tm_mon + 1, + tm->tm_mday, tm->tm_hour, tm->tm_min, user_len, lr->user); + + (void) sprintf (workdir, "%s%s", lr->dir, lr->end); + if ((cp = rindex (workdir, '/')) != NULL) + { + if (lr->mod && !strcmp (++cp, lr->mod)) + { + (void) strcpy (cp, "*"); + } + } + (void) strcpy (repos, lr->repos); + if ((cp = rindex (repos, '/')) != NULL) + { + if (lr->mod && !strcmp (++cp, lr->mod)) + { + (void) strcpy (cp, "*"); + } + } + + switch (ty) + { + case 'T': + /* 'T'ag records: repository is a "tag type", rev is the tag */ + (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev, + repos); + if (working) + (void) printf (" {%s}", workdir); + break; + case 'F': + case 'O': + if (lr->rev && *(lr->rev)) + (void) printf (" [%s]", lr->rev); + (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod, + mod_len + 1 - strlen (lr->mod), "=", workdir); + break; + case 'W': + case 'U': + case 'C': + case 'G': + case 'M': + case 'A': + case 'R': + (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev, + file_len, lr->file, repos_len, repos, + lr->mod ? lr->mod : "", workdir); + break; + default: + (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty); + break; + } + (void) putchar ('\n'); + } +} + +static int +accept_hrec (lr, hr) + struct hrec *hr, *lr; +{ + int ty; + + ty = *(lr->type); + + if (last_since_tag && ty == 'T') + return (1); + + if (v_checkout) + { + if (ty != 'O') + return (0); /* Only interested in 'O' records */ + + /* We want to identify all the states that cause the next record + * ("hr") to be different from the current one ("lr") and only + * print a line at the allowed boundaries. + */ + + if (!hr || /* The last record */ + strcmp (hr->user, lr->user) || /* User has changed */ + strcmp (hr->mod, lr->mod) ||/* Module has changed */ + (working && /* If must match "workdir" */ + (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */ + strcmp (hr->end, lr->end)))) /* the 2nd parts differ */ + + return (1); + } + else if (modified) + { + if (!last_entry || /* Don't want only last rec */ + !hr || /* Last entry is a "last entry" */ + strcmp (hr->repos, lr->repos) || /* Repository has changed */ + strcmp (hr->file, lr->file))/* File has changed */ + return (1); + + if (working) + { /* If must match "workdir" */ + if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */ + strcmp (hr->end, lr->end)) /* the 2nd parts differ */ + return (1); + } + } + else if (module_report) + { + if (!last_entry || /* Don't want only last rec */ + !hr || /* Last entry is a "last entry" */ + strcmp (hr->mod, lr->mod) ||/* Module has changed */ + strcmp (hr->repos, lr->repos) || /* Repository has changed */ + strcmp (hr->file, lr->file))/* File has changed */ + return (1); + } + else + { + /* "extract" and "tag_report" always print selected records. */ + return (1); + } + + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/ignore.c b/gnu/usr.bin/cvs/cvs/ignore.c new file mode 100644 index 0000000..74ab90c --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/ignore.c @@ -0,0 +1,227 @@ +/* + * .cvsignore file support contributed by David G. Grubbs <dgg@ksr.com> + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)ignore.c 1.13 92/04/03"; +#endif + +/* + * Ignore file section. + * + * "!" may be included any time to reset the list (i.e. ignore nothing); + * "*" may be specified to ignore everything. It stays as the first + * element forever, unless a "!" clears it out. + */ + +static char **ign_list; /* List of files to ignore in update + * and import */ +static char **s_ign_list = NULL; +static int ign_count; /* Number of active entries */ +static int s_ign_count = 0; +static int ign_size; /* This many slots available (plus + * one for a NULL) */ +static int ign_hold; /* Index where first "temporary" item + * is held */ + +char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state .nse_depinfo #* .#* cvslog.* ,* CVS* .del-* *.a *.o *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej"; + +#define IGN_GROW 16 /* grow the list by 16 elements at a + * time */ + +/* + * To the "ignore list", add the hard-coded default ignored wildcards above, + * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in + * ~/.cvsignore and the wildcards found in the CVSIGNORE environment + * variable. + */ +void +ign_setup () +{ + extern char *getenv (); + struct passwd *pw; + char file[PATH_MAX]; + char *tmp; + + /* Start with default list and special case */ + tmp = xstrdup (ign_default); + ign_add (tmp, 0); + free (tmp); + + /* Then add entries found in repository, if it exists */ + (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_IGNORE); + if (isfile (file)) + ign_add_file (file, 0); + + /* Then add entries found in home dir, (if user has one) and file exists */ + if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir) + { + (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTIGNORE); + if (isfile (file)) + ign_add_file (file, 0); + } + + /* Then add entries found in CVSIGNORE environment variable. */ + ign_add (getenv (IGNORE_ENV), 0); + + /* Later, add ignore entries found in -I arguments */ +} + +/* + * Open a file and read lines, feeding each line to a line parser. Arrange + * for keeping a temporary list of wildcards at the end, if the "hold" + * argument is set. + */ +void +ign_add_file (file, hold) + char *file; + int hold; +{ + FILE *fp; + char line[1024]; + + /* restore the saved list (if any) */ + if (s_ign_list != NULL) + { + int i; + + for (i = 0; i < s_ign_count; i++) + ign_list[i] = s_ign_list[i]; + ign_count = s_ign_count; + ign_list[ign_count] = NULL; + + s_ign_count = 0; + free (s_ign_list); + s_ign_list = NULL; + } + + /* is this a temporary ignore file? */ + if (hold) + { + /* re-set if we had already done a temporary file */ + if (ign_hold) + { + int i; + + for (i = ign_hold; i < ign_count; i++) + free (ign_list[i]); + ign_count = ign_hold; + ign_list[ign_count] = NULL; + } + else + { + ign_hold = ign_count; + } + } + + /* load the file */ + if (!(fp = fopen (file, "r"))) + return; + while (fgets (line, sizeof (line), fp)) + ign_add (line, hold); + (void) fclose (fp); +} + +/* Parse a line of space-separated wildcards and add them to the list. */ +void +ign_add (ign, hold) + char *ign; + int hold; +{ + if (!ign || !*ign) + return; + + for (; *ign; ign++) + { + char *mark; + char save; + + /* ignore whitespace before the token */ + if (isspace (*ign)) + continue; + + /* + * if we find a single character !, we must re-set the ignore list + * (saving it if necessary). We also catch * as a special case in a + * global ignore file as an optimization + */ + if (isspace (*(ign + 1)) && (*ign == '!' || *ign == '*')) + { + if (!hold) + { + /* permanently reset the ignore list */ + int i; + + for (i = 0; i < ign_count; i++) + free (ign_list[i]); + ign_count = 0; + ign_list[0] = NULL; + + /* if we are doing a '!', continue; otherwise add the '*' */ + if (*ign == '!') + continue; + } + else if (*ign == '!') + { + /* temporarily reset the ignore list */ + int i; + + if (ign_hold) + { + for (i = ign_hold; i < ign_count; i++) + free (ign_list[i]); + ign_hold = 0; + } + s_ign_list = (char **) xmalloc (ign_count * sizeof (char *)); + for (i = 0; i < ign_count; i++) + s_ign_list[i] = ign_list[i]; + s_ign_count = ign_count; + ign_count = 0; + ign_list[0] = NULL; + continue; + } + } + + /* If we have used up all the space, add some more */ + if (ign_count >= ign_size) + { + ign_size += IGN_GROW; + ign_list = (char **) xrealloc ((char *) ign_list, + (ign_size + 1) * sizeof (char *)); + } + + /* find the end of this token */ + for (mark = ign; *mark && !isspace (*mark); mark++) + /* do nothing */ ; + + save = *mark; + *mark = '\0'; + + ign_list[ign_count++] = xstrdup (ign); + ign_list[ign_count] = NULL; + + *mark = save; + if (save) + ign = mark; + else + ign = mark - 1; + } +} + +/* Return 1 if the given filename should be ignored by update or import. */ +int +ign_name (name) + char *name; +{ + char **cpp = ign_list; + + if (cpp == NULL) + return (0); + + while (*cpp) + if (fnmatch (*cpp++, name, 0) == 0) + return (1); + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/import.c b/gnu/usr.bin/cvs/cvs/import.c new file mode 100644 index 0000000..bf66dcb --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/import.c @@ -0,0 +1,974 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * "import" checks in the vendor release located in the current directory into + * the CVS source repository. The CVS vendor branch support is utilized. + * + * At least three arguments are expected to follow the options: + * repository Where the source belongs relative to the CVSROOT + * VendorTag Vendor's major tag + * VendorReleTag Tag for this particular release + * + * Additional arguments specify more Vendor Release Tags. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)import.c 1.52 92/03/31"; +#endif + +#define FILE_HOLDER ".#cvsxxx" + +#if __STDC__ +static char *get_comment (char *user); +static int add_rcs_file (char *message, char *rcs, char *user, char *vtag, + int targc, char *targv[]); +static int expand_at_signs (char *buf, off_t size, FILE *fp); +static int add_rev (char *message, char *rcs, char *vfile, char *vers); +static int add_tags (char *rcs, char *vfile, char *vtag, int targc, + char *targv[]); +static int import_descend (char *message, char *vtag, int targc, char *targv[]); +static int import_descend_dir (char *message, char *dir, char *vtag, + int targc, char *targv[]); +static int process_import_file (char *message, char *vfile, char *vtag, + int targc, char *targv[]); +static int update_rcs_file (char *message, char *vfile, char *vtag, int targc, + char *targv[]); +static void add_log (int ch, char *fname); +#else +static int import_descend (); +static int process_import_file (); +static int update_rcs_file (); +static int add_rev (); +static int add_tags (); +static char *get_comment (); +static int add_rcs_file (); +static int expand_at_signs (); +static void add_log (); +static int import_descend_dir (); +#endif /* __STDC__ */ + +static int repos_len; +static char vhead[50]; +static char vbranch[50]; +static FILE *logfp; +static char repository[PATH_MAX]; +static int conflicts; + +static char *import_usage[] = +{ + "Usage: %s %s [-Qq] [-I ign] [-m msg] [-b branch]\n", + " repository vendor-tag release-tags...\n", + "\t-Q\tReally quiet.\n", + "\t-q\tSomewhat quiet.\n", + "\t-I ign\tMore files to ignore (! to reset).\n", + "\t-b bra\tVendor branch id.\n", + "\t-m msg\tLog message.\n", + NULL +}; + +int +import (argc, argv) + int argc; + char *argv[]; +{ + char message[MAXMESGLEN]; + char tmpfile[L_tmpnam+1]; + char *cp; + int i, c, msglen, err; + List *ulist; + Node *p; + + if (argc == -1) + usage (import_usage); + + ign_setup (); + + (void) strcpy (vbranch, CVSBRANCH); + message[0] = '\0'; + optind = 1; + while ((c = gnu_getopt (argc, argv, "Qqb:m:I:")) != -1) + { + switch (c) + { + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'b': + (void) strcpy (vbranch, optarg); + break; + case 'm': +#ifdef FORCE_USE_EDITOR + use_editor = TRUE; +#else + use_editor = FALSE; +#endif + if (strlen (optarg) >= (sizeof (message) - 1)) + { + error (0, 0, "warning: message too long; truncated!"); + (void) strncpy (message, optarg, sizeof (message)); + message[sizeof (message) - 2] = '\0'; + } + else + (void) strcpy (message, optarg); + break; + case 'I': + ign_add (optarg, 0); + break; + case '?': + default: + usage (import_usage); + break; + } + } + argc -= optind; + argv += optind; + if (argc < 3) + usage (import_usage); + + for (i = 1; i < argc; i++) /* check the tags for validity */ + RCS_check_tag (argv[i]); + + /* XXX - this should be a module, not just a pathname */ + if (argv[0][0] != '/') + { + if (CVSroot == NULL) + { + error (0, 0, "missing CVSROOT environment variable\n"); + error (1, 0, "Set it or specify the '-d' option to %s.", + program_name); + } + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + repos_len = strlen (CVSroot); + } + else + { + (void) strcpy (repository, argv[0]); + repos_len = 0; + } + + /* + * Consistency checks on the specified vendor branch. It must be + * composed of only numbers and dots ('.'). Also, for now we only + * support branching to a single level, so the specified vendor branch + * must only have two dots in it (like "1.1.1"). + */ + for (cp = vbranch; *cp != '\0'; cp++) + if (!isdigit (*cp) && *cp != '.') + error (1, 0, "%s is not a numeric branch", vbranch); + if (numdots (vbranch) != 2) + error (1, 0, "Only branches with two dots are supported: %s", vbranch); + (void) strcpy (vhead, vbranch); + cp = rindex (vhead, '.'); + *cp = '\0'; + if (use_editor) + do_editor ((char *) NULL, message, repository, (List *) NULL); + msglen = strlen (message); + if (msglen == 0 || message[msglen - 1] != '\n') + { + message[msglen] = '\n'; + message[msglen + 1] = '\0'; + } + + /* + * Make all newly created directories writable. Should really use a more + * sophisticated security mechanism here. + */ + (void) umask (2); + make_directories (repository); + + /* Create the logfile that will be logged upon completion */ + if ((logfp = fopen (tmpnam (tmpfile), "w+")) == NULL) + error (1, errno, "cannot create temporary file `%s'", tmpfile); + (void) unlink (tmpfile); /* to be sure it goes away */ + (void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]); + (void) fprintf (logfp, "Release Tags:\t"); + for (i = 2; i < argc; i++) + (void) fprintf (logfp, "%s\n\t\t", argv[i]); + (void) fprintf (logfp, "\n"); + + /* Just Do It. */ + err = import_descend (message, argv[1], argc - 2, argv + 2); + if (conflicts) + { + if (!really_quiet) + { + (void) printf ("\n%d conflicts created by this import.\n", + conflicts); + (void) printf ("Use the following command to help the merge:\n\n"); + (void) printf ("\t%s checkout -j%s:yesterday -j%s %s\n\n", + program_name, argv[1], argv[1], argv[0]); + } + + (void) fprintf (logfp, "\n%d conflicts created by this import.\n", + conflicts); + (void) fprintf (logfp, + "Use the following command to help the merge:\n\n"); + (void) fprintf (logfp, "\t%s checkout -j%s:yesterday -j%s %s\n\n", + program_name, argv[1], argv[1], argv[0]); + } + else + { + if (!really_quiet) + (void) printf ("\nNo conflicts created by this import\n\n"); + (void) fprintf (logfp, "\nNo conflicts created by this import\n\n"); + } + + /* + * Write out the logfile and clean up. + */ + ulist = getlist (); + p = getnode (); + p->type = UPDATE; + p->delproc = update_delproc; + p->key = xstrdup ("- Imported sources"); + p->data = (char *) T_TITLE; + (void) addnode (ulist, p); + Update_Logfile (repository, message, vbranch, logfp, ulist); + dellist (&ulist); + (void) fclose (logfp); + return (err); +} + +/* + * process all the files in ".", then descend into other directories. + */ +static int +import_descend (message, vtag, targc, targv) + char *message; + char *vtag; + int targc; + char *targv[]; +{ + DIR *dirp; + struct direct *dp; + int err = 0; + int has_dirs = 0; + + /* first, load up any per-directory ignore lists */ + ign_add_file (CVSDOTIGNORE, 1); + + if ((dirp = opendir (".")) == NULL) + { + err++; + } + else + { + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0) + continue; + if (ign_name (dp->d_name)) + { + add_log ('I', dp->d_name); + continue; + } + if (isdir (dp->d_name)) + { + has_dirs = 1; + } + else + { + if (islink (dp->d_name)) + { + add_log ('L', dp->d_name); + err++; + } + else + { + err += process_import_file (message, dp->d_name, + vtag, targc, targv); + } + } + } + (void) closedir (dirp); + } + if (has_dirs) + { + if ((dirp = opendir (".")) == NULL) + err++; + else + { + while ((dp = readdir (dirp)) != NULL) + { + if (ign_name (dp->d_name) || !isdir (dp->d_name)) + continue; + err += import_descend_dir (message, dp->d_name, + vtag, targc, targv); + } + (void) closedir (dirp); + } + } + return (err); +} + +/* + * Process the argument import file. + */ +static int +process_import_file (message, vfile, vtag, targc, targv) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; +{ + char attic_name[PATH_MAX]; + char rcs[PATH_MAX]; + + (void) sprintf (rcs, "%s/%s%s", repository, vfile, RCSEXT); + if (!isfile (rcs)) + { + (void) sprintf (attic_name, "%s/%s/%s%s", repository, CVSATTIC, + vfile, RCSEXT); + if (!isfile (attic_name)) + { + + /* + * A new import source file; it doesn't exist as a ,v within the + * repository nor in the Attic -- create it anew. + */ + add_log ('N', vfile); + return (add_rcs_file (message, rcs, vfile, vtag, targc, targv)); + } + } + + /* + * an rcs file exists. have to do things the official, slow, way. + */ + return (update_rcs_file (message, vfile, vtag, targc, targv)); +} + +/* + * The RCS file exists; update it by adding the new import file to the + * (possibly already existing) vendor branch. + */ +static int +update_rcs_file (message, vfile, vtag, targc, targv) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; +{ + Vers_TS *vers; + char letter; + int ierrno; + + vers = Version_TS (repository, (char *) NULL, vbranch, (char *) NULL, vfile, + 1, 0, (List *) NULL, (List *) NULL); + if (vers->vn_rcs != NULL) + { + char xtmpfile[50]; + int different; + int retcode = 0; + + /* XXX - should be more unique */ + (void) sprintf (xtmpfile, "/tmp/%s", FILE_HOLDER); + + /* + * The rcs file does have a revision on the vendor branch. Compare + * this revision with the import file; if they match exactly, there + * is no need to install the new import file as a new revision to the + * branch. Just tag the revision with the new import tags. + * + * This is to try to cut down the number of "C" conflict messages for + * locally modified import source files. + */ +#ifdef HAVE_RCS5 + run_setup ("%s%s -q -f -r%s -p -ko", Rcsbin, RCS_CO, vers->vn_rcs); +#else + run_setup ("%s%s -q -f -r%s -p", Rcsbin, RCS_CO, vers->vn_rcs); +#endif + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, xtmpfile, RUN_TTY, + RUN_NORMAL|RUN_REALLY)) != 0) + { + ierrno = errno; + fperror (logfp, 0, retcode == -1 ? ierrno : 0, + "ERROR: cannot co revision %s of file %s", vers->vn_rcs, + vers->srcfile->path); + error (0, retcode == -1 ? ierrno : 0, + "ERROR: cannot co revision %s of file %s", vers->vn_rcs, + vers->srcfile->path); + (void) unlink_file (xtmpfile); + return (1); + } + different = xcmp (xtmpfile, vfile); + (void) unlink_file (xtmpfile); + if (!different) + { + int retval = 0; + + /* + * The two files are identical. Just update the tags, print the + * "U", signifying that the file has changed, but needs no + * attention, and we're done. + */ + if (add_tags (vers->srcfile->path, vfile, vtag, targc, targv)) + retval = 1; + add_log ('U', vfile); + freevers_ts (&vers); + return (retval); + } + } + + /* We may have failed to parse the RCS file; check just in case */ + if (vers->srcfile == NULL || add_rev (message, vers->srcfile->path, + vfile, vers->vn_rcs) || + add_tags (vers->srcfile->path, vfile, vtag, targc, targv)) + { + freevers_ts (&vers); + return (1); + } + + if (vers->srcfile->branch == NULL || + strcmp (vers->srcfile->branch, vbranch) != 0) + { + conflicts++; + letter = 'C'; + } + else + letter = 'U'; + add_log (letter, vfile); + + freevers_ts (&vers); + return (0); +} + +/* + * Add the revision to the vendor branch + */ +static int +add_rev (message, rcs, vfile, vers) + char *message; + char *rcs; + char *vfile; + char *vers; +{ + int locked, status, ierrno; + int retcode = 0; + + if (noexec) + return (0); + + locked = 0; + if (vers != NULL) + { + run_setup ("%s%s -q -l%s", Rcsbin, RCS, vbranch); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, DEVNULL, DEVNULL, RUN_NORMAL)) == 0) + locked = 1; + else if (retcode == -1) + { + error (0, errno, "fork failed"); + return (1); + } + } + if (link_file (vfile, FILE_HOLDER) < 0) + { + if (errno == EEXIST) + { + (void) unlink_file (FILE_HOLDER); + (void) link_file (vfile, FILE_HOLDER); + } + else + { + ierrno = errno; + fperror (logfp, 0, ierrno, "ERROR: cannot create link to %s", vfile); + error (0, ierrno, "ERROR: cannot create link to %s", vfile); + return (1); + } + } + run_setup ("%s%s -q -f -r%s", Rcsbin, RCS_CI, vbranch); + run_args ("-m%s", message); + run_arg (rcs); + status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + ierrno = errno; + rename_file (FILE_HOLDER, vfile); + if (status) + { + if (!noexec) + { + fperror (logfp, 0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs); + error (0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs); + } + if (locked) + { + run_setup ("%s%s -q -u%s", Rcsbin, RCS, vbranch); + run_arg (rcs); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + return (1); + } + return (0); +} + +/* + * Add the vendor branch tag and all the specified import release tags to the + * RCS file. The vendor branch tag goes on the branch root (1.1.1) while the + * vendor release tags go on the newly added leaf of the branch (1.1.1.1, + * 1.1.1.2, ...). + */ +static int +add_tags (rcs, vfile, vtag, targc, targv) + char *rcs; + char *vfile; + char *vtag; + int targc; + char *targv[]; +{ + int i, ierrno; + Vers_TS *vers; + int retcode = 0; + + if (noexec) + return (0); + + run_setup ("%s%s -q -N%s:%s", Rcsbin, RCS, vtag, vbranch); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + ierrno = errno; + fperror (logfp, 0, retcode == -1 ? ierrno : 0, + "ERROR: Failed to set tag %s in %s", vtag, rcs); + error (0, retcode == -1 ? ierrno : 0, + "ERROR: Failed to set tag %s in %s", vtag, rcs); + return (1); + } + vers = Version_TS (repository, (char *) NULL, vtag, (char *) NULL, vfile, + 1, 0, (List *) NULL, (List *) NULL); + for (i = 0; i < targc; i++) + { + run_setup ("%s%s -q -N%s:%s", Rcsbin, RCS, targv[i], vers->vn_rcs); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + ierrno = errno; + fperror (logfp, 0, retcode == -1 ? ierrno : 0, + "WARNING: Couldn't add tag %s to %s", targv[i], rcs); + error (0, retcode == -1 ? ierrno : 0, + "WARNING: Couldn't add tag %s to %s", targv[i], rcs); + } + } + freevers_ts (&vers); + return (0); +} + +/* + * Stolen from rcs/src/rcsfnms.c, and adapted/extended. + */ +struct compair +{ + char *suffix, *comlead; +}; + +struct compair comtable[] = +{ + +/* + * comtable pairs each filename suffix with a comment leader. The comment + * leader is placed before each line generated by the $Log keyword. This + * table is used to guess the proper comment leader from the working file's + * suffix during initial ci (see InitAdmin()). Comment leaders are needed for + * languages without multiline comments; for others they are optional. + */ + "a", "-- ", /* Ada */ + "ada", "-- ", + "asm", ";; ", /* assembler (MS-DOS) */ + "bat", ":: ", /* batch (MS-DOS) */ + "c", " * ", /* C */ + "c++", "// ", /* C++ in all its infinite guises */ + "cc", "// ", + "cpp", "// ", + "cxx", "// ", + "cl", ";;; ", /* Common Lisp */ + "cmd", ":: ", /* command (OS/2) */ + "cmf", "c ", /* CM Fortran */ + "cs", " * ", /* C* */ + "csh", "# ", /* shell */ + "e", "# ", /* efl */ + "el", "; ", /* Emacs Lisp */ + "f", "c ", /* Fortran */ + "for", "c ", + "h", " * ", /* C-header */ + "hh", "// ", /* C++ header */ + "hpp", "// ", + "hxx", "// ", + "in", "# ", /* for Makefile.in */ + "l", " * ", /* lex (conflict between lex and + * franzlisp) */ + "mac", ";; ", /* macro (DEC-10, MS-DOS, PDP-11, + * VMS, etc) */ + "me", ".\\\" ", /* me-macros t/nroff */ + "ml", "; ", /* mocklisp */ + "mm", ".\\\" ", /* mm-macros t/nroff */ + "ms", ".\\\" ", /* ms-macros t/nroff */ + "man", ".\\\" ", /* man-macros t/nroff */ + "1", ".\\\" ", /* feeble attempt at man pages... */ + "2", ".\\\" ", + "3", ".\\\" ", + "4", ".\\\" ", + "5", ".\\\" ", + "6", ".\\\" ", + "7", ".\\\" ", + "8", ".\\\" ", + "9", ".\\\" ", + "p", " * ", /* pascal */ + "pas", " * ", + "pl", "# ", /* perl (conflict with Prolog) */ + "ps", "% ", /* postscript */ + "r", "# ", /* ratfor */ + "red", "% ", /* psl/rlisp */ +#ifdef sparc + "s", "! ", /* assembler */ +#endif +#ifdef mc68000 + "s", "| ", /* assembler */ +#endif +#ifdef pdp11 + "s", "/ ", /* assembler */ +#endif +#ifdef vax + "s", "# ", /* assembler */ +#endif +#ifdef __ksr__ + "s", "# ", /* assembler */ + "S", "# ", /* Macro assembler */ +#endif + "sh", "# ", /* shell */ + "sl", "% ", /* psl */ + "tex", "% ", /* tex */ + "y", " * ", /* yacc */ + "ye", " * ", /* yacc-efl */ + "yr", " * ", /* yacc-ratfor */ + "", "# ", /* default for empty suffix */ + NULL, "# " /* default for unknown suffix; */ +/* must always be last */ +}; + +static char * +get_comment (user) + char *user; +{ + char *cp, *suffix; + char suffix_path[PATH_MAX]; + int i; + + cp = rindex (user, '.'); + if (cp != NULL) + { + cp++; + + /* + * Convert to lower-case, since we are not concerned about the + * case-ness of the suffix. + */ + (void) strcpy (suffix_path, cp); + for (cp = suffix_path; *cp; cp++) + if (isupper (*cp)) + *cp = tolower (*cp); + suffix = suffix_path; + } + else + suffix = ""; /* will use the default */ + for (i = 0;; i++) + { + if (comtable[i].suffix == NULL) /* default */ + return (comtable[i].comlead); + if (strcmp (suffix, comtable[i].suffix) == 0) + return (comtable[i].comlead); + } +} + +static int +add_rcs_file (message, rcs, user, vtag, targc, targv) + char *message; + char *rcs; + char *user; + char *vtag; + int targc; + char *targv[]; +{ + FILE *fprcs, *fpuser; + struct stat sb; + struct tm *ftm; + time_t now; + char altdate1[50], altdate2[50]; + char *author, *buf; + int i, mode, ierrno, err = 0; + + if (noexec) + return (0); + + fprcs = open_file (rcs, "w+"); + fpuser = open_file (user, "r"); + + /* + * putadmin() + */ + if (fprintf (fprcs, "head %s;\n", vhead) == EOF || + fprintf (fprcs, "branch %s;\n", vbranch) == EOF || + fprintf (fprcs, "access ;\n") == EOF || + fprintf (fprcs, "symbols ") == EOF) + { + goto write_error; + } + + for (i = targc - 1; i >= 0; i--) /* RCS writes the symbols backwards */ + if (fprintf (fprcs, "%s:%s.1 ", targv[i], vbranch) == EOF) + goto write_error; + + if (fprintf (fprcs, "%s:%s;\n", vtag, vbranch) == EOF || + fprintf (fprcs, "locks ; strict;\n") == EOF || + /* XXX - make sure @@ processing works in the RCS file */ + fprintf (fprcs, "comment @%s@;\n\n", get_comment (user)) == EOF) + { + goto write_error; + } + + /* + * puttree() + */ + (void) time (&now); +#ifdef HAVE_RCS5 + ftm = gmtime (&now); +#else + ftm = localtime (&now); +#endif + (void) sprintf (altdate1, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + now++; +#ifdef HAVE_RCS5 + ftm = gmtime (&now); +#else + ftm = localtime (&now); +#endif + (void) sprintf (altdate2, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + author = getcaller (); + + if (fprintf (fprcs, "\n%s\n", vhead) == EOF || + fprintf (fprcs, "date %s; author %s; state Exp;\n", + altdate1, author) == EOF || + fprintf (fprcs, "branches %s.1;\n", vbranch) == EOF || + fprintf (fprcs, "next ;\n") == EOF || + fprintf (fprcs, "\n%s.1\n", vbranch) == EOF || + fprintf (fprcs, "date %s; author %s; state Exp;\n", + altdate2, author) == EOF || + fprintf (fprcs, "branches ;\n") == EOF || + fprintf (fprcs, "next ;\n\n") == EOF || + /* + * putdesc() + */ + fprintf (fprcs, "\ndesc\n") == EOF || + fprintf (fprcs, "@@\n\n\n") == EOF || + /* + * putdelta() + */ + fprintf (fprcs, "\n%s\n", vhead) == EOF || + fprintf (fprcs, "log\n") == EOF || + fprintf (fprcs, "@Initial revision\n@\n") == EOF || + fprintf (fprcs, "text\n@") == EOF) + { + goto write_error; + } + + if (fstat (fileno (fpuser), &sb) < 0) + error (1, errno, "cannot fstat %s", user); + if (sb.st_size > 0) + { + off_t size; + + size = sb.st_size; + buf = xmalloc ((int) size); + if (fread (buf, (int) size, 1, fpuser) != 1) + error (1, errno, "cannot read file %s for copying", user); + if (expand_at_signs (buf, size, fprcs) == EOF) + goto write_error; + free (buf); + } + if (fprintf (fprcs, "@\n\n") == EOF || + fprintf (fprcs, "\n%s.1\n", vbranch) == EOF || + fprintf (fprcs, "log\n@") == EOF || + expand_at_signs (message, (off_t) strlen (message), fprcs) == EOF || + fprintf (fprcs, "@\ntext\n") == EOF || + fprintf (fprcs, "@@\n") == EOF) + { + goto write_error; + } + if (fclose (fprcs) == EOF) + { + ierrno = errno; + goto write_error_noclose; + } + (void) fclose (fpuser); + + /* + * Fix the modes on the RCS files. They must maintain the same modes as + * the original user file, except that all write permissions must be + * turned off. + */ + mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH); + if (chmod (rcs, mode) < 0) + { + ierrno = errno; + fperror (logfp, 0, ierrno, + "WARNING: cannot change mode of file %s", rcs); + error (0, ierrno, "WARNING: cannot change mode of file %s", rcs); + err++; + } + return (err); + +write_error: + ierrno = errno; + (void) fclose (fprcs); +write_error_noclose: + (void) fclose (fpuser); + fperror (logfp, 0, ierrno, "ERROR: cannot write file %s", rcs); + error (0, ierrno, "ERROR: cannot write file %s", rcs); + if (ierrno == ENOSPC) + { + (void) unlink (rcs); + fperror (logfp, 0, 0, "ERROR: out of space - aborting"); + error (1, 0, "ERROR: out of space - aborting"); + } + return (err + 1); +} + +/* + * Sigh.. need to expand @ signs into double @ signs + */ +static int +expand_at_signs (buf, size, fp) + char *buf; + off_t size; + FILE *fp; +{ + char *cp, *end; + + for (cp = buf, end = buf + size; cp < end; cp++) + { + if (*cp == '@') + (void) putc ('@', fp); + if (putc (*cp, fp) == EOF) + return (EOF); + } + return (1); +} + +/* + * Write an update message to (potentially) the screen and the log file. + */ +static void +add_log (ch, fname) + char ch; + char *fname; +{ + if (!really_quiet) /* write to terminal */ + { + if (repos_len) + (void) printf ("%c %s/%s\n", ch, repository + repos_len + 1, fname); + else if (repository[0]) + (void) printf ("%c %s/%s\n", ch, repository, fname); + else + (void) printf ("%c %s\n", ch, fname); + } + + if (repos_len) /* write to logfile */ + (void) fprintf (logfp, "%c %s/%s\n", ch, + repository + repos_len + 1, fname); + else if (repository[0]) + (void) fprintf (logfp, "%c %s/%s\n", ch, repository, fname); + else + (void) fprintf (logfp, "%c %s\n", ch, fname); +} + +/* + * This is the recursive function that walks the argument directory looking + * for sub-directories that have CVS administration files in them and updates + * them recursively. + * + * Note that we do not follow symbolic links here, which is a feature! + */ +static int +import_descend_dir (message, dir, vtag, targc, targv) + char *message; + char *dir; + char *vtag; + int targc; + char *targv[]; +{ + char cwd[PATH_MAX]; + char *cp; + int ierrno, err; + + if (islink (dir)) + return (0); + if (getwd (cwd) == NULL) + { + fperror (logfp, 0, 0, "ERROR: cannot get working directory: %s", cwd); + error (0, 0, "ERROR: cannot get working directory: %s", cwd); + return (1); + } + if (repository[0] == '\0') + (void) strcpy (repository, dir); + else + { + (void) strcat (repository, "/"); + (void) strcat (repository, dir); + } + if (!quiet) + error (0, 0, "Importing %s", repository); + if (chdir (dir) < 0) + { + ierrno = errno; + fperror (logfp, 0, ierrno, "ERROR: cannot chdir to %s", repository); + error (0, ierrno, "ERROR: cannot chdir to %s", repository); + err = 1; + goto out; + } + if (!isdir (repository)) + { + if (isfile (repository)) + { + fperror (logfp, 0, 0, "ERROR: %s is a file, should be a directory!", + repository); + error (0, 0, "ERROR: %s is a file, should be a directory!", + repository); + err = 1; + goto out; + } + if (noexec == 0 && mkdir (repository, 0777) < 0) + { + ierrno = errno; + fperror (logfp, 0, ierrno, + "ERROR: cannot mkdir %s -- not added", repository); + error (0, ierrno, + "ERROR: cannot mkdir %s -- not added", repository); + err = 1; + goto out; + } + } + err = import_descend (message, vtag, targc, targv); + out: + if ((cp = rindex (repository, '/')) != NULL) + *cp = '\0'; + else + repository[0] = '\0'; + if (chdir (cwd) < 0) + error (1, errno, "cannot chdir to %s", cwd); + return (err); +} diff --git a/gnu/usr.bin/cvs/cvs/lock.c b/gnu/usr.bin/cvs/cvs/lock.c new file mode 100644 index 0000000..5da23b3 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/lock.c @@ -0,0 +1,522 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Set Lock + * + * Lock file support for CVS. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)lock.c 1.42 92/04/10"; +#endif + +extern char *ctime (); + +#if __STDC__ +static int readers_exist (char *repository); +static int set_lock (char *lockdir, int will_wait, char *repository); +static void set_lockers_name (struct stat *statp); +static int set_writelock_proc (Node * p); +static int unlock_proc (Node * p); +static int write_lock (char *repository); +static void unlock (char *repository); +static void lock_wait (); +#else +static int unlock_proc (); +static void unlock (); +static int set_writelock_proc (); +static int write_lock (); +static int readers_exist (); +static int set_lock (); +static void set_lockers_name (); +static void lock_wait (); +#endif /* __STDC__ */ + +static char lockers_name[20]; +static char *repository; +static char readlock[PATH_MAX], writelock[PATH_MAX]; +static int cleanup_lckdir; +static List *locklist; + +#define L_OK 0 /* success */ +#define L_ERROR 1 /* error condition */ +#define L_LOCK_OWNED 2 /* lock already owned by us */ +#define L_LOCKED 3 /* lock owned by someone else */ + +/* + * Clean up all outstanding locks + */ +void +Lock_Cleanup () +{ + /* clean up simple locks (if any) */ + if (repository != NULL) + { + unlock (repository); + repository = (char *) NULL; + } + + /* clean up multiple locks (if any) */ + if (locklist != (List *) NULL) + { + (void) walklist (locklist, unlock_proc); + locklist = (List *) NULL; + } +} + +/* + * walklist proc for removing a list of locks + */ +static int +unlock_proc (p) + Node *p; +{ + unlock (p->key); + return (0); +} + +/* + * Remove the lock files (without complaining if they are not there), + */ +static void +unlock (repository) + char *repository; +{ + char tmp[PATH_MAX]; + struct stat sb; + + if (readlock[0] != '\0') + { + (void) sprintf (tmp, "%s/%s", repository, readlock); + (void) unlink (tmp); + } + + if (writelock[0] != '\0') + { + (void) sprintf (tmp, "%s/%s", repository, writelock); + (void) unlink (tmp); + } + + /* + * Only remove the lock directory if it is ours, note that this does + * lead to the limitation that one user ID should not be committing + * files into the same Repository directory at the same time. Oh well. + */ + (void) sprintf (tmp, "%s/%s", repository, CVSLCK); + if (stat (tmp, &sb) != -1 && sb.st_uid == geteuid () && + (writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir))) + { + (void) rmdir (tmp); + } + cleanup_lckdir = 0; +} + +/* + * Create a lock file for readers + */ +int +Reader_Lock (xrepository) + char *xrepository; +{ + int err = 0; + FILE *fp; + char tmp[PATH_MAX]; + + if (noexec) + return (0); + + /* we only do one directory at a time for read locks! */ + if (repository != NULL) + { + error (0, 0, "Reader_Lock called while read locks set - Help!"); + return (1); + } + + if (readlock[0] == '\0') + (void) sprintf (readlock, "%s.%d", CVSRFL, getpid ()); + + /* remember what we're locking (for lock_cleanup) */ + repository = xrepository; + + /* make sure we clean up on error */ + (void) SIG_register (SIGHUP, Lock_Cleanup); + (void) SIG_register (SIGINT, Lock_Cleanup); + (void) SIG_register (SIGQUIT, Lock_Cleanup); + (void) SIG_register (SIGPIPE, Lock_Cleanup); + (void) SIG_register (SIGTERM, Lock_Cleanup); + + /* make sure we can write the repository */ + (void) sprintf (tmp, "%s/%s.%d", xrepository, CVSTFL, getpid ()); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + error (0, errno, "cannot create read lock in repository `%s'", + xrepository); + readlock[0] = '\0'; + (void) unlink (tmp); + return (1); + } + (void) unlink (tmp); + + /* get the lock dir for our own */ + (void) sprintf (tmp, "%s/%s", xrepository, CVSLCK); + if (set_lock (tmp, 1, xrepository) != L_OK) + { + error (0, 0, "failed to obtain dir lock in repository `%s'", + xrepository); + readlock[0] = '\0'; + return (1); + } + + /* write a read-lock */ + (void) sprintf (tmp, "%s/%s", xrepository, readlock); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + error (0, errno, "cannot create read lock in repository `%s'", + xrepository); + readlock[0] = '\0'; + err = 1; + } + + /* free the lock dir */ + (void) sprintf (tmp, "%s/%s", xrepository, CVSLCK); + if (rmdir (tmp) < 0) + error (0, errno, "failed to remove lock dir `%s'", tmp); + + return (err); +} + +/* + * Lock a list of directories for writing + */ +static char *lock_error_repos; +static int lock_error; +int +Writer_Lock (list) + List *list; +{ + if (noexec) + return (0); + + /* We only know how to do one list at a time */ + if (locklist != (List *) NULL) + { + error (0, 0, "Writer_Lock called while write locks set - Help!"); + return (1); + } + + for (;;) + { + /* try to lock everything on the list */ + lock_error = L_OK; /* init for set_writelock_proc */ + lock_error_repos = (char *) NULL; /* init for set_writelock_proc */ + locklist = list; /* init for Lock_Cleanup */ + (void) strcpy (lockers_name, "unknown"); + + (void) walklist (list, set_writelock_proc); + + switch (lock_error) + { + case L_ERROR: /* Real Error */ + Lock_Cleanup (); /* clean up any locks we set */ + error (0, 0, "lock failed - giving up"); + return (1); + + case L_LOCKED: /* Someone already had a lock */ + Lock_Cleanup (); /* clean up any locks we set */ + lock_wait (lock_error_repos); /* sleep a while and try again */ + continue; + + case L_OK: /* we got the locks set */ + return (0); + + default: + error (0, 0, "unknown lock status %d in Writer_Lock", + lock_error); + return (1); + } + } +} + +/* + * walklist proc for setting write locks + */ +static int +set_writelock_proc (p) + Node *p; +{ + /* if some lock was not OK, just skip this one */ + if (lock_error != L_OK) + return (0); + + /* apply the write lock */ + lock_error_repos = p->key; + lock_error = write_lock (p->key); + return (0); +} + +/* + * Create a lock file for writers returns L_OK if lock set ok, L_LOCKED if + * lock held by someone else or L_ERROR if an error occurred + */ +static int +write_lock (repository) + char *repository; +{ + int status; + FILE *fp; + char tmp[PATH_MAX]; + + if (writelock[0] == '\0') + (void) sprintf (writelock, "%s.%d", CVSWFL, getpid ()); + + /* make sure we clean up on error */ + (void) SIG_register (SIGHUP, Lock_Cleanup); + (void) SIG_register (SIGINT, Lock_Cleanup); + (void) SIG_register (SIGQUIT, Lock_Cleanup); + (void) SIG_register (SIGPIPE, Lock_Cleanup); + (void) SIG_register (SIGTERM, Lock_Cleanup); + + /* make sure we can write the repository */ + (void) sprintf (tmp, "%s/%s.%d", repository, CVSTFL, getpid ()); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + error (0, errno, "cannot create write lock in repository `%s'", + repository); + (void) unlink (tmp); + return (L_ERROR); + } + (void) unlink (tmp); + + /* make sure the lock dir is ours (not necessarily unique to us!) */ + (void) sprintf (tmp, "%s/%s", repository, CVSLCK); + status = set_lock (tmp, 0, repository); + if (status == L_OK || status == L_LOCK_OWNED) + { + /* we now own a writer - make sure there are no readers */ + if (readers_exist (repository)) + { + /* clean up the lock dir if we created it */ + if (status == L_OK) + { + if (rmdir (tmp) < 0) + error (0, errno, "failed to remove lock dir `%s'", tmp); + } + + /* indicate we failed due to read locks instead of error */ + return (L_LOCKED); + } + + /* write the write-lock file */ + (void) sprintf (tmp, "%s/%s", repository, writelock); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + int xerrno = errno; + + (void) unlink (tmp); + /* free the lock dir if we created it */ + if (status == L_OK) + { + (void) sprintf (tmp, "%s/%s", repository, CVSLCK); + if (rmdir (tmp) < 0) + error (0, errno, "failed to remove lock dir `%s'", tmp); + } + + /* return the error */ + error (0, xerrno, "cannot create write lock in repository `%s'", + repository); + return (L_ERROR); + } + return (L_OK); + } + else + return (status); +} + +/* + * readers_exist() returns 0 if there are no reader lock files remaining in + * the repository; else 1 is returned, to indicate that the caller should + * sleep a while and try again. + */ +static int +readers_exist (repository) + char *repository; +{ + char line[MAXLINELEN]; + DIR *dirp; + struct direct *dp; + struct stat sb; + CONST char *regex_err; + int ret = 0; + +#ifdef CVS_FUDGELOCKS +again: +#endif + + if ((dirp = opendir (repository)) == NULL) + error (1, 0, "cannot open directory %s", repository); + + (void) sprintf (line, "^%s.*", CVSRFL); + if ((regex_err = re_comp (line)) != NULL) + error (1, 0, "%s", regex_err); + + while ((dp = readdir (dirp)) != NULL) + { + (void) sprintf (line, "%s/%s", repository, dp->d_name); + if (re_exec (dp->d_name)) + { +#ifdef CVS_FUDGELOCKS + time_t now; + + (void) time (&now); + + /* + * If the create time of the file is more than CVSLCKAGE seconds + * ago, try to clean-up the lock file, and if successful, re-open + * the directory and try again. + */ + if (stat (line, &sb) != -1) + { + if (now >= (sb.st_ctime + CVSLCKAGE) && unlink (line) != -1) + { + (void) closedir (dirp); + goto again; + } + set_lockers_name (&sb); + } +#else + if (stat (line, &sb) != -1) + set_lockers_name (&sb); +#endif + ret = 1; + break; + } + } + (void) closedir (dirp); + return (ret); +} + +/* + * Set the static variable lockers_name appropriately, based on the stat + * structure passed in. + */ +static void +set_lockers_name (statp) + struct stat *statp; +{ + struct passwd *pw; + + if ((pw = (struct passwd *) getpwuid (statp->st_uid)) != + (struct passwd *) NULL) + { + (void) strcpy (lockers_name, pw->pw_name); + } + else + (void) sprintf (lockers_name, "uid%d", statp->st_uid); +} + +/* + * Persistently tries to make the directory "lckdir",, which serves as a + * lock. If the create time on the directory is greater than CVSLCKAGE + * seconds old, just try to remove the directory. + */ +static int +set_lock (lockdir, will_wait, repository) + char *lockdir; + int will_wait; + char *repository; +{ + struct stat sb; +#ifdef CVS_FUDGELOCKS + time_t now; +#endif + + /* + * Note that it is up to the callers of set_lock() to arrange for signal + * handlers that do the appropriate things, like remove the lock + * directory before they exit. + */ + cleanup_lckdir = 0; + for (;;) + { + SIG_beginCrSect (); + if (mkdir (lockdir, 0777) == 0) + { + cleanup_lckdir = 1; + SIG_endCrSect (); + return (L_OK); + } + SIG_endCrSect (); + + if (errno != EEXIST) + { + error (0, errno, + "failed to create lock directory in repository `%s'", + repository); + return (L_ERROR); + } + + /* + * stat the dir - if it is non-existent, re-try the loop since + * someone probably just removed it (thus releasing the lock) + */ + if (stat (lockdir, &sb) < 0) + { + if (errno == ENOENT) + continue; + + error (0, errno, "couldn't stat lock directory `%s'", lockdir); + return (L_ERROR); + } + + /* + * if we already own the lock, go ahead and return 1 which means it + * existed but we owned it + */ + if (sb.st_uid == geteuid () && !will_wait) + return (L_LOCK_OWNED); + +#ifdef CVS_FUDGELOCKS + + /* + * If the create time of the directory is more than CVSLCKAGE seconds + * ago, try to clean-up the lock directory, and if successful, just + * quietly retry to make it. + */ + (void) time (&now); + if (now >= (sb.st_ctime + CVSLCKAGE)) + { + if (rmdir (lockdir) >= 0) + continue; + } +#endif + + /* set the lockers name */ + set_lockers_name (&sb); + + /* if he wasn't willing to wait, return an error */ + if (!will_wait) + return (L_LOCKED); + lock_wait (repository); + } +} + +/* + * Print out a message that the lock is still held, then sleep a while. + */ +static void +lock_wait (repos) + char *repos; +{ + time_t now; + + (void) time (&now); + error (0, 0, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11, + lockers_name, repos); + (void) sleep (CVSLCKSLEEP); +} diff --git a/gnu/usr.bin/cvs/cvs/log.c b/gnu/usr.bin/cvs/cvs/log.c new file mode 100644 index 0000000..9e7ab8b --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/log.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Print Log Information + * + * Prints the RCS "log" (rlog) information for the specified files. With no + * argument, prints the log information for all the files in the directory + * (recursive by default). + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)log.c 1.39 92/03/31"; +#endif + +#if __STDC__ +static Dtype log_dirproc (char *dir, char *repository, char *update_dir); +static int log_fileproc (char *file, char *update_dir, char *repository, + List * entries, List * srcfiles); +#else +static int log_fileproc (); +static Dtype log_dirproc (); +#endif /* __STDC__ */ + +static char options[PATH_MAX]; + +static char *log_usage[] = +{ + "Usage: %s %s [-l] [rlog-options] [files...]\n", + "\t-l\tLocal directory only, no recursion.\n", + NULL +}; + +int +cvslog (argc, argv) + int argc; + char *argv[]; +{ + int i; + int numopt = 1; + int err = 0; + int local = 0; + + if (argc == -1) + usage (log_usage); + + /* + * All 'log' command options except -l are passed directly on to 'rlog' + */ + options[0] = '\0'; /* Assume none */ + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-' || argv[i][0] == '\0') + { + numopt++; + switch (argv[i][1]) + { + case 'l': + local = 1; + break; + default: + (void) strcat (options, " "); + (void) strcat (options, argv[i]); + break; + } + } + } + argc -= numopt; + argv += numopt; + + err = start_recursion (log_fileproc, (int (*) ()) NULL, log_dirproc, + (int (*) ()) NULL, argc, argv, local, + W_LOCAL | W_REPOS | W_ATTIC, 0, 1, + (char *) NULL, 1); + return (err); +} + +/* + * Do an rlog on a file + */ +/* ARGSUSED */ +static int +log_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Node *p; + RCSNode *rcsfile; + int retcode = 0; + + p = findnode (srcfiles, file); + if (p == NULL || (rcsfile = (RCSNode *) p->data) == NULL) + { + if (!really_quiet) + error (0, 0, "nothing known about %s", file); + return (1); + } + + run_setup ("%s%s %s", Rcsbin, RCS_RLOG, options); + run_arg (rcsfile->path); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_REALLY)) == -1) + { + error (1, errno, "fork failed for rlog on %s", file); + } + return (retcode); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +log_dirproc (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + if (!isdir (dir)) + return (R_SKIP_ALL); + + if (!quiet) + error (0, 0, "Logging %s", update_dir); + return (R_PROCESS); +} diff --git a/gnu/usr.bin/cvs/cvs/logmsg.c b/gnu/usr.bin/cvs/cvs/logmsg.c new file mode 100644 index 0000000..ce1da92 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/logmsg.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)logmsg.c 1.40 92/04/10"; +#endif + +#if __STDC__ +static int find_type (Node * p); +static int fmt_proc (Node * p); +static int logfile_write (char *repository, char *filter, char *title, + char *message, char *revision, FILE * logfp, + List * changes); +static int rcsinfo_proc (char *repository, char *template); +static int title_proc (Node * p); +static int update_logfile_proc (char *repository, char *filter); +static void setup_tmpfile (FILE * xfp, char *xprefix, List * changes); +static int editinfo_proc (char *repository, char *template); +#else +static void setup_tmpfile (); +static int find_type (); +static int fmt_proc (); +static int rcsinfo_proc (); +static int update_logfile_proc (); +static int title_proc (); +static int logfile_write (); +static int editinfo_proc (); +#endif /* __STDC__ */ + +static FILE *fp; +static char *strlist; +static char *editinfo_editor; +static Ctype type; + +/* + * Puts a standard header on the output which is either being prepared for an + * editor session, or being sent to a logfile program. The modified, added, + * and removed files are included (if any) and formatted to look pretty. + */ +static char *prefix; +static int col; +static void +setup_tmpfile (xfp, xprefix, changes) + FILE *xfp; + char *xprefix; + List *changes; +{ + /* set up statics */ + fp = xfp; + prefix = xprefix; + + type = T_MODIFIED; + if (walklist (changes, find_type) != 0) + { + (void) fprintf (fp, "%sModified Files:\n", prefix); + (void) fprintf (fp, "%s\t", prefix); + col = 8; + (void) walklist (changes, fmt_proc); + (void) fprintf (fp, "\n"); + } + type = T_ADDED; + if (walklist (changes, find_type) != 0) + { + (void) fprintf (fp, "%sAdded Files:\n", prefix); + (void) fprintf (fp, "%s\t", prefix); + col = 8; + (void) walklist (changes, fmt_proc); + (void) fprintf (fp, "\n"); + } + type = T_REMOVED; + if (walklist (changes, find_type) != 0) + { + (void) fprintf (fp, "%sRemoved Files:\n", prefix); + (void) fprintf (fp, "%s\t", prefix); + col = 8; + (void) walklist (changes, fmt_proc); + (void) fprintf (fp, "\n"); + } +} + +/* + * Looks for nodes of a specified type and returns 1 if found + */ +static int +find_type (p) + Node *p; +{ + if (p->data == (char *) type) + return (1); + else + return (0); +} + +/* + * Breaks the files list into reasonable sized lines to avoid line wrap... + * all in the name of pretty output. It only works on nodes whose types + * match the one we're looking for + */ +static int +fmt_proc (p) + Node *p; +{ + if (p->data == (char *) type) + { + if ((col + (int) strlen (p->key)) > 70) + { + (void) fprintf (fp, "\n%s\t", prefix); + col = 8; + } + (void) fprintf (fp, "%s ", p->key); + col += strlen (p->key) + 1; + } + return (0); +} + +/* + * Builds a temporary file using setup_tmpfile() and invokes the user's + * editor on the file. The header garbage in the resultant file is then + * stripped and the log message is stored in the "message" argument. + * + * rcsinfo - is the name of a file containing lines tacked onto the end of the + * RCS info offered to the user for editing. If specified, the '-m' flag to + * "commit" is disabled -- users are forced to run the editor. + * + */ +void +do_editor (dir, message, repository, changes) + char *dir; + char *message; + char *repository; + List *changes; +{ + static int reuse_log_message = 0; + char line[MAXLINELEN], fname[L_tmpnam+1]; + char *orig_message; + struct stat pre_stbuf, post_stbuf; + int retcode = 0; + + if (noexec || reuse_log_message) + return; + + orig_message = xstrdup (message); /* save it for later */ + + /* Create a temporary file */ + (void) tmpnam (fname); + again: + if ((fp = fopen (fname, "w+")) == NULL) + error (1, 0, "cannot create temporary file %s", fname); + + /* set up the file so that the first line is blank if no msg specified */ + if (*orig_message) + { + (void) fprintf (fp, "%s", orig_message); + if (orig_message[strlen (orig_message) - 1] != '\n') + (void) fprintf (fp, "\n"); + } + else + (void) fprintf (fp, "\n"); + + /* tack templates on if necessary */ + (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1); + + (void) fprintf (fp, + "%s----------------------------------------------------------------------\n", + CVSEDITPREFIX); + (void) fprintf (fp, + "%sEnter Log. Lines beginning with `%s' are removed automatically\n%s\n", + CVSEDITPREFIX, CVSEDITPREFIX, CVSEDITPREFIX); + if (dir != NULL) + (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX, + dir, CVSEDITPREFIX); + setup_tmpfile (fp, CVSEDITPREFIX, changes); + (void) fprintf (fp, + "%s----------------------------------------------------------------------\n", + CVSEDITPREFIX); + + /* finish off the temp file */ + (void) fclose (fp); + if (stat (fname, &pre_stbuf) == -1) + pre_stbuf.st_mtime = 0; + + if (editinfo_editor) + free (editinfo_editor); + editinfo_editor = (char *) NULL; + (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0); + + /* run the editor */ + run_setup ("%s", editinfo_editor ? editinfo_editor : Editor); + run_arg (fname); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, + RUN_NORMAL | RUN_SIGIGNORE)) != 0) + error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0, + editinfo_editor ? "Logfile verification failed" : + "warning: editor session failed"); + + /* put the entire message back into the message variable */ + fp = open_file (fname, "r"); + *message = '\0'; + while (fgets (line, sizeof (line), fp) != NULL) + { + if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0) + continue; + if (((int) strlen (message) + (int) strlen (line)) >= MAXMESGLEN) + { + error (0, 0, "warning: log message truncated!"); + break; + } + (void) strcat (message, line); + } + (void) fclose (fp); + if ((stat (fname, &post_stbuf) == 0 && + pre_stbuf.st_mtime == post_stbuf.st_mtime) || + (*message == '\0' || strcmp (message, "\n") == 0)) + { + for (;;) + { + (void) printf ("\nLog message unchanged or not specified\n"); + (void) printf ("a)bort, c)continue, e)dit, !)reuse this message unchanged for remaining dirs\n"); + (void) printf ("Action: (continue) "); + (void) fflush (stdout); + *line = '\0'; + (void) fgets (line, sizeof (line), stdin); + if (*line == '\0' || *line == '\n' || *line == 'c' || *line == 'C') + break; + if (*line == 'a' || *line == 'A') + error (1, 0, "aborted by user"); + if (*line == 'e' || *line == 'E') + goto again; + if (*line == '!') + { + reuse_log_message = 1; + break; + } + (void) printf ("Unknown input\n"); + } + } + free (orig_message); + (void) unlink_file (fname); +} + +/* + * callback proc for Parse_Info for rcsinfo templates this routine basically + * copies the matching template onto the end of the tempfile we are setting + * up + */ +/* ARGSUSED */ +static int +rcsinfo_proc (repository, template) + char *repository; + char *template; +{ + static char *last_template; + FILE *tfp; + char line[MAXLINELEN]; + + /* nothing to do if the last one included is the same as this one */ + if (last_template && strcmp (last_template, template) == 0) + return (0); + if (last_template) + free (last_template); + last_template = xstrdup (template); + + if ((tfp = fopen (template, "r")) != NULL) + { + while (fgets (line, sizeof (line), tfp) != NULL) + (void) fputs (line, fp); + (void) fclose (tfp); + return (0); + } + else + { + error (0, 0, "Couldn't open rcsinfo template file %s", template); + return (1); + } +} + +/* + * Uses setup_tmpfile() to pass the updated message on directly to any + * logfile programs that have a regular expression match for the checked in + * directory in the source repository. The log information is fed into the + * specified program as standard input. + */ +static char *title; +static FILE *logfp; +static char *message; +static char *revision; +static List *changes; + +void +Update_Logfile (repository, xmessage, xrevision, xlogfp, xchanges) + char *repository; + char *xmessage; + char *xrevision; + FILE *xlogfp; + List *xchanges; +{ + char *srepos; + + /* set up static vars for update_logfile_proc */ + message = xmessage; + revision = xrevision; + logfp = xlogfp; + changes = xchanges; + + /* figure out a good title string */ + srepos = Short_Repository (repository); + + /* allocate a chunk of memory to hold the title string */ + if (!strlist) + strlist = xmalloc (MAXLISTLEN); + strlist[0] = '\0'; + + type = T_TITLE; + (void) walklist (changes, title_proc); + type = T_ADDED; + (void) walklist (changes, title_proc); + type = T_MODIFIED; + (void) walklist (changes, title_proc); + type = T_REMOVED; + (void) walklist (changes, title_proc); + title = xmalloc (strlen (srepos) + strlen (strlist) + 1 + 2); /* for 's */ + (void) sprintf (title, "'%s%s'", srepos, strlist); + + /* to be nice, free up this chunk of memory */ + free (strlist); + strlist = (char *) NULL; + + /* call Parse_Info to do the actual logfile updates */ + (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1); + + /* clean up */ + free (title); +} + +/* + * callback proc to actually do the logfile write from Update_Logfile + */ +static int +update_logfile_proc (repository, filter) + char *repository; + char *filter; +{ + return (logfile_write (repository, filter, title, message, revision, + logfp, changes)); +} + +/* + * concatenate each name onto strlist + */ +static int +title_proc (p) + Node *p; +{ + if (p->data == (char *) type) + { + (void) strcat (strlist, " "); + (void) strcat (strlist, p->key); + } + return (0); +} + +/* + * Since some systems don't define this... + */ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +/* + * Writes some stuff to the logfile "filter" and returns the status of the + * filter program. + */ +static int +logfile_write (repository, filter, title, message, revision, logfp, changes) + char *repository; + char *filter; + char *title; + char *message; + char *revision; + FILE *logfp; + List *changes; +{ + char cwd[PATH_MAX], host[MAXHOSTNAMELEN]; + FILE *pipefp, *Popen (); + char *prog = xmalloc (MAXPROGLEN); + char *cp; + int c; + + /* + * A maximum of 6 %s arguments are supported in the filter + */ + (void) sprintf (prog, filter, title, title, title, title, title, title); + if ((pipefp = Popen (prog, "w")) == NULL) + { + if (!noexec) + error (0, 0, "cannot write entry to log filter: %s", prog); + free (prog); + return (1); + } + if (gethostname (host, sizeof (host)) < 0) + (void) strcpy (host, "(unknown)"); + (void) fprintf (pipefp, "Update of %s\n", repository); + (void) fprintf (pipefp, "In directory %s:%s\n\n", host, + ((cp = getwd (cwd)) != NULL) ? cp : cwd); + if (revision && *revision) + (void) fprintf (pipefp, "Revision/Branch: %s\n\n", revision); + setup_tmpfile (pipefp, "", changes); + (void) fprintf (pipefp, "Log Message:\n%s\n", message); + if (logfp != (FILE *) 0) + { + (void) fprintf (pipefp, "Status:\n"); + (void) rewind (logfp); + while ((c = getc (logfp)) != EOF) + (void) putc ((char) c, pipefp); + } + free (prog); + return (pclose (pipefp)); +} + +/* + * We choose to use the *last* match within the editinfo file for this + * repository. This allows us to have a global editinfo program for the + * root of some hierarchy, for example, and different ones within different + * sub-directories of the root (like a special checker for changes made to + * the "src" directory versus changes made to the "doc" or "test" + * directories. + */ +/* ARGSUSED */ +static int +editinfo_proc(repository, editor) + char *repository; + char *editor; +{ + /* nothing to do if the last match is the same as this one */ + if (editinfo_editor && strcmp (editinfo_editor, editor) == 0) + return (0); + if (editinfo_editor) + free (editinfo_editor); + + editinfo_editor = xstrdup (editor); + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/main.c b/gnu/usr.bin/cvs/cvs/main.c new file mode 100644 index 0000000..ce10eb8 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/main.c @@ -0,0 +1,444 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License + * as specified in the README file that comes with the CVS 1.3 kit. + * + * This is the main C driver for the CVS system. + * + * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing + * the shell-script CVS system that this is based on. + * + * Usage: + * cvs [options] command [options] [files/modules...] + * + * Where "command" is composed of: + * admin RCS command + * checkout Check out a module/dir/file + * export Like checkout, but used for exporting sources + * update Brings work tree in sync with repository + * commit Checks files into the repository + * diff Runs diffs between revisions + * log Prints "rlog" information for files + * add Adds an entry to the repository + * remove Removes an entry from the repository + * status Status info on the revisions + * rdiff "patch" format diff listing between releases + * tag Add/delete a symbolic tag to the RCS file + * rtag Add/delete a symbolic tag to the RCS file + * import Import sources into CVS, using vendor branches + * release Indicate that Module is no longer in use. + * history Display history of Users and Modules. + */ + +#include "cvs.h" +#include "patchlevel.h" + +char rcsid[] = "@(#)main.c 1.64 92/03/31\n"; + +extern char *getenv (); + +char *program_name; +char *command_name = ""; + +int use_editor = TRUE; +int cvswrite = !CVSREAD_DFLT; +int really_quiet = FALSE; +int quiet = FALSE; +int trace = FALSE; +int noexec = FALSE; +int logoff = FALSE; + +char *CurDir; + +/* + * Defaults, for the environment variables that are not set + */ +char *Rcsbin = RCSBIN_DFLT; +char *Editor = EDITOR_DFLT; +char *CVSroot = CVSROOT_DFLT; + +#if __STDC__ +int add (int argc, char **argv); +int admin (int argc, char **argv); +int checkout (int argc, char **argv); +int commit (int argc, char **argv); +int diff (int argc, char **argv); +int history (int argc, char **argv); +int import (int argc, char **argv); +int cvslog (int argc, char **argv); +int patch (int argc, char **argv); +int release (int argc, char **argv); +int cvsremove (int argc, char **argv); +int rtag (int argc, char **argv); +int status (int argc, char **argv); +int tag (int argc, char **argv); +int update (int argc, char **argv); +#else +int add (); +int admin (); +int checkout (); +int commit (); +int diff (); +int history (); +int import (); +int cvslog (); +int patch (); +int release (); +int cvsremove (); +int rtag (); +int status (); +int tag (); +int update (); +#endif /* __STDC__ */ + +struct cmd +{ + char *fullname; /* Full name of the function (e.g. "commit") */ + char *nick1; /* alternate name (e.g. "ci") */ + char *nick2; /* another alternate names (e.g. "ci") */ + int (*func) (); /* Function takes (argc, argv) arguments. */ +} cmds[] = + +{ + { "add", "ad", "new", add }, + { "admin", "adm", "rcs", admin }, + { "checkout", "co", "get", checkout }, + { "commit", "ci", "com", commit }, + { "diff", "di", "dif", diff }, + { "export", "exp", "ex", checkout }, + { "history", "hi", "his", history }, + { "import", "im", "imp", import }, + { "log", "lo", "rlog", cvslog }, + { "rdiff", "patch", "pa", patch }, + { "release", "re", "rel", release }, + { "remove", "rm", "delete", cvsremove }, + { "status", "st", "stat", status }, + { "rtag", "rt", "rfreeze", rtag }, + { "tag", "ta", "freeze", tag }, + { "update", "up", "upd", update }, + { NULL, NULL, NULL, NULL }, +}; + +static char *usg[] = +{ + "Usage: %s [cvs-options] command [command-options] [files...]\n", + " Where 'cvs-options' are:\n", + " -H Displays Usage information for command\n", + " -Q Cause CVS to be really quiet.\n", + " -q Cause CVS to be somewhat quiet.\n", + " -r Make checked-out files read-only\n", + " -w Make checked-out files read-write (default)\n", + " -l Turn History logging off\n", + " -n Do not execute anything that will change the disk\n", + " -t Show trace of program execution -- Try with -n\n", + " -v CVS version and copyright\n", + " -b bindir Find RCS programs in 'bindir'\n", + " -e editor Use 'editor' for editing log information\n", + " -d CVS_root Overrides $CVSROOT as the root of the CVS tree\n", + "\n", + " and where 'command' is:\n", + " add Adds a new file/directory to the repository\n", + " admin Administration front end for rcs\n", + " checkout Checkout sources for editing\n", + " commit Checks files into the repository\n", + " diff Runs diffs between revisions\n", + " history Shows status of files and users\n", + " import Import sources into CVS, using vendor branches\n", + " export Export sources from CVS, similar to checkout\n", + " log Prints out 'rlog' information for files\n", + " rdiff 'patch' format diffs between releases\n", + " release Indicate that a Module is no longer in use\n", + " remove Removes an entry from the repository\n", + " status Status info on the revisions\n", + " tag Add a symbolic tag to checked out version of RCS file\n", + " rtag Add a symbolic tag to the RCS file\n", + " update Brings work tree in sync with repository\n", + NULL, +}; + +static SIGTYPE +main_cleanup () +{ + exit (1); +} + +int +main (argc, argv) + int argc; + char *argv[]; +{ + extern char *version_string; + char *cp; + struct cmd *cm; + int c, help = FALSE, err = 0; + int rcsbin_update_env, cvs_update_env; + char tmp[PATH_MAX]; + + /* + * Just save the last component of the path for error messages + */ + if ((program_name = rindex (argv[0], '/')) == NULL) + program_name = argv[0]; + else + program_name++; + + CurDir = xmalloc (PATH_MAX); + if (!getwd (CurDir)) + error (1, 0, "cannot get working directory: %s", CurDir); + + /* + * Query the environment variables up-front, so that + * they can be overridden by command line arguments + */ + rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */ + if ((cp = getenv (RCSBIN_ENV)) != NULL) + { + Rcsbin = cp; + rcsbin_update_env = 0; /* it's already there */ + } + if ((cp = getenv (EDITOR_ENV)) != NULL) + Editor = cp; + if ((cp = getenv (CVSROOT_ENV)) != NULL) + { + CVSroot = cp; + cvs_update_env = 0; /* it's already there */ + } + if (getenv (CVSREAD_ENV) != NULL) + cvswrite = FALSE; + + optind = 1; + while ((c = gnu_getopt (argc, argv, "Qqrwtnlvb:e:d:H")) != -1) + { + switch (c) + { + case 'Q': + really_quiet = TRUE; + /* FALL THROUGH */ + case 'q': + quiet = TRUE; + break; + case 'r': + cvswrite = FALSE; + break; + case 'w': + cvswrite = TRUE; + break; + case 't': + trace = TRUE; + break; + case 'n': + noexec = TRUE; + case 'l': /* Fall through */ + logoff = TRUE; + break; + case 'v': + (void) fputs (rcsid, stdout); + (void) fputs (version_string, stdout); + (void) sprintf (tmp, "Patch Level: %d\n", PATCHLEVEL); + (void) fputs (tmp, stdout); + (void) fputs ("\nCopyright (c) 1992, Brian Berliner and Jeff Polk\nCopyright (c) 1989-1992, Brian Berliner\n\nCVS may be copied only under the terms of the GNU General Public License,\na copy of which can be found with the CVS 1.3 distribution kit.\n", stdout); + exit (0); + break; + case 'b': + Rcsbin = optarg; + rcsbin_update_env = 1; /* need to update environment */ + break; + case 'e': + Editor = optarg; + break; + case 'd': + CVSroot = optarg; + cvs_update_env = 1; /* need to update environment */ + break; + case 'H': + help = TRUE; + break; + case '?': + default: + usage (usg); + } + } + argc -= optind; + argv += optind; + if (argc < 1) + usage (usg); + + /* + * XXX - Compatibility. This can be removed in the release after CVS 1.3. + * Try to rename the CVSROOT.adm file to CVSROOT, unless there already is + * a CVSROOT directory. + */ + if (CVSroot != NULL) + { + char rootadm[PATH_MAX]; + char orootadm[PATH_MAX]; + + (void) sprintf (rootadm, "%s/%s", CVSroot, CVSROOTADM); + if (!isdir (rootadm)) + { + (void) sprintf (orootadm, "%s/%s", CVSroot, OCVSROOTADM); + if (isdir (orootadm)) + (void) rename (orootadm, rootadm); + } + strip_path (CVSroot); + } + + /* + * Specifying just the '-H' flag to the sub-command causes a Usage + * message to be displayed. + */ + command_name = cp = argv[0]; + if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0)) + argc = -1; + else + { + /* + * Check to see if we can write into the history file. If not, + * we assume that we can't work in the repository. + * BUT, only if the history file exists. + */ + { + char path[PATH_MAX]; + int save_errno; + + if (!CVSroot || !*CVSroot) + error (1, 0, "You don't have a %s environment variable", + CVSROOT_ENV); + (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM); + if (access (path, R_OK | X_OK)) + { + save_errno = errno; + error (0, 0, + "Sorry, you don't have sufficient access to %s", CVSroot); + error (1, save_errno, "%s", path); + } + (void) strcat (path, "/"); + (void) strcat (path, CVSROOTADM_HISTORY); + if (isfile (path) && access (path, R_OK | W_OK)) + { + save_errno = errno; + error (0, 0, + "Sorry, you don't have read/write access to the history file"); + error (1, save_errno, "%s", path); + } + } + } + +#ifndef PUTENV_MISSING + /* Now, see if we should update the environment with the Rcsbin value */ + if (cvs_update_env) + { + char *env; + + env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1); + (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot); + (void) putenv (env); + /* do not free env, as putenv has control of it */ + } + if (rcsbin_update_env) + { + char *env; + + env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1); + (void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin); + (void) putenv (env); + /* do not free env, as putenv has control of it */ + } +#endif + + /* + * If Rcsbin is set to something, make sure it is terminated with + * a slash character. If not, add one. + */ + if (*Rcsbin) + { + int len = strlen (Rcsbin); + char *rcsbin; + + if (Rcsbin[len - 1] != '/') + { + rcsbin = Rcsbin; + Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */ + (void) strcpy (Rcsbin, rcsbin); + (void) strcat (Rcsbin, "/"); + } + } + + for (cm = cmds; cm->fullname; cm++) + { + if (cm->nick1 && !strcmp (cp, cm->nick1)) + break; + if (cm->nick2 && !strcmp (cp, cm->nick2)) + break; + if (!strcmp (cp, cm->fullname)) + break; + } + + if (!cm->fullname) + usage (usg); /* no match */ + else + { + command_name = cm->fullname; /* Global pointer for later use */ + (void) SIG_register (SIGHUP, main_cleanup); + (void) SIG_register (SIGINT, main_cleanup); + (void) SIG_register (SIGQUIT, main_cleanup); + (void) SIG_register (SIGPIPE, main_cleanup); + (void) SIG_register (SIGTERM, main_cleanup); + +#ifndef SETVBUF_MISSING + /* + * Make stdout line buffered, so 'tail -f' can monitor progress. + * Patch creates too much output to monitor and it runs slowly. + */ + if (strcmp (cm->fullname, "patch")) + (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0); +#endif + + err = (*(cm->func)) (argc, argv); + } + /* + * If the command's error count is modulo 256, we need to change it + * so that we don't overflow the 8-bits we get to report exit status + */ + if (err && (err % 256) == 0) + err = 1; + Lock_Cleanup (); + return (err); +} + +char * +Make_Date (rawdate) + char *rawdate; +{ + struct tm *ftm; + time_t unixtime; + char date[256]; /* XXX bigger than we'll ever need? */ + char *ret; + + unixtime = get_date (rawdate, (struct timeb *) NULL); + if (unixtime == (time_t) - 1) + error (1, 0, "Can't parse date/time: %s", rawdate); +#ifdef HAVE_RCS5 + ftm = gmtime (&unixtime); +#else + ftm = localtime (&unixtime); +#endif + (void) sprintf (date, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + ret = xstrdup (date); + return (ret); +} + +void +usage (cpp) + register char **cpp; +{ + (void) fprintf (stderr, *cpp++, program_name, command_name); + for (; *cpp; cpp++) + (void) fprintf (stderr, *cpp); + exit (1); +} diff --git a/gnu/usr.bin/cvs/cvs/modules.c b/gnu/usr.bin/cvs/cvs/modules.c new file mode 100644 index 0000000..b0658d7 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/modules.c @@ -0,0 +1,810 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License + * as specified in the README file that comes with the CVS 1.3 kit. + * + * Modules + * + * Functions for accessing the modules file. + * + * The modules file supports basically three formats of lines: + * key [options] directory files... [ -x directory [files] ] ... + * key [options] directory [ -x directory [files] ] ... + * key -a aliases... + * + * The -a option allows an aliasing step in the parsing of the modules + * file. The "aliases" listed on a line following the -a are + * processed one-by-one, as if they were specified as arguments on the + * command line. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)modules.c 1.57 92/04/10"; +#endif + +struct sortrec +{ + char *modname; + char *status; + char *rest; + char *comment; +}; + +#if __STDC__ +static int sort_order (CONST PTR l, CONST PTR r); +static void save_d (char *k, int ks, char *d, int ds); +#else +static int sort_order (); +static void save_d (); +#endif /* __STDC__ */ + + +/* + * Open the modules file, and die if the CVSROOT environment variable + * was not set. If the modules file does not exist, that's fine, and + * a warning message is displayed and a NULL is returned. + */ +DBM * +open_module () +{ + char mfile[PATH_MAX]; + + if (CVSroot == NULL) + { + (void) fprintf (stderr, + "%s: must set the CVSROOT environment variable\n", + program_name); + error (1, 0, "or specify the '-d' option to %s", program_name); + } + (void) sprintf (mfile, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_MODULES); + return (dbm_open (mfile, O_RDONLY, 0666)); +} + +/* + * Close the modules file, if the open succeeded, that is + */ +void +close_module (db) + DBM *db; +{ + if (db != NULL) + dbm_close (db); +} + +/* + * This is the recursive function that processes a module name. + * It calls back the passed routine for each directory of a module + * It runs the post checkout or post tag proc from the modules file + */ +int +do_module (db, mname, m_type, msg, callback_proc, where, + shorten, local_specified, run_module_prog, extra_arg) + DBM *db; + char *mname; + enum mtype m_type; + char *msg; + int (*callback_proc) (); + char *where; + int shorten; + int local_specified; + int run_module_prog; + char *extra_arg; +{ + char *checkin_prog = NULL; + char *checkout_prog = NULL; + char *tag_prog = NULL; + char *update_prog = NULL; + char cwd[PATH_MAX]; + char line[MAXLINELEN]; + char *xmodargv[MAXFILEPERDIR]; + char **modargv; + char *value; + char *zvalue; + char *mwhere = NULL; + char *mfile = NULL; + char *spec_opt = NULL; + char xvalue[PATH_MAX]; + int modargc, alias = 0; + datum key, val; + char *cp; + int c, err = 0; + + /* remember where we start */ + if (getwd (cwd) == NULL) + error (1, 0, "cannot get current working directory: %s", cwd); + + /* strip extra stuff from the module name */ + strip_path (mname); + + /* + * Look up the module using the following scheme: + * 1) look for mname as a module name + * 2) look for mname as a directory + * 3) look for mname as a file + * 4) take mname up to the first slash and look it up as a module name + * (this is for checking out only part of a module) + */ + + /* look it up as a module name */ + key.dptr = mname; + key.dsize = strlen (key.dptr); + if (db != NULL) + val = dbm_fetch (db, key); + else + val.dptr = NULL; + if (val.dptr != NULL) + { + /* null terminate the value XXX - is this space ours? */ + val.dptr[val.dsize] = '\0'; + + /* If the line ends in a comment, strip it off */ + if ((cp = index (val.dptr, '#')) != NULL) + { + do + *cp-- = '\0'; + while (isspace (*cp)); + } + value = val.dptr; + mwhere = xstrdup (mname); + goto found; + } + else + { + char file[PATH_MAX]; + char attic_file[PATH_MAX]; + char *acp; + + /* check to see if mname is a directory or file */ + + (void) sprintf (file, "%s/%s", CVSroot, mname); + if ((acp = rindex (mname, '/')) != NULL) + { + *acp = '\0'; + (void) sprintf (attic_file, "%s/%s/%s/%s%s", CVSroot, mname, + CVSATTIC, acp + 1, RCSEXT); + *acp = '/'; + } + else + (void) sprintf (attic_file, "%s/%s/%s%s", CVSroot, CVSATTIC, + mname, RCSEXT); + + if (isdir (file)) + { + value = mname; + goto found; + } + else + { + (void) strcat (file, RCSEXT); + if (isfile (file) || isfile (attic_file)) + { + /* if mname was a file, we have to split it into "dir file" */ + if ((cp = rindex (mname, '/')) != NULL && cp != mname) + { + char *slashp; + + /* put the ' ' in a copy so we don't mess up the original */ + value = strcpy (xvalue, mname); + slashp = rindex (value, '/'); + *slashp = ' '; + } + else + { + /* + * the only '/' at the beginning or no '/' at all + * means the file we are interested in is in CVSROOT + * itself so the directory should be '.' + */ + if (cp == mname) + { + /* drop the leading / if specified */ + value = strcpy (xvalue, ". "); + (void) strcat (xvalue, mname + 1); + } + else + { + /* otherwise just copy it */ + value = strcpy (xvalue, ". "); + (void) strcat (xvalue, mname); + } + } + goto found; + } + } + } + + /* look up everything to the first / as a module */ + if (mname[0] != '/' && (cp = index (mname, '/')) != NULL) + { + /* Make the slash the new end of the string temporarily */ + *cp = '\0'; + key.dptr = mname; + key.dsize = strlen (key.dptr); + + /* do the lookup */ + if (db != NULL) + val = dbm_fetch (db, key); + else + val.dptr = NULL; + + /* if we found it, clean up the value and life is good */ + if (val.dptr != NULL) + { + char *cp2; + + /* null terminate the value XXX - is this space ours? */ + val.dptr[val.dsize] = '\0'; + + /* If the line ends in a comment, strip it off */ + if ((cp2 = index (val.dptr, '#')) != NULL) + { + do + *cp2-- = '\0'; + while (isspace (*cp2)); + } + value = val.dptr; + + /* mwhere gets just the module name */ + mwhere = xstrdup (mname); + mfile = cp + 1; + + /* put the / back in mname */ + *cp = '/'; + + goto found; + } + + /* put the / back in mname */ + *cp = '/'; + } + + /* if we got here, we couldn't find it using our search, so give up */ + error (0, 0, "cannot find module `%s' - ignored", mname); + err++; + if (mwhere) + free (mwhere); + return (err); + + + /* + * At this point, we found what we were looking for in one + * of the many different forms. + */ + found: + + /* copy value to our own string since if we go recursive we'll be + really screwed if we do another dbm lookup */ + zvalue = xstrdup (value); + value = zvalue; + + /* search the value for the special delimiter and save for later */ + if ((cp = index (value, CVSMODULE_SPEC)) != NULL) + { + *cp = '\0'; /* null out the special char */ + spec_opt = cp + 1; /* save the options for later */ + + if (cp != value) /* strip whitespace if necessary */ + while (isspace (*--cp)) + *cp = '\0'; + + if (cp == value) + { + /* + * we had nothing but special options, so skip arg + * parsing and regular stuff entirely + * + * If there were only special ones though, we must + * make the appropriate directory and cd to it + */ + char *dir; + + /* XXX - XXX - MAJOR HACK - DO NOT SHIP - this needs to + be !pipeout, but we don't know that here yet */ + if (!run_module_prog) + goto out; + + dir = where ? where : mname; + /* XXX - think about making null repositories at each dir here + instead of just at the bottom */ + make_directories (dir); + if (chdir (dir) < 0) + { + error (0, errno, "cannot chdir to %s", dir); + spec_opt = NULL; + err++; + goto out; + } + if (!isfile (CVSADM) && !isfile (OCVSADM)) + { + char nullrepos[PATH_MAX]; + + (void) sprintf (nullrepos, "%s/%s/%s", CVSroot, + CVSROOTADM, CVSNULLREPOS); + if (!isfile (nullrepos)) + (void) mkdir (nullrepos, 0777); + Create_Admin (".", nullrepos, (char *) NULL, (char *) NULL); + if (!noexec) + { + FILE *fp; + + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_ENTSTAT); + } + } + out: + goto do_special; + } + } + + /* don't do special options only part of a module was specified */ + if (mfile != NULL) + spec_opt = NULL; + + /* + * value now contains one of the following: + * 1) dir + * 2) dir file + * 3) the value from modules without any special args + * [ args ] dir [file] [file] ... + * or -a module [ module ] ... + */ + + /* Put the value on a line with XXX prepended for getopt to eat */ + (void) sprintf (line, "%s %s", "XXX", value); + + /* turn the line into an argv[] array */ + line2argv (&modargc, xmodargv, line); + modargv = xmodargv; + + /* parse the args */ + optind = 1; + while ((c = gnu_getopt (modargc, modargv, CVSMODULE_OPTS)) != -1) + { + switch (c) + { + case 'a': + alias = 1; + break; + case 'd': + if (mwhere) + free (mwhere); + mwhere = xstrdup (optarg); + break; + case 'i': + checkin_prog = optarg; + break; + case 'l': + local_specified = 1; + case 'o': + checkout_prog = optarg; + break; + case 't': + tag_prog = optarg; + break; + case 'u': + update_prog = optarg; + break; + case '?': + error (0, 0, + "modules file has invalid option for key %s value %s", + key.dptr, val.dptr); + err++; + if (mwhere) + free (mwhere); + free (zvalue); + return (err); + } + } + modargc -= optind; + modargv += optind; + if (modargc == 0) + { + error (0, 0, "modules file missing directory for module %s", mname); + if (mwhere) + free (mwhere); + free (zvalue); + return (++err); + } + + /* if this was an alias, call ourselves recursively for each module */ + if (alias) + { + int i; + + for (i = 0; i < modargc; i++) + err += do_module (db, modargv[i], m_type, msg, callback_proc, + where, shorten, local_specified, + run_module_prog, extra_arg); + if (mwhere) + free (mwhere); + free (zvalue); + return (err); + } + + /* otherwise, process this module */ + err += callback_proc (&modargc, modargv, where, mwhere, mfile, shorten, + local_specified, mname, msg); + + /* clean up */ + free_names (&modargc, modargv); + + /* if there were special include args, process them now */ + + do_special: + + /* blow off special options if -l was specified */ + if (local_specified) + spec_opt = NULL; + + while (spec_opt != NULL) + { + char *next_opt; + + cp = index (spec_opt, CVSMODULE_SPEC); + if (cp != NULL) + { + /* save the beginning of the next arg */ + next_opt = cp + 1; + + /* strip whitespace off the end */ + do + *cp = '\0'; + while (isspace (*--cp)); + } + else + next_opt = NULL; + + /* strip whitespace from front */ + while (isspace (*spec_opt)) + spec_opt++; + + if (*spec_opt == '\0') + error (0, 0, "Mal-formed %c option for module %s - ignored", + CVSMODULE_SPEC, mname); + else + err += do_module (db, spec_opt, m_type, msg, callback_proc, + (char *) NULL, 0, local_specified, + run_module_prog, extra_arg); + spec_opt = next_opt; + } + + /* write out the checkin/update prog files if necessary */ + if (err == 0 && !noexec && m_type == CHECKOUT && run_module_prog) + { + FILE *fp; + + if (checkin_prog != NULL) + { + fp = open_file (CVSADM_CIPROG, "w+"); + (void) fprintf (fp, "%s\n", checkin_prog); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_CIPROG); + } + if (update_prog != NULL) + { + fp = open_file (CVSADM_UPROG, "w+"); + (void) fprintf (fp, "%s\n", update_prog); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_UPROG); + } + } + + /* cd back to where we started */ + if (chdir (cwd) < 0) + error (1, errno, "failed chdir to %s!", cwd); + + /* run checkout or tag prog if appropriate */ + if (err == 0 && run_module_prog) + { + if ((m_type == TAG && tag_prog != NULL) || + (m_type == CHECKOUT && checkout_prog != NULL)) + { + /* + * If a relative pathname is specified as the checkout or + * tag proc, try to tack on the current "where" value. + * if we can't find a matching program, just punt and use + * whatever is specified in the modules file. + */ + char real_prog[PATH_MAX]; + char *prog = (m_type == TAG ? tag_prog : checkout_prog); + char *real_where = (where != NULL ? where : mwhere); + + if ((*prog != '/') && (*prog != '.')) + { + (void) sprintf (real_prog, "%s/%s", real_where, prog); + if (isfile (real_prog)) + prog = real_prog; + } + + run_setup ("%s %s", prog, real_where); + if (extra_arg) + run_arg (extra_arg); + + if (!quiet) + { + (void) printf ("%s %s: Executing '", program_name, + command_name); + run_print (stdout); + (void) printf ("'\n"); + } + err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + } + + /* clean up */ + if (mwhere) + free (mwhere); + free (zvalue); + + return (err); +} + +/* - Read all the records from the modules database into an array. + - Sort the array depending on what format is desired. + - Print the array in the format desired. + + Currently, there are only two "desires": + + 1. Sort by module name and format the whole entry including switches, + files and the comment field: (Including aliases) + + modulename -s switches, one per line, even if + -i it has many switches. + Directories and files involved, formatted + to cover multiple lines if necessary. + # Comment, also formatted to cover multiple + # lines if necessary. + + 2. Sort by status field string and print: (*not* including aliases) + + modulename STATUS Directories and files involved, formatted + to cover multiple lines if necessary. + # Comment, also formatted to cover multiple + # lines if necessary. +*/ + +static struct sortrec *s_head; + +static int s_max = 0; /* Number of elements allocated */ +static int s_count = 0; /* Number of elements used */ + +static int Status; +static char def_status[] = "NONE"; + +/* Sort routine for qsort: + - If we want the "Status" field to be sorted, check it first. + - Then compare the "module name" fields. Since they are unique, we don't + have to look further. +*/ +static int +sort_order (l, r) + CONST PTR l; + CONST PTR r; +{ + int i; + CONST struct sortrec *left = (CONST struct sortrec *) l; + CONST struct sortrec *right = (CONST struct sortrec *) r; + + if (Status) + { + /* If Sort by status field, compare them. */ + if ((i = strcmp (left->status, right->status)) != 0) + return (i); + } + return (strcmp (left->modname, right->modname)); +} + +static void +save_d (k, ks, d, ds) + char *k; + int ks; + char *d; + int ds; +{ + char *cp, *cp2; + struct sortrec *s_rec; + + if (Status && *d == '-' && *(d + 1) == 'a') + return; /* We want "cvs co -s" and it is an alias! */ + + if (s_count == s_max) + { + s_max += 64; + s_head = (struct sortrec *) xrealloc ((char *) s_head, s_max * sizeof (*s_head)); + } + s_rec = &s_head[s_count]; + s_rec->modname = cp = xmalloc (ks + 1); + (void) strncpy (cp, k, ks); + *(cp + ks) = '\0'; + + s_rec->rest = cp2 = xmalloc (ds + 1); + cp = d; + *(cp + ds) = '\0'; /* Assumes an extra byte at end of static dbm buffer */ + + while (isspace (*cp)) + cp++; + /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */ + while (*cp) + { + if (isspace (*cp)) + { + *cp2++ = ' '; + while (isspace (*cp)) + cp++; + } + else + *cp2++ = *cp++; + } + *cp2 = '\0'; + + /* Look for the "-s statusvalue" text */ + if (Status) + { + s_rec->status = def_status; + + /* Minor kluge, but general enough to maintain */ + for (cp = s_rec->rest; (cp2 = index (cp, '-')) != NULL; cp = ++cp2) + { + if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ') + { + s_rec->status = (cp2 += 3); + while (*cp2 != ' ') + cp2++; + *cp2++ = '\0'; + cp = cp2; + break; + } + } + } + else + cp = s_rec->rest; + + /* Find comment field, clean up on all three sides & compress blanks */ + if ((cp2 = cp = index (cp, '#')) != NULL) + { + if (*--cp2 == ' ') + *cp2 = '\0'; + if (*++cp == ' ') + cp++; + s_rec->comment = cp; + } + else + s_rec->comment = ""; + + s_count++; +} + +void +cat_module (status) + int status; +{ + DBM *db; + datum key, val; + int i, c, wid, argc, cols = 80, indent, fill; + int moduleargc; + struct sortrec *s_h; + char *cp, *cp2, **argv; + char line[MAXLINELEN], *moduleargv[MAXFILEPERDIR]; + +#ifdef sun +#ifdef TIOCGSIZE + struct ttysize ts; + + (void) ioctl (0, TIOCGSIZE, &ts); + cols = ts.ts_cols; +#endif +#else +#ifdef TIOCGWINSZ + struct winsize ws; + + (void) ioctl (0, TIOCGWINSZ, &ws); + cols = ws.ws_col; +#endif +#endif + + Status = status; + + /* Read the whole modules file into allocated records */ + if (!(db = open_module ())) + error (1, 0, "failed to open the modules file"); + + for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db)) + { + val = dbm_fetch (db, key); + if (val.dptr != NULL) + save_d (key.dptr, key.dsize, val.dptr, val.dsize); + } + + /* Sort the list as requested */ + qsort ((PTR) s_head, s_count, sizeof (struct sortrec), sort_order); + + /* + * Run through the sorted array and format the entries + * indent = space for modulename + space for status field + */ + indent = 12 + (status * 12); + fill = cols - (indent + 2); + for (s_h = s_head, i = 0; i < s_count; i++, s_h++) + { + /* Print module name (and status, if wanted) */ + (void) printf ("%-12s", s_h->modname); + if (status) + { + (void) printf (" %-11s", s_h->status); + if (s_h->status != def_status) + *(s_h->status + strlen (s_h->status)) = ' '; + } + + /* Parse module file entry as command line and print options */ + (void) sprintf (line, "%s %s", s_h->modname, s_h->rest); + line2argv (&moduleargc, moduleargv, line); + argc = moduleargc; + argv = moduleargv; + + optind = 1; + wid = 0; + while ((c = gnu_getopt (argc, argv, CVSMODULE_OPTS)) != -1) + { + if (!status) + { + if (c == 'a') + { + (void) printf (" -a"); + wid += 3; /* Could just set it to 3 */ + } + else + { + if (strlen (optarg) + 4 + wid > (unsigned) fill) + { + (void) printf ("\n%*s", indent, ""); + wid = 0; + } + (void) printf (" -%c %s", c, optarg); + wid += strlen (optarg) + 4; + } + } + } + argc -= optind; + argv += optind; + + /* Format and Print all the files and directories */ + for (; argc--; argv++) + { + if (strlen (*argv) + wid > (unsigned) fill) + { + (void) printf ("\n%*s", indent, ""); + wid = 0; + } + (void) printf (" %s", *argv); + wid += strlen (*argv) + 1; + } + (void) printf ("\n"); + + /* Format the comment field -- save_d (), compressed spaces */ + for (cp2 = cp = s_h->comment; *cp; cp2 = cp) + { + (void) printf ("%*s # ", indent, ""); + if (strlen (cp2) < (unsigned) (fill - 2)) + { + (void) printf ("%s\n", cp2); + break; + } + cp += fill - 2; + while (*cp != ' ' && cp > cp2) + cp--; + if (cp == cp2) + { + (void) printf ("%s\n", cp2); + break; + } + + *cp++ = '\0'; + (void) printf ("%s\n", cp2); + } + } +} diff --git a/gnu/usr.bin/cvs/cvs/no_diff.c b/gnu/usr.bin/cvs/cvs/no_diff.c new file mode 100644 index 0000000..4057c88 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/no_diff.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * No Difference + * + * The user file looks modified judging from its time stamp; however it needn't + * be. No_difference() finds out whether it is or not. If it is not, it + * updates the administration. + * + * returns 0 if no differences are found and non-zero otherwise + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)no_diff.c 1.35 92/03/31"; +#endif + +int +No_Difference (file, vers, entries) + char *file; + Vers_TS *vers; + List *entries; +{ + Node *p; + char tmp[L_tmpnam+1]; + int ret; + char *ts, *options; + int retcode = 0; + + if (!vers->srcfile || !vers->srcfile->path) + return (-1); /* different since we couldn't tell */ + + if (vers->entdata && vers->entdata->options) + options = xstrdup (vers->entdata->options); + else + options = xstrdup (""); + + run_setup ("%s%s -p -q -r%s %s", Rcsbin, RCS_CO, + vers->vn_user ? vers->vn_user : "", options); + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, tmpnam (tmp), RUN_TTY, RUN_REALLY)) == 0) + { + if (!iswritable (file)) /* fix the modes as a side effect */ + xchmod (file, 1); + + /* do the byte by byte compare */ + if (xcmp (file, tmp) == 0) + { + if (cvswrite == FALSE) /* fix the modes as a side effect */ + xchmod (file, 0); + + /* no difference was found, so fix the entries file */ + ts = time_stamp (file); + Register (entries, file, + vers->vn_user ? vers->vn_user : vers->vn_rcs, ts, + options, vers->tag, vers->date); + free (ts); + + /* update the entdata pointer in the vers_ts structure */ + p = findnode (entries, file); + vers->entdata = (Entnode *) p->data; + + ret = 0; + } + else + ret = 1; /* files were really different */ + } + else + { + error (0, retcode == -1 ? errno : 0, + "could not check out revision %s of %s", vers->vn_user, file); + ret = -1; /* different since we couldn't tell */ + } + + if (trace) + (void) fprintf (stderr, "-> unlink(%s)\n", tmp); + (void) unlink (tmp); + free (options); + return (ret); +} diff --git a/gnu/usr.bin/cvs/cvs/parseinfo.c b/gnu/usr.bin/cvs/cvs/parseinfo.c new file mode 100644 index 0000000..65343f5 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/parseinfo.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)parseinfo.c 1.16 92/04/10"; +#endif + +/* + * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for + * each line in the file that matches the REPOSITORY. + * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure. + */ +int +Parse_Info (infofile, repository, callproc, all) + char *infofile; + char *repository; + int (*callproc) (); + int all; +{ + int err = 0; + FILE *fp_info; + char infopath[PATH_MAX]; + char line[MAXLINELEN]; + char *default_value = NULL; + int callback_done, line_number; + char *cp, *exp, *value, *srepos; + CONST char *regex_err; + + if (CVSroot == NULL) + { + /* XXX - should be error maybe? */ + error (0, 0, "CVSROOT variable not set"); + return (1); + } + + /* find the info file and open it */ + (void) sprintf (infopath, "%s/%s/%s", CVSroot, + CVSROOTADM, infofile); + if ((fp_info = fopen (infopath, "r")) == NULL) + return (0); /* no file -> nothing special done */ + + /* strip off the CVSROOT if repository was absolute */ + srepos = Short_Repository (repository); + + /* search the info file for lines that match */ + callback_done = line_number = 0; + while (fgets (line, sizeof (line), fp_info) != NULL) + { + line_number++; + + /* skip lines starting with # */ + if (line[0] == '#') + continue; + + /* skip whitespace at beginning of line */ + for (cp = line; *cp && isspace (*cp); cp++) + ; + + /* if *cp is null, the whole line was blank */ + if (*cp == '\0') + continue; + + /* the regular expression is everything up to the first space */ + for (exp = cp; *cp && !isspace (*cp); cp++) + ; + if (*cp != '\0') + *cp++ = '\0'; + + /* skip whitespace up to the start of the matching value */ + while (*cp && isspace (*cp)) + cp++; + + /* no value to match with the regular expression is an error */ + if (*cp == '\0') + { + error (0, 0, "syntax error at line %d file %s; ignored", + line_number, infofile); + continue; + } + value = cp; + + /* strip the newline off the end of the value */ + if ((cp = rindex (value, '\n')) != NULL) + *cp = '\0'; + + /* + * At this point, exp points to the regular expression, and value + * points to the value to call the callback routine with. Evaluate + * the regular expression against srepos and callback with the value + * if it matches. + */ + + /* save the default value so we have it later if we need it */ + if (strcmp (exp, "DEFAULT") == 0) + { + default_value = xstrdup (value); + continue; + } + + /* + * For a regular expression of "ALL", do the callback always We may + * execute lots of ALL callbacks in addition to one regular matching + * callback or default + */ + if (strcmp (exp, "ALL") == 0) + { + if (all) + err += callproc (repository, value); + else + error(0, 0, "Keyword `ALL' is ignored at line %d in %s file", + line_number, infofile); + continue; + } + + /* see if the repository matched this regular expression */ + if ((regex_err = re_comp (exp)) != NULL) + { + error (0, 0, "bad regular expression at line %d file %s: %s", + line_number, infofile, regex_err); + continue; + } + if (re_exec (srepos) == 0) + continue; /* no match */ + + /* it did, so do the callback and note that we did one */ + err += callproc (repository, value); + callback_done = 1; + } + (void) fclose (fp_info); + + /* if we fell through and didn't callback at all, do the default */ + if (callback_done == 0 && default_value != NULL) + err += callproc (repository, default_value); + + /* free up space if necessary */ + if (default_value != NULL) + free (default_value); + + return (err); +} diff --git a/gnu/usr.bin/cvs/cvs/patch.c b/gnu/usr.bin/cvs/cvs/patch.c new file mode 100644 index 0000000..11134a7 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/patch.c @@ -0,0 +1,523 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Patch + * + * Create a Larry Wall format "patch" file between a previous release and the + * current head of a module, or between two releases. Can specify the + * release as either a date or a revision number. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)patch.c 1.50 92/04/10"; + +#endif + +#if __STDC__ +static SIGTYPE patch_cleanup (void); +static Dtype patch_dirproc (char *dir, char *repos, char *update_dir); +static int patch_fileproc (char *file, char *update_dir, char *repository, + List * entries, List * srcfiles); +static int patch_proc (int *pargc, char *argv[], char *xwhere, + char *mwhere, char *mfile, int shorten, + int local_specified, char *mname, char *msg); +#else +static int patch_proc (); +static int patch_fileproc (); +static Dtype patch_dirproc (); +static SIGTYPE patch_cleanup (); +#endif /* __STDC__ */ + +static int force_tag_match = 1; +static int patch_short = 0; +static int toptwo_diffs = 0; +static int local = 0; +static char *options = NULL; +static char *rev1 = NULL; +static char *rev2 = NULL; +static char *date1 = NULL; +static char *date2 = NULL; +static char tmpfile1[L_tmpnam+1], tmpfile2[L_tmpnam+1], tmpfile3[L_tmpnam+1]; +static int unidiff = 0; + +static char *patch_usage[] = +{ + "Usage: %s %s [-Qflq] [-c|-u] [-s|-t] [-V %%d]\n", + " -r rev|-D date [-r rev2 | -D date2] modules...\n", + "\t-Q\tReally quiet.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-c\tContext diffs (default)\n", + "\t-u\tUnidiff format.\n", + "\t-s\tShort patch - one liner per file.\n", + "\t-t\tTop two diffs - last change made to the file.\n", + "\t-D date\tDate.\n", + "\t-r rev\tRevision - symbolic or numeric.\n", + "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n", + NULL +}; + +int +patch (argc, argv) + int argc; + char *argv[]; +{ + register int i; + int c; + int err = 0; + DBM *db; + + if (argc == -1) + usage (patch_usage); + + optind = 1; + while ((c = gnu_getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1) + { + switch (c) + { + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 't': + toptwo_diffs = 1; + break; + case 's': + patch_short = 1; + break; + case 'D': + if (rev2 != NULL || date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (rev1 != NULL || date1 != NULL) + date2 = Make_Date (optarg); + else + date1 = Make_Date (optarg); + break; + case 'r': + if (rev2 != NULL || date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (rev1 != NULL || date1 != NULL) + rev2 = optarg; + else + rev1 = optarg; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'V': + if (atoi (optarg) <= 0) + error (1, 0, "must specify a version number to -V"); + if (options) + free (options); + options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */ + (void) sprintf (options, "-V%s", optarg); + break; + case 'u': + unidiff = 1; /* Unidiff */ + break; + case 'c': /* Context diff */ + unidiff = 0; + break; + case '?': + default: + usage (patch_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* Sanity checks */ + if (argc < 1) + usage (patch_usage); + + if (toptwo_diffs && patch_short) + error (1, 0, "-t and -s options are mutually exclusive"); + if (toptwo_diffs && (date1 != NULL || date2 != NULL || + rev1 != NULL || rev2 != NULL)) + error (1, 0, "must not specify revisions/dates with -t option!"); + + if (!toptwo_diffs && (date1 == NULL && date2 == NULL && + rev1 == NULL && rev2 == NULL)) + error (1, 0, "must specify at least one revision/date!"); + if (date1 != NULL && date2 != NULL) + if (RCS_datecmp (date1, date2) >= 0) + error (1, 0, "second date must come after first date!"); + + /* if options is NULL, make it a NULL string */ + if (options == NULL) + options = xstrdup (""); + + /* clean up if we get a signal */ + (void) SIG_register (SIGHUP, patch_cleanup); + (void) SIG_register (SIGINT, patch_cleanup); + (void) SIG_register (SIGQUIT, patch_cleanup); + (void) SIG_register (SIGPIPE, patch_cleanup); + (void) SIG_register (SIGTERM, patch_cleanup); + + db = open_module (); + for (i = 0; i < argc; i++) + err += do_module (db, argv[i], PATCH, "Patching", patch_proc, + (char *) NULL, 0, 0, 0, (char *) NULL); + close_module (db); + free (options); + patch_cleanup (); + return (err); +} + +/* + * callback proc for doing the real work of patching + */ +/* ARGSUSED */ +static char where[PATH_MAX]; +static int +patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified, + mname, msg) + int *pargc; + char *argv[]; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *mname; + char *msg; +{ + int err = 0; + int which; + char repository[PATH_MAX]; + + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + (void) strcpy (where, argv[0]); + + /* if mfile isn't null, we need to set up to do only part of the module */ + if (mfile != NULL) + { + char *cp; + char path[PATH_MAX]; + + /* if the portion of the module is a path, put the dir part on repos */ + if ((cp = rindex (mfile, '/')) != NULL) + { + *cp = '\0'; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + (void) sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* directory means repository gets the dir tacked on */ + (void) strcpy (repository, path); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + } + else + { + int i; + + /* a file means muck argv */ + for (i = 1; i < *pargc; i++) + free (argv[i]); + argv[1] = xstrdup (mfile); + (*pargc) = 2; + } + } + + /* cd to the starting repository */ + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + return (1); + } + + if (force_tag_match) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + + /* start the recursion processor */ + err = start_recursion (patch_fileproc, (int (*) ()) NULL, patch_dirproc, + (int (*) ()) NULL, *pargc - 1, argv + 1, local, + which, 0, 1, where, 1); + + return (err); +} + +/* + * Called to examine a particular RCS file, as appropriate with the options + * that were set above. + */ +/* ARGSUSED */ +static int +patch_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + char *vers_tag, *vers_head; + char rcsspace[PATH_MAX]; + char *rcs = rcsspace; + Node *p; + RCSNode *rcsfile; + FILE *fp1, *fp2, *fp3; + int ret = 0; + int isattic = 0; + int retcode = 0; + char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX]; + char line1[MAXLINELEN], line2[MAXLINELEN]; + char *cp1, *cp2, *commap; + FILE *fp; + + + /* find the parsed rcs file */ + p = findnode (srcfiles, file); + if (p == NULL) + return (1); + rcsfile = (RCSNode *) p->data; + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + isattic = 1; + + (void) sprintf (rcs, "%s%s", file, RCSEXT); + + /* if vers_head is NULL, may have been removed from the release */ + if (isattic && rev2 == NULL && date2 == NULL) + vers_head = NULL; + else + vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match); + + if (toptwo_diffs) + { + if (vers_head == NULL) + return (1); + + if (!date1) + date1 = xmalloc (50); /* plenty big :-) */ + *date1 = '\0'; + if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1) + { + if (!really_quiet) + error (0, 0, "cannot find date in rcs file %s revision %s", + rcs, vers_head); + return (1); + } + } + vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match); + + if (vers_tag == NULL && (vers_head == NULL || isattic)) + return (0); /* nothing known about specified revs */ + + if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0) + return (0); /* not changed between releases */ + + if (patch_short) + { + (void) printf ("File "); + if (vers_tag == NULL) + (void) printf ("%s is new; current revision %s\n", rcs, vers_head); + else if (vers_head == NULL) + (void) printf ("%s is removed; not included in release %s\n", + rcs, rev2 ? rev2 : date2); + else + (void) printf ("%s changed from revision %s to %s\n", + rcs, vers_tag, vers_head); + return (0); + } + if ((fp1 = fopen (tmpnam (tmpfile1), "w+")) != NULL) + (void) fclose (fp1); + if ((fp2 = fopen (tmpnam (tmpfile2), "w+")) != NULL) + (void) fclose (fp2); + if ((fp3 = fopen (tmpnam (tmpfile3), "w+")) != NULL) + (void) fclose (fp3); + if (fp1 == NULL || fp2 == NULL || fp3 == NULL) + { + error (0, 0, "cannot create temporary files"); + ret = 1; + goto out; + } + if (vers_tag != NULL) + { + run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_tag); + run_arg (rcsfile->path); + if ((retcode = run_exec (RUN_TTY, tmpfile1, RUN_TTY, RUN_NORMAL)) != 0) + { + if (!really_quiet) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "co of revision %s in %s failed", vers_tag, rcs); + ret = 1; + goto out; + } + } + else if (toptwo_diffs) + { + ret = 1; + goto out; + } + if (vers_head != NULL) + { + run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_head); + run_arg (rcsfile->path); + if ((retcode = run_exec (RUN_TTY, tmpfile2, RUN_TTY, RUN_NORMAL)) != 0) + { + if (!really_quiet) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "co of revision %s in %s failed", vers_head, rcs); + ret = 1; + goto out; + } + } + run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c'); + run_arg (tmpfile1); + run_arg (tmpfile2); + switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_NORMAL)) + { + case -1: /* fork/wait failure */ + error (1, errno, "fork for diff failed on %s", rcs); + break; + case 0: /* nothing to do */ + break; + case 1: + /* + * The two revisions are really different, so read the first two + * lines of the diff output file, and munge them to include more + * reasonable file names that "patch" will understand. + */ + fp = open_file (tmpfile3, "r"); + if (fgets (line1, sizeof (line1), fp) == NULL || + fgets (line2, sizeof (line2), fp) == NULL) + { + error (0, errno, "failed to read diff file header %s for %s", + tmpfile3, rcs); + ret = 1; + (void) fclose (fp); + goto out; + } + if (!unidiff) + { + if (strncmp (line1, "*** ", 4) != 0 || + strncmp (line2, "--- ", 4) != 0 || + (cp1 = index (line1, '\t')) == NULL || + (cp2 = index (line2, '\t')) == NULL) + { + error (0, 0, "invalid diff header for %s", rcs); + ret = 1; + (void) fclose (fp); + goto out; + } + } + else + { + if (strncmp (line1, "--- ", 4) != 0 || + strncmp (line2, "+++ ", 4) != 0 || + (cp1 = index (line1, '\t')) == NULL || + (cp2 = index (line2, '\t')) == NULL) + { + error (0, 0, "invalid unidiff header for %s", rcs); + ret = 1; + (void) fclose (fp); + goto out; + } + } + if (CVSroot != NULL) + (void) sprintf (strippath, "%s/", CVSroot); + else + (void) strcpy (strippath, REPOS_STRIP); + if (strncmp (rcs, strippath, strlen (strippath)) == 0) + rcs += strlen (strippath); + commap = rindex (rcs, ','); + *commap = '\0'; + if (vers_tag != NULL) + { + (void) sprintf (file1, "%s%s%s:%s", update_dir, + update_dir[0] ? "/" : "", rcs, vers_tag); + } + else + { + (void) strcpy (file1, DEVNULL); + } + (void) sprintf (file2, "%s%s%s:%s", update_dir, + update_dir[0] ? "/" : "", rcs, + vers_head ? vers_head : "removed"); + if (unidiff) + { + (void) printf ("diff -u %s %s\n", file1, file2); + (void) printf ("--- %s%s+++ ", file1, cp1); + } + else + { + (void) printf ("diff -c %s %s\n", file1, file2); + (void) printf ("*** %s%s--- ", file1, cp1); + } + + if (update_dir[0] != '\0') + (void) printf ("%s/", update_dir); + (void) printf ("%s%s", rcs, cp2); + while (fgets (line1, sizeof (line1), fp) != NULL) + (void) printf ("%s", line1); + (void) fclose (fp); + break; + default: + error (0, 0, "diff failed for %s", rcs); + } + out: + (void) unlink_file (tmpfile1); + (void) unlink_file (tmpfile2); + (void) unlink_file (tmpfile3); + return (ret); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +patch_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Diffing %s", update_dir); + return (R_PROCESS); +} + +/* + * Clean up temporary files + */ +static SIGTYPE +patch_cleanup () +{ + if (tmpfile1[0] != '\0') + (void) unlink_file (tmpfile1); + if (tmpfile2[0] != '\0') + (void) unlink_file (tmpfile2); + if (tmpfile3[0] != '\0') + (void) unlink_file (tmpfile3); +} diff --git a/gnu/usr.bin/cvs/cvs/patchlevel.h b/gnu/usr.bin/cvs/cvs/patchlevel.h new file mode 100644 index 0000000..dc2214d --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/patchlevel.h @@ -0,0 +1 @@ +#define PATCHLEVEL 0 diff --git a/gnu/usr.bin/cvs/cvs/rcs.c b/gnu/usr.bin/cvs/cvs/rcs.c new file mode 100644 index 0000000..e882b8c --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/rcs.c @@ -0,0 +1,1449 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * The routines contained in this file do all the rcs file parsing and + * manipulation + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)rcs.c 1.28 92/03/31"; +#endif + +#if __STDC__ +static char *RCS_getbranch (RCSNode * rcs, char *tag, int force_tag_match); +static char *RCS_getdatebranch (RCSNode * rcs, char *date, char *branch); +static int getrcskey (FILE * fp, char **keyp, char **valp); +static int parse_rcs_proc (Node * file); +static int checkmagic_proc (Node *p); +static void do_branches (List * list, char *val); +static void do_symbols (List * list, char *val); +static void null_delproc (Node * p); +static void rcsnode_delproc (Node * p); +static void rcsvers_delproc (Node * p); +#else +static int parse_rcs_proc (); +static int checkmagic_proc (); +static void rcsnode_delproc (); +static void rcsvers_delproc (); +static void null_delproc (); +static int getrcskey (); +static void do_symbols (); +static void do_branches (); +static char *RCS_getbranch (); +static char *RCS_getdatebranch (); +#endif /* __STDC__ */ + +static List *rcslist; +static char *repository; + +/* + * Parse all the rcs files specified and return a list + */ +List * +RCS_parsefiles (files, xrepos) + List *files; + char *xrepos; +{ + /* initialize */ + repository = xrepos; + rcslist = getlist (); + + /* walk the list parsing files */ + if (walklist (files, parse_rcs_proc) != 0) + { + /* free the list and return NULL on error */ + dellist (&rcslist); + return ((List *) NULL); + } + else + /* return the list we built */ + return (rcslist); +} + +/* + * Parse an rcs file into a node on the rcs list + */ +static int +parse_rcs_proc (file) + Node *file; +{ + Node *p; + RCSNode *rdata; + + /* parse the rcs file into rdata */ + rdata = RCS_parse (file->key, repository); + + /* if we got a valid RCSNode back, put it on the list */ + if (rdata != (RCSNode *) NULL) + { + p = getnode (); + p->key = xstrdup (file->key); + p->delproc = rcsnode_delproc; + p->type = RCSNODE; + p->data = (char *) rdata; + (void) addnode (rcslist, p); + } + return (0); +} + +/* + * Parse an rcsfile given a user file name and a repository + */ +RCSNode * +RCS_parse (file, repos) + char *file; + char *repos; +{ + RCSNode *rcs; + char rcsfile[PATH_MAX]; + + (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT); + if (!isreadable (rcsfile)) + { + (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, + file, RCSEXT); + if (!isreadable (rcsfile)) + return (NULL); + rcs = RCS_parsercsfile (rcsfile); + if (rcs != NULL) + { + rcs->flags |= INATTIC; + rcs->flags |= VALID; + } + return (rcs); + } + rcs = RCS_parsercsfile (rcsfile); + if (rcs != NULL) + rcs->flags |= VALID; + return (rcs); +} + +/* + * Do the real work of parsing an RCS file + */ +RCSNode * +RCS_parsercsfile (rcsfile) + char *rcsfile; +{ + Node *q, *r; + RCSNode *rdata; + RCSVers *vnode; + int n; + char *cp; + char *key, *value; + FILE *fp; + + /* open the rcsfile */ + if ((fp = fopen (rcsfile, "r")) == NULL) + { + error (0, errno, "Couldn't open rcs file `%s'", rcsfile); + return (NULL); + } + + /* make a node */ + rdata = (RCSNode *) xmalloc (sizeof (RCSNode)); + bzero ((char *) rdata, sizeof (RCSNode)); + rdata->refcount = 1; + rdata->path = xstrdup (rcsfile); + rdata->versions = getlist (); + rdata->dates = getlist (); + + /* + * process all the special header information, break out when we get to + * the first revision delta + */ + for (;;) + { + /* get the next key/value pair */ + + /* if key is NULL here, then the file is missing some headers */ + if (getrcskey (fp, &key, &value) == -1 || key == NULL) + { + if (!really_quiet) + error (0, 0, "`%s' does not appear to be a valid rcs file", + rcsfile); + freercsnode (&rdata); + (void) fclose (fp); + return (NULL); + } + + /* process it */ + if (strcmp (RCSHEAD, key) == 0 && value != NULL) + { + rdata->head = xstrdup (value); + continue; + } + if (strcmp (RCSBRANCH, key) == 0 && value != NULL) + { + rdata->branch = xstrdup (value); + if ((numdots (rdata->branch) & 1) != 0) + { + /* turn it into a branch if it's a revision */ + cp = rindex (rdata->branch, '.'); + *cp = '\0'; + } + continue; + } + if (strcmp (RCSSYMBOLS, key) == 0) + { + if (value != NULL) + { + /* if there are tags, set up the tag list */ + rdata->symbols = getlist (); + do_symbols (rdata->symbols, value); + continue; + } + } + + /* + * check key for '.''s and digits (probably a rev) if it is a + * revision, we are done with the headers and are down to the + * revision deltas, so we break out of the loop + */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + break; + + /* if we haven't grabbed it yet, we didn't want it */ + } + + /* + * we got out of the loop, so we have the first part of the first + * revision delta in our hand key=the revision and value=the date key and + * its value + */ + for (;;) + { + char *valp; + char date[MAXDATELEN]; + + /* grab the value of the date from value */ + valp = value + strlen (RCSDATE);/* skip the "date" keyword */ + while (isspace (*valp)) /* take space off front of value */ + valp++; + (void) strcpy (date, valp); + + /* get the nodes (q is by version, r is by date) */ + q = getnode (); + r = getnode (); + q->type = RCSVERS; + r->type = RCSVERS; + q->delproc = rcsvers_delproc; + r->delproc = null_delproc; + q->data = r->data = xmalloc (sizeof (RCSVers)); + bzero (q->data, sizeof (RCSVers)); + vnode = (RCSVers *) q->data; + + /* fill in the version before we forget it */ + q->key = vnode->version = xstrdup (key); + + /* throw away the author field */ + (void) getrcskey (fp, &key, &value); + + /* throw away the state field */ + (void) getrcskey (fp, &key, &value); + + /* fill in the date field */ + r->key = vnode->date = xstrdup (date); + + /* fill in the branch list (if any branches exist) */ + (void) getrcskey (fp, &key, &value); + if (value != (char *) NULL) + { + vnode->branches = getlist (); + do_branches (vnode->branches, value); + } + + /* fill in the next field if there is a next revision */ + (void) getrcskey (fp, &key, &value); + if (value != (char *) NULL) + vnode->next = xstrdup (value); + + /* + * at this point, we skip any user defined fields XXX - this is where + * we put the symbolic link stuff??? + */ + while ((n = getrcskey (fp, &key, &value)) >= 0) + { + /* if we have a revision, break and do it */ + for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++) + /* do nothing */ ; + if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0) + break; + } + + /* add the nodes to the lists */ + (void) addnode (rdata->versions, q); + (void) addnode (rdata->dates, r); + + /* + * if we left the loop because there were no more keys, we break out + * of the revision processing loop + */ + if (n < 0) + break; + } + + (void) fclose (fp); + return (rdata); +} + +/* + * rcsnode_delproc - free up an RCS type node + */ +static void +rcsnode_delproc (p) + Node *p; +{ + freercsnode ((RCSNode **) & p->data); +} + +/* + * freercsnode - free up the info for an RCSNode + */ +void +freercsnode (rnodep) + RCSNode **rnodep; +{ + if (rnodep == NULL || *rnodep == NULL) + return; + + ((*rnodep)->refcount)--; + if ((*rnodep)->refcount != 0) + { + *rnodep = (RCSNode *) NULL; + return; + } + free ((*rnodep)->path); + dellist (&(*rnodep)->versions); + dellist (&(*rnodep)->dates); + if ((*rnodep)->symbols != (List *) NULL) + dellist (&(*rnodep)->symbols); + if ((*rnodep)->head != (char *) NULL) + free ((*rnodep)->head); + if ((*rnodep)->branch != (char *) NULL) + free ((*rnodep)->branch); + free ((char *) *rnodep); + *rnodep = (RCSNode *) NULL; +} + +/* + * rcsvers_delproc - free up an RCSVers type node + */ +static void +rcsvers_delproc (p) + Node *p; +{ + RCSVers *rnode; + + rnode = (RCSVers *) p->data; + + if (rnode->branches != (List *) NULL) + dellist (&rnode->branches); + if (rnode->next != (char *) NULL) + free (rnode->next); + free ((char *) rnode); +} + +/* + * null_delproc - don't free anything since it will be free'd by someone else + */ +/* ARGSUSED */ +static void +null_delproc (p) + Node *p; +{ + /* don't do anything */ +} + +/* + * getrcskey - fill in the key and value from the rcs file the algorithm is + * as follows + * + * o skip whitespace o fill in key with everything up to next white + * space or semicolon + * o if key == "desc" then key and data are NULL and return -1 + * o if key wasn't terminated by a semicolon, skip white space and fill + * in value with everything up to a semicolon o compress all whitespace + * down to a single space + * o if a word starts with @, do funky rcs processing + * o strip whitespace off end of value or set value to NULL if it empty + * o return 0 since we found something besides "desc" + */ + +static char *key = NULL; +static int keysize = 0; +static char *value = NULL; +static int valsize = 0; + +#define ALLOCINCR 1024 + +static int +getrcskey (fp, keyp, valp) + FILE *fp; + char **keyp; + char **valp; +{ + char *cur, *max; + int c; + int funky = 0; + int white = 1; + + /* skip leading whitespace */ + while (1) + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + if (!isspace (c)) + break; + } + + /* fill in key */ + cur = key; + max = key + keysize; + while (!isspace (c) && c != ';') + { + if (cur < max) + *cur++ = c; + else + { + key = xrealloc (key, keysize + ALLOCINCR); + cur = key + keysize; + keysize += ALLOCINCR; + max = key + keysize; + *cur++ = c; + } + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + *cur = '\0'; + + /* if we got "desc", we are done with the file */ + if (strcmp (RCSDESC, key) == 0) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + + /* if we ended key with a semicolon, there is no value */ + if (c == ';') + { + *keyp = key; + *valp = (char *) NULL; + return (0); + } + + /* otherwise, there might be a value, so fill it in */ + (void) ungetc (c, fp); + cur = value; + max = value + valsize; + + /* process the value */ + for (;;) + { + /* get a character */ + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + + /* if we are in funky mode, do the rest of this string */ + if (funky) + { + + /* + * funky mode processing does the following: o @@ means one @ o + * all other characters are literal up to a single @ (including + * ';') + */ + for (;;) + { + if (c == '@') + { + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + if (c != '@') + { + /* @ followed by non @ turns off funky mode */ + funky = 0; + break; + } + /* otherwise, we already ate one @ so copy the other one */ + } + + /* put the character on the value (maybe allocating space) */ + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = c; + c = getc (fp); + if (c == EOF) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + } + } + + /* if we got the semi-colon we are done with the entire value */ + if (c == ';') + break; + + /* process the character we got */ + if (white && c == '@') + { + + /* + * if we are starting a word with an '@', enable funky processing + */ + white = 0; /* you can't be funky and white :-) */ + funky = 1; + } + else + { + + /* + * we put the character on the list, compressing all whitespace + * to a single space + */ + + /* whitespace with white set means compress it out */ + if (white && isspace (c)) + continue; + + if (isspace (c)) + { + /* make c a space and set white */ + white = 1; + c = ' '; + } + else + white = 0; + + /* put the char on the end of value (maybe allocating space) */ + if (cur >= max) + { + value = xrealloc (value, valsize + ALLOCINCR); + cur = value + valsize; + valsize += ALLOCINCR; + max = value + valsize; + } + *cur++ = c; + } + } + + /* if the last char was white space, take it off */ + if (white && cur != value) + cur--; + + /* terminate the string */ + if (cur) + *cur = '\0'; + + /* if the string is empty, make it null */ + if (value && *value != '\0') + *valp = value; + else + *valp = NULL; + *keyp = key; + return (0); +} + +/* + * process the symbols list of the rcs file + */ +static void +do_symbols (list, val) + List *list; + char *val; +{ + Node *p; + char *cp = val; + char *tag, *rev; + + for (;;) + { + /* skip leading whitespace */ + while (isspace (*cp)) + cp++; + + /* if we got to the end, we are done */ + if (*cp == '\0') + break; + + /* split it up into tag and rev */ + tag = cp; + cp = index (cp, ':'); + *cp++ = '\0'; + rev = cp; + while (!isspace (*cp) && *cp != '\0') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + + /* make a new node and add it to the list */ + p = getnode (); + p->key = xstrdup (tag); + p->data = xstrdup (rev); + (void) addnode (list, p); + } +} + +/* + * process the branches list of a revision delta + */ +static void +do_branches (list, val) + List *list; + char *val; +{ + Node *p; + char *cp = val; + char *branch; + + for (;;) + { + /* skip leading whitespace */ + while (isspace (*cp)) + cp++; + + /* if we got to the end, we are done */ + if (*cp == '\0') + break; + + /* find the end of this branch */ + branch = cp; + while (!isspace (*cp) && *cp != '\0') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + + /* make a new node and add it to the list */ + p = getnode (); + p->key = xstrdup (branch); + (void) addnode (list, p); + } +} + +/* + * Version Number + * + * Returns the requested version number of the RCS file, satisfying tags and/or + * dates, and walking branches, if necessary. + * + * The result is returned; null-string if error. + */ +char * +RCS_getversion (rcs, tag, date, force_tag_match) + RCSNode *rcs; + char *tag; + char *date; + int force_tag_match; +{ + /* make sure we have something to look at... */ + if (rcs == NULL) + return ((char *) NULL); + + if (tag && date) + { + char *cp, *rev, *tagrev; + + /* + * first lookup the tag; if that works, turn the revision into + * a branch and lookup the date. + */ + tagrev = RCS_gettag (rcs, tag, force_tag_match); + if (tagrev == NULL) + return ((char *) NULL); + + if ((cp = rindex (tagrev, '.')) != NULL) + *cp = '\0'; + rev = RCS_getdatebranch (rcs, date, tagrev); + free (tagrev); + return (rev); + } + else if (tag) + return (RCS_gettag (rcs, tag, force_tag_match)); + else if (date) + return (RCS_getdate (rcs, date, force_tag_match)); + else + return (RCS_head (rcs)); + +} + +/* + * Find the revision for a specific tag. + * If force_tag_match is set, return NULL if an exact match is not + * possible otherwise return RCS_head (). We are careful to look for + * and handle "magic" revisions specially. + * + * If the matched tag is a branch tag, find the head of the branch. + */ +char * +RCS_gettag (rcs, tag, force_tag_match) + RCSNode *rcs; + char *tag; + int force_tag_match; +{ + Node *p; + + /* make sure we have something to look at... */ + if (rcs == NULL) + return ((char *) NULL); + + /* If tag is "HEAD", special case to get head RCS revision */ + if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0')) + if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC)) + return ((char *) NULL); /* head request for removed file */ + else + return (RCS_head (rcs)); + + if (!isdigit (tag[0])) + { + /* If we got a symbolic tag, resolve it to a numeric */ + if (rcs == NULL) + p = NULL; + else + p = findnode (rcs->symbols, tag); + if (p != NULL) + { + int dots; + char *magic, *branch, *cp; + + tag = p->data; + + /* + * If this is a magic revision, we turn it into either its + * physical branch equivalent (if one exists) or into + * its base revision, which we assume exists. + */ + dots = numdots (tag); + if (dots > 2 && (dots & 1) != 0) + { + branch = rindex (tag, '.'); + cp = branch++ - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (tag) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + char *xtag; + + /* it's magic. See if the branch exists */ + *cp = '\0'; /* turn it into a revision */ + xtag = xstrdup (tag); + *cp = '.'; /* and back again */ + (void) sprintf (magic, "%s.%s", xtag, branch); + branch = RCS_getbranch (rcs, magic, 1); + free (magic); + if (branch != NULL) + { + free (xtag); + return (branch); + } + return (xtag); + } + free (magic); + } + } + else + { + /* The tag wasn't there, so return the head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + } + + /* + * numeric tag processing: + * 1) revision number - just return it + * 2) branch number - find head of branch + */ + + /* strip trailing dots */ + while (tag[strlen (tag) - 1] == '.') + tag[strlen (tag) - 1] = '\0'; + + if ((numdots (tag) & 1) == 0) + { + /* we have a branch tag, so we need to walk the branch */ + return (RCS_getbranch (rcs, tag, force_tag_match)); + } + else + { + /* we have a revision tag, so make sure it exists */ + if (rcs == NULL) + p = NULL; + else + p = findnode (rcs->versions, tag); + if (p != NULL) + return (xstrdup (tag)); + else + { + /* The revision wasn't there, so return the head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + } +} + +/* + * Return a "magic" revision as a virtual branch off of REV for the RCS file. + * A "magic" revision is one which is unique in the RCS file. By unique, I + * mean we return a revision which: + * - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH) + * - has a revision component which is not an existing branch off REV + * - has a revision component which is not an existing magic revision + * - is an even-numbered revision, to avoid conflicts with vendor branches + * The first point is what makes it "magic". + * + * As an example, if we pass in 1.37 as REV, we will look for an existing + * branch called 1.37.2. If it did not exist, we would look for an + * existing symbolic tag with a numeric part equal to 1.37.0.2. If that + * didn't exist, then we know that the 1.37.2 branch can be reserved by + * creating a symbolic tag with 1.37.0.2 as the numeric part. + * + * This allows us to fork development with very little overhead -- just a + * symbolic tag is used in the RCS file. When a commit is done, a physical + * branch is dynamically created to hold the new revision. + * + * Note: We assume that REV is an RCS revision and not a branch number. + */ +static char *check_rev; +char * +RCS_magicrev (rcs, rev) + RCSNode *rcs; + char *rev; +{ + int rev_num; + char *xrev, *test_branch; + + xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */ + check_rev = xrev; + + /* only look at even numbered branches */ + for (rev_num = 2; ; rev_num += 2) + { + /* see if the physical branch exists */ + (void) sprintf (xrev, "%s.%d", rev, rev_num); + test_branch = RCS_getbranch (rcs, xrev, 1); + if (test_branch != NULL) /* it did, so keep looking */ + { + free (test_branch); + continue; + } + + /* now, create a "magic" revision */ + (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num); + + /* walk the symbols list to see if a magic one already exists */ + if (walklist (rcs->symbols, checkmagic_proc) != 0) + continue; + + /* we found a free magic branch. Claim it as ours */ + return (xrev); + } +} + +/* + * walklist proc to look for a match in the symbols list. + * Returns 0 if the symbol does not match, 1 if it does. + */ +static int +checkmagic_proc (p) + Node *p; +{ + if (strcmp (check_rev, p->data) == 0) + return (1); + else + return (0); +} + +/* + * Returns non-zero if the specified revision number or symbolic tag + * resolves to a "branch" within the rcs file. We do take into account + * any magic branches as well. + */ +int +RCS_isbranch (file, rev, srcfiles) + char *file; + char *rev; + List *srcfiles; +{ + int dots; + Node *p; + RCSNode *rcs; + + /* numeric revisions are easy -- even number of dots is a branch */ + if (isdigit (*rev)) + return ((numdots (rev) & 1) == 0); + + /* assume a revision if you can't find the RCS info */ + p = findnode (srcfiles, file); + if (p == NULL) + return (0); + + /* now, look for a match in the symbols list */ + rcs = (RCSNode *) p->data; + p = findnode (rcs->symbols, rev); + if (p == NULL) + return (0); + dots = numdots (p->data); + if ((dots & 1) == 0) + return (1); + + /* got a symbolic tag match, but it's not a branch; see if it's magic */ + if (dots > 2) + { + char *magic; + char *branch = rindex (p->data, '.'); + char *cp = branch - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (p->data) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + free (magic); + return (1); + } + free (magic); + } + return (0); +} + +/* + * Returns a pointer to malloc'ed memory which contains the branch + * for the specified *symbolic* tag. Magic branches are handled correctly. + */ +char * +RCS_whatbranch (file, rev, srcfiles) + char *file; + char *rev; + List *srcfiles; +{ + int dots; + Node *p; + RCSNode *rcs; + + /* assume no branch if you can't find the RCS info */ + p = findnode (srcfiles, file); + if (p == NULL) + return ((char *) NULL); + + /* now, look for a match in the symbols list */ + rcs = (RCSNode *) p->data; + p = findnode (rcs->symbols, rev); + if (p == NULL) + return ((char *) NULL); + dots = numdots (p->data); + if ((dots & 1) == 0) + return (xstrdup (p->data)); + + /* got a symbolic tag match, but it's not a branch; see if it's magic */ + if (dots > 2) + { + char *magic; + char *branch = rindex (p->data, '.'); + char *cp = branch++ - 1; + while (*cp != '.') + cp--; + + /* see if we have .magic-branch. (".0.") */ + magic = xmalloc (strlen (p->data) + 1); + (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH); + if (strncmp (magic, cp, strlen (magic)) == 0) + { + /* yep. it's magic. now, construct the real branch */ + *cp = '\0'; /* turn it into a revision */ + (void) sprintf (magic, "%s.%s", p->data, branch); + *cp = '.'; /* and turn it back */ + return (magic); + } + free (magic); + } + return ((char *) NULL); +} + +/* + * Get the head of the specified branch. If the branch does not exist, + * return NULL or RCS_head depending on force_tag_match + */ +static char * +RCS_getbranch (rcs, tag, force_tag_match) + RCSNode *rcs; + char *tag; + int force_tag_match; +{ + Node *p, *head; + RCSVers *vn; + char *xtag; + char *nextvers; + char *cp; + + /* make sure we have something to look at... */ + if (rcs == NULL) + return ((char *) NULL); + + /* find out if the tag contains a dot, or is on the trunk */ + cp = rindex (tag, '.'); + + /* trunk processing is the special case */ + if (cp == NULL) + { + xtag = xmalloc (strlen (tag) + 1 + 1); /* +1 for an extra . */ + (void) strcpy (xtag, tag); + (void) strcat (xtag, "."); + for (cp = rcs->head; cp != NULL;) + { + if (strncmp (xtag, cp, strlen (xtag)) == 0) + break; + p = findnode (rcs->versions, cp); + if (p == NULL) + { + free (xtag); + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + vn = (RCSVers *) p->data; + cp = vn->next; + } + free (xtag); + if (cp == NULL) + { + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + return (xstrdup (cp)); + } + + /* if it had a `.', terminate the string so we have the base revision */ + *cp = '\0'; + + /* look up the revision this branch is based on */ + p = findnode (rcs->versions, tag); + + /* put the . back so we have the branch again */ + *cp = '.'; + + if (p == NULL) + { + /* if the base revision didn't exist, return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + + /* find the first element of the branch we are looking for */ + vn = (RCSVers *) p->data; + if (vn->branches == NULL) + return (NULL); + xtag = xmalloc (strlen (tag) + 1 + 1); /* 1 for the extra '.' */ + (void) strcpy (xtag, tag); + (void) strcat (xtag, "."); + head = vn->branches->list; + for (p = head->next; p != head; p = p->next) + if (strncmp (p->key, xtag, strlen (xtag)) == 0) + break; + free (xtag); + + if (p == head) + { + /* we didn't find a match so return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + + /* now walk the next pointers of the branch */ + nextvers = p->key; + do + { + p = findnode (rcs->versions, nextvers); + if (p == NULL) + { + /* a link in the chain is missing - return head or NULL */ + if (force_tag_match) + return (NULL); + else + return (RCS_head (rcs)); + } + vn = (RCSVers *) p->data; + nextvers = vn->next; + } while (nextvers != NULL); + + /* we have the version in our hand, so go for it */ + return (xstrdup (vn->version)); +} + +/* + * Get the head of the RCS file. If branch is set, this is the head of the + * branch, otherwise the real head + */ +char * +RCS_head (rcs) + RCSNode *rcs; +{ + /* make sure we have something to look at... */ + if (rcs == NULL) + return ((char *) NULL); + + if (rcs->branch) + return (RCS_getbranch (rcs, rcs->branch, 1)); + + /* + * NOTE: we call getbranch with force_tag_match set to avoid any + * possibility of recursion + */ + else + return (xstrdup (rcs->head)); +} + +/* + * Get the most recent revision, based on the supplied date, but use some + * funky stuff and follow the vendor branch maybe + */ +char * +RCS_getdate (rcs, date, force_tag_match) + RCSNode *rcs; + char *date; + int force_tag_match; +{ + char *cur_rev = NULL; + char *retval = NULL; + Node *p; + RCSVers *vers = NULL; + + /* make sure we have something to look at... */ + if (rcs == NULL) + return ((char *) NULL); + + /* if the head is on a branch, try the branch first */ + if (rcs->branch != NULL) + retval = RCS_getdatebranch (rcs, date, rcs->branch); + + /* if we found a match, we are done */ + if (retval != NULL) + return (retval); + + /* otherwise if we have a trunk, try it */ + if (rcs->head) + { + p = findnode (rcs->versions, rcs->head); + while (p != NULL) + { + /* if the date of this one is before date, take it */ + vers = (RCSVers *) p->data; + if (RCS_datecmp (vers->date, date) <= 0) + { + cur_rev = vers->version; + break; + } + + /* if there is a next version, find the node */ + if (vers->next != NULL) + p = findnode (rcs->versions, vers->next); + else + p = (Node *) NULL; + } + } + + /* + * at this point, either we have the revision we want, or we have the + * first revision on the trunk (1.1?) in our hands + */ + + /* if we found what we're looking for, and it's not 1.1 return it */ + if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0) + return (xstrdup (cur_rev)); + + /* look on the vendor branch */ + retval = RCS_getdatebranch (rcs, date, CVSBRANCH); + + /* + * if we found a match, return it; otherwise, we return the first + * revision on the trunk or NULL depending on force_tag_match and the + * date of the first rev + */ + if (retval != NULL) + return (retval); + + if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0) + return (xstrdup (vers->version)); + else + return (NULL); +} + +/* + * Look up the last element on a branch that was put in before the specified + * date (return the rev or NULL) + */ +static char * +RCS_getdatebranch (rcs, date, branch) + RCSNode *rcs; + char *date; + char *branch; +{ + char *cur_rev = NULL; + char *cp; + char *xbranch, *xrev; + Node *p; + RCSVers *vers; + + /* look up the first revision on the branch */ + xrev = xstrdup (branch); + cp = rindex (xrev, '.'); + if (cp == NULL) + { + free (xrev); + return (NULL); + } + *cp = '\0'; /* turn it into a revision */ + p = findnode (rcs->versions, xrev); + free (xrev); + if (p == NULL) + return (NULL); + vers = (RCSVers *) p->data; + + /* if no branches list, return NULL */ + if (vers->branches == NULL) + return (NULL); + + /* walk the branches list looking for the branch number */ + xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */ + (void) strcpy (xbranch, branch); + (void) strcat (xbranch, "."); + for (p = vers->branches->list->next; p != vers->branches->list; p = p->next) + if (strncmp (p->key, xbranch, strlen (xbranch)) == 0) + break; + free (xbranch); + if (p == vers->branches->list) + return (NULL); + + p = findnode (rcs->versions, p->key); + + /* walk the next pointers until you find the end, or the date is too late */ + while (p != NULL) + { + vers = (RCSVers *) p->data; + if (RCS_datecmp (vers->date, date) <= 0) + cur_rev = vers->version; + else + break; + + /* if there is a next version, find the node */ + if (vers->next != NULL) + p = findnode (rcs->versions, vers->next); + else + p = (Node *) NULL; + } + + /* if we found something acceptable, return it - otherwise NULL */ + if (cur_rev != NULL) + return (xstrdup (cur_rev)); + else + return (NULL); +} + +/* + * Compare two dates in RCS format. Beware the change in format on January 1, + * 2000, when years go from 2-digit to full format. + */ +int +RCS_datecmp (date1, date2) + char *date1, *date2; +{ + int length_diff = strlen (date1) - strlen (date2); + + return (length_diff ? length_diff : strcmp (date1, date2)); +} + +/* + * Lookup the specified revision in the ,v file and return, in the date + * argument, the date specified for the revision *minus one second*, so that + * the logically previous revision will be found later. + * + * Returns zero on failure, RCS revision time as a Unix "time_t" on success. + */ +time_t +RCS_getrevtime (rcs, rev, date, fudge) + RCSNode *rcs; + char *rev; + char *date; + int fudge; +{ + char tdate[MAXDATELEN]; + struct tm xtm, *ftm; + time_t revdate = 0; + Node *p; + RCSVers *vers; + + /* make sure we have something to look at... */ + if (rcs == NULL) + return (revdate); + + /* look up the revision */ + p = findnode (rcs->versions, rev); + if (p == NULL) + return (-1); + vers = (RCSVers *) p->data; + + /* split up the date */ + ftm = &xtm; + (void) sscanf (vers->date, SDATEFORM, &ftm->tm_year, &ftm->tm_mon, + &ftm->tm_mday, &ftm->tm_hour, &ftm->tm_min, + &ftm->tm_sec); + if (ftm->tm_year > 1900) + ftm->tm_year -= 1900; + + /* put the date in a form getdate can grok */ +#ifdef HAVE_RCS5 + (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon, + ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#else + (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon, + ftm->tm_mday, ftm->tm_year, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); +#endif + + /* turn it into seconds since the epoch */ + revdate = get_date (tdate, (struct timeb *) NULL); + if (revdate != (time_t) -1) + { + revdate -= fudge; /* remove "fudge" seconds */ + if (date) + { + /* put an appropriate string into ``date'' if we were given one */ +#ifdef HAVE_RCS5 + ftm = gmtime (&revdate); +#else + ftm = localtime (&revdate); +#endif + (void) sprintf (date, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + } + } + return (revdate); +} + +/* + * The argument ARG is the getopt remainder of the -k option specified on the + * command line. This function returns malloc'ed space that can be used + * directly in calls to RCS V5, with the -k flag munged correctly. + */ +char * +RCS_check_kflag (arg) + char *arg; +{ + static char *kflags[] = + {"kv", "kvl", "k", "v", "o", (char *) NULL}; + char karg[10]; + char **cpp = NULL; + +#ifndef HAVE_RCS5 + error (1, 0, "%s %s: your version of RCS does not support the -k option", + program_name, command_name); +#endif + + if (arg) + { + for (cpp = kflags; *cpp != NULL; cpp++) + { + if (strcmp (arg, *cpp) == 0) + break; + } + } + + if (arg == NULL || *cpp == NULL) + { + (void) fprintf (stderr, "%s %s: invalid -k option\n", + program_name, command_name); + (void) fprintf (stderr, "\tvalid options are:\n"); + for (cpp = kflags; *cpp != NULL; cpp++) + (void) fprintf (stderr, "\t\t-k%s\n", *cpp); + error (1, 0, "Please retry with a valid -k option"); + } + + (void) sprintf (karg, "-k%s", *cpp); + return (xstrdup (karg)); +} + +/* + * Do some consistency checks on the symbolic tag... These should equate + * pretty close to what RCS checks, though I don't know for certain. + */ +void +RCS_check_tag (tag) + char *tag; +{ + char *invalid = "$,.:;@"; /* invalid RCS tag characters */ + char *cp; + + /* + * The first character must be an alphabetic letter. The remaining + * characters cannot be non-visible graphic characters, and must not be + * in the set of "invalid" RCS identifier characters. + */ + if (isalpha (*tag)) + { + for (cp = tag; *cp; cp++) + { + if (!isgraph (*cp)) + error (1, 0, "tag `%s' has non-visible graphic characters", + tag); + if (index (invalid, *cp)) + error (1, 0, "tag `%s' must not contain the characters `%s'", + tag, invalid); + } + } + else + error (1, 0, "tag `%s' must start with a letter", tag); +} diff --git a/gnu/usr.bin/cvs/cvs/rcs.h b/gnu/usr.bin/cvs/cvs/rcs.h new file mode 100644 index 0000000..ba5bdae --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/rcs.h @@ -0,0 +1,102 @@ +/* @(#)rcs.h 1.14 92/03/31 */ + +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * RCS source control definitions needed by rcs.c and friends + */ + +#define RCS "rcs" +#define RCS_CI "ci" +#define RCS_CO "co" +#define RCS_RLOG "rlog" +#define RCS_DIFF "rcsdiff" +#define RCS_MERGE "merge" +#define RCS_RCSMERGE "rcsmerge" +#define RCS_MERGE_PAT "^>>>>>>> " /* runs "grep" with this pattern */ +#define RCSEXT ",v" +#define RCSHEAD "head" +#define RCSBRANCH "branch" +#define RCSSYMBOLS "symbols" +#define RCSDATE "date" +#define RCSDESC "desc" +#define DATEFORM "%02d.%02d.%02d.%02d.%02d.%02d" +#define SDATEFORM "%d.%d.%d.%d.%d.%d" + +/* + * Opaque structure definitions used by RCS specific lookup routines + */ +#define VALID 0x1 /* flags field contains valid data */ +#define INATTIC 0x2 /* RCS file is located in the Attic */ +struct rcsnode +{ + int refcount; + int flags; + char *path; + char *head; + char *branch; + List *symbols; + List *versions; + List *dates; +}; +typedef struct rcsnode RCSNode; + +struct rcsversnode +{ + char *version; + char *date; + char *next; + List *branches; +}; +typedef struct rcsversnode RCSVers; + +/* + * CVS reserves all even-numbered branches for its own use. "magic" branches + * (see rcs.c) are contained as virtual revision numbers (within symbolic + * tags only) off the RCS_MAGIC_BRANCH, which is 0. CVS also reserves the + * ".1" branch for vendor revisions. So, if you do your own branching, you + * should limit your use to odd branch numbers starting at 3. + */ +#define RCS_MAGIC_BRANCH 0 + +/* + * exported interfaces + */ +#if __STDC__ +List *RCS_parsefiles (List * files, char *xrepos); +RCSNode *RCS_parse (char *file, char *repos); +RCSNode *RCS_parsercsfile (char *rcsfile); +char *RCS_check_kflag (char *arg); +char *RCS_getdate (RCSNode * rcs, char *date, int force_tag_match); +char *RCS_gettag (RCSNode * rcs, char *tag, int force_tag_match); +char *RCS_getversion (RCSNode * rcs, char *tag, char *date, + int force_tag_match); +char *RCS_magicrev (RCSNode *rcs, char *rev); +int RCS_isbranch (char *file, char *rev, List *srcfiles); +char *RCS_whatbranch (char *file, char *tag, List *srcfiles); +char *RCS_head (RCSNode * rcs); +int RCS_datecmp (char *date1, char *date2); +time_t RCS_getrevtime (RCSNode * rcs, char *rev, char *date, int fudge); +void RCS_check_tag (char *tag); +void freercsnode (RCSNode ** rnodep); +#else +List *RCS_parsefiles (); +RCSNode *RCS_parse (); +char *RCS_head (); +char *RCS_getversion (); +char *RCS_magicrev (); +int RCS_isbranch (); +char *RCS_whatbranch (); +char *RCS_gettag (); +char *RCS_getdate (); +char *RCS_check_kflag (); +void RCS_check_tag (); +time_t RCS_getrevtime (); +RCSNode *RCS_parsercsfile (); +int RCS_datecmp (); +void freercsnode (); +#endif /* __STDC__ */ diff --git a/gnu/usr.bin/cvs/cvs/recurse.c b/gnu/usr.bin/cvs/cvs/recurse.c new file mode 100644 index 0000000..82301d5 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/recurse.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * General recursion handler + * + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)recurse.c 1.22 92/04/10"; +#endif + +#if __STDC__ +static int do_dir_proc (Node * p); +static int do_file_proc (Node * p); +static void addlist (List ** listp, char *key); +#else +static int do_file_proc (); +static int do_dir_proc (); +static void addlist (); +#endif /* __STDC__ */ + + +/* + * Local static versions eliminates the need for globals + */ +static int (*fileproc) (); +static int (*filesdoneproc) (); +static Dtype (*direntproc) (); +static int (*dirleaveproc) (); +static int which; +static Dtype flags; +static int aflag; +static int readlock; +static int dosrcs; +static char update_dir[PATH_MAX]; +static char *repository = NULL; +static List *entries = NULL; +static List *srcfiles = NULL; +static List *filelist = NULL; +static List *dirlist = NULL; + +/* + * Called to start a recursive command Command line arguments are processed + * if present, otherwise the local directory is processed. + */ +int +start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, + argc, argv, local, which, aflag, readlock, + update_preload, dosrcs) + int (*fileproc) (); + int (*filesdoneproc) (); + Dtype (*direntproc) (); + int (*dirleaveproc) (); + int argc; + char *argv[]; + int local; + int which; + int aflag; + int readlock; + char *update_preload; + int dosrcs; +{ + int i, err = 0; + Dtype flags; + + if (update_preload == NULL) + update_dir[0] = '\0'; + else + (void) strcpy (update_dir, update_preload); + + if (local) + flags = R_SKIP_DIRS; + else + flags = R_PROCESS; + + /* clean up from any previous calls to start_recursion */ + if (repository) + { + free (repository); + repository = (char *) NULL; + } + if (entries) + dellist (&entries); + if (srcfiles) + dellist (&srcfiles); + if (filelist) + dellist (&filelist); + if (dirlist) + dellist (&dirlist); + + if (argc == 0) + { + + /* + * There were no arguments, so we'll probably just recurse. The + * exception to the rule is when we are called from a directory + * without any CVS administration files. That has always meant to + * process each of the sub-directories, so we pretend like we were + * called with the list of sub-dirs of the current dir as args + */ + if ((which & W_LOCAL) && !isdir (CVSADM) && !isdir (OCVSADM)) + dirlist = Find_Dirs ((char *) NULL, W_LOCAL); + else + addlist (&dirlist, "."); + + err += do_recursion (fileproc, filesdoneproc, direntproc, + dirleaveproc, flags, which, aflag, + readlock, dosrcs); + } + else + { + + /* + * There were arguments, so we have to handle them by hand. To do + * that, we set up the filelist and dirlist with the arguments and + * call do_recursion. do_recursion recognizes the fact that the + * lists are non-null when it starts and doesn't update them + */ + + /* look for args with /-s in them */ + for (i = 0; i < argc; i++) + if (index (argv[i], '/') != NULL) + break; + + /* if we didn't find any hard one's, do it the easy way */ + if (i == argc) + { + /* set up the lists */ + for (i = 0; i < argc; i++) + { + if (isdir (argv[i])) + addlist (&dirlist, argv[i]); + else + { + if (isdir (CVSADM) || isdir (OCVSADM)) + { + char *repos; + char tmp[PATH_MAX]; + + repos = Name_Repository ((char *) NULL, update_dir); + (void) sprintf (tmp, "%s/%s", repos, argv[i]); + if (isdir (tmp)) + addlist (&dirlist, argv[i]); + else + addlist (&filelist, argv[i]); + free (repos); + } + else + addlist (&filelist, argv[i]); + } + } + + /* we aren't recursive if no directories were specified */ + if (dirlist == NULL) + local = 1; + + /* process the lists */ + err += do_recursion (fileproc, filesdoneproc, direntproc, + dirleaveproc, flags, which, aflag, + readlock, dosrcs); + } + /* otherwise - do it the hard way */ + else + { + char *cp; + char *dir = (char *) NULL; + char *comp = (char *) NULL; + char *oldupdate = (char *) NULL; + char savewd[PATH_MAX]; + + if (getwd (savewd) == NULL) + error (1, 0, "could not get working directory: %s", savewd); + + for (i = 0; i < argc; i++) + { + /* split the arg into the dir and component parts */ + dir = xstrdup (argv[i]); + if ((cp = rindex (dir, '/')) != NULL) + { + *cp = '\0'; + comp = xstrdup (cp + 1); + oldupdate = xstrdup (update_dir); + if (update_dir[0] != '\0') + (void) strcat (update_dir, "/"); + (void) strcat (update_dir, dir); + } + else + { + comp = xstrdup (dir); + if (dir) + free (dir); + dir = (char *) NULL; + } + + /* chdir to the appropriate place if necessary */ + if (dir && chdir (dir) < 0) + error (1, errno, "could not chdir to %s", dir); + + /* set up the list */ + if (isdir (comp)) + addlist (&dirlist, comp); + else + { + if (isdir (CVSADM) || isdir (OCVSADM)) + { + char *repos; + char tmp[PATH_MAX]; + + repos = Name_Repository ((char *) NULL, update_dir); + (void) sprintf (tmp, "%s/%s", repos, comp); + if (isdir (tmp)) + addlist (&dirlist, comp); + else + addlist (&filelist, comp); + free (repos); + } + else + addlist (&filelist, comp); + } + + /* do the recursion */ + err += do_recursion (fileproc, filesdoneproc, direntproc, + dirleaveproc, flags, which, + aflag, readlock, dosrcs); + + /* chdir back and fix update_dir if necessary */ + if (dir && chdir (savewd) < 0) + error (1, errno, "could not chdir to %s", dir); + if (oldupdate) + { + (void) strcpy (update_dir, oldupdate); + free (oldupdate); + } + + } + if (dir) + free (dir); + if (comp) + free (comp); + } + } + return (err); +} + +/* + * Implement the recursive policies on the local directory. This may be + * called directly, or may be called by start_recursion + */ +int +do_recursion (xfileproc, xfilesdoneproc, xdirentproc, xdirleaveproc, + xflags, xwhich, xaflag, xreadlock, xdosrcs) + int (*xfileproc) (); + int (*xfilesdoneproc) (); + Dtype (*xdirentproc) (); + int (*xdirleaveproc) (); + Dtype xflags; + int xwhich; + int xaflag; + int xreadlock; + int xdosrcs; +{ + int err = 0; + int dodoneproc = 1; + char *srepository; + + /* do nothing if told */ + if (xflags == R_SKIP_ALL) + return (0); + + /* set up the static vars */ + fileproc = xfileproc; + filesdoneproc = xfilesdoneproc; + direntproc = xdirentproc; + dirleaveproc = xdirleaveproc; + flags = xflags; + which = xwhich; + aflag = xaflag; + readlock = noexec ? 0 : xreadlock; + dosrcs = xdosrcs; + + /* + * Fill in repository with the current repository + */ + if (which & W_LOCAL) + { + if (isdir (CVSADM) || isdir (OCVSADM)) + repository = Name_Repository ((char *) NULL, update_dir); + else + repository = NULL; + } + else + { + repository = xmalloc (PATH_MAX); + (void) getwd (repository); + } + srepository = repository; /* remember what to free */ + + /* + * The filesdoneproc needs to be called for each directory where files + * processed, or each directory that is processed by a call where no + * directories were passed in. In fact, the only time we don't want to + * call back the filesdoneproc is when we are processing directories that + * were passed in on the command line (or in the special case of `.' when + * we were called with no args + */ + if (dirlist != NULL && filelist == NULL) + dodoneproc = 0; + + /* + * If filelist or dirlist is already set, we don't look again. Otherwise, + * find the files and directories + */ + if (filelist == NULL && dirlist == NULL) + { + /* both lists were NULL, so start from scratch */ + if (fileproc != NULL && flags != R_SKIP_FILES) + { + int lwhich = which; + + /* be sure to look in the attic if we have sticky tags/date */ + if ((lwhich & W_ATTIC) == 0) + if (isreadable (CVSADM_TAG)) + lwhich |= W_ATTIC; + + /* find the files and fill in entries if appropriate */ + filelist = Find_Names (repository, lwhich, aflag, &entries); + } + + /* find sub-directories if we will recurse */ + if (flags != R_SKIP_DIRS) + dirlist = Find_Dirs (repository, which); + } + else + { + /* something was passed on the command line */ + if (filelist != NULL && fileproc != NULL) + { + /* we will process files, so pre-parse entries */ + if (which & W_LOCAL) + entries = ParseEntries (aflag); + } + } + + /* process the files (if any) */ + if (filelist != NULL) + { + /* read lock it if necessary */ + if (readlock && repository && Reader_Lock (repository) != 0) + error (1, 0, "read lock failed - giving up"); + + /* pre-parse the source files */ + if (dosrcs && repository) + srcfiles = RCS_parsefiles (filelist, repository); + else + srcfiles = (List *) NULL; + + /* process the files */ + err += walklist (filelist, do_file_proc); + + /* unlock it */ + if (readlock) + Lock_Cleanup (); + + /* clean up */ + dellist (&filelist); + dellist (&srcfiles); + dellist (&entries); + } + + /* call-back files done proc (if any) */ + if (dodoneproc && filesdoneproc != NULL) + err = filesdoneproc (err, repository, update_dir[0] ? update_dir : "."); + + /* process the directories (if necessary) */ + if (dirlist != NULL) + err += walklist (dirlist, do_dir_proc); +#ifdef notdef + else if (dirleaveproc != NULL) + err += dirleaveproc(".", err, "."); +#endif + dellist (&dirlist); + + /* free the saved copy of the pointer if necessary */ + if (srepository) + { + (void) free (srepository); + repository = (char *) NULL; + } + + return (err); +} + +/* + * Process each of the files in the list with the callback proc + */ +static int +do_file_proc (p) + Node *p; +{ + if (fileproc != NULL) + return (fileproc (p->key, update_dir, repository, entries, srcfiles)); + else + return (0); +} + +/* + * Process each of the directories in the list (recursing as we go) + */ +static int +do_dir_proc (p) + Node *p; +{ + char *dir = p->key; + char savewd[PATH_MAX]; + char newrepos[PATH_MAX]; + List *sdirlist; + char *srepository; + char *cp; + Dtype dir_return = R_PROCESS; + int stripped_dot = 0; + int err = 0; + + /* set up update_dir - skip dots if not at start */ + if (strcmp (dir, ".") != 0) + { + if (update_dir[0] != '\0') + { + (void) strcat (update_dir, "/"); + (void) strcat (update_dir, dir); + } + else + (void) strcpy (update_dir, dir); + + /* + * Here we need a plausible repository name for the sub-directory. We + * create one by concatenating the new directory name onto the + * previous repository name. The only case where the name should be + * used is in the case where we are creating a new sub-directory for + * update -d and in that case the generated name will be correct. + */ + if (repository == NULL) + newrepos[0] = '\0'; + else + (void) sprintf (newrepos, "%s/%s", repository, dir); + } + else + { + if (update_dir[0] == '\0') + (void) strcpy (update_dir, dir); + + if (repository == NULL) + newrepos[0] = '\0'; + else + (void) strcpy (newrepos, repository); + } + + /* call-back dir entry proc (if any) */ + if (direntproc != NULL) + dir_return = direntproc (dir, newrepos, update_dir); + + /* only process the dir if the return code was 0 */ + if (dir_return != R_SKIP_ALL) + { + /* save our current directory and static vars */ + if (getwd (savewd) == NULL) + error (1, 0, "could not get working directory: %s", savewd); + sdirlist = dirlist; + srepository = repository; + dirlist = NULL; + + /* cd to the sub-directory */ + if (chdir (dir) < 0) + error (1, errno, "could not chdir to %s", dir); + + /* honor the global SKIP_DIRS (a.k.a. local) */ + if (flags == R_SKIP_DIRS) + dir_return = R_SKIP_DIRS; + + /* remember if the `.' will be stripped for subsequent dirs */ + if (strcmp (update_dir, ".") == 0) + { + update_dir[0] = '\0'; + stripped_dot = 1; + } + + /* make the recursive call */ + err += do_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, + dir_return, which, aflag, readlock, dosrcs); + + /* put the `.' back if necessary */ + if (stripped_dot) + (void) strcpy (update_dir, "."); + + /* call-back dir leave proc (if any) */ + if (dirleaveproc != NULL) + err = dirleaveproc (dir, err, update_dir); + + /* get back to where we started and restore state vars */ + if (chdir (savewd) < 0) + error (1, errno, "could not chdir to %s", savewd); + dirlist = sdirlist; + repository = srepository; + } + + /* put back update_dir */ + if ((cp = rindex (update_dir, '/')) != NULL) + *cp = '\0'; + else + update_dir[0] = '\0'; + + return (err); +} + +/* + * Add a node to a list allocating the list if necessary + */ +static void +addlist (listp, key) + List **listp; + char *key; +{ + Node *p; + + if (*listp == NULL) + *listp = getlist (); + p = getnode (); + p->type = FILES; + p->key = xstrdup (key); + (void) addnode (*listp, p); +} diff --git a/gnu/usr.bin/cvs/cvs/release.c b/gnu/usr.bin/cvs/cvs/release.c new file mode 100644 index 0000000..34d36bc --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/release.c @@ -0,0 +1,219 @@ +/* + * Release: "cancel" a checkout in the history log. + * + * - Don't allow release if anything is active - Don't allow release if not + * above or inside repository. - Don't allow release if ./CVS/Repository is + * not the same as the directory specified in the module database. + * + * - Enter a line in the history log indicating the "release". - If asked to, + * delete the local working directory. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)release.c 1.21 92/02/29"; +#endif + +#if __STDC__ +static void release_delete (char *dir); +#else +static void release_delete (); +#endif /* __STDC__ */ + +static char *release_usage[] = +{ + "Usage: %s %s [-d] modules...\n", + "\t-Q\tReally quiet.\n", + "\t-d\tDelete the given directory.\n", + "\t-q\tSomewhat quiet.\n", + NULL +}; + +static short delete; + +int +release (argc, argv) + int argc; + char **argv; +{ + FILE *fp; + register int i, c; + register char *cp; + int margc; + DBM *db; + datum key, val; + char *repository, *srepos; + char **margv, *modargv[MAXFILEPERDIR], line[PATH_MAX]; + + if (argc == -1) + usage (release_usage); + optind = 1; + while ((c = gnu_getopt (argc, argv, "Qdq")) != -1) + { + switch (c) + { + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'd': + delete++; + break; + case '?': + default: + usage (release_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (!(db = open_module ())) + return (1); + for (i = 0; i < argc; i++) + { + + /* + * If we are in a repository, do it. Else if we are in the parent of + * a directory with the same name as the module, "cd" into it and + * look for a repository there. + */ + if (isdir (argv[i])) + { + if (chdir (argv[i]) < 0) + { + if (!really_quiet) + error (0, 0, "can't chdir to: %s", argv[i]); + continue; + } + if (!isdir (CVSADM) && !isdir (OCVSADM)) + { + if (!really_quiet) + error (0, 0, "no repository module: %s", argv[i]); + continue; + } + } + else + { + if (!really_quiet) + error (0, 0, "no such directory/module: %s", argv[i]); + continue; + } + + repository = Name_Repository ((char *) NULL, (char *) NULL); + srepos = Short_Repository (repository); + + /* grab module entry from database and check against short repos */ + key.dptr = argv[i]; + key.dsize = strlen (key.dptr); + val = dbm_fetch (db, key); + if (!val.dptr) + { + error (0, 0, "no such module name: %s", argv[i]); + continue; + } + val.dptr[val.dsize] = '\0'; + if ((cp = index (val.dptr, '#')) != NULL) /* Strip out a comment */ + { + do + { + *cp-- = '\0'; + } while (isspace (*cp)); + } + (void) sprintf (line, "%s %s", key.dptr, val.dptr); + line2argv (&margc, modargv, line); + margv = modargv; + + optind = 1; + while (gnu_getopt (margc, margv, CVSMODULE_OPTS) != -1) + /* do nothing */ ; + margc -= optind; + margv += optind; + + if (margc < 1) + { + error (0, 0, "modules file missing directory for key %s value %s", + key.dptr, val.dptr); + continue; + } + if (strcmp (*margv, srepos)) + { + error (0, 0, "repository mismatch: module[%s], here[%s]", + *margv, srepos); + free (repository); + continue; + } + + if (!really_quiet) + { + + /* + * Now see if there is any reason not to allow a "Release" This + * is "popen()" instead of "Popen()" since we don't want "-n" to + * stop it. + */ + fp = popen ("cvs -n -q update", "r"); + c = 0; + while (fgets (line, sizeof (line), fp)) + { + if (index ("MARCZ", *line)) + c++; + (void) printf (line); + } + (void) pclose (fp); + (void) printf ("You have [%d] altered files in this repository.\n", + c); + (void) printf ("Are you sure you want to release %smodule `%s': ", + delete ? "(and delete) " : "", argv[i]); + c = !yesno (); + if (c) /* "No" */ + { + (void) fprintf (stderr, "** `%s' aborted by user choice.\n", + command_name); + free (repository); + continue; + } + } + + /* + * So, we've passed all the tests, go ahead and release it. First, + * log the release, then attempt to delete it. + */ + history_write ('F', argv[i], "", argv[i], ""); /* F == Free */ + free (repository); + + if (delete) + release_delete (argv[i]); + } + close_module (db); + return (0); +} + +/* We want to "rm -r" the repository, but let us be a little paranoid. */ +static void +release_delete (dir) + char *dir; +{ + struct stat st; + ino_t ino; + int retcode = 0; + + (void) stat (".", &st); + ino = st.st_ino; + (void) chdir (".."); + (void) stat (dir, &st); + if (ino != st.st_ino) + { + error (0, 0, + "Parent dir on a different disk, delete of %s aborted", dir); + return; + } + run_setup ("%s -r", RM); + run_arg (dir); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + error (0, retcode == -1 ? errno : 0, + "deletion of module %s failed.", dir); +} diff --git a/gnu/usr.bin/cvs/cvs/remove.c b/gnu/usr.bin/cvs/cvs/remove.c new file mode 100644 index 0000000..5cce883 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/remove.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Remove a File + * + * Removes entries from the present version. The entries will be removed from + * the RCS repository upon the next "commit". + * + * "remove" accepts no options, only file names that are to be removed. The + * file must not exist in the current directory for "remove" to work + * correctly. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)remove.c 1.34 92/04/10"; +#endif + +#if __STDC__ +static int remove_fileproc (char *file, char *update_dir, + char *repository, List *entries, + List *srcfiles); +static Dtype remove_dirproc (char *dir, char *repos, char *update_dir); +#else +static Dtype remove_dirproc (); +static int remove_fileproc (); +#endif + +static int local; +static int removed_files; +static int auto_removed_files; + +static char *remove_usage[] = +{ + "Usage: %s %s [-lR] [files...]\n", + "\t-l\tProcess this directory only (not recursive).\n", + "\t-R\tProcess directories recursively.\n", + NULL +}; + +int +cvsremove (argc, argv) + int argc; + char *argv[]; +{ + int c, err; + + if (argc == -1) + usage (remove_usage); + + optind = 1; + while ((c = gnu_getopt (argc, argv, "lR")) != -1) + { + switch (c) + { + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case '?': + default: + usage (remove_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* start the recursion processor */ + err = start_recursion (remove_fileproc, (int (*) ()) NULL, remove_dirproc, + (int (*) ()) NULL, argc, argv, local, + W_LOCAL, 0, 1, (char *) NULL, 1); + + if (removed_files) + error (0, 0, "use '%s commit' to remove %s permanently", program_name, + (removed_files == 1) ? "this file" : "these files"); + else + if (!auto_removed_files) + error (0, 0, "no files removed; use `%s' to remove the file first", + RM); + + return (err); +} + +/* + * remove the file, only if it has already been physically removed + */ +/* ARGSUSED */ +static int +remove_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + char fname[PATH_MAX]; + Vers_TS *vers; + + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 0, 0, entries, srcfiles); + + if (vers->ts_user != NULL) + { + freevers_ts (&vers); + return (0); + } + + if (vers->vn_user == NULL) + { + if (!quiet) + error (0, 0, "nothing known about %s", file); + freevers_ts (&vers); + return (0); + } + + if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + { + /* + * It's a file that has been added, but not commited yet. So, + * remove the ,p and ,t file for it and scratch it from the + * entries file. + */ + Scratch_Entry (entries, file); + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT); + (void) unlink_file (fname); + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG); + (void) unlink_file (fname); + if (!quiet) + error (0, 0, "removed `%s'.", file); + auto_removed_files++; + } + else if (vers->vn_user[0] == '-') + { + freevers_ts (&vers); + return (0); + } + else + { + /* Re-register it with a negative version number. */ + (void) strcpy (fname, "-"); + (void) strcat (fname, vers->vn_user); + Register (entries, file, fname, vers->ts_rcs, vers->options, + vers->tag, vers->date); + if (!quiet) + { + error (0, 0, "scheduling %s for removal", file); + removed_files++; + } + } + + freevers_ts (&vers); + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +remove_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Removing %s", update_dir); + return (R_PROCESS); +} diff --git a/gnu/usr.bin/cvs/cvs/repos.c b/gnu/usr.bin/cvs/cvs/repos.c new file mode 100644 index 0000000..43a9dfe --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/repos.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Name of Repository + * + * Determine the name of the RCS repository and sets "Repository" accordingly. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)repos.c 1.28 92/03/31"; +#endif + +char * +Name_Repository (dir, update_dir) + char *dir; + char *update_dir; +{ + FILE *fpin; + char *ret, *xupdate_dir; + char repos[PATH_MAX]; + char path[PATH_MAX]; + char tmp[PATH_MAX]; + char cvsadm[PATH_MAX]; + char ocvsadm[PATH_MAX]; + char *cp; + int has_cvsadm = 0, has_ocvsadm = 0; + + if (update_dir && *update_dir) + xupdate_dir = update_dir; + else + xupdate_dir = "."; + + if (dir != NULL) + { + (void) sprintf (cvsadm, "%s/%s", dir, CVSADM); + (void) sprintf (ocvsadm, "%s/%s", dir, OCVSADM); + } + else + { + (void) strcpy (cvsadm, CVSADM); + (void) strcpy (ocvsadm, OCVSADM); + } + + /* sanity checks */ + if (!(has_cvsadm = isdir (cvsadm)) && !(has_ocvsadm = isdir (ocvsadm))) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "there is no version here; do '%s checkout' first", + program_name); + } + + if (has_ocvsadm) + { + if (has_cvsadm) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "error: both `%s' and `%s' exist; I give up", + CVSADM, OCVSADM); + } + if (rename (ocvsadm, cvsadm) < 0) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, errno, "cannot rename `%s' to `%s'; I give up", + OCVSADM, CVSADM); + } + + /* + * We have converted the old CVS.adm directory to the new CVS + * directory. Now, convert the Entries file to the new format, if + * necessary. + */ + check_entries (dir); + } + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT); + else + (void) strcpy (tmp, CVSADM_ENT); + + if (!isreadable (tmp)) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "*PANIC* administration files missing"); + } + + if (dir != NULL) + (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP); + else + (void) strcpy (tmp, CVSADM_REP); + + if (!isreadable (tmp)) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "*PANIC* administration files missing"); + } + + /* + * The assumption here is that the repository is always contained in the + * first line of the "Repository" file. + */ + fpin = open_file (tmp, "r"); + + if (fgets (repos, PATH_MAX, fpin) == NULL) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, errno, "cannot read %s", CVSADM_REP); + } + (void) fclose (fpin); + if ((cp = rindex (repos, '\n')) != NULL) + *cp = '\0'; /* strip the newline */ + + /* + * If this is a relative repository pathname, turn it into an absolute + * one by tacking on the CVSROOT environment variable. If the CVSROOT + * environment variable is not set, die now. + */ + if (strcmp (repos, "..") == 0 || strncmp (repos, "../", 3) == 0) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, "`..'-relative repositories are not supported."); + error (1, 0, "illegal source repository"); + } + if (repos[0] != '/') + { + if (CVSroot == NULL) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, "must set the CVSROOT environment variable\n"); + error (0, 0, "or specify the '-d' option to %s.", program_name); + error (1, 0, "illegal repository setting"); + } + (void) strcpy (path, repos); + (void) sprintf (repos, "%s/%s", CVSroot, path); + } + if (!isdir (repos)) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "there is no repository %s", repos); + } + + /* allocate space to return and fill it in */ + strip_path (repos); + ret = xstrdup (repos); + return (ret); +} + +/* + * Return a pointer to the repository name relative to CVSROOT from a + * possibly fully qualified repository + */ +char * +Short_Repository (repository) + char *repository; +{ + if (repository == NULL) + return (NULL); + + /* if repository matches CVSroot at the beginning, strip off CVSroot */ + if (strncmp (CVSroot, repository, strlen (CVSroot)) == 0) + return (repository + strlen (CVSroot) + 1); + else + return (repository); +} diff --git a/gnu/usr.bin/cvs/cvs/rtag.c b/gnu/usr.bin/cvs/cvs/rtag.c new file mode 100644 index 0000000..a53e8c7 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/rtag.c @@ -0,0 +1,403 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Rtag + * + * Add or delete a symbolic name to an RCS file, or a collection of RCS files. + * Uses the modules database, if necessary. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)rtag.c 1.57 92/04/10"; +#endif + +#if __STDC__ +static Dtype rtag_dirproc (char *dir, char *repos, char *update_dir); +static int rtag_fileproc (char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles); +static int rtag_proc (int *pargc, char *argv[], char *xwhere, + char *mwhere, char *mfile, int shorten, + int local_specified, char *mname, char *msg); +static int rtag_delete (RCSNode *rcsfile); +#else +static int rtag_proc (); +static int rtag_fileproc (); +static Dtype rtag_dirproc (); +static int rtag_delete (); +#endif /* __STDC__ */ + +static char *symtag; +static char *numtag; +static int delete; /* adding a tag by default */ +static int attic_too; /* remove tag from Attic files */ +static int branch_mode; /* make an automagic "branch" tag */ +static char *date; +static int local; /* recursive by default */ +static int force_tag_match = 1; /* force by default */ + +static char *rtag_usage[] = +{ + "Usage: %s %s [-QaflRnq] [-b] [-d] [-r tag|-D date] tag modules...\n", + "\t-Q\tReally quiet.\n", + "\t-a\tClear tag from removed files that would not otherwise be tagged.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-R\tProcess directories recursively.\n", + "\t-n\tNo execution of 'tag program'\n", + "\t-q\tSomewhat quiet.\n", + "\t-d\tDelete the given Tag.\n", + "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", + "\t-[rD]\tExisting tag or Date.\n", + NULL +}; + +int +rtag (argc, argv) + int argc; + char *argv[]; +{ + register int i; + int c; + DBM *db; + int run_module_prog = 1; + int err = 0; + + if (argc == -1) + usage (rtag_usage); + + optind = 1; + while ((c = gnu_getopt (argc, argv, "anfQqlRdbr:D:")) != -1) + { + switch (c) + { + case 'a': + attic_too = 1; + break; + case 'n': + run_module_prog = 0; + break; + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'd': + delete = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'b': + branch_mode = 1; + break; + case 'r': + numtag = optarg; + break; + case 'D': + if (date) + free (date); + date = Make_Date (optarg); + break; + case '?': + default: + usage (rtag_usage); + break; + } + } + argc -= optind; + argv += optind; + if (argc < 2) + usage (rtag_usage); + symtag = argv[0]; + argc--; + argv++; + + if (date && numtag) + error (1, 0, "-r and -D options are mutually exclusive"); + if (delete && branch_mode) + error (0, 0, "warning: -b ignored with -d options"); + RCS_check_tag (symtag); + + db = open_module (); + for (i = 0; i < argc; i++) + { + /* XXX last arg should be repository, but doesn't make sense here */ + history_write ('T', (delete ? "D" : (numtag ? numtag : + (date ? date : "A"))), symtag, argv[i], ""); + err += do_module (db, argv[i], TAG, delete ? "Untagging" : "Tagging", + rtag_proc, (char *) NULL, 0, 0, run_module_prog, + symtag); + } + close_module (db); + return (err); +} + +/* + * callback proc for doing the real work of tagging + */ +/* ARGSUSED */ +static int +rtag_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified, + mname, msg) + int *pargc; + char *argv[]; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *mname; + char *msg; +{ + int err = 0; + int which; + char repository[PATH_MAX]; + char where[PATH_MAX]; + + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + (void) strcpy (where, argv[0]); + + /* if mfile isn't null, we need to set up to do only part of the module */ + if (mfile != NULL) + { + char *cp; + char path[PATH_MAX]; + + /* if the portion of the module is a path, put the dir part on repos */ + if ((cp = rindex (mfile, '/')) != NULL) + { + *cp = '\0'; + (void) strcat (repository, "/"); + (void) strcat (repository, mfile); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + (void) sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* directory means repository gets the dir tacked on */ + (void) strcpy (repository, path); + (void) strcat (where, "/"); + (void) strcat (where, mfile); + } + else + { + int i; + + /* a file means muck argv */ + for (i = 1; i < *pargc; i++) + free (argv[i]); + argv[1] = xstrdup (mfile); + (*pargc) = 2; + } + } + + /* chdir to the starting directory */ + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + return (1); + } + + if (delete || attic_too || (force_tag_match && numtag)) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + + /* start the recursion processor */ + err = start_recursion (rtag_fileproc, (int (*) ()) NULL, rtag_dirproc, + (int (*) ()) NULL, *pargc - 1, argv + 1, local, + which, 0, 1, where, 1); + + return (err); +} + +/* + * Called to tag a particular file, as appropriate with the options that were + * set above. + */ +/* ARGSUSED */ +static int +rtag_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Node *p; + RCSNode *rcsfile; + char *version, *rev; + int retcode = 0; + + /* find the parsed RCS data */ + p = findnode (srcfiles, file); + if (p == NULL) + return (1); + rcsfile = (RCSNode *) p->data; + + /* + * For tagging an RCS file which is a symbolic link, you'd best be + * running with RCS 5.6, since it knows how to handle symbolic links + * correctly without breaking your link! + */ + + if (delete) + return (rtag_delete (rcsfile)); + + /* + * If we get here, we are adding a tag. But, if -a was specified, we + * need to check to see if a -r or -D option was specified. If neither + * was specified and the file is in the Attic, remove the tag. + */ + if (attic_too && (!numtag && !date)) + { + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + return (rtag_delete (rcsfile)); + } + + version = RCS_getversion (rcsfile, numtag, date, force_tag_match); + if (version == NULL) + { + /* If -a specified, clean up any old tags */ + if (attic_too) + (void) rtag_delete (rcsfile); + + if (!quiet && !force_tag_match) + { + error (0, 0, "cannot find tag `%s' in `%s'", + numtag ? numtag : "head", rcsfile->path); + return (1); + } + return (0); + } + if (numtag && isdigit (*numtag) && strcmp (numtag, version) != 0) + { + + /* + * We didn't find a match for the numeric tag that was specified, but + * that's OK. just pass the numeric tag on to rcs, to be tagged as + * specified. Could get here if one tried to tag "1.1.1" and there + * was a 1.1.1 branch with some head revision. In this case, we want + * the tag to reference "1.1.1" and not the revision at the head of + * the branch. Use a symbolic tag for that. + */ + rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag; + run_setup ("%s%s -q -N%s:%s", Rcsbin, RCS, symtag, numtag); + } + else + { + char *oversion; + + /* + * As an enhancement for the case where a tag is being re-applied to + * a large body of a module, make one extra call to Version_Number to + * see if the tag is already set in the RCS file. If so, check to + * see if it needs to be moved. If not, do nothing. This will + * likely save a lot of time when simply moving the tag to the + * "current" head revisions of a module -- which I have found to be a + * typical tagging operation. + */ + oversion = RCS_getversion (rcsfile, symtag, (char *) 0, 1); + if (oversion != NULL) + { + if (strcmp (version, oversion) == 0) + { + free (version); + free (oversion); + return (0); + } + free (oversion); + } + rev = branch_mode ? RCS_magicrev (rcsfile, version) : version; + run_setup ("%s%s -q -N%s:%s", Rcsbin, RCS, symtag, rev); + } + run_arg (rcsfile->path); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to set tag `%s' to revision `%s' in `%s'", + symtag, rev, rcsfile->path); + free (version); + return (1); + } + free (version); + return (0); +} + +/* + * If -d is specified, "force_tag_match" is set, so that this call to + * Version_Number() will return a NULL version string if the symbolic + * tag does not exist in the RCS file. + * + * If the -r flag was used, numtag is set, and we only delete the + * symtag from files that have numtag. + * + * This is done here because it's MUCH faster than just blindly calling + * "rcs" to remove the tag... trust me. + */ +static int +rtag_delete (rcsfile) + RCSNode *rcsfile; +{ + char *version; + int retcode; + + if (numtag) + { + version = RCS_getversion (rcsfile, numtag, (char *) 0, 1); + if (version == NULL) + return (0); + free (version); + } + + version = RCS_getversion (rcsfile, symtag, (char *) 0, 1); + if (version == NULL) + return (0); + free (version); + + run_setup ("%s%s -q -N%s", Rcsbin, RCS, symtag); + run_arg (rcsfile->path); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to remove tag `%s' from `%s'", symtag, + rcsfile->path); + return (1); + } + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +rtag_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "%s %s", delete ? "Untagging" : "Tagging", update_dir); + return (R_PROCESS); +} diff --git a/gnu/usr.bin/cvs/cvs/status.c b/gnu/usr.bin/cvs/cvs/status.c new file mode 100644 index 0000000..9749740 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/status.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Status Information + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)status.c 1.48 92/03/31"; +#endif + +#if __STDC__ +static Dtype status_dirproc (char *dir, char *repos, char *update_dir); +static int status_fileproc (char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles); +static int tag_list_proc (Node * p); +#else +static int tag_list_proc (); +static int status_fileproc (); +static Dtype status_dirproc (); +#endif /* __STDC__ */ + +static int local = 0; +static int long_format = 0; + +static char *status_usage[] = +{ + "Usage: %s %s [-vlR] [files...]\n", + "\t-v\tVerbose format; includes tag information for the file\n", + "\t-l\tProcess this directory only (not recursive).\n", + "\t-R\tProcess directories recursively.\n", + NULL +}; + +int +status (argc, argv) + int argc; + char *argv[]; +{ + int c; + int err = 0; + + if (argc == -1) + usage (status_usage); + + optind = 1; + while ((c = gnu_getopt (argc, argv, "vlR")) != -1) + { + switch (c) + { + case 'v': + long_format = 1; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case '?': + default: + usage (status_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* start the recursion processor */ + err = start_recursion (status_fileproc, (int (*) ()) NULL, status_dirproc, + (int (*) ()) NULL, argc, argv, local, + W_LOCAL, 0, 1, (char *) NULL, 1); + + return (err); +} + +/* + * display the status of a file + */ +/* ARGSUSED */ +static int +status_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Ctype status; + char *sstat; + Vers_TS *vers; + + status = Classify_File (file, (char *) NULL, (char *) NULL, (char *) NULL, + 1, 0, repository, entries, srcfiles, &vers); + switch (status) + { + case T_UNKNOWN: + sstat = "Unknown"; + break; + case T_CHECKOUT: + sstat = "Needs Checkout"; + break; + case T_CONFLICT: + sstat = "Unresolved Conflict"; + break; + case T_ADDED: + sstat = "Locally Added"; + break; + case T_REMOVED: + sstat = "Locally Removed"; + break; + case T_MODIFIED: + sstat = "Locally Modified"; + break; + case T_REMOVE_ENTRY: + sstat = "Entry Invalid"; + break; + case T_UPTODATE: + sstat = "Up-to-date"; + break; + case T_NEEDS_MERGE: + sstat = "Needs Merge"; + break; + default: + sstat = "Classify Error"; + break; + } + + (void) printf ("===================================================================\n"); + if (vers->ts_user == NULL) + (void) printf ("File: no file %s\t\tStatus: %s\n\n", file, sstat); + else + (void) printf ("File: %-17.17s\tStatus: %s\n\n", file, sstat); + + if (vers->vn_user == NULL) + (void) printf (" Version:\t\tNo entry for %s\n", file); + else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') + (void) printf (" Version:\t\tNew file!\n"); + else + (void) printf (" Version:\t\t%s\t%s\n", vers->vn_user, + &vers->ts_rcs[25]); + + if (vers->vn_rcs == NULL) + (void) printf (" RCS Version:\tNo revision control file\n"); + else + (void) printf (" RCS Version:\t%s\t%s\n", vers->vn_rcs, + vers->srcfile->path); + + if (vers->entdata) + { + Entnode *edata; + + edata = vers->entdata; + if (edata->tag) + { + if (vers->vn_rcs == NULL) + (void) printf ( + " Sticky Tag:\t\t%s - MISSING from RCS file!\n", + edata->tag); + else + { + if (isdigit (edata->tag[0])) + (void) printf (" Sticky Tag:\t\t%s\n", edata->tag); + else + (void) printf (" Sticky Tag:\t\t%s (%s: %s)\n", + edata->tag, numdots (vers->vn_rcs) % 2 ? + "revision" : "branch", vers->vn_rcs); + } + } + else + (void) printf (" Sticky Tag:\t\t(none)\n"); + + if (edata->date) + (void) printf (" Sticky Date:\t%s\n", edata->date); + else + (void) printf (" Sticky Date:\t(none)\n"); + + if (edata->options && edata->options[0]) + (void) printf (" Sticky Options:\t%s\n", edata->options); + else + (void) printf (" Sticky Options:\t(none)\n"); + + if (long_format && vers->srcfile) + { + (void) printf ("\n Existing Tags:\n"); + if (vers->srcfile->symbols) + (void) walklist (vers->srcfile->symbols, tag_list_proc); + else + (void) printf ("\tNo Tags Exist\n"); + } + } + + (void) printf ("\n"); + freevers_ts (&vers); + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +status_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Examining %s", update_dir); + return (R_PROCESS); +} + +/* + * Print out a tag and its type + */ +static int +tag_list_proc (p) + Node *p; +{ + (void) printf ("\t%-25.25s\t(%s: %s)\n", p->key, + numdots (p->data) % 2 ? "revision" : "branch", + p->data); + return (0); +} diff --git a/gnu/usr.bin/cvs/cvs/tag.c b/gnu/usr.bin/cvs/cvs/tag.c new file mode 100644 index 0000000..71a8c46 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/tag.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Tag + * + * Add or delete a symbolic name to an RCS file, or a collection of RCS files. + * Uses the checked out revision in the current directory. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)tag.c 1.56 92/03/31"; +#endif + +#if __STDC__ +static Dtype tag_dirproc (char *dir, char *repos, char *update_dir); +static int tag_fileproc (char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles); +#else +static int tag_fileproc (); +static Dtype tag_dirproc (); +#endif /* __STDC__ */ + +static char *symtag; +static int delete; /* adding a tag by default */ +static int branch_mode; /* make an automagic "branch" tag */ +static int local; /* recursive by default */ + +static char *tag_usage[] = +{ + "Usage: %s %s [-QlRq] [-b] [-d] tag [files...]\n", + "\t-Q\tReally quiet.\n", + "\t-l\tLocal directory only, not recursive.\n", + "\t-R\tProcess directories recursively.\n", + "\t-q\tSomewhat quiet.\n", + "\t-d\tDelete the given Tag.\n", + "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n", + NULL +}; + +int +tag (argc, argv) + int argc; + char *argv[]; +{ + int c; + int err = 0; + + if (argc == -1) + usage (tag_usage); + + optind = 1; + while ((c = gnu_getopt (argc, argv, "QqlRdb")) != -1) + { + switch (c) + { + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'd': + delete = 1; + break; + case 'b': + branch_mode = 1; + break; + case '?': + default: + usage (tag_usage); + break; + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage (tag_usage); + symtag = argv[0]; + argc--; + argv++; + + if (delete && branch_mode) + error (0, 0, "warning: -b ignored with -d options"); + RCS_check_tag (symtag); + + /* start the recursion processor */ + err = start_recursion (tag_fileproc, (int (*) ()) NULL, tag_dirproc, + (int (*) ()) NULL, argc, argv, local, + W_LOCAL, 0, 1, (char *) NULL, 1); + return (err); +} + +/* + * Called to tag a particular file (the currently checked out version is + * tagged with the specified tag - or the specified tag is deleted). + */ +/* ARGSUSED */ +static int +tag_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + char *version, *oversion; + char *rev; + Vers_TS *vers; + int retcode = 0; + + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 0, 0, entries, srcfiles); + + if (delete) + { + + /* + * If -d is specified, "force_tag_match" is set, so that this call to + * Version_Number() will return a NULL version string if the symbolic + * tag does not exist in the RCS file. + * + * This is done here because it's MUCH faster than just blindly calling + * "rcs" to remove the tag... trust me. + */ + + version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1); + if (version == NULL || vers->srcfile == NULL) + { + freevers_ts (&vers); + return (0); + } + free (version); + + run_setup ("%s%s -q -N%s", Rcsbin, RCS, symtag); + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to remove tag %s from %s", symtag, + vers->srcfile->path); + freevers_ts (&vers); + return (1); + } + + /* warm fuzzies */ + if (!really_quiet) + { + if (update_dir[0]) + (void) printf ("D %s/%s\n", update_dir, file); + else + (void) printf ("D %s\n", file); + } + + freevers_ts (&vers); + return (0); + } + + /* + * If we are adding a tag, we need to know which version we have checked + * out and we'll tag that version. + */ + version = vers->vn_user; + if (version == NULL) + { + freevers_ts (&vers); + return (0); + } + else if (strcmp (version, "0") == 0) + { + if (!quiet) + error (0, 0, "couldn't tag added but un-commited file `%s'", file); + freevers_ts (&vers); + return (0); + } + else if (version[0] == '-') + { + if (!quiet) + error (0, 0, "skipping removed but un-commited file `%s'", file); + freevers_ts (&vers); + return (0); + } + else if (vers->srcfile == NULL) + { + if (!quiet) + error (0, 0, "cannot find revision control file for `%s'", file); + freevers_ts (&vers); + return (0); + } + + /* + * As an enhancement for the case where a tag is being re-applied to a + * large number of files, make one extra call to Version_Number to see if + * the tag is already set in the RCS file. If so, check to see if it + * needs to be moved. If not, do nothing. This will likely save a lot of + * time when simply moving the tag to the "current" head revisions of a + * module -- which I have found to be a typical tagging operation. + */ + oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1); + if (oversion != NULL) + { + if (strcmp (version, oversion) == 0) + { + free (oversion); + freevers_ts (&vers); + return (0); + } + free (oversion); + } + rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version; + run_setup ("%s%s -q -N%s:%s", Rcsbin, RCS, symtag, rev); + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to set tag %s to revision %s in %s", + symtag, rev, vers->srcfile->path); + freevers_ts (&vers); + return (1); + } + + /* more warm fuzzies */ + if (!really_quiet) + { + if (update_dir[0]) + (void) printf ("T %s/%s\n", update_dir, file); + else + (void) printf ("T %s\n", file); + } + + freevers_ts (&vers); + return (0); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +tag_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "%s %s", delete ? "Untagging" : "Tagging", update_dir); + return (R_PROCESS); +} diff --git a/gnu/usr.bin/cvs/cvs/update.c b/gnu/usr.bin/cvs/cvs/update.c new file mode 100644 index 0000000..e0dc287 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/update.c @@ -0,0 +1,1028 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * "update" updates the version in the present directory with respect to the RCS + * repository. The present version must have been created by "checkout". The + * user can keep up-to-date by calling "update" whenever he feels like it. + * + * The present version can be committed by "commit", but this keeps the version + * in tact. + * + * Arguments following the options are taken to be file names to be updated, + * rather than updating the entire directory. + * + * Modified or non-existent RCS files are checked out and reported as U + * <user_file> + * + * Modified user files are reported as M <user_file>. If both the RCS file and + * the user file have been modified, the user file is replaced by the result + * of rcsmerge, and a backup file is written for the user in .#file.version. + * If this throws up irreconcilable differences, the file is reported as C + * <user_file>, and as M <user_file> otherwise. + * + * Files added but not yet committed are reported as A <user_file>. Files + * removed but not yet committed are reported as R <user_file>. + * + * If the current directory contains subdirectories that hold concurrent + * versions, these are updated too. If the -d option was specified, new + * directories added to the repository are automatically created and updated + * as well. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)update.c 1.83 92/04/10"; +#endif + +#if __STDC__ +static int checkout_file (char *file, char *repository, List *entries, + List *srcfiles, Vers_TS *vers_ts, char *update_dir); +static int isemptydir (char *dir); +static int merge_file (char *file, char *repository, List *entries, + Vers_TS *vers, char *update_dir); +static int scratch_file (char *file, char *repository, List * entries, + char *update_dir); +static Dtype update_dirent_proc (char *dir, char *repository, char *update_dir); +static int update_dirleave_proc (char *dir, int err, char *update_dir); +static int update_file_proc (char *file, char *update_dir, char *repository, + List * entries, List * srcfiles); +static int update_filesdone_proc (int err, char *repository, char *update_dir); +static int write_letter (char *file, int letter, char *update_dir); +static void ignore_files (List * ilist, char *update_dir); +static void join_file (char *file, List *srcfiles, Vers_TS *vers_ts, + char *update_dir); +#else +static int update_file_proc (); +static int update_filesdone_proc (); +static Dtype update_dirent_proc (); +static int update_dirleave_proc (); +static int isemptydir (); +static int scratch_file (); +static int checkout_file (); +static int write_letter (); +static int merge_file (); +static void ignore_files (); +static void join_file (); +#endif /* __STDC__ */ + +static char *options = NULL; +static char *tag = NULL; +static char *date = NULL; +static char *join_rev1, *date_rev1; +static char *join_rev2, *date_rev2; +static int aflag = 0; +static int force_tag_match = 1; +static int update_build_dirs = 0; +static int update_prune_dirs = 0; +static int pipeout = 0; +static List *ignlist = (List *) NULL; + +static char *update_usage[] = +{ + "Usage:\n %s %s [-APQdflRpq] [-k kopt] [-r rev|-D date] [-j rev] [-I ign] [files...]\n", + "\t-A\tReset any sticky tags/date/kopts.\n", + "\t-P\tPrune empty directories.\n", + "\t-Q\tReally quiet.\n", + "\t-d\tBuild directories, like checkout does.\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, no recursion.\n", + "\t-R\tProcess directories recursively.\n", + "\t-p\tSend updates to standard output.\n", + "\t-q\tSomewhat quiet.\n", + "\t-k kopt\tUse RCS kopt -k option on checkout.\n", + "\t-r rev\tUpdate using specified revision/tag.\n", + "\t-D date\tSet date to update from.\n", + "\t-j rev\tMerge in changes made between current revision and rev.\n", + "\t-I ign\tMore files to ignore (! to reset).\n", + NULL +}; + +/* + * update is the argv,argc based front end for arg parsing + */ +int +update (argc, argv) + int argc; + char *argv[]; +{ + int c, err; + int local = 0; /* recursive by default */ + int which; /* where to look for files and dirs */ + + if (argc == -1) + usage (update_usage); + + ign_setup (); + + /* parse the args */ + optind = 1; + while ((c = gnu_getopt (argc, argv, "ApPflRQqdk:r:D:j:I:")) != -1) + { + switch (c) + { + case 'A': + aflag = 1; + break; + case 'I': + ign_add (optarg, 0); + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 'Q': + really_quiet = 1; + /* FALL THROUGH */ + case 'q': + quiet = 1; + break; + case 'd': + update_build_dirs = 1; + break; + case 'f': + force_tag_match = 0; + break; + case 'r': + tag = optarg; + break; + case 'D': + date = Make_Date (optarg); + break; + case 'P': + update_prune_dirs = 1; + break; + case 'p': + pipeout = 1; + noexec = 1; /* so no locks will be created */ + break; + case 'j': + if (join_rev2) + error (1, 0, "only two -j options can be specified"); + if (join_rev1) + join_rev2 = optarg; + else + join_rev1 = optarg; + break; + case '?': + default: + usage (update_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* + * If we are updating the entire directory (for real) and building dirs + * as we go, we make sure there is no static entries file and write the + * tag file as appropriate + */ + if (argc <= 0 && !pipeout) + { + if (update_build_dirs) + (void) unlink_file (CVSADM_ENTSTAT); + + /* keep the CVS/Tag file current with the specified arguments */ + if (aflag || tag || date) + WriteTag ((char *) NULL, tag, date); + } + + /* look for files/dirs locally and in the repository */ + which = W_LOCAL | W_REPOS; + + /* look in the attic too if a tag or date is specified */ + if (tag != NULL || date != NULL) + which |= W_ATTIC; + + /* call the command line interface */ + err = do_update (argc, argv, options, tag, date, force_tag_match, + local, update_build_dirs, aflag, update_prune_dirs, + pipeout, which, join_rev1, join_rev2, (char *) NULL); + + /* free the space Make_Date allocated if necessary */ + if (date != NULL) + free (date); + + return (err); +} + +/* + * Command line interface to update (used by checkout) + */ +int +do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag, + xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir) + int argc; + char *argv[]; + char *xoptions; + char *xtag; + char *xdate; + int xforce; + int local; + int xbuild; + int xaflag; + int xprune; + int xpipeout; + int which; + char *xjoin_rev1; + char *xjoin_rev2; + char *preload_update_dir; +{ + int err = 0; + char *cp; + + /* fill in the statics */ + options = xoptions; + tag = xtag; + date = xdate; + force_tag_match = xforce; + update_build_dirs = xbuild; + aflag = xaflag; + update_prune_dirs = xprune; + pipeout = xpipeout; + + /* setup the join support */ + join_rev1 = xjoin_rev1; + join_rev2 = xjoin_rev2; + if (join_rev1 && (cp = index (join_rev1, ':')) != NULL) + { + *cp++ = '\0'; + date_rev1 = Make_Date (cp); + } + else + date_rev1 = (char *) NULL; + if (join_rev2 && (cp = index (join_rev2, ':')) != NULL) + { + *cp++ = '\0'; + date_rev2 = Make_Date (cp); + } + else + date_rev2 = (char *) NULL; + + /* call the recursion processor */ + err = start_recursion (update_file_proc, update_filesdone_proc, + update_dirent_proc, update_dirleave_proc, + argc, argv, local, which, aflag, 1, + preload_update_dir, 1); + return (err); +} + +/* + * This is the callback proc for update. It is called for each file in each + * directory by the recursion code. The current directory is the local + * instantiation. file is the file name we are to operate on. update_dir is + * set to the path relative to where we started (for pretty printing). + * repository is the repository. entries and srcfiles are the pre-parsed + * entries and source control files. + * + * This routine decides what needs to be done for each file and does the + * appropriate magic for checkout + */ +static int +update_file_proc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + int retval; + Ctype status; + Vers_TS *vers; + + status = Classify_File (file, tag, date, options, force_tag_match, + aflag, repository, entries, srcfiles, &vers); + if (pipeout) + { + /* + * We just return success without doing anything if any of the really + * funky cases occur + * + * If there is still a valid RCS file, do a regular checkout type + * operation + */ + switch (status) + { + case T_UNKNOWN: /* unknown file was explicitly asked + * about */ + case T_REMOVE_ENTRY: /* needs to be un-registered */ + case T_ADDED: /* added but not committed */ + retval = 0; + break; + case T_CONFLICT: /* old punt-type errors */ + retval = 1; + break; + case T_UPTODATE: /* file was already up-to-date */ + case T_NEEDS_MERGE: /* needs merging */ + case T_MODIFIED: /* locally modified */ + case T_REMOVED: /* removed but not committed */ + case T_CHECKOUT: /* needs checkout */ + retval = checkout_file (file, repository, entries, srcfiles, + vers, update_dir); + break; + + default: /* can't ever happen :-) */ + error (0, 0, + "unknown file status %d for file %s", status, file); + retval = 0; + break; + } + } + else + { + switch (status) + { + case T_UNKNOWN: /* unknown file was explicitly asked + * about */ + case T_UPTODATE: /* file was already up-to-date */ + retval = 0; + break; + case T_CONFLICT: /* old punt-type errors */ + retval = 1; + break; + case T_NEEDS_MERGE: /* needs merging */ + retval = merge_file (file, repository, entries, + vers, update_dir); + break; + case T_MODIFIED: /* locally modified */ + retval = write_letter (file, 'M', update_dir); + break; + case T_CHECKOUT: /* needs checkout */ + retval = checkout_file (file, repository, entries, srcfiles, + vers, update_dir); + break; + case T_ADDED: /* added but not committed */ + retval = write_letter (file, 'A', update_dir); + break; + case T_REMOVED: /* removed but not committed */ + retval = write_letter (file, 'R', update_dir); + break; + case T_REMOVE_ENTRY: /* needs to be un-registered */ + retval = scratch_file (file, repository, entries, update_dir); + break; + default: /* can't ever happen :-) */ + error (0, 0, + "unknown file status %d for file %s", status, file); + retval = 0; + break; + } + } + + /* only try to join if things have gone well thus far */ + if (retval == 0 && join_rev1) + join_file (file, srcfiles, vers, update_dir); + + /* if this directory has an ignore list, add this file to it */ + if (ignlist) + { + Node *p; + + p = getnode (); + p->type = FILES; + p->key = xstrdup (file); + (void) addnode (ignlist, p); + } + + freevers_ts (&vers); + return (retval); +} + +/* + * update_filesdone_proc () is used + */ +/* ARGSUSED */ +static int +update_filesdone_proc (err, repository, update_dir) + int err; + char *repository; + char *update_dir; +{ + /* if this directory has an ignore list, process it then free it */ + if (ignlist) + { + ignore_files (ignlist, update_dir); + dellist (&ignlist); + } + + /* Clean up CVS admin dirs if we are export */ + if (strcmp (command_name, "export") == 0) + { + run_setup ("%s -fr", RM); + run_arg (CVSADM); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + + return (err); +} + +/* + * update_dirent_proc () is called back by the recursion processor before a + * sub-directory is processed for update. In this case, update_dirent proc + * will probably create the directory unless -d isn't specified and this is a + * new directory. A return code of 0 indicates the directory should be + * processed by the recursion code. A return of non-zero indicates the + * recursion code should skip this directory. + */ +static Dtype +update_dirent_proc (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + if (!isdir (dir)) + { + /* if we aren't building dirs, blow it off */ + if (!update_build_dirs) + return (R_SKIP_ALL); + + if (noexec) + { + /* ignore the missing dir if -n is specified */ + error (0, 0, "New directory `%s' -- ignored", dir); + return (R_SKIP_ALL); + } + else + { + /* otherwise, create the dir and appropriate adm files */ + make_directory (dir); + Create_Admin (dir, repository, tag, date); + } + } + + /* + * If we are building dirs and not going to stdout, we make sure there is + * no static entries file and write the tag file as appropriate + */ + if (!pipeout) + { + if (update_build_dirs) + { + char tmp[PATH_MAX]; + + (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT); + (void) unlink_file (tmp); + } + + /* keep the CVS/Tag file current with the specified arguments */ + if (aflag || tag || date) + WriteTag (dir, tag, date); + + /* initialize the ignore list for this directory */ + ignlist = getlist (); + } + + /* print the warm fuzzy message */ + if (!quiet) + error (0, 0, "Updating %s", update_dir); + + return (R_PROCESS); +} + +/* + * update_dirleave_proc () is called back by the recursion code upon leaving + * a directory. It will prune empty directories if needed and will execute + * any appropriate update programs. + */ +/* ARGSUSED */ +static int +update_dirleave_proc (dir, err, update_dir) + char *dir; + int err; + char *update_dir; +{ + FILE *fp; + + /* run the update_prog if there is one */ + if (err == 0 && !pipeout && !noexec && + (fp = fopen (CVSADM_UPROG, "r")) != NULL) + { + char *cp; + char *repository; + char line[MAXLINELEN]; + + repository = Name_Repository ((char *) NULL, update_dir); + if (fgets (line, sizeof (line), fp) != NULL) + { + if ((cp = rindex (line, '\n')) != NULL) + *cp = '\0'; + run_setup ("%s %s", line, repository); + (void) printf ("%s %s: Executing '", program_name, command_name); + run_print (stdout); + (void) printf ("'\n"); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + (void) fclose (fp); + free (repository); + } + + /* Clean up CVS admin dirs if we are export */ + if (strcmp (command_name, "export") == 0) + { + run_setup ("%s -fr", RM); + run_arg (CVSADM); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + + /* Prune empty dirs on the way out - if necessary */ + (void) chdir (".."); + if (update_prune_dirs && isemptydir (dir)) + { + run_setup ("%s -fr", RM); + run_arg (dir); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + + return (err); +} + +/* + * Returns 1 if the argument directory is completely empty, other than the + * existence of the CVS directory entry. Zero otherwise. + */ +static int +isemptydir (dir) + char *dir; +{ + DIR *dirp; + struct direct *dp; + + if ((dirp = opendir (dir)) == NULL) + { + error (0, 0, "cannot open directory %s for empty check", dir); + return (0); + } + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 && + strcmp (dp->d_name, CVSADM) != 0 && + strcmp (dp->d_name, OCVSADM) != 0) + { + (void) closedir (dirp); + return (0); + } + } + (void) closedir (dirp); + return (1); +} + +/* + * scratch the Entries file entry associated with a file + */ +static int +scratch_file (file, repository, entries, update_dir) + char *file; + char *repository; + List *entries; + char *update_dir; +{ + history_write ('W', update_dir, "", file, repository); + Scratch_Entry (entries, file); + (void) unlink_file (file); + return (0); +} + +/* + * check out a file - essentially returns the result of the fork on "co". + */ +static int +checkout_file (file, repository, entries, srcfiles, vers_ts, update_dir) + char *file; + char *repository; + List *entries; + List *srcfiles; + Vers_TS *vers_ts; + char *update_dir; +{ + char backup[PATH_MAX]; + int set_time, retval = 0; + int retcode = 0; + + /* don't screw with backup files if we're going to stdout */ + if (!pipeout) + { + (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file); + if (isfile (file)) + rename_file (file, backup); + else + (void) unlink_file (backup); + } + + run_setup ("%s%s -q -r%s %s", Rcsbin, RCS_CO, vers_ts->vn_rcs, + vers_ts->options); + + /* + * if we are checking out to stdout, print a nice message to stderr, and + * add the -p flag to the command + */ + if (pipeout) + { + run_arg ("-p"); + if (!quiet) + { + (void) fprintf (stderr, "===================================================================\n"); + if (update_dir[0]) + (void) fprintf (stderr, "Checking out %s/%s\n", + update_dir, file); + else + (void) fprintf (stderr, "Checking out %s\n", file); + (void) fprintf (stderr, "RCS: %s\n", vers_ts->srcfile->path); + (void) fprintf (stderr, "VERS: %s\n", vers_ts->vn_rcs); + (void) fprintf (stderr, "***************\n"); + } + } + + /* tack on the rcs and maybe the user file */ + run_arg (vers_ts->srcfile->path); + if (!pipeout) + run_arg (file); + + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, + (pipeout ? (RUN_NORMAL|RUN_REALLY) : RUN_NORMAL))) == 0) + { + if (!pipeout) + { + Vers_TS *xvers_ts; + + if (cvswrite == TRUE) + xchmod (file, 1); + + /* set the time from the RCS file iff it was unknown before */ + if (vers_ts->vn_user == NULL || + strncmp (vers_ts->ts_rcs, "Initial", 7) == 0) + { + set_time = 1; + } + else + set_time = 0; + + xvers_ts = Version_TS (repository, options, tag, date, file, + force_tag_match, set_time, entries, srcfiles); + if (strcmp (xvers_ts->options, "-V4") == 0) + xvers_ts->options[0] = '\0'; + Register (entries, file, xvers_ts->vn_rcs, xvers_ts->ts_user, + xvers_ts->options, xvers_ts->tag, xvers_ts->date); + + /* fix up the vers structure, in case it is used by join */ + if (join_rev1) + { + if (vers_ts->vn_user != NULL) + free (vers_ts->vn_user); + if (vers_ts->vn_rcs != NULL) + free (vers_ts->vn_rcs); + vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs); + vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs); + } + + /* If this is really Update and not Checkout, recode history */ + if (strcmp (command_name, "update") == 0) + history_write ('U', update_dir, xvers_ts->vn_rcs, file, + repository); + + freevers_ts (&xvers_ts); + + if (!really_quiet) + { + if (update_dir[0]) + (void) printf ("U %s/%s\n", update_dir, file); + else + (void) printf ("U %s\n", file); + } + } + } + else + { + int old_errno = errno; /* save errno value over the rename */ + + if (!pipeout && isfile (backup)) + rename_file (backup, file); + + error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0, + "could not check out %s", file); + + retval = retcode; + } + + if (!pipeout) + (void) unlink_file (backup); + + return (retval); +} + +/* + * Several of the types we process only print a bit of information consisting + * of a single letter and the name. + */ +static int +write_letter (file, letter, update_dir) + char *file; + char letter; + char *update_dir; +{ + if (!really_quiet) + { + if (update_dir[0]) + (void) printf ("%c %s/%s\n", letter, update_dir, file); + else + (void) printf ("%c %s\n", letter, file); + } + return (0); +} + +/* + * Do all the magic associated with a file which needs to be merged + */ +static int +merge_file (file, repository, entries, vers, update_dir) + char *file; + char *repository; + List *entries; + Vers_TS *vers; + char *update_dir; +{ + char user[PATH_MAX]; + char backup[PATH_MAX]; + int status; + int retcode = 0; + + /* + * The users currently modified file is moved to a backup file name + * ".#filename.version", so that it will stay around for a few days + * before being automatically removed by some cron daemon. The "version" + * is the version of the file that the user was most up-to-date with + * before the merge. + */ + (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user); + if (update_dir[0]) + (void) sprintf (user, "%s/%s", update_dir, file); + else + (void) strcpy (user, file); + + (void) unlink_file (backup); + copy_file (file, backup); + xchmod (file, 1); + + /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */ + run_setup ("%s%s %s -r%s -r%s", Rcsbin, RCS_RCSMERGE, vers->options, + vers->vn_user, vers->vn_rcs); + run_arg (vers->srcfile->path); + status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + if (status != 0 +#ifdef HAVE_RCS5 + && status != 1 +#endif + ) + { + error (0, status == -1 ? errno : 0, + "could not merge revision %s of %s", vers->vn_user, user); + error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", + user, backup); + rename_file (backup, file); + return (1); + } + /* XXX - Might want to make sure that rcsmerge changed the file */ + if (strcmp (vers->options, "-V4") == 0) + vers->options[0] = '\0'; + Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options, + vers->tag, vers->date); + + /* fix up the vers structure, in case it is used by join */ + if (join_rev1) + { + if (vers->vn_user != NULL) + free (vers->vn_user); + vers->vn_user = xstrdup (vers->vn_rcs); + } + + /* possibly run GREP to see if there appear to be conflicts in the file */ + run_setup ("%s -s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (file); + if (status == 1 || + (retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) == 0) + { + if (!noexec) + error (0, 0, "conflicts found in %s", user); + + if (!really_quiet) + (void) printf ("C %s\n", user); + + history_write ('C', update_dir, vers->vn_rcs, file, repository); + + } + else if (retcode == -1) + { + error (1, errno, "fork failed while examining update of %s", user); + } + else + { + if (!really_quiet) + (void) printf ("M %s\n", user); + history_write ('G', update_dir, vers->vn_rcs, file, repository); + } + return (0); +} + +/* + * Do all the magic associated with a file which needs to be joined + * (-j option) + */ +static void +join_file (file, srcfiles, vers, update_dir) + char *file; + List *srcfiles; + Vers_TS *vers; + char *update_dir; +{ + char user[PATH_MAX]; + char backup[PATH_MAX]; + char *rev, *baserev; + char *options; + int status; + + /* determine if we need to do anything at all */ + if (vers->vn_user == NULL || vers->srcfile == NULL || + vers->srcfile->path == NULL) + { + return; + } + + /* special handling when two revisions are specified */ + if (join_rev1 && join_rev2) + { + rev = RCS_getversion (vers->srcfile, join_rev2, date_rev2, 1); + if (rev == NULL) + { + if (!quiet && date_rev2 == NULL) + error (0, 0, + "cannot find revision %s in file %s", join_rev2, file); + return; + } + + baserev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1); + if (baserev == NULL) + { + if (!quiet && date_rev1 == NULL) + error (0, 0, + "cannot find revision %s in file %s", join_rev1, file); + free (rev); + return; + } + + /* + * nothing to do if: + * second revision matches our BASE revision (vn_user) && + * both revisions are on the same branch + */ + if (strcmp (vers->vn_user, rev) == 0 && + numdots (baserev) == numdots (rev)) + { + /* might be the same branch. take a real look */ + char *dot = rindex (baserev, '.'); + int len = (dot - baserev) + 1; + + if (strncmp (baserev, rev, len) == 0) + return; + } + } + else + { + rev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1); + if (rev == NULL) + return; + if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */ + { + free (rev); + return; + } + + baserev = RCS_whatbranch (file, join_rev1, srcfiles); + if (baserev) + { + char *cp; + + /* we get a branch -- turn it into a revision, or NULL if trunk */ + if ((cp = rindex (baserev, '.')) == NULL) + { + free (baserev); + baserev = (char *) NULL; + } + else + *cp = '\0'; + } + } + if (baserev && strcmp (baserev, rev) == 0) + { + /* they match -> nothing to do */ + free (rev); + free (baserev); + return; + } + + /* OK, so we have a revision and possibly a base revision; continue on */ + + /* + * The users currently modified file is moved to a backup file name + * ".#filename.version", so that it will stay around for a few days + * before being automatically removed by some cron daemon. The "version" + * is the version of the file that the user was most up-to-date with + * before the merge. + */ + (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user); + if (update_dir[0]) + (void) sprintf (user, "%s/%s", update_dir, file); + else + (void) strcpy (user, file); + + (void) unlink_file (backup); + copy_file (file, backup); + xchmod (file, 1); + + options = vers->options; +#ifdef HAVE_RCS5 + if (*options == '\0') + options = "-kk"; /* to ignore keyword expansions */ +#endif + + /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */ + run_setup ("%s%s %s %s%s -r%s", Rcsbin, RCS_RCSMERGE, options, + baserev ? "-r" : "", baserev ? baserev : "", rev); + run_arg (vers->srcfile->path); + status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + if (status != 0 +#ifdef HAVE_RCS5 + && status != 1 +#endif + ) + { + error (0, status == -1 ? errno : 0, + "could not merge revision %s of %s", rev, user); + error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", + user, backup); + rename_file (backup, file); + } + free (rev); + if (baserev) + free (baserev); + return; +} + +/* + * Process the current directory, looking for files not in ILIST and not on + * the global ignore list for this directory. + */ +static void +ignore_files (ilist, update_dir) + List *ilist; + char *update_dir; +{ + DIR *dirp; + struct direct *dp; + struct stat sb; + char *file; + char *xdir; + + /* we get called with update_dir set to "." sometimes... strip it */ + if (strcmp (update_dir, ".") == 0) + xdir = ""; + else + xdir = update_dir; + + dirp = opendir ("."); + if (dirp == NULL) + return; + + ign_add_file (CVSDOTIGNORE, 1); + while ((dp = readdir (dirp)) != NULL) + { + file = dp->d_name; + if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0) + continue; + if (findnode (ilist, file) != NULL) + continue; + if (lstat (file, &sb) != -1) + { + if (S_ISDIR (sb.st_mode)) + continue; +#ifdef S_IFLNK + if (S_ISLNK (sb.st_mode)) + continue; +#endif + } + if (ign_name (file)) + continue; + (void) write_letter (file, '?', xdir); + } + (void) closedir (dirp); +} diff --git a/gnu/usr.bin/cvs/cvs/vers_ts.c b/gnu/usr.bin/cvs/cvs/vers_ts.c new file mode 100644 index 0000000..6ac2488 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/vers_ts.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)vers_ts.c 1.36 92/03/31"; +#endif + +extern char *ctime (); /* XXX - should use gmtime/asctime */ + +/* + * Fill in and return a Vers_TS structure "user" is the name of the local + * file; entries is the entries file - preparsed for our pleasure. xfiles is + * all source code control files, preparsed for our pleasure + */ +Vers_TS * +Version_TS (repository, options, tag, date, user, force_tag_match, + set_time, entries, xfiles) + char *repository; + char *options; + char *tag; + char *date; + char *user; + int force_tag_match; + int set_time; + List *entries; + List *xfiles; +{ + Node *p; + RCSNode *rcsdata; + Vers_TS *vers_ts; + struct stickydirtag *sdtp; + + /* get a new Vers_TS struct */ + vers_ts = (Vers_TS *) xmalloc (sizeof (Vers_TS)); + bzero ((char *) vers_ts, sizeof (*vers_ts)); + + /* + * look up the entries file entry and fill in the version and timestamp + * if entries is NULL, there is no entries file so don't bother trying to + * look it up (used by checkout -P) + */ + if (entries == NULL) + { + sdtp = NULL; + p = NULL; + } + else + { + p = findnode (entries, user); + sdtp = (struct stickydirtag *) entries->list->data; /* list-private */ + } + + if (p != NULL) + { + Entnode *entdata = (Entnode *) p->data; + + vers_ts->vn_user = xstrdup (entdata->version); + vers_ts->ts_rcs = xstrdup (entdata->timestamp); + if (!tag) + { + if (!(sdtp && sdtp->aflag)) + vers_ts->tag = xstrdup (entdata->tag); + } + if (!date) + { + if (!(sdtp && sdtp->aflag)) + vers_ts->date = xstrdup (entdata->date); + } + if (!options || (options && *options == '\0')) + { + if (!(sdtp && sdtp->aflag)) + vers_ts->options = xstrdup (entdata->options); + } + vers_ts->entdata = entdata; + } + + /* + * -k options specified on the command line override (and overwrite) + * options stored in the entries file + */ + if (options) + vers_ts->options = xstrdup (options); + else if (sdtp && sdtp->aflag == 0) + { + if (!vers_ts->options) + vers_ts->options = xstrdup (sdtp->options); + } + if (!vers_ts->options) + vers_ts->options = xstrdup (""); + + /* + * if tags were specified on the command line, they override what is in + * the Entries file + */ + if (tag || date) + { + vers_ts->tag = xstrdup (tag); + vers_ts->date = xstrdup (date); + } + else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0)) + { + if (!vers_ts->tag) + vers_ts->tag = xstrdup (sdtp->tag); + if (!vers_ts->date) + vers_ts->date = xstrdup (sdtp->date); + } + + /* Now look up the info on the source controlled file */ + if (xfiles != (List *) NULL) + { + p = findnode (xfiles, user); + if (p != NULL) + { + rcsdata = (RCSNode *) p->data; + rcsdata->refcount++; + } + else + rcsdata = NULL; + } + else + rcsdata = RCS_parse (user, repository); + + if (rcsdata != NULL) + { + /* squirrel away the rcsdata pointer for others */ + vers_ts->srcfile = rcsdata; + + /* get RCS version number into vn_rcs (if appropriate) */ + if (((vers_ts->tag || vers_ts->date) && force_tag_match) || + ((rcsdata->flags & VALID) && (rcsdata->flags & INATTIC) == 0)) + { + if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0) + vers_ts->vn_rcs = xstrdup (vers_ts->vn_user); + else + vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag, + vers_ts->date, force_tag_match); + } + + /* + * If the source control file exists and has the requested revision, + * get the Date the revision was checked in. If "user" exists, set + * its mtime. + */ + if (set_time) + { + struct utimbuf t; + + if (vers_ts->vn_rcs && + (t.actime = t.modtime = RCS_getrevtime (rcsdata, vers_ts->vn_rcs, + (char *) 0, 0)) != -1) + (void) utime (user, &t); + } + } + + /* get user file time-stamp in ts_user */ + if (entries != (List *) NULL) + vers_ts->ts_user = time_stamp (user); + + return (vers_ts); +} + +/* + * Gets the time-stamp for the file "file" and returns it in space it + * allocates + */ +char * +time_stamp (file) + char *file; +{ + struct stat sb; + char *cp; + char *ts; + + if (stat (file, &sb) < 0) + { + ts = NULL; + } + else + { + ts = xmalloc (51); /* 51 = 2 ctime strings + NULL */ + cp = ctime (&sb.st_ctime); /* copy in the create time */ + cp[24] = ' '; + (void) strcpy (ts, cp); + cp = ctime (&sb.st_mtime); /* copy in the modify time */ + cp[24] = '\0'; + (void) strcat (ts, cp); + } + + return (ts); +} + +/* + * free up a Vers_TS struct + */ +void +freevers_ts (versp) + Vers_TS **versp; +{ + if ((*versp)->srcfile) + freercsnode (&((*versp)->srcfile)); + if ((*versp)->vn_user) + free ((*versp)->vn_user); + if ((*versp)->vn_rcs) + free ((*versp)->vn_rcs); + if ((*versp)->ts_user) + free ((*versp)->ts_user); + if ((*versp)->ts_rcs) + free ((*versp)->ts_rcs); + if ((*versp)->options) + free ((*versp)->options); + if ((*versp)->tag) + free ((*versp)->tag); + if ((*versp)->date) + free ((*versp)->date); + free ((char *) *versp); + *versp = (Vers_TS *) NULL; +} diff --git a/gnu/usr.bin/cvs/cvs/version.c b/gnu/usr.bin/cvs/cvs/version.c new file mode 100644 index 0000000..18a9d14 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/version.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * version.c - the CVS version number + */ + +char *version_string = "\nConcurrent Versions System (CVS) 1.3\n"; diff --git a/gnu/usr.bin/cvs/doc/cvs.ms b/gnu/usr.bin/cvs/doc/cvs.ms new file mode 100644 index 0000000..567179b --- /dev/null +++ b/gnu/usr.bin/cvs/doc/cvs.ms @@ -0,0 +1,1073 @@ +.\" soelim cvs.ms | pic | tbl | troff -ms +.\" @(#)cvs.ms 1.2 92/01/30 +.\" +.\" troff source to the cvs USENIX article, Winter 1990, Washington, D.C. +.\" Copyright (c) 1989, Brian Berliner +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 1, or (at your option) +.\" any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" The author can be reached at: berliner@prisma.com +.\" +.de SP +.if n .sp +.if t .sp .5 +.. +.de hl +.br +.in +0.5i +\l'\\n(LLu-1i' +.in -0.5i +.sp +.. +.OH "" +.nr PS 11 +.nr PO 1.25i +.pl -0.2i +.TL +.ps 14 +.ft B +.nf +CVS II: +Parallelizing Software Development +.fi +.ft +.ps +.AU +.ps 12 +.ft I +Brian Berliner +.ft +.ps +.AI +.ps 12 +.ft I +Prisma, Inc. +5465 Mark Dabling Blvd. +Colorado Springs, CO 80918 +berliner@prisma.com +.ft +.ps +.AB +The program described in this paper fills a need in the UNIX +community for a freely available tool to manage software revision and +release control in a multi-developer, multi-directory, multi-group +environment. +This tool also addresses the increasing need for tracking third-party vendor +source distributions while trying to maintain local modifications to +earlier releases. +.AE +.NH +Background +.PP +In large software development projects, it is usually necessary for more +than one software developer to be modifying (usually different) modules of the +code at the same time. +Some of these code modifications are done in an +experimental sense, at least until the code functions correctly, and some +testing of the entire program is usually necessary. +Then, the modifications are returned to a master source repository +so that others in the project can +enjoy the new bug-fix or functionality. +In order to manage such a project, some sort of revision control system is +necessary. +.PP +Specifically, UNIX\** +.FS +UNIX is a registered trademark of AT&T. +.FE +kernel development is an excellent example of the +problems that an adequate revision control system must address. +The SunOS\** +.FS +SunOS is a trademark of Sun Microsystems, Inc. +.FE +kernel is composed of over a thousand files spread across a +hierarchy of dozens of directories.\** +.FS +Yes, the SunOS 4.0 kernel is composed of over a \fIthousand\fP files! +.FE +Pieces of the kernel must be edited +by many software developers within an organization. +While undesirable in +theory, it is not uncommon to have two or more people making +modifications to the same file within the kernel sources in +order to facilitate a desired change. +Existing revision control systems like +.SM +RCS +.LG +[Tichy] or +.SM +SCCS +.LG +[Bell] serialize file modifications by +allowing only one developer to have a writable copy of a particular file at +any one point in time. +That developer is said to +have \*Qlocked\*U the file for his exclusive use, and no other developer is +allowed to check out a writable copy of the file until the locking +developer has finished impeding others' productivity. +Development pressures of productivity and deadlines +often force organizations to require that multiple developers be able to +simultaneously edit +copies of the same revision controlled file. +.PP +The necessity for multiple developers to modify the same file concurrently +questions the value of serialization-based policies in traditional revision +control. +This paper discusses the approach that +Prisma took in adapting a standard revision control system, +.SM +RCS\c +.LG +, along with an existing public-domain collection of shell scripts that sits +atop +.SM +RCS +.LG +and provides the basic conflict-resolution algorithms. +The resulting +program, \fBcvs\fP, addresses not only the issue of conflict-resolution in +a multi-developer open-editing environment, but also the issues of +software release control and vendor source support and integration. +.NH +The CVS Program +.PP +\fBcvs\fP +(Concurrent Versions System) +is a front end to the +.SM +RCS +.LG +revision control system which extends +the notion of revision control from a collection of files in a single +directory to a hierarchical collection of directories each containing +revision controlled files. +Directories and files in the \fBcvs\fP system can be combined together in +many ways to form a software release. +\fBcvs\fP +provides the functions necessary to manage these software releases and to +control the concurrent editing of source files among multiple software +developers. +.PP +The six major features of \fBcvs\fP are listed below, and will be +described in more detail in the following sections: +.RS +.IP 1. +Concurrent access and conflict-resolution algorithms to guarantee that +source changes are not \*Qlost.\*U +.IP 2. +Support for tracking third-party vendor source distributions while +maintaining the local modifications made to those sources. +.IP 3. +A flexible module database that provides a symbolic mapping of names to +components of a larger software distribution. +This symbolic mapping provides for location independence within the software +release and, for example, allows one to check out a copy of the \*Qdiff\*U +program without ever knowing that the sources to \*Qdiff\*U actually reside +in the \*Qbin/diff\*U directory. +.IP 4. +Configurable logging support allows all \*Qcommitted\*U source file changes +to be logged using an arbitrary program to save the log messages in a file, +notesfile, or news database. +.IP 5. +A software release can be symbolically tagged and checked out at any time +based on that tag. +An exact copy of a previous software release can be checked out at +any time, \fIregardless\fP of whether files or directories have been +added/removed from the \*Qcurrent\*U software release. +As well, +a \*Qdate\*U can be used to check out the \fIexact\fP version of the software +release as of the specified date. +.IP 6. +A \*Qpatch\*U format file [Wall] can be produced between two software +releases, even if the releases span multiple directories. +.RE +.PP +The sources maintained by \fBcvs\fP are kept within a single directory +hierarchy known as the \*Qsource repository.\*U +This \*Qsource repository\*U holds the actual +.SM +RCS +.LG +\*Q,v\*U files directly, as well as a special per-repository directory +(\c +.SM +CVSROOT.adm\c +.LG +) which contains a small number of administrative files that describe the +repository and how it can be accessed. +See Figure 1 for a picture of the \fBcvs\fP tree. +.KF +.hl +.DS B +.PS +line from 4.112,9.200 to 5.550,8.887 +line from 5.447,8.884 to 5.550,8.887 to 5.458,8.933 +line from 4.112,9.200 to 4.550,8.950 +line from 4.451,8.978 to 4.550,8.950 to 4.476,9.021 +line from 4.112,9.200 to 3.737,8.887 +line from 3.798,8.971 to 3.737,8.887 to 3.830,8.932 +line from 3.612,8.762 to 4.737,8.137 +line from 4.638,8.164 to 4.737,8.137 to 4.662,8.208 +line from 3.612,8.762 to 3.737,8.137 +line from 3.693,8.231 to 3.737,8.137 to 3.742,8.240 +line from 3.612,8.762 to 2.612,8.200 +line from 2.687,8.271 to 2.612,8.200 to 2.712,8.227 +line from 2.362,9.262 to 2.737,8.950 +line from 2.645,8.995 to 2.737,8.950 to 2.677,9.033 +line from 2.362,9.262 to 1.925,8.950 +line from 1.992,9.028 to 1.925,8.950 to 2.021,8.988 +line from 3.362,9.762 to 4.050,9.387 +line from 3.950,9.413 to 4.050,9.387 to 3.974,9.457 +line from 3.362,9.762 to 2.487,9.387 +line from 2.570,9.450 to 2.487,9.387 to 2.589,9.404 +.ps 11 +"newfs.c,v" at 4.487,8.043 ljust +.ps 11 +"mkfs.c,v" at 3.487,8.043 ljust +.ps 11 +"Makefile,v" at 2.237,8.043 ljust +.ps 11 +"newfs" at 3.487,8.793 ljust +.ps 11 +"halt.c,v" at 5.487,8.793 ljust +.ps 11 +"Makefile,v" at 4.237,8.793 ljust +.ps 11 +"modules,v" at 2.487,8.793 ljust +.ps 11 +"loginfo,v" at 1.488,8.793 ljust +.ps 11 +"etc" at 3.987,9.293 ljust +.ps 11 +"CVSROOT.adm" at 1.988,9.293 ljust +.ps 11 +"/src/master" at 2.987,9.793 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 1.\fP +.SM +\fBcvs\fP Source Repository +.ce 0 +.sp +.KE +.NH 2 +Software Conflict Resolution\** +.FS +The basic conflict-resolution algorithms +used in the \fBcvs\fP program find their roots +in the original work done by Dick Grune at Vrije Universiteit in Amsterdam +and posted to \fBcomp.sources.unix\fP in the volume 6 release sometime in 1986. +This original version of \fBcvs\fP was a collection of shell scripts that +combined to form a front end to the +.SM +RCS +.LG +programs. +.FE +.PP +\fBcvs\fP allows several software developers to edit personal copies of a +revision controlled file concurrently. +The revision number of each checked out file is maintained independently +for each user, and \fBcvs\fP forces the checked out file to be current with +the \*Qhead\*U revision before it can be \*Qcommitted\*U as a permanent change. +A checked out file is brought up-to-date with the \*Qhead\*U revision using +the \*Qupdate\*U command of \fBcvs\fP. +This command compares the \*Qhead\*U revision number with that of the user's +file and performs an +.SM +RCS +.LG +merge operation if they are not the same. +The result of the merge is a file that contains the user's modifications +and those modifications that were \*Qcommitted\*U after the user +checked out his version of the file (as well as a backup copy of the +user's original file). +\fBcvs\fP points out any conflicts during the merge. +It is the user's responsibility to resolve these conflicts +and to \*Qcommit\*U his/her changes when ready. +.PP +Although the \fBcvs\fP conflict-resolution algorithm was defined in 1986, +it is remarkably similar to the \*QCopy-Modify-Merge\*U scenario included +with NSE\** +.FS +NSE is the Network Software Environment, a product of Sun Microsystems, Inc. +.FE +and described in [Honda] and [Courington]. +The following explanation from [Honda] also applies to \fBcvs\fP: +.QP +Simply stated, a developer copies an object without locking it, modifies +the copy, and then merges the modified copy with the original. +This paradigm allows developers to work in isolation from one another since +changes are made to copies of objects. +Because locks are not used, development is not serialized and can proceed +in parallel. +Developers, however, must merge objects after the changes have been made. +In particular, a developer must resolve conflicts when the same object has +been modified by someone else. +.PP +In practice, Prisma has found that conflicts that occur when the same +object has been modified by someone else are quite rare. +When they do happen, the changes made by the other developer are usually +easily resolved. +This practical use has shown that the \*QCopy-Modify-Merge\*U paradigm is a +correct and useful one. +.NH 2 +Tracking Third-Party Source Distributions +.PP +Currently, a large amount of software is based on source +distributions from a third-party distributor. +It is often the case that local modifications are to be made to this +distribution, \fIand\fP that the vendor's future releases should be +tracked. +Rolling your local modifications forward into the new vendor release is a +time-consuming task, but \fBcvs\fP can ease this burden somewhat. +The \fBcheckin\fP program of \fBcvs\fP initially sets up a source +repository by integrating the source modules directly from the vendor's +release, preserving the directory hierarchy of the vendor's distribution. +The branch support of +.SM +RCS +.LG +is used to build this vendor release as a branch of the main +.SM +RCS +.LG +trunk. +Figure 2 shows how the \*Qhead\*U tracks a sample vendor +branch when no local modifications have been made to the file. +.KF +.hl +.DS B +.PS +ellipse at 3.237,6.763 wid 1.000 ht 0.500 +dashwid = 0.050i +line dashed from 3.237,7.513 to 3.737,7.513 to 3.737,9.762 to 4.237,9.762 +line from 4.138,9.737 to 4.237,9.762 to 4.138,9.787 +line dashed from 2.237,8.262 to 3.237,8.262 to 3.237,7.013 +line from 3.212,7.112 to 3.237,7.013 to 3.262,7.112 +line from 3.737,6.763 to 4.237,6.763 +line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788 +line from 2.237,6.763 to 2.737,6.763 +line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788 +line from 1.738,6.013 to 1.738,6.513 +line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413 +line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013 +line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012 +line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012 +line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013 +line from 4.737,7.013 to 4.737,7.513 +line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413 +line from 4.737,8.012 to 4.737,8.512 +line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412 +line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012 +line from 4.737,9.012 to 4.737,9.512 +line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412 +line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013 +.ps 11 +"\"HEAD\"" at 1.550,8.231 ljust +.ps 11 +"'SunOS'" at 2.987,6.293 ljust +.ps 11 +"1.1.1" at 3.050,6.793 ljust +.ps 11 +"1.1" at 1.613,6.793 ljust +.ps 11 +"1.1.1.1" at 4.487,6.793 ljust +.ps 11 +"1.1.1.2" at 4.487,7.793 ljust +.ps 11 +"1.1.1.3" at 4.487,8.793 ljust +.ps 11 +"1.1.1.4" at 4.487,9.793 ljust +.ps 11 +"'SunOS_4_0'" at 5.487,6.793 ljust +.ps 11 +"'SunOS_4_0_1'" at 5.487,7.793 ljust +.ps 11 +"'YAPT_5_5C'" at 5.487,8.793 ljust +.ps 11 +"'SunOS_4_0_3'" at 5.487,9.793 ljust +.ps 11 +"rcsfile.c,v" at 2.987,5.543 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 2.\fP +.SM +\fBcvs\fP Vendor Branch Example +.ce 0 +.sp .3 +.KE +Once this is done, developers can check out files and make local changes to +the vendor's source distribution. +These local changes form a new branch to the tree which is then used as the +source for future check outs. +Figure 3 shows how the \*Qhead\*U moves to the main +.SM +RCS +.LG +trunk when a local modification is made. +.KF +.hl +.DS B +.PS +ellipse at 3.237,6.763 wid 1.000 ht 0.500 +dashwid = 0.050i +line dashed from 2.800,9.075 to 1.738,9.075 to 1.738,8.012 +line from 1.713,8.112 to 1.738,8.012 to 1.762,8.112 +line from 1.738,7.013 to 1.738,7.513 +line from 1.762,7.413 to 1.738,7.513 to 1.713,7.413 +line from 1.238,8.012 to 2.237,8.012 to 2.237,7.513 to 1.238,7.513 to 1.238,8.012 +line from 3.737,6.763 to 4.237,6.763 +line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788 +line from 2.237,6.763 to 2.737,6.763 +line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788 +line from 1.738,6.013 to 1.738,6.513 +line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413 +line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013 +line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012 +line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012 +line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013 +line from 4.737,7.013 to 4.737,7.513 +line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413 +line from 4.737,8.012 to 4.737,8.512 +line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412 +line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012 +line from 4.737,9.012 to 4.737,9.512 +line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412 +line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013 +.ps 11 +"1.2" at 1.613,7.793 ljust +.ps 11 +"\"HEAD\"" at 2.862,9.043 ljust +.ps 11 +"'SunOS'" at 2.987,6.293 ljust +.ps 11 +"1.1.1" at 3.050,6.793 ljust +.ps 11 +"1.1" at 1.613,6.793 ljust +.ps 11 +"1.1.1.1" at 4.487,6.793 ljust +.ps 11 +"1.1.1.2" at 4.487,7.793 ljust +.ps 11 +"1.1.1.3" at 4.487,8.793 ljust +.ps 11 +"1.1.1.4" at 4.487,9.793 ljust +.ps 11 +"'SunOS_4_0'" at 5.487,6.793 ljust +.ps 11 +"'SunOS_4_0_1'" at 5.487,7.793 ljust +.ps 11 +"'YAPT_5_5C'" at 5.487,8.793 ljust +.ps 11 +"'SunOS_4_0_3'" at 5.487,9.793 ljust +.ps 11 +"rcsfile.c,v" at 2.987,5.543 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 3.\fP +.SM +\fBcvs\fP Local Modification to Vendor Branch +.ce 0 +.sp +.KE +.PP +When a new version of the vendor's source distribution arrives, the +\fBcheckin\fP program adds the new and changed vendor's files to the +already existing source repository. +For files that have not been changed locally, the new file from the +vendor becomes the current \*Qhead\*U revision. +For files that have been modified locally, \fBcheckin\fP warns that the +file must be merged with the new vendor release. +The \fBcvs\fP \*Qjoin\*U command is a useful tool that aids this process by +performing the necessary +.SM +RCS +.LG +merge, as is done above when performing an \*Qupdate.\*U +.PP +There is also limited support for \*Qdual\*U derivations for source files. +See Figure 4 for a sample dual-derived file. +.KF +.hl +.DS B +.PS +ellipse at 2.337,8.575 wid 0.700 ht 0.375 +ellipse at 2.312,9.137 wid 0.700 ht 0.375 +line from 1.225,9.012 to 1.225,9.363 +line from 1.250,9.263 to 1.225,9.363 to 1.200,9.263 +line from 0.875,9.725 to 1.600,9.725 to 1.600,9.363 to 0.875,9.363 to 0.875,9.725 +line from 0.875,9.012 to 1.600,9.012 to 1.600,8.650 to 0.875,8.650 to 0.875,9.012 +line from 4.050,10.200 to 4.775,10.200 to 4.775,9.850 to 4.050,9.850 to 4.050,10.200 +line from 4.050,9.475 to 4.775,9.475 to 4.775,9.113 to 4.050,9.113 to 4.050,9.475 +line from 4.050,8.762 to 4.775,8.762 to 4.775,8.400 to 4.050,8.400 to 4.050,8.762 +line from 4.425,8.762 to 4.425,9.113 +line from 4.450,9.013 to 4.425,9.113 to 4.400,9.013 +line from 4.425,9.475 to 4.425,9.850 +line from 4.450,9.750 to 4.425,9.850 to 4.400,9.750 +line from 3.050,10.000 to 3.775,10.000 to 3.775,9.637 to 3.050,9.637 to 3.050,10.000 +line from 3.050,9.312 to 3.775,9.312 to 3.775,8.950 to 3.050,8.950 to 3.050,9.312 +line from 0.713,7.325 to 0.713,8.075 to 4.925,8.075 to 4.925,7.325 to 0.713,7.325 +line from 1.238,8.075 to 1.238,8.637 +line from 1.262,8.537 to 1.238,8.637 to 1.213,8.537 +line from 1.613,8.825 to 1.975,8.575 +line from 1.878,8.611 to 1.975,8.575 to 1.907,8.652 +line from 2.675,8.575 to 4.050,8.575 +line from 3.950,8.550 to 4.050,8.575 to 3.950,8.600 +line from 2.675,9.137 to 3.050,9.137 +line from 2.950,9.112 to 3.050,9.137 to 2.950,9.162 +line from 3.425,9.325 to 3.425,9.637 +line from 3.450,9.537 to 3.425,9.637 to 3.400,9.537 +line from 1.613,8.825 to 1.925,9.137 +line from 1.872,9.049 to 1.925,9.137 to 1.837,9.084 +.ps 11 +"'BSD'" at 2.138,9.481 ljust +.ps 11 +"1.2" at 1.113,9.543 ljust +.ps 11 +"1.1" at 1.125,8.831 ljust +.ps 11 +"1.1.1.1" at 4.175,8.543 ljust +.ps 11 +"1.1.1.2" at 4.175,9.281 ljust +.ps 11 +"1.1.1.3" at 4.175,9.993 ljust +.ps 11 +"1.1.2.2" at 3.175,9.793 ljust +.ps 11 +"1.1.2.1" at 3.175,9.106 ljust +.ps 11 +"rcsfile.c,v" at 2.425,7.706 ljust +.ps 11 +"1.1.1" at 2.175,8.568 ljust +.ps 11 +"'SunOS'" at 2.125,8.243 ljust +.ps 11 +"1.1.2" at 2.163,9.131 ljust +.PE +.DE +.hl +.ce 100 +.LG +\fBFigure 4.\fP +.SM +\fBcvs\fP Support For \*QDual\*U Derivations +.ce 0 +.sp +.KE +This example tracks the SunOS distribution but includes major changes from +Berkeley. +These BSD files are saved directly in the +.SM +RCS +.LG +file off a new branch. +.NH 2 +Location Independent Module Database +.PP +\fBcvs\fP contains support for a simple, yet powerful, \*Qmodule\*U database. +For reasons of efficiency, this database is stored in \fBndbm\fP\|(3) format. +The module database is used to apply names to collections of directories +and files as a matter of convenience for checking out pieces of a large +software distribution. +The database records the physical location of the sources as a form of +information hiding, allowing one to check out whole directory hierarchies +or individual files without regard for their actual location within the +global source distribution. +.PP +Consider the following small sample of a module database, which must be +tailored manually to each specific source repository environment: +.DS +\f(CW #key [-option argument] directory [files...] + diff bin/diff + libc lib/libc + sys -o sys/tools/make_links sys + modules -i mkmodules CVSROOT.adm modules + kernel -a sys lang/adb + ps bin Makefile ps.c\fP +.DE +.PP +The \*Qdiff\*U and \*Qlibc\*U modules refer to whole directory hierarchies that +are extracted on check out. +The \*Qsys\*U module extracts the \*Qsys\*U hierarchy, and runs the +\*Qmake_links\*U program at the end of the check out process (the \fI-o\fP +option specifies a program to run on check\fIo\fPut). +The \*Qmodules\*U module allows one to edit the module database file and +runs the \*Qmkmodules\*U program on check\fIi\fPn to regenerate the +\fBndbm\fP database that \fBcvs\fP uses. +The \*Qkernel\*U module is an alias (as the \fI-a\fP option specifies) +which causes the remaining arguments after the \fI-a\fP to be interpreted +exactly as if they had been specified on the command line. +This is useful for objects that require shared pieces of code from far away +places to be compiled (as is the case with the kernel debugger, \fBkadb\fP, +which shares code with the standard \fBadb\fP debugger). +The \*Qps\*U module shows that the source for \*Qps\*U lives in the \*Qbin\*U +directory, but only \fIMakefile\fP and \fIps.c\fP are required to build the +object. +.PP +The module database at Prisma is now populated for the entire UNIX +distribution and thereby allows us to issue the +following convenient commands to check out components of the UNIX +distribution without regard for their actual location within the master source +repository: +.DS +\f(CW example% cvs checkout diff + example% cvs checkout libc ps + example% cd diff; make\fP +.DE +.PP +In building the module database file, it is quite possible to have name +conflicts within a global software distribution. +For example, SunOS provides two \fBcat\fP programs: +one for the standard environment, \fI/bin/cat\fP, and one for the System V +environment, \fI/usr/5bin/cat\fP. +We resolved this conflict by naming the standard \fBcat\fP module +\*Qcat\*U, and the System V \fBcat\fP module \*Q5cat\*U. +Similar name modifications must be applied to other conflicting names, as +might be found between a utility program and a library function, though +Prisma chose not to include individual library functions within the module +database at this time. +.NH 2 +Configurable Logging Support +.PP +The \fBcvs\fP \*Qcommit\*U command is used to make a permanent change to the +master source repository (where the +.SM +RCS +.LG +\*Q,v\*U files live). +Whenever a \*Qcommit\*U is done, the log message for the change is carefully +logged by an arbitrary program (in a file, notesfile, news database, or +mail). +For example, a collection of these updates can be used to produce release +notices. +\fBcvs\fP can be configured to send log updates through one or more filter +programs, based on a regular expression match on the directory that is +being changed. +This allows multiple related or unrelated projects to exist within a single +\fBcvs\fP source repository tree, with each different project sending its +\*Qcommit\*U reports to a unique log device. +.PP +A sample logging configuration file might look as follows: +.DS +\f(CW #regex filter-program + DEFAULT /usr/local/bin/nfpipe -t %s utils.updates + ^diag /usr/local/bin/nfpipe -t %s diag.updates + ^local /usr/local/bin/nfpipe -t %s local.updates + ^perf /usr/local/bin/nfpipe -t %s perf.updates + ^sys /usr/local/bin/nfpipe -t %s kernel.updates\fP +.DE +.PP +This sample allows the diagnostics and performance groups to +share the same source repository with the kernel and utilities groups. +Changes that they make are sent directly to their own notesfile [Essick] +through the \*Qnfpipe\*U program. +A sufficiently simple title is substituted for the \*Q%s\*U argument before +the filter program is executed. +This logging configuration file is tailored manually to each specific +source repository environment. +.NH 2 +Tagged Releases and Dates +.PP +Any release can be given a symbolic tag name that is stored directly in the +.SM +RCS +.LG +files. +This tag can be used at any time to get an exact copy of any previous +release. +With equal ease, one can also extract an exact copy of the source files as +of any arbitrary date in the past as well. +Thus, all that's required to tag the current kernel, and to tag the kernel +as of the Fourth of July is: +.DS +\f(CW example% cvs tag TEST_KERNEL kernel + example% cvs tag -D 'July 4' PATRIOTIC_KERNEL kernel\fP +.DE +The following command would retrieve an exact copy of the test kernel at +some later date: +.DS +\f(CW example% cvs checkout -fp -rTEST_KERNEL kernel\fP +.DE +The \fI-f\fP option causes only files that match the specified tag to be +extracted, while the \fI-p\fP option automatically prunes empty directories. +Consequently, directories added to the kernel after the test kernel was +tagged are not included in the newly extracted copy of the test kernel. +.PP +The \fBcvs\fP date support has exactly the same interface as that provided +with +.SM +RCS\c +.LG +, however \fBcvs\fP must process the \*Q,v\*U files directly due to the +special handling required by the vendor branch support. +The standard +.SM +RCS +.LG +date handling only processes one branch (or the main trunk) when checking +out based on a date specification. +\fBcvs\fP must instead process the current \*Qhead\*U branch and, if a +match is not found, proceed to look for a match on the vendor branch. +This, combined with reasons of performance, is why \fBcvs\fP processes +revision (symbolic and numeric) and date specifications directly from the +\*Q,v\*U files. +.NH 2 +Building \*Qpatch\*U Source Distributions +.PP +\fBcvs\fP can produce a \*Qpatch\*U format [Wall] output file which can be +used to bring a previously released software distribution current with the +newest release. +This patch file supports an entire directory hierarchy within a single +patch, as well as being able to add whole new files to the previous +release. +One can combine symbolic revisions and dates together to display changes in +a very generic way: +.DS +\f(CW example% cvs patch -D 'December 1, 1988' \e + -D 'January 1, 1989' sys\fP +.DE +This example displays the kernel changes made in the month of December, +1988. +To release a patch file, for example, to take the \fBcvs\fP distribution +from version 1.0 to version 1.4 might be done as follows: +.DS +\f(CW example% cvs patch -rCVS_1_0 -rCVS_1_4 cvs\fP +.DE +.NH +CVS Experience +.NH 2 +Statistics +.PP +A quick summary of the scale that \fBcvs\fP is addressing today +can be found in Table 1. +.KF +.TS +box center tab(:); +c s +c s +c | c +l | n . +\fB\s+2Revision Control Statistics at Prisma +as of 11/11/89\fP\s-2 +_ +How Many...:Total += +Files:17243 +Directories:1005 +Lines of code:3927255 +Removed files:131 +Software developers:14 +Software groups:6 +Megabytes of source:128 +.TE +.ce 100 +.LG +\fBTable 1.\fP +.SM +\fBcvs\fP Statistics +.ce 0 +.sp .3 +.KE +Table 2 shows the history of files changed or added and the number +of source lines affected by the change at Prisma. +Only changes made to the kernel sources are included. +.KF +.TS +box center tab(:); +c s s s s +c s s s s +c || c | c || c | c +c || c | c || c | c +l || n | n || n | n. +\fB\s+2Prisma Kernel Source File Changes +By Month, 1988-1989\fP\s-2 +_ +Month:# Changed:# Lines:# Added:# Lines +\^:Files:Changed:Files:Added += +Dec:87:3619:68:9266 +Jan:39:4324:0:0 +Feb:73:1578:5:3550 +Mar:99:5301:18:11461 +Apr:112:7333:11:5759 +May:138:5371:17:13986 +Jun:65:2261:27:12875 +Jul:34:2000:1:58 +Aug:65:6378:8:4724 +Sep:266:23410:113:39965 +Oct:22:621:1:155 +Total:1000:62196:269:101799 +.TE +.ce 100 +.LG +\fBTable 2.\fP +.SM +\fBcvs\fP Usage History for the Kernel +.ce 0 +.sp +.KE +The large number of source file changes made in September are the result of +merging the SunOS 4.0.3 sources into the kernel. +This merge process is described in section 3.3. +.NH 2 +Performance +.PP +The performance of \fBcvs\fP is currently quite reasonable. +Little effort has been expended on tuning \fBcvs\fP, although performance +related decisions were made during the \fBcvs\fP design. +For example, \fBcvs\fP parses the +.SM +RCS +.LG +\*Q,v\*U files directly instead of running an +.SM +RCS +.LG +process. +This includes following branches as well as integrating with the vendor +source branches and the main trunk when checking out files based on a date. +.PP +Checking out the entire kernel source tree (1223 files/59 directories) +currently takes 16 wall clock minutes on a Sun-4/280. +However, bringing the tree up-to-date with the current kernel sources, once +it has been checked out, takes only 1.5 wall clock minutes. +Updating the \fIcomplete\fP 128 MByte source tree under \fBcvs\fP control +(17243 files/1005 directories) takes roughly 28 wall clock minutes and +utilizes one-third of the machine. +For now this is entirely acceptable; improvements on these numbers will +possibly be made in the future. +.NH 2 +The SunOS 4.0.3 Merge +.PP +The true test of the \fBcvs\fP vendor branch support came with the arrival +of the SunOS 4.0.3 source upgrade tape. +As described above, the \fBcheckin\fP program was used to install the new +sources and the resulting output file listed the files that had been +locally modified, needing to be merged manually. +For the kernel, there were 94 files in conflict. +The \fBcvs\fP \*Qjoin\*U command was used on each of the 94 conflicting +files, and the remaining conflicts were resolved. +.PP +The \*Qjoin\*U command performs an \fBrcsmerge\fP operation. +This in turn uses \fI/usr/lib/diff3\fP to produce a three-way diff file. +As it happens, the \fBdiff3\fP program has a hard-coded limit of 200 +source-file changes maximum. +This proved to be too small for a few of the kernel files that needed +merging by hand, due to the large number of local changes that Prisma had +made. +The \fBdiff3\fP problem was solved by increasing the hard-coded limit by an +order of magnitude. +.PP +The SunOS 4.0.3 kernel source upgrade distribution contained +346 files, 233 of which were modifications to previously released files, +and 113 of which were newly added files. +\fBcheckin\fP added the 113 new files to the source repository +without intervention. +Of the 233 modified files, 139 dropped in cleanly by \fBcheckin\fP, since +Prisma had not made any local changes to them, and 94 required manual +merging due to local modifications. +The 233 modified files consisted of 20,766 lines of differences. +It took one developer two days to manually merge the 94 files using the +\*Qjoin\*U command and resolving conflicts manually. +An additional day was required for kernel debugging. +The entire process of merging over 20,000 lines of differences was +completed in less than a week. +This one time-savings alone was justification enough for the \fBcvs\fP +development effort; we expect to gain even more when tracking future SunOS +releases. +.NH +Future Enhancements and Current Bugs +.PP +Since \fBcvs\fP was designed to be incomplete, for reasons of design +simplicity, there are naturally a good +number of enhancements that can be made to make it more useful. +As well, some nuisances exist in the current implementation. +.RS +.IP \(bu 3 +\fBcvs\fP does not currently \*Qremember\*U who has a checked out a copy of a +module. +As a result, it is impossible to know who might be working on the same +module that you are. +A simple-minded database that is updated nightly would likely suffice. +.IP \(bu 3 +Signal processing, keyboard interrupt handling in particular, is currently +somewhat weak. +This is due to the heavy use of the \fBsystem\fP\|(3) library +function to execute +.SM +RCS +.LG +programs like \fBco\fP and \fBci\fP. +It sometimes takes multiple interrupts to make \fBcvs\fP quit. +This can be fixed by using a home-grown \fBsystem\fP\|() replacement. +.IP \(bu 3 +Security of the source repository is currently not dealt with directly. +The usual UNIX approach of user-group-other security permissions through +the file system is utilized, but nothing else. +\fBcvs\fP could likely be a set-group-id executable that checks a +protected database to verify user access permissions for particular objects +before allowing any operations to affect those objects. +.IP \(bu 3 +With every checked-out directory, \fBcvs\fP maintains some administrative +files that record the current revision numbers of the checked-out files as +well as the location of the respective source repository. +\fBcvs\fP does not recover nicely at all if these administrative files are +removed. +.IP \(bu 3 +The source code for \fBcvs\fP has been tested extensively on Sun-3 and +Sun-4 systems, all running SunOS 4.0 or later versions of the operating +system. +Since the code has not yet been compiled under other platforms, the overall +portability of the code is still questionable. +.IP \(bu 3 +As witnessed in the previous section, the \fBcvs\fP method for tracking +third party vendor source distributions can work quite nicely. +However, if the vendor changes the directory structure or the file names +within the source distribution, \fBcvs\fP has no way of matching the old +release with the new one. +It is currently unclear as to how to solve this, though it is certain to +happen in practice. +.RE +.NH +Availability +.PP +The \fBcvs\fP program sources can be found in a recent posting to the +\fBcomp.sources.unix\fP newsgroup. +It is also currently available via anonymous ftp from \*Qprisma.com\*U. +Copying rights for \fBcvs\fP will be covered by the GNU General Public +License. +.NH +Summary +.PP +Prisma has used \fBcvs\fP since December, 1988. +It has evolved to meet our specific needs of revision and release control. +We will make our code freely available so that others can +benefit from our work, and can enhance \fBcvs\fP to meet broader needs yet. +.PP +Many of the other software release and revision control systems, like the +one described in [Glew], appear to use a collection of tools that are +geared toward specific environments \(em one set of tools for the kernel, +one set for \*Qgeneric\*U software, one set for utilities, and one set for +kernel and utilities. +Each of these tool sets apparently handle some specific aspect of the +problem uniquely. +\fBcvs\fP took a somewhat different approach. +File sharing through symbolic or hard links is not addressed; instead, the +disk space is simply burned since it is \*Qcheap.\*U +Support for producing objects for multiple architectures is not addressed; +instead, a parallel checked-out source tree must be used for each +architecture, again wasting disk space to simplify complexity and ease of +use \(em punting on this issue allowed \fIMakefile\fPs to remain +unchanged, unlike the approach taken in [Mahler], thereby maintaining closer +compatibility with the third-party vendor sources. +\fBcvs\fP is essentially a source-file server, making no assumptions or +special handling of the sources that it controls. +To \fBcvs\fP: +.QP +A source is a source, of course, of course, unless of course the source is +Mr. Ed.\** +.FS +\fBcvs\fP, of course, does not really discriminate against Mr. Ed.\** +.FE +.FS +Yet. +.FE +.LP +Sources are maintained, saved, and retrievable at any time based on +symbolic or numeric revision or date in the past. +It is entirely up to \fBcvs\fP wrapper programs to provide for release +environments and such. +.PP +The major advantage of \fBcvs\fP over the +many other similar systems that have already been designed is the +simplicity of \fBcvs\fP. +\fBcvs\fP contains only three programs that do all the work of release +and revision control, and two manually-maintained administrative +files for each source repository. +Of course, the deciding factor of any tool is whether people use it, and if +they even \fIlike\fP to use it. +At Prisma, \fBcvs\fP prevented members of the kernel +group from killing each other. +.NH +Acknowledgements +.PP +Many thanks to Dick Grune at Vrije Universiteit in Amsterdam for his work +on the original version of \fBcvs\fP and for making it available to the +world. +Thanks to Jeff Polk of Prisma for helping with the design of the module +database, vendor branch support, and for writing the \fBcheckin\fP shell +script. +Thanks also to the entire software group at Prisma for taking the +time to review the paper and correct my grammar. +.NH +References +.IP [Bell] 12 +Bell Telephone Laboratories. +\*QSource Code Control System User's Guide.\*U +\fIUNIX System III Programmer's Manual\fP, October 1981. +.IP [Courington] 12 +Courington, W. +\fIThe Network Software Environment\fP, +Sun Technical Report FE197-0, Sun Microsystems Inc, February 1989. +.IP [Essick] 12 +Essick, Raymond B. and Robert Bruce Kolstad. +\fINotesfile Reference Manual\fP, +Department of Computer Science Technical Report #1081, +University of Illinois at Urbana-Champaign, Urbana, Illinois, +1982, p. 26. +.IP [Glew] 12 +Glew, Andy. +\*QBoxes, Links, and Parallel Trees: +Elements of a Configuration Management System.\*U +\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX, +New Orleans, April 1989. +.IP [Grune] 12 +Grune, Dick. +Distributed the original shell script version of \fBcvs\fP in the +\fBcomp.sources.unix\fP volume 6 release in 1986. +.IP [Honda] 12 +Honda, Masahiro and Terrence Miller. +\*QSoftware Management Using a CASE Environment.\*U +\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX, +New Orleans, April 1989. +.IP [Mahler] 12 +Mahler, Alex and Andreas Lampen. +\*QAn Integrated Toolset for Engineering Software Configurations.\*U +\fIProceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on +Practical Software Development Environments\fP, ACM, Boston, November 1988. +Described is the \fBshape\fP toolkit posted to the +\fBcomp.sources.unix\fP newsgroup in the volume 19 release. +.IP [Tichy] 12 +Tichy, Walter F. +\*QDesign, Implementation, and Evaluation of a Revision Control System.\*U +\fIProceedings of the 6th International Conference on Software +Engineering\fP, IEEE, Tokyo, September 1982. +.IP [Wall] 12 +Wall, Larry. +The \fBpatch\fP program is an indispensable tool for applying a diff file +to an original. +Can be found on uunet.uu.net in ~ftp/pub/patch.tar. diff --git a/gnu/usr.bin/cvs/examples/commitinfo b/gnu/usr.bin/cvs/examples/commitinfo new file mode 100644 index 0000000..bdb94b6 --- /dev/null +++ b/gnu/usr.bin/cvs/examples/commitinfo @@ -0,0 +1,21 @@ +# +# commitinfo,v 1.2 1992/03/31 04:19:47 berliner Exp +# +# The "commitinfo" file is used to control pre-commit checks. +# The filter on the right is invoked with the repository and a list +# of files to check. A non-zero exit of the filter program will +# cause the commit to be aborted. +# +# The first entry on a line is a regular expression which is tested +# against the directory that the change is being committed to, relative to the +# $CVSROOT. If a match is found, then the remainder of the line is the +# name of the filter to run. +# +# If the repository name does not match any of the regular expressions in this +# file, the "DEFAULT" line is used, if it is specified. +# +# If the name ALL appears as a regular expression it is always used +# in addition to the first matching regex or DEFAULT. +# +^cvs checkforcvsid +DEFAULT checkforid diff --git a/gnu/usr.bin/cvs/examples/editinfo b/gnu/usr.bin/cvs/examples/editinfo new file mode 100644 index 0000000..b454abd --- /dev/null +++ b/gnu/usr.bin/cvs/examples/editinfo @@ -0,0 +1,30 @@ +# +# editinfo,v 1.1 1992/03/21 06:49:39 berliner Exp +# +# The "editinfo" file is used to allow verification of logging +# information. It works best when a template (as specified in the +# rcsinfo file) is provided for the logging procedure. Given a +# template with locations for, a bug-id number, a list of people who +# reviewed the code before it can be checked in, and an external +# process to catalog the differences that were code reviewed, the +# following test can be applied to the code: +# +# Making sure that the entered bug-id number is correct. +# Validating that the code that was reviewed is indeed the code being +# checked in (using the bug-id number or a seperate review +# number to identify this particular code set.). +# +# If any of the above test failed, then the commit would be aborted. +# +# Actions such as mailing a copy of the report to each reviewer are +# better handled by an entry in the loginfo file. +# +# Although these test could be handled by an interactive script being +# called via an entry in commitinfo, The information reported in +# such a script can't be easily merged into the report. +# +# One thing that should be noted is the the ALL keyword is not +# supported. There can be only one entry that matches a given +# repository. +# +DEFAULT $CVSROOT/CVSROOT/edit "%s" diff --git a/gnu/usr.bin/cvs/examples/loginfo b/gnu/usr.bin/cvs/examples/loginfo new file mode 100644 index 0000000..6339439 --- /dev/null +++ b/gnu/usr.bin/cvs/examples/loginfo @@ -0,0 +1,20 @@ +# +# @(#)loginfo 1.5 92/03/31 +# +# The "loginfo" file is used to control where "cvs commit" log information +# is sent. The first entry on a line is a regular expression which is tested +# against the directory that the change is being made to, relative to the +# $CVSROOT. If a match is found, then the remainder of the line is a filter +# program that should expect log information on its standard input. +# +# The filter program may use one and only one % modifier (ala printf). If +# %s is specified in the filter program, a brief title is included (enclosed +# in single quotes) showing the modified file names. +# +# If the repository name does not match any of the regular expressions in this +# file, the "DEFAULT" line is used, if it is specified. +# +# If the name ALL appears as a regular expression it is always used +# in addition to the first matching regex or DEFAULT. +# +DEFAULT $CVSROOT/CVSROOT/log.pl %s $CVSROOT/CVSROOT/commitlog diff --git a/gnu/usr.bin/cvs/examples/modules b/gnu/usr.bin/cvs/examples/modules new file mode 100644 index 0000000..893b47b --- /dev/null +++ b/gnu/usr.bin/cvs/examples/modules @@ -0,0 +1,566 @@ +# +# CVS Modules file for Prisma sources +# @(#)modules 1.5 92/03/31 +# +# Three different line formats are valid: +# key -a aliases... +# key [options] directory +# key [options] directory files... +# +# Where "options" are composed of: +# -i prog Run "prog" on "cvs commit" from top-level of module. +# -o prog Run "prog" on "cvs checkout" of module. +# -t prog Run "prog" on "cvs rtag" of module. +# -u prog Run "prog" on "cvs update" of module. +# -d dir Place module in directory "dir" instead of module name. +# -l Top-level directory only -- do not recurse. +# +# And "directory" is a path to a directory relative to $CVSROOT. +# +# The "-a" option specifies an alias. An alias is interpreted as if +# everything on the right of the "-a" had been typed on the command line. +# +# You can encode a module within a module by using the special '&' +# character to interpose another module into the current module. This +# can be useful for creating a module that consists of many directories +# spread out over the entire source repository. +# + +# Convenient aliases +world -a . +kernel -a sys lang/adb sparcsim + +# CVSROOT support +CVSROOT -i /usr/local/bin/mkmodules CVSROOT +modules -i /usr/local/bin/mkmodules CVSROOT modules +loginfo -i /usr/local/bin/mkmodules CVSROOT loginfo +commitinfo -i /usr/local/bin/mkmodules CVSROOT commitinfo +rcsinfo -i /usr/local/bin/mkmodules CVSROOT rcsinfo + +# The "sys" entry exists only to make symbolic links after checkout +sys -o sys/tools/make_links sys + +# Sub-directories of "bin" +awk bin/awk +csh bin/csh +diff bin/diff +make bin/make +sed bin/sed +sh bin/sh + +# Programs that live in "bin" +cat bin Makefile cat.c +chgrp bin Makefile chgrp.c +chmod bin Makefile chmod.c +cmp bin Makefile cmp.c +cp bin Makefile cp.c +date bin Makefile date.c +dd bin Makefile dd.c +df bin Makefile df.c +domainname bin Makefile domainname.c +du bin Makefile du.c +echo bin Makefile echo.c +ed bin Makefile ed.c +env bin Makefile env.c +expr bin Makefile expr.c +grep bin Makefile grep.c +hostid bin Makefile hostid.c +hostname bin Makefile hostname.c +kill bin Makefile kill.c +ldd bin Makefile ldd.c +line bin Makefile line.c +ln bin Makefile ln.c +login bin Makefile login.c +ls bin Makefile ls.c +mail bin Makefile mail.c +mkdir bin Makefile mkdir.c +mt bin Makefile mt.c +mv bin Makefile mv.c +newgrp bin Makefile newgrp.c +nice bin Makefile nice.c +od bin Makefile od.c +pagesize bin Makefile pagesize.c +passwd bin Makefile passwd.c +pr bin Makefile pr.c +ps bin Makefile ps.c +pwd bin Makefile pwd.c +rm bin Makefile rm.c +rmail bin Makefile rmail.c +rmdir bin Makefile rmdir.c +stty bin Makefile stty.c +su bin Makefile su.c +sync bin Makefile sync.c +tar bin Makefile tar.c +tee bin Makefile tee.c +test bin Makefile test.c +time bin Makefile time.c +wall bin Makefile wall.c +who bin Makefile who.c +write bin Makefile write.c + +# Sub-directories of "etc" +dump etc/dump +files etc/files +fsck etc/fsck +getty etc/getty +in.routed etc/in.routed +restore etc/restore +rpc.lockd etc/rpc.lockd +rpc.statd etc/rpc.statd + +# Programs that live in "etc" +arp etc Makefile arp.c +biod etc Makefile biod.c +chown etc Makefile chown.c +clri etc Makefile clri.c +dkinfo etc Makefile dkinfo.c +dmesg etc Makefile dmesg.c +fsirand etc Makefile fsirand.c +halt etc Makefile halt.c +ifconfig etc Makefile ifconfig.c +in.rlogind etc Makefile in.rlogind.c +in.rshd etc Makefile in.rshd.c +inetd etc Makefile inetd.c +init etc Makefile init.c +mkfs etc Makefile mkfs.c +mknod etc Makefile mknod.c +mount etc Makefile mount.c +newfs etc Makefile newfs.c +nfsd etc Makefile nfsd.c +portmap etc Makefile portmap.c +pstat etc Makefile pstat.c +reboot etc Makefile reboot.c +renice etc Makefile renice.c +rmt etc Makefile rmt.c +shutdown etc Makefile shutdown.c +syslogd etc Makefile syslogd.c +umount etc Makefile umount.c +update etc Makefile update.c +vipw etc Makefile vipw.c +ypbind etc Makefile ypbind.c + +# Sub-directories of "games" +adventure games/adventure +backgammon games/backgammon +battlestar games/battlestar +boggle games/boggle +chess games/chess +ching games/ching +cribbage games/cribbage +fortune games/fortune +hack games/hack +hangman games/hangman +hunt games/hunt +life games/life +mille games/mille +monop games/monop +quiz games/quiz +robots games/robots +sail games/sail +snake games/snake +trek games/trek + +# Programs that live in "games" +arithmetic games Makefile arithmetic.c +banner games Makefile banner.c +bcd games Makefile bcd.c +bj games Makefile bj.c +btlgammon games Makefile btlgammon.c +canfield games Makefile canfield.c +cfscores games Makefile cfscores.c +craps games Makefile craps.c +factor games Makefile factor.c +fish games Makefile fish.c +moo games Makefile moo.c +number games Makefile number.c +primes games Makefile primes.c +rain games Makefile rain.c +random games Makefile random.c +worm games Makefile worm.c +worms games Makefile worms.c +wump games Makefile wump.c + +# Sub-directories of "lang" +adb lang/adb +as lang/as +boot lang/boot +c2 lang/c2 +cgrdr lang/cgrdr +compile lang/compile +cpp lang/cpp +dbx lang/dbx +f77 lang/f77 +inline lang/inline +iropt lang/iropt +ld lang/ld +lint lang/lint +m4 lang/m4 +pascal lang/pascal +pcc lang/pcc +ratfor lang/ratfor +rtld lang/rtld +tcov lang/tcov +vroot lang/vroot + +# Programs that live in "lang" +ar lang Makefile ar.c +nm lang Makefile nm.c +ranlib lang Makefile ranlib.c +size lang Makefile size.c +strip lang Makefile strip.c +symorder lang Makefile symorder.c + +# Sub-directories of "lib" +csu lib/csu +libc lib/libc + +# Programs that live in "lib" +# NONE + +# Sub-directories of "lib/libc" +libc_compat lib/libc/compat +libc_crt lib/libc/crt +libc_des lib/libc/des +libc_gen lib/libc/gen +libc_net lib/libc/net +libc_inet lib/libc/inet +libc_rpc lib/libc/rpc +libc_stdio lib/libc/stdio +libc_sun lib/libc/sun +libc_sys lib/libc/sys +libc_yp lib/libc/yp + +# Programs that live in "lib/libc" +# NONE + +#Sub-directories of "local" +notes local/notes + +# Sub-directories of "man" +man1 man/man1 +man2 man/man2 +man3 man/man3 +man4 man/man4 +man5 man/man5 +man6 man/man6 +man7 man/man7 +man8 man/man8 +manl man/manl + +# Programs that live in "man" +# NONE + +# Sub-directories of "old" +old_compact old/compact +old_eyacc old/eyacc +old_filemerge old/filemerge +old_make old/make + +# Programs that live in "old" +old_analyze old Makefile analyze.c +old_prmail old Makefile prmail.c +old_pti old Makefile pti.c +old_syslog old Makefile syslog.c + +# Sub-directories of "ucb" +Mail ucb/Mail +compress ucb/compress +error ucb/error +ex ucb/ex +ftp ucb/ftp +gprof ucb/gprof +indent ucb/indent +lpr ucb/lpr +more ucb/more +msgs ucb/msgs +netstat ucb/netstat +rdist ucb/rdist +talk ucb/talk +tftp ucb/tftp +tset ucb/tset +vgrind ucb/vgrind + +# Programs that live in "ucb" +biff ucb Makefile biff.c +checknr ucb Makefile checknr.c +clear ucb Makefile clear.c +colcrt ucb Makefile colcrt.c +colrm ucb Makefile colrm.c +ctags ucb Makefile ctags.c +expand ucb Makefile expand.c +finger ucb Makefile finger.c +fold ucb Makefile fold.c +from ucb Makefile from.c +fsplit ucb Makefile fsplit.c +gcore ucb Makefile gcore.c +groups ucb Makefile groups.c +head ucb Makefile head.c +last ucb Makefile last.c +lastcomm ucb Makefile lastcomm.c +leave ucb Makefile leave.c +logger ucb Makefile logger.c +man_prog ucb Makefile man.c +mkstr ucb Makefile mkstr.c +printenv ucb Makefile printenv.c +quota ucb Makefile quota.c +rcp ucb Makefile rcp.c +rdate ucb Makefile rdate.c +rlogin ucb Makefile rlogin.c +rsh ucb Makefile rsh.c +rup ucb Makefile rup.c +ruptime ucb Makefile ruptime.c +rusers ucb Makefile rusers.c +rwho ucb Makefile rwho.c +sccs ucb Makefile sccs.c +script ucb Makefile script.c +soelim ucb Makefile soelim.c +strings ucb Makefile strings.c +tail ucb Makefile tail.c +tcopy ucb Makefile tcopy.c +telnet ucb Makefile telnet.c +ul ucb Makefile ul.c +unexpand ucb Makefile unexpand.c +unifdef ucb Makefile unifdef.c +users ucb Makefile users.c +vmstat ucb Makefile vmstat.c +w ucb Makefile w.c +wc ucb Makefile wc.c +what ucb Makefile what.c +whatis ucb Makefile whatis.c +whereis ucb Makefile whereis.c +whoami ucb Makefile whoami.c +whois ucb Makefile whois.c +xstr ucb Makefile xstr.c +yes ucb Makefile yes.c + +# Sub-directories of "usr.bin" +calendar usr.bin/calendar +cflow usr.bin/cflow +ctrace usr.bin/ctrace +cxref usr.bin/cxref +dc usr.bin/dc +des usr.bin/des +diff3 usr.bin/diff3 +sun_eqn usr.bin/eqn +file usr.bin/file +find usr.bin/find +graph usr.bin/graph +lex usr.bin/lex +sun_neqn usr.bin/neqn +sun_nroff usr.bin/nroff +sun_plot usr.bin/plot +prof usr.bin/prof +refer usr.bin/refer +rpcgen usr.bin/rpcgen +spell usr.bin/spell +sun_tbl usr.bin/tbl +tip usr.bin/tip +trace usr.bin/trace +sun_troff usr.bin/troff +uucp usr.bin/uucp +xsend usr.bin/xsend +yacc usr.bin/yacc + +# Programs that live in "usr.bin" +basename usr.bin Makefile basename.c +bc usr.bin Makefile bc.c +cal usr.bin Makefile cal.c +cb usr.bin Makefile cb.c +checkeq usr.bin Makefile checkeq.c +chkey usr.bin Makefile chkey.c +click usr.bin Makefile click.c +col usr.bin Makefile col.c +comm usr.bin Makefile comm.c +cpio usr.bin Makefile cpio.c +crypt usr.bin Makefile crypt.c +csplit usr.bin Makefile csplit.c +cut usr.bin Makefile cut.c +deroff usr.bin Makefile deroff.c +egrep usr.bin Makefile egrep.c +fgrep usr.bin Makefile fgrep.c +getopt usr.bin Makefile getopt.c +id usr.bin Makefile id.c +installcmd usr.bin Makefile installcmd.c +iostat usr.bin Makefile iostat.c +ipcrm usr.bin Makefile ipcrm.c +ipcs usr.bin Makefile ipcs.c +join usr.bin Makefile join.c +keylogin usr.bin Makefile keylogin.c +logname usr.bin Makefile logname.c +look usr.bin Makefile look.c +mesg usr.bin Makefile mesg.c +nl usr.bin Makefile nl.c +pack usr.bin Makefile pack.c +paste usr.bin Makefile paste.c +ptx usr.bin Makefile ptx.c +rev usr.bin Makefile rev.c +screenblank usr.bin Makefile screenblank.c +sdiff usr.bin Makefile sdiff.c +sleep usr.bin Makefile sleep.c +sort usr.bin Makefile sort.c +spline usr.bin Makefile spline.c +split usr.bin Makefile split.c +sum usr.bin Makefile sum.c +touch usr.bin Makefile touch.c +tr usr.bin Makefile tr.c +tsort usr.bin Makefile tsort.c +tty usr.bin Makefile tty.c +uniq usr.bin Makefile uniq.c +units usr.bin Makefile units.c +unpack usr.bin Makefile unpack.c +xargs usr.bin Makefile xargs.c +ypcat usr.bin Makefile ypcat.c +ypmatch usr.bin Makefile ypmatch.c +yppasswd usr.bin Makefile yppasswd.c +ypwhich usr.bin Makefile ypwhich.c + +# Sub-directories of "usr.etc" +automount usr.etc/automount +c2convert usr.etc/c2convert +config usr.etc/config +cron usr.etc/cron +eeprom usr.etc/eeprom +etherfind usr.etc/etherfind +format usr.etc/format +htable usr.etc/htable +implog usr.etc/implog +in.ftpd -a usr.etc/in.ftpd ucb/ftp +in.named usr.etc/in.named +in.rwhod usr.etc/in.rwhod +keyserv usr.etc/keyserv +ndbootd usr.etc/ndbootd +praudit usr.etc/praudit +rexd usr.etc/rexd +rpc.bootparamd usr.etc/rpc.bootparamd +termcap usr.etc/termcap +upgrade usr.etc/upgrade +yp usr.etc/yp +zic usr.etc/zic + +# Programs that live in "usr.etc" +ac usr.etc Makefile ac.c +accton usr.etc Makefile accton.c +audit usr.etc Makefile audit.c +auditd usr.etc Makefile auditd.c +catman usr.etc Makefile catman.c +chroot usr.etc Makefile chroot.c +dcheck usr.etc Makefile dcheck.c +devnm usr.etc Makefile devnm.c +dumpfs usr.etc Makefile dumpfs.c +edquota usr.etc Makefile edquota.c +exportfs usr.etc Makefile exportfs.c +foption usr.etc Makefile foption.c +gettable usr.etc Makefile gettable.c +grpck usr.etc Makefile grpck.c +icheck usr.etc Makefile icheck.c +in.comsat usr.etc Makefile in.comsat.c +in.fingerd usr.etc Makefile in.fingerd.c +in.rexecd usr.etc Makefile in.rexecd.c +in.telnetd usr.etc Makefile in.telnetd.c +in.tnamed usr.etc Makefile in.tnamed.c +kgmon usr.etc Makefile kgmon.c +link usr.etc Makefile link.c +mkfile usr.etc Makefile mkfile.c +mkproto usr.etc Makefile mkproto.c +mount_lo usr.etc Makefile mount_lo.c +ncheck usr.etc Makefile ncheck.c +nfsstat usr.etc Makefile nfsstat.c +ping usr.etc Makefile ping.c +pwck usr.etc Makefile pwck.c +quot usr.etc Makefile quot.c +quotacheck usr.etc Makefile quotacheck.c +quotaon usr.etc Makefile quotaon.c +rarpd usr.etc Makefile rarpd.c +repquota usr.etc Makefile repquota.c +route usr.etc Makefile route.c +rpc.etherd usr.etc Makefile rpc.etherd.c +rpc.mountd usr.etc Makefile rpc.mountd.c +rpc.pwdauthd usr.etc Makefile rpc.pwdauthd.c +rpc.rquotad usr.etc Makefile rpc.rquotad.c +rpc.rstatd usr.etc Makefile rpc.rstatd.c +rpc.rusersd usr.etc Makefile rpc.rusersd.c +rpc.rwalld usr.etc Makefile rpc.rwalld.c +rpc.sprayd usr.etc Makefile rpc.sprayd.c +rpc.yppasswdd usr.etc Makefile rpc.yppasswdd.c +rpc.ypupdated usr.etc Makefile rpc.ypupdated.c +rpcinfo usr.etc Makefile rpcinfo.c +rwall usr.etc Makefile rwall.c +sa usr.etc Makefile sa.c +savecore usr.etc Makefile savecore.c +showmount usr.etc Makefile showmount.c +spray usr.etc Makefile spray.c +swapon usr.etc Makefile swapon.c +trpt usr.etc Makefile trpt.c +tunefs usr.etc Makefile tunefs.c +unlink usr.etc Makefile unlink.c + +# Sub-directories of "usr.lib" +bb_count usr.lib/bb_count +fixedwidthfonts usr.lib/fixedwidthfonts +libcurses usr.lib/libcurses +libdbm usr.lib/libdbm +libg usr.lib/libg +libkvm usr.lib/libkvm +libln usr.lib/libln +liblwp usr.lib/liblwp +libm usr.lib/libm +libmp usr.lib/libmp +libpixrect usr.lib/libpixrect +libplot usr.lib/libplot +libresolv usr.lib/libresolv +librpcsvc usr.lib/librpcsvc +libtermlib usr.lib/libtermlib +liby usr.lib/liby +me usr.lib/me +ms usr.lib/ms +sendmail usr.lib/sendmail +sun_tmac usr.lib/tmac +vfont usr.lib/vfont + +# Programs that live in "usr.lib" +getNAME usr.lib Makefile getNAME +makekey usr.lib Makefile makekey + +# Sub-directories of "5bin" +5diff3 5bin/diff3 +5m4 5bin/m4 + +# Sub-directories of "5bin", but use sources from other places +5cxref -a 5bin/cxref usr.bin/cxref +5sed -a 5bin/sed bin/sed +5lint -a 5bin/lint lang/pcc lang/lint + +# Programs that live in "5bin" +5banner 5bin Makefile banner.c +5cat 5bin Makefile cat.c +5du 5bin Makefile du.c +5echo 5bin Makefile echo.c +5expr 5bin Makefile expr.c +5ls 5bin Makefile ls.c +5nohup 5bin Makefile nohup.c +5od 5bin Makefile od.c +5pg 5bin Makefile pg.c +5pr 5bin Makefile pr.c +5sum 5bin Makefile sum.c +5tabs 5bin Makefile tabs.c +5time 5bin Makefile time.c +5tr 5bin Makefile tr.c +5uname 5bin Makefile uname.c + +# Programs that live in "5bin", but use sources from other places +5chmod -a 5bin/Makefile bin/chmod.c +5date -a 5bin/Makefile bin/date.c +5grep -a 5bin/Makefile bin/grep.c +5stty -a 5bin/Makefile bin/stty.c +5col -a 5bin/Makefile usr.bin/col.c +5sort -a 5bin/Makefile usr.bin/sort.c +5touch -a 5bin/Makefile usr.bin/touch.c + +# Sub-directories of "5lib" +5compile 5lib/compile +5libcurses 5lib/libcurses +5liby 5lib/liby +5terminfo 5lib/terminfo + +# Programs that live in "5lib" +# NONE diff --git a/gnu/usr.bin/cvs/examples/rcsinfo b/gnu/usr.bin/cvs/examples/rcsinfo new file mode 100644 index 0000000..6d91455 --- /dev/null +++ b/gnu/usr.bin/cvs/examples/rcsinfo @@ -0,0 +1,18 @@ +# +# rcsinfo,v 1.3 1992/04/10 18:59:14 berliner Exp +# +# The "rcsinfo" file is used to control templates with which the editor +# is invoked on commit and import. +# +# The first entry on a line is a regular expression which is tested +# against the directory that the change is being made to, relative to the +# $CVSROOT. If a match is found, then the remainder of the line is the +# name of the file that contains the template. +# +# If the repository name does not match any of the regular expressions in this +# file, the "DEFAULT" line is used, if it is specified. +# +# If the name ALL appears as a regular expression it is always used +# in addition to the first matching regex or DEFAULT. +# +DEFAULT /src/master/CVSROOT/rcstemplate diff --git a/gnu/usr.bin/cvs/lib/Makefile b/gnu/usr.bin/cvs/lib/Makefile new file mode 100644 index 0000000..cf3f20f --- /dev/null +++ b/gnu/usr.bin/cvs/lib/Makefile @@ -0,0 +1,8 @@ +LIB = cvs + +CFLAGS += -I${.CURDIR} -I${.CURDIR}/../cvs -DFTIME_MISSING -DHAVE_TIMEZONE + +SRCS = argmatch.c error.c getopt.c sighandle.c strippath.c stripslash.c yesno.c \ + getdate.y fnmatch.c regex.c subr.c myndbm.c hash.c + +.include <bsd.lib.mk> diff --git a/gnu/usr.bin/cvs/lib/Makefile.in b/gnu/usr.bin/cvs/lib/Makefile.in new file mode 100644 index 0000000..a8309f2 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/Makefile.in @@ -0,0 +1,91 @@ +# Makefile for library files used by GNU CVS. +# Do not use this makefile directly, but only from `../Makefile'. +# Copyright (C) 1986, 1988-1992 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# @(#)Makefile.in 1.12 92/03/31 + +SHELL = /bin/sh + +srcdir = @srcdir@ +@VPATH@ + +SOURCES = argmatch.c \ +error.c getopt.c getopt1.c \ +sighandle.c \ +strippath.c stripslash.c yesno.c \ +getdate.y \ +hostname.c fnmatch.c ftruncate.c mkdir.c rename.c regex.c \ +strdup.c getwd.c alloca.c + +OBJECTS = argmatch.o \ +error.o getopt.o getopt1.o \ +sighandle.o \ +strippath.o stripslash.o yesno.o \ +getdate.o \ +@LIBOBJS@ + +DISTFILES = Makefile.in getopt.h \ +fnmatch.h regex.h system.h wait.h $(SOURCES) + +xxx: + @cd ..; $(MAKE) all SUBDIRS=lib + +all: libcvs.a +.PHONY: all + +install: all +.PHONY: install + +tags: $(DISTFILES) + ctags $(DISTFILES) + +TAGS: $(DISTFILES) + etags $(DISTFILES) + +ls: + @echo $(DISTFILES) +.PHONY: ls + +clean: + rm -f *.a *.o *.tab.c getdate.c +.PHONY: clean + +distclean: clean + rm -f tags TAGS Makefile +.PHONY: distclean + +realclean: distclean +.PHONY: realclean + +dist: + ln $(DISTFILES) ../`cat ../.fname`/lib +.PHONY: dist + +libcvs.a: $(OBJECTS) + $(AR) cr $@ $(OBJECTS) + -$(RANLIB) $@ + +getdate.c: getdate.y + @echo expect 8 shift/reduce conflicts + $(YACC) $(srcdir)/getdate.y + -if test -f y.tab.c ; then mv y.tab.c getdate.c ; fi + -if test -f getdate.tab.c ; then mv getdate.tab.c getdate.c ; fi + +fnmatch.o: fnmatch.h +getopt1.o: getopt.h +regex.o: regex.h +getwd.o: system.h diff --git a/gnu/usr.bin/cvs/lib/alloca.c b/gnu/usr.bin/cvs/lib/alloca.c new file mode 100644 index 0000000..d2a54b3 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/alloca.c @@ -0,0 +1,191 @@ +/* + alloca -- (mostly) portable public-domain implementation -- D A Gwyn + + last edit: 86/05/30 rms + include config.h, since on VMS it renames some symbols. + Use xmalloc instead of malloc. + + This implementation of the PWB library alloca() function, + which is used to allocate space off the run-time stack so + that it is automatically reclaimed upon procedure exit, + was inspired by discussions with J. Q. Johnson of Cornell. + + It should work under any C implementation that uses an + actual procedure stack (as opposed to a linked list of + frames). There are some preprocessor constants that can + be defined when compiling for your specific system, for + improved efficiency; however, the defaults should be okay. + + The general concept of this implementation is to keep + track of all alloca()-allocated blocks, and reclaim any + that are found to be deeper in the stack than the current + invocation. This heuristic does not reclaim storage as + soon as it becomes invalid, but it will do so eventually. + + As a special case, alloca(0) reclaims storage without + allocating any. It is a good idea to use alloca(0) in + your main control loop, etc. to force garbage collection. +*/ +#ifndef lint +static char SCCSid[] = "@(#)alloca.c 1.1"; /* for the "what" utility */ +#endif + +#ifdef emacs +#include "config.h" +#ifdef static +/* actually, only want this if static is defined as "" + -- this is for usg, in which emacs must undefine static + in order to make unexec workable + */ +#ifndef STACK_DIRECTION +you +lose +-- must know STACK_DIRECTION at compile-time +#endif /* STACK_DIRECTION undefined */ +#endif /* static */ +#endif /* emacs */ + +#if __STDC__ +typedef void *pointer; /* generic pointer type */ +#else +typedef char *pointer; /* generic pointer type */ +#endif + +#define NULL 0 /* null pointer constant */ + +extern void free(); +extern pointer xmalloc(); + +/* + Define STACK_DIRECTION if you know the direction of stack + growth for your system; otherwise it will be automatically + deduced at run-time. + + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown +*/ + +#ifndef STACK_DIRECTION +#define STACK_DIRECTION 0 /* direction unknown */ +#endif + +#if STACK_DIRECTION != 0 + +#define STACK_DIR STACK_DIRECTION /* known at compile-time */ + +#else /* STACK_DIRECTION == 0; need run-time code */ + +static int stack_dir; /* 1 or -1 once known */ +#define STACK_DIR stack_dir + +static void +find_stack_direction (/* void */) +{ + static char *addr = NULL; /* address of first + `dummy', once known */ + auto char dummy; /* to get stack address */ + + if (addr == NULL) + { /* initial entry */ + addr = &dummy; + + find_stack_direction (); /* recurse once */ + } + else /* second entry */ + if (&dummy > addr) + stack_dir = 1; /* stack grew upward */ + else + stack_dir = -1; /* stack grew downward */ +} + +#endif /* STACK_DIRECTION == 0 */ + +/* + An "alloca header" is used to: + (a) chain together all alloca()ed blocks; + (b) keep track of stack depth. + + It is very important that sizeof(header) agree with malloc() + alignment chunk size. The following default should work okay. +*/ + +#ifndef ALIGN_SIZE +#define ALIGN_SIZE sizeof(double) +#endif + +typedef union hdr +{ + char align[ALIGN_SIZE]; /* to force sizeof(header) */ + struct + { + union hdr *next; /* for chaining headers */ + char *deep; /* for stack depth measure */ + } h; +} header; + +/* + alloca( size ) returns a pointer to at least `size' bytes of + storage which will be automatically reclaimed upon exit from + the procedure that called alloca(). Originally, this space + was supposed to be taken from the current stack frame of the + caller, but that method cannot be made to work for some + implementations of C, for example under Gould's UTX/32. +*/ + +static header *last_alloca_header = NULL; /* -> last alloca header */ + +pointer +alloca (size) /* returns pointer to storage */ + unsigned size; /* # bytes to allocate */ +{ + auto char probe; /* probes stack depth: */ + register char *depth = &probe; + +#if STACK_DIRECTION == 0 + if (STACK_DIR == 0) /* unknown growth direction */ + find_stack_direction (); +#endif + + /* Reclaim garbage, defined as all alloca()ed storage that + was allocated from deeper in the stack than currently. */ + + { + register header *hp; /* traverses linked list */ + + for (hp = last_alloca_header; hp != NULL;) + if (STACK_DIR > 0 && hp->h.deep > depth + || STACK_DIR < 0 && hp->h.deep < depth) + { + register header *np = hp->h.next; + + free ((pointer) hp); /* collect garbage */ + + hp = np; /* -> next header */ + } + else + break; /* rest are not deeper */ + + last_alloca_header = hp; /* -> last valid storage */ + } + + if (size == 0) + return NULL; /* no allocation required */ + + /* Allocate combined header + user data storage. */ + + { + register pointer new = xmalloc (sizeof (header) + size); + /* address of header */ + + ((header *)new)->h.next = last_alloca_header; + ((header *)new)->h.deep = depth; + + last_alloca_header = (header *)new; + + /* User storage begins just after header. */ + + return (pointer)((char *)new + sizeof(header)); + } +} + diff --git a/gnu/usr.bin/cvs/lib/argmatch.c b/gnu/usr.bin/cvs/lib/argmatch.c new file mode 100644 index 0000000..3f765fe --- /dev/null +++ b/gnu/usr.bin/cvs/lib/argmatch.c @@ -0,0 +1,83 @@ +/* argmatch.c -- find a match for a string in an array + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Written by David MacKenzie <djm@ai.mit.edu> */ + +#include <stdio.h> +#ifdef STDC_HEADERS +#include <string.h> +#endif + +extern char *program_name; + +/* If ARG is an unambiguous match for an element of the + null-terminated array OPTLIST, return the index in OPTLIST + of the matched element, else -1 if it does not match any element + or -2 if it is ambiguous (is a prefix of more than one element). */ + +int +argmatch (arg, optlist) + char *arg; + char **optlist; +{ + int i; /* Temporary index in OPTLIST. */ + int arglen; /* Length of ARG. */ + int matchind = -1; /* Index of first nonexact match. */ + int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ + + arglen = strlen (arg); + + /* Test all elements for either exact match or abbreviated matches. */ + for (i = 0; optlist[i]; i++) + { + if (!strncmp (optlist[i], arg, arglen)) + { + if (strlen (optlist[i]) == arglen) + /* Exact match found. */ + return i; + else if (matchind == -1) + /* First nonexact match found. */ + matchind = i; + else + /* Second nonexact match found. */ + ambiguous = 1; + } + } + if (ambiguous) + return -2; + else + return matchind; +} + +/* Error reporting for argmatch. + KIND is a description of the type of entity that was being matched. + VALUE is the invalid value that was given. + PROBLEM is the return value from argmatch. */ + +void +invalid_arg (kind, value, problem) + char *kind; + char *value; + int problem; +{ + fprintf (stderr, "%s: ", program_name); + if (problem == -1) + fprintf (stderr, "invalid"); + else /* Assume -2. */ + fprintf (stderr, "ambiguous"); + fprintf (stderr, " %s `%s'\n", kind, value); +} diff --git a/gnu/usr.bin/cvs/lib/dup2.c b/gnu/usr.bin/cvs/lib/dup2.c new file mode 100644 index 0000000..0bd3aca --- /dev/null +++ b/gnu/usr.bin/cvs/lib/dup2.c @@ -0,0 +1,36 @@ +/* + dup2 -- 7th Edition UNIX system call emulation for UNIX System V + + last edit: 11-Feb-1987 D A Gwyn +*/ + +#include <errno.h> +#include <fcntl.h> + +extern int close(), fcntl(); + +int +dup2( oldfd, newfd ) + int oldfd; /* already-open file descriptor */ + int newfd; /* desired duplicate descriptor */ +{ + register int ret; /* for fcntl() return value */ + register int save; /* for saving entry errno */ + + if ( oldfd == newfd ) + return oldfd; /* be careful not to close() */ + + save = errno; /* save entry errno */ + (void) close( newfd ); /* in case newfd is open */ + /* (may have just clobbered the original errno value) */ + + ret = fcntl( oldfd, F_DUPFD, newfd ); /* dupe it */ + + if ( ret >= 0 ) + errno = save; /* restore entry errno */ + else /* fcntl() returned error */ + if ( errno == EINVAL ) + errno = EBADF; /* we think of everything */ + + return ret; /* return file descriptor */ +} diff --git a/gnu/usr.bin/cvs/lib/error.c b/gnu/usr.bin/cvs/lib/error.c new file mode 100644 index 0000000..fadb1c5 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/error.c @@ -0,0 +1,193 @@ +/* error.c -- error handler for noninteractive utilities + Copyright (C) 1990-1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* David MacKenzie */ +/* Brian Berliner added support for CVS */ + +#ifndef lint +static char rcsid[] = "@(#)error.c 1.9 92/03/31"; +#endif /* not lint */ + +#include <stdio.h> + +/* turn on CVS support by default, since this is the CVS distribution */ +#define CVS_SUPPORT + +#ifdef CVS_SUPPORT +#if __STDC__ +void Lock_Cleanup(void); +#else +void Lock_Cleanup(); +#endif /* __STDC__ */ +#endif /* CVS_SUPPORT */ + +#ifndef VPRINTF_MISSING + +#if __STDC__ +#include <stdarg.h> +#define VA_START(args, lastarg) va_start(args, lastarg) +#else +#include <varargs.h> +#define VA_START(args, lastarg) va_start(args) +#endif + +#else + +#ifndef DOPRNT_MISSING +#define va_alist args +#define va_dcl int args; +#else +#define va_alist a1, a2, a3, a4, a5, a6, a7, a8 +#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; +#endif + +#endif + +#ifdef STDC_HEADERS +#include <stdlib.h> +#include <string.h> +#else +#if __STDC__ +void exit(int status); +#else +void exit (); +#endif /* __STDC__ */ +#endif + +#ifdef STRERROR_MISSING +static char * +strerror (errnum) + int errnum; +{ + extern char *sys_errlist[]; + extern int sys_nerr; + + if (errnum > 0 && errnum < sys_nerr) + return sys_errlist[errnum]; + return "Unknown system error"; +} +#endif /* STRERROR_MISSING */ + +/* Print the program name and error message MESSAGE, which is a printf-style + format string with optional args. + If ERRNUM is nonzero, print its corresponding system error message. + Exit with status STATUS if it is nonzero. */ +/* VARARGS */ +void +#if !defined (VPRINTF_MISSING) && __STDC__ +error (int status, int errnum, char *message, ...) +#else +error (status, errnum, message, va_alist) + int status; + int errnum; + char *message; + va_dcl +#endif +{ + extern char *program_name; +#ifdef CVS_SUPPORT + extern char *command_name; +#endif +#ifndef VPRINTF_MISSING + va_list args; +#endif + +#ifdef CVS_SUPPORT + if (command_name && *command_name) + if (status) + fprintf (stderr, "%s [%s aborted]: ", program_name, command_name); + else + fprintf (stderr, "%s %s: ", program_name, command_name); + else + fprintf (stderr, "%s: ", program_name); +#else + fprintf (stderr, "%s: ", program_name); +#endif +#ifndef VPRINTF_MISSING + VA_START (args, message); + vfprintf (stderr, message, args); + va_end (args); +#else +#ifndef DOPRNT_MISSING + _doprnt (message, &args, stderr); +#else + fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8); +#endif +#endif + if (errnum) + fprintf (stderr, ": %s", strerror (errnum)); + putc ('\n', stderr); + fflush (stderr); + if (status) + { +#ifdef CVS_SUPPORT + Lock_Cleanup(); +#endif + exit (status); + } +} + +#ifdef CVS_SUPPORT + +/* Print the program name and error message MESSAGE, which is a printf-style + format string with optional args to the file specified by FP. + If ERRNUM is nonzero, print its corresponding system error message. + Exit with status STATUS if it is nonzero. */ +/* VARARGS */ +void +#if !defined (VPRINTF_MISSING) && __STDC__ +fperror (FILE *fp, int status, int errnum, char *message, ...) +#else +fperror (fp, status, errnum, message, va_alist) + FILE *fp; + int status; + int errnum; + char *message; + va_dcl +#endif +{ + extern char *program_name; +#ifndef VPRINTF_MISSING + va_list args; +#endif + + fprintf (fp, "%s: ", program_name); +#ifndef VPRINTF_MISSING + VA_START (args, message); + vfprintf (fp, message, args); + va_end (args); +#else +#ifndef DOPRNT_MISSING + _doprnt (message, &args, fp); +#else + fprintf (fp, message, a1, a2, a3, a4, a5, a6, a7, a8); +#endif +#endif + if (errnum) + fprintf (fp, ": %s", strerror (errnum)); + putc ('\n', fp); + fflush (fp); + if (status) + { +#ifdef CVS_SUPPORT + Lock_Cleanup(); +#endif + exit (status); + } +} + +#endif /* CVS_SUPPORT */ diff --git a/gnu/usr.bin/cvs/lib/fnmatch.c b/gnu/usr.bin/cvs/lib/fnmatch.c new file mode 100644 index 0000000..50fa94c --- /dev/null +++ b/gnu/usr.bin/cvs/lib/fnmatch.c @@ -0,0 +1,183 @@ +/* Copyright (C) 1992 Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +/* Modified slightly by Brian Berliner <berliner@sun.com> for CVS use */ + +/* IGNORE(@ */ +/* #include <ansidecl.h> */ +/* @) */ +#include <errno.h> +#include <fnmatch.h> + +#if !defined(__GNU_LIBRARY__) && !defined(STDC_HEADERS) +extern int errno; +#endif + +#if !__STDC__ +#define const +#endif + +/* Match STRING against the filename pattern PATTERN, returning zero if + it matches, nonzero if not. */ +int +#if __STDC__ +fnmatch (const char *pattern, const char *string, int flags) +#else +fnmatch (pattern, string, flags) + char *pattern; + char *string; + int flags; +#endif +{ + register const char *p = pattern, *n = string; + register char c; + + if ((flags & ~__FNM_FLAGS) != 0) + { + errno = EINVAL; + return -1; + } + + while ((c = *p++) != '\0') + { + switch (c) + { + case '?': + if (*n == '\0') + return FNM_NOMATCH; + else if ((flags & FNM_PATHNAME) && *n == '/') + return FNM_NOMATCH; + else if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + break; + + case '\\': + if (!(flags & FNM_NOESCAPE)) + c = *p++; + if (*n != c) + return FNM_NOMATCH; + break; + + case '*': + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + for (c = *p++; c == '?' || c == '*'; c = *p++, ++n) + if (((flags & FNM_PATHNAME) && *n == '/') || + (c == '?' && *n == '\0')) + return FNM_NOMATCH; + + if (c == '\0') + return 0; + + { + char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c; + for (--p; *n != '\0'; ++n) + if ((c == '[' || *n == c1) && + fnmatch(p, n, flags & ~FNM_PERIOD) == 0) + return 0; + return FNM_NOMATCH; + } + + case '[': + { + /* Nonzero if the sense of the character class is inverted. */ + register int not; + + if (*n == '\0') + return FNM_NOMATCH; + + if ((flags & FNM_PERIOD) && *n == '.' && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/'))) + return FNM_NOMATCH; + + not = (*p == '!' || *p == '^'); + if (not) + ++p; + + c = *p++; + for (;;) + { + register char cstart = c, cend = c; + + if (!(flags & FNM_NOESCAPE) && c == '\\') + cstart = cend = *p++; + + if (c == '\0') + /* [ (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + + if ((flags & FNM_PATHNAME) && c == '/') + /* [/] can never match. */ + return FNM_NOMATCH; + + if (c == '-' && *p != ']') + { + cend = *p++; + if (!(flags & FNM_NOESCAPE) && cend == '\\') + cend = *p++; + if (cend == '\0') + return FNM_NOMATCH; + c = *p++; + } + + if (*n >= cstart && *n <= cend) + goto matched; + + if (c == ']') + break; + } + if (!not) + return FNM_NOMATCH; + break; + + matched:; + /* Skip the rest of the [...] that already matched. */ + while (c != ']') + { + if (c == '\0') + /* [... (unterminated) loses. */ + return FNM_NOMATCH; + + c = *p++; + if (!(flags & FNM_NOESCAPE) && c == '\\') + /* 1003.2d11 is unclear if this is right. %%% */ + ++p; + } + if (not) + return FNM_NOMATCH; + } + break; + + default: + if (c != *n) + return FNM_NOMATCH; + } + + ++n; + } + + if (*n == '\0') + return 0; + + return FNM_NOMATCH; +} diff --git a/gnu/usr.bin/cvs/lib/fnmatch.h b/gnu/usr.bin/cvs/lib/fnmatch.h new file mode 100644 index 0000000..a1e4f87 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/fnmatch.h @@ -0,0 +1,45 @@ +/* Copyright (C) 1992 Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +#ifndef _FNMATCH_H + +#define _FNMATCH_H 1 + +/* Bits set in the FLAGS argument to `fnmatch'. */ +#undef FNM_PATHNAME +#define FNM_PATHNAME (1 << 0)/* No wildcard can ever match `/'. */ +#undef FNM_NOESCAPE +#define FNM_NOESCAPE (1 << 1)/* Backslashes don't quote special chars. */ +#undef FNM_PERIOD +#define FNM_PERIOD (1 << 2)/* Leading `.' is matched only explicitly. */ +#undef __FNM_FLAGS +#define __FNM_FLAGS (FNM_PATHNAME|FNM_NOESCAPE|FNM_PERIOD) + +/* Value returned by `fnmatch' if STRING does not match PATTERN. */ +#undef FNM_NOMATCH +#define FNM_NOMATCH 1 + +/* Match STRING against the filename pattern PATTERN, + returning zero if it matches, FNM_NOMATCH if not. */ +#if __STDC__ +extern int fnmatch (const char *pattern, const char *string, int flags); +#else +extern int fnmatch (); +#endif + +#endif /* fnmatch.h */ diff --git a/gnu/usr.bin/cvs/lib/ftruncate.c b/gnu/usr.bin/cvs/lib/ftruncate.c new file mode 100644 index 0000000..17d263d --- /dev/null +++ b/gnu/usr.bin/cvs/lib/ftruncate.c @@ -0,0 +1,72 @@ +/* ftruncate emulations that work on some System V's. + This file is in the public domain. */ + +#include <sys/types.h> +#include <fcntl.h> + +#ifdef F_CHSIZE +int +ftruncate (fd, length) + int fd; + off_t length; +{ + return fcntl (fd, F_CHSIZE, length); +} +#else +#ifdef F_FREESP +/* The following function was written by + kucharsk@Solbourne.com (William Kucharski) */ + +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> + +int +ftruncate (fd, length) + int fd; + off_t length; +{ + struct flock fl; + struct stat filebuf; + + if (fstat (fd, &filebuf) < 0) + return -1; + + if (filebuf.st_size < length) + { + /* Extend file length. */ + if (lseek (fd, (length - 1), SEEK_SET) < 0) + return -1; + + /* Write a "0" byte. */ + if (write (fd, "", 1) != 1) + return -1; + } + else + { + /* Truncate length. */ + fl.l_whence = 0; + fl.l_len = 0; + fl.l_start = length; + fl.l_type = F_WRLCK; /* Write lock on file space. */ + + /* This relies on the UNDOCUMENTED F_FREESP argument to + fcntl, which truncates the file so that it ends at the + position indicated by fl.l_start. + Will minor miracles never cease? */ + if (fcntl (fd, F_FREESP, &fl) < 0) + return -1; + } + + return 0; +} +#else +int +ftruncate (fd, length) + int fd; + off_t length; +{ + return chsize (fd, length); +} +#endif +#endif diff --git a/gnu/usr.bin/cvs/lib/getdate.y b/gnu/usr.bin/cvs/lib/getdate.y new file mode 100644 index 0000000..d010cb6 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/getdate.y @@ -0,0 +1,889 @@ +%{ +/* 1.8 +** @(#)getdate.y 1.8 92/03/03 +** +** Originally written by Steven M. Bellovin <smb@research.att.com> while +** at the University of North Carolina at Chapel Hill. Later tweaked by +** a couple of people on Usenet. Completely overhauled by Rich $alz +** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990; +** send any email to Rich. +** +** This grammar has eight shift/reduce conflicts. +** +** This code is in the public domain and has no copyright. +*/ +/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */ +/* SUPPRESS 288 on yyerrlab *//* Label unused */ + +#include "system.h" +#include <ctype.h> + +#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) +#ifdef __GNUC__ +#undef alloca /* might get redefined below */ +#endif +#endif + +extern struct tm *localtime(); + +#define yyparse getdate_yyparse +#define yylex getdate_yylex +#define yyerror getdate_yyerror + +#if !defined(lint) && !defined(SABER) +static char RCS[] = "@(#)getdate.y 1.8 92/03/03"; +#endif /* !defined(lint) && !defined(SABER) */ + + +#define EPOCH 1970 +#define HOUR(x) ((time_t)(x) * 60) +#define SECSPERDAY (24L * 60L * 60L) + + +/* +** An entry in the lexical lookup table. +*/ +typedef struct _TABLE { + char *name; + int type; + time_t value; +} TABLE; + + +/* +** Daylight-savings mode: on, off, or not yet known. +*/ +typedef enum _DSTMODE { + DSTon, DSToff, DSTmaybe +} DSTMODE; + +/* +** Meridian: am, pm, or 24-hour style. +*/ +typedef enum _MERIDIAN { + MERam, MERpm, MER24 +} MERIDIAN; + + +/* +** Global variables. We could get rid of most of these by using a good +** union as the yacc stack. (This routine was originally written before +** yacc had the %union construct.) Maybe someday; right now we only use +** the %union very rarely. +*/ +static char *yyInput; +static DSTMODE yyDSTmode; +static time_t yyDayOrdinal; +static time_t yyDayNumber; +static int yyHaveDate; +static int yyHaveDay; +static int yyHaveRel; +static int yyHaveTime; +static int yyHaveZone; +static time_t yyTimezone; +static time_t yyDay; +static time_t yyHour; +static time_t yyMinutes; +static time_t yyMonth; +static time_t yySeconds; +static time_t yyYear; +static MERIDIAN yyMeridian; +static time_t yyRelMonth; +static time_t yyRelSeconds; + +%} + +%union { + time_t Number; + enum _MERIDIAN Meridian; +} + +%token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT +%token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST + +%type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT +%type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE +%type <Meridian> tMERIDIAN o_merid + +%% + +spec : /* NULL */ + | spec item + ; + +item : time { + yyHaveTime++; + } + | zone { + yyHaveZone++; + } + | date { + yyHaveDate++; + } + | day { + yyHaveDay++; + } + | rel { + yyHaveRel++; + } + | number + ; + +time : tUNUMBER tMERIDIAN { + yyHour = $1; + yyMinutes = 0; + yySeconds = 0; + yyMeridian = $2; + } + | tUNUMBER ':' tUNUMBER o_merid { + yyHour = $1; + yyMinutes = $3; + yySeconds = 0; + yyMeridian = $4; + } + | tUNUMBER ':' tUNUMBER tSNUMBER { + yyHour = $1; + yyMinutes = $3; + yyMeridian = MER24; + yyDSTmode = DSToff; + yyTimezone = - ($4 % 100 + ($4 / 100) * 60); + } + | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid { + yyHour = $1; + yyMinutes = $3; + yySeconds = $5; + yyMeridian = $6; + } + | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER { + yyHour = $1; + yyMinutes = $3; + yySeconds = $5; + yyMeridian = MER24; + yyDSTmode = DSToff; + yyTimezone = - ($6 % 100 + ($6 / 100) * 60); + } + ; + +zone : tZONE { + yyTimezone = $1; + yyDSTmode = DSToff; + } + | tDAYZONE { + yyTimezone = $1; + yyDSTmode = DSTon; + } + | + tZONE tDST { + yyTimezone = $1; + yyDSTmode = DSTon; + } + ; + +day : tDAY { + yyDayOrdinal = 1; + yyDayNumber = $1; + } + | tDAY ',' { + yyDayOrdinal = 1; + yyDayNumber = $1; + } + | tUNUMBER tDAY { + yyDayOrdinal = $1; + yyDayNumber = $2; + } + ; + +date : tUNUMBER '/' tUNUMBER { + yyMonth = $1; + yyDay = $3; + } + | tUNUMBER '/' tUNUMBER '/' tUNUMBER { + yyMonth = $1; + yyDay = $3; + yyYear = $5; + } + | tMONTH tUNUMBER { + yyMonth = $1; + yyDay = $2; + } + | tMONTH tUNUMBER ',' tUNUMBER { + yyMonth = $1; + yyDay = $2; + yyYear = $4; + } + | tUNUMBER tMONTH { + yyMonth = $2; + yyDay = $1; + } + | tUNUMBER tMONTH tUNUMBER { + yyMonth = $2; + yyDay = $1; + yyYear = $3; + } + ; + +rel : relunit tAGO { + yyRelSeconds = -yyRelSeconds; + yyRelMonth = -yyRelMonth; + } + | relunit + ; + +relunit : tUNUMBER tMINUTE_UNIT { + yyRelSeconds += $1 * $2 * 60L; + } + | tSNUMBER tMINUTE_UNIT { + yyRelSeconds += $1 * $2 * 60L; + } + | tMINUTE_UNIT { + yyRelSeconds += $1 * 60L; + } + | tSNUMBER tSEC_UNIT { + yyRelSeconds += $1; + } + | tUNUMBER tSEC_UNIT { + yyRelSeconds += $1; + } + | tSEC_UNIT { + yyRelSeconds++; + } + | tSNUMBER tMONTH_UNIT { + yyRelMonth += $1 * $2; + } + | tUNUMBER tMONTH_UNIT { + yyRelMonth += $1 * $2; + } + | tMONTH_UNIT { + yyRelMonth += $1; + } + ; + +number : tUNUMBER { + if (yyHaveTime && yyHaveDate && !yyHaveRel) + yyYear = $1; + else { + if($1>10000) { + time_t date_part; + + date_part= $1/10000; + yyHaveDate++; + yyDay= (date_part)%100; + yyMonth= (date_part/100)%100; + yyYear = date_part/10000; + } + yyHaveTime++; + if ($1 < 100) { + yyHour = $1; + yyMinutes = 0; + } + else { + yyHour = $1 / 100; + yyMinutes = $1 % 100; + } + yySeconds = 0; + yyMeridian = MER24; + } + } + ; + +o_merid : /* NULL */ { + $$ = MER24; + } + | tMERIDIAN { + $$ = $1; + } + ; + +%% + +/* Month and day table. */ +static TABLE MonthDayTable[] = { + { "january", tMONTH, 1 }, + { "february", tMONTH, 2 }, + { "march", tMONTH, 3 }, + { "april", tMONTH, 4 }, + { "may", tMONTH, 5 }, + { "june", tMONTH, 6 }, + { "july", tMONTH, 7 }, + { "august", tMONTH, 8 }, + { "september", tMONTH, 9 }, + { "sept", tMONTH, 9 }, + { "october", tMONTH, 10 }, + { "november", tMONTH, 11 }, + { "december", tMONTH, 12 }, + { "sunday", tDAY, 0 }, + { "monday", tDAY, 1 }, + { "tuesday", tDAY, 2 }, + { "tues", tDAY, 2 }, + { "wednesday", tDAY, 3 }, + { "wednes", tDAY, 3 }, + { "thursday", tDAY, 4 }, + { "thur", tDAY, 4 }, + { "thurs", tDAY, 4 }, + { "friday", tDAY, 5 }, + { "saturday", tDAY, 6 }, + { NULL } +}; + +/* Time units table. */ +static TABLE UnitsTable[] = { + { "year", tMONTH_UNIT, 12 }, + { "month", tMONTH_UNIT, 1 }, + { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 }, + { "week", tMINUTE_UNIT, 7 * 24 * 60 }, + { "day", tMINUTE_UNIT, 1 * 24 * 60 }, + { "hour", tMINUTE_UNIT, 60 }, + { "minute", tMINUTE_UNIT, 1 }, + { "min", tMINUTE_UNIT, 1 }, + { "second", tSEC_UNIT, 1 }, + { "sec", tSEC_UNIT, 1 }, + { NULL } +}; + +/* Assorted relative-time words. */ +static TABLE OtherTable[] = { + { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 }, + { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 }, + { "today", tMINUTE_UNIT, 0 }, + { "now", tMINUTE_UNIT, 0 }, + { "last", tUNUMBER, -1 }, + { "this", tMINUTE_UNIT, 0 }, + { "next", tUNUMBER, 2 }, + { "first", tUNUMBER, 1 }, +/* { "second", tUNUMBER, 2 }, */ + { "third", tUNUMBER, 3 }, + { "fourth", tUNUMBER, 4 }, + { "fifth", tUNUMBER, 5 }, + { "sixth", tUNUMBER, 6 }, + { "seventh", tUNUMBER, 7 }, + { "eighth", tUNUMBER, 8 }, + { "ninth", tUNUMBER, 9 }, + { "tenth", tUNUMBER, 10 }, + { "eleventh", tUNUMBER, 11 }, + { "twelfth", tUNUMBER, 12 }, + { "ago", tAGO, 1 }, + { NULL } +}; + +/* The timezone table. */ +/* Some of these are commented out because a time_t can't store a float. */ +static TABLE TimezoneTable[] = { + { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ + { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ + { "utc", tZONE, HOUR( 0) }, + { "wet", tZONE, HOUR( 0) }, /* Western European */ + { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ + { "wat", tZONE, HOUR( 1) }, /* West Africa */ + { "at", tZONE, HOUR( 2) }, /* Azores */ +#if 0 + /* For completeness. BST is also British Summer, and GST is + * also Guam Standard. */ + { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */ + { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */ +#endif +#if 0 + { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */ + { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */ + { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */ +#endif + { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ + { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ + { "est", tZONE, HOUR( 5) }, /* Eastern Standard */ + { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ + { "cst", tZONE, HOUR( 6) }, /* Central Standard */ + { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ + { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ + { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ + { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ + { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ + { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ + { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ + { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ + { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ + { "cat", tZONE, HOUR(10) }, /* Central Alaska */ + { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ + { "nt", tZONE, HOUR(11) }, /* Nome */ + { "idlw", tZONE, HOUR(12) }, /* International Date Line West */ + { "cet", tZONE, -HOUR(1) }, /* Central European */ + { "met", tZONE, -HOUR(1) }, /* Middle European */ + { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */ + { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */ + { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */ + { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */ + { "fwt", tZONE, -HOUR(1) }, /* French Winter */ + { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */ + { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */ + { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */ +#if 0 + { "it", tZONE, -HOUR(3.5) },/* Iran */ +#endif + { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */ + { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */ +#if 0 + { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */ +#endif + { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */ +#if 0 + /* For completeness. NST is also Newfoundland Stanard, and SST is + * also Swedish Summer. */ + { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */ + { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */ +#endif /* 0 */ + { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */ + { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */ +#if 0 + { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */ +#endif + { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */ + { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */ +#if 0 + { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */ + { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */ +#endif + { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ + { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ + { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */ + { "nzt", tZONE, -HOUR(12) }, /* New Zealand */ + { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ + { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ + { "idle", tZONE, -HOUR(12) }, /* International Date Line East */ + { NULL } +}; + +/* Military timezone table. */ +static TABLE MilitaryTable[] = { + { "a", tZONE, HOUR( 1) }, + { "b", tZONE, HOUR( 2) }, + { "c", tZONE, HOUR( 3) }, + { "d", tZONE, HOUR( 4) }, + { "e", tZONE, HOUR( 5) }, + { "f", tZONE, HOUR( 6) }, + { "g", tZONE, HOUR( 7) }, + { "h", tZONE, HOUR( 8) }, + { "i", tZONE, HOUR( 9) }, + { "k", tZONE, HOUR( 10) }, + { "l", tZONE, HOUR( 11) }, + { "m", tZONE, HOUR( 12) }, + { "n", tZONE, HOUR(- 1) }, + { "o", tZONE, HOUR(- 2) }, + { "p", tZONE, HOUR(- 3) }, + { "q", tZONE, HOUR(- 4) }, + { "r", tZONE, HOUR(- 5) }, + { "s", tZONE, HOUR(- 6) }, + { "t", tZONE, HOUR(- 7) }, + { "u", tZONE, HOUR(- 8) }, + { "v", tZONE, HOUR(- 9) }, + { "w", tZONE, HOUR(-10) }, + { "x", tZONE, HOUR(-11) }, + { "y", tZONE, HOUR(-12) }, + { "z", tZONE, HOUR( 0) }, + { NULL } +}; + + + + +/* ARGSUSED */ +int +yyerror(s) + char *s; +{ + return 0; +} + + +static time_t +ToSeconds(Hours, Minutes, Seconds, Meridian) + time_t Hours; + time_t Minutes; + time_t Seconds; + MERIDIAN Meridian; +{ + if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) + return -1; + switch (Meridian) { + case MER24: + if (Hours < 0 || Hours > 23) + return -1; + return (Hours * 60L + Minutes) * 60L + Seconds; + case MERam: + if (Hours < 1 || Hours > 12) + return -1; + return (Hours * 60L + Minutes) * 60L + Seconds; + case MERpm: + if (Hours < 1 || Hours > 12) + return -1; + return ((Hours + 12) * 60L + Minutes) * 60L + Seconds; + } + /* NOTREACHED */ +} + + +static time_t +Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode) + time_t Month; + time_t Day; + time_t Year; + time_t Hours; + time_t Minutes; + time_t Seconds; + MERIDIAN Meridian; + DSTMODE DSTmode; +{ + static int DaysInMonth[12] = { + 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + time_t tod; + time_t Julian; + int i; + + if (Year < 0) + Year = -Year; + if (Year < 100) + Year += 1900; + DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) + ? 29 : 28; + if (Year < EPOCH || Year > 1999 + || Month < 1 || Month > 12 + /* Lint fluff: "conversion from long may lose accuracy" */ + || Day < 1 || Day > DaysInMonth[(int)--Month]) + return -1; + + for (Julian = Day - 1, i = 0; i < Month; i++) + Julian += DaysInMonth[i]; + for (i = EPOCH; i < Year; i++) + Julian += 365 + (i % 4 == 0); + Julian *= SECSPERDAY; + Julian += yyTimezone * 60L; + if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0) + return -1; + Julian += tod; + if (DSTmode == DSTon + || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst)) + Julian -= 60 * 60; + return Julian; +} + + +static time_t +DSTcorrect(Start, Future) + time_t Start; + time_t Future; +{ + time_t StartDay; + time_t FutureDay; + + StartDay = (localtime(&Start)->tm_hour + 1) % 24; + FutureDay = (localtime(&Future)->tm_hour + 1) % 24; + return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; +} + + +static time_t +RelativeDate(Start, DayOrdinal, DayNumber) + time_t Start; + time_t DayOrdinal; + time_t DayNumber; +{ + struct tm *tm; + time_t now; + + now = Start; + tm = localtime(&now); + now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7); + now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); + return DSTcorrect(Start, now); +} + + +static time_t +RelativeMonth(Start, RelMonth) + time_t Start; + time_t RelMonth; +{ + struct tm *tm; + time_t Month; + time_t Year; + + if (RelMonth == 0) + return 0; + tm = localtime(&Start); + Month = 12 * tm->tm_year + tm->tm_mon + RelMonth; + Year = Month / 12; + Month = Month % 12 + 1; + return DSTcorrect(Start, + Convert(Month, (time_t)tm->tm_mday, Year, + (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, + MER24, DSTmaybe)); +} + + +static int +LookupWord(buff) + char *buff; +{ + register char *p; + register char *q; + register TABLE *tp; + int i; + int abbrev; + + /* Make it lowercase. */ + for (p = buff; *p; p++) + if (isupper(*p)) + *p = tolower(*p); + + if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) { + yylval.Meridian = MERam; + return tMERIDIAN; + } + if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) { + yylval.Meridian = MERpm; + return tMERIDIAN; + } + + /* See if we have an abbreviation for a month. */ + if (strlen(buff) == 3) + abbrev = 1; + else if (strlen(buff) == 4 && buff[3] == '.') { + abbrev = 1; + buff[3] = '\0'; + } + else + abbrev = 0; + + for (tp = MonthDayTable; tp->name; tp++) { + if (abbrev) { + if (strncmp(buff, tp->name, 3) == 0) { + yylval.Number = tp->value; + return tp->type; + } + } + else if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + } + + for (tp = TimezoneTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + if (strcmp(buff, "dst") == 0) + return tDST; + + for (tp = UnitsTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + /* Strip off any plural and try the units table again. */ + i = strlen(buff) - 1; + if (buff[i] == 's') { + buff[i] = '\0'; + for (tp = UnitsTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + buff[i] = 's'; /* Put back for "this" in OtherTable. */ + } + + for (tp = OtherTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + /* Military timezones. */ + if (buff[1] == '\0' && isalpha(*buff)) { + for (tp = MilitaryTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + } + + /* Drop out any periods and try the timezone table again. */ + for (i = 0, p = q = buff; *q; q++) + if (*q != '.') + *p++ = *q; + else + i++; + *p = '\0'; + if (i) + for (tp = TimezoneTable; tp->name; tp++) + if (strcmp(buff, tp->name) == 0) { + yylval.Number = tp->value; + return tp->type; + } + + return tID; +} + + +int +yylex() +{ + register char c; + register char *p; + char buff[20]; + int Count; + int sign; + + for ( ; ; ) { + while (isspace(*yyInput)) + yyInput++; + + if (isdigit(c = *yyInput) || c == '-' || c == '+') { + if (c == '-' || c == '+') { + sign = c == '-' ? -1 : 1; + if (!isdigit(*++yyInput)) + /* skip the '-' sign */ + continue; + } + else + sign = 0; + for (yylval.Number = 0; isdigit(c = *yyInput++); ) + yylval.Number = 10 * yylval.Number + c - '0'; + yyInput--; + if (sign < 0) + yylval.Number = -yylval.Number; + return sign ? tSNUMBER : tUNUMBER; + } + if (isalpha(c)) { + for (p = buff; isalpha(c = *yyInput++) || c == '.'; ) + if (p < &buff[sizeof buff - 1]) + *p++ = c; + *p = '\0'; + yyInput--; + return LookupWord(buff); + } + if (c != '(') + return *yyInput++; + Count = 0; + do { + c = *yyInput++; + if (c == '\0') + return c; + if (c == '(') + Count++; + else if (c == ')') + Count--; + } while (Count > 0); + } +} + + +time_t +get_date(p, now) + char *p; + struct timeb *now; +{ + struct tm *tm; + struct timeb ftz; + time_t Start; + time_t tod; + + yyInput = p; + if (now == NULL) { + now = &ftz; +#if defined(FTIME_MISSING) + (void)time(&ftz.time); + /* Set the timezone global. */ + tzset(); +#if defined(HAVE_TIMEZONE) + tm = localtime(&ftz.time); + ftz.timezone = tm->tm_gmtoff / 60; +#else +#if defined(timezone) + ftz.tzone = (int) timezone / 60; +#else + ftz.timezone = (int) timezone / 60; +#endif /* defined(timezone) */ +#endif /* defined(HAVE_TIMEZONE) */ +#else + (void)ftime(&ftz); +#endif /* defined(FTIME_MISSING) */ + } + + tm = localtime(&now->time); + yyYear = tm->tm_year; + yyMonth = tm->tm_mon + 1; + yyDay = tm->tm_mday; +#if defined(timezone) + yyTimezone = now->tzone; +#else + yyTimezone = now->timezone; +#endif /* defined(timezone) */ + yyDSTmode = DSTmaybe; + yyHour = 0; + yyMinutes = 0; + yySeconds = 0; + yyMeridian = MER24; + yyRelSeconds = 0; + yyRelMonth = 0; + yyHaveDate = 0; + yyHaveDay = 0; + yyHaveRel = 0; + yyHaveTime = 0; + yyHaveZone = 0; + + if (yyparse() + || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1) + return -1; + + if (yyHaveDate || yyHaveTime || yyHaveDay) { + Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds, + yyMeridian, yyDSTmode); + if (Start < 0) + return -1; + } + else { + Start = now->time; + if (!yyHaveRel) + Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; + } + + Start += yyRelSeconds; + Start += RelativeMonth(Start, yyRelMonth); + + if (yyHaveDay && !yyHaveDate) { + tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber); + Start += tod; + } + + /* Have to do *something* with a legitimate -1 so it's distinguishable + * from the error return value. (Alternately could set errno on error.) */ + return Start == -1 ? 0 : Start; +} + + +#if defined(TEST) + +/* ARGSUSED */ +main(ac, av) + int ac; + char *av[]; +{ + char buff[128]; + time_t d; + + (void)printf("Enter date, or blank line to exit.\n\t> "); + (void)fflush(stdout); + while (gets(buff) && buff[0]) { + d = get_date(buff, (struct timeb *)NULL); + if (d == -1) + (void)printf("Bad format - couldn't convert.\n"); + else + (void)printf("%s", ctime(&d)); + (void)printf("\t> "); + (void)fflush(stdout); + } + exit(0); + /* NOTREACHED */ +} +#endif /* defined(TEST) */ diff --git a/gnu/usr.bin/cvs/lib/getopt.c b/gnu/usr.bin/cvs/lib/getopt.c new file mode 100644 index 0000000..c322fc2 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/getopt.c @@ -0,0 +1,604 @@ +/* Getopt for GNU. + Copyright (C) 1987-1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#if !__STDC__ +#define const +#endif + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of `argv' so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable _POSIX_OPTION_ORDER disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#ifndef lint +static char rcsid[] = "@(#)getopt.c 1.7 92/03/31"; +#endif + +#include <stdio.h> + +#if defined(STDC_HEADERS) || defined(__GNU_LIBRARY__) +#include <stdlib.h> +#else /* STDC_HEADERS or __GNU_LIBRARY__ */ +char *getenv (); +char *malloc (); +#endif /* STDC_HEADERS or __GNU_LIBRARY__ */ + +/* AIX requires this to be the first thing in the file. */ +#ifdef __GNUC__ +#if !defined(bsdi) && !defined(__386BSD__) +#define alloca __builtin_alloca +#endif +#else /* not __GNUC__ */ +#ifdef sparc +#include <alloca.h> +#else +#ifdef _AIX + #pragma alloca +#else +char *alloca (); +#endif +#endif /* sparc */ +#endif /* not __GNUC__ */ + +#if defined(USG) || defined(STDC_HEADERS) || defined(__GNU_LIBRARY__) +#include <string.h> +#ifndef bcopy +#define bcopy(s, d, n) memcpy ((d), (s), (n)) +#endif +#ifndef index +#define index strchr +#endif +#else /* USG or STDC_HEADERS or __GNU_LIBRARY__ */ +#ifdef VMS +#include <string.h> +#else /* VMS */ +#include <strings.h> +#endif /* VMS */ +/* Declaring bcopy causes errors on systems whose declarations are different. + If the declaration is omitted, everything works fine. */ +#endif /* USG or STDC_HEADERS or __GNU_LIBRARY__ */ + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = 0; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + _POSIX_OPTION_ORDER is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIX_ME_HARDER, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Describe the long-named options requested by the application. + _GETOPT_LONG_OPTIONS is a vector of `struct option' terminated by an + element containing a name which is zero. + The field `has_arg' is 1 if the option takes an argument, + 2 if it takes an optional argument. */ + +struct option +{ + char *name; + int has_arg; + int *flag; + int val; +}; + +const struct option *_getopt_long_options; + +int _getopt_long_only = 0; + +/* Index in _GETOPT_LONG_OPTIONS of the long-named option actually found. + Only valid when a long-named option was found. */ + +int option_index; + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (argv) + char **argv; +{ + int nonopts_size = (last_nonopt - first_nonopt) * sizeof (char *); + char **temp = (char **) alloca (nonopts_size); + + /* Interchange the two blocks of data in ARGV. */ + + bcopy (&argv[first_nonopt], temp, nonopts_size); + bcopy (&argv[last_nonopt], &argv[first_nonopt], + (optind - last_nonopt) * sizeof (char *)); + bcopy (temp, &argv[first_nonopt + optind - last_nonopt], nonopts_size); + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `+' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + otherwise. */ + +int +gnu_getopt (argc, argv, optstring) + int argc; + char **argv; + const char *optstring; +{ + optarg = 0; + + /* Initialize the internal data when the first call is made. + Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + if (optind == 0) + { + first_nonopt = last_nonopt = optind = 1; + + nextchar = 0; + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (getenv ("POSIX_ME_HARDER") != 0) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + } + + if (nextchar == 0 || *nextchar == 0) + { + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange (argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Now skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc + && (argv[optind][0] != '-' + || argv[optind][1] == 0) + && (_getopt_long_options == 0 + || argv[optind][0] != '+' + || argv[optind][1] == 0)) + optind++; + last_nonopt = optind; + } + + /* Special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange (argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if ((argv[optind][0] != '-' || argv[optind][1] == 0) + && (_getopt_long_options == 0 + || argv[optind][0] != '+' || argv[optind][1] == 0)) + { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Start decoding its characters. */ + + nextchar = argv[optind] + 1; + } + + if (_getopt_long_options != 0 + && (argv[optind][0] == '+' + || (_getopt_long_only && argv[optind][0] == '-')) + ) + { + const struct option *p; + char *s = nextchar; + int exact = 0; + int ambig = 0; + const struct option *pfound = 0; + int indfound = 0; + + while (*s && *s != '=') + s++; + + /* Test all options for either exact match or abbreviated matches. */ + for (p = _getopt_long_options, option_index = 0; p->name; + p++, option_index++) + if (!strncmp (p->name, nextchar, s - nextchar)) + { + if (s - nextchar == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == 0) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + fprintf (stderr, "%s: option `%s' is ambiguous\n", + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + + if (pfound != 0) + { + option_index = indfound; + optind++; + if (*s) + { + if (pfound->has_arg > 0) + optarg = s + 1; + else + { + fprintf (stderr, + "%s: option `%c%s' doesn't allow an argument\n", + argv[0], argv[optind - 1][0], pfound->name); + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + fprintf (stderr, "%s: option `%s' requires an argument\n", + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return '?'; + } + } + nextchar += strlen (nextchar); + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + /* Can't find it as a long option. If this is getopt_long_only, + and the option starts with '-' and is a valid short + option, then interpret it as a short option. Otherwise it's + an error. */ + if (_getopt_long_only == 0 || argv[optind][0] == '+' || + index (optstring, *nextchar) == 0) + { + if (opterr != 0) + fprintf (stderr, "%s: unrecognized option `%c%s'\n", + argv[0], argv[optind][0], nextchar); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + } + + /* Look at and handle the next option-character. */ + + { + char c = *nextchar++; + char *temp = index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == 0) + optind++; + + if (temp == 0 || c == ':') + { + if (opterr != 0) + { + if (c < 040 || c >= 0177) + fprintf (stderr, "%s: unrecognized option, character code 0%o\n", + argv[0], c); + else + fprintf (stderr, "%s: unrecognized option `-%c'\n", + argv[0], c); + } + return '?'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != 0) + { + optarg = nextchar; + optind++; + } + else + optarg = 0; + nextchar = 0; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != 0) + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr != 0) + fprintf (stderr, "%s: option `-%c' requires an argument\n", + argv[0], c); + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = 0; + } + } + return c; + } +} + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = gnu_getopt (argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/gnu/usr.bin/cvs/lib/getopt.h b/gnu/usr.bin/cvs/lib/getopt.h new file mode 100644 index 0000000..5f902de --- /dev/null +++ b/gnu/usr.bin/cvs/lib/getopt.h @@ -0,0 +1,102 @@ +/* declarations for getopt + Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* @(#)getopt.h 1.6 92/03/31 */ + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Describe the long-named options requested by the application. + _GETOPT_LONG_OPTIONS is a vector of `struct option' terminated by an + element containing a name which is zero. + + The field `has_arg' is: + 0 if the option does not take an argument, + 1 if the option requires an argument, + 2 if the option takes an optional argument. + + If the field `flag' is nonzero, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ + char *name; + int has_arg; + int *flag; + int val; +}; + +#if __STDC__ +extern const struct option *_getopt_long_options; +#else +extern struct option *_getopt_long_options; +#endif + +/* If nonzero, '-' can introduce long-named options. + Set by getopt_long_only. */ + +extern int _getopt_long_only; + +/* The index in GETOPT_LONG_OPTIONS of the long-named option found. + Only valid when a long-named option has been found by the most + recent call to `getopt'. */ + +extern int option_index; + +#if __STDC__ +int gnu_getopt (int argc, char **argv, const char *shortopts); +int gnu_getopt_long (int argc, char **argv, const char *shortopts, + const struct option *longopts, int *longind); +int gnu_getopt_long_only (int argc, char **argv, const char *shortopts, + const struct option *longopts, int *longind); +#else +int gnu_getopt (); +int gnu_getopt_long (); +int gnu_getopt_long_only (); +#endif diff --git a/gnu/usr.bin/cvs/lib/getopt1.c b/gnu/usr.bin/cvs/lib/getopt1.c new file mode 100644 index 0000000..8606462 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/getopt1.c @@ -0,0 +1,166 @@ +/* Getopt for GNU. + Copyright (C) 1987-1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "getopt.h" + +#if !__STDC__ +#define const +#endif + +#if defined(STDC_HEADERS) || defined(__GNU_LIBRARY__) +#include <stdlib.h> +#else /* STDC_HEADERS or __GNU_LIBRARY__ */ +char *getenv (); +#endif /* STDC_HEADERS or __GNU_LIBRARY__ */ + +#if !defined (NULL) +#define NULL 0 +#endif + +int +gnu_getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char **argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + int val; + + /* For strict POSIX compatibility, we must turn off long options. */ + if (getenv ("POSIX_ME_HARDER") == 0) + _getopt_long_options = long_options; + val = gnu_getopt (argc, argv, options); + if (val == 0 && opt_index != NULL) + *opt_index = option_index; + return val; +} + +/* Like getopt_long, but '-' as well as '+' can indicate a long option. + If an option that starts with '-' doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +gnu_getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char **argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + int val; + + _getopt_long_options = long_options; + _getopt_long_only = 1; + val = gnu_getopt (argc, argv, options); + if (val == 0 && opt_index != NULL) + *opt_index = option_index; + return val; +} + + +#ifdef TEST + +#include <stdio.h> + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + char *name = '\0'; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == EOF) + break; + + switch (c) + { + case 0: + printf ("option %s", (long_options[option_index]).name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/gnu/usr.bin/cvs/lib/getwd.c b/gnu/usr.bin/cvs/lib/getwd.c new file mode 100644 index 0000000..854feaf --- /dev/null +++ b/gnu/usr.bin/cvs/lib/getwd.c @@ -0,0 +1,31 @@ +/* getwd.c -- get current working directory pathname + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Some systems which include both getwd() and getcwd() have an implementation + of getwd() which is much faster than getcwd(). As a result, we use the + system's getwd() if it is available */ + +#include "system.h" + +/* Get the current working directory into PATHNAME */ + +char * +getwd (pathname) + char *pathname; +{ + return (getcwd(pathname, PATH_MAX)); +} diff --git a/gnu/usr.bin/cvs/lib/hash.c b/gnu/usr.bin/cvs/lib/hash.c new file mode 100644 index 0000000..fb29497 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/hash.c @@ -0,0 +1,338 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Polk's hash list manager. So cool. + */ + +#include "cvs.h" + +#ifndef lint +static char rcsid[] = "@(#)hash.c 1.14 92/03/31"; +#endif + +/* global caches */ +static List *listcache = NULL; +static Node *nodecache = NULL; + +#if __STDC__ +static void freenode_mem (Node * p); +#else +static void freenode_mem (); +#endif /* __STDC__ */ + +/* hash function */ +static int +hashp (key) + char *key; +{ + register char *p; + register int n = 0; + + for (p = key; *p; p++) + n += *p; + + return (n % HASHSIZE); +} + +/* + * create a new list (or get an old one from the cache) + */ +List * +getlist () +{ + int i; + List *list; + Node *node; + + if (listcache != NULL) + { + /* get a list from the cache and clear it */ + list = listcache; + listcache = listcache->next; + list->next = (List *) NULL; + for (i = 0; i < HASHSIZE; i++) + list->hasharray[i] = (Node *) NULL; + } + else + { + /* make a new list from scratch */ + list = (List *) xmalloc (sizeof (List)); + bzero ((char *) list, sizeof (List)); + node = getnode (); + list->list = node; + node->type = HEADER; + node->next = node->prev = node; + } + return (list); +} + +/* + * free up a list + */ +void +dellist (listp) + List **listp; +{ + int i; + Node *p; + + if (*listp == (List *) NULL) + return; + + p = (*listp)->list; + + /* free each node in the list (except header) */ + while (p->next != p) + delnode (p->next); + + /* free any list-private data, without freeing the actual header */ + freenode_mem (p); + + /* free up the header nodes for hash lists (if any) */ + for (i = 0; i < HASHSIZE; i++) + { + if ((p = (*listp)->hasharray[i]) != (Node *) NULL) + { + /* put the nodes into the cache */ + p->type = UNKNOWN; + p->next = nodecache; + nodecache = p; + } + } + + /* put it on the cache */ + (*listp)->next = listcache; + listcache = *listp; + *listp = (List *) NULL; +} + +/* + * get a new list node + */ +Node * +getnode () +{ + Node *p; + + if (nodecache != (Node *) NULL) + { + /* get one from the cache */ + p = nodecache; + nodecache = p->next; + } + else + { + /* make a new one */ + p = (Node *) xmalloc (sizeof (Node)); + } + + /* always make it clean */ + bzero ((char *) p, sizeof (Node)); + p->type = UNKNOWN; + + return (p); +} + +/* + * remove a node from it's list (maybe hash list too) and free it + */ +void +delnode (p) + Node *p; +{ + if (p == (Node *) NULL) + return; + + /* take it out of the list */ + p->next->prev = p->prev; + p->prev->next = p->next; + + /* if it was hashed, remove it from there too */ + if (p->hashnext != (Node *) NULL) + { + p->hashnext->hashprev = p->hashprev; + p->hashprev->hashnext = p->hashnext; + } + + /* free up the storage */ + freenode (p); +} + +/* + * free up the storage associated with a node + */ +static void +freenode_mem (p) + Node *p; +{ + if (p->delproc != (void (*) ()) NULL) + p->delproc (p); /* call the specified delproc */ + else + { + if (p->data != NULL) /* otherwise free() it if necessary */ + free (p->data); + } + if (p->key != NULL) /* free the key if necessary */ + free (p->key); + + /* to be safe, re-initialize these */ + p->key = p->data = (char *) NULL; + p->delproc = (void (*) ()) NULL; +} + +/* + * free up the storage associated with a node and recycle it + */ +void +freenode (p) + Node *p; +{ + /* first free the memory */ + freenode_mem (p); + + /* then put it in the cache */ + p->type = UNKNOWN; + p->next = nodecache; + nodecache = p; +} + +/* + * insert item p at end of list "list" (maybe hash it too) if hashing and it + * already exists, return -1 and don't actually put it in the list + * + * return 0 on success + */ +int +addnode (list, p) + List *list; + Node *p; +{ + int hashval; + Node *q; + + if (p->key != NULL) /* hash it too? */ + { + hashval = hashp (p->key); + if (list->hasharray[hashval] == NULL) /* make a header for list? */ + { + q = getnode (); + q->type = HEADER; + list->hasharray[hashval] = q->hashnext = q->hashprev = q; + } + + /* put it into the hash list if it's not already there */ + for (q = list->hasharray[hashval]->hashnext; + q != list->hasharray[hashval]; q = q->hashnext) + { + if (strcmp (p->key, q->key) == 0) + return (-1); + } + q = list->hasharray[hashval]; + p->hashprev = q->hashprev; + p->hashnext = q; + p->hashprev->hashnext = p; + q->hashprev = p; + } + + /* put it into the regular list */ + p->prev = list->list->prev; + p->next = list->list; + list->list->prev->next = p; + list->list->prev = p; + + return (0); +} + +/* + * look up an entry in hash list table + */ +Node * +findnode (list, key) + List *list; + char *key; +{ + Node *head, *p; + + if (list == (List *) NULL) + return ((Node *) NULL); + + head = list->hasharray[hashp (key)]; + if (head == (Node *) NULL) + return ((Node *) NULL); + + for (p = head->hashnext; p != head; p = p->hashnext) + if (strcmp (p->key, key) == 0) + return (p); + return ((Node *) NULL); +} + +/* + * walk a list with a specific proc + */ +int +walklist (list, proc) + List *list; + int (*proc) (); +{ + Node *head, *p; + int err = 0; + + if (list == NULL) + return (0); + + head = list->list; + for (p = head->next; p != head; p = p->next) + err += proc (p); + return (err); +} + +/* + * sort the elements of a list (in place) + */ +void +sortlist (list, comp) + List *list; + int (*comp) (); +{ + Node *head, *remain, *p, *q; + + /* save the old first element of the list */ + head = list->list; + remain = head->next; + + /* make the header node into a null list of it's own */ + head->next = head->prev = head; + + /* while there are nodes remaining, do insert sort */ + while (remain != head) + { + /* take one from the list */ + p = remain; + remain = remain->next; + + /* traverse the sorted list looking for the place to insert it */ + for (q = head->next; q != head; q = q->next) + { + if (comp (p, q) < 0) + { + /* p comes before q */ + p->next = q; + p->prev = q->prev; + p->prev->next = p; + q->prev = p; + break; + } + } + if (q == head) + { + /* it belongs at the end of the list */ + p->next = head; + p->prev = head->prev; + p->prev->next = p; + head->prev = p; + } + } +} diff --git a/gnu/usr.bin/cvs/lib/hash.h b/gnu/usr.bin/cvs/lib/hash.h new file mode 100644 index 0000000..54f227e --- /dev/null +++ b/gnu/usr.bin/cvs/lib/hash.h @@ -0,0 +1,77 @@ +/* @(#)hash.h 1.18 92/03/31 */ + +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + */ + +/* + * The number of buckets for the hash table contained in each list. This + * should probably be prime. + */ +#define HASHSIZE 151 + +/* + * Types of nodes + */ +enum ntype +{ + UNKNOWN, HEADER, ENTRIES, FILES, LIST, RCSNODE, + RCSVERS, DIRS, UPDATE, LOCK, NDBMNODE +}; +typedef enum ntype Ntype; + +struct node +{ + Ntype type; + struct node *next; + struct node *prev; + struct node *hashnext; + struct node *hashprev; + char *key; + char *data; + void (*delproc) (); +}; +typedef struct node Node; + +struct list +{ + Node *list; + Node *hasharray[HASHSIZE]; + struct list *next; +}; +typedef struct list List; + +struct entnode +{ + char *version; + char *timestamp; + char *options; + char *tag; + char *date; +}; +typedef struct entnode Entnode; + +#if __STDC__ +List *getlist (void); +Node *findnode (List * list, char *key); +Node *getnode (void); +int addnode (List * list, Node * p); +int walklist (List * list, int (*proc) ()); +void dellist (List ** listp); +void delnode (Node * p); +void freenode (Node * p); +void sortlist (List * list, int (*comp) ()); +#else +List *getlist (); +Node *findnode (); +Node *getnode (); +int addnode (); +int walklist (); +void dellist (); +void delnode (); +void freenode (); +void sortlist (); +#endif /* __STDC__ */ diff --git a/gnu/usr.bin/cvs/lib/mkdir.c b/gnu/usr.bin/cvs/lib/mkdir.c new file mode 100644 index 0000000..b17cca2 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/mkdir.c @@ -0,0 +1,125 @@ +/* mkrmdir.c -- BSD compatible directory functions for System V + Copyright (C) 1988, 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#ifndef STDC_HEADERS +extern int errno; +#endif + +/* mkdir and rmdir adapted from GNU tar. */ + +/* Make directory DPATH, with permission mode DMODE. + + Written by Robert Rother, Mariah Corporation, August 1985 + (sdcsvax!rmr or rmr@uscd). If you want it, it's yours. + + Severely hacked over by John Gilmore to make a 4.2BSD compatible + subroutine. 11Mar86; hoptoad!gnu + + Modified by rmtodd@uokmax 6-28-87 -- when making an already existing dir, + subroutine didn't return EEXIST. It does now. */ + +int +mkdir (dpath, dmode) + char *dpath; + int dmode; +{ + int cpid, status; + struct stat statbuf; + + if (stat (dpath, &statbuf) == 0) + { + errno = EEXIST; /* stat worked, so it already exists. */ + return -1; + } + + /* If stat fails for a reason other than non-existence, return error. */ + if (errno != ENOENT) + return -1; + + cpid = fork (); + switch (cpid) + { + case -1: /* Cannot fork. */ + return -1; /* errno is set already. */ + + case 0: /* Child process. */ + /* Cheap hack to set mode of new directory. Since this child + process is going away anyway, we zap its umask. + This won't suffice to set SUID, SGID, etc. on this + directory, so the parent process calls chmod afterward. */ + status = umask (0); /* Get current umask. */ + umask (status | (0777 & ~dmode)); /* Set for mkdir. */ + execl ("/bin/mkdir", "mkdir", dpath, (char *) 0); + _exit (1); + + default: /* Parent process. */ + while (wait (&status) != cpid) /* Wait for kid to finish. */ + /* Do nothing. */ ; + + if (status & 0xFFFF) + { + errno = EIO; /* /bin/mkdir failed. */ + return -1; + } + return chmod (dpath, dmode); + } +} + +/* Remove directory DPATH. + Return 0 if successful, -1 if not. */ + +int +rmdir (dpath) + char *dpath; +{ + int cpid, status; + struct stat statbuf; + + if (stat (dpath, &statbuf) != 0) + return -1; /* stat set errno. */ + + if ((statbuf.st_mode & S_IFMT) != S_IFDIR) + { + errno = ENOTDIR; + return -1; + } + + cpid = fork (); + switch (cpid) + { + case -1: /* Cannot fork. */ + return -1; /* errno is set already. */ + + case 0: /* Child process. */ + execl ("/bin/rmdir", "rmdir", dpath, (char *) 0); + _exit (1); + + default: /* Parent process. */ + while (wait (&status) != cpid) /* Wait for kid to finish. */ + /* Do nothing. */ ; + + if (status & 0xFFFF) + { + errno = EIO; /* /bin/rmdir failed. */ + return -1; + } + return 0; + } +} diff --git a/gnu/usr.bin/cvs/lib/myndbm.c b/gnu/usr.bin/cvs/lib/myndbm.c new file mode 100644 index 0000000..8069698 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/myndbm.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * A simple ndbm-emulator for CVS. It parses a text file of the format: + * + * key value + * + * at dbm_open time, and loads the entire file into memory. As such, it is + * probably only good for fairly small modules files. Ours is about 30K in + * size, and this code works fine. + */ + +#include "cvs.h" + +#ifdef MY_NDBM + +#ifndef lint +static char rcsid[] = "@(#)myndbm.c 1.5 92/03/31"; +#endif + +static void mydbm_load_file (); + +/* ARGSUSED */ +DBM * +mydbm_open (file, flags, mode) + char *file; + int flags; + int mode; +{ + FILE *fp; + DBM *db; + + if ((fp = fopen (file, "r")) == NULL) + return ((DBM *) 0); + + db = (DBM *) xmalloc (sizeof (*db)); + db->dbm_list = getlist (); + + mydbm_load_file (fp, db->dbm_list); + (void) fclose (fp); + return (db); +} + +void +mydbm_close (db) + DBM *db; +{ + dellist (&db->dbm_list); + free ((char *) db); +} + +datum +mydbm_fetch (db, key) + DBM *db; + datum key; +{ + Node *p; + char *s; + datum val; + + /* make sure it's null-terminated */ + s = xmalloc (key.dsize + 1); + (void) strncpy (s, key.dptr, key.dsize); + s[key.dsize] = '\0'; + + p = findnode (db->dbm_list, s); + if (p) + { + val.dptr = p->data; + val.dsize = strlen (p->data); + } + else + { + val.dptr = (char *) NULL; + val.dsize = 0; + } + free (s); + return (val); +} + +datum +mydbm_firstkey (db) + DBM *db; +{ + Node *head, *p; + datum key; + + head = db->dbm_list->list; + p = head->next; + if (p != head) + { + key.dptr = p->key; + key.dsize = strlen (p->key); + } + else + { + key.dptr = (char *) NULL; + key.dsize = 0; + } + db->dbm_next = p->next; + return (key); +} + +datum +mydbm_nextkey (db) + DBM *db; +{ + Node *head, *p; + datum key; + + head = db->dbm_list->list; + p = db->dbm_next; + if (p != head) + { + key.dptr = p->key; + key.dsize = strlen (p->key); + } + else + { + key.dptr = (char *) NULL; + key.dsize = 0; + } + db->dbm_next = p->next; + return (key); +} + +static void +mydbm_load_file (fp, list) + FILE *fp; + List *list; +{ + char line[MAXLINELEN], value[MAXLINELEN]; + char *cp, *vp; + int len, cont; + + for (cont = 0; fgets (line, sizeof (line), fp) != NULL;) + { + if ((cp = rindex (line, '\n')) != NULL) + *cp = '\0'; /* strip the newline */ + + /* + * Add the line to the value, at the end if this is a continuation + * line; otherwise at the beginning, but only after any trailing + * backslash is removed. + */ + vp = value; + if (cont) + vp += strlen (value); + + /* + * See if the line we read is a continuation line, and strip the + * backslash if so. + */ + len = strlen (line); + if (len > 0) + cp = &line[len - 1]; + else + cp = line; + if (*cp == '\\') + { + cont = 1; + *cp = '\0'; + } + else + { + cont = 0; + } + (void) strcpy (vp, line); + if (value[0] == '#') + continue; /* comment line */ + vp = value; + while (*vp && isspace (*vp)) + vp++; + if (*vp == '\0') + continue; /* empty line */ + + /* + * If this was not a continuation line, add the entry to the database + */ + if (!cont) + { + Node *p = getnode (); + char *kp; + + kp = vp; + while (*vp && !isspace (*vp)) + vp++; + *vp++ = '\0'; /* NULL terminate the key */ + p->type = NDBMNODE; + p->key = xstrdup (kp); + while (*vp && isspace (*vp)) + vp++; /* skip whitespace to value */ + if (*vp == '\0') + { + error (0, 0, "warning: NULL value for key `%s'", p->key); + freenode (p); + continue; + } + p->data = xstrdup (vp); + if (addnode (list, p) == -1) + { + error (0, 0, "duplicate key found for `%s'", p->key); + freenode (p); + } + } + } +} + +#endif /* MY_NDBM */ diff --git a/gnu/usr.bin/cvs/lib/myndbm.h b/gnu/usr.bin/cvs/lib/myndbm.h new file mode 100644 index 0000000..d71acdf --- /dev/null +++ b/gnu/usr.bin/cvs/lib/myndbm.h @@ -0,0 +1,44 @@ +/* @(#)myndbm.h 1.3 92/02/29 */ + +#ifdef MY_NDBM + +#define DBLKSIZ 4096 + +typedef struct +{ + List *dbm_list; /* cached database */ + Node *dbm_next; /* next key to return for nextkey() */ +} DBM; + +typedef struct +{ + char *dptr; + int dsize; +} datum; + +/* + * So as not to conflict with other dbm_open, etc., routines that may + * be included by someone's libc, all of my emulation routines are prefixed + * by "my" and we define the "standard" ones to be "my" ones here. + */ +#define dbm_open mydbm_open +#define dbm_close mydbm_close +#define dbm_fetch mydbm_fetch +#define dbm_firstkey mydbm_firstkey +#define dbm_nextkey mydbm_nextkey + +#if __STDC__ +DBM *mydbm_open (char *file, int flags, int mode); +void mydbm_close (DBM * db); +datum mydbm_fetch (DBM * db, datum key); +datum mydbm_firstkey (DBM * db); +datum mydbm_nextkey (DBM * db); +#else +DBM *mydbm_open (); +void mydbm_close (); +datum mydbm_fetch (); +datum mydbm_firstkey (); +datum mydbm_nextkey (); +#endif /* __STDC__ */ + +#endif /* MY_NDBM */ diff --git a/gnu/usr.bin/cvs/lib/regex.c b/gnu/usr.bin/cvs/lib/regex.c new file mode 100644 index 0000000..3bccfd3 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/regex.c @@ -0,0 +1,4867 @@ +/* Extended regular expression matching and search library, + version 0.4. + (Implements POSIX draft P10003.2/D11.2, except for multibyte characters.) + + Copyright (C) 1985, 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#if defined (_AIX) && !defined (REGEX_MALLOC) + #pragma alloca +#endif + +#define _GNU_SOURCE + +/* For interactive testing, compile with -Dtest. Then this becomes + a self-contained program which reads a pattern, describes how it + compiles, then reads a string and searches for it. If a command-line + argument is present, it is taken to be the value for obscure_syntax (in + decimal). The default is 0 (Emacs-style syntax). + + If DEBUG is defined, this prints many voluminous messages about what + it is doing (if the variable `debug' is nonzero). */ + + +/* The `emacs' switch turns on certain matching commands + that make sense only in Emacs. */ +#ifdef emacs +#include "config.h" +#include "lisp.h" +#include "buffer.h" +#include "syntax.h" + +/* Emacs uses `NULL' as a predicate. */ +#undef NULL + +#else /* not emacs */ + +/* POSIX.1 says that <unistd.h> might need <sys/types.h>. We also need + it for regex.h. */ +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if defined (USG) || defined (POSIX) || defined (STDC_HEADERS) +#ifndef BSTRING +#include <string.h> +#ifndef bcopy +#define bcopy(s,d,n) memcpy ((d), (s), (n)) +#endif +#ifndef bcmp +#define bcmp(s1,s2,n) memcmp ((s1), (s2), (n)) +#endif +#ifndef bzero +#define bzero(s,n) memset ((s), 0, (n)) +#endif +#endif /* not BSTRING */ +#endif /* USG or POSIX or STDC_HEADERS */ + +#ifdef STDC_HEADERS +#include <stdlib.h> +#else /* not STDC_HEADERS */ +char *malloc (); +char *realloc (); +#endif /* not STDC_HEADERS */ + +/* If debugging, we use standard I/O. */ +#ifdef DEBUG +#include <stdio.h> +#endif + +/* Define the syntax stuff for \<, \>, etc. */ + +/* This must be nonzero for the wordchar and notwordchar pattern + commands in re_match_2. */ +#ifndef Sword +#define Sword 1 +#endif + +#ifdef SYNTAX_TABLE + +extern char *re_syntax_table; + +#else /* not SYNTAX_TABLE */ + +/* How many characters in the character set. */ +#define CHAR_SET_SIZE 256 + +static char re_syntax_table[CHAR_SET_SIZE]; + +static void +init_syntax_once () +{ + register int c; + static int done = 0; + + if (done) + return; + + bzero (re_syntax_table, sizeof re_syntax_table); + + for (c = 'a'; c <= 'z'; c++) + re_syntax_table[c] = Sword; + + for (c = 'A'; c <= 'Z'; c++) + re_syntax_table[c] = Sword; + + for (c = '0'; c <= '9'; c++) + re_syntax_table[c] = Sword; + + re_syntax_table['_'] = Sword; + + done = 1; +} + +#endif /* not SYNTAX_TABLE */ + +#define SYNTAX(c) re_syntax_table[c] + +#endif /* not emacs */ + + +/* Get the interface, including the syntax bits. */ +#include "regex.h" + + +/* isalpha(3) etc. are used for the character classes. */ +#include <ctype.h> +#ifndef isgraph +#define isgraph(c) (isprint (c) && !isspace (c)) +#endif +#ifndef isblank +#define isblank(c) ((c) == ' ' || (c) == '\t') +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef SIGN_EXTEND_CHAR +#ifdef __CHAR_UNSIGNED__ /* for, e.g., IBM RT */ +#define SIGN_EXTEND_CHAR(c) (((c)^128) - 128) /* As in Harbison and Steele. */ +#else +#define SIGN_EXTEND_CHAR /* As nothing. */ +#endif /* not CHAR_UNSIGNED */ +#endif /* not SIGN_EXTEND_CHAR */ + +/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we + use `alloca' instead of `malloc'. This is because using malloc in + re_search* or re_match* could cause memory leaks when C-g is used in + Emacs; also, malloc is slower and causes storage fragmentation. On + the other hand, malloc is more portable, and easier to debug. + + Because we sometimes use alloca, some routines have to be macros, + not functions---alloca-allocated space disappears at the end of the + function it is called in. */ +#ifdef REGEX_MALLOC + +#define REGEX_ALLOCATE malloc +#define REGEX_REALLOCATE(source, size) (realloc (source, size)) + +#else /* not REGEX_MALLOC */ + +/* Emacs already defines alloca, sometimes. */ +#ifndef alloca + +/* Make alloca work the best possible way. */ +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not __GNUC__ */ +#ifdef sparc +#include <alloca.h> +#else /* not __GNUC__ or sparc */ +char *alloca (); +#endif /* not sparc */ +#endif /* not __GNUC__ */ + +#endif /* not alloca */ + +/* Still not REGEX_MALLOC. */ + +#define REGEX_ALLOCATE alloca + +/* Requires a `char *destination' declared. */ +#define REGEX_REALLOCATE(source, size) \ + (destination = (char *) alloca (size), \ + bcopy (source, destination, size), \ + destination) + +#endif /* not REGEX_MALLOC */ + +/* (Re)Allocate N items of type T using malloc, or fail. */ +#define TALLOC(n, t) (t *) malloc ((n) * sizeof (t)) +#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t))) + + +#define BYTEWIDTH 8 /* In bits. */ + +#define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +/* These are the command codes that appear in compiled regular + expressions. Some opcodes are followed by argument bytes. A + command code can specify any interpretation whatsoever for its + arguments. Zero bytes may appear in the compiled regular expression. + + The value of `exactn' is needed in search.c (search_buffer) in Emacs. + So regex.h defines a symbol `RE_EXACTN_VALUE' to be 1; the value of + `exactn' we use here must also be 1. */ + +typedef enum +{ + no_op = 0, + + /* Followed by one byte giving n, then by n literal bytes. */ + exactn = 1, + + /* Matches any (more or less) character. */ + anychar, + + /* Matches any one char belonging to specified set. First + following byte is number of bitmap bytes. Then come bytes + for a bitmap saying which chars are in. Bits in each byte + are ordered low-bit-first. A character is in the set if its + bit is 1. A character too large to have a bit in the map is + automatically not in the set. */ + charset, + + /* Same parameters as charset, but match any character that is + not one of those specified. */ + charset_not, + + /* Start remembering the text that is matched, for storing in a + register. Followed by one byte with the register number, in + the range 0 to one less than the pattern buffer's re_nsub + field. Then followed by one byte with the number of groups + inner to this one. (This last has to be part of the + start_memory only because we need it in the on_failure_jump + of re_match_2.) */ + start_memory, + + /* Stop remembering the text that is matched and store it in a + memory register. Followed by one byte with the register + number, in the range 0 to one less than `re_nsub' in the + pattern buffer, and one byte with the number of inner groups, + just like `start_memory'. (We need the number of inner + groups here because we don't have any easy way of finding the + corresponding start_memory when we're at a stop_memory.) */ + stop_memory, + + /* Match a duplicate of something remembered. Followed by one + byte containing the register number. */ + duplicate, + + /* Fail unless at beginning of line. */ + begline, + + /* Fail unless at end of line. */ + endline, + + /* Succeeds if at beginning of buffer (if emacs) or at beginning + of string to be matched (if not). */ + begbuf, + + /* Analogously, for end of buffer/string. */ + endbuf, + + /* Followed by two byte relative address to which to jump. */ + no_pop_jump, + + /* Same as no_pop_jump, but marks the end of an alternative. */ + jump_past_next_alt, + + /* Followed by two-byte relative address of place to resume at + in case of failure. */ + on_failure_jump, + + /* Like on_failure_jump, but pushes a placeholder instead of the + current string position. */ + on_failure_keep_string_jump, + + /* Throw away latest failure point and then jump to following + two-byte relative address. */ + pop_failure_jump, + + /* Change to pop_failure_jump if know won't have to backtrack to + match; otherwise change to no_pop_jump. This is used to jump + back to the beginning of a repeat. If what follows this jump + clearly won't match what the repeat does, such that we can be + sure that there is no use backtracking out of repetitions + already matched, then we change it to a pop_failure_jump. + Followed by two-byte address. */ + maybe_pop_jump, + + /* Jump to following two-byte address, and push a dummy failure + point. This failure point will be thrown away if an attempt + is made to use it for a failure. A `+' construct makes this + before the first repeat. Also used as an intermediary kind + of jump when compiling an alternative. */ + dummy_failure_jump, + + /* Used like on_failure_jump except has to succeed n times; The + two-byte relative address following it is useless until then. + The address is followed by two more bytes containing n. */ + succeed_n, + + /* Similar to no_pop_jump, but jump n times only; also the + relative address following is in turn followed by yet two + more bytes containing n. */ + no_pop_jump_n, + + /* Set the following relative location (two bytes) to the + subsequent (two-byte) number. */ + set_number_at, + + wordchar, /* Matches any word-constituent character. */ + notwordchar, /* Matches any char that is not a word-constituent. */ + + wordbeg, /* Succeeds if at word beginning. */ + wordend, /* Succeeds if at word end. */ + + wordbound, /* Succeeds if at a word boundary. */ + notwordbound /* Succeeds if not at a word boundary. */ + +#ifdef emacs + ,before_dot, /* Succeeds if before point. */ + at_dot, /* Succeeds if at point. */ + after_dot, /* Succeeds if after point. */ + + /* Matches any character whose syntax is specified. Followed by + a byte which contains a syntax code, e.g., Sword. */ + syntaxspec, + + /* Matches any character whose syntax is not that specified. */ + notsyntaxspec +#endif /* emacs */ +} re_opcode_t; + +/* Common operations on the compiled pattern. */ + +/* Store NUMBER in two contiguous bytes starting at DESTINATION. */ + +#define STORE_NUMBER(destination, number) \ + do { \ + (destination)[0] = (number) & 0377; \ + (destination)[1] = (number) >> 8; \ + } while (0) + + +/* Same as STORE_NUMBER, except increment DESTINATION to + the byte after where the number is stored. Therefore, DESTINATION + must be an lvalue. */ + +#define STORE_NUMBER_AND_INCR(destination, number) \ + do { \ + STORE_NUMBER (destination, number); \ + (destination) += 2; \ + } while (0) + + +/* Put into DESTINATION a number stored in two contiguous bytes starting + at SOURCE. */ + +#define EXTRACT_NUMBER(destination, source) \ + do { \ + (destination) = *(source) & 0377; \ + (destination) += SIGN_EXTEND_CHAR (*(const char *)((source) + 1)) << 8;\ + } while (0) + +#ifdef DEBUG +static int +extract_number (source) + unsigned char *source; +{ + int answer = *source & 0377; + answer += (SIGN_EXTEND_CHAR (*(char *)((source) + 1))) << 8; + + return answer; +} +#endif + + +/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number. + SOURCE must be an lvalue. */ + +#define EXTRACT_NUMBER_AND_INCR(destination, source) \ + do { \ + EXTRACT_NUMBER (destination, source); \ + (source) += 2; \ + } while (0) + +#ifdef DEBUG +static void +extract_number_and_incr (destination, source) + int *destination; + unsigned char **source; +{ + *destination = extract_number (*source); + *source += 2; +} +#endif + + +/* Is true if there is a first string and if PTR is pointing anywhere + inside it or just past the end. */ + +#define IS_IN_FIRST_STRING(ptr) \ + (size1 && string1 <= (ptr) && (ptr) <= string1 + size1) + +#ifdef DEBUG + +extern void printchar (); + +/* Print a compiled pattern buffer in human-readable form, starting at + the START pointer into it and ending just before the pointer END. */ + +static void +partial_compiled_pattern_printer (pbufp, start, end) + struct re_pattern_buffer *pbufp; + unsigned char *start; + unsigned char *end; +{ + + int mcnt, mcnt2; + unsigned char *p = start; + unsigned char *pend = end; + + if (start == NULL) + { + printf ("(null)\n"); + return; + } + + /* This loop loops over pattern commands. */ + while (p < pend) + { + switch ((re_opcode_t) *p++) + { + case no_op: + printf ("/no_op"); + break; + + case exactn: + mcnt = *p++; + printf ("/exactn/%d", mcnt); + do + { + putchar ('/'); + printchar (*p++); + } + while (--mcnt); + break; + + case start_memory: + mcnt = *p++; + printf ("/start_memory/%d/%d", mcnt, *p++); + break; + + case stop_memory: + mcnt = *p++; + printf ("/stop_memory/%d/%d", mcnt, *p++); + break; + + case duplicate: + printf ("/duplicate/%d", *p++); + break; + + case anychar: + printf ("/anychar"); + break; + + case charset: + case charset_not: + { + register int c; + + printf ("/charset%s/", *(p - 1) == charset_not ? "_not" : ""); + + for (c = 0; p < pend && c < *p * BYTEWIDTH; c++) + { + if (p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + printchar (c); + } + p += 1 + *p; + break; + } + + case begline: + printf ("/begline"); + break; + + case endline: + printf ("/endline"); + break; + + case on_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/on_failure_jump/0/%d", mcnt); + break; + + case on_failure_keep_string_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/on_failure_keep_string_jump/0/%d", mcnt); + break; + + case dummy_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/dummy_failure_jump/0/%d", mcnt); + break; + + case maybe_pop_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/maybe_pop_jump/0/%d", mcnt); + break; + + case pop_failure_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/pop_failure_jump/0/%d", mcnt); + break; + + case jump_past_next_alt: + extract_number_and_incr (&mcnt, &p); + printf ("/jump_past_next_alt/0/%d", mcnt); + break; + + case no_pop_jump: + extract_number_and_incr (&mcnt, &p); + printf ("/no_pop_jump/0/%d", mcnt); + break; + + case succeed_n: + extract_number_and_incr (&mcnt, &p); + extract_number_and_incr (&mcnt2, &p); + printf ("/succeed_n/0/%d/0/%d", mcnt, mcnt2); + break; + + case no_pop_jump_n: + extract_number_and_incr (&mcnt, &p); + extract_number_and_incr (&mcnt2, &p); + printf ("/no_pop_jump_n/0/%d/0/%d", mcnt, mcnt2); + break; + + case set_number_at: + extract_number_and_incr (&mcnt, &p); + extract_number_and_incr (&mcnt2, &p); + printf ("/set_number_at/0/%d/0/%d", mcnt, mcnt2); + break; + + case wordbound: + printf ("/wordbound"); + break; + + case notwordbound: + printf ("/notwordbound"); + break; + + case wordbeg: + printf ("/wordbeg"); + break; + + case wordend: + printf ("/wordend"); + +#ifdef emacs + case before_dot: + printf ("/before_dot"); + break; + + case at_dot: + printf ("/at_dot"); + break; + + case after_dot: + printf ("/after_dot"); + break; + + case wordchar: + printf ("/wordchar-emacs"); + mcnt = (int) Sword; + break; + + case syntaxspec: + printf ("/syntaxspec"); + mcnt = *p++; + printf ("/%d", mcnt); + break; + + case notwordchar: + printf ("/notwordchar-emacs"); + mcnt = (int) Sword; + break; + + case notsyntaxspec: + printf ("/notsyntaxspec"); + mcnt = *p++; + printf ("/%d", mcnt); + break; +#else /* not emacs */ + case wordchar: + printf ("/wordchar-notemacs"); + break; + + case notwordchar: + printf ("/notwordchar-notemacs"); + break; +#endif /* not emacs */ + + case begbuf: + printf ("/begbuf"); + break; + + case endbuf: + printf ("/endbuf"); + break; + + default: + printf ("?%d", *(p-1)); + } + } + printf ("/\n"); +} + +static void +compiled_pattern_printer (pbufp) + struct re_pattern_buffer *pbufp; +{ + partial_compiled_pattern_printer (pbufp, pbufp->buffer, + pbufp->buffer + pbufp->used); +} + + +static void +double_string_printer (where, string1, size1, string2, size2) + unsigned char *where; + unsigned char *string1; + unsigned char *string2; + int size1; + int size2; +{ + unsigned this_char; + + if (where == NULL) + printf ("(null)"); + else + { + if (IS_IN_FIRST_STRING (where)) + { + for (this_char = where - string1; this_char < size1; this_char++) + printchar (string1[this_char]); + + where = string2; + } + + for (this_char = where - string2; this_char < size2; this_char++) + printchar (string2[this_char]); + } +} + +#endif /* DEBUG */ + +#ifdef DEBUG + +/* It is useful to test things that must to be true when debugging. */ +#include <assert.h> + +static int debug = 0; + +#define DEBUG_STATEMENT(e) e +#define DEBUG_PRINT1(x) if (debug) printf (x) +#define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2) +#define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3) +#define DEBUG_COMPILED_PATTERN_PRINTER(p, s, e) \ + if (debug) partial_compiled_pattern_printer (p, s, e) +#define DEBUG_DOUBLE_STRING_PRINTER(w, s1, sz1, s2, sz2) \ + if (debug) double_string_printer (w, s1, sz1, s2, sz2) + +#else /* not DEBUG */ + +#undef assert +#define assert(e) + +#define DEBUG_STATEMENT(e) +#define DEBUG_PRINT1(x) +#define DEBUG_PRINT2(x1, x2) +#define DEBUG_PRINT3(x1, x2, x3) +#define DEBUG_COMPILED_PATTERN_PRINTER(p, s, e) +#define DEBUG_DOUBLE_STRING_PRINTER(w, s1, sz1, s2, sz2) + +#endif /* not DEBUG */ + +typedef char boolean; +#define false 0 +#define true 1 + +/* Set by re_set_syntax to the current regexp syntax to recognize. Can + also be assigned to more or less arbitrarily. Since we use this as a + collection of bits, declaring it unsigned maximizes portability. */ +reg_syntax_t obscure_syntax = 0; + + +/* Specify the precise syntax of regexps for compilation. This provides + for compatibility for various utilities which historically have + different, incompatible syntaxes. + + The argument SYNTAX is a bit mask comprised of the various bits + defined in regex.h. We return the old syntax. */ + +reg_syntax_t +re_set_syntax (syntax) + reg_syntax_t syntax; +{ + reg_syntax_t ret = obscure_syntax; + + obscure_syntax = syntax; + return ret; +} + +/* This table gives an error message for each of the error codes listed + in regex.h. Obviously the order here has to be same as there. */ + +static const char *re_error_msg[] = + { NULL, /* REG_NOERROR */ + "No match", /* REG_NOMATCH */ + "Invalid regular expression", /* REG_BADPAT */ + "Invalid collation character", /* REG_ECOLLATE */ + "Invalid character class name", /* REG_ECTYPE */ + "Trailing backslash", /* REG_EESCAPE */ + "Invalid back reference", /* REG_ESUBREG */ + "Unmatched [ or [^", /* REG_EBRACK */ + "Unmatched ( or \\(", /* REG_EPAREN */ + "Unmatched \\{", /* REG_EBRACE */ + "Invalid content of \\{\\}", /* REG_BADBR */ + "Invalid range end", /* REG_ERANGE */ + "Memory exhausted", /* REG_ESPACE */ + "Invalid preceding regular expression", /* REG_BADRPT */ + "Premature end of regular expression", /* REG_EEND */ + "Regular expression too big", /* REG_ESIZE */ + "Unmatched ) or \\)", /* REG_ERPAREN */ + }; + +/* Other subroutine declarations and macros for regex_compile. */ + +static void store_jump (), insert_jump (), store_jump_n (), + insert_jump_n (), insert_op_2 (); + +static boolean at_endline_op_p (), group_in_compile_stack (); + +/* Fetch the next character in the uncompiled pattern---translating it + if necessary. Also cast from a signed character in the constant + string passed to us by the user to an unsigned char that we can use + as an array index (in, e.g., `translate'). */ +#define PATFETCH(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + if (translate) c = translate[c]; \ + } while (0) + +/* Fetch the next character in the uncompiled pattern, with no + translation. */ +#define PATFETCH_RAW(c) \ + do {if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + } while (0) + +/* Go backwards one character in the pattern. */ +#define PATUNFETCH p-- + + +/* If `translate' is non-null, return translate[D], else just D. We + cast the subscript to translate because some data is declared as + `char *', to avoid warnings when a string constant is passed. But + when we use a character as a subscript we must make it unsigned. */ +#define TRANSLATE(d) (translate ? translate[(unsigned char) (d)] : (d)) + + +/* Macros for outputting the compiled pattern into `buffer'. */ + +/* If the buffer isn't allocated when it comes in, use this. */ +#define INIT_BUF_SIZE 32 + +/* Make sure we have at least N more bytes of space in buffer. */ +#define GET_BUFFER_SPACE(n) \ + { \ + while (b - bufp->buffer + (n) > bufp->allocated) \ + EXTEND_BUFFER (); \ + } + +/* Make sure we have one more byte of buffer space and then add C to it. */ +#define PAT_PUSH(c) \ + do { \ + GET_BUFFER_SPACE (1); \ + *b++ = (unsigned char) (c); \ + } while (0) + + +/* Make sure we have two more bytes of buffer space and then add C1 and + C2 to it. */ +#define PAT_PUSH_2(c1, c2) \ + do { \ + GET_BUFFER_SPACE (2); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + } while (0) + + +/* Make sure we have two more bytes of buffer space and then add C1, C2 + and C3 to it. */ +#define PAT_PUSH_3(c1, c2, c3) \ + do { \ + GET_BUFFER_SPACE (3); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + *b++ = (unsigned char) (c3); \ + } while (0) + +/* This is not an arbitrary limit: the arguments to the opcodes which + represent offsets into the pattern are two bytes long. So if 2^16 + bytes turns out to be too small, many things would have to change. */ +#define MAX_BUF_SIZE (1L << 16) + +/* Extend the buffer by twice its current size via realloc and + reset the pointers that pointed into the old block to point to the + correct places in the new one. If extending the buffer results in it + being larger than MAX_BUF_SIZE, then flag memory exhausted. */ +#define EXTEND_BUFFER() \ + do { \ + unsigned char *old_buffer = bufp->buffer; \ + if (bufp->allocated == MAX_BUF_SIZE) \ + return REG_ESIZE; \ + bufp->allocated <<= 1; \ + if (bufp->allocated > MAX_BUF_SIZE) \ + bufp->allocated = MAX_BUF_SIZE; \ + bufp->buffer = (unsigned char *) realloc (bufp->buffer, bufp->allocated);\ + if (bufp->buffer == NULL) \ + return REG_ESPACE; \ + /* If the buffer moved, move all the pointers into it. */ \ + if (old_buffer != bufp->buffer) \ + { \ + b = (b - old_buffer) + bufp->buffer; \ + begalt = (begalt - old_buffer) + bufp->buffer; \ + if (fixup_alt_jump) \ + fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer;\ + if (laststart) \ + laststart = (laststart - old_buffer) + bufp->buffer; \ + if (pending_exact) \ + pending_exact = (pending_exact - old_buffer) + bufp->buffer; \ + } \ + } while (0) + + +/* Since we have one byte reserved for the register number argument to + {start,stop}_memory, the maximum number of groups we can report + things about is what fits in that byte. */ +typedef unsigned char regnum_t; +#define MAX_REGNUM ((regnum_t) ((1 << BYTEWIDTH) - 1)) + + +/* Macros for the compile stack. */ + +/* This type needs to be able to hold values from 0 to MAX_BUF_SIZE - 1. */ +typedef short pattern_offset_t; + +typedef struct +{ + pattern_offset_t begalt_offset; + pattern_offset_t fixup_alt_jump; + pattern_offset_t inner_group_offset; + pattern_offset_t laststart_offset; + regnum_t regnum; +} compile_stack_elt_t; + + +typedef struct +{ + compile_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} compile_stack_type; + + +#define INIT_COMPILE_STACK_SIZE 32 + +#define COMPILE_STACK_EMPTY (compile_stack.avail == 0) +#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size) + +/* The next available element. */ +#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail]) + + +/* Set the bit for character C in a list. */ +#define SET_LIST_BIT(c) (b[(c) / BYTEWIDTH] |= 1 << ((c) % BYTEWIDTH)) + + +/* Get the next unsigned number in the uncompiled pattern. */ +#define GET_UNSIGNED_NUMBER(num) \ + { if (p != pend) \ + { \ + PATFETCH (c); \ + while (isdigit (c)) \ + { \ + if (num < 0) \ + num = 0; \ + num = num * 10 + c - '0'; \ + if (p == pend) \ + break; \ + PATFETCH (c); \ + } \ + } \ + } + + +/* Read the endpoint of a range from the uncompiled pattern and set the + corresponding bits in the compiled pattern. */ + +#define DO_RANGE \ + { \ + char end; \ + char this_char = p[-2]; \ + \ + if (p == pend) \ + return REG_ERANGE; \ + PATFETCH (end); \ + if (syntax & RE_NO_EMPTY_RANGES && this_char > end) \ + return REG_ERANGE; \ + while (this_char <= end) \ + { \ + SET_LIST_BIT (TRANSLATE (this_char)); \ + this_char++; \ + } \ + } + + +#define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ + +#define IS_CHAR_CLASS(string) \ + (STREQ (string, "alpha") || STREQ (string, "upper") \ + || STREQ (string, "lower") || STREQ (string, "digit") \ + || STREQ (string, "alnum") || STREQ (string, "xdigit") \ + || STREQ (string, "space") || STREQ (string, "print") \ + || STREQ (string, "punct") || STREQ (string, "graph") \ + || STREQ (string, "cntrl") || STREQ (string, "blank")) + + +/* regex_compile compiles PATTERN (of length SIZE) according to SYNTAX. + Returns one of error codes defined in regex.h, or zero for success. + + Assumes the `allocated' (and perhaps `buffer') and `translate' + fields are set in BUFP on entry. + + If it succeeds, results are put in BUFP (if it returns an error, the + contents of BUFP are undefined): + `buffer' is the compiled pattern; + `syntax' is set to SYNTAX; + `used' is set to the length of the compiled pattern; + `fastmap_accurate' is set to zero; + `re_nsub' is set to the number of groups in PATTERN; + `not_bol' and `not_eol' are set to zero. + + The `fastmap' and `newline_anchor' fields are neither + examined nor set. */ + +static reg_errcode_t +regex_compile (pattern, size, syntax, bufp) + const char *pattern; + int size; + reg_syntax_t syntax; + struct re_pattern_buffer *bufp; +{ + register unsigned char c, c1; + const char *p1; + + /* Points to the end of the buffer, where we should append. */ + register unsigned char *b; + + /* Points to the current (ending) position in the pattern. */ + const char *p = pattern; + const char *pend = pattern + size; + + /* How to translate the characters in the pattern. */ + char *translate = bufp->translate; + + /* Address of the count-byte of the most recently inserted `exactn' + command. This makes it possible to tell if a new exact-match + character can be added to that command or if the character requires + a new `exactn' command. */ + unsigned char *pending_exact = 0; + + /* Address of start of the most recently finished expression. + This tells, e.g., postfix * where to find the start of its + operand. Reset at the beginning of groups and alternatives. */ + unsigned char *laststart = 0; + + /* Place in the uncompiled pattern (i.e., the {) to + which to go back if the interval is invalid. */ + const char *beg_interval; /* The `{'. */ + const char *following_left_brace; + + /* Address of beginning of regexp, or inside of last group. */ + unsigned char *begalt; + + /* Address of the place where a forward jump should go to the end of + the containing expression. Each alternative of an `or'---except the + last---ends with a forward jump of this sort. */ + unsigned char *fixup_alt_jump = 0; + + /* Counts open-groups as they are encountered. Remembered for the + matching close-group on the compile stack, so the same register + number is put in the stop_memory as the start_memory. The type + here is determined by MAX_REGNUM. */ + regnum_t regnum = 0; + + /* Keeps track of unclosed groups. */ + compile_stack_type compile_stack; + +#ifdef DEBUG + DEBUG_PRINT1 ("\nCompiling pattern: "); + if (debug) + { + unsigned debug_count; + + for (debug_count = 0; debug_count < size; debug_count++) + printchar (pattern[debug_count]); + + DEBUG_PRINT1 ("\n"); + } +#endif /* DEBUG */ + + /* Initialize the compile stack. */ + compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t); + if (compile_stack.stack == NULL) + return REG_ESPACE; + + compile_stack.size = INIT_COMPILE_STACK_SIZE; + compile_stack.avail = 0; + + /* Initialize the pattern buffer. */ + bufp->syntax = syntax; + bufp->fastmap_accurate = 0; + bufp->not_bol = bufp->not_eol = 0; + + /* Set `used' to zero, so that if we return an error, the pattern + printer (for debugging) will think there's no pattern. We reset it + at the end. */ + bufp->used = 0; + + /* Always count groups, whether or not bufp->no_sub is set. */ + bufp->re_nsub = 0; + +#if !defined (emacs) && !defined (SYNTAX_TABLE) + /* Initialize the syntax table. */ + init_syntax_once (); +#endif + + if (bufp->allocated == 0) + { + if (bufp->buffer) + { /* EXTEND_BUFFER loses when bufp->allocated is 0. This loses if + buffer's address is bogus, but that is the user's + responsibility. */ + RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char); + } + else + { /* Caller did not allocate a buffer. Do it for them. */ + bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char); + } + if (!bufp->buffer) return REG_ESPACE; + + bufp->allocated = INIT_BUF_SIZE; + } + + begalt = b = bufp->buffer; + + /* Loop through the uncompiled pattern until we're at the end. */ + while (p != pend) + { + PATFETCH (c); + + switch (c) + { + /* ^ matches the empty string at the beginning of a string (or + possibly a line). If RE_CONTEXT_INDEP_ANCHORS is set, ^ is + always an operator (and foo^bar is unmatchable). If that bit + isn't set, it's an operator only at the beginning of the + pattern or after an alternation or open-group operator, or, + if RE_NEWLINE_ORDINARY is not set, after a newline (except it + can be preceded by other operators that match the empty + string); otherwise, it's a normal character. */ + case '^': + { + if ( /* If at start of (sub)pattern, it's an operator. */ + laststart == 0 + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* If after a newline, might be an operator. (Since + laststart is nonzero here, we know we have at + least one byte before the ^.) */ + || (!(syntax & RE_NEWLINE_ORDINARY) && p[-2] == '\n')) + PAT_PUSH (begline); + else + goto normal_char; + } + break; + + + /* $ matches the empty string following the end of the string (or + possibly a line). It follows rules dual to those for ^. */ + case '$': + { + if ( /* If at end of pattern, it's an operator. */ + p == pend + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's next. */ + || at_endline_op_p (p, pend, syntax)) + PAT_PUSH (endline); + else + goto normal_char; + } + break; + + + case '+': + case '?': + if ((syntax & RE_BK_PLUS_QM) + || (syntax & RE_LIMITED_OPS)) + goto normal_char; + handle_plus: + case '*': + /* If there is no previous pattern... */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + return REG_BADRPT; + else if (!(syntax & RE_CONTEXT_INDEP_OPS)) + goto normal_char; + } + + { + /* Are we optimizing this jump? */ + boolean keep_string_p = false; + + /* 1 means zero (many) matches is allowed. */ + char zero_times_ok = 0, many_times_ok = 0; + + /* If there is a sequence of repetition chars, collapse it + down to just one (the right one). We can't combine + interval operators with these because of, e.g., `a{2}*', + which should only match an even number of `a's. */ + + for (;;) + { + zero_times_ok |= c != '+'; + many_times_ok |= c != '?'; + + if (p == pend) + break; + + PATFETCH (c); + + if (c == '*' + || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?'))) + ; + + else if (syntax & RE_BK_PLUS_QM && c == '\\') + { + if (p == pend) return REG_EESCAPE; + + PATFETCH (c1); + if (!(c1 == '+' || c1 == '?')) + { + PATUNFETCH; + PATUNFETCH; + break; + } + + c = c1; + } + else + { + PATUNFETCH; + break; + } + + /* If we get here, we found another repeat character. */ + } + + /* Star, etc. applied to an empty pattern is equivalent + to an empty pattern. */ + if (!laststart) + break; + + /* Now we know whether or not zero matches is allowed + and also whether or not two or more matches is allowed. */ + if (many_times_ok) + { /* More than one repetition is allowed, so put in at the + end a backward relative jump from `b' to before the next + jump we're going to put in below (which jumps from + laststart to after this jump). + + But if we are at the `*' in the exact sequence `.*\n', + insert an unconditional jump backwards to the ., + instead of the beginning of the loop. This way we only + push a failure point once, instead of every time + through the loop. */ + assert (p - 1 > pattern); + + /* Get the space for the jump. */ + GET_BUFFER_SPACE (3); + + /* We know we are not at the first character of the pattern, + because laststart was nonzero. And we've already + incremented `p', by the way, to be the character after + the `*'. Do we have to do something analogous here + for null bytes, because of RE_DOT_NOT_NULL? */ + if (TRANSLATE (*(p - 2)) == TRANSLATE ('.') + && p < pend && TRANSLATE (*p) == TRANSLATE ('\n') + && !(syntax & RE_DOT_NEWLINE)) + { /* We have .*\n. */ + store_jump (b, no_pop_jump, laststart); + keep_string_p = true; + } + else + /* Anything else. */ + store_jump (b, maybe_pop_jump, laststart - 3); + + /* We've added more stuff to the buffer. */ + b += 3; + } + + /* On failure, jump from laststart to b + 3, which will be the + end of the buffer after this jump is inserted. */ + GET_BUFFER_SPACE (3); + insert_jump (keep_string_p ? on_failure_keep_string_jump + : on_failure_jump, + laststart, b + 3, b); + pending_exact = 0; + b += 3; + + if (!zero_times_ok) + { + /* At least one repetition is required, so insert a + dummy_failure before the initial on_failure_jump + instruction of the loop. This effects a skip over that + instruction the first time we hit that loop. */ + GET_BUFFER_SPACE (3); + insert_jump (dummy_failure_jump, laststart, laststart + 6, b); + b += 3; + } + } + break; + + + case '.': + laststart = b; + PAT_PUSH (anychar); + break; + + + case '[': + { + boolean just_had_a_char_class = false; + + if (p == pend) return REG_EBRACK; + + /* Ensure that we have enough space to push an entire + charset: the opcode, the byte count, and the bitmap. */ + while (b - bufp->buffer + 2 + (1 << BYTEWIDTH) / BYTEWIDTH + > bufp->allocated) + EXTEND_BUFFER (); + + laststart = b; + + PAT_PUSH (*p == '^' ? charset_not : charset); + if (*p == '^') + p++; + + /* Remember the first position in the bracket expression. */ + p1 = p; + + /* Push the number of bytes in the bitmap. */ + PAT_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH); + + /* Clear the whole map. */ + bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH); + + /* charset_not matches newline according to a syntax bit. */ + if ((re_opcode_t) b[-2] == charset_not + && (syntax & RE_HAT_LISTS_NOT_NEWLINE)) + SET_LIST_BIT ('\n'); + + /* Read in characters and ranges, setting map bits. */ + for (;;) + { + if (p == pend) return REG_EBRACK; + + PATFETCH (c); + + /* \ might escape characters inside [...] and [^...]. */ + if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\') + { + if (p == pend) return REG_EESCAPE; + + PATFETCH (c1); + SET_LIST_BIT (c1); + continue; + } + + /* Could be the end of the bracket expression. If it's + not (i.e., when the bracket expression is `[]' so + far), the ']' character bit gets set way below. */ + if (c == ']' && p != p1 + 1) + break; + + /* Look ahead to see if it's a range when the last thing + was a character class. */ + if (just_had_a_char_class && c == '-' && *p != ']') + return REG_ERANGE; + + /* Look ahead to see if it's a range when the last thing + was a character: if this is a hyphen not at the + beginning or the end of a list, then it's the range + operator. */ + if (c == '-' + && !(p - 2 >= pattern && p[-2] == '[') + && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^') + && *p != ']') + { + DO_RANGE; + } + + else if (p[0] == '-' && p[1] != ']') + { /* This handles ranges made up of characters only. */ + PATFETCH (c1); /* The `-'. */ + DO_RANGE; + } + + /* See if we're at the beginning of a possible character + class. */ + + else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':') + { /* Leave room for the null. */ + char str[CHAR_CLASS_MAX_LENGTH + 1]; + + PATFETCH (c); + c1 = 0; + + /* If pattern is `[[:'. */ + if (p == pend) return REG_EBRACK; + + for (;;) + { + PATFETCH (c); + if (c == ':' || c == ']' || p == pend + || c1 == CHAR_CLASS_MAX_LENGTH) + break; + str[c1++] = c; + } + str[c1] = '\0'; + + /* If isn't a word bracketed by `[:' and:`]': + undo the ending character, the letters, and leave + the leading `:' and `[' (but set bits for them). */ + if (c == ':' && *p == ']') + { + int ch; + boolean is_alnum = STREQ (str, "alnum"); + boolean is_alpha = STREQ (str, "alpha"); + boolean is_blank = STREQ (str, "blank"); + boolean is_cntrl = STREQ (str, "cntrl"); + boolean is_digit = STREQ (str, "digit"); + boolean is_graph = STREQ (str, "graph"); + boolean is_lower = STREQ (str, "lower"); + boolean is_print = STREQ (str, "print"); + boolean is_punct = STREQ (str, "punct"); + boolean is_space = STREQ (str, "space"); + boolean is_upper = STREQ (str, "upper"); + boolean is_xdigit = STREQ (str, "xdigit"); + + if (!IS_CHAR_CLASS (str)) return REG_ECTYPE; + + /* Throw away the ] at the end of the character + class. */ + PATFETCH (c); + + if (p == pend) return REG_EBRACK; + + for (ch = 0; ch < 1 << BYTEWIDTH; ch++) + { + if ( (is_alnum && isalnum (ch)) + || (is_alpha && isalpha (ch)) + || (is_blank && isblank (ch)) + || (is_cntrl && iscntrl (ch)) + || (is_digit && isdigit (ch)) + || (is_graph && isgraph (ch)) + || (is_lower && islower (ch)) + || (is_print && isprint (ch)) + || (is_punct && ispunct (ch)) + || (is_space && isspace (ch)) + || (is_upper && isupper (ch)) + || (is_xdigit && isxdigit (ch))) + SET_LIST_BIT (ch); + } + just_had_a_char_class = true; + } + else + { + c1++; + while (c1--) + PATUNFETCH; + SET_LIST_BIT ('['); + SET_LIST_BIT (':'); + just_had_a_char_class = false; + } + } + else + { + just_had_a_char_class = false; + SET_LIST_BIT (c); + } + } + + /* Discard any (non)matching list bytes that are all 0 at the + end of the map. Decrease the map-length byte too. */ + while ((int) b[-1] > 0 && b[b[-1] - 1] == 0) + b[-1]--; + b += b[-1]; + } + break; + + + case '(': + if (syntax & RE_NO_BK_PARENS) + goto handle_open; + else + goto normal_char; + + + case ')': + if (syntax & RE_NO_BK_PARENS) + goto handle_close; + else + goto normal_char; + + + case '\n': + if (syntax & RE_NEWLINE_ALT) + goto handle_bar; + else + goto normal_char; + + + case '|': + if (syntax & RE_NO_BK_VBAR) + goto handle_bar; + else + goto normal_char; + + + case '{': + if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES) + goto handle_interval; + else + goto normal_char; + + + case '\\': + if (p == pend) return REG_EESCAPE; + + /* Do not translate the character after the \, so that we can + distinguish, e.g., \B from \b, even if we normally would + translate, e.g., B to b. */ + PATFETCH_RAW (c); + + switch (c) + { + case '(': + if (syntax & RE_NO_BK_PARENS) + goto normal_backslash; + handle_open: + if (syntax & RE_NO_EMPTY_GROUPS) + { + p1 = p; + if (!(syntax & RE_NO_BK_PARENS) && *p1 == '\\') p1++; + + /* If found an empty group... */ + if (*p1 == ')') return REG_BADPAT; + } + + bufp->re_nsub++; + regnum++; + + if (COMPILE_STACK_FULL) + { + RETALLOC (compile_stack.stack, compile_stack.size << 1, + compile_stack_elt_t); + if (compile_stack.stack == NULL) return REG_ESPACE; + + compile_stack.size <<= 1; + } + + /* These are the values to restore when we hit end of this + group. They are all relative offsets, so that if the + whole pattern moves because of realloc, they will still + be valid. */ + COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer; + COMPILE_STACK_TOP.fixup_alt_jump + = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0; + COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer; + COMPILE_STACK_TOP.regnum = regnum; + + /* We will eventually replace the 0 with the number of + groups inner to this one. */ + if (regnum <= MAX_REGNUM) + { + COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2; + PAT_PUSH_3 (start_memory, regnum, 0); + } + + compile_stack.avail++; + + fixup_alt_jump = 0; + laststart = 0; + begalt = b; + break; + + + case ')': + if (syntax & RE_NO_BK_PARENS) goto normal_backslash; + + if (COMPILE_STACK_EMPTY) + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_backslash; + else + return REG_ERPAREN; + + handle_close: + if (fixup_alt_jump) + store_jump (fixup_alt_jump, jump_past_next_alt, b); + + /* See similar code for backslashed left paren above. */ + + if (COMPILE_STACK_EMPTY) + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_char; + else + return REG_ERPAREN; + + /* Since we just checked for an empty stack above, this + ``can't happen''. */ + assert (compile_stack.avail != 0); + { + /* We don't just want to restore into `regnum', because + later groups should continue to be numbered higher, + as in `(ab)c(de)' -- the second group is #2. */ + regnum_t this_group_regnum; + + compile_stack.avail--; + begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset; + fixup_alt_jump + = COMPILE_STACK_TOP.fixup_alt_jump + ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1 + : 0; + laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset; + this_group_regnum = COMPILE_STACK_TOP.regnum; + + /* We're at the end of the group, so now we know how many + groups were inside this one. */ + if (this_group_regnum <= MAX_REGNUM) + { + unsigned char *inner_group_loc + = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset; + + *inner_group_loc = regnum - this_group_regnum; + PAT_PUSH_3 (stop_memory, this_group_regnum, + regnum - this_group_regnum); + } + } + break; + + + case '|': /* `\|'. */ + if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR) + goto normal_backslash; + handle_bar: + if (syntax & RE_LIMITED_OPS) + goto normal_char; + + /* Disallow empty alternatives if RE_NO_EMPTY_ALTS is set. + Caveat: can't detect if the vbar is followed by a + trailing '$' yet, unless it's the last thing in a + pattern; the routine for verifying endlines has to do + the rest. */ + if ((syntax & RE_NO_EMPTY_ALTS) + && (!laststart || p == pend + || (*p == '$' && p + 1 == pend) + || ((syntax & RE_NO_BK_PARENS) + ? (p < pend && *p == ')') + : (p + 1 < pend && p[0] == '\\' && p[1] == ')')))) + return REG_BADPAT; + + /* Insert before the previous alternative a jump which + jumps to this alternative if the former fails. */ + GET_BUFFER_SPACE (3); + insert_jump (on_failure_jump, begalt, b + 6, b); + pending_exact = 0; + b += 3; + + /* The alternative before this one has a jump after it + which gets executed if it gets matched. Adjust that + jump so it will jump to this alternative's analogous + jump (put in below, which in turn will jump to the next + (if any) alternative's such jump, etc.). The last such + jump jumps to the correct final destination. A picture: + _____ _____ + | | | | + | v | v + a | b | c + + If we are at `b,' then fixup_alt_jump right now points to a + three-byte space after `a.' We'll put in the jump, set + fixup_alt_jump to right after `b,' and leave behind three + bytes which we'll fill in when we get to after `c.' */ + + if (fixup_alt_jump) + store_jump (fixup_alt_jump, jump_past_next_alt, b); + + /* Mark and leave space for a jump after this alternative, + to be filled in later either by next alternative or + when know we're at the end of a series of alternatives. */ + fixup_alt_jump = b; + GET_BUFFER_SPACE (3); + b += 3; + + laststart = 0; + begalt = b; + break; + + + case '{': + /* If \{ is a literal. */ + if (!(syntax & RE_INTERVALS) + /* If we're at `\{' and it's not the open-interval + operator. */ + || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + || (p - 2 == pattern && p == pend)) + goto normal_backslash; + + handle_interval: + { + /* If got here, then intervals must be allowed. */ + + /* For intervals, at least (most) this many matches must + be made. */ + int lower_bound = -1, upper_bound = -1; + + beg_interval = p - 1; /* The `{'. */ + following_left_brace = NULL; + + if (p == pend) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + return REG_EBRACE; + } + + GET_UNSIGNED_NUMBER (lower_bound); + + if (c == ',') + { + GET_UNSIGNED_NUMBER (upper_bound); + if (upper_bound < 0) upper_bound = RE_DUP_MAX; + } + + if (upper_bound < 0) + upper_bound = lower_bound; + + if (lower_bound < 0 || upper_bound > RE_DUP_MAX + || lower_bound > upper_bound) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + return REG_BADBR; + } + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (c != '\\') return REG_EBRACE; + + PATFETCH (c); + } + + if (c != '}') + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + return REG_BADBR; + } + + /* We just parsed a valid interval. */ + + /* If it's invalid to have no preceding re. */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + return REG_BADRPT; + else if (syntax & RE_CONTEXT_INDEP_OPS) + laststart = b; + else + goto unfetch_interval; + } + + /* If upper_bound is zero, don't want to succeed at all; + jump from laststart to b + 3, which will be the end of + the buffer after this jump is inserted. */ + if (upper_bound == 0) + { + GET_BUFFER_SPACE (3); + insert_jump (no_pop_jump, laststart, b + 3, b); + b += 3; + } + + /* Otherwise, after lower_bound number of succeeds, jump + to after the no_pop_jump_n which will be inserted at + the end of the buffer, and insert that + no_pop_jump_n. */ + else + { /* Set to 5 if only one repetition is allowed and + hence no no_pop_jump_n is inserted at the current + end of the buffer. Otherwise, need 10 bytes total + for the succeed_n and the no_pop_jump_n. */ + unsigned slots_needed = upper_bound == 1 ? 5 : 10; + + GET_BUFFER_SPACE (slots_needed); + /* Initialize the succeed_n to n, even though it will + be set by its attendant set_number_at, because + re_compile_fastmap will need to know it. Jump to + what the end of buffer will be after inserting + this succeed_n and possibly appending a + no_pop_jump_n. */ + insert_jump_n (succeed_n, laststart, b + slots_needed, + b, lower_bound); + b += 5; /* Just increment for the succeed_n here. */ + + + /* More than one repetition is allowed, so put in at + the end of the buffer a backward jump from b to the + succeed_n we put in above. By the time we've gotten + to this jump when matching, we'll have matched once + already, so jump back only upper_bound - 1 times. */ + if (upper_bound > 1) + { + store_jump_n (b, no_pop_jump_n, laststart, + upper_bound - 1); + b += 5; + + /* When hit this when matching, reset the + preceding no_pop_jump_n's n to upper_bound - 1. */ + PAT_PUSH (set_number_at); + + /* Only need to get space for the numbers. */ + GET_BUFFER_SPACE (4); + STORE_NUMBER_AND_INCR (b, -5); + STORE_NUMBER_AND_INCR (b, upper_bound - 1); + } + + /* When hit this when matching, set the succeed_n's n. */ + GET_BUFFER_SPACE (5); + insert_op_2 (set_number_at, laststart, b, 5, lower_bound); + b += 5; + } + pending_exact = 0; + beg_interval = NULL; + + if (following_left_brace) + goto normal_char; + } + break; + + unfetch_interval: + /* If an invalid interval, match the characters as literals. */ + assert (beg_interval); + p = beg_interval; + beg_interval = NULL; + + /* normal_char and normal_backslash need `c'. */ + PATFETCH (c); + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (p > pattern && p[-1] == '\\') + goto normal_backslash; + } + goto normal_char; + +#ifdef emacs + /* There is no way to specify the before_dot and after_dot + operators. rms says this is ok. --karl */ + case '=': + PAT_PUSH (at_dot); + break; + + case 's': + laststart = b; + PATFETCH (c); + PAT_PUSH_2 (syntaxspec, syntax_spec_code[c]); + break; + + case 'S': + laststart = b; + PATFETCH (c); + PAT_PUSH_2 (notsyntaxspec, syntax_spec_code[c]); + break; +#endif /* emacs */ + + + case 'w': + laststart = b; + PAT_PUSH (wordchar); + break; + + + case 'W': + laststart = b; + PAT_PUSH (notwordchar); + break; + + + case '<': + PAT_PUSH (wordbeg); + break; + + case '>': + PAT_PUSH (wordend); + break; + + case 'b': + PAT_PUSH (wordbound); + break; + + case 'B': + PAT_PUSH (notwordbound); + break; + + case '`': + PAT_PUSH (begbuf); + break; + + case '\'': + PAT_PUSH (endbuf); + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (syntax & RE_NO_BK_REFS) + goto normal_char; + + c1 = c - '0'; + + if (c1 > regnum) + { + if (syntax & RE_NO_MISSING_BK_REF) + return REG_ESUBREG; + else + goto normal_char; + } + + /* Can't back reference to a subexpression if inside of it. */ + if (group_in_compile_stack (compile_stack, c1)) + goto normal_char; + + laststart = b; + PAT_PUSH_2 (duplicate, c1); + break; + + + case '+': + case '?': + if (syntax & RE_BK_PLUS_QM) + goto handle_plus; + else + goto normal_backslash; + + default: + normal_backslash: + /* You might think it would be useful for \ to mean + not to translate; but if we don't translate it + it will never match anything. */ + c = TRANSLATE (c); + goto normal_char; + } + break; + + + default: + /* Expects the character in `c'. */ + normal_char: + /* If no exactn currently being built. */ + if (!pending_exact + + /* If last exactn not at current position. */ + || pending_exact + *pending_exact + 1 != b + + /* We have only one byte following the exactn for the count. */ + || *pending_exact == (1 << BYTEWIDTH) - 1 + + /* If followed by a repetition operator. */ + || *p == '*' || *p == '^' + || ((syntax & RE_BK_PLUS_QM) + ? *p == '\\' && (p[1] == '+' || p[1] == '?') + : (*p == '+' || *p == '?')) + || ((syntax & RE_INTERVALS) + && ((syntax & RE_NO_BK_BRACES) + ? *p == '{' + : (p[0] == '\\' && p[1] == '{')))) + { + /* Start building a new exactn. */ + + laststart = b; + + PAT_PUSH_2 (exactn, 0); + pending_exact = b - 1; + } + + PAT_PUSH (c); + (*pending_exact)++; + break; + } /* switch (c) */ + } /* while p != pend */ + + + /* Through the pattern now. */ + + if (fixup_alt_jump) + store_jump (fixup_alt_jump, jump_past_next_alt, b); + + if (!COMPILE_STACK_EMPTY) + return REG_EPAREN; + + free (compile_stack.stack); + + /* We have succeeded; set the length of the buffer. */ + bufp->used = b - bufp->buffer; + return REG_NOERROR; +} /* regex_compile */ + +/* Subroutines for regex_compile. */ + +/* Store a jump of the form <OPCODE> <relative address>. + Store in the location FROM a jump operation to jump to relative + address FROM - TO. OPCODE is the opcode to store. */ + +static void +store_jump (from, op, to) + unsigned char *from, *to; + re_opcode_t op; +{ + from[0] = (unsigned char) op; + STORE_NUMBER (from + 1, to - (from + 3)); +} + + +/* Open up space before char FROM, and insert there a jump to TO. + CURRENT_END gives the end of the storage not in use, so we know + how much data to copy up. OP is the opcode of the jump to insert. + + If you call this function, you must zero out pending_exact. */ + +static void +insert_jump (op, from, to, current_end) + re_opcode_t op; + unsigned char *from, *to, *current_end; +{ + register unsigned char *pfrom = current_end; /* Copy from here... */ + register unsigned char *pto = current_end + 3; /* ...to here. */ + + while (pfrom != from) + *--pto = *--pfrom; + + store_jump (from, op, to); +} + + +/* Store a jump of the form <opcode> <relative address> <n>. + + Store in the location FROM a jump operation to jump to relative + address FROM - TO. OPCODE is the opcode to store, N is a number the + jump uses, say, to decide how many times to jump. + + If you call this function, you must zero out pending_exact. */ + +static void +store_jump_n (from, op, to, n) + unsigned char *from, *to; + re_opcode_t op; + unsigned n; +{ + from[0] = (unsigned char) op; + STORE_NUMBER (from + 1, to - (from + 3)); + STORE_NUMBER (from + 3, n); +} + + +/* Similar to insert_jump, but handles a jump which needs an extra + number to handle minimum and maximum cases. Open up space at + location FROM, and insert there a jump to TO. CURRENT_END gives the + end of the storage in use, so we know how much data to copy up. OP is + the opcode of the jump to insert. + + If you call this function, you must zero out pending_exact. */ + +static void +insert_jump_n (op, from, to, current_end, n) + re_opcode_t op; + unsigned char *from, *to, *current_end; + unsigned n; +{ + register unsigned char *pfrom = current_end; + register unsigned char *pto = current_end + 5; + + while (pfrom != from) + *--pto = *--pfrom; + + store_jump_n (from, op, to, n); +} + + +/* Open up space at location THERE, and insert operation OP followed by + NUM_1 and NUM_2. CURRENT_END gives the end of the storage in use, so + we know how much data to copy up. + + If you call this function, you must zero out pending_exact. */ + +static void +insert_op_2 (op, there, current_end, num_1, num_2) + re_opcode_t op; + unsigned char *there, *current_end; + int num_1, num_2; +{ + register unsigned char *pfrom = current_end; + register unsigned char *pto = current_end + 5; + + while (pfrom != there) + *--pto = *--pfrom; + + there[0] = (unsigned char) op; + STORE_NUMBER (there + 1, num_1); + STORE_NUMBER (there + 3, num_2); +} + + +/* Return true if the pattern position P is at a close-group or + alternation operator, or if it is a newline and RE_NEWLINE_ORDINARY + is not set in SYNTAX. Before checking, though, we skip past all + operators that match the empty string. + + This is not quite the dual of what happens with ^. There, we can + easily check if the (sub)pattern so far can match only the empty + string, because we have seen the pattern, and `laststart' is set to + exactly that. But we cannot easily look at the pattern yet to come + to see if it matches the empty string; that would require us to compile + the pattern, then go back and analyze the pattern after every + endline. POSIX required this at one point (that $ be in a + ``trailing'' position to be considered an anchor), so we implemented + it, but it was slow and took lots of code, and we were never really + convinced it worked in all cases. So now it's gone, and we live with + the slight inconsistency between ^ and $. */ + +static boolean +at_endline_op_p (p, pend, syntax) + const char *p, *pend; + int syntax; +{ + boolean context_indep = !!(syntax & RE_CONTEXT_INDEP_ANCHORS); + + /* Skip past operators that match the empty string. (Except we don't + handle empty groups.) */ + while (p < pend) + { + if (context_indep && (*p == '^' || *p == '$')) + p++; + + /* All others start with \. */ + else if (*p == '\\' && p + 1 < pend + && (p[1] == 'b' || p[1] == 'B' + || p[1] == '<' || p[1] == '>' + || p[1] == '`' || p[1] == '\'' +#ifdef emacs + || p[1] == '=' +#endif + )) + p += 2; + + else /* Not an empty string operator. */ + break; + } + + /* See what we're at now. */ + return p < pend + && ((!(syntax & RE_NEWLINE_ORDINARY) && *p == '\n') + || (syntax & RE_NO_BK_PARENS + ? *p == ')' + : *p == '\\' && p + 1 < pend && p[1] == ')') + || (syntax & RE_NO_BK_VBAR + ? *p == '|' + : (*p == '\\' && p + 1 < pend && p[1] == '|'))); +} + + +/* Returns true if REGNUM is in one of COMPILE_STACK's elements and + false if it's not. */ + +static boolean +group_in_compile_stack (compile_stack, regnum) + compile_stack_type compile_stack; + regnum_t regnum; +{ + int this_element; + + for (this_element = compile_stack.avail - 1; + this_element >= 0; + this_element--) + if (compile_stack.stack[this_element].regnum == regnum) + return true; + + return false; +} + +/* Failure stack declarations and macros; both re_compile_fastmap and + re_match_2 use a failure stack. These have to be macros because of + REGEX_ALLOCATE. */ + + +/* Number of failure points for which to initially allocate space + when matching. If this number is exceeded, we allocate more + space---so it is not a hard limit. */ +#ifndef INIT_FAILURE_ALLOC +#define INIT_FAILURE_ALLOC 5 +#endif + +/* Roughly the maximum number of failure points on the stack. Would be + exactly that if always used MAX_FAILURE_SPACE each time we failed. + This is a variable only so users of regex can assign to it; we never + change it ourselves. */ +int re_max_failures = 2000; + +typedef const unsigned char *failure_stack_elt_t; + +typedef struct +{ + failure_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} failure_stack_type; + +#define FAILURE_STACK_EMPTY() (failure_stack.avail == 0) +#define FAILURE_STACK_PTR_EMPTY() (failure_stack_ptr->avail == 0) +#define FAILURE_STACK_FULL() (failure_stack.avail == failure_stack.size) +#define FAILURE_STACK_TOP() (failure_stack.stack[failure_stack.avail]) + + +/* Initialize FAILURE_STACK. Return 1 if success, 0 if not. */ + +#define INIT_FAILURE_STACK(failure_stack) \ + ((failure_stack).stack = (failure_stack_elt_t *) \ + REGEX_ALLOCATE (INIT_FAILURE_ALLOC * sizeof (failure_stack_elt_t)), \ + (failure_stack).stack == NULL \ + ? 0 \ + : ((failure_stack).size = INIT_FAILURE_ALLOC, \ + (failure_stack).avail = 0, \ + 1)) + + +/* Double the size of FAILURE_STACK, up to approximately + `re_max_failures' items. + + Return 1 if succeeds, and 0 if either ran out of memory + allocating space for it or it was already too large. + + REGEX_REALLOCATE requires `destination' be declared. */ + +#define DOUBLE_FAILURE_STACK(failure_stack) \ + ((failure_stack).size > re_max_failures * MAX_FAILURE_ITEMS \ + ? 0 \ + : ((failure_stack).stack = (failure_stack_elt_t *) \ + REGEX_REALLOCATE ((failure_stack).stack, \ + ((failure_stack).size << 1) * sizeof (failure_stack_elt_t)), \ + \ + (failure_stack).stack == NULL \ + ? 0 \ + : ((failure_stack).size <<= 1, \ + 1))) + + +/* Push PATTERN_OP on FAILURE_STACK. + + Return 1 if was able to do so and 0 if ran out of memory allocating + space to do so. */ +#define PUSH_PATTERN_OP(pattern_op, failure_stack) \ + ((FAILURE_STACK_FULL () \ + && !DOUBLE_FAILURE_STACK (failure_stack)) \ + ? 0 \ + : ((failure_stack).stack[(failure_stack).avail++] = pattern_op, \ + 1)) + +/* This pushes an item onto the failure stack. Must be a four-byte + value. Assumes the variable `failure_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_ITEM(item) \ + failure_stack.stack[failure_stack.avail++] = (failure_stack_elt_t) item + +/* The complement operation. Assumes stack is nonempty, and pointed to + `failure_stack_ptr'. */ +#define POP_FAILURE_ITEM() \ + failure_stack_ptr->stack[--failure_stack_ptr->avail] + +/* Used to omit pushing failure point id's when we're not debugging. */ +#ifdef DEBUG +#define DEBUG_PUSH PUSH_FAILURE_ITEM +#define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_ITEM () +#else +#define DEBUG_PUSH(item) +#define DEBUG_POP(item_addr) +#endif + + +/* Push the information about the state we will need + if we ever fail back to it. + + Requires variables failure_stack, regstart, regend, reg_info, and + num_regs be declared. DOUBLE_FAILURE_STACK requires `destination' be + declared. + + Does `return FAILURE_CODE' if runs out of memory. */ + +#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \ + do { \ + char *destination; \ + /* Must be int, so when we don't save any registers, the arithmetic \ + of 0 + -1 isn't done as unsigned. */ \ + int this_reg; \ + \ + DEBUG_STATEMENT (failure_id++); \ + DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id); \ + DEBUG_PRINT2 (" Before push, next avail: %d\n", (failure_stack).avail);\ + DEBUG_PRINT2 (" size: %d\n", (failure_stack).size);\ + \ + DEBUG_PRINT2 (" slots needed: %d\n", NUM_FAILURE_ITEMS); \ + DEBUG_PRINT2 (" available: %d\n", REMAINING_AVAIL_SLOTS); \ + \ + /* Ensure we have enough space allocated for what we will push. */ \ + while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \ + { \ + if (!DOUBLE_FAILURE_STACK (failure_stack)) \ + return failure_code; \ + \ + DEBUG_PRINT2 ("\n Doubled stack; size now: %d\n", \ + (failure_stack).size); \ + DEBUG_PRINT2 (" slots available: %d\n", REMAINING_AVAIL_SLOTS);\ + } \ + \ + /* Push the info, starting with the registers. */ \ + DEBUG_PRINT1 ("\n"); \ + \ + for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \ + this_reg++) \ + { \ + DEBUG_PRINT2 (" Pushing reg: %d\n", this_reg); \ + DEBUG_STATEMENT (num_regs_pushed++); \ + \ + DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \ + PUSH_FAILURE_ITEM (regstart[this_reg]); \ + \ + DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \ + PUSH_FAILURE_ITEM (regend[this_reg]); \ + \ + DEBUG_PRINT2 (" info: 0x%x\n ", reg_info[this_reg]); \ + DEBUG_PRINT2 (" match_nothing=%d", \ + REG_MATCH_NULL_STRING_P (reg_info[this_reg])); \ + DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg])); \ + DEBUG_PRINT2 (" matched_something=%d", \ + MATCHED_SOMETHING (reg_info[this_reg])); \ + DEBUG_PRINT2 (" ever_matched=%d", \ + EVER_MATCHED_SOMETHING (reg_info[this_reg])); \ + DEBUG_PRINT1 ("\n"); \ + PUSH_FAILURE_ITEM (reg_info[this_reg].word); \ + } \ + \ + DEBUG_PRINT2 (" Pushing low active reg: %d\n", lowest_active_reg); \ + PUSH_FAILURE_ITEM (lowest_active_reg); \ + \ + DEBUG_PRINT2 (" Pushing high active reg: %d\n", highest_active_reg);\ + PUSH_FAILURE_ITEM (highest_active_reg); \ + \ + DEBUG_PRINT2 (" Pushing pattern 0x%x: ", pattern_place); \ + DEBUG_COMPILED_PATTERN_PRINTER (bufp, pattern_place, pend); \ + PUSH_FAILURE_ITEM (pattern_place); \ + \ + DEBUG_PRINT2 (" Pushing string 0x%x: `", string_place); \ + DEBUG_DOUBLE_STRING_PRINTER (string_place, string1, size1, string2, \ + size2); \ + DEBUG_PRINT1 ("'\n"); \ + PUSH_FAILURE_ITEM (string_place); \ + \ + DEBUG_PRINT2 (" Pushing failure id: %u\n", failure_id); \ + DEBUG_PUSH (failure_id); \ + } while (0) + +/* This is the number of items that are pushed and popped on the stack + for each register. */ +#define NUM_REG_ITEMS 3 + +/* Individual items aside from the registers. */ +#ifdef DEBUG +#define NUM_NONREG_ITEMS 5 /* Includes failure point id. */ +#else +#define NUM_NONREG_ITEMS 4 +#endif + +/* We push at most this many items on the stack. */ +#define MAX_FAILURE_ITEMS \ + ((num_regs - 1) * NUM_REG_ITEMS + NUM_NONREG_ITEMS) + +/* We actually push this many items. */ +#define NUM_FAILURE_ITEMS \ + ((highest_active_reg - lowest_active_reg + 1) * NUM_REG_ITEMS \ + + NUM_NONREG_ITEMS) + +/* How many items can still be added to the stack without overflowing it. */ +#define REMAINING_AVAIL_SLOTS \ + ((failure_stack).size - (failure_stack).avail) + +/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in + BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible + characters can start a string that matches the pattern. This fastmap + is used by re_search to skip quickly over impossible starting points. + + The caller must supply the address of a (1 << BYTEWIDTH)-byte data + area as BUFP->fastmap. The other components of BUFP describe the + pattern to be used. + + We set the `can_be_null' and `fastmap_accurate' fields in the pattern + + Returns 0 if it can compile a fastmap. Returns -2 if there is an + internal error. */ + +int +re_compile_fastmap (bufp) + struct re_pattern_buffer *bufp; +{ + int j, k; + failure_stack_type failure_stack; +#ifndef REGEX_MALLOC + char *destination; +#endif + /* We don't push any register information onto the failure stack. */ + unsigned num_regs = 0; + + register char *fastmap = bufp->fastmap; + unsigned char *pattern = bufp->buffer; + unsigned long size = bufp->used; + const unsigned char *p = pattern; + register unsigned char *pend = pattern + size; + + INIT_FAILURE_STACK (failure_stack); + + bzero (fastmap, 1 << BYTEWIDTH); + bufp->fastmap_accurate = 1; /* It will be when we're done. */ + bufp->can_be_null = 0; + + while (p) + { + boolean is_a_succeed_n = false; + + if (p == pend) + if (FAILURE_STACK_EMPTY ()) + { + bufp->can_be_null = 1; + break; + } + else + p = failure_stack.stack[--failure_stack.avail]; + +#ifdef SWITCH_ENUM_BUG + switch ((int) ((re_opcode_t) *p++)) +#else + switch ((re_opcode_t) *p++) +#endif + { + case exactn: + fastmap[p[1]] = 1; + break; + + + case charset: + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))) + fastmap[j] = 1; + break; + + + case charset_not: + /* Chars beyond end of map must be allowed. */ + for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++) + fastmap[j] = 1; + + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))) + fastmap[j] = 1; + break; + + + case no_op: + case begline: + case begbuf: + case endbuf: + case wordbound: + case notwordbound: + case wordbeg: + case wordend: + continue; + + + case endline: + if (!bufp->can_be_null) + bufp->can_be_null = 2; + break; + + + case no_pop_jump_n: + case pop_failure_jump: + case maybe_pop_jump: + case no_pop_jump: + case jump_past_next_alt: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + if (j > 0) + continue; + + /* Jump backward reached implies we just went through + the body of a loop and matched nothing. Opcode jumped to + should be an on_failure_jump or succeed_n. Just treat it + like an ordinary jump. For a * loop, it has pushed its + failure point already; if so, discard that as redundant. */ + + if ((re_opcode_t) *p != on_failure_jump + && (re_opcode_t) *p != succeed_n) + continue; + + p++; + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + + /* If what's on the stack is where we are now, pop it. */ + if (!FAILURE_STACK_EMPTY () + && failure_stack.stack[failure_stack.avail - 1] == p) + failure_stack.avail--; + + continue; + + + case on_failure_jump: + handle_on_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + + /* For some patterns, e.g., `(a?)?', `p+j' here points to the + end of the pattern. We don't want to push such a point, + since when we restore it above, entering the switch will + increment `p' past the end of the pattern. We don't need + to push such a point since there can't be any more + possibilities for the fastmap beyond pend. */ + if (p + j < pend) + { + if (!PUSH_PATTERN_OP (p + j, failure_stack)) + return -2; + } + + if (is_a_succeed_n) + EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */ + + continue; + + + case succeed_n: + is_a_succeed_n = true; + + /* Get to the number of times to succeed. */ + p += 2; + + /* Increment p past the n for when k != 0. */ + EXTRACT_NUMBER_AND_INCR (k, p); + if (k == 0) + { + p -= 4; + goto handle_on_failure_jump; + } + continue; + + + case set_number_at: + p += 4; + continue; + + + case start_memory: + case stop_memory: + p += 2; + continue; + + + /* I don't understand this case (any of it). --karl */ + case duplicate: + bufp->can_be_null = 1; + fastmap['\n'] = 1; + + + case anychar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (j != '\n') + fastmap[j] = 1; + if (bufp->can_be_null) + return 0; + + /* Don't return; check the alternative paths + so we can set can_be_null if appropriate. */ + break; + + + case wordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == Sword) + fastmap[j] = 1; + break; + + + case notwordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != Sword) + fastmap[j] = 1; + break; + + +#ifdef emacs + case before_dot: + case at_dot: + case after_dot: + continue; + + + case syntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == (enum syntaxcode) k) + fastmap[j] = 1; + break; + + + case notsyntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != (enum syntaxcode) k) + fastmap[j] = 1; + break; +#endif /* not emacs */ + + default: + abort (); + } /* switch *p++ */ + + /* Getting here means we have successfully found the possible starting + characters of one path of the pattern. We need not follow this + path any farther. Instead, look at the next alternative + remembered in the stack, or quit. The test at the top of the + loop does these things. */ + p = pend; + } /* while p */ + + return 0; +} /* re_compile_fastmap */ + +/* Searching routines. */ + +/* Like re_search_2, below, but only one string is specified, and + doesn't let you say where to stop matching. */ + +int +re_search (bufp, string, size, startpos, range, regs) + struct re_pattern_buffer *bufp; + const char *string; + int size, startpos, range; + struct re_registers *regs; +{ + return re_search_2 (bufp, NULL, 0, string, size, startpos, range, + regs, size); +} + + +/* Using the compiled pattern in BUFP->buffer, first tries to match the + virtual concatenation of STRING1 and STRING2, starting first at index + STARTPOS, then at STARTPOS + 1, and so on. + + STRING1 and STRING2 have length SIZE1 and SIZE2, respectively. + + RANGE is how far to scan while trying to match. RANGE = 0 means try + only at STARTPOS; in general, the last start tried is STARTPOS + + RANGE. + + In REGS, return the indices of the virtual concatenation of STRING1 + and STRING2 that matched the entire BUFP->buffer and its contained + subexpressions. + + Do not consider matching one past the index STOP in the virtual + concatenation of STRING1 and STRING2. + + We return either the position in the strings at which the match was + found, -1 if no match, or -2 if error (such as failure + stack overflow). */ + +int +re_search_2 (bufp, string1, size1, string2, size2, startpos, range, + regs, stop) + struct re_pattern_buffer *bufp; + const char *string1, *string2; + int size1, size2; + int startpos; + int range; + struct re_registers *regs; + int stop; +{ + int val; + register char *fastmap = bufp->fastmap; + register char *translate = bufp->translate; + int total_size = size1 + size2; + int endpos = startpos + range; + + /* Check for out-of-range STARTPOS. */ + if (startpos < 0 || startpos > total_size) + return -1; + + /* Fix up RANGE if it might eventually take us outside + the virtual concatenation of STRING1 and STRING2. */ + if (endpos < -1) + range = -1 - startpos; + else if (endpos > total_size) + range = total_size - startpos; + + /* Update the fastmap now if not correct already. */ + if (fastmap && !bufp->fastmap_accurate) + if (re_compile_fastmap (bufp) == -2) + return -2; + + /* If the search isn't to be a backwards one, don't waste time in a + long search for a pattern that says it is anchored. */ + if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == begbuf + && range > 0) + { + if (startpos > 0) + return -1; + else + range = 1; + } + + for (;;) + { + /* If a fastmap is supplied, skip quickly over characters that + cannot be the start of a match. If the pattern can match the + null string, however, we don't want to skip over characters; we + want the first null string. */ + if (fastmap && startpos < total_size && !bufp->can_be_null) + { + if (range > 0) /* Searching forwards. */ + { + register const char *d; + register int lim = 0; + int irange = range; + + if (startpos < size1 && startpos + range >= size1) + lim = range - (size1 - startpos); + + d = (startpos >= size1 ? string2 - size1 : string1) + startpos; + + /* Written out as an if-else to avoid testing `translate' + inside the loop. */ + if (translate) + { + while (range > lim + && !fastmap[(unsigned char) translate[*d++]]) + range--; + } + else + { + while (range > lim && !fastmap[(unsigned char) *d++]) + range--; + } + + startpos += irange - range; + } + else /* Searching backwards. */ + { + register char c + = (size1 == 0 || startpos >= size1 + ? string2[startpos - size1] + : string1[startpos]); + + if (translate + ? !fastmap[(unsigned char) translate[(unsigned char) c]] + : !fastmap[(unsigned char) c]) + goto advance; + } + } + + /* If can't match the null string, and that's all we have left, fail. */ + if (range >= 0 && startpos == total_size + && fastmap && bufp->can_be_null == 0) + return -1; + + val = re_match_2 (bufp, string1, size1, string2, size2, + startpos, regs, stop); + if (val >= 0) + return startpos; + + if (val == -2) + return -2; + + advance: + if (!range) + break; + else if (range > 0) + { + range--; + startpos++; + } + else + { + range++; + startpos--; + } + } + return -1; +} /* re_search_2 */ + +/* Declarations and macros for re_match_2. */ + +static int bcmp_translate (); +static boolean alt_match_null_string_p (), + common_op_match_null_string_p (), + group_match_null_string_p (); +static void pop_failure_point (); + + +/* Structure for per-register (a.k.a. per-group) information. + This must not be longer than one word, because we push this value + onto the failure stack. Other register information, such as the + starting and ending positions (which are addresses), and the list of + inner groups (which is a bits list) are maintained in separate + variables. + + We are making a (strictly speaking) nonportable assumption here: that + the compiler will pack our bit fields into something that fits into + the type of `word', i.e., is something that fits into one item on the + failure stack. */ +typedef union +{ + failure_stack_elt_t word; + struct + { + /* This field is one if this group can match the empty string, + zero if not. If not yet determined, `MATCH_NOTHING_UNSET_VALUE'. */ +#define MATCH_NOTHING_UNSET_VALUE 3 + unsigned match_null_string_p : 2; + unsigned is_active : 1; + unsigned matched_something : 1; + unsigned ever_matched_something : 1; + } bits; +} register_info_type; + +#define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p) +#define IS_ACTIVE(R) ((R).bits.is_active) +#define MATCHED_SOMETHING(R) ((R).bits.matched_something) +#define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something) + + +/* Call this when have matched something; it sets `matched' flags for the + registers corresponding to the group of which we currently are inside. + Also records whether this group ever matched something. We only care + about this information at `stop_memory', and then only about the + previous time through the loop (if the group is starred or whatever). + So it is ok to clear all the nonactive registers here. */ +#define SET_REGS_MATCHED() \ + do \ + { \ + unsigned r; \ + for (r = lowest_active_reg; r <= highest_active_reg; r++) \ + { \ + MATCHED_SOMETHING (reg_info[r]) \ + = EVER_MATCHED_SOMETHING (reg_info[r]) \ + = 1; \ + } \ + } \ + while (0) + + +/* This converts a pointer into one or the other of the strings into an + offset from the beginning of that string. */ +#define POINTER_TO_OFFSET(pointer) IS_IN_FIRST_STRING (pointer) \ + ? (pointer) - string1 \ + : (pointer) - string2 + size1 + +/* Registers are set to a sentinel value when they haven't yet matched + anything. */ +#define REG_UNSET_VALUE ((char *) -1) +#define REG_UNSET(e) ((e) == REG_UNSET_VALUE) + + +/* Macros for dealing with the split strings in re_match_2. */ + +#define MATCHING_IN_FIRST_STRING (dend == end_match_1) + +/* Call before fetching a character with *d. This switches over to + string2 if necessary. */ +#define PREFETCH \ + while (d == dend) \ + { \ + /* End of string2 => fail. */ \ + if (dend == end_match_2) \ + goto fail; \ + /* End of string1 => advance to string2. */ \ + d = string2; \ + dend = end_match_2; \ + } + + +/* Test if at very beginning or at very end of the virtual concatenation + of string1 and string2. If there is only one string, we've put it in + string2. */ +#define AT_STRINGS_BEG (d == (size1 ? string1 : string2) || !size2) +#define AT_STRINGS_END (d == end2) + + +/* Test if D points to a character which is word-constituent. We have + two special cases to check for: if past the end of string1, look at + the first character in string2; and if before the beginning of + string2, look at the last character in string1. + + We assume there is a string1, so use this in conjunction with + AT_STRINGS_BEG. */ +#define LETTER_P(d) \ + (SYNTAX ((d) == end1 ? *string2 : (d) == string2 - 1 ? *(end1 - 1) : *(d))\ + == Sword) + +/* Test if the character before D and the one at D differ with respect + to being word-constituent. */ +#define AT_WORD_BOUNDARY(d) \ + (AT_STRINGS_BEG || AT_STRINGS_END || LETTER_P (d - 1) != LETTER_P (d)) + + +/* Free everything we malloc. */ +#ifdef REGEX_MALLOC +#define FREE_VARIABLES() \ + do { \ + free (failure_stack.stack); \ + free (regstart); \ + free (regend); \ + free (old_regstart); \ + free (old_regend); \ + free (reg_info); \ + free (best_regstart); \ + free (best_regend); \ + reg_info = NULL; \ + failure_stack.stack = NULL; \ + regstart = regend = old_regstart = old_regend \ + = best_regstart = best_regend = NULL; \ + } while (0) +#else /* not REGEX_MALLOC */ +#define FREE_VARIABLES() /* As nothing, since we use alloca. */ +#endif /* not REGEX_MALLOC */ + + +/* These values must meet several constraints. They must not be valid + register values; since we have a limit of 255 registers (because + we use only one byte in the pattern for the register number), we can + use numbers larger than 255. They must differ by 1, because of + NUM_FAILURE_ITEMS above. And the value for the lowest register must + be larger than the value for the highest register, so we do not try + to actually save any registers when none are active. */ +#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH) +#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1) + +/* Matching routines. */ + +#ifndef emacs /* Emacs never uses this. */ + +/* re_match is like re_match_2 except it takes only a single string. */ + +int +re_match (bufp, string, size, pos, regs) + const struct re_pattern_buffer *bufp; + const char *string; + int size, pos; + struct re_registers *regs; + { + return re_match_2 (bufp, NULL, 0, string, size, pos, regs, size); +} +#endif /* not emacs */ + + +/* re_match_2 matches the compiled pattern in BUFP against the + the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1 + and SIZE2, respectively). We start matching at POS, and stop + matching at STOP. + + If REGS is non-null and the `no_sub' field of BUFP is nonzero, we + store offsets for the substring each group matched in REGS. (If + BUFP->caller_allocated_regs is nonzero, we fill REGS->num_regs + registers; if zero, we set REGS->num_regs to max (RE_NREGS, + re_nsub+1) and allocate the space with malloc before filling.) + + We return -1 if no match, -2 if an internal error (such as the + failure stack overflowing). Otherwise, we return the length of the + matched substring. */ + +int +re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) + const struct re_pattern_buffer *bufp; + const char *string1, *string2; + int size1, size2; + int pos; + struct re_registers *regs; + int stop; +{ + /* General temporaries. */ + int mcnt; + unsigned char *p1; + + /* Just past the end of the corresponding string. */ + const char *end1, *end2; + + /* Pointers into string1 and string2, just past the last characters in + each to consider matching. */ + const char *end_match_1, *end_match_2; + + /* Where we are in the data, and the end of the current string. */ + const char *d, *dend; + + /* Where we are in the pattern, and the end of the pattern. */ + unsigned char *p = bufp->buffer; + register unsigned char *pend = p + bufp->used; + + /* We use this to map every character in the string. */ + char *translate = bufp->translate; + + /* Failure point stack. Each place that can handle a failure further + down the line pushes a failure point on this stack. It consists of + restart, regend, and reg_info for all registers corresponding to the + subexpressions we're currently inside, plus the number of such + registers, and, finally, two char *'s. The first char * is where to + resume scanning the pattern; the second one is where to resume + scanning the strings. If the latter is zero, the failure point is a + ``dummy''; if a failure happens and the failure point is a dummy, it + gets discarded and the next next one is tried. */ + failure_stack_type failure_stack; +#ifdef DEBUG + static unsigned failure_id = 0; +#endif + + /* We fill all the registers internally, independent of what we + return, for use in backreferences. The number here includes + register zero. */ + unsigned num_regs = bufp->re_nsub + 1; + + /* The currently active registers. */ + unsigned lowest_active_reg = NO_LOWEST_ACTIVE_REG; + unsigned highest_active_reg = NO_HIGHEST_ACTIVE_REG; + + /* Information on the contents of registers. These are pointers into + the input strings; they record just what was matched (on this + attempt) by a subexpression part of the pattern, that is, the + regnum-th regstart pointer points to where in the pattern we began + matching and the regnum-th regend points to right after where we + stopped matching the regnum-th subexpression. (The zeroth register + keeps track of what the whole pattern matches.) */ + const char **regstart + = (const char **) REGEX_ALLOCATE (num_regs * sizeof (char *)); + const char **regend + = (const char **) REGEX_ALLOCATE (num_regs * sizeof (char *)); + + /* If a group that's operated upon by a repetition operator fails to + match anything, then the register for its start will need to be + restored because it will have been set to wherever in the string we + are when we last see its open-group operator. Similarly for a + register's end. */ + const char **old_regstart + = (const char **) REGEX_ALLOCATE (num_regs * sizeof (char *)); + const char **old_regend + = (const char **) REGEX_ALLOCATE (num_regs * sizeof (char *)); + + /* The is_active field of reg_info helps us keep track of which (possibly + nested) subexpressions we are currently in. The matched_something + field of reg_info[reg_num] helps us tell whether or not we have + matched any of the pattern so far this time through the reg_num-th + subexpression. These two fields get reset each time through any + loop their register is in. */ + register_info_type *reg_info = (register_info_type *) + REGEX_ALLOCATE (num_regs * sizeof (register_info_type)); + + /* The following record the register info as found in the above + variables when we find a match better than any we've seen before. + This happens as we backtrack through the failure points, which in + turn happens only if we have not yet matched the entire string. */ + unsigned best_regs_set = 0; + const char **best_regstart + = (const char **) REGEX_ALLOCATE (num_regs * sizeof (char *)); + const char **best_regend + = (const char **) REGEX_ALLOCATE (num_regs * sizeof (char *)); + + /* Used when we pop values we don't care about. */ + const char **reg_dummy + = (const char **) REGEX_ALLOCATE (num_regs * sizeof (char *)); + register_info_type *reg_info_dummy = (register_info_type *) + REGEX_ALLOCATE (num_regs * sizeof (register_info_type)); + +#ifdef DEBUG + /* Counts the total number of registers pushed. */ + unsigned num_regs_pushed = 0; +#endif + + DEBUG_PRINT1 ("\n\nEntering re_match_2.\n"); + + if (!INIT_FAILURE_STACK (failure_stack)) + return -2; + + if (!(regstart && regend && old_regstart && old_regend && reg_info + && best_regstart && best_regend)) + { + FREE_VARIABLES (); + return -2; + } + + /* The starting position is bogus. */ + if (pos < 0 || pos > size1 + size2) + { + FREE_VARIABLES (); + return -1; + } + + + /* Initialize subexpression text positions to -1 to mark ones that no + \( or ( and \) or ) has been seen for. Also set all registers to + inactive and mark them as not having any inner groups, able to + match the empty string, matched anything so far, or ever failed. */ + for (mcnt = 0; mcnt < num_regs; mcnt++) + { + regstart[mcnt] = regend[mcnt] + = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE; + + REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NOTHING_UNSET_VALUE; + IS_ACTIVE (reg_info[mcnt]) = 0; + MATCHED_SOMETHING (reg_info[mcnt]) = 0; + EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0; + } + + IS_ACTIVE (reg_info[0]) = 1; + + /* We move string1 into string2 if the latter's empty---but not if + string1 is null. */ + if (size2 == 0 && string1 != NULL) + { + string2 = string1; + size2 = size1; + string1 = 0; + size1 = 0; + } + end1 = string1 + size1; + end2 = string2 + size2; + + /* Compute where to stop matching, within the two strings. */ + if (stop <= size1) + { + end_match_1 = string1 + stop; + end_match_2 = string2; + } + else + { + end_match_1 = end1; + end_match_2 = string2 + stop - size1; + } + + /* `p' scans through the pattern as `d' scans through the data. `dend' + is the end of the input string that `d' points within. `d' is + advanced into the following input string whenever necessary, but + this happens before fetching; therefore, at the beginning of the + loop, `d' can be pointing at the end of a string, but it cannot + equal `string2'. */ + if (size1 > 0 && pos <= size1) + { + d = string1 + pos; + dend = end_match_1; + } + else + { + d = string2 + pos - size1; + dend = end_match_2; + } + + DEBUG_PRINT1 ("The compiled pattern is: "); + DEBUG_COMPILED_PATTERN_PRINTER (bufp, p, pend); + DEBUG_PRINT1 ("The string to match is: `"); + DEBUG_DOUBLE_STRING_PRINTER (d, string1, size1, string2, size2); + DEBUG_PRINT1 ("'\n"); + + /* This loops over pattern commands. It exits by returning from the + function if the match is complete, or it drops through if the match + fails at this starting point in the input data. */ + for (;;) + { + DEBUG_PRINT2 ("\n0x%x: ", p); + + if (p == pend) + { /* End of pattern means we might have succeeded. */ + DEBUG_PRINT1 ("End of pattern: "); + /* If not end of string, try backtracking. Otherwise done. */ + if (d != end_match_2) + { + DEBUG_PRINT1 ("backtracking.\n"); + + if (!FAILURE_STACK_EMPTY ()) + { /* More failure points to try. */ + + boolean in_same_string = + IS_IN_FIRST_STRING (best_regend[0]) + == MATCHING_IN_FIRST_STRING; + + /* If exceeds best match so far, save it. */ + if (!best_regs_set + || (in_same_string && d > best_regend[0]) + || (!in_same_string && !MATCHING_IN_FIRST_STRING)) + { + best_regs_set = 1; + best_regend[0] = d; /* Never use regstart[0]. */ + + for (mcnt = 1; mcnt < num_regs; mcnt++) + { + best_regstart[mcnt] = regstart[mcnt]; + best_regend[mcnt] = regend[mcnt]; + } + } + goto fail; + } + + /* If no failure points, don't restore garbage. */ + else if (best_regs_set) + { + restore_best_regs: + /* Restore best match. */ + d = best_regend[0]; + + if (d >= string1 && d <= end1) + dend = end_match_1; + + for (mcnt = 0; mcnt < num_regs; mcnt++) + { + regstart[mcnt] = best_regstart[mcnt]; + regend[mcnt] = best_regend[mcnt]; + } + } + } /* d != end_match_2 */ + + DEBUG_PRINT1 ("accepting match.\n"); + + /* If caller wants register contents data back, do it. */ + if (regs && !bufp->no_sub) + { + /* If they haven't allocated it, we'll do it. */ + if (!bufp->caller_allocated_regs) + { + regs->num_regs = MAX (RE_NREGS, num_regs + 1); + regs->start = TALLOC (regs->num_regs, regoff_t); + regs->end = TALLOC (regs->num_regs, regoff_t); + if (regs->start == NULL || regs->end == NULL) + return -2; + } + + /* Convert the pointer data in `regstart' and `regend' to + indices. Register zero has to be set differently, + since we haven't kept track of any info for it. */ + if (regs->num_regs > 0) + { + regs->start[0] = pos; + regs->end[0] = MATCHING_IN_FIRST_STRING + ? d - string1 + : d - string2 + size1; + } + + /* Go through the first min (num_regs, regs->num_regs) + registers, since that is all we initialized at the + beginning. */ + for (mcnt = 1; mcnt < MIN (num_regs, regs->num_regs); mcnt++) + { + if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt])) + regs->start[mcnt] = regs->end[mcnt] = -1; + else + { + regs->start[mcnt] = POINTER_TO_OFFSET (regstart[mcnt]); + regs->end[mcnt] = POINTER_TO_OFFSET (regend[mcnt]); + } + } + + /* If the regs structure we return has more elements than + it than were in the pattern, set the extra elements to + -1. If we allocated the registers, this is the case, + because we always allocate enough to have at least -1 + at the end. */ + for (mcnt = num_regs; mcnt < regs->num_regs; mcnt++) + regs->start[mcnt] = regs->end[mcnt] = -1; + } /* regs && !bufp->no_sub */ + + FREE_VARIABLES (); + DEBUG_PRINT2 ("%d registers pushed.\n", num_regs_pushed); + + mcnt = d - pos - (MATCHING_IN_FIRST_STRING + ? string1 + : string2 - size1); + + DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt); + + return mcnt; + } + + /* Otherwise match next pattern command. */ +#ifdef SWITCH_ENUM_BUG + switch ((int) ((re_opcode_t) *p++)) +#else + switch ((re_opcode_t) *p++) +#endif + { + /* Ignore these. Used to ignore the n of succeed_n's which + currently have n == 0. */ + case no_op: + DEBUG_PRINT1 ("EXECUTING no_op.\n"); + break; + + + /* Match the next n pattern characters exactly. The following + byte in the pattern defines n, and the n bytes after that + are the characters to match. */ + case exactn: + mcnt = *p++; + DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt); + + /* This is written out as an if-else so we don't waste time + testing `translate' inside the loop. */ + if (translate) + { + do + { + PREFETCH; + if (translate[(unsigned char) *d++] != (char) *p++) + goto fail; + } + while (--mcnt); + } + else + { + do + { + PREFETCH; + if (*d++ != (char) *p++) goto fail; + } + while (--mcnt); + } + SET_REGS_MATCHED (); + break; + + + /* Match anything but possibly a newline or a null. */ + case anychar: + DEBUG_PRINT1 ("EXECUTING anychar.\n"); + + PREFETCH; + + if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n') + || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000')) + goto fail; + + SET_REGS_MATCHED (); + d++; + break; + + + case charset: + case charset_not: + { + register unsigned char c; + boolean not = (re_opcode_t) *(p - 1) == charset_not; + + DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : ""); + + PREFETCH; + c = TRANSLATE (*d); /* The character to match. */ + + if (c < (unsigned char) (*p * BYTEWIDTH) + && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + + p += 1 + *p; + + if (!not) goto fail; + + SET_REGS_MATCHED (); + d++; + break; + } + + + /* The beginning of a group is represented by start_memory. + The arguments are the register number in the next byte, and the + number of groups inner to this one in the next. The text + matched within the group is recorded (in the internal + registers data structure) under the register number. */ + case start_memory: + DEBUG_PRINT3 ("EXECUTING start_memory %d (%d):\n", *p, p[1]); + + /* Find out if this group can match the empty string. */ + p1 = p; /* To send to group_match_null_string_p. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[*p]) + == MATCH_NOTHING_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[*p]) + = group_match_null_string_p (&p1, pend, reg_info); + + /* Save the position in the string where we were the last time + we were at this open-group operator in case the group is + operated upon by a repetition operator, e.g., with `(a*)*b' + against `ab'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regstart[*p]) ? d : regstart[*p] + : regstart[*p]; + DEBUG_PRINT2 (" old_regstart: %d\n", + POINTER_TO_OFFSET (old_regstart[*p])); + + regstart[*p] = d; + DEBUG_PRINT2 (" regstart: %d\n", POINTER_TO_OFFSET (regstart[*p])); + + IS_ACTIVE (reg_info[*p]) = 1; + MATCHED_SOMETHING (reg_info[*p]) = 0; + + /* This is the new highest active register. */ + highest_active_reg = *p; + + /* If nothing was active before, this is the new lowest active + register. */ + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *p; + + /* Move past the register number and inner group count. */ + p += 2; + break; + + + /* The stop_memory opcode represents the end of a group. Its + arguments are the same as start_memory's: the register + number, and the number of inner groups. */ + case stop_memory: + DEBUG_PRINT3 ("EXECUTING stop_memory %d (%d):\n", *p, p[1]); + + /* We need to save the string position the last time we were at + this close-group operator in case the group is operated + upon by a repetition operator, e.g., with `((a*)*(b*)*)*' + against `aba'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regend[*p]) ? d : regend[*p] + : regend[*p]; + DEBUG_PRINT2 (" old_regend: %d\n", + POINTER_TO_OFFSET (old_regend[*p])); + + regend[*p] = d; + DEBUG_PRINT2 (" regend: %d\n", POINTER_TO_OFFSET (regend[*p])); + + /* This register isn't active anymore. */ + IS_ACTIVE (reg_info[*p]) = 0; + + /* If this was the only register active, nothing is active + anymore. */ + if (lowest_active_reg == highest_active_reg) + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + else + { /* We must scan for the new highest active register, since + it isn't necessarily one less than now: consider + (a(b)c(d(e)f)g). When group 3 ends, after the f), the + new highest active register is 1. */ + unsigned char r = *p - 1; + + /* This loop will always terminate, because register 0 is + always active. */ + assert (IS_ACTIVE (reg_info[0])); + while (!IS_ACTIVE (reg_info[r])) + r--; + + /* If we end up at register zero, that means that we saved + the registers as the result of an on_failure_jump, not + a start_memory, and we jumped to past the innermost + stop_memory. For example, in ((.)*). We save + registers 1 and 2 as a result of the *, but when we pop + back to the second ), we are at the stop_memory 1. + Thus, nothing is active. */ + if (r != 0) + highest_active_reg = r; + else + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + } + + /* If just failed to match something this time around with a + group that's operated on by a repetition operator, try to + force exit from the ``loop,'' and restore the register + information for this group that we had before trying this + last match. */ + if ((!MATCHED_SOMETHING (reg_info[*p]) + || (re_opcode_t) p[-3] == start_memory) + && (p + 2) < pend) + { + boolean is_a_jump_n = false; + + p1 = p + 2; + mcnt = 0; + switch ((re_opcode_t) *p1++) + { + case no_pop_jump_n: + is_a_jump_n = true; + case pop_failure_jump: + case maybe_pop_jump: + case no_pop_jump: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (is_a_jump_n) + p1 += 2; + break; + + default: + /* do nothing */ ; + } + p1 += mcnt; + + /* If the next operation is a jump backwards in the pattern + to an on_failure_jump right before the start_memory + corresponding to this stop_memory, exit from the loop + by forcing a failure after pushing on the stack the + on_failure_jump's jump in the pattern, and d. */ + if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump + && (re_opcode_t) p1[3] == start_memory && p1[4] == *p) + { + /* If this group ever matched anything, then restore + what its registers were before trying this last + failed match, e.g., with `(a*)*b' against `ab' for + regstart[1], and, e.g., with `((a*)*(b*)*)*' + against `aba' for regend[3]. + + Also restore the registers for inner groups for, + e.g., `((a*)(b*))*' against `aba' (register 3 would + otherwise get trashed). */ + + if (EVER_MATCHED_SOMETHING (reg_info[*p])) + { + unsigned r; + + EVER_MATCHED_SOMETHING (reg_info[*p]) = 0; + + /* Restore this and inner groups' (if any) registers. */ + for (r = *p; r < *p + *(p + 1); r++) + { + regstart[r] = old_regstart[r]; + + /* xx why this test? */ + if ((int) old_regend[r] >= (int) regstart[r]) + regend[r] = old_regend[r]; + } + } + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + PUSH_FAILURE_POINT (p1 + mcnt, d, -2); + + goto fail; + } + } + + /* Move past the register number and the inner group count. */ + p += 2; + break; + + + /* \<digit> has been turned into a `duplicate' command which is + followed by the numeric value of <digit> as the register number. */ + case duplicate: + { + register const char *d2, *dend2; + int regno = *p++; /* Get which register to match against. */ + DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno); + + /* Can't back reference a group which we've never matched. */ + if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno])) + goto fail; + + /* Where in input to try to start matching. */ + d2 = regstart[regno]; + + /* Where to stop matching; if both the place to start and + the place to stop matching are in the same string, then + set to the place to stop, otherwise, for now have to use + the end of the first string. */ + + dend2 = ((IS_IN_FIRST_STRING (regstart[regno]) + == IS_IN_FIRST_STRING (regend[regno])) + ? regend[regno] : end_match_1); + for (;;) + { + /* If necessary, advance to next segment in register + contents. */ + while (d2 == dend2) + { + if (dend2 == end_match_2) break; + if (dend2 == regend[regno]) break; + + /* End of string1 => advance to string2. */ + d2 = string2; + dend2 = regend[regno]; + } + /* At end of register contents => success */ + if (d2 == dend2) break; + + /* If necessary, advance to next segment in data. */ + PREFETCH; + + /* How many characters left in this segment to match. */ + mcnt = dend - d; + + /* Want how many consecutive characters we can match in + one shot, so, if necessary, adjust the count. */ + if (mcnt > dend2 - d2) + mcnt = dend2 - d2; + + /* Compare that many; failure if mismatch, else move + past them. */ + if (translate + ? bcmp_translate (d, d2, mcnt, translate) + : bcmp (d, d2, mcnt)) + goto fail; + d += mcnt, d2 += mcnt; + } + } + break; + + + /* begline matches the empty string at the beginning of the string + (unless `not_bol' is set in `bufp'), and, if + `newline_anchor' is set, after newlines. */ + case begline: + DEBUG_PRINT1 ("EXECUTING begline.\n"); + + if (AT_STRINGS_BEG) + { + if (!bufp->not_bol) break; + } + else if (d[-1] == '\n' && bufp->newline_anchor) + { + break; + } + /* In all other cases, we fail. */ + goto fail; + + + /* endline is the dual of begline. */ + case endline: + DEBUG_PRINT1 ("EXECUTING endline.\n"); + + if (AT_STRINGS_END) + { + if (!bufp->not_eol) break; + } + + /* We have to ``prefetch'' the next character. */ + else if ((d == end1 ? *string2 : *d) == '\n' + && bufp->newline_anchor) + { + break; + } + goto fail; + + + /* Match at the very beginning of the data. */ + case begbuf: + DEBUG_PRINT1 ("EXECUTING begbuf.\n"); + if (AT_STRINGS_BEG) + break; + goto fail; + + + /* Match at the very end of the data. */ + case endbuf: + DEBUG_PRINT1 ("EXECUTING endbuf.\n"); + if (AT_STRINGS_END) + break; + goto fail; + + + /* on_failure_keep_string_jump is used to optimize `.*\n'. It + pushes NULL as the value for the string on the stack. Then + pop_failure_point will keep the current value for the string, + instead of restoring it. To see why, consider matching + `foo\nbar' against `.*\n'. The .* matches the foo; then the + . fails against the \n. But the next thing we want to do is + match the \n against the \n; if we restored the string value, + we would be back at the foo. + + Because this is used only in specific cases, we don't need to + go through the hassle of checking all the things that + on_failure_jump does, to make sure the right things get saved + on the stack. Hence we don't share its code. The only + reason to push anything on the stack at all is that otherwise + we would have to change anychar's code to do something + besides goto fail in this case; that seems worse than this. */ + case on_failure_keep_string_jump: + DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt); + + PUSH_FAILURE_POINT (p + mcnt, NULL, -2); + break; + + + /* Uses of on_failure_jump: + + Each alternative starts with an on_failure_jump that points + to the beginning of the next alternative. Each alternative + except the last ends with a jump that in effect jumps past + the rest of the alternatives. (They really jump to the + ending jump of the following alternative, because tensioning + these jumps is a hassle.) + + Repeats start with an on_failure_jump that points past both + the repetition text and either the following jump or + pop_failure_jump back to this on_failure_jump. */ + case on_failure_jump: + on_failure: + DEBUG_PRINT1 ("EXECUTING on_failure_jump"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt); + + /* If this on_failure_jump comes right before a group (i.e., + the original * applied to a group), save the information + for that group and all inner ones, so that if we fail back + to this point, the group's information will be correct. + For example, in \(a*\)*\1, we only need the preceding group, + and in \(\(a*\)b*\)\2, we need the inner group. */ + + /* We can't use `p' to check ahead because we push + a failure point to `p + mcnt' after we do this. */ + p1 = p; + + /* We need to skip no_op's before we look for the + start_memory in case this on_failure_jump is happening as + the result of a completed succeed_n, as in \(a\)\{1,3\}b\1 + against aba. */ + while (p1 < pend && (re_opcode_t) *p1 == no_op) + p1++; + + if (p1 < pend && (re_opcode_t) *p1 == start_memory) + { + /* We have a new highest active register now. This will + get reset at the start_memory we are about to get to, + but we will have saved all the registers relevant to + this repetition op, as described above. */ + highest_active_reg = *(p1 + 1) + *(p1 + 2); + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *(p1 + 1); + } + + DEBUG_PRINT1 (":\n"); + PUSH_FAILURE_POINT (p + mcnt, d, -2); + break; + + + /* A smart repeat ends with a maybe_pop_jump. + We change it either to a pop_failure_jump or a no_pop_jump. */ + case maybe_pop_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); + DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt); + { + register unsigned char *p2 = p; + + /* Compare the beginning of the repeat with what in the + pattern follows its end. If we can establish that there + is nothing that they would both match, i.e., that we + would have to backtrack because of (as in, e.g., `a*a') + then we can change to pop_failure_jump, because we'll + never have to backtrack. */ + + /* Skip over open/close-group commands. */ + while (p2 + 2 < pend + && ((re_opcode_t) *p2 == stop_memory + || (re_opcode_t) *p2 == start_memory)) + p2 += 3; /* Skip over args, too. */ + + /* If we're at the end of the pattern, we can change. */ + if (p2 == pend) + p[-3] = (unsigned char) pop_failure_jump; + + else if ((re_opcode_t) *p2 == exactn + || (bufp->newline_anchor && (re_opcode_t) *p2 == endline)) + { + register unsigned char c + = *p2 == (unsigned char) endline ? '\n' : p2[2]; + p1 = p + mcnt; + + /* p1[0] ... p1[2] are the on_failure_jump corresponding + to the maybe_finalize_jump of this case. Examine what + follows it. */ + if ((re_opcode_t) p1[3] == exactn && p1[5] != c) + p[-3] = (unsigned char) pop_failure_jump; + else if ((re_opcode_t) p1[3] == charset + || (re_opcode_t) p1[3] == charset_not) + { + int not = (re_opcode_t) p1[3] == charset_not; + + if (c < (unsigned char) (p1[4] * BYTEWIDTH) + && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not = !not; + + /* `not' is equal to 1 if c would match, which means + that we can't change to pop_failure_jump. */ + if (!not) + p[-3] = (unsigned char) pop_failure_jump; + } + } + } + p -= 2; /* Point at relative address again. */ + if ((re_opcode_t) p[-1] != pop_failure_jump) + { + p[-1] = (unsigned char) no_pop_jump; + goto no_pop; + } + /* Note fall through. */ + + + /* The end of a simple repeat has a pop_failure_jump back to + its matching on_failure_jump, where the latter will push a + failure point. The pop_failure_jump takes off failure + points put on by this pop_failure_jump's matching + on_failure_jump; we got through the pattern to here from the + matching on_failure_jump, so didn't fail. */ + case pop_failure_jump: + { + /* We need to pass separate storage for the lowest and + highest registers, even though we aren't interested. + Otherwise, we will restore only one register from the + stack, since lowest will equal highest in + pop_failure_point (since they'll be the same memory + location). */ + unsigned dummy_low, dummy_high; + unsigned char *pdummy = NULL; + + DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n"); + pop_failure_point (bufp, pend, +#ifdef DEBUG + string1, size1, string2, size2, +#endif + &failure_stack, &pdummy, &pdummy, + &dummy_low, &dummy_high, + ®_dummy, ®_dummy, ®_info_dummy); + } + /* Note fall through. */ + + + /* Jump without taking off any failure points. */ + case no_pop_jump: + no_pop: + EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */ + DEBUG_PRINT2 ("EXECUTING no_pop_jump %d ", mcnt); + p += mcnt; /* Do the jump. */ + DEBUG_PRINT2 ("(to 0x%x).\n", p); + break; + + + /* We need this opcode so we can detect where alternatives end + in `group_match_null_string_p' et al. */ + case jump_past_next_alt: + DEBUG_PRINT1 ("EXECUTING jump_past_next_alt.\n"); + goto no_pop; + + + /* Normally, the on_failure_jump pushes a failure point, which + then gets popped at pop_failure_jump. We will end up at + pop_failure_jump, also, and with a pattern of, say, `a+', we + are skipping over the on_failure_jump, so we have to push + something meaningless for pop_failure_jump to pop. */ + case dummy_failure_jump: + DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n"); + /* It doesn't matter what we push for the string here. What + the code at `fail' tests is the value for the pattern. */ + PUSH_FAILURE_POINT (0, 0, -2); + goto no_pop; + + + /* Have to succeed matching what follows at least n times. Then + just handle like an on_failure_jump. */ + case succeed_n: + EXTRACT_NUMBER (mcnt, p + 2); + DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt); + + /* Originally, this is how many times we HAVE to succeed. */ + if (mcnt) + { + mcnt--; + p += 2; + STORE_NUMBER_AND_INCR (p, mcnt); + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p, mcnt); + } + else if (mcnt == 0) + { + DEBUG_PRINT2 (" Setting two bytes from 0x%x to no_op.\n", p+2); + p[2] = (unsigned char) no_op; + p[3] = (unsigned char) no_op; + goto on_failure; + } +#ifdef DEBUG + else + { + fprintf (stderr, "regex: negative n at succeed_n.\n"); + abort (); + } +#endif /* DEBUG */ + break; + + case no_pop_jump_n: + EXTRACT_NUMBER (mcnt, p + 2); + DEBUG_PRINT2 ("EXECUTING no_pop_jump_n %d.\n", mcnt); + + /* Originally, this is how many times we CAN jump. */ + if (mcnt) + { + mcnt--; + STORE_NUMBER(p + 2, mcnt); + goto no_pop; + } + /* If don't have to jump any more, skip over the rest of command. */ + else + p += 4; + break; + + case set_number_at: + { + DEBUG_PRINT1 ("EXECUTING set_number_at.\n"); + + EXTRACT_NUMBER_AND_INCR (mcnt, p); + p1 = p + mcnt; + EXTRACT_NUMBER_AND_INCR (mcnt, p); + STORE_NUMBER (p1, mcnt); + break; + } + + case wordbound: + DEBUG_PRINT1 ("EXECUTING wordbound.\n"); + if (AT_WORD_BOUNDARY (d)) + break; + goto fail; + + case notwordbound: + DEBUG_PRINT1 ("EXECUTING notwordbound.\n"); + if (AT_WORD_BOUNDARY (d)) + goto fail; + break; + + case wordbeg: + DEBUG_PRINT1 ("EXECUTING wordbeg.\n"); + if (LETTER_P (d) && (AT_STRINGS_BEG || !LETTER_P (d - 1))) + break; + goto fail; + + case wordend: + DEBUG_PRINT1 ("EXECUTING wordend.\n"); + if (!AT_STRINGS_BEG && LETTER_P (d - 1) + && (!LETTER_P (d) || AT_STRINGS_END)) + break; + goto fail; + +#ifdef emacs +#ifdef emacs19 + case before_dot: + DEBUG_PRINT1 ("EXECUTING before_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) >= point) + goto fail; + break; + + case at_dot: + DEBUG_PRINT1 ("EXECUTING at_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) != point) + goto fail; + break; + + case after_dot: + DEBUG_PRINT1 ("EXECUTING after_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) <= point) + goto fail; + break; +#else /* not emacs19 */ + case at_dot: + DEBUG_PRINT1 ("EXECUTING at_dot.\n"); + if (PTR_CHAR_POS ((unsigned char *) d) + 1 != point) + goto fail; + break; +#endif /* not emacs19 */ + + case syntaxspec: + DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt); + mcnt = *p++; + goto matchsyntax; + + case wordchar: + DEBUG_PRINT1 ("EXECUTING wordchar.\n"); + mcnt = (int) Sword; + matchsyntax: + PREFETCH; + if (SYNTAX (*d++) != (enum syntaxcode) mcnt) goto fail; + SET_REGS_MATCHED (); + break; + + case notsyntaxspec: + DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt); + mcnt = *p++; + goto matchnotsyntax; + + case notwordchar: + DEBUG_PRINT1 ("EXECUTING notwordchar.\n"); + mcnt = (int) Sword; + matchnotsyntax: /* We goto here from notsyntaxspec. */ + PREFETCH; + if (SYNTAX (*d++) == (enum syntaxcode) mcnt) goto fail; + SET_REGS_MATCHED (); + break; + +#else /* not emacs */ + case wordchar: + DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n"); + PREFETCH; + if (!LETTER_P (d)) + goto fail; + SET_REGS_MATCHED (); + break; + + case notwordchar: + DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n"); + PREFETCH; + if (LETTER_P (d)) + goto fail; + SET_REGS_MATCHED (); + break; +#endif /* not emacs */ + + default: + abort (); + } + continue; /* Successfully executed one pattern command; keep going. */ + + + /* We goto here if a matching operation fails. */ + fail: + if (!FAILURE_STACK_EMPTY ()) + { /* A restart point is known. Restore to that state. */ + DEBUG_PRINT1 ("\nFAIL:\n"); + pop_failure_point (bufp, pend, +#ifdef DEBUG + string1, size1, string2, size2, +#endif + &failure_stack, &p, &d, &lowest_active_reg, + &highest_active_reg, ®start, ®end, + ®_info); + + /* If this failure point is a dummy, try the next one. */ + if (!p) + goto fail; + + /* If we failed to the end of the pattern, don't examine *p. */ + assert (p <= pend); + if (p < pend) + { + boolean is_a_jump_n = false; + + /* If failed to a backwards jump that's part of a repetition + loop, need to pop this failure point and use the next one. */ + switch ((re_opcode_t) *p) + { + case no_pop_jump_n: + is_a_jump_n = true; + case maybe_pop_jump: + case pop_failure_jump: + case no_pop_jump: + p1 = p + 1; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + + if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n) + || (!is_a_jump_n + && (re_opcode_t) *p1 == on_failure_jump)) + goto fail; + break; + default: + /* do nothing */ ; + } + } + + if (d >= string1 && d <= end1) + dend = end_match_1; + } + else + break; /* Matching at this starting point really fails. */ + } /* for (;;) */ + + if (best_regs_set) + goto restore_best_regs; + + FREE_VARIABLES (); + + return -1; /* Failure to match. */ +} /* re_match_2 */ + +/* Subroutine definitions for re_match_2. */ + + +/* Pops what PUSH_FAILURE_STACK pushes. */ + +static void +pop_failure_point (bufp, pattern_end, +#ifdef DEBUG + string1, size1, string2, size2, +#endif + failure_stack_ptr, pattern_place, string_place, + lowest_active_reg, highest_active_reg, + regstart, regend, reg_info) + const struct re_pattern_buffer *bufp; /* These not modified. */ + unsigned char *pattern_end; +#ifdef DEBUG + unsigned char *string1, *string2; + int size1, size2; +#endif + failure_stack_type *failure_stack_ptr; /* These get modified. */ + const unsigned char **pattern_place; + const unsigned char **string_place; + unsigned *lowest_active_reg, *highest_active_reg; + const unsigned char ***regstart; + const unsigned char ***regend; + register_info_type **reg_info; +{ +#ifdef DEBUG + /* Type is really unsigned; it's declared this way just to avoid a + compiler warning. */ + failure_stack_elt_t failure_id; +#endif + int this_reg; + const unsigned char *string_temp; + + assert (!FAILURE_STACK_PTR_EMPTY ()); + + /* Remove failure points and point to how many regs pushed. */ + DEBUG_PRINT1 ("pop_failure_point:\n"); + DEBUG_PRINT2 (" Before pop, next avail: %d\n", failure_stack_ptr->avail); + DEBUG_PRINT2 (" size: %d\n", failure_stack_ptr->size); + + assert (failure_stack_ptr->avail >= NUM_NONREG_ITEMS); + + DEBUG_POP (&failure_id); + DEBUG_PRINT2 (" Popping failure id: %u\n", failure_id); + + /* If the saved string location is NULL, it came from an + on_failure_keep_string_jump opcode, and we want to throw away the + saved NULL, thus retaining our current position in the string. */ + string_temp = POP_FAILURE_ITEM (); + if (string_temp != NULL) + *string_place = string_temp; + + DEBUG_PRINT2 (" Popping string 0x%x: `", *string_place); + DEBUG_DOUBLE_STRING_PRINTER (*string_place, string1, size1, string2, size2); + DEBUG_PRINT1 ("'\n"); + + *pattern_place = POP_FAILURE_ITEM (); + DEBUG_PRINT2 (" Popping pattern 0x%x: ", *pattern_place); + DEBUG_COMPILED_PATTERN_PRINTER (bufp, *pattern_place, pattern_end); + + /* Restore register info. */ + *highest_active_reg = (unsigned) POP_FAILURE_ITEM (); + DEBUG_PRINT2 (" Popping high active reg: %d\n", *highest_active_reg); + + *lowest_active_reg = (unsigned) POP_FAILURE_ITEM (); + DEBUG_PRINT2 (" Popping low active reg: %d\n", *lowest_active_reg); + + for (this_reg = *highest_active_reg; this_reg >= *lowest_active_reg; + this_reg--) + { + DEBUG_PRINT2 (" Popping reg: %d\n", this_reg); + + (*reg_info)[this_reg].word = POP_FAILURE_ITEM (); + DEBUG_PRINT2 (" info: 0x%x\n", (*reg_info)[this_reg]); + + (*regend)[this_reg] = POP_FAILURE_ITEM (); + DEBUG_PRINT2 (" end: 0x%x\n", (*regend)[this_reg]); + + (*regstart)[this_reg] = POP_FAILURE_ITEM (); + DEBUG_PRINT2 (" start: 0x%x\n", (*regstart)[this_reg]); + } +} /* pop_failure_point */ + + +/* We are passed P pointing to a register number after a start_memory. + + Return true if the pattern up to the corresponding stop_memory can + match the empty string, and false otherwise. + + If we find the matching stop_memory, sets P to point to one past its number. + Otherwise, sets P to an undefined byte less than or equal to END. + + We don't handle duplicates properly (yet). */ + +static boolean +group_match_null_string_p (p, end, reg_info) + unsigned char **p, *end; + register_info_type *reg_info; +{ + int mcnt; + /* Point to after the args to the start_memory. */ + unsigned char *p1 = *p + 2; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and return true or + false, as appropriate, when we get to one that can't, or to the + matching stop_memory. */ + + switch ((re_opcode_t) *p1) + { + /* Could be either a loop or a series of alternatives. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + /* If the next operation is not a jump backwards in the + pattern. */ + + if (mcnt >= 0) + { + /* Go through the on_failure_jumps of the alternatives, + seeing if any of the alternatives cannot match nothing. + The last alternative starts with only a no_pop_jump, + whereas the rest start with on_failure_jump and end + with a no_pop_jump, e.g., here is the pattern for `a|b|c': + + /on_failure_jump/0/6/exactn/1/a/jump_past_next_alt/0/6 + /on_failure_jump/0/6/exactn/1/b/jump_past_next_alt/0/3 + /exactn/1/c + + So, we have to first go through the first (n-1) + alternatives and then deal with the last one separately. */ + + + /* Deal with the first (n-1) alternatives, which start + with an on_failure_jump (see above) that jumps to right + past a jump_past_next_alt. */ + + while ((re_opcode_t) p1[mcnt-3] == jump_past_next_alt) + { + /* `mcnt' holds how many bytes long the alternative + is, including the ending `jump_past_next_alt' and + its number. */ + + if (!alt_match_null_string_p (p1, p1 + mcnt - 3, + reg_info)) + return false; + + /* Move to right after this alternative, including the + jump_past_next_alt. */ + p1 += mcnt; + + /* Break if it's the beginning of an n-th alternative + that doesn't begin with an on_failure_jump. */ + if ((re_opcode_t) *p1 != on_failure_jump) + break; + + /* Still have to check that it's not an n-th + alternative that starts with an on_failure_jump. */ + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if ((re_opcode_t) p1[mcnt-3] != jump_past_next_alt) + { + /* Get to the beginning of the n-th alternative. */ + p1 -= 3; + break; + } + } + + /* Deal with the last alternative: go back and get number + of the jump_past_next_alt just before it. `mcnt' + contains how many bytes long the alternative is. */ + EXTRACT_NUMBER (mcnt, p1 - 2); + + if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info)) + return false; + + p1 += mcnt; /* Get past the n-th alternative. */ + } /* if mcnt > 0 */ + break; + + + case stop_memory: + assert (p1[1] == **p); + *p = p1 + 2; + return true; + + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return false; +} /* group_match_null_string_p */ + + +/* Similar to group_match_null_string_p, but doesn't deal with alternatives: + It expects P to be the first byte of a single alternative and END one + byte past the last. The alternative can contain groups. */ + +static boolean +alt_match_null_string_p (p, end, reg_info) + unsigned char *p, *end; + register_info_type *reg_info; +{ + int mcnt; + unsigned char *p1 = p; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and break when we get + to one that can't. */ + + switch ((re_opcode_t) *p1) + { + /* It's a loop. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + break; + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return true; +} /* alt_match_null_string_p */ + + +/* Deals with the ops common to group_match_null_string_p and + alt_match_null_string_p. + + Sets P to one after the op and its arguments, if any. */ + +static boolean +common_op_match_null_string_p (p, end, reg_info) + unsigned char **p, *end; + register_info_type *reg_info; +{ + int mcnt; + boolean ret; + int reg_no; + unsigned char *p1 = *p; + + switch ((re_opcode_t) *p1++) + { + case no_op: + case begline: + case endline: + case begbuf: + case endbuf: + case wordbeg: + case wordend: + case wordbound: + case notwordbound: +#ifdef emacs + case before_dot: + case at_dot: + case after_dot: +#endif + break; + + case start_memory: + reg_no = *p1; + ret = group_match_null_string_p (&p1, end, reg_info); + + /* Have to set this here in case we're checking a group which + contains a group and a back reference to it. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) + == MATCH_NOTHING_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret; + + if (!ret) + return false; + break; + + /* If this is an optimized succeed_n for zero times, make the jump. */ + case no_pop_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (mcnt >= 0) + p1 += mcnt; + else + return false; + break; + + case succeed_n: + /* Get to the number of times to succeed. */ + p1 += 2; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + if (mcnt == 0) + { + p1 -= 4; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + } + else + return false; + break; + + case duplicate: + if (!REG_MATCH_NULL_STRING_P (reg_info[*p1])) + return false; + break; + + case set_number_at: + p1 += 4; + + default: + /* All other opcodes mean we cannot match the empty string. */ + return false; + } + + *p = p1; + return true; +} /* common_op_match_null_string_p */ + + +/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN + bytes; nonzero otherwise. */ + +static int +bcmp_translate (s1, s2, len, translate) + unsigned char *s1, *s2; + register int len; + char *translate; +{ + register unsigned char *p1 = s1, *p2 = s2; + while (len) + { + if (translate[*p1++] != translate[*p2++]) return 1; + len--; + } + return 0; +} + +/* Entry points for GNU code. */ + +/* re_compile_pattern is the GNU regular expression compiler: it + compiles PATTERN (of length SIZE) and puts the result in BUFP. + Returns 0 if the pattern was valid, otherwise an error string. + + Assumes the `allocated' (and perhaps `buffer') and `translate' fields + are set in BUFP on entry. + + We call regex_compile to do the actual compilation. */ + +const char * +re_compile_pattern (pattern, length, bufp) + const char *pattern; + int length; + struct re_pattern_buffer *bufp; +{ + reg_errcode_t ret; + + /* GNU code is written to assume RE_NREGS registers will be set + (and extraneous ones will be filled with -1). */ + bufp->caller_allocated_regs = 0; + + /* And GNU code determines whether or not to get register information + by passing null for the REGS argument to re_match, etc., not by + setting no_sub. */ + bufp->no_sub = 0; + + /* Match anchors at newline. */ + bufp->newline_anchor = 1; + + ret = regex_compile (pattern, length, obscure_syntax, bufp); + + return re_error_msg[(int) ret]; +} + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them if this is an Emacs compilation. */ + +#if !defined (emacs) + +static struct re_pattern_buffer re_comp_buf; + +const char * +re_comp (s) + const char *s; +{ + reg_errcode_t ret; + + if (!s) + { + if (!re_comp_buf.buffer) + return "No previous regular expression"; + return 0; + } + + if (!re_comp_buf.buffer) + { + re_comp_buf.buffer = (unsigned char *) malloc (200); + if (re_comp_buf.buffer == NULL) + return "Memory exhausted"; + re_comp_buf.allocated = 200; + + re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH); + if (re_comp_buf.fastmap == NULL) + return "Memory exhausted"; + } + + /* Match anchors at newlines. */ + re_comp_buf.newline_anchor = 1; + + ret = regex_compile (s, strlen (s), obscure_syntax, &re_comp_buf); + + return re_error_msg[(int) ret]; +} + + +int +re_exec (s) + const char *s; +{ + const int len = strlen (s); + return 0 <= re_search (&re_comp_buf, s, len, 0, len, + (struct re_registers *) 0); +} +#endif /* not emacs */ + +/* Entry points compatible with POSIX regex library. Don't define these + for Emacs. */ + +#ifndef emacs + +/* regcomp takes a regular expression as a string and compiles it. + + PREG is a regex_t *. We do not expect any fields to be initialized, + since POSIX says we shouldn't. Thus, we set + + `buffer' to the compiled pattern; + `used' to the length of the compiled pattern; + `syntax' to RE_SYNTAX_POSIX_EXTENDED if the + REG_EXTENDED bit in CFLAGS is set; otherwise, to + RE_SYNTAX_POSIX_BASIC; + `newline_anchor' to REG_NEWLINE being set in CFLAGS; + `fastmap' and `fastmap_accurate' to zero; + `re_nsub' to the number of subexpressions in PATTERN. + + PATTERN is the address of the pattern string. + + CFLAGS is a series of bits which affect compilation. + + If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we + use POSIX basic syntax. + + If REG_NEWLINE is set, then . and [^...] don't match newline. + Also, regexec will try a match beginning after every newline. + + If REG_ICASE is set, then we considers upper- and lowercase + versions of letters to be equivalent when matching. + + If REG_NOSUB is set, then when PREG is passed to regexec, that + routine will report only success or failure, and nothing about the + registers. + + It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for + the return codes and their meanings.) */ + +int +regcomp (preg, pattern, cflags) + regex_t *preg; + const char *pattern; + int cflags; +{ + reg_errcode_t ret; + unsigned syntax + = cflags & REG_EXTENDED ? RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC; + + /* regex_compile will allocate the space for the compiled pattern. */ + preg->buffer = 0; + + /* Don't bother to use a fastmap when searching. This simplifies the + REG_NEWLINE case: if we used a fastmap, we'd have to put all the + characters after newlines into the fastmap. This way, we just try + every character. */ + preg->fastmap = 0; + + if (cflags & REG_ICASE) + { + unsigned i; + + preg->translate = (char *) malloc (CHAR_SET_SIZE); + if (preg->translate == NULL) + return (int) REG_ESPACE; + + /* Map uppercase characters to corresponding lowercase ones. */ + for (i = 0; i < CHAR_SET_SIZE; i++) + preg->translate[i] = isupper (i) ? tolower (i) : i; + } + else + preg->translate = NULL; + + /* If REG_NEWLINE is set, newlines are treated differently. */ + if (cflags & REG_NEWLINE) + { /* REG_NEWLINE implies neither . nor [^...] match newline. */ + syntax &= ~RE_DOT_NEWLINE; + syntax |= RE_HAT_LISTS_NOT_NEWLINE; + /* It also changes the matching behavior. */ + preg->newline_anchor = 1; + } + else + preg->newline_anchor = 0; + + preg->no_sub = !!(cflags & REG_NOSUB); + + /* POSIX says a null character in the pattern terminates it, so we + can use strlen here in compiling the pattern. */ + ret = regex_compile (pattern, strlen (pattern), syntax, preg); + + /* POSIX doesn't distinguish between an unmatched open-group and an + unmatched close-group: both are REG_EPAREN. */ + if (ret == REG_ERPAREN) ret = REG_EPAREN; + + return (int) ret; +} + + +/* regexec searches for a given pattern, specified by PREG, in the + string STRING. + + If NMATCH is zero or REG_NOSUB was set in the cflags argument to + `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at + least NMATCH elements, and we set them to the offsets of the + corresponding matched substrings. + + EFLAGS specifies `execution flags' which affect matching: if + REG_NOTBOL is set, then ^ does not match at the beginning of the + string; if REG_NOTEOL is set, then $ does not match at the end. + + We return 0 if we find a match and REG_NOMATCH if not. */ + +int +regexec (preg, string, nmatch, pmatch, eflags) + const regex_t *preg; + const char *string; + size_t nmatch; + regmatch_t pmatch[]; + int eflags; +{ + int ret; + struct re_registers regs; + regex_t private_preg; + int len = strlen (string); + boolean want_reg_info = !preg->no_sub && nmatch > 0; + + private_preg = *preg; + + private_preg.not_bol = !!(eflags & REG_NOTBOL); + private_preg.not_eol = !!(eflags & REG_NOTEOL); + + /* The user has told us how many registers to return information + about, via `nmatch'. We have to pass that on to the matching + routines. */ + private_preg.caller_allocated_regs = 1; + + if (want_reg_info) + { + regs.num_regs = nmatch; + regs.start = TALLOC (nmatch, regoff_t); + regs.end = TALLOC (nmatch, regoff_t); + if (regs.start == NULL || regs.end == NULL) + return (int) REG_NOMATCH; + } + + /* Perform the searching operation. */ + ret = re_search (&private_preg, string, len, + /* start: */ 0, /* range: */ len, + want_reg_info ? ®s : NULL); + + /* Copy the register information to the POSIX structure. */ + if (want_reg_info) + { + if (ret >= 0) + { + unsigned r; + + for (r = 0; r < nmatch; r++) + { + pmatch[r].rm_so = regs.start[r]; + pmatch[r].rm_eo = regs.end[r]; + } + } + + /* If we needed the temporary register info, free the space now. */ + free (regs.start); + free (regs.end); + } + + /* We want zero return to mean success, unlike `re_search'. */ + return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH; +} + + +/* Returns a message corresponding to an error code, ERRCODE, returned + from either regcomp or regexec. */ + +size_t +regerror (errcode, preg, errbuf, errbuf_size) + int errcode; + const regex_t *preg; + char *errbuf; + size_t errbuf_size; +{ + const char *msg + = re_error_msg[errcode] == NULL ? "Success" : re_error_msg[errcode]; + size_t msg_size = strlen (msg) + 1; /* Includes the null. */ + + if (errbuf_size != 0) + { + if (msg_size > errbuf_size) + { + strncpy (errbuf, msg, errbuf_size - 1); + errbuf[errbuf_size - 1] = 0; + } + else + strcpy (errbuf, msg); + } + + return msg_size; +} + + +/* Free dynamically allocated space used by PREG. */ + +void +regfree (preg) + regex_t *preg; +{ + if (preg->buffer != NULL) + free (preg->buffer); + preg->buffer = NULL; + + preg->allocated = 0; + preg->used = 0; + + if (preg->fastmap != NULL) + free (preg->fastmap); + preg->fastmap = NULL; + preg->fastmap_accurate = 0; + + if (preg->translate != NULL) + free (preg->translate); + preg->translate = NULL; +} + +#endif /* not emacs */ + +#ifdef test + +#include <stdio.h> + +/* Indexed by a character, gives the upper case equivalent of the + character. */ + +char upcase[0400] = + { 000, 001, 002, 003, 004, 005, 006, 007, + 010, 011, 012, 013, 014, 015, 016, 017, + 020, 021, 022, 023, 024, 025, 026, 027, + 030, 031, 032, 033, 034, 035, 036, 037, + 040, 041, 042, 043, 044, 045, 046, 047, + 050, 051, 052, 053, 054, 055, 056, 057, + 060, 061, 062, 063, 064, 065, 066, 067, + 070, 071, 072, 073, 074, 075, 076, 077, + 0100, 0101, 0102, 0103, 0104, 0105, 0106, 0107, + 0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117, + 0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127, + 0130, 0131, 0132, 0133, 0134, 0135, 0136, 0137, + 0140, 0101, 0102, 0103, 0104, 0105, 0106, 0107, + 0110, 0111, 0112, 0113, 0114, 0115, 0116, 0117, + 0120, 0121, 0122, 0123, 0124, 0125, 0126, 0127, + 0130, 0131, 0132, 0173, 0174, 0175, 0176, 0177, + 0200, 0201, 0202, 0203, 0204, 0205, 0206, 0207, + 0210, 0211, 0212, 0213, 0214, 0215, 0216, 0217, + 0220, 0221, 0222, 0223, 0224, 0225, 0226, 0227, + 0230, 0231, 0232, 0233, 0234, 0235, 0236, 0237, + 0240, 0241, 0242, 0243, 0244, 0245, 0246, 0247, + 0250, 0251, 0252, 0253, 0254, 0255, 0256, 0257, + 0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267, + 0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277, + 0300, 0301, 0302, 0303, 0304, 0305, 0306, 0307, + 0310, 0311, 0312, 0313, 0314, 0315, 0316, 0317, + 0320, 0321, 0322, 0323, 0324, 0325, 0326, 0327, + 0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337, + 0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347, + 0350, 0351, 0352, 0353, 0354, 0355, 0356, 0357, + 0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367, + 0370, 0371, 0372, 0373, 0374, 0375, 0376, 0377 + }; + + +/* Use this to run interactive tests. */ + +void +main (argc, argv) + int argc; + char **argv; +{ + char pat[500]; + struct re_pattern_buffer buf; + int i; + char c; + char fastmap[(1 << BYTEWIDTH)]; + + /* Allow a command argument to specify the style of syntax. */ + if (argc > 1) + re_set_syntax (atoi (argv[1])); + + buf.allocated = 40; + buf.buffer = (unsigned char *) malloc (buf.allocated); + buf.fastmap = fastmap; + buf.translate = upcase; + + for (;;) + { + printf ("Pattern = "); + gets (pat); + + if (*pat) + { + void printchar (); + re_compile_pattern (pat, strlen (pat), &buf); + + for (i = 0; i < buf.used; i++) + printchar (buf.buffer[i]); + + putchar ('\n'); + + printf ("%d allocated, %d used.\n", buf.allocated, buf.used); + + re_compile_fastmap (&buf); + printf ("Allowed by fastmap: "); + for (i = 0; i < (1 << BYTEWIDTH); i++) + if (fastmap[i]) printchar (i); + putchar ('\n'); + } + + printf ("String = "); + gets (pat); /* Now read the string to match against */ + + i = re_match (&buf, pat, strlen (pat), 0, 0); + printf ("Match value %d.\n\n", i); + } +} + + +#if 0 +/* We have a fancier version now, compiled_pattern_printer. */ +print_buf (bufp) + struct re_pattern_buffer *bufp; +{ + int i; + + printf ("buf is :\n----------------\n"); + for (i = 0; i < bufp->used; i++) + printchar (bufp->buffer[i]); + + printf ("\n%d allocated, %d used.\n", bufp->allocated, bufp->used); + + printf ("Allowed by fastmap: "); + for (i = 0; i < (1 << BYTEWIDTH); i++) + if (bufp->fastmap[i]) + printchar (i); + printf ("\nAllowed by translate: "); + if (bufp->translate) + for (i = 0; i < (1 << BYTEWIDTH); i++) + if (bufp->translate[i]) + printchar (i); + printf ("\nfastmap is%s accurate\n", bufp->fastmap_accurate ? "" : "n't"); + printf ("can %s be null\n----------", bufp->can_be_null ? "" : "not"); +} +#endif /* 0 */ + + +void +printchar (c) + char c; +{ + if (c < 040 || c >= 0177) + { + putchar ('\\'); + putchar (((c >> 6) & 3) + '0'); + putchar (((c >> 3) & 7) + '0'); + putchar ((c & 7) + '0'); + } + else + putchar (c); +} +#endif /* test */ + +/* +Local variables: +make-backup-files: t +version-control: t +trim-versions-without-asking: nil +End: +*/ diff --git a/gnu/usr.bin/cvs/lib/regex.h b/gnu/usr.bin/cvs/lib/regex.h new file mode 100644 index 0000000..211ad09 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/regex.h @@ -0,0 +1,479 @@ +/* Definitions for data structures and routines for the regular + expression library, version REPLACE-WITH-VERSION. + + Copyright (C) 1985, 1989, 1990, 1991, 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef __REGEXP_LIBRARY_H__ +#define __REGEXP_LIBRARY_H__ + +/* POSIX says that <sys/types.h> must be included before <regex.h>. */ + +/* The following bits are used to determine the regexp syntax we + recognize. The set/not-set meanings are chosen so that Emacs syntax + remains the value 0. The bits are given in alphabetical order, and + the definitions shifted by one from the previous bit; thus, when we + add or remove a bit, only one other definition need change. */ +typedef unsigned reg_syntax_t; + +/* If this bit is not set, then \ inside a bracket expression is literal. + If set, then such a \ quotes the following character. */ +#define RE_BACKSLASH_ESCAPE_IN_LISTS (1) + +/* If this bit is not set, then + and ? are operators, and \+ and \? are + literals. + If set, then \+ and \? are operators and + and ? are literals. */ +#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) + +/* If this bit is set, then character classes are supported. They are: + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. + If not set, then character classes are not supported. */ +#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) + +/* If this bit is set, then ^ and $ are always anchors (outside bracket + expressions). + If this bit is not set, then it depends: + ^ is an anchor if it is at the beginning of a regular + expression or after an open-group or an alternation operator; + $ is an anchor if it is at the end of a regular expression, or + before a close-group or an alternation operator. + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because + POSIX now says that the behavior of * etc. in leading positions is + undefined. We have already implemented a previous draft which + made those constructs invalid, so we may as well not change the code + back. */ +#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) + +/* If this bit is set, then special characters are always special + regardless of where they are in the pattern. + If this bit is not set, then special characters are special only in + some contexts; otherwise they are ordinary. Specifically, + * + ? and intervals are only special when not after the beginning, + open-group, or alternation operator. */ +#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) + +/* If this bit is set, then *, +, ?, and { cannot be first in an re or + immediately after an alternation or begin-group operator. + Furthermore, alternation cannot be first or last in an re, or + immediately follow another alternation or begin-group. */ +#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) + +/* If this bit is set, then . matches a newline. + If not set, then it doesn't. */ +#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) + +/* If this bit is set, then period doesn't match a null. + If not set, then it does. */ +#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) + +/* If this bit is set, nonmatching lists [^...] do not match newline. + If not set, they do. */ +#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) + +/* If this bit is set, either \{...\} or {...} defines an + interval, depending on RE_NO_BK_BRACES. + If not set, \{, \}, {, and } are literals. */ +#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) + +/* If this bit is set, +, ? and | aren't recognized as operators. + If not set, they are. */ +#define RE_LIMITED_OPS (RE_INTERVALS << 1) + +/* If this bit is set, newline is an alternation operator. + If not set, newline is literal. */ +#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) + +/* If this bit is set, newline in the pattern is an ordinary character. + If not set, newline before ^ or after $ allows the ^ or $ to be an + anchor. */ +#define RE_NEWLINE_ORDINARY (RE_NEWLINE_ALT << 1) + +/* If this bit is not set, then \{ and \} defines an interval, + and { and } are literals. + If set, then { and } defines an interval, and \{ and \} are literals. */ +#define RE_NO_BK_BRACES (RE_NEWLINE_ORDINARY << 1) + +/* If this bit is set, (...) defines a group, and \( and \) are literals. + If not set, \(...\) defines a group, and ( and ) are literals. */ +#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) + +/* If this bit is set, then back references (i.e., \<digit>) are not + recognized. + If not set, then they are. */ +#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) + +/* If this bit is set, then | is an alternation operator, and \| is literal. + If not set, then \| is an alternation operator, and | is literal. */ +#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) + +/* If this bit is set, then you can't have empty alternatives. + If not set, then you can. */ +#define RE_NO_EMPTY_ALTS (RE_NO_BK_VBAR << 1) + +/* If this bit is set, then you can't have empty groups. + If not set, then you can. */ +#define RE_NO_EMPTY_GROUPS (RE_NO_EMPTY_ALTS << 1) + +/* If this bit is set, then an ending range point has to collate higher + than or equal to the starting range point. + If not set, then when the ending range point collates higher than the + starting range point, we consider such a range to be empty. */ +#define RE_NO_EMPTY_RANGES (RE_NO_EMPTY_GROUPS << 1) + +/* If this bit is set, then all back references must refer to a preceding + subexpression. + If not set, then a back reference to a nonexistent subexpression is + treated as literal characters. */ +#define RE_NO_MISSING_BK_REF (RE_NO_EMPTY_RANGES << 1) + +/* If this bit is set, then Regex considers an unmatched close-group + operator to be the ordinary character parenthesis. + If not set, then an unmatched close-group operator is invalid. */ +#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_MISSING_BK_REF << 1) + +/* This global variable defines the particular regexp syntax to use (for + some interfaces). When a regexp is compiled, the syntax used is + stored in the pattern buffer, so changing this does not affect + already-compiled regexps. */ +extern reg_syntax_t obscure_syntax; + + + +/* Define combinations of the above bits for the standard possibilities. + (The [[[ comments delimit what gets put into the Texinfo file.) */ +/* [[[begin syntaxes]]] */ +#define RE_SYNTAX_EMACS 0 + +#define RE_SYNTAX_POSIX_AWK \ + (RE_CONTEXT_INDEP_ANCHORS | RE_CONTEXT_INDEP_OPS | RE_NO_BK_PARENS \ + | RE_NO_BK_VBAR) + +#define RE_SYNTAX_AWK \ + (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_SYNTAX_POSIX_AWK) + +#define RE_SYNTAX_GREP \ + (RE_BK_PLUS_QM | RE_NEWLINE_ALT) + +#define RE_SYNTAX_EGREP \ + (RE_CONTEXT_INDEP_ANCHORS | RE_CONTEXT_INDEP_OPS \ + | RE_NEWLINE_ALT | RE_NO_BK_PARENS | RE_NO_BK_VBAR) + +#define RE_SYNTAX_POSIX_BASIC \ + (RE_CHAR_CLASSES | RE_DOT_NEWLINE \ + | RE_DOT_NOT_NULL | RE_INTERVALS | RE_LIMITED_OPS \ + | RE_NEWLINE_ORDINARY | RE_NO_EMPTY_RANGES | RE_NO_MISSING_BK_REF) + +#define RE_SYNTAX_POSIX_EXTENDED \ + (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INVALID_OPS | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ + | RE_INTERVALS | RE_NEWLINE_ORDINARY | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS | RE_NO_BK_VBAR \ + | RE_NO_EMPTY_ALTS | RE_NO_EMPTY_GROUPS | RE_NO_EMPTY_RANGES \ + | RE_UNMATCHED_RIGHT_PAREN_ORD) +/* [[[end syntaxes]]] */ + + + + +/* Maximum number of duplicates an interval can allow. */ +#undef RE_DUP_MAX +#define RE_DUP_MAX ((1 << 15) - 1) + + +/* POSIX `cflags' bits (i.e., information for regcomp). */ + +/* If this bit is set, then use extended regular expression syntax. + If not set, then use basic regular expression syntax. */ +#define REG_EXTENDED 1 + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +#define REG_ICASE (REG_EXTENDED << 1) + +/* If this bit is set, then anchors do not match at newline + characters in the string. + If not set, then anchors do match at newlines. */ +#define REG_NEWLINE (REG_ICASE << 1) + +/* If this bit is set, then report only success or fail in regexec. + If not set, then returns differ between not matching and errors. */ +#define REG_NOSUB (REG_NEWLINE << 1) + + +/* POSIX `eflags' bits (i.e., information for regexec). */ + +/* If this bit is set, then the beginning-of-line operator doesn't match + the beginning of the string (presumably because it's not the + beginning of a line). + If not set, then the beginning-of-line operator does match the + beginning of the string. */ +#define REG_NOTBOL 1 + +/* Like REG_NOTBOL, except for the end-of-line. */ +#define REG_NOTEOL (1 << 1) + + +/* If any error codes are removed, changed, or added, update the + `re_error_msg' table in regex.c. */ +typedef enum +{ + REG_NOERROR = 0, /* Success. */ + REG_NOMATCH, /* Didn't find a match (for regexec). */ + + /* POSIX regcomp return error codes. (In the order listed in the + standard.) */ + REG_BADPAT, /* Invalid pattern. */ + REG_ECOLLATE, /* Not implemented. */ + REG_ECTYPE, /* Invalid character class name. */ + REG_EESCAPE, /* Trailing backslash. */ + REG_ESUBREG, /* Invalid back reference. */ + REG_EBRACK, /* Unmatched left bracket. */ + REG_EPAREN, /* Parenthesis imbalance. */ + REG_EBRACE, /* Unmatched \{. */ + REG_BADBR, /* Invalid contents of \{\}. */ + REG_ERANGE, /* Invalid range end. */ + REG_ESPACE, /* Ran out of memory. */ + REG_BADRPT, /* No preceding re for repetition op. */ + + /* Error codes we've added. */ + REG_EEND, /* Premature end. */ + REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */ + REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ +} reg_errcode_t; + + + + +/* This data structure represents a compiled pattern. Before calling + the pattern compiler, the fields `buffer', `allocated', `fastmap', + `translate', and `no_sub' can be set. After the pattern has been + compiled, the `re_nsub' field is available. All other fields are + private to the regex routines. */ + +struct re_pattern_buffer +{ +/* [[[begin pattern_buffer]]] */ + /* Space that holds the compiled pattern. It is declared as + `unsigned char *' because its elements are + sometimes used as array indexes. */ + unsigned char *buffer; + + /* Number of bytes to which `buffer' points. */ + unsigned long allocated; + + /* Number of bytes actually used in `buffer'. */ + unsigned long used; + + /* Syntax setting with which the pattern was compiled. */ + reg_syntax_t syntax; + + /* Pointer to a fastmap, if any, otherwise zero. re_search uses + the fastmap, if there is one, to skip over impossible + starting points for matches. */ + char *fastmap; + + /* Either a translate table to apply to all characters before + comparing them, or zero for no translation. The translation + is applied to a pattern when it is compiled and to a string + when it is matched. */ + char *translate; + + /* Number of subexpressions found by the compiler. */ + size_t re_nsub; + + /* Set to 1 by re_compile_fastmap if this pattern can match the + null string; 0 prevents the searcher from matching it with + the null string. Set to 2 if it might match the null string + either at the end of a search range or just before a + character listed in the fastmap. */ + unsigned can_be_null : 2; + + /* Set to zero when regex_compile compiles a pattern; set to one + by re_compile_fastmap when it updates the fastmap, if any. */ + unsigned fastmap_accurate : 1; + + /* If set, regexec reports only success or failure and does not + return anything in pmatch. */ + unsigned no_sub : 1; + + /* If set, a beginning-of-line anchor doesn't match at the + beginning of the string. */ + unsigned not_bol : 1; + + /* Similarly for an end-of-line anchor. */ + unsigned not_eol : 1; + + /* If true, an anchor at a newline matches. */ + unsigned newline_anchor : 1; + + /* If set, re_match_2 assumes a non-null REGS argument is + initialized. If not set, REGS is initialized to the max of + RE_NREGS and re_nsub + 1 registers. */ + unsigned caller_allocated_regs : 1; +/* [[[end pattern_buffer]]] */ +}; + +typedef struct re_pattern_buffer regex_t; + + +/* search.c (search_buffer) in Emacs needs this one opcode value. It is + defined both in `regex.c' and here. */ +#define RE_EXACTN_VALUE 1 + + + + +/* Type for byte offsets within the string. POSIX mandates us defining + this. */ +typedef int regoff_t; + + +/* This is the structure we store register match data in. See + regex.texinfo for a full description of what registers match. */ +struct re_registers +{ + unsigned num_regs; + regoff_t *start; + regoff_t *end; +}; + + +/* If `caller_allocated_regs' is zero in the pattern buffer, re_match_2 + returns information about this many registers. */ +#ifndef RE_NREGS +#define RE_NREGS 30 +#endif + + +/* POSIX specification for registers. Aside from the different names than + `re_registers', POSIX uses an array of structures, instead of a + structure of arrays. */ +typedef struct +{ + regoff_t rm_so; /* Byte offset from string's start to substring's start. */ + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ +} regmatch_t; + + + + +/* Declarations for routines. */ + +#if __STDC__ + +/* Sets the current syntax to SYNTAX. You can also simply assign to the + `obscure_syntax' variable. */ +extern reg_syntax_t re_set_syntax (reg_syntax_t syntax); + +/* Compile the regular expression PATTERN, with length LENGTH + and syntax given by the global `obscure_syntax', into the buffer + BUFFER. Return NULL if successful, and an error string if not. */ +extern const char *re_compile_pattern (const char *pattern, int length, + struct re_pattern_buffer *buffer); + + +/* Compile a fastmap for the compiled pattern in BUFFER; used to + accelerate searches. Return 0 if successful and -2 if was an + internal error. */ +extern int re_compile_fastmap (struct re_pattern_buffer *buffer); + + +/* Search in the string STRING (with length LENGTH) for the pattern + compiled into BUFFER. Start searching at position START, for RANGE + characters. Return the starting position of the match, -1 for no + match, or -2 for an internal error. Also return register + information in REGS (if REGS and BUFFER->no_sub are nonzero). */ +extern int re_search (struct re_pattern_buffer *buffer, + const char *string, int length, + int start, int range, + struct re_registers *regs); + + +/* Like `re_search', but search in the concatenation of STRING1 and + STRING2. Also, stop searching at index START + STOP. */ +extern int re_search_2 (struct re_pattern_buffer *buffer, + const char *string1, int length1, + const char *string2, int length2, + int start, int range, + struct re_registers *regs, + int stop); + + +/* Like `re_search', but return how many characters in STRING the regexp + in BUFFER matched, starting at position START. */ +extern int re_match (const struct re_pattern_buffer *buffer, + const char *string, int length, + int start, struct re_registers *regs); + + +/* Relates to `re_match' as `re_search_2' relates to `re_search'. */ +extern int re_match_2 (const struct re_pattern_buffer *buffer, + const char *string1, int length1, + const char *string2, int length2, + int start, + struct re_registers *regs, + int stop); + + +#ifndef __386BSD__ +/* 4.2 bsd compatibility. */ +#ifndef bsdi +extern const char *re_comp (const char *); +#endif +extern int re_exec (const char *); +#endif + +/* POSIX compatibility. */ +extern int regcomp (regex_t *preg, const char *pattern, int cflags); +extern int regexec (const regex_t *preg, const char *string, size_t nmatch, + regmatch_t pmatch[], int eflags); +extern size_t regerror (int errcode, const regex_t *preg, char *errbuf, + size_t errbuf_size); +extern void regfree (regex_t *preg); + +#else /* not __STDC__ */ + +/* Support old C compilers. */ +#define const + +extern reg_syntax_t re_set_syntax (); +extern char *re_compile_pattern (); +extern int re_search (), re_search_2 (); +extern int re_match (), re_match_2 (); + +/* 4.2 BSD compatibility. */ +extern char *re_comp (); +extern int re_exec (); + +/* POSIX compatibility. */ +extern int regcomp (); +extern int regexec (); +extern size_t regerror (); +extern void regfree (); + +#endif /* not __STDC__ */ +#endif /* not __REGEXP_LIBRARY_H__ */ + + + +/* +Local variables: +make-backup-files: t +version-control: t +trim-versions-without-asking: nil +End: +*/ diff --git a/gnu/usr.bin/cvs/lib/rename.c b/gnu/usr.bin/cvs/lib/rename.c new file mode 100644 index 0000000..3e0b481 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/rename.c @@ -0,0 +1,68 @@ +/* rename.c -- BSD compatible directory function for System V + Copyright (C) 1988, 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#ifndef STDC_HEADERS +extern int errno; +#endif + +/* Rename file FROM to file TO. + Return 0 if successful, -1 if not. */ + +int +rename (from, to) + char *from; + char *to; +{ + struct stat from_stats; + int pid, status; + + if (stat (from, &from_stats) == 0) + { + if (unlink (to) && errno != ENOENT) + return -1; + if ((from_stats.st_mode & S_IFMT) == S_IFDIR) + { + /* Need a setuid root process to link and unlink directories. */ + pid = fork (); + switch (pid) + { + case -1: /* Error. */ + error (1, errno, "cannot fork"); + + case 0: /* Child. */ + execl (MVDIR, "mvdir", from, to, (char *) 0); + error (255, errno, "cannot run `%s'", MVDIR); + + default: /* Parent. */ + while (wait (&status) != pid) + /* Do nothing. */ ; + + errno = 0; /* mvdir printed the system error message. */ + return status != 0 ? -1 : 0; + } + } + else + { + if (link (from, to) == 0 && (unlink (from) == 0 || errno == ENOENT)) + return 0; + } + } + return -1; +} diff --git a/gnu/usr.bin/cvs/lib/sighandle.c b/gnu/usr.bin/cvs/lib/sighandle.c new file mode 100644 index 0000000..1b73b93 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/sighandle.c @@ -0,0 +1,412 @@ +/* sighandle.c -- Library routines for manipulating chains of signal handlers + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Written by Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> + Brian Berliner <berliner@Sun.COM> added POSIX support */ + +/************************************************************************* + * + * signal.c -- This file contains code that manipulates chains of signal + * handlers. + * + * Facilities are provided to register a signal handler for + * any specific signal. When a signal is received, all of the + * registered signal handlers are invoked in the reverse order + * in which they are registered. Note that the signal handlers + * must not themselves make calls to the signal handling + * facilities. + * + * @(#)sighandle.c 1.9 92/03/31 + * + *************************************************************************/ + +#include <sys/types.h> +#include <stdio.h> +#include <signal.h> + +#ifdef STDC_HEADERS +#include <stdlib.h> +#else +#if __STDC__ +char *calloc(unsigned nelem, unsigned size); +char *malloc(unsigned size); +#else +char *calloc(); +char *malloc(); +#endif /* __STDC__ */ +#endif /* STDC_HEADERS */ + +#ifdef _MINIX +#undef POSIX /* Minix 1.6 doesn't support POSIX.1 sigaction yet */ +#endif + +#ifndef SIGTYPE +#define SIGTYPE void +#endif + +/* Define the highest signal number (usually) */ +#ifndef SIGMAX +#define SIGMAX 32 +#endif + +/* Define linked list of signal handlers structure */ +struct SIG_hlist { + SIGTYPE (*handler)(); + struct SIG_hlist *next; +}; + +/* + * Define array of lists of signal handlers. Note that this depends on + * the implementation to initialize each element to a null pointer. + */ + +static struct SIG_hlist **SIG_handlers; + +/* Define array of default signal vectors */ + +#ifdef POSIX +static struct sigaction *SIG_defaults; +#else +#ifdef BSD_SIGNALS +static struct sigvec *SIG_defaults; +#else +static SIGTYPE (**SIG_defaults)(); +#endif +#endif + +/* Critical section housekeeping */ +static int SIG_crSectNest = 0; /* Nesting level */ +#ifdef POSIX +static sigset_t SIG_crSectMask; /* Signal mask */ +#else +static int SIG_crSectMask; /* Signal mask */ +#endif + +/* + * Initialize the signal handler arrays + */ + +static int SIG_init() +{ + int i; +#ifdef POSIX + sigset_t sigset_test; +#endif + + if (SIG_defaults && SIG_handlers) /* already allocated */ + return (0); + +#ifdef POSIX + (void) sigfillset(&sigset_test); + for (i = 1; sigismember(&sigset_test, i) == 1; i++) +#ifdef BROKEN_SIGISMEMBER + if ( i >= NSIG ) + break +#endif + ; + if (i < SIGMAX) + i = SIGMAX; + i++; + if (!SIG_defaults) + SIG_defaults = (struct sigaction *) + calloc(i, sizeof(struct sigaction)); + (void) sigemptyset(&SIG_crSectMask); +#else + i = SIGMAX+1; +#ifdef BSD_SIGNALS + if (!SIG_defaults) + SIG_defaults = (struct sigvec *) + calloc(i, sizeof(struct sigvec)); +#else + if (!SIG_defaults) + SIG_defaults = (SIGTYPE (**)()) + calloc(i, sizeof(SIGTYPE (**)())); +#endif + SIG_crSectMask = 0; +#endif + if (!SIG_handlers) + SIG_handlers = (struct SIG_hlist **) + calloc(i, sizeof(struct SIG_hlist *)); + return (!SIG_defaults || !SIG_handlers); +} + +/* + * The following invokes each signal handler in the reverse order in which + * they were registered. + */ + +static SIGTYPE SIG_handle(sig) +int sig; +{ + struct SIG_hlist *this; + + /* Dispatch signal handlers */ + this = SIG_handlers[sig]; + while (this != (struct SIG_hlist *) NULL) + { + (*this->handler)(sig); + this = this->next; + } + + return; +} + +/* + * The following registers a signal handler. If the handler is already + * registered, it is not registered twice, nor is the order in which signal + * handlers are invoked changed. If this is the first signal handler + * registered for a given signal, the old sigvec structure is saved for + * restoration later. + */ + +int SIG_register(sig,fn) +int sig; +SIGTYPE (*fn)(); +{ + int val; + struct SIG_hlist *this; +#ifdef POSIX + struct sigaction act; + sigset_t sigset_mask, sigset_omask; +#else +#ifdef BSD_SIGNALS + struct sigvec vec; + int mask; +#endif +#endif + + /* Initialize */ + if (SIG_init() != 0) + return (-1); + val = 0; + + /* Block this signal while we look at handler chain */ +#ifdef POSIX + (void) sigemptyset(&sigset_mask); + (void) sigaddset(&sigset_mask, sig); + (void) sigprocmask(SIG_BLOCK, &sigset_mask, &sigset_omask); +#else +#ifdef BSD_SIGNALS + mask = sigblock(sigmask(sig)); +#endif +#endif + + /* See if this handler was already registered */ + this = SIG_handlers[sig]; + while (this != (struct SIG_hlist *) NULL) + { + if (this->handler == fn) break; + this = this->next; + } + + /* Register the new handler only if it is not already registered. */ + if (this == (struct SIG_hlist *) NULL) + { + + /* + * If this is the first handler registered for this signal, + * set up the signal handler dispatcher + */ + + if (SIG_handlers[sig] == (struct SIG_hlist *) NULL) + { +#ifdef POSIX + act.sa_handler = SIG_handle; + (void) sigemptyset(&act.sa_mask); + act.sa_flags = 0; + val = sigaction(sig, &act, &SIG_defaults[sig]); +#else +#ifdef BSD_SIGNALS + bzero((char *)&vec, sizeof(vec)); + vec.sv_handler = SIG_handle; + val = sigvec(sig, &vec, &SIG_defaults[sig]); +#else + if ((SIG_defaults[sig] = signal(sig, SIG_handle)) == + (SIGTYPE (*)()) -1) + val = -1; +#endif +#endif + } + + /* If not, register it */ + if ((val == 0) && (this == (struct SIG_hlist *) NULL)) + { + this = (struct SIG_hlist *) + malloc(sizeof(struct SIG_hlist)); + if (this == NULL) + { + val = -1; + } + else + { + this->handler = fn; + this->next = SIG_handlers[sig]; + SIG_handlers[sig] = this; + } + } + } + + /* Unblock the signal */ +#ifdef POSIX + (void) sigprocmask(SIG_SETMASK, &sigset_omask, NULL); +#else +#ifdef BSD_SIGNALS + (void) sigsetmask(mask); +#endif +#endif + + return val; +} + +/* + * The following deregisters a signal handler. If the last signal handler for + * a given signal is deregistered, the default sigvec information is restored. + */ + +int SIG_deregister(sig,fn) +int sig; +SIGTYPE (*fn)(); +{ + int val; + struct SIG_hlist *this; + struct SIG_hlist *last; +#ifdef POSIX + sigset_t sigset_mask, sigset_omask; +#else +#ifdef BSD_SIGNALS + int mask; +#endif +#endif + + /* Initialize */ + if (SIG_init() != 0) + return (-1); + val = 0; + last = (struct SIG_hlist *) NULL; + + /* Block this signal while we look at handler chain */ +#ifdef POSIX + (void) sigemptyset(&sigset_mask); + (void) sigaddset(&sigset_mask, sig); + (void) sigprocmask(SIG_BLOCK, &sigset_mask, &sigset_omask); +#else +#ifdef BSD_SIGNALS + mask = sigblock(sigmask(sig)); +#endif +#endif + + /* Search for the signal handler */ + this = SIG_handlers[sig]; + while ((this != (struct SIG_hlist *) NULL) && (this->handler != fn)) + { + last = this; + this = this->next; + } + + /* If it was registered, remove it */ + if (this != (struct SIG_hlist *) NULL) + { + if (last == (struct SIG_hlist *) NULL) + { + SIG_handlers[sig] = this->next; + } + else + { + last->next = this->next; + } + free((char *) this); + } + + /* Restore default behavior if there are no registered handlers */ + if (SIG_handlers[sig] == (struct SIG_hlist *) NULL) + { +#ifdef POSIX + val = sigaction(sig, &SIG_defaults[sig], + (struct sigaction *) NULL); +#else +#ifdef BSD_SIGNALS + val = sigvec(sig, &SIG_defaults[sig], (struct sigvec *) NULL); +#else + if (signal(sig, SIG_defaults[sig]) == (SIGTYPE (*)()) -1) + val = -1; +#endif +#endif + } + + /* Unblock the signal */ +#ifdef POSIX + (void) sigprocmask(SIG_SETMASK, &sigset_omask, NULL); +#else +#ifdef BSD_SIGNALS + (void) sigsetmask(mask); +#endif +#endif + + return val; +} + +/* + * The following begins a critical section. + */ + +void SIG_beginCrSect() +{ + if (SIG_init() == 0) + { + if (SIG_crSectNest == 0) + { +#ifdef POSIX + sigset_t sigset_mask; + + (void) sigfillset(&sigset_mask); + (void) sigprocmask(SIG_SETMASK, + &sigset_mask, &SIG_crSectMask); +#else +#ifdef BSD_SIGNALS + SIG_crSectMask = sigblock(~0); +#else + /* TBD */ +#endif +#endif + } + SIG_crSectNest++; + } +} + +/* + * The following ends a critical section. + */ + +void SIG_endCrSect() +{ + if (SIG_init() == 0) + { + SIG_crSectNest--; + if (SIG_crSectNest == 0) + { +#ifdef POSIX + (void) sigprocmask(SIG_SETMASK, &SIG_crSectMask, NULL); +#else +#ifdef BSD_SIGNALS + (void) sigsetmask(SIG_crSectMask); +#else + /* TBD */ +#endif +#endif + } + } +} diff --git a/gnu/usr.bin/cvs/lib/strdup.c b/gnu/usr.bin/cvs/lib/strdup.c new file mode 100644 index 0000000..4e5af07 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/strdup.c @@ -0,0 +1,39 @@ +/* strdup.c -- return a newly allocated copy of a string + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef STDC_HEADERS +#include <string.h> +#include <stdlib.h> +#else +char *malloc (); +char *strcpy (); +#endif + +/* Return a newly allocated copy of STR, + or 0 if out of memory. */ + +char * +strdup (str) + char *str; +{ + char *newstr; + + newstr = (char *) malloc (strlen (str) + 1); + if (newstr) + strcpy (newstr, str); + return newstr; +} diff --git a/gnu/usr.bin/cvs/lib/strippath.c b/gnu/usr.bin/cvs/lib/strippath.c new file mode 100644 index 0000000..3d606a8 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/strippath.c @@ -0,0 +1,74 @@ +/* strippath.c -- remove unnecessary components from a path specifier + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#if defined(STDC_HEADERS) || defined(USG) +#include <string.h> +#ifndef index +#define index strchr +#endif +#else +#include <strings.h> +#endif + +#include <stdio.h> + +#if __STDC__ +static void remove_component(char *beginc, char *endc); +void strip_trailing_slashes(char *path); +#else +static void remove_component(); +void strip_trailing_slashes(); +#endif /* __STDC__ */ + +/* Remove unnecessary components from PATH. */ + +void +strip_path (path) + char *path; +{ + int stripped = 0; + char *cp, *slash; + + for (cp = path; (slash = index(cp, '/')) != NULL; cp = slash) + { + *slash = '\0'; + if ((!*cp && (cp != path || stripped)) || + strcmp(cp, ".") == 0 || strcmp(cp, "/") == 0) + { + stripped = 1; + remove_component(cp, slash); + slash = cp; + } + else + { + *slash++ = '/'; + } + } + strip_trailing_slashes(path); +} + +/* Remove the component delimited by BEGINC and ENDC from the path */ + +static void +remove_component (beginc, endc) + char *beginc; + char *endc; +{ + for (endc++; *endc; endc++) + *beginc++ = *endc; + *beginc = '\0'; +} diff --git a/gnu/usr.bin/cvs/lib/stripslash.c b/gnu/usr.bin/cvs/lib/stripslash.c new file mode 100644 index 0000000..0c26ac6 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/stripslash.c @@ -0,0 +1,35 @@ +/* stripslash.c -- remove trailing slashes from a string + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#if defined(STDC_HEADERS) || defined(USG) +#include <string.h> +#else +#include <strings.h> +#endif + +/* Remove trailing slashes from PATH. */ + +void +strip_trailing_slashes (path) + char *path; +{ + int last; + + last = strlen (path) - 1; + while (last > 0 && path[last] == '/') + path[last--] = '\0'; +} diff --git a/gnu/usr.bin/cvs/lib/subr.c b/gnu/usr.bin/cvs/lib/subr.c new file mode 100644 index 0000000..2b728c2 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/subr.c @@ -0,0 +1,912 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * Various useful functions for the CVS support code. + */ + +#include "cvs.h" + +#ifdef _MINIX +#undef POSIX /* Minix 1.6 doesn't support POSIX.1 sigaction yet */ +#endif + +#ifndef VPRINTF_MISSING +#if __STDC__ +#include <stdarg.h> +#define VA_START(args, lastarg) va_start(args, lastarg) +#else +#include <varargs.h> +#define VA_START(args, lastarg) va_start(args) +#endif +#else +#define va_alist a1, a2, a3, a4, a5, a6, a7, a8 +#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; +#endif + +#ifndef lint +static char rcsid[] = "@(#)subr.c 1.52 92/03/31"; +#endif + +#if __STDC__ +static void run_add_arg (char *s); +static void run_init_prog (void); +#else +static void run_add_arg (); +static void run_init_prog (); +#endif /* __STDC__ */ + +extern char *getlogin (); +extern char *strtok (); + +/* + * Copies "from" to "to". mallocs a buffer large enough to hold the entire + * file and does one read/one write to do the copy. This is reasonable, + * since source files are typically not too large. + */ +void +copy_file (from, to) + char *from; + char *to; +{ + struct stat sb; + struct utimbuf t; + int fdin, fdout; + char *buf; + + if (trace) + (void) fprintf (stderr, "-> copy(%s,%s)\n", from, to); + if (noexec) + return; + + if ((fdin = open (from, O_RDONLY)) < 0) + error (1, errno, "cannot open %s for copying", from); + if (fstat (fdin, &sb) < 0) + error (1, errno, "cannot fstat %s", from); + if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0) + error (1, errno, "cannot create %s for copying", to); + if (sb.st_size > 0) + { + buf = xmalloc ((int) sb.st_size); + if (read (fdin, buf, (int) sb.st_size) != (int) sb.st_size) + error (1, errno, "cannot read file %s for copying", from); + if (write (fdout, buf, (int) sb.st_size) != (int) sb.st_size +#ifndef FSYNC_MISSING + || fsync (fdout) == -1 +#endif + ) + { + error (1, errno, "cannot write file %s for copying", to); + } + free (buf); + } + (void) close (fdin); + if (close (fdout) < 0) + error (1, errno, "cannot close %s", to); + + /* now, set the times for the copied file to match those of the original */ + t.actime = sb.st_atime; + t.modtime = sb.st_mtime; + (void) utime (to, &t); +} + +/* + * Returns non-zero if the argument file is a directory, or is a symbolic + * link which points to a directory. + */ +int +isdir (file) + char *file; +{ + struct stat sb; + + if (stat (file, &sb) < 0) + return (0); + return (S_ISDIR (sb.st_mode)); +} + +/* + * Returns non-zero if the argument file is a symbolic link. + */ +int +islink (file) + char *file; +{ +#ifdef S_ISLNK + struct stat sb; + + if (lstat (file, &sb) < 0) + return (0); + return (S_ISLNK (sb.st_mode)); +#else + return (0); +#endif +} + +/* + * Returns non-zero if the argument file exists. + */ +int +isfile (file) + char *file; +{ + struct stat sb; + + if (stat (file, &sb) < 0) + return (0); + return (1); +} + +/* + * Returns non-zero if the argument file is readable. + * XXX - must be careful if "cvs" is ever made setuid! + */ +int +isreadable (file) + char *file; +{ + return (access (file, R_OK) != -1); +} + +/* + * Returns non-zero if the argument file is writable + * XXX - muct be careful if "cvs" is ever made setuid! + */ +int +iswritable (file) + char *file; +{ + return (access (file, W_OK) != -1); +} + +/* + * Open a file and die if it fails + */ +FILE * +open_file (name, mode) + char *name; + char *mode; +{ + FILE *fp; + + if ((fp = fopen (name, mode)) == NULL) + error (1, errno, "cannot open %s", name); + return (fp); +} + +/* + * Open a file if allowed and return. + */ +FILE * +Fopen (name, mode) + char *name; + char *mode; +{ + if (trace) + (void) fprintf (stderr, "-> fopen(%s,%s)\n", name, mode); + if (noexec) + return (NULL); + + return (fopen (name, mode)); +} + +/* + * Make a directory and die if it fails + */ +void +make_directory (name) + char *name; +{ + struct stat buf; + + if (stat (name, &buf) == 0) + { + if (S_ISDIR (buf.st_mode)) + { + if (access (name, (R_OK | W_OK | X_OK)) == 0) + { + error (0, 0, "Directory %s already exists", name); + return; + } + else + { + error (0, 0, + "Directory %s already exists but is protected from you", + name); + } + } + else + error (0, 0, "%s already exists but is not a directory", name); + } + if (!noexec && mkdir (name, 0777) < 0) + error (1, errno, "cannot make directory %s", name); +} + +/* + * Make a path to the argument directory, printing a message if something + * goes wrong. + */ +void +make_directories (name) + char *name; +{ + char *cp; + + if (noexec) + return; + + if (mkdir (name, 0777) == 0 || errno == EEXIST) + return; + if (errno != ENOENT) + { + error (0, errno, "cannot make path to %s", name); + return; + } + if ((cp = rindex (name, '/')) == NULL) + return; + *cp = '\0'; + make_directories (name); + *cp++ = '/'; + if (*cp == '\0') + return; + (void) mkdir (name, 0777); +} + +/* + * malloc some data and die if it fails + */ +char * +xmalloc (bytes) + int bytes; +{ + char *cp; + + if (bytes <= 0) + error (1, 0, "bad malloc size %d", bytes); + if ((cp = malloc ((unsigned) bytes)) == NULL) + error (1, 0, "malloc failed"); + return (cp); +} + +/* + * realloc data and die if it fails [I've always wanted to have "realloc" do + * a "malloc" if the argument is NULL, but you can't depend on it. Here, I + * can *force* it. + */ +char * +xrealloc (ptr, bytes) + char *ptr; + int bytes; +{ + char *cp; + + if (!ptr) + return (xmalloc (bytes)); + + if (bytes <= 0) + error (1, 0, "bad realloc size %d", bytes); + if ((cp = realloc (ptr, (unsigned) bytes)) == NULL) + error (1, 0, "realloc failed"); + return (cp); +} + +/* + * Duplicate a string, calling xmalloc to allocate some dynamic space + */ +char * +xstrdup (str) + char *str; +{ + char *s; + + if (str == NULL) + return ((char *) NULL); + s = xmalloc (strlen (str) + 1); + (void) strcpy (s, str); + return (s); +} + +/* + * Change the mode of a file, either adding write permissions, or removing + * all write permissions. Adding write permissions honors the current umask + * setting. + */ +void +xchmod (fname, writable) + char *fname; + int writable; +{ + struct stat sb; + int mode, oumask; + + if (stat (fname, &sb) < 0) + { + if (!noexec) + error (0, errno, "cannot stat %s", fname); + return; + } + if (writable) + { + oumask = umask (0); + (void) umask (oumask); + mode = sb.st_mode | ((S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask); + } + else + { + mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH); + } + + if (trace) + (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, mode); + if (noexec) + return; + + if (chmod (fname, mode) < 0) + error (0, errno, "cannot change mode of file %s", fname); +} + +/* + * Rename a file and die if it fails + */ +void +rename_file (from, to) + char *from; + char *to; +{ + if (trace) + (void) fprintf (stderr, "-> rename(%s,%s)\n", from, to); + if (noexec) + return; + + if (rename (from, to) < 0) + error (1, errno, "cannot rename file %s to %s", from, to); +} + +/* + * link a file, if possible. + */ +int +link_file (from, to) + char *from, *to; +{ + if (trace) + (void) fprintf (stderr, "-> link(%s,%s)\n", from, to); + if (noexec) + return (0); + + return (link (from, to)); +} + +/* + * unlink a file, if possible. + */ +int +unlink_file (f) + char *f; +{ + if (trace) + (void) fprintf (stderr, "-> unlink(%s)\n", f); + if (noexec) + return (0); + + return (unlink (f)); +} + +/* + * Compare "file1" to "file2". Return non-zero if they don't compare exactly. + * + * mallocs a buffer large enough to hold the entire file and does two reads to + * load the buffer and calls bcmp to do the cmp. This is reasonable, since + * source files are typically not too large. + */ +int +xcmp (file1, file2) + char *file1; + char *file2; +{ + register char *buf1, *buf2; + struct stat sb; + off_t size; + int ret, fd1, fd2; + + if ((fd1 = open (file1, O_RDONLY)) < 0) + error (1, errno, "cannot open file %s for comparing", file1); + if ((fd2 = open (file2, O_RDONLY)) < 0) + error (1, errno, "cannot open file %s for comparing", file2); + if (fstat (fd1, &sb) < 0) + error (1, errno, "cannot fstat %s", file1); + size = sb.st_size; + if (fstat (fd2, &sb) < 0) + error (1, errno, "cannot fstat %s", file2); + if (size == sb.st_size) + { + if (size == 0) + ret = 0; + else + { + buf1 = xmalloc ((int) size); + buf2 = xmalloc ((int) size); + if (read (fd1, buf1, (int) size) != (int) size) + error (1, errno, "cannot read file %s cor comparing", file1); + if (read (fd2, buf2, (int) size) != (int) size) + error (1, errno, "cannot read file %s for comparing", file2); + ret = bcmp (buf1, buf2, (int) size); + free (buf1); + free (buf2); + } + } + else + ret = 1; + (void) close (fd1); + (void) close (fd2); + return (ret); +} + +/* + * Recover the space allocated by Find_Names() and line2argv() + */ +void +free_names (pargc, argv) + int *pargc; + char *argv[]; +{ + register int i; + + for (i = 0; i < *pargc; i++) + { /* only do through *pargc */ + free (argv[i]); + } + *pargc = 0; /* and set it to zero when done */ +} + +/* + * Convert a line into argc/argv components and return the result in the + * arguments as passed. Use free_names() to return the memory allocated here + * back to the free pool. + */ +void +line2argv (pargc, argv, line) + int *pargc; + char *argv[]; + char *line; +{ + char *cp; + + *pargc = 0; + for (cp = strtok (line, " \t"); cp; cp = strtok ((char *) NULL, " \t")) + { + argv[*pargc] = xstrdup (cp); + (*pargc)++; + } +} + +/* + * Returns the number of dots ('.') found in an RCS revision number + */ +int +numdots (s) + char *s; +{ + char *cp; + int dots = 0; + + for (cp = s; *cp; cp++) + { + if (*cp == '.') + dots++; + } + return (dots); +} + +/* + * Get the caller's login from his uid. If the real uid is "root" try LOGNAME + * USER or getlogin(). If getlogin() and getpwuid() both fail, return + * the uid as a string. + */ +char * +getcaller () +{ + static char uidname[20]; + struct passwd *pw; + char *name; + int uid; + + uid = getuid (); + if (uid == 0) + { + /* super-user; try getlogin() to distinguish */ + if (((name = getenv("LOGNAME")) || (name = getenv("USER")) || + (name = getlogin ())) && *name) + return (name); + } + if ((pw = (struct passwd *) getpwuid (uid)) == NULL) + { + (void) sprintf (uidname, "uid%d", uid); + return (uidname); + } + return (pw->pw_name); +} + +/* + * To exec a program under CVS, first call run_setup() to setup any initial + * arguments. The options to run_setup are essentially like printf(). The + * arguments will be parsed into whitespace separated words and added to the + * global run_argv list. + * + * Then, optionally call run_arg() for each additional argument that you'd like + * to pass to the executed program. + * + * Finally, call run_exec() to execute the program with the specified arguments. + * The execvp() syscall will be used, so that the PATH is searched correctly. + * File redirections can be performed in the call to run_exec(). + */ +static char *run_prog; +static char **run_argv; +static int run_argc; +static int run_argc_allocated; + +/* VARARGS */ +#if !defined (VPRINTF_MISSING) && __STDC__ +void +run_setup (char *fmt,...) +#else +void +run_setup (fmt, va_alist) + char *fmt; + va_dcl + +#endif +{ +#ifndef VPRINTF_MISSING + va_list args; + +#endif + char *cp; + int i; + + run_init_prog (); + + /* clean out any malloc'ed values from run_argv */ + for (i = 0; i < run_argc; i++) + { + if (run_argv[i]) + { + free (run_argv[i]); + run_argv[i] = (char *) 0; + } + } + run_argc = 0; + + /* process the varargs into run_prog */ +#ifndef VPRINTF_MISSING + VA_START (args, fmt); + (void) vsprintf (run_prog, fmt, args); + va_end (args); +#else + (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); +#endif + + /* put each word into run_argv, allocating it as we go */ + for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t")) + run_add_arg (cp); +} + +void +run_arg (s) + char *s; +{ + run_add_arg (s); +} + +/* VARARGS */ +#if !defined (VPRINTF_MISSING) && __STDC__ +void +run_args (char *fmt,...) +#else +void +run_args (fmt, va_alist) + char *fmt; + va_dcl + +#endif +{ +#ifndef VPRINTF_MISSING + va_list args; + +#endif + + run_init_prog (); + + /* process the varargs into run_prog */ +#ifndef VPRINTF_MISSING + VA_START (args, fmt); + (void) vsprintf (run_prog, fmt, args); + va_end (args); +#else + (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); +#endif + + /* and add the (single) argument to the run_argv list */ + run_add_arg (run_prog); +} + +static void +run_add_arg (s) + char *s; +{ + /* allocate more argv entries if we've run out */ + if (run_argc >= run_argc_allocated) + { + run_argc_allocated += 50; + run_argv = (char **) xrealloc ((char *) run_argv, + run_argc_allocated * sizeof (char **)); + } + + if (s) + run_argv[run_argc++] = xstrdup (s); + else + run_argv[run_argc] = (char *) 0;/* not post-incremented on purpose! */ +} + +static void +run_init_prog () +{ + /* make sure that run_prog is allocated once */ + if (run_prog == (char *) 0) + run_prog = xmalloc (10 * 1024); /* 10K of args for _setup and _arg */ +} + +int +run_exec (stin, stout, sterr, flags) + char *stin; + char *stout; + char *sterr; + int flags; +{ + int shin, shout, sherr; + int mode_out, mode_err; + int status = -1; + int rerrno = 0; + int pid, w; + +#ifdef POSIX + sigset_t sigset_mask, sigset_omask; + struct sigaction act, iact, qact; + +#else +#ifdef BSD_SIGNALS + int mask; + struct sigvec vec, ivec, qvec; + +#else + SIGTYPE (*istat) (), (*qstat) (); +#endif +#endif + + if (trace) + { + (void) fprintf (stderr, "-> system("); + run_print (stderr); + (void) fprintf (stderr, ")\n"); + } + if (noexec && (flags & RUN_REALLY) == 0) + return (0); + + /* make sure that we are null terminated, since we didn't calloc */ + run_add_arg ((char *) 0); + + /* setup default file descriptor numbers */ + shin = 0; + shout = 1; + sherr = 2; + + /* set the file modes for stdout and stderr */ + mode_out = mode_err = O_WRONLY | O_CREAT; + mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC); + mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC); + + if (stin && (shin = open (stin, O_RDONLY)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for reading (prog %s)", + stin, run_argv[0]); + goto out0; + } + if (stout && (shout = open (stout, mode_out, 0666)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for writing (prog %s)", + stout, run_argv[0]); + goto out1; + } + if (sterr && (flags & RUN_COMBINED) == 0) + { + if ((sherr = open (sterr, mode_err, 0666)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for writing (prog %s)", + sterr, run_argv[0]); + goto out2; + } + } + + /* The output files, if any, are now created. Do the fork and dups */ +#ifdef VFORK_MISSING + pid = fork (); +#else + pid = vfork (); +#endif + if (pid == 0) + { + if (shin != 0) + { + (void) dup2 (shin, 0); + (void) close (shin); + } + if (shout != 1) + { + (void) dup2 (shout, 1); + (void) close (shout); + } + if (flags & RUN_COMBINED) + (void) dup2 (1, 2); + else if (sherr != 2) + { + (void) dup2 (sherr, 2); + (void) close (sherr); + } + + /* dup'ing is done. try to run it now */ + (void) execvp (run_argv[0], run_argv); + _exit (127); + } + else if (pid == -1) + { + rerrno = errno; + goto out; + } + + /* the parent. Ignore some signals for now */ +#ifdef POSIX + if (flags & RUN_SIGIGNORE) + { + act.sa_handler = SIG_IGN; + (void) sigemptyset (&act.sa_mask); + act.sa_flags = 0; + (void) sigaction (SIGINT, &act, &iact); + (void) sigaction (SIGQUIT, &act, &qact); + } + else + { + (void) sigemptyset (&sigset_mask); + (void) sigaddset (&sigset_mask, SIGINT); + (void) sigaddset (&sigset_mask, SIGQUIT); + (void) sigprocmask (SIG_SETMASK, &sigset_mask, &sigset_omask); + } +#else +#ifdef BSD_SIGNALS + if (flags & RUN_SIGIGNORE) + { + bzero ((char *) &vec, sizeof (vec)); + vec.sv_handler = SIG_IGN; + (void) sigvec (SIGINT, &vec, &ivec); + (void) sigvec (SIGQUIT, &vec, &qvec); + } + else + mask = sigblock (sigmask (SIGINT) | sigmask (SIGQUIT)); +#else + istat = signal (SIGINT, SIG_IGN); + qstat = signal (SIGQUIT, SIG_IGN); +#endif +#endif + + /* wait for our process to die and munge return status */ +#ifdef POSIX + while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR) + ; +#else + while ((w = wait (&status)) != pid) + { + if (w == -1 && errno != EINTR) + break; + } +#endif + if (w == -1) + { + status = -1; + rerrno = errno; + } + else if (WIFEXITED (status)) + status = WEXITSTATUS (status); + else if (WIFSIGNALED (status)) + { + if (WTERMSIG (status) == SIGPIPE) + error (1, 0, "broken pipe"); + status = 2; + } + else + status = 1; + + /* restore the signals */ +#ifdef POSIX + if (flags & RUN_SIGIGNORE) + { + (void) sigaction (SIGINT, &iact, (struct sigaction *) NULL); + (void) sigaction (SIGQUIT, &qact, (struct sigaction *) NULL); + } + else + (void) sigprocmask (SIG_SETMASK, &sigset_omask, (sigset_t *) NULL); +#else +#ifdef BSD_SIGNALS + if (flags & RUN_SIGIGNORE) + { + (void) sigvec (SIGINT, &ivec, (struct sigvec *) NULL); + (void) sigvec (SIGQUIT, &qvec, (struct sigvec *) NULL); + } + else + (void) sigsetmask (mask); +#else + (void) signal (SIGINT, istat); + (void) signal (SIGQUIT, qstat); +#endif +#endif + + /* cleanup the open file descriptors */ + out: + if (sterr) + (void) close (sherr); + out2: + if (stout) + (void) close (shout); + out1: + if (stin) + (void) close (shin); + + out0: + if (rerrno) + errno = rerrno; + return (status); +} + +void +run_print (fp) + FILE *fp; +{ + int i; + + for (i = 0; i < run_argc; i++) + { + (void) fprintf (fp, "%s", run_argv[i]); + if (i != run_argc - 1) + (void) fprintf (fp, " "); + } +} + +FILE * +Popen (cmd, mode) + char *cmd, *mode; +{ + if (trace) + (void) fprintf (stderr, "-> Popen(%s,%s)\n", cmd, mode); + if (noexec) + return (NULL); + return (popen (cmd, mode)); +} + +#ifdef lint +#ifndef __GNUC__ +/* ARGSUSED */ +time_t +get_date (date, now) + char *date; + struct timeb *now; +{ + time_t foo = 0; + + return (foo); +} +#endif +#endif diff --git a/gnu/usr.bin/cvs/lib/system.h b/gnu/usr.bin/cvs/lib/system.h new file mode 100644 index 0000000..6cfd68f --- /dev/null +++ b/gnu/usr.bin/cvs/lib/system.h @@ -0,0 +1,223 @@ +/* system-dependent definitions for CVS. + Copyright (C) 1989-1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* @(#)system.h 1.14 92/04/10 */ + +#include <sys/types.h> +#include <sys/stat.h> +#ifndef S_ISREG /* Doesn't have POSIX.1 stat stuff. */ +#ifndef mode_t +#define mode_t unsigned short +#endif +#endif +#if !defined(S_ISBLK) && defined(S_IFBLK) +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#endif +#if !defined(S_ISCHR) && defined(S_IFCHR) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISDIR) && defined(S_IFDIR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) && defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISFIFO) && defined(S_IFIFO) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISLNK) && defined(S_IFLNK) +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) && defined(S_IFSOCK) +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */ +#define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB) +#define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC) +#endif +#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */ +#define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK) +#endif +#if defined(MKFIFO_MISSING) +#define mkfifo(path, mode) (mknod ((path), (mode) | S_IFIFO, 0)) +#endif + +#ifdef POSIX +#include <unistd.h> +#include <limits.h> +#ifndef PATH_MAX +#define PATH_MAX pathconf ("/", _PC_PATH_MAX) +#endif +#else +off_t lseek (); +#endif + +#ifdef TM_IN_SYS_TIME +#include <sys/time.h> +#else +#include <time.h> +#endif + +#ifdef TIMEB_H_MISSING +struct timeb { + time_t time; /* Seconds since the epoch */ + unsigned short millitm; /* Field not used */ +#ifdef timezone + short tzone; +#else + short timezone; +#endif + short dstflag; /* Field not used */ +}; +#else +#include <sys/timeb.h> +#endif + +#if defined(FTIME_MISSING) && !defined(HAVE_TIMEZONE) +#if !defined(timezone) +extern char *timezone(); +#endif +#endif + +#ifndef POSIX +#include <sys/param.h> +#endif + +#ifndef _POSIX_PATH_MAX +#define _POSIX_PATH_MAX 255 +#endif + +#ifndef PATH_MAX +#ifdef MAXPATHLEN +#define PATH_MAX MAXPATHLEN +#else +#define PATH_MAX _POSIX_PATH_MAX +#endif +#endif + +#ifdef POSIX +#include <utime.h> +#else +#ifndef ALTOS +struct utimbuf +{ + long actime; + long modtime; +}; +#endif +int utime (); +#endif + +#if defined(USG) || defined(STDC_HEADERS) +#include <string.h> +#ifndef STDC_HEADERS +#include <memory.h> +#endif +#ifndef index +#define index strchr +#endif +#ifndef rindex +#define rindex strrchr +#endif +#ifndef bcopy +#define bcopy(from, to, len) memcpy ((to), (from), (len)) +#endif +#ifndef bzero +#define bzero(s, n) (void) memset ((s), 0, (n)) +#endif +#ifndef bcmp +#define bcmp(s1, s2, n) memcmp((s1), (s2), (n)) +#endif +#else +#include <strings.h> +#endif + +#include <errno.h> +#ifdef STDC_HEADERS +#include <stdlib.h> +#else +char *getenv (); +char *malloc (); +char *realloc (); +char *calloc (); +extern int errno; +#endif + +#ifdef __GNUC__ +#ifdef bsdi +#define alloca __builtin_alloca +#endif +#else +#ifdef sparc +#include <alloca.h> +#else +#ifndef _AIX +/* AIX alloca decl has to be the first thing in the file, bletch! */ +char *alloca (); +#endif +#endif +#endif + +#if defined(USG) || defined(POSIX) +#include <fcntl.h> +char *getcwd (); +#else +#include <sys/file.h> +char *getwd (); +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#endif +#ifndef F_OK +#define F_OK 0 +#define X_OK 1 +#define W_OK 2 +#define R_OK 4 +#endif + +#ifdef DIRENT +#include <dirent.h> +#ifdef direct +#undef direct +#endif +#define direct dirent +#else +#ifdef SYSNDIR +#include <sys/ndir.h> +#else +#ifdef NDIR +#include <ndir.h> +#else /* must be BSD */ +#include <sys/dir.h> +#endif +#endif +#endif + +/* Convert B 512-byte blocks to kilobytes if K is nonzero, + otherwise return it unchanged. */ +#define convert_blocks(b, k) ((k) ? ((b) + 1) / 2 : (b)) + +#ifndef S_ISLNK +#define lstat stat +#endif + +#ifndef SIGTYPE +#define SIGTYPE void +#endif diff --git a/gnu/usr.bin/cvs/lib/wait.h b/gnu/usr.bin/cvs/lib/wait.h new file mode 100644 index 0000000..49cfb6d --- /dev/null +++ b/gnu/usr.bin/cvs/lib/wait.h @@ -0,0 +1,29 @@ +/* wait.h -- POSIX macros for evaluating exit statuses + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifdef POSIX +#include <sys/types.h> /* For pid_t. */ +#include <sys/wait.h> +#else +#define WIFSTOPPED(w) (((w) & 0xff) == 0x7f) +#define WIFSIGNALED(w) (((w) & 0xff) != 0x7f && ((w) & 0xff) != 0) +#define WIFEXITED(w) (((w) & 0xff) == 0) + +#define WSTOPSIG(w) (((w) >> 8) & 0xff) +#define WTERMSIG(w) ((w) & 0x7f) +#define WEXITSTATUS(w) (((w) >> 8) & 0xff) +#endif diff --git a/gnu/usr.bin/cvs/lib/y.tab.h b/gnu/usr.bin/cvs/lib/y.tab.h new file mode 100644 index 0000000..4a541d2 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/y.tab.h @@ -0,0 +1,18 @@ +#define tAGO 257 +#define tDAY 258 +#define tDAYZONE 259 +#define tID 260 +#define tMERIDIAN 261 +#define tMINUTE_UNIT 262 +#define tMONTH 263 +#define tMONTH_UNIT 264 +#define tSEC_UNIT 265 +#define tSNUMBER 266 +#define tUNUMBER 267 +#define tZONE 268 +#define tDST 269 +typedef union { + time_t Number; + enum _MERIDIAN Meridian; +} YYSTYPE; +extern YYSTYPE yylval; diff --git a/gnu/usr.bin/cvs/lib/yesno.c b/gnu/usr.bin/cvs/lib/yesno.c new file mode 100644 index 0000000..a705da7 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/yesno.c @@ -0,0 +1,37 @@ +/* yesno.c -- read a yes/no response from stdin + Copyright (C) 1990 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <stdio.h> + +/* Read one line from standard input + and return nonzero if that line begins with y or Y, + otherwise return 0. */ + +int +yesno () +{ + int c; + int rv; + + fflush (stderr); + c = getchar (); + rv = (c == 'y') || (c == 'Y'); + while (c != EOF && c != '\n') + c = getchar (); + + return rv; +} diff --git a/gnu/usr.bin/cvs/mkmodules/Makefile b/gnu/usr.bin/cvs/mkmodules/Makefile new file mode 100644 index 0000000..9f66fde --- /dev/null +++ b/gnu/usr.bin/cvs/mkmodules/Makefile @@ -0,0 +1,8 @@ +PROG = mkmodules +SRCS = mkmodules.c + +CFLAGS += -I${.CURDIR}/../cvs -I${.CURDIR}/../lib +LDADD= -L${.CURDIR}/../lib/obj -lcvs + +.include <bsd.prog.mk> +.include "../../Makefile.inc" diff --git a/gnu/usr.bin/cvs/mkmodules/mkmodules.1 b/gnu/usr.bin/cvs/mkmodules/mkmodules.1 new file mode 100644 index 0000000..07ba3f5 --- /dev/null +++ b/gnu/usr.bin/cvs/mkmodules/mkmodules.1 @@ -0,0 +1,65 @@ +.\" +.\" @(#)mkmodules.1 1.3 92/01/30 +.\" +.TH MKMODULES 1 "12 October 1991" +.SH "NAME" +mkmodules \- Rebuild modules database for CVS +.SH "SYNOPSIS" +.B mkmodules +.I directory +.SH "DESCRIPTION" +.B mkmodules +rebuilds the modules database that +.BR cvs (1) +uses. +The +.I directory +specified is expected to contain the +.BR modules,v " and " loginfo,v +files. +.B mkmodules +carefully checks out the current head revisions of each of these files and +reuilds the +.BR ndbm (3) +format modules database. +A warning is generated if the modules file contains a duplicate key. +.SH "FILES" +.TP +modules,v +The modules +.SM RCS +file. +.TP +modules +The checked out modules file. +.TP +loginfo,v +The loginfo +.SM RCS +file. +.TP +loginfo +The checked out loginfo file. +.TP +modules.dir, modules.pag +The +.BR ndbm (1) +format modules database. +.SH "ENVIRONMENT VARIABLES" +.TP +.SM RCSBIN +Specifies the full pathname where to find +.SM RCS +programs, such as +.BR co (1) +and +.BR ci (1). +If not set, the default is +.BR /usr/local/bin . +.SH "SEE ALSO" +.BR checkin (1), +.BR co (1), +.BR cvs (1), +.BR ndbm (3), +.BR rcs (1), +.SH "BUGS" diff --git a/gnu/usr.bin/cvs/mkmodules/mkmodules.c b/gnu/usr.bin/cvs/mkmodules/mkmodules.c new file mode 100644 index 0000000..59ec13a --- /dev/null +++ b/gnu/usr.bin/cvs/mkmodules/mkmodules.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.3 kit. + * + * mkmodules + * + * Re-build the modules database for the CVS system. Accepts one argument, + * which is the directory that the modules,v file lives in. + */ + +#include "cvs.h" + +#undef PATH_MAX +#define PATH_MAX 1024 /* max number of bytes in pathname */ + +#ifndef lint +static char rcsid[] = "@(#)mkmodules.c 1.39 92/03/31"; +#endif + +#ifndef DBLKSIZ +#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */ +#endif + +char *program_name, *command_name; + +char *Rcsbin = RCSBIN_DFLT; +int noexec = 0; /* Here only to satisfy use in subr.c */ +int trace = 0; /* Here only to satisfy use in subr.c */ + +#if __STDC__ +static int checkout_file (char *file, char *temp); +static void make_tempfile (char *temp); +static void mkmodules_usage (void); +static void rename_rcsfile (char *temp, char *real); + +#ifndef MY_NDBM +static void rename_dbmfile (char *temp); +static void write_dbmfile (char *temp); +#endif /* !MY_NDBM */ + +#else /* !__STDC__ */ + +static void make_tempfile (); +static int checkout_file (); +static void rename_rcsfile (); +static void mkmodules_usage (); + +#ifndef MY_NDBM +static void write_dbmfile (); +static void rename_dbmfile (); +#endif /* !MY_NDBM */ + +#endif /* __STDC__ */ + +int +main (argc, argv) + int argc; + char *argv[]; +{ + extern char *getenv (); + char temp[PATH_MAX]; + char *cp; +#ifdef MY_NDBM + DBM *db; +#endif + + /* + * Just save the last component of the path for error messages + */ + if ((program_name = rindex (argv[0], '/')) == NULL) + program_name = argv[0]; + else + program_name++; + + if (argc != 2) + mkmodules_usage (); + + if ((cp = getenv (RCSBIN_ENV)) != NULL) + Rcsbin = cp; + + /* + * If Rcsbin is set to something, make sure it is terminated with a slash + * character. If not, add one. + */ + if (Rcsbin[0] != '\0') + { + int len = strlen (Rcsbin); + char *rcsbin; + + if (Rcsbin[len - 1] != '/') + { + rcsbin = Rcsbin; + Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */ + (void) strcpy (Rcsbin, rcsbin); + (void) strcat (Rcsbin, "/"); + } + } + + if (chdir (argv[1]) < 0) + error (1, errno, "cannot chdir to %s", argv[1]); + + /* + * First, do the work necessary to update the "modules" database. + */ + make_tempfile (temp); + switch (checkout_file (CVSROOTADM_MODULES, temp)) + { + + case 0: /* everything ok */ +#ifdef MY_NDBM + /* open it, to generate any duplicate errors */ + if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL) + dbm_close (db); +#else + write_dbmfile (temp); + rename_dbmfile (temp); +#endif + rename_rcsfile (temp, CVSROOTADM_MODULES); + break; + + case -1: /* fork failed */ + (void) unlink_file (temp); + exit (1); + /* NOTREACHED */ + + default: + error (0, 0, + "'cvs checkout' is less functional without a %s file", + CVSROOTADM_MODULES); + break; + } /* switch on checkout_file() */ + + (void) unlink_file (temp); + + /* + * Now, check out the "loginfo" file, so that it is always up-to-date in + * the CVSROOT directory. + */ + make_tempfile (temp); + if (checkout_file (CVSROOTADM_LOGINFO, temp) == 0) + rename_rcsfile (temp, CVSROOTADM_LOGINFO); + else + error (0, 0, + "no logging of 'cvs commit' messages is done without a %s file", + CVSROOTADM_LOGINFO); + (void) unlink_file (temp); + + /* + * Now, check out the "rcsinfo" file, so that it is always up-to-date in + * the CVSROOT directory. + */ + make_tempfile (temp); + if (checkout_file (CVSROOTADM_RCSINFO, temp) == 0) + rename_rcsfile (temp, CVSROOTADM_RCSINFO); + else + error (0, 0, + "a %s file can be used to configure 'cvs commit' templates", + CVSROOTADM_RCSINFO); + (void) unlink_file (temp); + + /* + * Now, check out the "editinfo" file, so that it is always up-to-date in + * the CVSROOT directory. + */ + make_tempfile (temp); + if (checkout_file (CVSROOTADM_EDITINFO, temp) == 0) + rename_rcsfile (temp, CVSROOTADM_EDITINFO); + else + error (0, 0, + "a %s file can be used to validate log messages", + CVSROOTADM_EDITINFO); + (void) unlink_file (temp); + + /* + * Now, check out the "commitinfo" file, so that it is always up-to-date + * in the CVSROOT directory. + */ + make_tempfile (temp); + if (checkout_file (CVSROOTADM_COMMITINFO, temp) == 0) + rename_rcsfile (temp, CVSROOTADM_COMMITINFO); + else + error (0, 0, + "a %s file can be used to configure 'cvs commit' checking", + CVSROOTADM_COMMITINFO); + (void) unlink_file (temp); + return (0); +} + +/* + * Yeah, I know, there are NFS race conditions here. + */ +static void +make_tempfile (temp) + char *temp; +{ + static int seed = 0; + int fd; + + if (seed == 0) + seed = getpid (); + while (1) + { + (void) sprintf (temp, "%s%d", BAKPREFIX, seed++); + if ((fd = open (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1) + break; + if (errno != EEXIST) + error (1, errno, "cannot create temporary file %s", temp); + } + if (close(fd) < 0) + error(1, errno, "cannot close temporary file %s", temp); +} + +static int +checkout_file (file, temp) + char *file; + char *temp; +{ + char rcs[PATH_MAX]; + int retcode = 0; + + (void) sprintf (rcs, "%s%s", file, RCSEXT); + if (!isfile (rcs)) + return (1); + run_setup ("%s%s -q -p", Rcsbin, RCS_CO); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, temp, RUN_TTY, RUN_NORMAL)) != 0) + { + error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file); + } + return (retcode); +} + +#ifndef MY_NDBM + +static void +write_dbmfile (temp) + char *temp; +{ + char line[DBLKSIZ], value[DBLKSIZ]; + FILE *fp; + DBM *db; + char *cp, *vp; + datum key, val; + int len, cont, err = 0; + + fp = open_file (temp, "r"); + if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL) + error (1, errno, "cannot open dbm file %s for creation", temp); + for (cont = 0; fgets (line, sizeof (line), fp) != NULL;) + { + if ((cp = rindex (line, '\n')) != NULL) + *cp = '\0'; /* strip the newline */ + + /* + * Add the line to the value, at the end if this is a continuation + * line; otherwise at the beginning, but only after any trailing + * backslash is removed. + */ + vp = value; + if (cont) + vp += strlen (value); + + /* + * See if the line we read is a continuation line, and strip the + * backslash if so. + */ + len = strlen (line); + if (len > 0) + cp = &line[len - 1]; + else + cp = line; + if (*cp == '\\') + { + cont = 1; + *cp = '\0'; + } + else + { + cont = 0; + } + (void) strcpy (vp, line); + if (value[0] == '#') + continue; /* comment line */ + vp = value; + while (*vp && isspace (*vp)) + vp++; + if (*vp == '\0') + continue; /* empty line */ + + /* + * If this was not a continuation line, add the entry to the database + */ + if (!cont) + { + key.dptr = vp; + while (*vp && !isspace (*vp)) + vp++; + key.dsize = vp - key.dptr; + *vp++ = '\0'; /* NULL terminate the key */ + while (*vp && isspace (*vp)) + vp++; /* skip whitespace to value */ + if (*vp == '\0') + { + error (0, 0, "warning: NULL value for key `%s'", key.dptr); + continue; + } + val.dptr = vp; + val.dsize = strlen (vp); + if (dbm_store (db, key, val, DBM_INSERT) == 1) + { + error (0, 0, "duplicate key found for `%s'", key.dptr); + err++; + } + } + } + dbm_close (db); + (void) fclose (fp); + if (err) + { + char dotdir[50], dotpag[50]; + + (void) sprintf (dotdir, "%s.dir", temp); + (void) sprintf (dotpag, "%s.pag", temp); + (void) unlink_file (dotdir); + (void) unlink_file (dotpag); + error (1, 0, "DBM creation failed; correct above errors"); + } +} + +static void +rename_dbmfile (temp) + char *temp; +{ + char newdir[50], newpag[50]; + char dotdir[50], dotpag[50]; + char bakdir[50], bakpag[50]; + + (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES); + (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES); + (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES); + (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES); + (void) sprintf (newdir, "%s.dir", temp); + (void) sprintf (newpag, "%s.pag", temp); + + (void) chmod (newdir, 0666); + (void) chmod (newpag, 0666); + + /* don't mess with me */ + SIG_beginCrSect (); + + (void) unlink_file (bakdir); /* rm .#modules.dir .#modules.pag */ + (void) unlink_file (bakpag); + (void) rename (dotdir, bakdir); /* mv modules.dir .#modules.dir */ + (void) rename (dotpag, bakpag); /* mv modules.pag .#modules.pag */ + (void) rename (newdir, dotdir); /* mv "temp".dir modules.dir */ + (void) rename (newpag, dotpag); /* mv "temp".pag modules.pag */ + + /* OK -- make my day */ + SIG_endCrSect (); +} + +#endif /* !MY_NDBM */ + +static void +rename_rcsfile (temp, real) + char *temp; + char *real; +{ + char bak[50]; + + if (chmod (temp, 0444) < 0) /* chmod 444 "temp" */ + error (0, errno, "warning: cannot chmod %s", temp); + (void) sprintf (bak, "%s%s", BAKPREFIX, real); + (void) unlink_file (bak); /* rm .#loginfo */ + (void) rename (real, bak); /* mv loginfo .#loginfo */ + (void) rename (temp, real); /* mv "temp" loginfo */ +} + +/* + * For error() only + */ +void +Lock_Cleanup () +{ +} + +static void +mkmodules_usage () +{ + (void) fprintf (stderr, "Usage: %s modules-directory\n", program_name); + exit (1); +} diff --git a/gnu/usr.bin/cvs/mkmodules/xxx b/gnu/usr.bin/cvs/mkmodules/xxx new file mode 100644 index 0000000..f0dd87d --- /dev/null +++ b/gnu/usr.bin/cvs/mkmodules/xxx @@ -0,0 +1,5320 @@ +# 1 "/usr/src/gnu/cvs/mkmodules/mkmodules.c" + + + + + + + + + + + + + +# 1 "/usr/include/sys/syslimits.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 14 "/usr/src/gnu/cvs/mkmodules/mkmodules.c" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 1 + + +# 1 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 1 + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/types.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +typedef unsigned char u_char; +typedef unsigned short u_short; +typedef unsigned int u_int; +typedef unsigned long u_long; +typedef unsigned short ushort; + +typedef char * caddr_t; +typedef long daddr_t; +typedef short dev_t; +typedef u_long ino_t; +typedef long off_t; +typedef u_short nlink_t; +typedef long swblk_t; +typedef long segsz_t; +typedef u_short uid_t; +typedef u_short gid_t; +typedef short pid_t; +typedef u_short mode_t; +typedef u_long fixpt_t; + + +typedef struct _uquad { u_long val[2]; } u_quad; +typedef struct _quad { long val[2]; } quad; +typedef long * qaddr_t; + + + + + + +# 1 "/usr/include/machine/ansi.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 69 "/usr/include/sys/types.h" 2 + + +# 1 "/usr/include/machine/types.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +typedef struct _physadr { + int r[1]; +} *physadr; + +typedef struct label_t { + int val[6]; +} label_t; + +typedef u_long vm_offset_t; +typedef u_long vm_size_t; + + +# 71 "/usr/include/sys/types.h" 2 + + + + +typedef unsigned long clock_t; + + + + +typedef unsigned int size_t; + + + + +typedef long time_t; + + + + + + + + + + + + + + + + +typedef long fd_mask; + + + + + + +typedef struct fd_set { + fd_mask fds_bits[(((256 )+(( (sizeof(fd_mask) * 8 ) )-1))/( (sizeof(fd_mask) * 8 ) )) ]; +} fd_set; + + + + + + +# 132 "/usr/include/sys/types.h" + + + + +# 20 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + +# 1 "/usr/include/sys/stat.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +struct stat +{ + dev_t st_dev; + ino_t st_ino; + mode_t st_mode; + nlink_t st_nlink; + uid_t st_uid; + gid_t st_gid; + dev_t st_rdev; + off_t st_size; + time_t st_atime; + long st_spare1; + time_t st_mtime; + long st_spare2; + time_t st_ctime; + long st_spare3; + long st_blksize; + long st_blocks; + u_long st_flags; + u_long st_gen; +}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 76 "/usr/include/sys/cdefs.h" + + + +# 114 "/usr/include/sys/stat.h" 2 + + + +mode_t umask (mode_t) ; +int chmod (const char *, mode_t) ; +int fstat (int, struct stat *) ; +int mkdir (const char *, mode_t) ; +int mkfifo (const char *, mode_t) ; +int stat (const char *, struct stat *) ; + +int fchmod (int, mode_t) ; +int lstat (const char *, struct stat *) ; + + + +# 21 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +off_t lseek (); + + + + + +# 1 "/usr/include/time.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/machine/ansi.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 57 "/usr/include/machine/ansi.h" + +# 39 "/usr/include/time.h" 2 + + + + + + + + + + + + + + + + + + + + + +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; + long tm_gmtoff; + char *tm_zone; +}; + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 74 "/usr/include/time.h" 2 + + + +char *asctime (const struct tm *) ; +clock_t clock (void) ; +char *ctime (const time_t *) ; +double difftime (time_t, time_t) ; +struct tm *gmtime (const time_t *) ; +struct tm *localtime (const time_t *) ; +time_t mktime (struct tm *) ; +size_t strftime (char *, size_t, const char *, const struct tm *) ; +time_t time (time_t *) ; + + +void tzset (void) ; + + + +char *timezone (int, int) ; +void tzsetwall (void) ; + + + + +# 72 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + + + +# 86 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" + +# 1 "/usr/include/sys/timeb.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +struct timeb { + time_t time; + unsigned short millitm; + short timezone; + short dstflag; +}; +# 87 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + + + + + + + + + + +# 1 "/usr/include/sys/param.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/types.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 135 "/usr/include/sys/types.h" + +# 46 "/usr/include/sys/param.h" 2 + + + + + + + + + + +# 1 "/usr/include/sys/syslimits.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 56 "/usr/include/sys/param.h" 2 + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/signal.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/machine/trap.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 42 "/usr/include/sys/signal.h" 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 90 "/usr/include/sys/signal.h" 2 + + + +typedef void (*sig_t) (int) ; + + +typedef void (*__sighandler_t) (int) ; +typedef unsigned int sigset_t; + + +int sigaddset (sigset_t *, int) ; +int sigdelset (sigset_t *, int) ; +int sigemptyset (sigset_t *) ; +int sigfillset (sigset_t *) ; +int sigismember (const sigset_t *, int) ; + + + + + + + + + + + +struct sigaction { + __sighandler_t sa_handler; + sigset_t sa_mask; + int sa_flags; +}; + + + + + + + + + + + + + + + + + + +struct sigvec { + void (*sv_handler)(); + int sv_mask; + int sv_flags; +}; + + + + + + + +struct sigaltstack { + char *ss_base; + int ss_len; + int ss_onstack; +}; + + + + +struct sigstack { + char *ss_sp; + int ss_onstack; +}; + + + + + + + + +struct sigcontext { + int sc_onstack; + int sc_mask; + int sc_sp; + int sc_fp; + int sc_ap; + int sc_pc; + int sc_ps; +}; + + + + + + + + + + + + + + +# 1 "/usr/include/sys/types.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 135 "/usr/include/sys/types.h" + +# 195 "/usr/include/sys/signal.h" 2 + + + + +__sighandler_t signal (int, __sighandler_t) ; +int raise (int) ; + +int kill (pid_t, int) ; +int sigaction (int, const struct sigaction *, struct sigaction *) ; +int sigpending (sigset_t *) ; +int sigprocmask (int, const sigset_t *, sigset_t *) ; +int sigsuspend (const sigset_t *) ; + + +int killpg (pid_t, int) ; +void psignal (unsigned, const char *) ; +int sigblock (int) ; +int siginterrupt (int, int) ; +int sigpause (int) ; +int sigreturn (struct sigcontext *) ; +int sigsetmask (int) ; +int sigstack (const struct sigstack *, struct sigstack *) ; +int sigvec (int, struct sigvec *, struct sigvec *) ; + + + + + +# 79 "/usr/include/sys/param.h" 2 + + + +# 1 "/usr/include/machine/param.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 82 "/usr/include/sys/param.h" 2 + +# 1 "/usr/include/machine/endian.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 55 "/usr/include/machine/endian.h" 2 + + + + + + + + + +# 77 "/usr/include/machine/endian.h" + + + + + + + + + + + + + + + + + + +# 106 "/usr/include/machine/endian.h" + + + + + + + + + + + + +# 83 "/usr/include/sys/param.h" 2 + +# 1 "/usr/include/machine/limits.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 84 "/usr/include/sys/param.h" 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 97 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + + + + + + + + + + + + + + + + + + + +struct utimbuf +{ + long actime; + long modtime; +}; + +int utime (); + + +# 145 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" + +# 1 "/usr/include/strings.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/string.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/machine/ansi.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 57 "/usr/include/machine/ansi.h" + +# 38 "/usr/include/string.h" 2 + + + + + + + + + + + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 49 "/usr/include/string.h" 2 + + + +void *memchr (const void *, int, size_t) ; +int memcmp (const void *, const void *, size_t) ; +void *memcpy (void *, const void *, size_t) ; +void *memmove (void *, const void *, size_t) ; +void *memset (void *, int, size_t) ; +char *strcat (char *, const char *) ; +char *strchr (const char *, int) ; +int strcmp (const char *, const char *) ; +int strcoll (const char *, const char *) ; +char *strcpy (char *, const char *) ; +size_t strcspn (const char *, const char *) ; +char *strerror (int) ; +size_t strlen (const char *) ; +char *strncat (char *, const char *, size_t) ; +int strncmp (const char *, const char *, size_t) ; +char *strncpy (char *, const char *, size_t) ; +char *strpbrk (const char *, const char *) ; +char *strrchr (const char *, int) ; +size_t strspn (const char *, const char *) ; +char *strstr (const char *, const char *) ; +char *strtok (char *, const char *) ; +size_t strxfrm (char *, const char *, size_t) ; + + + +int bcmp (const void *, const void *, size_t) ; +void bcopy (const void *, void *, size_t) ; +void bzero (void *, size_t) ; +int ffs (int) ; +char *index (const char *, int) ; +void *memccpy (void *, const void *, int, size_t) ; +char *rindex (const char *, int) ; +int strcasecmp (const char *, const char *) ; +char *strdup (const char *) ; +void strmode (int, char *) ; +int strncasecmp (const char *, const char *, size_t) ; +char *strsep (char **, const char *) ; +void swab (const void *, void *, size_t) ; + + + + +# 36 "/usr/include/strings.h" 2 + +# 146 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + + + +# 1 "/usr/include/errno.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +extern int errno; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 149 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + + + + +char *getenv (); +char *malloc (); +char *realloc (); +char *calloc (); +extern int errno; + + + + + + +# 173 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" + + + + + + +# 1 "/usr/include/sys/file.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/fcntl.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/types.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 135 "/usr/include/sys/types.h" + +# 46 "/usr/include/sys/fcntl.h" 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 99 "/usr/include/sys/fcntl.h" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +struct flock { + short l_type; + short l_whence; + off_t l_start; + off_t l_len; + pid_t l_pid; +}; + + + + + + + + + + + + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 169 "/usr/include/sys/fcntl.h" 2 + + + +int open (const char *, int, ...) ; +int creat (const char *, mode_t) ; +int fcntl (int, int, ...) ; + +int flock (int, int) ; + + + + + +# 36 "/usr/include/sys/file.h" 2 + +# 1 "/usr/include/sys/unistd.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 37 "/usr/include/sys/file.h" 2 + + +# 73 "/usr/include/sys/file.h" + +# 179 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + +char *getwd (); + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/dir.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/dirent.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +struct dirent { + u_long d_fileno; + u_short d_reclen; + u_short d_namlen; + + + + + char d_name[255 + 1]; + +}; + + + + + + + + + + + +typedef struct _dirdesc { + int dd_fd; + long dd_loc; + long dd_size; + char *dd_buf; + int dd_len; + long dd_seek; +} DIR; + + + + + + + + + + + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 88 "/usr/include/dirent.h" 2 + + + +DIR *opendir (const char *) ; +struct dirent *readdir (DIR *) ; +void rewinddir (DIR *) ; +int closedir (DIR *) ; + +long telldir (const DIR *) ; +void seekdir (DIR *, long) ; +int scandir (const char *, struct dirent ***, + int (*)(struct dirent *), int (*)(const void *, const void *)) ; +int alphasort (const void *, const void *) ; +int getdirentries (int, char *, int, long *) ; + + + + + + +# 44 "/usr/include/sys/dir.h" 2 + + + + + + + + + + + + + + + + + + +# 208 "/usr/src/gnu/cvs/mkmodules/../lib/system.h" 2 + + + + + + + + + + + + + + + + +# 3 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/include/stdio.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 42 "/usr/include/stdio.h" 2 + + +# 1 "/usr/include/machine/ansi.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 57 "/usr/include/machine/ansi.h" + +# 44 "/usr/include/stdio.h" 2 + + + + + + + + + + +typedef long fpos_t; + + + + + + + + + + +struct __sbuf { + unsigned char *_base; + int _size; +}; + + + + + + + + + + + + + + + + + + + + + + + + + +typedef struct __sFILE { + unsigned char *_p; + int _r; + int _w; + short _flags; + short _file; + struct __sbuf _bf; + int _lbfsize; + + + void *_cookie; + int (*_close) (void *) ; + int (*_read) (void *, char *, int) ; + fpos_t (*_seek) (void *, fpos_t, int) ; + int (*_write) (void *, const char *, int) ; + + + struct __sbuf _ub; + unsigned char *_up; + int _ur; + + + unsigned char _ubuf[3]; + unsigned char _nbuf[1]; + + + struct __sbuf _lb; + + + int _blksize; + int _offset; +} FILE; + + +extern FILE __sF[]; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +void clearerr (FILE *) ; +int fclose (FILE *) ; +int feof (FILE *) ; +int ferror (FILE *) ; +int fflush (FILE *) ; +int fgetc (FILE *) ; +int fgetpos (FILE *, fpos_t *) ; +char *fgets (char *, size_t, FILE *) ; +FILE *fopen (const char *, const char *) ; +int fprintf (FILE *, const char *, ...) ; +int fputc (int, FILE *) ; +int fputs (const char *, FILE *) ; +int fread (void *, size_t, size_t, FILE *) ; +FILE *freopen (const char *, const char *, FILE *) ; +int fscanf (FILE *, const char *, ...) ; +int fseek (FILE *, long, int) ; +int fsetpos (FILE *, const fpos_t *) ; +long ftell (const FILE *) ; +int fwrite (const void *, size_t, size_t, FILE *) ; +int getc (FILE *) ; +int getchar (void) ; +char *gets (char *) ; + +extern int sys_nerr; +extern char *sys_errlist[]; + +void perror (const char *) ; +int printf (const char *, ...) ; +int putc (int, FILE *) ; +int putchar (int) ; +int puts (const char *) ; +int remove (const char *) ; +int rename (const char *, const char *) ; +void rewind (FILE *) ; +int scanf (const char *, ...) ; +void setbuf (FILE *, char *) ; +int setvbuf (FILE *, char *, int, size_t) ; +int sprintf (char *, const char *, ...) ; +int sscanf (char *, const char *, ...) ; +FILE *tmpfile (void) ; +char *tmpnam (char *) ; +int ungetc (int, FILE *) ; +int vfprintf (FILE *, const char *, char * ) ; +int vprintf (const char *, char * ) ; +int vsprintf (char *, const char *, char * ) ; + + + + + + + + + + +char *ctermid (char *) ; +FILE *fdopen (int, const char *) ; +int fileno (FILE *) ; + + + + + + + + +char *fgetline (FILE *, size_t *) ; +int fpurge (FILE *) ; +int getw (FILE *) ; +int pclose (FILE *) ; +FILE *popen (const char *, const char *) ; +int putw (int, FILE *) ; +void setbuffer (FILE *, char *, int) ; +int setlinebuf (FILE *) ; +char *tempnam (const char *, const char *) ; +int snprintf (char *, size_t, const char *, ...) ; +int vsnprintf (char *, size_t, const char *, char * ) ; +int vscanf (const char *, char * ) ; +int vsscanf (const char *, const char *, char * ) ; + + + + + + + + + + + + + +FILE *funopen (const void *, + int (*)(void *, char *, int), + int (*)(void *, const char *, int), + fpos_t (*)(void *, fpos_t, int), + int (*)(void *)) ; + + + + + + + + + +int __srget (FILE *) ; +int __svfscanf (FILE *, const char *, char * ) ; +int __swbuf (int, FILE *) ; + + + + + + + + +static inline int __sputc(int _c, FILE *_p) { + if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n')) + return (*_p->_p++ = _c); + else + return (__swbuf(_c, _p)); +} +# 331 "/usr/include/stdio.h" + + + + + + + + + + + + + + + + + + + + + + + +# 4 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/include/ctype.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +extern char _ctype_[]; + + + + + + + + + + + + + + + + + + +# 5 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/include/pwd.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 1 "/usr/include/sys/types.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 135 "/usr/include/sys/types.h" + +# 39 "/usr/include/pwd.h" 2 + + + + + + + + + + + + + + + + + + + + +struct passwd { + char *pw_name; + char *pw_passwd; + int pw_uid; + int pw_gid; + time_t pw_change; + char *pw_class; + char *pw_gecos; + char *pw_dir; + char *pw_shell; + time_t pw_expire; +}; + +# 1 "/usr/include/sys/cdefs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 78 "/usr/include/sys/cdefs.h" + +# 72 "/usr/include/pwd.h" 2 + + + +struct passwd *getpwuid (uid_t) ; +struct passwd *getpwnam (const char *) ; + +struct passwd *getpwent (void) ; +int setpassent (int) ; +int setpwent (void) ; +void endpwent (void) ; + + + + +# 6 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/include/signal.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 222 "/usr/include/signal.h" + +# 7 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../cvs/hash.h" 1 + + + + + + + + + + + + + + + + + + +enum ntype +{ + UNKNOWN, HEADER, ENTRIES, FILES, LIST, RCSNODE, + RCSVERS, DIRS, UPDATE, LOCK, NDBMNODE +}; +typedef enum ntype Ntype; + +struct node +{ + Ntype type; + struct node *next; + struct node *prev; + struct node *hashnext; + struct node *hashprev; + char *key; + char *data; + void (*delproc) (); +}; +typedef struct node Node; + +struct list +{ + Node *list; + Node *hasharray[151 ]; + struct list *next; +}; +typedef struct list List; + +struct entnode +{ + char *version; + char *timestamp; + char *options; + char *tag; + char *date; +}; +typedef struct entnode Entnode; + + +List *getlist (void); +Node *findnode (List * list, char *key); +Node *getnode (void); +int addnode (List * list, Node * p); +int walklist (List * list, int (*proc) ()); +void dellist (List ** listp); +void delnode (Node * p); +void freenode (Node * p); +void sortlist (List * list, int (*comp) ()); +# 77 "/usr/src/gnu/cvs/mkmodules/../cvs/hash.h" + +# 8 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../cvs/rcs.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +struct rcsnode +{ + int refcount; + int flags; + char *path; + char *head; + char *branch; + List *symbols; + List *versions; + List *dates; +}; +typedef struct rcsnode RCSNode; + +struct rcsversnode +{ + char *version; + char *date; + char *next; + List *branches; +}; +typedef struct rcsversnode RCSVers; + + + + + + + + + + + + + + +List *RCS_parsefiles (List * files, char *xrepos); +RCSNode *RCS_parse (char *file, char *repos); +RCSNode *RCS_parsercsfile (char *rcsfile); +char *RCS_check_kflag (char *arg); +char *RCS_getdate (RCSNode * rcs, char *date, int force_tag_match); +char *RCS_gettag (RCSNode * rcs, char *tag, int force_tag_match); +char *RCS_getversion (RCSNode * rcs, char *tag, char *date, + int force_tag_match); +char *RCS_magicrev (RCSNode *rcs, char *rev); +int RCS_isbranch (char *file, char *rev, List *srcfiles); +char *RCS_whatbranch (char *file, char *tag, List *srcfiles); +char *RCS_head (RCSNode * rcs); +int RCS_datecmp (char *date1, char *date2); +time_t RCS_getrevtime (RCSNode * rcs, char *rev, char *date, int fudge); +void RCS_check_tag (char *tag); +void freercsnode (RCSNode ** rnodep); +# 102 "/usr/src/gnu/cvs/mkmodules/../cvs/rcs.h" + +# 9 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../lib/regex.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +typedef unsigned reg_syntax_t; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +extern reg_syntax_t obscure_syntax; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +typedef enum +{ + REG_NOERROR = 0, + REG_NOMATCH, + + + + REG_BADPAT, + REG_ECOLLATE, + REG_ECTYPE, + REG_EESCAPE, + REG_ESUBREG, + REG_EBRACK, + REG_EPAREN, + REG_EBRACE, + REG_BADBR, + REG_ERANGE, + REG_ESPACE, + REG_BADRPT, + + + REG_EEND, + REG_ESIZE, + REG_ERPAREN +} reg_errcode_t; + + + + + + + + + + +struct re_pattern_buffer +{ + + + + + unsigned char *buffer; + + + unsigned long allocated; + + + unsigned long used; + + + reg_syntax_t syntax; + + + + + char *fastmap; + + + + + + char *translate; + + + size_t re_nsub; + + + + + + + unsigned can_be_null : 2; + + + + unsigned fastmap_accurate : 1; + + + + unsigned no_sub : 1; + + + + unsigned not_bol : 1; + + + unsigned not_eol : 1; + + + unsigned newline_anchor : 1; + + + + + unsigned caller_allocated_regs : 1; + +}; + +typedef struct re_pattern_buffer regex_t; + + + + + + + + + + + +typedef int regoff_t; + + + + +struct re_registers +{ + unsigned num_regs; + regoff_t *start; + regoff_t *end; +}; + + + + + + + + + + + + +typedef struct +{ + regoff_t rm_so; + regoff_t rm_eo; +} regmatch_t; + + + + + + + + + + +extern reg_syntax_t re_set_syntax (reg_syntax_t syntax); + + + + +extern const char *re_compile_pattern (const char *pattern, int length, + struct re_pattern_buffer *buffer); + + + + + +extern int re_compile_fastmap (struct re_pattern_buffer *buffer); + + + + + + + +extern int re_search (struct re_pattern_buffer *buffer, + const char *string, int length, + int start, int range, + struct re_registers *regs); + + + + +extern int re_search_2 (struct re_pattern_buffer *buffer, + const char *string1, int length1, + const char *string2, int length2, + int start, int range, + struct re_registers *regs, + int stop); + + + + +extern int re_match (const struct re_pattern_buffer *buffer, + const char *string, int length, + int start, struct re_registers *regs); + + + +extern int re_match_2 (const struct re_pattern_buffer *buffer, + const char *string1, int length1, + const char *string2, int length2, + int start, + struct re_registers *regs, + int stop); + + + + + + + + + + + +extern int regcomp (regex_t *preg, const char *pattern, int cflags); +extern int regexec (const regex_t *preg, const char *string, size_t nmatch, + regmatch_t pmatch[], int eflags); +extern size_t regerror (int errcode, const regex_t *preg, char *errbuf, + size_t errbuf_size); +extern void regfree (regex_t *preg); + +# 468 "/usr/src/gnu/cvs/mkmodules/../lib/regex.h" + + + + + + + + + + + + +# 10 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../lib/fnmatch.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +extern int fnmatch (const char *pattern, const char *string, int flags); + + + + + +# 11 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../lib/getopt.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + +extern char *optarg; + + + + + + + + + + + + + +extern int optind; + + + + +extern int opterr; + + + + + + + + + + + + + + + + + + + + + +struct option +{ + char *name; + int has_arg; + int *flag; + int val; +}; + + +extern const struct option *_getopt_long_options; + + + + + + + +extern int _getopt_long_only; + + + + + +extern int option_index; + + +int gnu_getopt (int argc, char **argv, const char *shortopts); +int gnu_getopt_long (int argc, char **argv, const char *shortopts, + const struct option *longopts, int *longind); +int gnu_getopt_long_only (int argc, char **argv, const char *shortopts, + const struct option *longopts, int *longind); + + + + + +# 12 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../lib/wait.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# 13 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + +# 1 "/usr/src/gnu/cvs/mkmodules/../cvs/config.h" 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +extern void exit (); + + + +extern char *getwd (); + + + + + + + + + + + + + + + +# 14 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + + +# 1 "/usr/src/gnu/cvs/mkmodules/../cvs/myndbm.h" 1 + + + + + + +typedef struct +{ + List *dbm_list; + Node *dbm_next; +} DBM; + +typedef struct +{ + char *dptr; + int dsize; +} datum; + + + + + + + + + + + + + +DBM *mydbm_open (char *file, int flags, int mode); +void mydbm_close (DBM * db); +datum mydbm_fetch (DBM * db, datum key); +datum mydbm_firstkey (DBM * db); +datum mydbm_nextkey (DBM * db); + + + + + + + + + +# 16 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +enum mtype +{ + CHECKOUT, TAG, PATCH +}; + + + + + + +enum classify_type +{ + T_UNKNOWN = 1, + T_CONFLICT, + T_NEEDS_MERGE, + T_MODIFIED, + T_CHECKOUT, + T_ADDED, + T_REMOVED, + T_REMOVE_ENTRY, + T_UPTODATE, + T_TITLE +}; +typedef enum classify_type Ctype; + + + + + + + + +struct vers_ts +{ + char *vn_user; + + + + + + char *vn_rcs; + + char *ts_user; + char *ts_rcs; + char *options; + + char *tag; + char *date; + Entnode *entdata; + RCSNode *srcfile; +}; +typedef struct vers_ts Vers_TS; + + + + + +struct stickydirtag +{ + int aflag; + char *tag; + char *date; + char *options; +}; + + + + + + + + + + + + + + + + +enum direnter_type +{ + R_PROCESS = 1, + R_SKIP_FILES, + R_SKIP_DIRS, + R_SKIP_ALL +}; +typedef enum direnter_type Dtype; + +extern char *program_name, *command_name; +extern char *Rcsbin, *Editor, *CVSroot; +extern char *CurDir; +extern int really_quiet, quiet; +extern int use_editor; +extern int cvswrite; + +extern int trace; +extern int noexec; +extern int logoff; + + + +int Reader_Lock (char *xrepository); +DBM *open_module (void); +FILE *Fopen (char *name, char *mode); +FILE *open_file (char *name, char *mode); +List *Find_Dirs (char *repository, int which); +List *ParseEntries (int aflag); +char *Make_Date (char *rawdate); +char *Name_Repository (char *dir, char *update_dir); +char *Short_Repository (char *repository); +char *getcaller (void); +char *time_stamp (char *file); +char *xmalloc (int bytes); +char *xrealloc (char *ptr, int bytes); +char *xstrdup (char *str); +int No_Difference (char *file, Vers_TS * vers, List * entries); +int Parse_Info (char *infofile, char *repository, int (*callproc) (), int all); +int Reader_Lock (char *xrepository); +int SIG_register (int sig, void (*fn) ()); +int Writer_Lock (List * list); +int gethostname (char *name, int namelen); +int ign_name (char *name); +int isdir (char *file); +int isfile (char *file); +int islink (char *file); +int isreadable (char *file); +int iswritable (char *file); +int link_file (char *from, char *to); +int numdots (char *s); +int run_exec (char *stin, char *stout, char *sterr, int flags); +int unlink_file (char *f); +int update (int argc, char *argv[]); +int xcmp (char *file1, char *file2); +int yesno (void); +time_t get_date (char *date, struct timeb *now); +void Create_Admin (char *dir, char *repository, char *tag, char *date); +void Lock_Cleanup (void); +void ParseTag (char **tagp, char **datep); +void Scratch_Entry (List * list, char *fname); +void WriteTag (char *dir, char *tag, char *date); +void cat_module (int status); +void check_entries (char *dir); +void close_module (DBM * db); +void copy_file (char *from, char *to); +void error (int status, int errnum, char *message,...); +void fperror (FILE * fp, int status, int errnum, char *message,...); +void free_names (int *pargc, char *argv[]); +void freevers_ts (Vers_TS ** versp); +void ign_add (char *ign, int hold); +void ign_add_file (char *file, int hold); +void ign_setup (void); +void line2argv (int *pargc, char *argv[], char *line); +void make_directories (char *name); +void make_directory (char *name); +void rename_file (char *from, char *to); +void run_arg (char *s); +void run_args (char *fmt,...); +void run_print (FILE * fp); +void run_setup (char *fmt,...); +void strip_path (char *path); +void update_delproc (Node * p); +void usage (char **cpp); +void xchmod (char *fname, int writable); +int Checkin (int type, char *file, char *repository, char *rcs, char *rev, + char *tag, char *message, List * entries); +Ctype Classify_File (char *file, char *tag, char *date, char *options, + int force_tag_match, int aflag, char *repository, + List *entries, List *srcfiles, Vers_TS **versp); +List *Find_Names (char *repository, int which, int aflag, + List ** optentries); +void Register (List * list, char *fname, char *vn, char *ts, + char *options, char *tag, char *date); +void Update_Logfile (char *repository, char *xmessage, char *xrevision, + FILE * xlogfp, List * xchanges); +Vers_TS *Version_TS (char *repository, char *options, char *tag, + char *date, char *user, int force_tag_match, + int set_time, List * entries, List * xfiles); +void do_editor (char *dir, char *message, char *repository, + List * changes); +int do_module (DBM * db, char *mname, enum mtype m_type, char *msg, + int (*callback_proc) (), char *where, int shorten, + int local_specified, int run_module_prog, char *extra_arg); +int do_recursion (int (*xfileproc) (), int (*xfilesdoneproc) (), + Dtype (*xdirentproc) (), int (*xdirleaveproc) (), + Dtype xflags, int xwhich, int xaflag, int xreadlock, + int xdosrcs); +int do_update (int argc, char *argv[], char *xoptions, char *xtag, + char *xdate, int xforce, int local, int xbuild, + int xaflag, int xprune, int xpipeout, int which, + char *xjoin_rev1, char *xjoin_rev2, char *preload_update_dir); +void history_write (int type, char *update_dir, char *revs, char *name, + char *repository); +int start_recursion (int (*fileproc) (), int (*filesdoneproc) (), + Dtype (*direntproc) (), int (*dirleaveproc) (), + int argc, char *argv[], int local, int which, + int aflag, int readlock, char *update_preload, + int dosrcs); +void SIG_beginCrSect (); +void SIG_endCrSect (); +# 438 "/usr/src/gnu/cvs/mkmodules/../cvs/cvs.h" + +# 15 "/usr/src/gnu/cvs/mkmodules/mkmodules.c" 2 + + + + + + +PATH_MAX +2 + + +static char rcsid[] = "@(#)mkmodules.c 1.39 92/03/31"; + + + + + + +char *program_name, *command_name; + +char *Rcsbin = "" ; +int noexec = 0; +int trace = 0; + + +static int checkout_file (char *file, char *temp); +static void make_tempfile (char *temp); +static void mkmodules_usage (void); +static void rename_rcsfile (char *temp, char *real); + + + + + + +# 60 "/usr/src/gnu/cvs/mkmodules/mkmodules.c" + + +int +main (argc, argv) + int argc; + char *argv[]; +{ + extern char *getenv (); + char temp[PATH_MAX +2 ]; + char *cp; + + DBM *db; + + + + + + if ((program_name = rindex (argv[0], '/')) == 0 ) + program_name = argv[0]; + else + program_name++; + + if (argc != 2) + mkmodules_usage (); + + if ((cp = getenv ("RCSBIN" )) != 0 ) + Rcsbin = cp; + + + + + + if (Rcsbin[0] != '\0') + { + int len = strlen (Rcsbin); + char *rcsbin; + + if (Rcsbin[len - 1] != '/') + { + rcsbin = Rcsbin; + Rcsbin = xmalloc (len + 2); + (void) strcpy (Rcsbin, rcsbin); + (void) strcat (Rcsbin, "/"); + } + } + + if (chdir (argv[1]) < 0) + error (1, errno, "cannot chdir to %s", argv[1]); + + + + + make_tempfile (temp); + switch (checkout_file ("modules" , temp)) + { + + case 0: + + + if ((db = mydbm_open (temp, 0x0000 , 0666)) != 0 ) + mydbm_close (db); + + + + + rename_rcsfile (temp, "modules" ); + break; + + case -1: + (void) unlink_file (temp); + exit (1); + + + default: + error (0, 0, + "'cvs checkout' is less functional without a %s file", + "modules" ); + break; + } + + (void) unlink_file (temp); + + + + + + make_tempfile (temp); + if (checkout_file ("loginfo" , temp) == 0) + rename_rcsfile (temp, "loginfo" ); + else + error (0, 0, + "no logging of 'cvs commit' messages is done without a %s file", + "loginfo" ); + (void) unlink_file (temp); + + + + + + make_tempfile (temp); + if (checkout_file ("rcsinfo" , temp) == 0) + rename_rcsfile (temp, "rcsinfo" ); + else + error (0, 0, + "a %s file can be used to configure 'cvs commit' templates", + "rcsinfo" ); + (void) unlink_file (temp); + + + + + + make_tempfile (temp); + if (checkout_file ("editinfo" , temp) == 0) + rename_rcsfile (temp, "editinfo" ); + else + error (0, 0, + "a %s file can be used to validate log messages", + "editinfo" ); + (void) unlink_file (temp); + + + + + + make_tempfile (temp); + if (checkout_file ("commitinfo" , temp) == 0) + rename_rcsfile (temp, "commitinfo" ); + else + error (0, 0, + "a %s file can be used to configure 'cvs commit' checking", + "commitinfo" ); + (void) unlink_file (temp); + return (0); +} + + + + +static void +make_tempfile (temp) + char *temp; +{ + static int seed = 0; + int fd; + + if (seed == 0) + seed = getpid (); + while (1) + { + (void) sprintf (temp, "%s%d", ".#" , seed++); + if ((fd = open (temp, 0x0200 | 0x0800 | 0x0002 , 0666)) != -1) + break; + if (errno != 17 ) + error (1, errno, "cannot create temporary file %s", temp); + } + if (close(fd) < 0) + error(1, errno, "cannot close temporary file %s", temp); +} + +static int +checkout_file (file, temp) + char *file; + char *temp; +{ + char rcs[PATH_MAX +2 ]; + int retcode = 0; + + (void) sprintf (rcs, "%s%s", file, ",v" ); + if (!isfile (rcs)) + return (1); + run_setup ("%s%s -q -p", Rcsbin, "co" ); + run_arg (rcs); + if ((retcode = run_exec ( (char *)0 , temp, (char *)0 , 0x0000 )) != 0) + { + error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file); + } + return (retcode); +} + +# 369 "/usr/src/gnu/cvs/mkmodules/mkmodules.c" + + +static void +rename_rcsfile (temp, real) + char *temp; + char *real; +{ + char bak[50]; + + if (chmod (temp, 0444) < 0) + error (0, errno, "warning: cannot chmod %s", temp); + (void) sprintf (bak, "%s%s", ".#" , real); + (void) unlink_file (bak); + (void) rename (real, bak); + (void) rename (temp, real); +} + + + + +void +Lock_Cleanup () +{ +} + +static void +mkmodules_usage () +{ + (void) fprintf ((&__sF[2]) , "Usage: %s modules-directory\n", program_name); + exit (1); +} |