From 7067738d6ce62410bf037507ed279733f38b3910 Mon Sep 17 00:00:00 2001 From: jkh Date: Fri, 18 Jun 1993 04:22:21 +0000 Subject: Updated GNU utilities --- gnu/usr.bin/rcs/Makefile | 3 + gnu/usr.bin/rcs/Makefile.inc | 3 + gnu/usr.bin/rcs/ci/Makefile | 7 + gnu/usr.bin/rcs/ci/ci.1 | 772 +++++++++++++++ gnu/usr.bin/rcs/ci/ci.c | 1165 ++++++++++++++++++++++ gnu/usr.bin/rcs/co/Makefile | 7 + gnu/usr.bin/rcs/co/co.1 | 569 +++++++++++ gnu/usr.bin/rcs/co/co.c | 769 +++++++++++++++ gnu/usr.bin/rcs/doc/rcs.ms | 1524 +++++++++++++++++++++++++++++ gnu/usr.bin/rcs/doc/rcs_func.ms | 95 ++ gnu/usr.bin/rcs/ident/Makefile | 7 + gnu/usr.bin/rcs/ident/ident.1 | 76 ++ gnu/usr.bin/rcs/ident/ident.c | 214 +++++ gnu/usr.bin/rcs/lib/Makefile | 5 + gnu/usr.bin/rcs/lib/conf.h | 495 ++++++++++ gnu/usr.bin/rcs/lib/maketime.c | 344 +++++++ gnu/usr.bin/rcs/lib/merger.c | 139 +++ gnu/usr.bin/rcs/lib/partime.c | 639 ++++++++++++ gnu/usr.bin/rcs/lib/rcsbase.h | 677 +++++++++++++ gnu/usr.bin/rcs/lib/rcsedit.c | 1656 ++++++++++++++++++++++++++++++++ gnu/usr.bin/rcs/lib/rcsfcmp.c | 321 +++++++ gnu/usr.bin/rcs/lib/rcsfnms.c | 1088 +++++++++++++++++++++ gnu/usr.bin/rcs/lib/rcsgen.c | 432 +++++++++ gnu/usr.bin/rcs/lib/rcskeep.c | 422 ++++++++ gnu/usr.bin/rcs/lib/rcskeys.c | 102 ++ gnu/usr.bin/rcs/lib/rcslex.c | 1241 ++++++++++++++++++++++++ gnu/usr.bin/rcs/lib/rcsmap.c | 68 ++ gnu/usr.bin/rcs/lib/rcsrev.c | 790 +++++++++++++++ gnu/usr.bin/rcs/lib/rcssyn.c | 857 +++++++++++++++++ gnu/usr.bin/rcs/lib/rcsutil.c | 994 +++++++++++++++++++ gnu/usr.bin/rcs/merge/Makefile | 7 + gnu/usr.bin/rcs/merge/merge.1 | 102 ++ gnu/usr.bin/rcs/merge/merge.c | 97 ++ gnu/usr.bin/rcs/rcs/Makefile | 10 + gnu/usr.bin/rcs/rcs/rcs.1 | 397 ++++++++ gnu/usr.bin/rcs/rcs/rcs.c | 1554 ++++++++++++++++++++++++++++++ gnu/usr.bin/rcs/rcs/rcsfile.5 | 224 +++++ gnu/usr.bin/rcs/rcs/rcsintro.1 | 292 ++++++ gnu/usr.bin/rcs/rcsclean/Makefile | 7 + gnu/usr.bin/rcs/rcsclean/rcsclean.1 | 177 ++++ gnu/usr.bin/rcs/rcsclean/rcsclean.c | 297 ++++++ gnu/usr.bin/rcs/rcsdiff/Makefile | 7 + gnu/usr.bin/rcs/rcsdiff/rcsdiff.1 | 152 +++ gnu/usr.bin/rcs/rcsdiff/rcsdiff.c | 422 ++++++++ gnu/usr.bin/rcs/rcsfreeze/Makefile | 7 + gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1 | 68 ++ gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh | 100 ++ gnu/usr.bin/rcs/rcsmerge/Makefile | 7 + gnu/usr.bin/rcs/rcsmerge/rcsmerge.1 | 140 +++ gnu/usr.bin/rcs/rcsmerge/rcsmerge.c | 252 +++++ gnu/usr.bin/rcs/rcstest | 397 ++++++++ gnu/usr.bin/rcs/rlog/Makefile | 7 + gnu/usr.bin/rcs/rlog/rlog.1 | 260 +++++ gnu/usr.bin/rcs/rlog/rlog.c | 1204 +++++++++++++++++++++++ 54 files changed, 21668 insertions(+) create mode 100644 gnu/usr.bin/rcs/Makefile create mode 100644 gnu/usr.bin/rcs/Makefile.inc create mode 100644 gnu/usr.bin/rcs/ci/Makefile create mode 100644 gnu/usr.bin/rcs/ci/ci.1 create mode 100644 gnu/usr.bin/rcs/ci/ci.c create mode 100644 gnu/usr.bin/rcs/co/Makefile create mode 100644 gnu/usr.bin/rcs/co/co.1 create mode 100644 gnu/usr.bin/rcs/co/co.c create mode 100644 gnu/usr.bin/rcs/doc/rcs.ms create mode 100644 gnu/usr.bin/rcs/doc/rcs_func.ms create mode 100644 gnu/usr.bin/rcs/ident/Makefile create mode 100644 gnu/usr.bin/rcs/ident/ident.1 create mode 100644 gnu/usr.bin/rcs/ident/ident.c create mode 100644 gnu/usr.bin/rcs/lib/Makefile create mode 100644 gnu/usr.bin/rcs/lib/conf.h create mode 100644 gnu/usr.bin/rcs/lib/maketime.c create mode 100644 gnu/usr.bin/rcs/lib/merger.c create mode 100644 gnu/usr.bin/rcs/lib/partime.c create mode 100644 gnu/usr.bin/rcs/lib/rcsbase.h create mode 100644 gnu/usr.bin/rcs/lib/rcsedit.c create mode 100644 gnu/usr.bin/rcs/lib/rcsfcmp.c create mode 100644 gnu/usr.bin/rcs/lib/rcsfnms.c create mode 100644 gnu/usr.bin/rcs/lib/rcsgen.c create mode 100644 gnu/usr.bin/rcs/lib/rcskeep.c create mode 100644 gnu/usr.bin/rcs/lib/rcskeys.c create mode 100644 gnu/usr.bin/rcs/lib/rcslex.c create mode 100644 gnu/usr.bin/rcs/lib/rcsmap.c create mode 100644 gnu/usr.bin/rcs/lib/rcsrev.c create mode 100644 gnu/usr.bin/rcs/lib/rcssyn.c create mode 100644 gnu/usr.bin/rcs/lib/rcsutil.c create mode 100644 gnu/usr.bin/rcs/merge/Makefile create mode 100644 gnu/usr.bin/rcs/merge/merge.1 create mode 100644 gnu/usr.bin/rcs/merge/merge.c create mode 100644 gnu/usr.bin/rcs/rcs/Makefile create mode 100644 gnu/usr.bin/rcs/rcs/rcs.1 create mode 100644 gnu/usr.bin/rcs/rcs/rcs.c create mode 100644 gnu/usr.bin/rcs/rcs/rcsfile.5 create mode 100644 gnu/usr.bin/rcs/rcs/rcsintro.1 create mode 100644 gnu/usr.bin/rcs/rcsclean/Makefile create mode 100644 gnu/usr.bin/rcs/rcsclean/rcsclean.1 create mode 100644 gnu/usr.bin/rcs/rcsclean/rcsclean.c create mode 100644 gnu/usr.bin/rcs/rcsdiff/Makefile create mode 100644 gnu/usr.bin/rcs/rcsdiff/rcsdiff.1 create mode 100644 gnu/usr.bin/rcs/rcsdiff/rcsdiff.c create mode 100644 gnu/usr.bin/rcs/rcsfreeze/Makefile create mode 100644 gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1 create mode 100644 gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh create mode 100644 gnu/usr.bin/rcs/rcsmerge/Makefile create mode 100644 gnu/usr.bin/rcs/rcsmerge/rcsmerge.1 create mode 100644 gnu/usr.bin/rcs/rcsmerge/rcsmerge.c create mode 100755 gnu/usr.bin/rcs/rcstest create mode 100644 gnu/usr.bin/rcs/rlog/Makefile create mode 100644 gnu/usr.bin/rcs/rlog/rlog.1 create mode 100644 gnu/usr.bin/rcs/rlog/rlog.c (limited to 'gnu') diff --git a/gnu/usr.bin/rcs/Makefile b/gnu/usr.bin/rcs/Makefile new file mode 100644 index 0000000..2181815 --- /dev/null +++ b/gnu/usr.bin/rcs/Makefile @@ -0,0 +1,3 @@ +SUBDIR= lib ci co ident merge rcs rcsdiff rcsmerge rlog rcsfreeze + +.include diff --git a/gnu/usr.bin/rcs/Makefile.inc b/gnu/usr.bin/rcs/Makefile.inc new file mode 100644 index 0000000..b9eca7d --- /dev/null +++ b/gnu/usr.bin/rcs/Makefile.inc @@ -0,0 +1,3 @@ +# @(#)Makefile.inc 5.1 (Berkeley) 5/11/90 + +BINDIR?= /usr/bin diff --git a/gnu/usr.bin/rcs/ci/Makefile b/gnu/usr.bin/rcs/ci/Makefile new file mode 100644 index 0000000..9b64e08 --- /dev/null +++ b/gnu/usr.bin/rcs/ci/Makefile @@ -0,0 +1,7 @@ +PROG= ci + +SRCS= ci.c +LDADD= -L${.CURDIR}/../lib/obj -lrcs +CFLAGS+= -I${.CURDIR}/../lib + +.include diff --git a/gnu/usr.bin/rcs/ci/ci.1 b/gnu/usr.bin/rcs/ci/ci.1 new file mode 100644 index 0000000..5736dc9 --- /dev/null +++ b/gnu/usr.bin/rcs/ci/ci.1 @@ -0,0 +1,772 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: ci.1,v 5.9 1991/10/07 17:32:46 eggert Exp $ +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH CI 1 \*(Dt GNU +.SH NAME +ci \- check in RCS revisions +.SH SYNOPSIS +.B ci +.RI [ options ] " file " .\|.\|. +.SH DESCRIPTION +.B ci +stores new revisions into \*r files. +Each pathname matching an \*r suffix +is taken to be an \*r file. +All others +are assumed to be working files containing new revisions. +.B ci +deposits the contents of each working file +into the corresponding \*r file. +If only a working file is given, +.B ci +tries to find the corresponding \*r file in an \*r subdirectory +and then in the working file's directory. +For more details, see +.SM "FILE NAMING" +below. +.PP +For +.B ci +to work, the caller's login must be on the access list, +except if the access list is empty or the caller is the superuser or the +owner of the file. +To append a new revision to an existing branch, the tip revision on +that branch must be locked by the caller. Otherwise, only a +new branch can be created. This restriction is not enforced +for the owner of the file if non-strict locking is used +(see +.BR rcs (1)). +A lock held by someone else may be broken with the +.B rcs +command. +.PP +Unless the +.B \-f +option is given, +.B ci +checks whether the revision to be deposited differs from the preceding one. +If not, instead of creating a new revision +.B ci +reverts to the preceding one. +To revert, ordinary +.B ci +removes the working file and any lock; +.B "ci\ \-l" +keeps and +.B "ci\ \-u" +removes any lock, and then they both generate a new working file much as if +.B "co\ \-l" +or +.B "co\ \-u" +had been applied to the preceding revision. +When reverting, any +.B \-n +and +.B \-s +options apply to the preceding revision. +.PP +For each revision deposited, +.B ci +prompts for a log message. +The log message should summarize the change and must be terminated by +end-of-file or by a line containing +.BR \&. "\ by" +itself. +If several files are checked in +.B ci +asks whether to reuse the +previous log message. +If the standard input is not a terminal, +.B ci +suppresses the prompt +and uses the same log message for all files. +See also +.BR \-m . +.PP +If the \*r file does not exist, +.B ci +creates it and +deposits the contents of the working file as the initial revision +(default number: +.BR 1.1 ). +The access list is initialized to empty. +Instead of the log message, +.B ci +requests descriptive text (see +.B \-t +below). +.PP +The number +.I rev +of the deposited revision can be given by any of the options +.BR \-f , +.BR \-I , +.BR \-k , +.BR \-l , +.BR \-M , +.BR \-q , +.BR \-r , +or +.BR \-u . +.I rev +may be symbolic, numeric, or mixed. +If +.I rev +is +.BR $ , +.B ci +determines the revision number from keyword values in the working file. +.PP +If +.I rev +is a revision number, it must be higher than the latest +one on the branch to which +.I rev +belongs, or must start a new branch. +.PP +If +.I rev +is a branch rather than a revision number, +the new revision is appended to that branch. The level number is obtained +by incrementing the tip revision number of that branch. +If +.I rev +indicates a non-existing branch, +that branch is created with the initial revision numbered +.IB rev .1\f1.\fP +.br +.ne 8 +.PP +If +.I rev +is omitted, +.B ci +tries to derive the new revision number from +the caller's last lock. If the caller has locked the tip revision of a branch, +the new revision is appended to that branch. +The new revision number is obtained +by incrementing the tip revision number. +If the caller locked a non-tip revision, a new branch is started at +that revision by incrementing the highest branch number at that revision. +The default initial branch and level numbers are +.BR 1 . +.PP +If +.I rev +is omitted and the caller has no lock, but owns +the file and locking +is not set to +.IR strict , +then the revision is appended to the +default branch (normally the trunk; see the +.B \-b +option of +.BR rcs (1)). +.PP +Exception: On the trunk, revisions can be appended to the end, but +not inserted. +.SH OPTIONS +.TP +.BR \-r [\f2rev\fP] +checks in a revision, releases the corresponding lock, and +removes the working file. This is the default. +.RS +.PP +The +.B \-r +option has an unusual meaning in +.BR ci . +In other \*r commands, +.B \-r +merely specifies a revision number, +but in +.B ci +it also releases a lock and removes the working file. +See +.B \-u +for a tricky example. +.RE +.TP +.BR \-l [\f2rev\fP] +works like +.BR \-r , +except it performs an additional +.B "co\ \-l" +for the +deposited revision. Thus, the deposited revision is immediately +checked out again and locked. +This is useful for saving a revision although one wants to continue +editing it after the checkin. +.TP +.BR \-u [\f2rev\fP] +works like +.BR \-l , +except that the deposited revision is not locked. +This lets one read the working file +immediately after checkin. +.RS +.PP +The +.BR \-l , +.BR \-r , +and +.B \-u +options are mutually exclusive and silently override each other. +For example, +.B "ci\ \-u\ \-r" +is equivalent to +.B "ci\ \-r" +because +.B \-r +overrides +.BR \-u . +.RE +.TP +.BR \-f [\f2rev\fP] +forces a deposit; the new revision is deposited even it is not different +from the preceding one. +.TP +.BR \-k [\f2rev\fP] +searches the working file for keyword values to determine its revision number, +creation date, state, and author (see +.BR co (1)), +and assigns these +values to the deposited revision, rather than computing them locally. +It also generates a default login message noting the login of the caller +and the actual checkin date. +This option is useful for software distribution. A revision that is sent to +several sites should be checked in with the +.B \-k +option at these sites to +preserve the original number, date, author, and state. +The extracted keyword values and the default log message may be overridden +with the options +.BR \-d , +.BR \-m , +.BR \-s , +.BR \-w , +and any option that carries a revision number. +.TP +.BR \-q [\f2rev\fP] +quiet mode; diagnostic output is not printed. +A revision that is not different from the preceding one is not deposited, +unless +.B \-f +is given. +.TP +.BR \-I [\f2rev\fP] +interactive mode; +the user is prompted and questioned +even if the standard input is not a terminal. +.TP +.BR \-d "[\f2date\fP]" +uses +.I date +for the checkin date and time. +The +.I date +is specified in free format as explained in +.BR co (1). +This is useful for lying about the checkin date, and for +.B \-k +if no date is available. +If +.I date +is empty, the working file's time of last modification is used. +.TP +.BR \-M [\f2rev\fP] +Set the modification time on any new working file +to be the date of the retrieved revision. +For example, +.BI "ci\ \-d\ \-M\ \-u" "\ f" +does not alter +.IR f 's +modification time, even if +.IR f 's +contents change due to keyword substitution. +Use this option with care; it can confuse +.BR make (1). +.TP +.BI \-m "msg" +uses the string +.I msg +as the log message for all revisions checked in. +.TP +.BI \-n "name" +assigns the symbolic name +.I name +to the number of the checked-in revision. +.B ci +prints an error message if +.I name +is already assigned to another +number. +.TP +.BI \-N "name" +same as +.BR \-n , +except that it overrides a previous assignment of +.IR name . +.TP +.BI \-s "state" +sets the state of the checked-in revision to the identifier +.IR state . +The default state is +.BR Exp . +.TP +.BI \-t file +writes descriptive text from the contents of the named +.I file +into the \*r file, +deleting the existing text. +The +.I file +may not begin with +.BR \- . +.TP +.BI \-t\- string +Write descriptive text from the +.I string +into the \*r file, deleting the existing text. +.RS +.PP +The +.B \-t +option, in both its forms, has effect only during an initial checkin; +it is silently ignored otherwise. +.PP +During the initial checkin, if +.B \-t +is not given, +.B ci +obtains the text from standard input, +terminated by end-of-file or by a line containing +.BR \&. "\ by" +itself. +The user is prompted for the text if interaction is possible; see +.BR \-I . +.PP +For backward compatibility with older versions of \*r, a bare +.B \-t +option is ignored. +.RE +.TP +.BI \-w "login" +uses +.I login +for the author field of the deposited revision. +Useful for lying about the author, and for +.B \-k +if no author is available. +.TP +.BI \-V n +Emulate \*r version +.IR n . +See +.BR co (1) +for details. +.TP +.BI \-x "suffixes" +specifies the suffixes for \*r files. +A nonempty suffix matches any pathname ending in the suffix. +An empty suffix matches any pathname of the form +.BI RCS/ file +or +.IB path /RCS/ file. +The +.B \-x +option can specify a list of suffixes +separated by +.BR / . +For example, +.B \-x,v/ +specifies two suffixes: +.B ,v +and the empty suffix. +If two or more suffixes are specified, +they are tried in order when looking for an \*r file; +the first one that works is used for that file. +If no \*r file is found but an \*r file can be created, +the suffixes are tried in order +to determine the new \*r file's name. +The default for +.IR suffixes +is installation-dependent; normally it is +.B ,v/ +for hosts like Unix that permit commas in file names, +and is empty (i.e. just the empty suffix) for other hosts. +.SH "FILE NAMING" +Pairs of \*r files and working files may be specified in three ways +(see also the +example section). +.PP +1) Both the \*r file and the working file are given. The \*r pathname is of +the form +.IB path1 / workfileX +and the working pathname is of the form +.IB path2 / workfile +where +.IB path1 / +and +.IB path2 / +are (possibly different or empty) paths, +.I workfile +is a filename, and +.I X +is an \*r suffix. +If +.I X +is empty, +.IB path1 / +must be +.B RCS/ +or must end in +.BR /RCS/ . +.PP +2) Only the \*r file is given. Then the working file is created in the current +directory and its name is derived from the name of the \*r file +by removing +.IB path1 / +and the suffix +.IR X . +.PP +3) Only the working file is given. +Then +.B ci +considers each \*r suffix +.I X +in turn, looking for an \*r file of the form +.IB path2 /RCS/ workfileX +or (if the former is not found and +.I X +is nonempty) +.IB path2 / workfileX. +.PP +If the \*r file is specified without a path in 1) and 2), +.B ci +looks for the \*r file first in the directory +.B ./RCS +and then in the current +directory. +.PP +.B ci +reports an error if an attempt to open an \*r file fails for an unusual reason, +even if the \*r file's pathname is just one of several possibilities. +For example, to suppress use of \*r commands in a directory +.IR d , +create a regular file named +.IB d /RCS +so that casual attempts to use \*r commands in +.I d +fail because +.IB d /RCS +is not a directory. +.SH EXAMPLES +Suppose +.B ,v +is an \*r suffix and the current directory contains a subdirectory +.B RCS +with an \*r file +.BR io.c,v . +Then each of the following commands check in a copy of +.B io.c +into +.B RCS/io.c,v +as the latest revision, removing +.BR io.c . +.LP +.RS +.nf +.ft 3 +ci io.c; ci RCS/io.c,v; ci io.c,v; +ci io.c RCS/io.c,v; ci io.c io.c,v; +ci RCS/io.c,v io.c; ci io.c,v io.c; +.ft +.fi +.RE +.PP +Suppose instead that the empty suffix +is an \*r suffix and the current directory contains a subdirectory +.B RCS +with an \*r file +.BR io.c . +The each of the following commands checks in a new revision. +.LP +.RS +.nf +.ft 3 +ci io.c; ci RCS/io.c; +ci io.c RCS/io.c; +ci RCS/io.c io.c; +.ft +.fi +.RE +.SH "FILE MODES" +An \*r file created by +.B ci +inherits the read and execute permissions +from the working file. If the \*r file exists already, +.B ci +preserves its read and execute permissions. +.B ci +always turns off all write permissions of \*r files. +.SH FILES +Several temporary files may be created in the directory containing +the working file, and also in the temporary directory (see +.B \s-1TMPDIR\s0 +under +.BR \s-1ENVIRONMENT\s0 ). +A semaphore file or files are created in the directory containing the \*r file. +With a nonempty suffix, the semaphore names begin with +the first character of the suffix; therefore, do not specify an suffix +whose first character could be that of a working filename. +With an empty suffix, the semaphore names end with +.B _ +so working filenames should not end in +.BR _ . +.PP +.B ci +never changes an \*r or working file. +Normally, +.B ci +unlinks the file and creates a new one; +but instead of breaking a chain of one or more symbolic links to an \*r file, +it unlinks the destination file instead. +Therefore, +.B ci +breaks any hard or symbolic links to any working file it changes; +and hard links to \*r files are ineffective, +but symbolic links to \*r files are preserved. +.PP +The effective user must be able to +search and write the directory containing the \*r file. +Normally, the real user must be able to +read the \*r and working files +and to search and write the directory containing the working file; +however, some older hosts +cannot easily switch between real and effective users, +so on these hosts the effective user is used for all accesses. +The effective user is the same as the real user +unless your copies of +.B ci +and +.B co +have setuid privileges. +As described in the next section, +these privileges yield extra security if +the effective user owns all \*r files and directories, +and if only the effective user can write \*r directories. +.PP +Users can control access to \*r files by setting the permissions +of the directory containing the files; only users with write access +to the directory can use \*r commands to change its \*r files. +For example, in hosts that allow a user to belong to several groups, +one can make a group's \*r directories writable to that group only. +This approach suffices for informal projects, +but it means that any group member can arbitrarily change the group's \*r files, +and can even remove them entirely. +Hence more formal projects sometimes distinguish between an \*r administrator, +who can change the \*r files at will, and other project members, +who can check in new revisions but cannot otherwise change the \*r files. +.SH "SETUID USE" +To prevent anybody but their \*r administrator from deleting revisions, +a set of users can employ setuid privileges as follows. +.nr n \w'\(bu '+1n-1/1n +.IP \(bu \nn +Check that the host supports \*r setuid use. +Consult a trustworthy expert if there are any doubts. +It is best if the +.B seteuid() +system call works as described in Posix 1003.1a Draft 5, +because \*r can switch back and forth easily +between real and effective users, even if the real user is +.BR root . +If not, the second best is if the +.B setuid() +system call supports saved setuid +(the {\s-1_POSIX_SAVED_IDS\s0} behavior of Posix 1003.1-1990); +this fails only if the real user is +.BR root . +If \*r detects any failure in setuid, it quits immediately. +.IP \(bu \nn +Choose a user +.I A +to serve as \*r administrator for the set of users. +Only +.I A +will be able to invoke the +.B rcs +command on the users' \*r files. +.I A +should not be +.B root +or any other user with special powers. +Mutually suspicious sets of users should use different administrators. +.IP \(bu \nn +Choose a path name +.I B +that will be a directory of files to be executed by the users. +.IP \(bu \nn +Have +.I A +set up +.I B +to contain copies of +.B ci +and +.B co +that are setuid to +.I A +by copying the commands from their standard installation directory +.I D +as follows: +.LP +.RS +.nf +.ne 3 +\f3mkdir\fP \f2B\fP +\f3cp\fP \f2D\fP\^\f3/c[io]\fP \f2B\fP +\f3chmod go\-w,u+s\fP \f2B\fP\f3/c[io]\fP +.fi +.RE +.IP \(bu \nn +Have each user prepend +.I B +to their path as follows: +.LP +.RS +.nf +.ne 2 +\f3PATH=\fP\f2B\fP\f3:$PATH; export PATH\fP # ordinary shell +\f3set path=(\fP\f2B\fP \f3$path)\fP # C shell +.fi +.RE +.IP \(bu \nn +Have +.I A +create each \*r directory +.I R +with write access only to +.I A +as follows: +.LP +.RS +.nf +.ne 2 +\f3mkdir\fP \f2R\fP +\f3chmod go\-w\fP \f2R\fP +.fi +.RE +.IP \(bu \nn +If you want to let only certain users read the \*r files, +put the users into a group +.IR G , +and have +.I A +further protect the \*r directory as follows: +.LP +.RS +.nf +.ne 2 +\f3chgrp\fP \f2G R\fP +\f3chmod g\-w,o\-rwx\fP \f2R\fP +.fi +.RE +.IP \(bu \nn +Have +.I A +copy old \*r files (if any) into +.IR R , +to ensure that +.I A +owns them. +.IP \(bu \nn +An \*r file's access list limits who can check in and lock revisions. +The default access list is empty, +which grants checkin access to anyone who can read the \*r file. +If you want limit checkin access, +have +.I A +invoke +.B "rcs\ \-a" +on the file; see +.BR rcs (1). +In particular, +.BI "rcs\ \-e\ \-a" A +limits access to just +.IR A . +.IP \(bu \nn +Have +.I A +initialize any new \*r files with +.B "rcs\ \-i" +before initial checkin, adding the +.B \-a +option if you want to limit checkin access. +.IP \(bu \nn +Give setuid privileges only to +.BR ci , +.BR co , +and +.BR rcsclean ; +do not give them to +.B rcs +or to any other command. +.IP \(bu \nn +Do not use other setuid commands to invoke \*r commands; +setuid is trickier than you think! +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +A backslash escapes spaces within an option. +The +.B \s-1RCSINIT\s0 +options are prepended to the argument lists of most \*r commands. +Useful +.B \s-1RCSINIT\s0 +options include +.BR \-q , +.BR \-V , +and +.BR \-x . +.TP +.B \s-1TMPDIR\s0 +Name of the temporary directory. +If not set, the environment variables +.B \s-1TMP\s0 +and +.B \s-1TEMP\s0 +are inspected instead and the first value found is taken; +if none of them are set, +a host-dependent default is used, typically +.BR /tmp . +.SH DIAGNOSTICS +For each revision, +.B ci +prints the \*r file, the working file, and the number +of both the deposited and the preceding revision. +The exit status is zero if and only if all operations were successful. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +co(1), ident(1), make(1), rcs(1), rcsclean(1), rcsdiff(1), +rcsintro(1), rcsmerge(1), rlog(1), rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.br diff --git a/gnu/usr.bin/rcs/ci/ci.c b/gnu/usr.bin/rcs/ci/ci.c new file mode 100644 index 0000000..566747e --- /dev/null +++ b/gnu/usr.bin/rcs/ci/ci.c @@ -0,0 +1,1165 @@ +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +/* + * RCS checkin operation + */ +/******************************************************************* + * check revisions into RCS files + ******************************************************************* + */ + + + +/* $Log: ci.c,v $ + * Revision 5.21 1991/11/20 17:58:07 eggert + * Don't read the delta tree from a nonexistent RCS file. + * + * Revision 5.20 1991/10/07 17:32:46 eggert + * Fix log bugs. Remove lint. + * + * Revision 5.19 1991/09/26 23:10:30 eggert + * Plug file descriptor leak. + * + * Revision 5.18 1991/09/18 07:29:10 eggert + * Work around a common ftruncate() bug. + * + * Revision 5.17 1991/09/10 22:15:46 eggert + * Fix test for redirected stdin. + * + * Revision 5.16 1991/08/19 23:17:54 eggert + * When there are no changes, revert to previous revision instead of aborting. + * Add piece tables, -M, -r$. Tune. + * + * Revision 5.15 1991/04/21 11:58:14 eggert + * Ensure that working file is newer than RCS file after ci -[lu]. + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.14 1991/02/28 19:18:47 eggert + * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first. + * Fix ci -ko -l mode bug. Open work file at most once. + * + * Revision 5.13 1991/02/25 07:12:33 eggert + * getdate -> getcurdate (SVR4 name clash) + * + * Revision 5.12 1990/12/31 01:00:12 eggert + * Don't use uninitialized storage when handling -{N,n}. + * + * Revision 5.11 1990/12/04 05:18:36 eggert + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.10 1990/11/05 20:30:10 eggert + * Don't remove working file when aborting due to no changes. + * + * Revision 5.9 1990/11/01 05:03:23 eggert + * Add -I and new -t behavior. Permit arbitrary data in logs. + * + * Revision 5.8 1990/10/04 06:30:09 eggert + * Accumulate exit status across files. + * + * Revision 5.7 1990/09/25 20:11:46 hammer + * fixed another small typo + * + * Revision 5.6 1990/09/24 21:48:50 hammer + * added cleanups from Paul Eggert. + * + * Revision 5.5 1990/09/21 06:16:38 hammer + * made it handle multiple -{N,n}'s. Also, made it treat re-directed stdin + * the same as the terminal + * + * Revision 5.4 1990/09/20 02:38:51 eggert + * ci -k now checks dates more thoroughly. + * + * Revision 5.3 1990/09/11 02:41:07 eggert + * Fix revision bug with `ci -k file1 file2'. + * + * Revision 5.2 1990/09/04 08:02:10 eggert + * Permit adjacent revisions with identical time stamps (possible on fast hosts). + * Improve incomplete line handling. Standardize yes-or-no procedure. + * + * Revision 5.1 1990/08/29 07:13:44 eggert + * Expand locker value like co. Clean old log messages too. + * + * Revision 5.0 1990/08/22 08:10:00 eggert + * Don't require a final newline. + * Make lock and temp files faster and safer. + * Remove compile-time limits; use malloc instead. + * Permit dates past 1999/12/31. Switch to GMT. + * Add setuid support. Don't pass +args to diff. Check diff's output. + * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. + * Check diff's output. + * + * Revision 4.9 89/05/01 15:10:54 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.8 88/11/08 13:38:23 narten + * changes from root@seismo.CSS.GOV (Super User) + * -d with no arguments uses the mod time of the file it is checking in + * + * Revision 4.7 88/08/09 19:12:07 eggert + * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one. + * Use execv(), not system(); allow cc -R; remove lint. + * isatty(fileno(stdin)) -> ttystdin() + * + * Revision 4.6 87/12/18 11:34:41 narten + * lint cleanups (from Guy Harris) + * + * Revision 4.5 87/10/18 10:18:48 narten + * Updating version numbers. Changes relative to revision 1.1 are actually + * relative to 4.3 + * + * Revision 1.3 87/09/24 13:57:19 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:21:33 jenkins + * Port to suns + * + * Revision 4.3 83/12/15 12:28:54 wft + * ci -u and ci -l now set mode of working file properly. + * + * Revision 4.2 83/12/05 13:40:54 wft + * Merged with 3.9.1.1: added calls to clearerr(stdin). + * made rewriteflag external. + * + * Revision 4.1 83/05/10 17:03:06 wft + * Added option -d and -w, and updated assingment of date, etc. to new delta. + * Added handling of default branches. + * Option -k generates std. log message; fixed undef. pointer in reading of log. + * Replaced getlock() with findlock(), link--unlink with rename(), + * getpwuid() with getcaller(). + * Moved all revision number generation to new routine addelta(). + * Removed calls to stat(); now done by pairfilenames(). + * Changed most calls to catchints() with restoreints(). + * Directed all interactive messages to stderr. + * + * Revision 3.9.1.1 83/10/19 04:21:03 lepreau + * Added clearerr(stdin) to getlogmsg() for re-reading stdin. + * + * Revision 3.9 83/02/15 15:25:44 wft + * 4.2 prerelease + * + * Revision 3.9 83/02/15 15:25:44 wft + * Added call to fastcopy() to copy remainder of RCS file. + * + * Revision 3.8 83/01/14 15:34:05 wft + * Added ignoring of interrupts while new RCS file is renamed; + * Avoids deletion of RCS files by interrupts. + * + * Revision 3.7 82/12/10 16:09:20 wft + * Corrected checking of return code from diff. + * + * Revision 3.6 82/12/08 21:34:49 wft + * Using DATEFORM to prepare date of checked-in revision; + * Fixed return from addbranch(). + * + * Revision 3.5 82/12/04 18:32:42 wft + * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated + * field lockedby in removelock(), moved getlogmsg() before calling diff. + * + * Revision 3.4 82/12/02 13:27:13 wft + * added option -k. + * + * Revision 3.3 82/11/28 20:53:31 wft + * Added mustcheckin() to check for redundant checkins. + * Added xpandfile() to do keyword expansion for -u and -l; + * -m appends linefeed to log message if necessary. + * getlogmsg() suppresses prompt if stdin is not a terminal. + * Replaced keeplock with lockflag, fclose() with ffclose(), + * %02d with %.2d, getlogin() with getpwuid(). + * + * Revision 3.2 82/10/18 20:57:23 wft + * An RCS file inherits its mode during the first ci from the working file, + * otherwise it stays the same, except that write permission is removed. + * Fixed ci -l, added ci -u (both do an implicit co after the ci). + * Fixed call to getlogin(), added call to getfullRCSname(), added check + * for write error. + * Changed conflicting identifiers. + * + * Revision 3.1 82/10/13 16:04:59 wft + * fixed type of variables receiving from getc() (char -> int). + * added include file dbm.h for getting BYTESIZ. This is used + * to check the return code from diff portably. + */ + +#include "rcsbase.h" + +struct Symrev { + char const *ssymbol; + int override; + struct Symrev * nextsym; +}; + +static char const *getcurdate P((void)); +static int addbranch P((struct hshentry*,struct buf*)); +static int addelta P((void)); +static int addsyms P((char const*)); +static int fixwork P((mode_t,char const*)); +static int removelock P((struct hshentry*)); +static int xpandfile P((RILE*,char const*,struct hshentry const*,char const**)); +static struct cbuf getlogmsg P((void)); +static void cleanup P((void)); +static void incnum P((char const*,struct buf*)); +static void addassoclst P((int, char *)); + +static FILE *exfile; +static RILE *workptr; /* working file pointer */ +static struct buf newdelnum; /* new revision number */ +static struct cbuf msg; +static int exitstatus; +static int forceciflag; /* forces check in */ +static int keepflag, keepworkingfile, rcsinitflag; +static struct hshentries *gendeltas; /* deltas to be generated */ +static struct hshentry *targetdelta; /* old delta to be generated */ +static struct hshentry newdelta; /* new delta to be inserted */ +static struct stat workstat; +static struct Symrev *assoclst, *lastassoc; + +mainProg(ciId, "ci", "$Id: ci.c,v 5.21 1991/11/20 17:58:07 eggert Exp $") +{ + static char const cmdusage[] = + "\nci usage: ci -{fklqru}[rev] -mmsg -{nN}name -sstate -t[textfile] -Vn file ..."; + static char const default_state[] = DEFAULTSTATE; + + char altdate[datesize]; + char olddate[datesize]; + char newdatebuf[datesize], targetdatebuf[datesize]; + char *a, **newargv, *textfile; + char const *author, *krev, *rev, *state; + char const *diffilename, *expfilename; + char const *workdiffname, *newworkfilename; + char const *mtime; + int lockflag, lockthis, mtimeflag, removedlock; + int r; + int changedRCS, changework, newhead; + int usestatdate; /* Use mod time of file for -d. */ + mode_t newworkmode; /* mode for working file */ + struct hshentry *workdelta; + + setrid(); + + author = rev = state = textfile = nil; + lockflag = false; + mtimeflag = false; + altdate[0]= '\0'; /* empty alternate date for -d */ + usestatdate=false; + suffixes = X_DEFAULT; + + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + while (a = *++argv, 0<--argc && *a++=='-') { + switch (*a++) { + + case 'r': + keepworkingfile = lockflag = false; + revno: + if (*a) { + if (rev) warn("redefinition of revision number"); + rev = a; + } + break; + + case 'l': + keepworkingfile=lockflag=true; + goto revno; + + case 'u': + keepworkingfile=true; lockflag=false; + goto revno; + + case 'I': + interactiveflag = true; + goto revno; + + case 'q': + quietflag=true; + goto revno; + + case 'f': + forceciflag=true; + goto revno; + + case 'k': + keepflag=true; + goto revno; + + case 'm': + if (msg.size) redefined('m'); + msg = cleanlogmsg(a, strlen(a)); + if (!msg.size) + warn("missing message for -m option"); + break; + + case 'n': + if (!*a) { + error("missing symbolic name after -n"); + break; + } + checksid(a); + addassoclst(false, a); + break; + + case 'N': + if (!*a) { + error("missing symbolic name after -N"); + break; + } + checksid(a); + addassoclst(true, a); + break; + + case 's': + if (*a) { + if (state) redefined('s'); + checksid(a); + state = a; + } else + warn("missing state for -s option"); + break; + + case 't': + if (*a) { + if (textfile) redefined('t'); + textfile = a; + } + break; + + case 'd': + if (altdate[0] || usestatdate) + redefined('d'); + altdate[0] = 0; + if (!(usestatdate = !*a)) + str2date(a, altdate); + break; + + case 'M': + mtimeflag = true; + goto revno; + + case 'w': + if (*a) { + if (author) redefined('w'); + checksid(a); + author = a; + } else + warn("missing author for -w option"); + break; + + case 'x': + suffixes = a; + break; + + case 'V': + setRCSversion(*argv); + break; + + + + default: + faterror("unknown option: %s%s", *argv, cmdusage); + }; + } /* end processing of options */ + + if (argc<1) faterror("no input file%s", cmdusage); + + /* now handle all filenames */ + do { + targetdelta=nil; + ffree(); + + switch (pairfilenames(argc, argv, rcswriteopen, false, false)) { + + case -1: /* New RCS file */ +# if has_setuid && has_getuid + if (euid() != ruid()) { + error("setuid initial checkin prohibited; use `rcs -i -a' first"); + continue; + } +# endif + rcsinitflag = true; + break; + + case 0: /* Error */ + continue; + + case 1: /* Normal checkin with prev . RCS file */ + rcsinitflag = !Head; + } + + /* now RCSfilename contains the name of the RCS file, and + * workfilename contains the name of the working file. + * If the RCS file exists, finptr contains the file descriptor for the + * RCS file. The admin node is initialized. + * RCSstat is set. + */ + + diagnose("%s <-- %s\n", RCSfilename,workfilename); + + if (!(workptr = Iopen(workfilename, FOPEN_R_WORK, &workstat))) { + eerror(workfilename); + continue; + } + if (finptr && !checkaccesslist()) continue; /* give up */ + + krev = rev; + if (keepflag) { + /* get keyword values from working file */ + if (!getoldkeys(workptr)) continue; + if (!rev && !*(krev = prevrev.string)) { + error("can't find a revision number in %s",workfilename); + continue; + } + if (!*prevdate.string && *altdate=='\0' && usestatdate==false) + warn("can't find a date in %s", workfilename); + if (!*prevauthor.string && !author) + warn("can't find an author in %s", workfilename); + if (!*prevstate.string && !state) + warn("can't find a state in %s", workfilename); + } /* end processing keepflag */ + + /* Read the delta tree. */ + if (finptr) + gettree(); + + /* expand symbolic revision number */ + if (!fexpandsym(krev, &newdelnum, workptr)) + continue; + + /* splice new delta into tree */ + if ((removedlock = addelta()) < 0) + continue; + + newdelta.num = newdelnum.string; + newdelta.branches=nil; + newdelta.lockedby=nil; /*might be changed by addlock() */ + newdelta.selector = true; + /* set author */ + if (author!=nil) + newdelta.author=author; /* set author given by -w */ + else if (keepflag && *prevauthor.string) + newdelta.author=prevauthor.string; /* preserve old author if possible*/ + else newdelta.author=getcaller();/* otherwise use caller's id */ + newdelta.state = default_state; + if (state!=nil) + newdelta.state=state; /* set state given by -s */ + else if (keepflag && *prevstate.string) + newdelta.state=prevstate.string; /* preserve old state if possible */ + if (usestatdate) { + time2date(workstat.st_mtime, altdate); + } + if (*altdate!='\0') + newdelta.date=altdate; /* set date given by -d */ + else if (keepflag && *prevdate.string) { + /* Preserve old date if possible. */ + str2date(prevdate.string, olddate); + newdelta.date = olddate; + } else + newdelta.date = getcurdate(); /* use current date */ + /* now check validity of date -- needed because of -d and -k */ + if (targetdelta!=nil && + cmpnum(newdelta.date,targetdelta->date) < 0) { + error("Date %s precedes %s in existing revision %s.", + date2str(newdelta.date, newdatebuf), + date2str(targetdelta->date, targetdatebuf), + targetdelta->num + ); + continue; + } + + + if (lockflag && addlock(&newdelta) < 0) continue; + if (!addsyms(newdelta.num)) + continue; + + + putadmin(frewrite); + puttree(Head,frewrite); + putdesc(false,textfile); + + changework = Expand != OLD_EXPAND; + lockthis = lockflag; + workdelta = &newdelta; + + /* build rest of file */ + if (rcsinitflag) { + diagnose("initial revision: %s\n", newdelnum.string); + /* get logmessage */ + newdelta.log=getlogmsg(); + if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue; + RCSstat.st_mode = workstat.st_mode; + changedRCS = true; + } else { + diffilename = maketemp(0); + workdiffname = workfilename; + if (workdiffname[0] == '+') { + /* Some diffs have options with leading '+'. */ + char *dp = ftnalloc(char, strlen(workfilename)+3); + workdiffname = dp; + *dp++ = '.'; + *dp++ = SLASH; + VOID strcpy(dp, workfilename); + } + newhead = Head == &newdelta; + if (!newhead) + foutptr = frewrite; + expfilename = buildrevision( + gendeltas, targetdelta, (FILE*)0, false + ); + if ( + !forceciflag && + (changework = rcsfcmp( + workptr, &workstat, expfilename, targetdelta + )) <= 0 + ) { + diagnose("file is unchanged; reverting to previous revision %s\n", + targetdelta->num + ); + if (removedlock < lockflag) { + diagnose("previous revision was not locked; ignoring -l option\n"); + lockthis = 0; + } + if (!(changedRCS = + lockflag < removedlock + || assoclst + || newdelta.state != default_state + && strcmp(newdelta.state, targetdelta->state) != 0 + )) + workdelta = targetdelta; + else { + /* + * We have started to build the wrong new RCS file. + * Start over from the beginning. + */ + long hwm = ftell(frewrite); + int bad_truncate; + if (fseek(frewrite, 0L, SEEK_SET) != 0) + Oerror(); +# if !has_ftruncate + bad_truncate = 1; +# else + /* + * Work around a common ftruncate() bug. + * We can't rely on has_truncate, because we might + * be using a filesystem exported to us via NFS. + */ + bad_truncate = ftruncate(fileno(frewrite),(off_t)0); + if (bad_truncate && errno != EACCES) + Oerror(); +# endif + Irewind(finptr); + Lexinit(); + getadmin(); + gettree(); + if (!(workdelta = genrevs( + targetdelta->num, (char*)0, (char*)0, (char*)0, + &gendeltas + ))) + continue; + workdelta->log = targetdelta->log; + if (newdelta.state != default_state) + workdelta->state = newdelta.state; + if (removedlock && removelock(workdelta)<0) + continue; + if (!addsyms(workdelta->num)) + continue; + if (!dorewrite(true, true)) + continue; + fastcopy(finptr, frewrite); + if (bad_truncate) + while (ftell(frewrite) < hwm) + /* White out any earlier mistake with '\n's. */ + /* This is unlikely. */ + afputc('\n', frewrite); + } + } else { + diagnose("new revision: %s; previous revision: %s\n", + newdelnum.string, targetdelta->num + ); + newdelta.log = getlogmsg(); + switch (run((char*)0, diffilename, + DIFF DIFF_FLAGS, + newhead ? workdiffname : expfilename, + newhead ? expfilename : workdiffname, + (char*)0 + )) { + case DIFF_FAILURE: case DIFF_SUCCESS: break; + default: faterror("diff failed"); + } + if (newhead) { + Irewind(workptr); + if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue; + if (!putdtext(targetdelta->num,targetdelta->log,diffilename,frewrite,true)) continue; + } else + if (!putdtext(newdelnum.string,newdelta.log,diffilename,frewrite,true)) continue; + changedRCS = true; + } + } + if (!donerewrite(changedRCS)) + continue; + + if (!keepworkingfile) { + Izclose(&workptr); + r = un_link(workfilename); /* Get rid of old file */ + } else { + newworkmode = WORKMODE(RCSstat.st_mode, + ! (Expand==VAL_EXPAND || lockthis < StrictLocks) + ); + mtime = mtimeflag ? workdelta->date : (char const*)0; + + /* Expand if it might change or if we can't fix mode, time. */ + if (changework || (r=fixwork(newworkmode,mtime)) != 0) { + Irewind(workptr); + /* Expand keywords in file. */ + locker_expansion = lockthis; + switch (xpandfile( + workptr, workfilename, + workdelta, &newworkfilename + )) { + default: + continue; + + case 0: + /* + * No expansion occurred; try to reuse working file + * unless we already tried and failed. + */ + if (changework) + if ((r=fixwork(newworkmode,mtime)) == 0) + break; + /* fall into */ + case 1: + if (!(r = setfiledate(newworkfilename,mtime))) { + Izclose(&workptr); + ignoreints(); + r = chnamemod(&exfile, newworkfilename, workfilename, newworkmode); + keepdirtemp(newworkfilename); + restoreints(); + } + } + } + } + if (r != 0) { + eerror(workfilename); + continue; + } + diagnose("done\n"); + + } while (cleanup(), + ++argv, --argc >=1); + + tempunlink(); + exitmain(exitstatus); +} /* end of main (ci) */ + + static void +cleanup() +{ + if (nerror) exitstatus = EXIT_FAILURE; + Izclose(&finptr); + Izclose(&workptr); + Ozclose(&exfile); + Ozclose(&fcopy); + Ozclose(&frewrite); + dirtempunlink(); +} + +#if lint +# define exiterr ciExit +#endif + exiting void +exiterr() +{ + dirtempunlink(); + tempunlink(); + _exit(EXIT_FAILURE); +} + +/*****************************************************************/ +/* the rest are auxiliary routines */ + + + static int +addelta() +/* Function: Appends a delta to the delta tree, whose number is + * given by newdelnum. Updates Head, newdelnum, newdelnumlength, + * and the links in newdelta. + * Return -1 on error, 1 if a lock is removed, 0 otherwise. + */ +{ + register char *tp; + register unsigned i; + int removedlock; + unsigned newdnumlength; /* actual length of new rev. num. */ + + newdnumlength = countnumflds(newdelnum.string); + + if (rcsinitflag) { + /* this covers non-existing RCS file and a file initialized with rcs -i */ + if ((newdnumlength==0)&&(Dbranch!=nil)) { + bufscpy(&newdelnum, Dbranch); + newdnumlength = countnumflds(Dbranch); + } + if (newdnumlength==0) bufscpy(&newdelnum, "1.1"); + else if (newdnumlength==1) bufscat(&newdelnum, ".1"); + else if (newdnumlength>2) { + error("Branch point doesn't exist for %s.",newdelnum.string); + return -1; + } /* newdnumlength == 2 is OK; */ + Head = &newdelta; + newdelta.next=nil; + return 0; + } + if (newdnumlength==0) { + /* derive new revision number from locks */ + switch (findlock(true, &targetdelta)) { + + default: + /* found two or more old locks */ + return -1; + + case 1: + /* found an old lock */ + /* check whether locked revision exists */ + if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas)) + return -1; + if (targetdelta==Head) { + /* make new head */ + newdelta.next=Head; + Head= &newdelta; + } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) { + /* new tip revision on side branch */ + targetdelta->next= &newdelta; + newdelta.next = nil; + } else { + /* middle revision; start a new branch */ + bufscpy(&newdelnum, ""); + return addbranch(targetdelta,&newdelnum); + } + incnum(targetdelta->num, &newdelnum); + return 1; /* successful use of existing lock */ + + case 0: + /* no existing lock; try Dbranch */ + /* update newdelnum */ + if (StrictLocks || !myself(RCSstat.st_uid)) { + error("no lock set by %s",getcaller()); + return -1; + } + if (Dbranch) { + bufscpy(&newdelnum, Dbranch); + } else { + incnum(Head->num, &newdelnum); + } + newdnumlength = countnumflds(newdelnum.string); + /* now fall into next statement */ + } + } + if (newdnumlength<=2) { + /* add new head per given number */ + if(newdnumlength==1) { + /* make a two-field number out of it*/ + if (cmpnumfld(newdelnum.string,Head->num,1)==0) + incnum(Head->num, &newdelnum); + else + bufscat(&newdelnum, ".1"); + } + if (cmpnum(newdelnum.string,Head->num) <= 0) { + error("deltanumber %s too low; must be higher than %s", + newdelnum.string, Head->num); + return -1; + } + targetdelta = Head; + if (0 <= (removedlock = removelock(Head))) { + if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas)) + return -1; + newdelta.next = Head; + Head = &newdelta; + } + return removedlock; + } else { + /* put new revision on side branch */ + /*first, get branch point */ + tp = newdelnum.string; + for (i = newdnumlength - (newdnumlength&1 ^ 1); (--i); ) + while (*tp++ != '.') + ; + *--tp = 0; /* Kill final dot to get old delta temporarily. */ + if (!(targetdelta=genrevs(newdelnum.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas))) + return -1; + if (cmpnum(targetdelta->num, newdelnum.string) != 0) { + error("can't find branchpoint %s", newdelnum.string); + return -1; + } + *tp = '.'; /* Restore final dot. */ + return addbranch(targetdelta,&newdelnum); + } +} + + + + static int +addbranch(branchpoint,num) + struct hshentry *branchpoint; + struct buf *num; +/* adds a new branch and branch delta at branchpoint. + * If num is the null string, appends the new branch, incrementing + * the highest branch number (initially 1), and setting the level number to 1. + * the new delta and branchhead are in globals newdelta and newbranch, resp. + * the new number is placed into num. + * Return -1 on error, 1 if a lock is removed, 0 otherwise. + */ +{ + struct branchhead *bhead, **btrail; + struct buf branchnum; + int removedlock, result; + unsigned field, numlength; + static struct branchhead newbranch; /* new branch to be inserted */ + + numlength = countnumflds(num->string); + + if (branchpoint->branches==nil) { + /* start first branch */ + branchpoint->branches = &newbranch; + if (numlength==0) { + bufscpy(num, branchpoint->num); + bufscat(num, ".1.1"); + } else if (numlength&1) + bufscat(num, ".1"); + newbranch.nextbranch=nil; + + } else if (numlength==0) { + /* append new branch to the end */ + bhead=branchpoint->branches; + while (bhead->nextbranch) bhead=bhead->nextbranch; + bhead->nextbranch = &newbranch; + bufautobegin(&branchnum); + getbranchno(bhead->hsh->num, &branchnum); + incnum(branchnum.string, num); + bufautoend(&branchnum); + bufscat(num, ".1"); + newbranch.nextbranch=nil; + } else { + /* place the branch properly */ + field = numlength - (numlength&1 ^ 1); + /* field of branch number */ + btrail = &branchpoint->branches; + while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) { + btrail = &(*btrail)->nextbranch; + if (!*btrail) { + result = -1; + break; + } + } + if (result < 0) { + /* insert/append new branchhead */ + newbranch.nextbranch = *btrail; + *btrail = &newbranch; + if (numlength&1) bufscat(num, ".1"); + } else { + /* branch exists; append to end */ + bufautobegin(&branchnum); + getbranchno(num->string, &branchnum); + targetdelta=genrevs(branchnum.string,(char*)nil, + (char*)nil,(char*)nil,&gendeltas); + bufautoend(&branchnum); + if (!targetdelta) + return -1; + if (cmpnum(num->string,targetdelta->num) <= 0) { + error("deltanumber %s too low; must be higher than %s", + num->string,targetdelta->num); + return -1; + } + if (0 <= (removedlock = removelock(targetdelta))) { + if (numlength&1) + incnum(targetdelta->num,num); + targetdelta->next = &newdelta; + newdelta.next = 0; + } + return removedlock; + /* Don't do anything to newbranch. */ + } + } + newbranch.hsh = &newdelta; + newdelta.next=nil; + return 0; +} + + static int +addsyms(num) + char const *num; +{ + register struct Symrev *p; + + for (p = assoclst; p; p = p->nextsym) + if (!addsymbol(num, p->ssymbol, p->override)) + return false; + return true; +} + + + static void +incnum(onum,nnum) + char const *onum; + struct buf *nnum; +/* Increment the last field of revision number onum by one and + * place the result into nnum. + */ +{ + register char *tp, *np; + register size_t l; + + l = strlen(onum); + bufalloc(nnum, l+2); + np = tp = nnum->string; + VOID strcpy(np, onum); + for (tp = np + l; np != tp; ) + if (isdigit(*--tp)) { + if (*tp != '9') { + ++*tp; + return; + } + *tp = '0'; + } else { + tp++; + break; + } + /* We changed 999 to 000; now change it to 1000. */ + *tp = '1'; + tp = np + l; + *tp++ = '0'; + *tp = 0; +} + + + + static int +removelock(delta) +struct hshentry * delta; +/* function: Finds the lock held by caller on delta, + * removes it, and returns nonzero if successful. + * Print an error message and return -1 if there is no such lock. + * An exception is if !StrictLocks, and caller is the owner of + * the RCS file. If caller does not have a lock in this case, + * return 0; return 1 if a lock is actually removed. + */ +{ + register struct lock *next, **trail; + char const *num; + + num=delta->num; + for (trail = &Locks; (next = *trail); trail = &next->nextlock) + if (next->delta == delta) + if (strcmp(getcaller(), next->login) == 0) { + /* We found a lock on delta by caller; delete it. */ + *trail = next->nextlock; + delta->lockedby = 0; + return 1; + } else { + error("revision %s locked by %s",num,next->login); + return -1; + } + if (!StrictLocks && myself(RCSstat.st_uid)) + return 0; + error("no lock set by %s for revision %s", getcaller(), num); + return -1; +} + + + + static char const * +getcurdate() +/* Return a pointer to the current date. */ +{ + static char buffer[datesize]; /* date buffer */ + time_t t; + + if (!buffer[0]) { + t = time((time_t *)0); + if (t == -1) + faterror("time not available"); + time2date(t, buffer); + } + return buffer; +} + + static int +#if has_prototypes +fixwork(mode_t newworkmode, char const *mtime) + /* The `#if has_prototypes' is needed because mode_t might promote to int. */ +#else + fixwork(newworkmode, mtime) + mode_t newworkmode; + char const *mtime; +#endif +{ + int r; + return + 1 < workstat.st_nlink + || newworkmode&S_IWUSR && !myself(workstat.st_uid) + ? -1 + : + workstat.st_mode != newworkmode + && + (r = +# if has_fchmod + fchmod(Ifileno(workptr), newworkmode) +# else + chmod(workfilename, newworkmode) +# endif + ) != 0 + ? r + : + setfiledate(workfilename, mtime); +} + + static int +xpandfile(unexfile, dir, delta, exfilename) + RILE *unexfile; + char const *dir; + struct hshentry const *delta; + char const **exfilename; +/* + * Read unexfile and copy it to a + * file in dir, performing keyword substitution with data from delta. + * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise. + * If successful, stores the stream descriptor into *EXFILEP + * and its name into *EXFILENAME. + */ +{ + char const *targetfname; + int e, r; + + targetfname = makedirtemp(dir, 1); + if (!(exfile = fopen(targetfname, FOPEN_W_WORK))) { + eerror(targetfname); + error("can't expand working file"); + return -1; + } + r = 0; + if (Expand == OLD_EXPAND) + fastcopy(unexfile,exfile); + else { + for (;;) { + e = expandline(unexfile,exfile,delta,false,(FILE*)nil); + if (e < 0) + break; + r |= e; + if (e <= 1) + break; + } + } + *exfilename = targetfname; + aflush(exfile); + return r & 1; +} + + + + +/* --------------------- G E T L O G M S G --------------------------------*/ + + + static struct cbuf +getlogmsg() +/* Obtain and yield a log message. + * If a log message is given with -m, yield that message. + * If this is the initial revision, yield a standard log message. + * Otherwise, reads a character string from the terminal. + * Stops after reading EOF or a single '.' on a + * line. getlogmsg prompts the first time it is called for the + * log message; during all later calls it asks whether the previous + * log message can be reused. + */ +{ + static char const + emptych[] = EMPTYLOG, + initialch[] = "Initial revision"; + static struct cbuf const + emptylog = { emptych, sizeof(emptych)-sizeof(char) }, + initiallog = { initialch, sizeof(initialch)-sizeof(char) }; + static struct buf logbuf; + static struct cbuf logmsg; + + register char *tp; + register size_t i; + char const *caller; + + if (msg.size) return msg; + + if (keepflag) { + /* generate std. log message */ + caller = getcaller(); + i = sizeof(ciklog)+strlen(caller)+3; + bufalloc(&logbuf, i+datesize); + tp = logbuf.string; + VOID sprintf(tp, "%s%s at ", ciklog, caller); + VOID date2str(getcurdate(), tp+i); + logmsg.string = tp; + logmsg.size = strlen(tp); + return logmsg; + } + + if (!targetdelta && ( + cmpnum(newdelnum.string,"1.1")==0 || + cmpnum(newdelnum.string,"1.0")==0 + )) + return initiallog; + + if (logmsg.size) { + /*previous log available*/ + if (yesorno(true, "reuse log message of previous file? [yn](y): ")) + return logmsg; + } + + /* now read string from stdin */ + logmsg = getsstdin("m", "log message", "", &logbuf); + + /* now check whether the log message is not empty */ + if (logmsg.size) + return logmsg; + return emptylog; +} + +/* Make a linked list of Symbolic names */ + + static void +addassoclst(flag, sp) +int flag; +char * sp; +{ + struct Symrev *pt; + + pt = talloc(struct Symrev); + pt->ssymbol = sp; + pt->override = flag; + pt->nextsym = nil; + if (lastassoc) + lastassoc->nextsym = pt; + else + assoclst = pt; + lastassoc = pt; + return; +} diff --git a/gnu/usr.bin/rcs/co/Makefile b/gnu/usr.bin/rcs/co/Makefile new file mode 100644 index 0000000..e9de8da --- /dev/null +++ b/gnu/usr.bin/rcs/co/Makefile @@ -0,0 +1,7 @@ +PROG= co + +SRCS= co.c +LDADD= -L${.CURDIR}/../lib/obj -lrcs +CFLAGS+= -I${.CURDIR}/../lib + +.include diff --git a/gnu/usr.bin/rcs/co/co.1 b/gnu/usr.bin/rcs/co/co.1 new file mode 100644 index 0000000..d9ce65e --- /dev/null +++ b/gnu/usr.bin/rcs/co/co.1 @@ -0,0 +1,569 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: co.1,v 5.7 1991/08/19 03:13:55 eggert Exp $ +.ds g \&\s-1UTC\s0 +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH CO 1 \*(Dt GNU +.SH NAME +co \- check out RCS revisions +.SH SYNOPSIS +.B co +.RI [ options ] " file " .\|.\|. +.SH DESCRIPTION +.B co +retrieves a revision from each \*r file and stores it into +the corresponding working file. +.PP +Pathnames matching an \*r suffix denote \*r files; +all others denote working files. +Names are paired as explained in +.BR ci (1). +.PP +Revisions of an \*r file may be checked out locked or unlocked. Locking a +revision prevents overlapping updates. A revision checked out for reading or +processing (e.g., compiling) need not be locked. A revision checked out +for editing and later checkin must normally be locked. Checkout with locking +fails if the revision to be checked out is currently locked by another user. +(A lock may be broken with +.BR rcs "(1).)\ \&" +Checkout with locking also requires the caller to be on the access list of +the \*r file, unless he is the owner of the +file or the superuser, or the access list is empty. +Checkout without locking is not subject to accesslist restrictions, and is +not affected by the presence of locks. +.PP +A revision is selected by options for revision or branch number, +checkin date/time, author, or state. +When the selection options +are applied in combination, +.B co +retrieves the latest revision +that satisfies all of them. +If none of the selection options +is specified, +.B co +retrieves the latest revision +on the default branch (normally the trunk, see the +.B \-b +option of +.BR rcs (1)). +A revision or branch number may be attached +to any of the options +.BR \-f , +.BR \-I , +.BR \-l , +.BR \-M , +.BR \-p , +.BR \-q , +.BR \-r , +or +.BR \-u . +The options +.B \-d +(date), +.B \-s +(state), and +.B \-w +(author) +retrieve from a single branch, the +.I selected +branch, +which is either specified by one of +.BR \-f, +\&.\|.\|., +.BR \-u , +or the default branch. +.PP +A +.B co +command applied to an \*r +file with no revisions creates a zero-length working file. +.B co +always performs keyword substitution (see below). +.SH OPTIONS +.TP +.BR \-r [\f2rev\fP] +retrieves the latest revision whose number is less than or equal to +.I rev. +If +.I rev +indicates a branch rather than a revision, +the latest revision on that branch is retrieved. +If +.I rev +is omitted, the latest revision on the default branch +(see the +.B \-b +option of +.BR rcs (1)) +is retrieved. +If +.I rev +is +.BR $ , +.B co +determines the revision number from keyword values in the working file. +Otherwise, a revision is composed of one or more numeric or symbolic fields +separated by periods. The numeric equivalent of a symbolic field +is specified with the +.B \-n +option of the commands +.BR ci (1) +and +.BR rcs (1). +.TP +.BR \-l [\f2rev\fP] +same as +.BR \-r , +except that it also locks the retrieved revision for +the caller. +.TP +.BR \-u [\f2rev\fP] +same as +.BR \-r , +except that it unlocks the retrieved revision if it was +locked by the caller. If +.I rev +is omitted, +.B \-u +retrieves the revision locked by the caller, if there is one; otherwise, +it retrieves the latest revision on the default branch. +.TP +.BR \-f [\f2rev\fP] +forces the overwriting of the working file; +useful in connection with +.BR \-q . +See also +.SM "FILE MODES" +below. +.TP +.B \-kkv +Generate keyword strings using the default form, e.g.\& +.B "$\&Revision: \*(Rv $" +for the +.B Revision +keyword. +A locker's name is inserted in the value of the +.BR Header , +.BR Id , +and +.B Locker +keyword strings +only as a file is being locked, +i.e. by +.B "ci\ \-l" +and +.BR "co\ \-l". +This is the default. +.TP +.B \-kkvl +Like +.BR \-kkv , +except that a locker's name is always inserted +if the given revision is currently locked. +.TP +.BR \-kk +Generate only keyword names in keyword strings; omit their values. +See +.SM "KEYWORD SUBSTITUTION" +below. +For example, for the +.B Revision +keyword, generate the string +.B $\&Revision$ +instead of +.BR "$\&Revision: \*(Rv $". +This option is useful to ignore differences due to keyword substitution +when comparing different revisions of a file. +.TP +.BR \-ko +Generate the old keyword string, +present in the working file just before it was checked in. +For example, for the +.B Revision +keyword, generate the string +.B "$\&Revision: 1.1 $" +instead of +.B "$\&Revision: \*(Rv $" +if that is how the string appeared when the file was checked in. +This can be useful for binary file formats +that cannot tolerate any changes to substrings +that happen to take the form of keyword strings. +.TP +.BR \-kv +Generate only keyword values for keyword strings. +For example, for the +.B Revision +keyword, generate the string +.B \*(Rv +instead of +.BR "$\&Revision: \*(Rv $". +This can help generate files in programming languages where it is hard to +strip keyword delimiters like +.B "$\&Revision:\ $" +from a string. +However, further keyword substitution cannot be performed once the +keyword names are removed, so this option should be used with care. +Because of this danger of losing keywords, +this option cannot be combined with +.BR \-l , +and the owner write permission of the working file is turned off; +to edit the file later, check it out again without +.BR \-kv . +.TP +.BR \-p [\f2rev\fP] +prints the retrieved revision on the standard output rather than storing it +in the working file. +This option is useful when +.B co +is part of a pipe. +.TP +.BR \-q [\f2rev\fP] +quiet mode; diagnostics are not printed. +.TP +.BR \-I [\f2rev\fP] +interactive mode; +the user is prompted and questioned +even if the standard input is not a terminal. +.TP +.BI \-d date +retrieves the latest revision on the selected branch whose checkin date/time is +less than or equal to +.I date. +The date and time may be given in free format. +The time zone +.B LT +stands for local time; +other common time zone names are understood. +For example, the following +.IR date s +are equivalent +if local time is January 11, 1990, 8pm Pacific Standard Time, +eight hours west of Coordinated Universal Time (\*g): +.RS +.LP +.RS +.nf +.ta \w'\f3Thu, 11 Jan 1990 20:00:00 \-0800\fP 'u +.ne 9 +\f38:00 pm lt\fP +\f34:00 AM, Jan. 12, 1990\fP note: default is \*g +\f31990/01/12 04:00:00\fP \*r date format +\f3Thu Jan 11 20:00:00 1990 LT\fP output of \f3ctime\fP(3) + \f3LT\fP +\f3Thu Jan 11 20:00:00 PST 1990\fP output of \f3date\fP(1) +\f3Fri Jan 12 04:00:00 GMT 1990\fP +\f3Thu, 11 Jan 1990 20:00:00 \-0800\fP +\f3Fri-JST, 1990, 1pm Jan 12\fP +\f312-January-1990, 04:00-WET\fP +.ta 4n +4n +4n +4n +.fi +.RE +.LP +Most fields in the date and time may be defaulted. +The default time zone is \*g. +The other defaults are determined in the order year, month, day, +hour, minute, and second (most to least significant). At least one of these +fields must be provided. For omitted fields that are of higher significance +than the highest provided field, the time zone's current values are assumed. +For all other omitted fields, +the lowest possible values are assumed. +For example, the date +.B "20, 10:30" +defaults to +10:30:00 \*g of the 20th of the \*g time zone's current month and year. +The date/time must be quoted if it contains spaces. +.RE +.TP +.BR \-M [\f2rev\fP] +Set the modification time on the new working file +to be the date of the retrieved revision. +Use this option with care; it can confuse +.BR make (1). +.TP +.BI \-s state +retrieves the latest revision on the selected branch whose state is set to +.I state. +.TP +.BR \-w [\f2login\fP] +retrieves the latest revision on the selected branch which was checked in +by the user with login name +.I login. +If the argument +.I login +is +omitted, the caller's login is assumed. +.TP +.BI \-j joinlist +generates a new revision which is the join of the revisions on +.I joinlist. +This option is largely obsoleted by +.BR rcsmerge (1) +but is retained for backwards compatibility. +.RS +.PP +The +.I joinlist +is a comma-separated list of pairs of the form +.IB rev2 : rev3, +where +.I rev2 +and +.I rev3 +are (symbolic or numeric) +revision numbers. +For the initial such pair, +.I rev1 +denotes the revision selected +by the above options +.BR \-f, +\&.\|.\|., +.BR \-w . +For all other pairs, +.I rev1 +denotes the revision generated by the previous pair. +(Thus, the output +of one join becomes the input to the next.) +.PP +For each pair, +.B co +joins revisions +.I rev1 +and +.I rev3 +with respect to +.I rev2. +This means that all changes that transform +.I rev2 +into +.I rev1 +are applied to a copy of +.I rev3. +This is particularly useful if +.I rev1 +and +.I rev3 +are the ends of two branches that have +.I rev2 +as a common ancestor. If +.IR rev1 < rev2 < rev3 +on the same branch, +joining generates a new revision which is like +.I rev3, +but with all changes that lead from +.I rev1 +to +.I rev2 +undone. +If changes from +.I rev2 +to +.I rev1 +overlap with changes from +.I rev2 +to +.I rev3, +.B co +reports overlaps as described in +.BR merge (1). +.PP +For the initial pair, +.I rev2 +may be omitted. The default is the common +ancestor. +If any of the arguments indicate branches, the latest revisions +on those branches are assumed. +The options +.B \-l +and +.B \-u +lock or unlock +.I rev1. +.RE +.TP +.BI \-V n +Emulate \*r version +.I n, +where +.I n +may be +.BR 3 , +.BR 4 , +or +.BR 5 . +This may be useful when interchanging \*r files with others who are +running older versions of \*r. +To see which version of \*r your correspondents are running, have them invoke +.B rlog +on an \*r file; +if none of the first few lines of output contain the string +.B branch: +it is version 3; +if the dates' years have just two digits, it is version 4; +otherwise, it is version 5. +An \*r file generated while emulating version 3 will lose its default branch. +An \*r revision generated while emulating version 4 or earlier will have +a timestamp that is off by up to 13 hours. +A revision extracted while emulating version 4 or earlier will contain +dates of the form +.IB yy / mm / dd +instead of +.IB yyyy / mm / dd +and may also contain different white space in the substitution for +.BR $\&Log$ . +.TP +.BI \-x "suffixes" +Use +.I suffixes +to characterize \*r files. +See +.BR ci (1) +for details. +.SH "KEYWORD SUBSTITUTION" +Strings of the form +.BI $ keyword $ +and +.BI $ keyword : .\|.\|. $ +embedded in +the text are replaced +with strings of the form +.BI $ keyword : value $ +where +.I keyword +and +.I value +are pairs listed below. +Keywords may be embedded in literal strings +or comments to identify a revision. +.PP +Initially, the user enters strings of the form +.BI $ keyword $ . +On checkout, +.B co +replaces these strings with strings of the form +.BI $ keyword : value $ . +If a revision containing strings of the latter form +is checked back in, the value fields will be replaced during the next +checkout. +Thus, the keyword values are automatically updated on checkout. +This automatic substitution can be modified by the +.B \-k +options. +.PP +Keywords and their corresponding values: +.TP +.B $\&Author$ +The login name of the user who checked in the revision. +.TP +.B $\&Date$ +The date and time (\*g) the revision was checked in. +.TP +.B $\&Header$ +A standard header containing the full pathname of the \*r file, the +revision number, the date (\*g), the author, the state, +and the locker (if locked). +.TP +.B $\&Id$ +Same as +.BR $\&Header$ , +except that the \*r filename is without a path. +.TP +.B $\&Locker$ +The login name of the user who locked the revision (empty if not locked). +.TP +.B $\&Log$ +The log message supplied during checkin, preceded by a header +containing the \*r filename, the revision number, the author, and the date +(\*g). +Existing log messages are +.I not +replaced. +Instead, the new log message is inserted after +.BR $\&Log: .\|.\|. $ . +This is useful for +accumulating a complete change log in a source file. +.TP +.B $\&RCSfile$ +The name of the \*r file without a path. +.TP +.B $\&Revision$ +The revision number assigned to the revision. +.TP +.B $\&Source$ +The full pathname of the \*r file. +.TP +.B $\&State$ +The state assigned to the revision with the +.B \-s +option of +.BR rcs (1) +or +.BR ci (1). +.SH "FILE MODES" +The working file inherits the read and execute permissions from the \*r +file. In addition, the owner write permission is turned on, unless +.B \-kv +is set or the file +is checked out unlocked and locking is set to strict (see +.BR rcs (1)). +.PP +If a file with the name of the working file exists already and has write +permission, +.B co +aborts the checkout, +asking beforehand if possible. +If the existing working file is +not writable or +.B \-f +is given, the working file is deleted without asking. +.SH FILES +.B co +accesses files much as +.BR ci (1) +does, except that it does not need to read the working file. +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +See +.BR ci (1) +for details. +.SH DIAGNOSTICS +The \*r pathname, the working pathname, +and the revision number retrieved are +written to the diagnostic output. +The exit status is zero if and only if all operations were successful. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +ci(1), ctime(3), date(1), ident(1), make(1), +rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1), +rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.SH LIMITS +Links to the \*r and working files are not preserved. +.PP +There is no way to selectively suppress the expansion of keywords, except +by writing them differently. In nroff and troff, this is done by embedding the +null-character +.B \e& +into the keyword. +.SH BUGS +The +.B \-d +option sometimes gets confused, and accepts no date before 1970. +.br diff --git a/gnu/usr.bin/rcs/co/co.c b/gnu/usr.bin/rcs/co/co.c new file mode 100644 index 0000000..9435574 --- /dev/null +++ b/gnu/usr.bin/rcs/co/co.c @@ -0,0 +1,769 @@ +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +/* + * RCS checkout operation + */ +/***************************************************************************** + * check out revisions from RCS files + ***************************************************************************** + */ + + +/* $Log: co.c,v $ + * Revision 5.9 1991/10/07 17:32:46 eggert + * ci -u src/RCS/co.c,v src/co.c <<\. + * -k affects just working file, not RCS file. + * + * Revision 5.8 1991/08/19 03:13:55 eggert + * Warn before removing somebody else's file. + * Add -M. Fix co -j bugs. Tune. + * + * Revision 5.7 1991/04/21 11:58:15 eggert + * Ensure that working file is newer than RCS file after co -[lu]. + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.6 1990/12/04 05:18:38 eggert + * Don't checkaccesslist() unless necessary. + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.5 1990/11/01 05:03:26 eggert + * Fix -j. Add -I. + * + * Revision 5.4 1990/10/04 06:30:11 eggert + * Accumulate exit status across files. + * + * Revision 5.3 1990/09/11 02:41:09 eggert + * co -kv yields a readonly working file. + * + * Revision 5.2 1990/09/04 08:02:13 eggert + * Standardize yes-or-no procedure. + * + * Revision 5.0 1990/08/22 08:10:02 eggert + * Permit multiple locks by same user. Add setuid support. + * Remove compile-time limits; use malloc instead. + * Permit dates past 1999/12/31. Switch to GMT. + * Make lock and temp files faster and safer. + * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. + * + * Revision 4.7 89/05/01 15:11:41 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.6 88/08/09 19:12:15 eggert + * Fix "co -d" core dump; rawdate wasn't always initialized. + * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint + * + * Revision 4.5 87/12/18 11:35:40 narten + * lint cleanups (from Guy Harris) + * + * Revision 4.4 87/10/18 10:20:53 narten + * Updating version numbers changes relative to 1.1, are actually + * relative to 4.2 + * + * Revision 1.3 87/09/24 13:58:30 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:21:38 jenkins + * Port to suns + * + * Revision 4.2 83/12/05 13:39:48 wft + * made rewriteflag external. + * + * Revision 4.1 83/05/10 16:52:55 wft + * Added option -u and -f. + * Added handling of default branch. + * Replaced getpwuid() with getcaller(). + * Removed calls to stat(); now done by pairfilenames(). + * Changed and renamed rmoldfile() to rmworkfile(). + * Replaced catchints() calls with restoreints(), unlink()--link() with rename(); + * + * Revision 3.7 83/02/15 15:27:07 wft + * Added call to fastcopy() to copy remainder of RCS file. + * + * Revision 3.6 83/01/15 14:37:50 wft + * Added ignoring of interrupts while RCS file is renamed; this avoids + * deletion of RCS files during the unlink/link window. + * + * Revision 3.5 82/12/08 21:40:11 wft + * changed processing of -d to use DATEFORM; removed actual from + * call to preparejoin; re-fixed printing of done at the end. + * + * Revision 3.4 82/12/04 18:40:00 wft + * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. + * Fixed printing of "done". + * + * Revision 3.3 82/11/28 22:23:11 wft + * Replaced getlogin() with getpwuid(), flcose() with ffclose(), + * %02d with %.2d, mode generation for working file with WORKMODE. + * Fixed nil printing. Fixed -j combined with -l and -p, and exit + * for non-existing revisions in preparejoin(). + * + * Revision 3.2 82/10/18 20:47:21 wft + * Mode of working file is now maintained even for co -l, but write permission + * is removed. + * The working file inherits its mode from the RCS file, plus write permission + * for the owner. The write permission is not given if locking is strict and + * co does not lock. + * An existing working file without write permission is deleted automatically. + * Otherwise, co asks (empty answer: abort co). + * Call to getfullRCSname() added, check for write error added, call + * for getlogin() fixed. + * + * Revision 3.1 82/10/13 16:01:30 wft + * fixed type of variables receiving from getc() (char -> int). + * removed unused variables. + */ + + + + +#include "rcsbase.h" + +static char const *getancestor P((char const*,char const*)); +static int buildjoin P((char const*)); +static int preparejoin P((void)); +static int rmlock P((struct hshentry const*)); +static int rmworkfile P((void)); +static void cleanup P((void)); + +static char const quietarg[] = "-q"; + +static char const *expandarg, *join, *suffixarg, *versionarg; +static char const *joinlist[joinlength]; /* revisions to be joined */ +static FILE *neworkptr; +static int exitstatus; +static int forceflag; +static int lastjoin; /* index of last element in joinlist */ +static int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */ +static int mtimeflag; +static struct hshentries *gendeltas; /* deltas to be generated */ +static struct hshentry *targetdelta; /* final delta to be generated */ +static struct stat workstat; + +mainProg(coId, "co", "$Id: co.c,v 5.9 1991/10/07 17:32:46 eggert Exp $") +{ + static char const cmdusage[] = + "\nco usage: co -{flpqru}[rev] -ddate -jjoinlist -sstate -w[login] -Vn file ..."; + + char *a, **newargv; + char const *author, *date, *rev, *state; + char const *joinfilename, *newdate, *neworkfilename; + int changelock; /* 1 if a lock has been changed, -1 if error */ + int expmode, r, tostdout, workstatstat; + struct buf numericrev; /* expanded revision number */ + char finaldate[datesize]; + + setrid(); + author = date = rev = state = nil; + bufautobegin(&numericrev); + expmode = -1; + suffixes = X_DEFAULT; + tostdout = false; + + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + while (a = *++argv, 0<--argc && *a++=='-') { + switch (*a++) { + + case 'r': + revno: + if (*a) { + if (rev) warn("redefinition of revision number"); + rev = a; + } + break; + + case 'f': + forceflag=true; + goto revno; + + case 'l': + if (lockflag < 0) { + warn("-l overrides -u."); + } + lockflag = 1; + goto revno; + + case 'u': + if (0 < lockflag) { + warn("-l overrides -u."); + } + lockflag = -1; + goto revno; + + case 'p': + tostdout = true; + goto revno; + + case 'I': + interactiveflag = true; + goto revno; + + case 'q': + quietflag=true; + goto revno; + + case 'd': + if (date) + redefined('d'); + str2date(a, finaldate); + date=finaldate; + break; + + case 'j': + if (*a) { + if (join) redefined('j'); + join = a; + } + break; + + case 'M': + mtimeflag = true; + goto revno; + + case 's': + if (*a) { + if (state) redefined('s'); + state = a; + } + break; + + case 'w': + if (author) redefined('w'); + if (*a) + author = a; + else + author = getcaller(); + break; + + case 'x': + suffixarg = *argv; + suffixes = a; + break; + + case 'V': + versionarg = *argv; + setRCSversion(versionarg); + break; + + case 'k': /* set keyword expand mode */ + expandarg = *argv; + if (0 <= expmode) redefined('k'); + if (0 <= (expmode = str2expmode(a))) + break; + /* fall into */ + default: + faterror("unknown option: %s%s", *argv, cmdusage); + + }; + } /* end of option processing */ + + if (argc<1) faterror("no input file%s", cmdusage); + if (tostdout) +# if text_equals_binary_stdio || text_work_stdio + workstdout = stdout; +# else + if (!(workstdout = fdopen(STDOUT_FILENO, FOPEN_W_WORK))) + efaterror("stdout"); +# endif + + /* now handle all filenames */ + do { + ffree(); + + if (pairfilenames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false) <= 0) + continue; + + /* now RCSfilename contains the name of the RCS file, and finptr + * points at it. workfilename contains the name of the working file. + * Also, RCSstat has been set. + */ + diagnose("%s --> %s\n", RCSfilename,tostdout?"stdout":workfilename); + + workstatstat = -1; + if (tostdout) { + neworkfilename = 0; + neworkptr = workstdout; + } else { + workstatstat = stat(workfilename, &workstat); + neworkfilename = makedirtemp(workfilename, 1); + if (!(neworkptr = fopen(neworkfilename, FOPEN_W_WORK))) { + if (errno == EACCES) + error("%s: parent directory isn't writable", + workfilename + ); + else + eerror(neworkfilename); + continue; + } + } + + gettree(); /* reads in the delta tree */ + + if (Head==nil) { + /* no revisions; create empty file */ + diagnose("no revisions present; generating empty revision 0.0\n"); + Ozclose(&fcopy); + if (workstatstat == 0) + if (!rmworkfile()) continue; + changelock = 0; + newdate = 0; + /* Can't reserve a delta, so don't call addlock */ + } else { + if (rev!=nil) { + /* expand symbolic revision number */ + if (!expandsym(rev, &numericrev)) + continue; + } else + switch (lockflag<0 ? findlock(false,&targetdelta) : 0) { + default: + continue; + case 0: + bufscpy(&numericrev, Dbranch?Dbranch:""); + break; + case 1: + bufscpy(&numericrev, targetdelta->num); + break; + } + /* get numbers of deltas to be generated */ + if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas))) + continue; + /* check reservations */ + changelock = + lockflag < 0 ? + rmlock(targetdelta) + : lockflag == 0 ? + 0 + : + addlock(targetdelta); + + if ( + changelock < 0 || + changelock && !checkaccesslist() || + !dorewrite(lockflag, changelock) + ) + continue; + + if (0 <= expmode) + Expand = expmode; + if (0 < lockflag && Expand == VAL_EXPAND) { + error("cannot combine -kv and -l"); + continue; + } + + if (join && !preparejoin()) continue; + + diagnose("revision %s%s\n",targetdelta->num, + 0first!=targetdelta, + finptr, MADV_SEQUENTIAL + ); + + if (!donerewrite(changelock)) + continue; + + newdate = targetdelta->date; + if (join) { + newdate = 0; + if (!joinfilename) { + aflush(neworkptr); + joinfilename = neworkfilename; + } + if (!buildjoin(joinfilename)) + continue; + } + } + if (!tostdout) { + r = 0; + if (mtimeflag && newdate) { + if (!join) + aflush(neworkptr); + r = setfiledate(neworkfilename, newdate); + } + if (r == 0) { + ignoreints(); + r = chnamemod(&neworkptr, neworkfilename, workfilename, + WORKMODE(RCSstat.st_mode, + !(Expand==VAL_EXPAND || lockflag<=0&&StrictLocks) + ) + ); + keepdirtemp(neworkfilename); + restoreints(); + } + if (r != 0) { + eerror(workfilename); + error("see %s", neworkfilename); + continue; + } + diagnose("done\n"); + } + } while (cleanup(), + ++argv, --argc >=1); + + tempunlink(); + Ofclose(workstdout); + exitmain(exitstatus); + +} /* end of main (co) */ + + static void +cleanup() +{ + if (nerror) exitstatus = EXIT_FAILURE; + Izclose(&finptr); + Ozclose(&frewrite); +# if !large_memory + if (fcopy!=workstdout) Ozclose(&fcopy); +# endif + if (neworkptr!=workstdout) Ozclose(&neworkptr); + dirtempunlink(); +} + +#if lint +# define exiterr coExit +#endif + exiting void +exiterr() +{ + dirtempunlink(); + tempunlink(); + _exit(EXIT_FAILURE); +} + + +/***************************************************************** + * The following routines are auxiliary routines + *****************************************************************/ + + static int +rmworkfile() +/* Function: prepares to remove workfilename, if it exists, and if + * it is read-only. + * Otherwise (file writable): + * if !quietmode asks the user whether to really delete it (default: fail); + * otherwise failure. + * Returns true if permission is gotten. + */ +{ + if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) { + /* File is writable */ + if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ", + workfilename, + myself(workstat.st_uid) ? "" : ", and you do not own it" + )) { + error(!quietflag && ttystdin() + ? "checkout aborted" + : "writable %s exists; checkout aborted", workfilename); + return false; + } + } + /* Actual unlink is done later by caller. */ + return true; +} + + + static int +rmlock(delta) + struct hshentry const *delta; +/* Function: removes the lock held by caller on delta. + * Returns -1 if someone else holds the lock, + * 0 if there is no lock on delta, + * and 1 if a lock was found and removed. + */ +{ register struct lock * next, * trail; + char const *num; + struct lock dummy; + int whomatch, nummatch; + + num=delta->num; + dummy.nextlock=next=Locks; + trail = &dummy; + while (next!=nil) { + whomatch = strcmp(getcaller(), next->login); + nummatch=strcmp(num,next->delta->num); + if ((whomatch==0) && (nummatch==0)) break; + /*found a lock on delta by caller*/ + if ((whomatch!=0)&&(nummatch==0)) { + error("revision %s locked by %s; use co -r or rcs -u",num,next->login); + return -1; + } + trail=next; + next=next->nextlock; + } + if (next!=nil) { + /*found one; delete it */ + trail->nextlock=next->nextlock; + Locks=dummy.nextlock; + next->delta->lockedby=nil; /* reset locked-by */ + return 1; /*success*/ + } else return 0; /*no lock on delta*/ +} + + + + +/***************************************************************** + * The rest of the routines are for handling joins + *****************************************************************/ + + + static char const * +addjoin(joinrev) + char *joinrev; +/* Add joinrev's number to joinlist, yielding address of char past joinrev, + * or nil if no such revision exists. + */ +{ + register char *j; + register struct hshentry const *d; + char terminator; + struct buf numrev; + struct hshentries *joindeltas; + + j = joinrev; + for (;;) { + switch (*j++) { + default: + continue; + case 0: + case ' ': case '\t': case '\n': + case ':': case ',': case ';': + break; + } + break; + } + terminator = *--j; + *j = 0; + bufautobegin(&numrev); + d = 0; + if (expandsym(joinrev, &numrev)) + d = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&joindeltas); + bufautoend(&numrev); + *j = terminator; + if (d) { + joinlist[++lastjoin] = d->num; + return j; + } + return nil; +} + + static int +preparejoin() +/* Function: Parses a join list pointed to by join and places pointers to the + * revision numbers into joinlist. + */ +{ + register char const *j; + + j=join; + lastjoin= -1; + for (;;) { + while ((*j==' ')||(*j=='\t')||(*j==',')) j++; + if (*j=='\0') break; + if (lastjoin>=joinlength-2) { + error("too many joins"); + return(false); + } + if (!(j = addjoin(j))) return false; + while ((*j==' ') || (*j=='\t')) j++; + if (*j == ':') { + j++; + while((*j==' ') || (*j=='\t')) j++; + if (*j!='\0') { + if (!(j = addjoin(j))) return false; + } else { + error("join pair incomplete"); + return false; + } + } else { + if (lastjoin==0) { /* first pair */ + /* common ancestor missing */ + joinlist[1]=joinlist[0]; + lastjoin=1; + /*derive common ancestor*/ + if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1]))) + return false; + } else { + error("join pair incomplete"); + return false; + } + } + } + if (lastjoin<1) { + error("empty join"); + return false; + } else return true; +} + + + + static char const * +getancestor(r1, r2) + char const *r1, *r2; +/* Yield the common ancestor of r1 and r2 if successful, nil otherwise. + * Work reliably only if r1 and r2 are not branch numbers. + */ +{ + static struct buf t1, t2; + + unsigned l1, l2, l3; + char const *r; + + l1 = countnumflds(r1); + l2 = countnumflds(r2); + if ((22 ? (unsigned)2 : l1); + VOID partialno(&t2, r2, l2>2 ? (unsigned)2 : l2); + r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string; + if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0) + return r; + } else if (cmpnumfld(r1, r2, l3+1)!=0) + return partialno(&t1,r1,l3); + } + error("common ancestor of %s and %s undefined", r1, r2); + return nil; +} + + + + static int +buildjoin(initialfile) + char const *initialfile; +/* Function: merge pairs of elements in joinlist into initialfile + * If workstdout is set, copy result to stdout. + * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink(). + */ +{ + struct buf commarg; + struct buf subs; + char const *rev2, *rev3; + int i; + char const *cov[10], *mergev[12]; + char const **p; + + bufautobegin(&commarg); + bufautobegin(&subs); + rev2 = maketemp(0); + rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */ + + cov[0] = nil; + /* cov[1] setup below */ + cov[2] = CO; + /* cov[3] setup below */ + p = &cov[4]; + if (expandarg) *p++ = expandarg; + if (suffixarg) *p++ = suffixarg; + if (versionarg) *p++ = versionarg; + *p++ = quietarg; + *p++ = RCSfilename; + *p = nil; + + mergev[0] = nil; + mergev[1] = nil; + mergev[2] = MERGE; + mergev[3] = mergev[5] = "-L"; + /* rest of mergev setup below */ + + i=0; + while (inum); + else { + bufscat(&subs, ","); + bufscat(&subs, joinlist[i-2]); + bufscat(&subs, ":"); + bufscat(&subs, joinlist[i-1]); + } + diagnose("revision %s\n",joinlist[i]); + bufscpy(&commarg, "-p"); + bufscat(&commarg, joinlist[i]); + cov[1] = rev2; + cov[3] = commarg.string; + if (runv(cov)) + goto badmerge; + diagnose("revision %s\n",joinlist[i+1]); + bufscpy(&commarg, "-p"); + bufscat(&commarg, joinlist[i+1]); + cov[1] = rev3; + cov[3] = commarg.string; + if (runv(cov)) + goto badmerge; + diagnose("merging...\n"); + mergev[4] = subs.string; + mergev[6] = joinlist[i+1]; + p = &mergev[7]; + if (quietflag) *p++ = quietarg; + if (lastjoin<=i+2 && workstdout) *p++ = "-p"; + *p++ = initialfile; + *p++ = rev2; + *p++ = rev3; + *p = nil; + switch (runv(mergev)) { + case DIFF_FAILURE: case DIFF_SUCCESS: + break; + default: + goto badmerge; + } + i=i+2; + } + bufautoend(&commarg); + bufautoend(&subs); + return true; + + badmerge: + nerror++; + bufautoend(&commarg); + bufautoend(&subs); + return false; +} diff --git a/gnu/usr.bin/rcs/doc/rcs.ms b/gnu/usr.bin/rcs/doc/rcs.ms new file mode 100644 index 0000000..7b3f807 --- /dev/null +++ b/gnu/usr.bin/rcs/doc/rcs.ms @@ -0,0 +1,1524 @@ +.\" Format this file with: +.\" pic file | tbl | troff -ms +.\" +.\" \*s stands for $, and avoids problems when this file is checked in. +.ds s $ +.\" PS and PE center pic diagrams. (The corresponding ms-macros may not.) +.de PS +.nr pE (\\n(.lu-\\$2u)/2u +.in +\\n(pEu +.ne \\$1u +.. +.de PE +.in -\\n(pEu +.. +.de D( +.DS +.nr VS 12p +.vs 12p +.I +.. +.de D) +.DE +.nr VS 18p +.vs 18p +.R +.. +.de Id +.ND \\$4 +.. +.Id $Id: rcs.ms,v 5.2 1991/01/03 10:57:28 eggert Exp $ +.RP +.TL +RCS\*-A System for Version Control +.sp +.AU +Walter F. Tichy +.AI +Department of Computer Sciences +Purdue University +West Lafayette, Indiana 47907 +.sp +.AB +An important problem in program development and maintenance is version control, +i.e., the task of keeping a software system consisting of many versions and +configurations well organized. +The Revision Control System (RCS) +is a software tool that assists with that task. +RCS manages revisions of text documents, in particular source programs, +documentation, and test data. +It automates the storing, retrieval, logging and identification of revisions, +and it provides selection mechanisms for composing configurations. +This paper introduces basic version control concepts and +discusses the practice of version control +using RCS. +For conserving space, RCS stores deltas, i.e., differences between +successive revisions. Several delta storage methods are discussed. +Usage statistics show that RCS's delta storage method is +space and time efficient. +The paper concludes with a detailed survey of version control tools. +.sp +\fBKeywords\fR: configuration management, history management, +version control, revisions, deltas. +.AE +.FS +An earlier version of this paper was published in +.I "Software\*-Practice & Experience" +.B 15 , +7 (July 1985), 637-654. +.FE +.nr VS 18p +.LP +.NH +Introduction +.PP +Version control is the task of keeping software +systems consisting of many versions and configurations well organized. +The Revision Control System (RCS) is a set of UNIX +commands that assist with that task. +.PP +RCS' primary function is to manage \fIrevision groups\fR. +A revision group is a set of text documents, called \fIrevisions\fR, +that evolved from each other. A new revision is +created by manually editing an existing one. +RCS organizes the revisions into an ancestral tree. The initial revision +is the root of the tree, and the tree edges indicate +from which revision a given one evolved. +Besides managing individual revision groups, RCS provides +flexible selection functions for composing configurations. +RCS may be combined with MAKE\u1\d, +resulting in a powerful package for version control. +.PP +RCS also offers facilities for +merging updates with customer modifications, +for distributed software development, and +for automatic identification. +Identification is the `stamping' +of revisions and configurations with unique markers. +These markers are akin to serial numbers, +telling software maintainers unambiguously which configuration +is before them. +.PP +RCS is designed for both production and experimental +environments. +In production environments, +access controls detect update conflicts and prevent overlapping changes. +In experimental environments, where strong controls are +counterproductive, it is possible to loosen the controls. +.PP +Although RCS was originally intended for programs, it is useful for any +text that is revised frequently and whose previous revisions must be +preserved. RCS has been applied successfully to store the source +text for drawings, VLSI layouts, documentation, specifications, +test data, form letters and articles. +.PP +This paper discusses the practice of +version control using RCS. +It also introduces basic version control concepts, +useful for clarifying current practice and designing similar systems. +Revision groups of individual components are treated in the next three sections, +and the extensions to configurations follow. +Because of its size, a survey of version control tools +appears at the end of the paper. +.NH +Getting started with RCS +.PP +Suppose a text file \fIf.c\fR is to be placed under control of RCS. +Invoking the check-in command +.D( +ci f.c +.D) +creates a new revision group with the contents of +\fIf.c\fR as the initial +revision (numbered 1.1) +and stores the group into the file \fIf.c,v\fR. +Unless told otherwise, the command deletes \fIf.c\fR. +It also asks for a description of the group. +The description should state the common purpose of all revisions in the group, +and becomes part of the group's documentation. +All later check-in commands will ask for a log entry, +which should summarize the changes made. +(The first revision is assigned a default log message, +which just records the fact that it is the initial revision.) +.PP +Files ending in \fI,v\fR +are called \fIRCS files\fR (\fIv\fR stands for \fIv\fRersions); +the others are called working files. +To get back the working file \fIf.c\fR in the previous example, +execute the check-out command: +.D( +co f.c +.D) +.R +This command extracts the latest revision from +the revision group \fIf.c,v\fR and writes +it into \fIf.c\fR. +The file \fIf.c\fR can now be edited and, when finished, +checked back in with \fIci\fR: +.D( +ci f.c +.D) +\fICi\fR assigns number 1.2 to +the new revision. +If \fIci\fR complains with the message +.D( +ci error: no lock set by +.D) +then the system administrator has decided to configure RCS for a +production environment by enabling the `strict locking feature'. +If this feature is enabled, all RCS files are initialized +such that check-in operations require a lock on the previous revision +(the one from which the current one evolved). +Locking prevents overlapping modifications if several people work on the same file. +If locking is required, the revision should +have been locked during the check-out by using +the option \fI\-l\fR: +.D( +co \-l f.c +.D) +Of course it is too late now for the check-out with locking, because +\fIf.c\fR has already been changed; checking out the file again +would overwrite the modifications. +(To prevent accidental overwrites, \fIco\fR senses the presence +of a working file and asks whether the user really intended to overwrite it. +The overwriting check-out is sometimes useful for +backing up to the previous revision.) +To be able to proceed with the check-in in the present case, first execute +.D( +rcs \-l f.c +.D) +This command retroactively locks the latest revision, unless someone +else locked it in the meantime. In this case, the two programmers +involved have to negotiate whose +modifications should take precedence. +.PP +If an RCS file is private, i.e., if only the owner of the file is expected +to deposit revisions into it, the strict locking feature is unnecessary and +may be disabled. +If strict locking is disabled, +the owner of the RCS file need not have a lock for check-in. +For safety reasons, all others +still do. Turning strict locking off and on is done with the commands: +.D( +rcs \-U f.c \fRand\fP rcs \-L f.c +.D) +These commands enable or disable the strict locking feature for each RCS file +individually. +The system administrator only decides whether strict locking is +enabled initially. +.PP +To reduce the clutter in a working directory, all RCS files can be moved +to a subdirectory with the name \fIRCS\fR. +RCS commands look first into that directory for RCS files. +All the commands presented above work +with the \fIRCS\fR subdirectory without change.\(dg +.FS \(dg +Pairs of RCS and working files can actually be specified in 3 ways: +a) both are given, b) only the working file is given, c) only the +RCS file is given. +If a pair is given, both files may have arbitrary path prefixes; +RCS commands pair them up intelligently. +.FE +.PP +It may be undesirable that \fIci\fR deletes the working file. +For instance, sometimes one would like to save the current revision, +but continue editing. +Invoking +.D( +ci \-l f.c +.D) +checks in \fIf.c\fR as usual, but performs an additional +check-out with locking afterwards. Thus, the working file does +not disappear after the check-in. +Similarly, the option +\fI\-u\fR does a check-in followed by a check-out without +locking. This option is useful if the file is needed for compilation after the check-in. +Both options update the identification markers in the working file +(see below). +.PP +Besides the operations \fIci\fR and \fIco\fR, RCS provides the following +commands: +.sp 0 +.nr VS 12p +.vs 12p +.TS +tab(%); +li l. +ident%extract identification markers +rcs%change RCS file attributes +rcsclean%remove unchanged working files (optional) +rcsdiff%compare revisions +rcsfreeze%record a configuration (optional) +rcsmerge%merge revisions +rlog%read log messages and other information in RCS files +.TE +A synopsis of these commands appears in the Appendix. +.NH 2 +Automatic Identification +.PP +RCS can stamp source and object code with special identification strings, +similar to product and serial numbers. +To obtain such identification, place the marker +.D( +\*sId\*s +.D) +into the text of a revision, for instance inside a comment. +The check-out operation will replace this marker with a string of the form +.D( +\*sId: filename revisionnumber date time author state locker \*s +.D) +This string need never be touched, because \fIco\fR keeps it +up to date automatically. +To propagate the marker into object code, simply put +it into a literal character string. In C, this is done as follows: +.D( +static char rcsid[] = \&"\*sId\*s\&"; +.D) +The command \fIident\fR extracts such markers from any file, in particular from +object code. +\fIIdent\fR helps to find out +which revisions of which modules were used in a given program. +It returns a complete and unambiguous component list, +from which a copy of the program can be reconstructed. +This facility is invaluable for program maintenance. +.PP +There are several additional identification markers, one for each component +of \*sId\*s. +The marker +.D( +\*sLog\*s +.D) +has a similar function. It accumulates +the log messages that are requested during check-in. +Thus, one can maintain the complete history of a revision directly inside it, +by enclosing it in a comment. +Figure 1 is a partial reproduction of a log contained in revision 4.1 of +the file \fIci.c\fR. The log appears at the beginning of the file, +and makes it easy to determine what the recent modifications were. +.sp +.nr VS 12p +.vs 12p +.ne 18 +.nf +.in +0.5i +/* \*sLog: ci.c,v \*s + * Revision 4.1 1983/05/10 17:03:06 wft + * Added option \-d and \-w, and updated assignment of date, etc. to new delta. + * Added handling of default branches. + * + * Revision 3.9 1983/02/15 15:25:44 wft + * Added call to fastcopy() to copy remainder of RCS file. + * + * Revision 3.8 1983/01/14 15:34:05 wft + * Added ignoring of interrupts while new RCS file is renamed; + * avoids deletion of RCS files by interrupts. + * + * Revision 3.7 1982/12/10 16:09:20 wft + * Corrected checking of return code from diff. + * An RCS file now inherits its mode during the first ci from the working file, + * except that write permission is removed. + */ +.in 0 +.ce 1 +Figure 1. Log entries produced by the marker \*sLog\*s. +.fi +.nr VS 18p +.vs 18p +.sp 0 +.LP +Since revisions are stored in the form of differences, +each log message is +physically stored once, +independent of the number of revisions present. +Thus, the \*sLog\*s marker incurs negligible space overhead. +.NH +The RCS Revision Tree +.PP +RCS arranges revisions in an ancestral tree. +The \fIci\fR command builds this tree; the auxiliary command \fIrcs\fR +prunes it. +The tree has a root revision, normally numbered 1.1, and successive revisions +are numbered 1.2, 1.3, etc. The first field of a revision number +is called the \fIrelease number\fR and the second one +the \fIlevel number\fR. Unless given explicitly, +the \fIci\fR command assigns a new revision number +by incrementing the level number of the previous revision. +The release number must be incremented explicitly, using the +\fI\-r\fR option of \fIci\fR. +Assuming there are revisions 1.1, 1.2, and 1.3 in the RCS file f.c,v, the command +.D( +ci \-r2.1 f.c \fRor\fP ci \-r2 f.c +.D) +assigns the number 2.1 to the new revision. +Later check-ins without the \fI\-r\fR option will assign the numbers 2.2, 2.3, +and so on. +The release number should be incremented only at major transition points +in the development, for instance when a new release of a software product has +been completed. +.NH 2 +When are branches needed? +.PP +A young revision tree is slender: +It consists of only one branch, called the trunk. +As the tree ages, side branches may form. +Branches are needed in the following 4 situations. +.IP "\fITemporary fixes\fR" +.sp 0 +Suppose a tree has 5 revisions grouped in 2 releases, +as illustrated in Figure 2. +Revision 1.3, the last one of release 1, is in operation at customer sites, +while release 2 is in active development. +.ne 4 +.PS 4i +.ps -2 +box "1.1" +arrow +box "1.2" +arrow +box "1.3" +arrow +box "2.1" +arrow +box "2.2" +arrow dashed +.ps +2 +.PE +.ce 1 +Figure 2. A slender revision tree. +.sp 0 +Now imagine a customer requesting a fix of +a problem in revision 1.3, although actual development has moved on +to release 2. RCS does not permit an extra +revision to be spliced in between 1.3 and 2.1, since that would not reflect +the actual development history. Instead, create a branch +at revision 1.3, and check in the fix on that branch. +The first branch starting at 1.3 has number 1.3.1, and +the revisions on that branch are numbered 1.3.1.1, 1.3.1.2, etc. +The double numbering is needed to allow for another +branch at 1.3, say 1.3.2. +Revisions on the second branch would be numbered +1.3.2.1, 1.3.2.2, and so on. +The following steps create +branch 1.3.1 and add revision 1.3.1.1: +.sp 0 +.I +.nr VS 12p +.vs 12p +.TS +tab(%); +l l l. + %co \-r1.3 f.c% \*- check out revision 1.3 + %edit f.c% \*- change it + %ci \-r1.3.1 f.c% \*- check it in on branch 1.3.1 +.TE +.nr VS 18p +.vs 18p +.R +This sequence of commands transforms the tree of Figure 2 into +the one in Figure 3. +Note that it may be necessary to incorporate the differences +between 1.3 and 1.3.1.1 +into a revision at level 2. The operation \fIrcsmerge\fR automates this +process (see the Appendix). +.ne 7 +.PS 4i +.ps -2 + box "1.1" + arrow + box "1.2" + arrow +R13: box "1.3" + arrow +R21: box "2.1" + arrow +R22: box "2.2" + arrow dashed + line invis down from R21.s +RB1: box "1.3.1.1" + arrow dashed right from RB1.e + arrow from R13.s to RB1.w +.ps +2 +.PE +.ce 1 +Figure 3. A revision tree with one side branch +.sp +.IP "\fIDistributed development and customer modifications\fR" +.sp 0 +Assume a situation as in Figure 2, where revision 1.3 is in operation +at several customer sites, +while release 2 is in development. +Customer sites should use RCS to store the distributed software. +However, customer modifications should not be placed on the same branch +as the distributed source; instead, they should be placed on a side branch. +When the next software distribution arrives, +it should be appended to the trunk of +the customer's RCS file, and the customer +can then merge the local modifications back into the new release. +In the above example, a +customer's RCS file would contain the following tree, assuming +that the customer has received revision 1.3, added his local modifications +as revision 1.3.1.1, then received revision 2.4, and merged +2.4 and 1.3.1.1, resulting in 2.4.1.1. +.ne 7 +.PS 4i +.ps -2 +R13: box "1.3" + line invis +R21: box invis + line invis +R22: box invis + line invis +R24: box "2.4" + line invis +R25: box invis + line invis + arrow from R13.e to R24.w + line invis down from R21.s +RB1: box "1.3.1.1" + arrow from R13.s to RB1.w + right + line invis down from R25.s +RB2: box "2.4.1.1" + arrow from R24.s to RB2.w +.ps +2 +.PE +.ce 1 +Figure 4. A customer's revision tree with local modifications. +.sp 1 +This approach is actually practiced in the CSNET project, +where several universities and a company cooperate +in developing a national computer network. +.IP "\fIParallel development\fR" +.sp 0 +Sometimes it is desirable to explore an alternate design or +a different implementation technique in parallel with the +main line development. Such development +should be carried out on a side branch. +The experimental changes may later be moved into the main line, or abandoned. +.IP "\fIConflicting updates\fR" +.sp 0 +A common occurrence is that one programmer +has checked out a revision, but cannot complete the assignment +for some reason. In the meantime, another person +must perform another modification +immediately. In that case, the second person should check-out the same revision, +modify it, and check it in on a side branch, for later merging. +.PP +Every node in a revision tree consists of the following attributes: +a revision number, a check-in date and time, the author's identification, +a log entry, a state and the actual text. All these attributes +are determined at the time the revision is checked in. +The state attribute indicates the status of a revision. +It is set automatically to `experimental' during check-in. +A revision can later be promoted to a higher status, for example +`stable' or `released'. The set of states is user-defined. +.NH 2 +Revisions are represented as deltas +.PP +For conserving space, RCS stores revisions in the form +of deltas, i.e., as differences between revisions. +The user interface completely hides this fact. +.PP +A delta is a sequence of edit commands that transforms one string +into another. The deltas employed by RCS are line-based, which means +that the only edit commands allowed are insertion and deletion of lines. +If a single character in a line is changed, the +edit scripts consider the entire line changed. +The program \fIdiff\fR\u2\d +produces a small, line-based delta between pairs of text files. +A character-based edit script would take much longer to compute, +and would not be significantly shorter. +.PP +Using deltas is a classical space-time tradeoff: deltas reduce the +space consumed, but increase access time. +However, a version control tool should impose as little delay +as possible on programmers. +Excessive delays discourage the use of version controls, +or induce programmers to take shortcuts that compromise system integrity. +To gain reasonably fast access time for both editing and compiling, +RCS arranges deltas in the following way. +The most recent revision on the trunk is stored intact. +All other revisions on the trunk are stored as reverse deltas. +A reverse delta describes how to go backward in the development history: +it produces the desired revision if applied to the successor of that revision. +This implementation has the advantage +that extraction of the latest revision is a simple and fast copy +operation. +Adding a new revision to the trunk is also fast: \fIci\fR simply +adds the new revision intact, replaces the previous +revision with a reverse delta, and keeps the rest of the old deltas. +Thus, \fIci\fR requires the computation +of only one new delta. +.PP +Branches need special treatment. The naive solution would be to +store complete copies for the tips of all branches. +Clearly, this approach would cost too much space. Instead, +RCS uses \fIforward\fR deltas for branches. Regenerating a revision +on a side branch proceeds as follows. First, extract the latest revision +on the trunk; secondly, apply reverse deltas until the fork revision for +the branch is obtained; thirdly, apply forward deltas until the desired +branch revision is reached. Figure 5 illustrates a tree with +one side branch. Triangles pointing to the left and right represent +reverse and forward deltas, respectively. +.ne 8 +.PS 4i +.ps -2 +define BD X [line invis $1 right .5; +line up .3 then left .5 down .3 then right .5 down .3 then up .3] X + +define FD X [line invis $1 right .5; +line left .5 down .3 then up .6 then right .5 down .3;] X + +right +D11: BD(" 1.1") + arrow right from D11.e +D12: BD(" 1.2") + arrow right from D12.e +D13: BD(" 1.3") + arrow right from D13.e +D21: BD(" 2.1") + arrow right from D21.e +D22: box "2.2" + line invis down from D21.s +F1: FD("1.3.1.1 ") + arrow from D13.se to F1.w + arrow from F1.e right + right +F2: FD("1.3.1.2 ") +.ps +2 +.PE +.ce 1 +Figure 5. A revision tree with reverse and forward deltas. +.sp 0 +.PP +Although implementing fast check-out for the latest trunk revision, +this arrangement has the disadvantage that generation of other revisions +takes time proportional to the number of deltas applied. For example, +regenerating the branch tip in Figure 5 requires application of five +deltas (including the initial one). Since usage statistics show that +the latest trunk revision is the one that is retrieved in 95 per cent +of all cases (see the section on usage statistics), biasing check-out time +in favor of that revision results in significant savings. +However, careful implementation of the delta application process is +necessary to provide low retrieval overhead for other revisions, in +particular for branch tips. +.PP +There are several techniques for delta application. +The naive one is to pass each delta to a general-purpose text editor. +A prototype of RCS invoked the UNIX editor \fIed\fR both +for applying deltas and for expanding the identification markers. +Although easy to implement, performance was poor, owing to the +high start-up costs and excess generality of \fIed\fR. An intermediate +version of RCS used a special-purpose, stream-oriented editor. +This technique reduced the cost of applying a delta to the cost of +checking out the latest trunk revision. The reason for this behavior +is that each delta application involves a complete pass over +the preceding revision. +.PP +However, there is a much better algorithm. Note that the deltas are +line oriented and that most of the work of a stream editor involves +copying unchanged lines from one revision to the next. A faster +algorithm avoids unnecessary copying of character strings by using +a \fIpiece table\fR. +A piece table is a one-dimensional array, specifying how a given +revision is `pieced together' from lines in the RCS file. +Suppose piece table \fIPT\dr\u\fR represents revision \fIr\fR. +Then \fIPT\dr\u[i]\fR contains the starting position of line \fIi\fR +of revision \fIr\fR. +Application of the next delta transforms piece table \fIPT\dr\u\fR +into \fIPT\dr+1\u\fR. For instance, a delete command removes a +series of entries from the piece table. An insertion command inserts +new entries, moving the entries following the insertion point further down the +array. The inserted entries point to the text lines in the delta. +Thus, no I/O is involved except for reading the delta itself. When all +deltas have been applied to the piece table, a sequential pass +through the table looks up each line in the RCS file and copies it to +the output file, updating identification markers at the same time. +Of course, the RCS file must permit random access, since the copied +lines are scattered throughout that file. Figure 6 illustrates an +RCS file with two revisions and the corresponding piece tables. +.ne 13 +.sp 6 +.ce 1 +\fIFigure 6 is not available.\fP +.sp 5 +.ce 1 +Figure 6. An RCS file and its piece tables +.sp 0 +.PP +The piece table approach has the property that the time for applying a single +delta is roughly determined by the size of the delta, and not by the +size of the revision. For example, if a delta is +10 per cent of the size of a revision, then applying it takes only +10 per cent of the time to generate the latest trunk revision. (The stream +editor would take 100 per cent.) +.PP +There is an important alternative for representing deltas that affects +performance. SCCS\u3\d, +a precursor of RCS, uses \fIinterleaved\fR deltas. +A file containing interleaved deltas is partitioned into blocks of lines. +Each block has a header that specifies to which revision(s) the block +belongs. The blocks are sorted out in such a way that a single +pass over the file can pick up all the lines belonging to a given +revision. Thus, the regeneration time for all revisions is the same: +all headers must be inspected, and the associated blocks either copied +or skipped. As the number of revisions increases, the cost of retrieving +any revision is much higher than the cost of checking out the +latest trunk revision with reverse deltas. A detailed comparison +of SCCS's interleaved deltas and RCS's reverse deltas can be found +in Reference 4. +This reference considers the version of RCS with the +stream editor only. The piece table method improves performance +further, so that RCS is always faster than SCCS, except if 10 +or more deltas are applied. +.PP +Additional speed-up for both delta methods can be obtained by caching +the most recently generated revision, as has been implemented in DSEE.\u5\d +With caching, access time to frequently used revisions can approach normal file +access time, at the cost of some additional space. +.NH +Locking: A Controversial Issue +.PP +The locking mechanism for RCS was difficult to design. +The problem and its solution are first presented in their `pure' form, +followed by a discussion of the complications +caused by `real-world' considerations. +.PP +RCS must prevent two or more persons from depositing competing changes of the +same revision. +Suppose two programmers check out revision 2.4 and +modify it. Programmer A checks in a revision before programmer B\&. +Unfortunately, programmer B has not seen A's +changes, so the effect is that A's changes are covered up by B's deposit. +A's changes are not lost since all revisions +are saved, but they are confined to a single revision.\(dd +.FS \(dd +Note that this problem is entirely different from the atomicity problem. +Atomicity means that +concurrent update operations on the same RCS file cannot be permitted, +because that may result in inconsistent data. +Atomic updates are essential (and implemented in RCS), +but do not solve the conflict discussed here. +.FE +.PP +This conflict is prevented in RCS by locking. +Whenever someone intends to edit a revision (as opposed +to reading or compiling it), the revision should be checked out +and locked, +using the \fI\-l\fR option on \fIco\fR. On subsequent check-in, +\fIci\fR tests the lock and then removes it. +At most one programmer at a time may +lock a particular revision, and only this programmer may check in +the succeeding revision. +Thus, while a revision is locked, it is the exclusive responsibility +of the locker. +.PP +An important maxim for software tools like RCS is that they must +not stand in the way of making progress with a project. +This consideration leads to several weakenings of the locking mechanism. +First of all, even if a revision is locked, it can +still be checked out. This is necessary if other people +wish to compile or inspect the locked revision +while the next one is in preparation. The only operations they +cannot do are to lock the revision or to check in the succeeding one. Secondly, +check-in operations on other branches in the RCS file are still possible; the +locking of one revision does not affect any other revision. +Thirdly, revisions are occasionally locked for a long period of time +because a programmer is absent or otherwise unable to complete +the assignment. If another programmer has to make a pressing change, +there are the following three alternatives for making progress: +a) find out who is holding the lock and ask that person to release it; +b) check out the locked revision, modify it, check it +in on a branch, and merge the changes later; +c) break the lock. Breaking a lock leaves a highly visible +trace, namely an electronic mail message that is sent automatically to the +holder of the lock, recording the breaker and a commentary requested from him. +Thus, breaking locks is tolerated under certain circumstances, +but will not go unnoticed. +Experience has shown that the automatic mail message attaches a high enough +stigma to lock breaking, +such that programmers break locks only in real emergencies, +or when a co-worker resigns and leaves locked revisions behind. +.PP +If an RCS file is private, i.e., when a programmer owns an RCS file +and does not expect anyone else to perform check-in operations, +locking is an unnecessary nuisance. +In this case, +the `strict locking feature' discussed earlier may be disabled, +provided that file protection +is set such that only the owner may write the RCS file. +This has the effect that only the owner can check-in revisions, +and that no lock is needed for doing so. +.PP +As added protection, +each RCS file contains an access list that specifies the users +who may execute update operations. If an access list is empty, +only normal UNIX file protection applies. Thus, the access list is +useful for restricting the set of people who would otherwise have update +permission. Just as with locking, the access list +has no effect on read-only operations such as \fIco\fR. This approach +is consistent with the UNIX philosophy of openness, which contributes +to a productive software development environment. +.NH +Configuration Management +.PP +The preceding sections described how RCS deals with revisions of individual +components; this section discusses how to handle configurations. +A configuration is a set of revisions, where each revision comes +from a different revision group, and the revisions are selected +according to a certain criterion. +For example, +in order to build a functioning compiler, the `right' +revisions from the scanner, the parser, the optimizer +and the code generator must be combined. +RCS, in conjunction with MAKE, +provides a number of facilities to effect a smooth selection. +.NH 2 +RCS Selection Functions +.PP +.IP "\fIDefault selection\fR" +.sp 0 +During development, the usual selection criterion is to choose +the latest revision of all components. The \fIco\fR command +makes this selection by default. For example, the command +.D( +co *,v +.D) +retrieves the latest revision on the default branch of each RCS file +in the current directory. +The default branch is usually the trunk, but may be +set to be a side branch. +Side branches as defaults are needed in distributed software development, +as discussed in the section on the RCS revision tree. +.sp +.IP "\fIRelease based selection\fR" +.sp 0 +Specifying a release or branch number selects the latest revision in +that release or branch. +For instance, +.D( +co \-r2 *,v +.D) +retrieves the latest revision with release number 2 from each RCS file. +This selection is convenient if a release has been completed and +development has moved on to the next release. +.sp +.IP "\fIState and author based selection\fR" +.sp 0 +If the highest level number within a given release number +is not the desired one, +the state attribute can help. For example, +.D( +co \-r2 \-sReleased *,v +.D) +retrieves the latest revision with release number 2 whose state attribute +is `Released'. +Of course, the state attribute has to be set appropriately, using the +\fIci\fR or \fIrcs\fR commands. +Another alternative is to select a revision by its author, +using the \fI\-w\fR option. +.sp +.IP "\fIDate based selection\fR" +.sp 0 +Revisions may also be selected by date. +Suppose a release of an entire system was +completed and current on March 4, at 1:00 p.m. local time. Then the command +.D( +co \-d'March 4, 1:00 pm LT' *,v +.D) +checks out all the components of that release, independent of the numbering. +The \fI\-d\fR option specifies a `cutoff date', i.e., +the revision selected has a check-in date that +is closest to, but not after the date given. +.IP "\fIName based selection\fR" +.sp 0 +The most powerful selection function is based on assigning symbolic +names to revisions and branches. +In large systems, a single release number or date is not sufficient +to collect the appropriate revisions from all groups. +For example, suppose one wishes to combine release 2 +of one subsystem and release 15 of another. +Most likely, the creation dates of those releases differ also. +Thus, a single revision number or date passed to the \fIco\fR command +will not suffice to select the right revisions. +Symbolic revision numbers solve this problem. +Each RCS file may contain a set of symbolic names that are mapped +to numeric revision numbers. For example, assume +the symbol \fIV3\fR is bound to release number 2 in file \fIs,v\fR, and to +revision number 15.9 in \fIt,v\fR. +Then the single command +.D( +co \-rV3 s,v t,v +.D) +retrieves the latest revision of release 2 from \fIs,v\fR, +and revision 15.9 from \fIt,v\fR. +In a large system with many modules, checking out all +revisions with one command greatly simplifies configuration management. +.PP +Judicious use of symbolic revision numbers helps with organizing +large configurations. +A special command, \fIrcsfreeze\fR, +assigns a symbolic revision number to a selected revision +in every RCS file. +\fIRcsfreeze\fR effectively freezes a configuration. +The assigned symbolic revision number selects all components +of the configuration. +If necessary, symbolic numbers +may even be intermixed with numeric ones. Thus, \fIV3.5\fR in the +above example +would select revision 2.5 in \fIs,v\fR and branch 15.9.5 in \fIt,v\fR. +.PP +The options \fI\-r\fR, \fI\-s\fR, \fI\-w\fR and \fI\-d\fR +may be combined. If a branch is given, the latest revision +on that branch satisfying all conditions is retrieved; +otherwise, the default branch is used. +.NH 2 +Combining MAKE and RCS +.PP +MAKE\u1\d +is a program that processes configurations. +It is driven by configuration specifications +recorded in a special file, called a `Makefile'. +MAKE avoids redundant processing steps +by comparing creation dates of source and processed objects. +For example, when instructed to compile all +modules of a given system, it only recompiles +those source modules that were changed +since they were processed last. +.PP +MAKE has been extended with an auto-checkout feature for RCS.* +.FS * +This auto-checkout extension is available only in some versions of MAKE, +e.g. GNU MAKE. +.FE +When a certain file to be processed is not present, +MAKE attempts a check-out operation. +If successful, MAKE performs the required processing, and then deletes +the checked out file to conserve space. +The selection parameters discussed above can be passed to MAKE +either as parameters, or directly embedded in the Makefile. +MAKE has also been extended to search the subdirectory named \fIRCS\fR +for needed files, rather than just the current working directory. +However, if a working file is present, MAKE totally ignores the corresponding +RCS file and uses the working file. +(In newer versions of MAKE distributed by AT&T and others, +auto-checkout can be +achieved with the rule DEFAULT, instead of a special extension of MAKE. +However, a file checked out by the rule DEFAULT +will not be deleted after processing. \fIRcsclean\fR can be +used for that purpose.) +.PP +With auto-checkout, RCS/MAKE can effect a selection rule +especially tuned for multi-person software development and maintenance. +In these situations, +programmers should obtain configurations that consist of +the revisions they have personally checked out plus the latest +checked in revision of all other revision groups. +This schema can be set up as follows. +.PP +Each programmer chooses a working directory +and places into it a symbolic link, named \fIRCS\fR, +to the directory containing the relevant RCS files. +The symbolic link makes sure that \fIco\fR and \fIci\fR +operations need only specify the working files, and that +the Makefile need not be changed. +The programmer then checks out the needed files and modifies them. +If MAKE is invoked, +it composes configurations by selecting those +revisions that are checked out, and the rest from the +subdirectory \fIRCS\fR. +The latter selection may be controlled by a symbolic +revision number or any of the other selection criteria. +If there are several programmers editing in separate working directories, +they are insulated from each other's changes until checking in their +modifications. +.PP +Similarly, a maintainer can recreate an older configuration +by starting to work in an empty working directory. +During the initial MAKE invocation, all revisions are selected from RCS files. +As the maintainer checks out files and modifies them, +a new configuration is gradually built up. +Every time MAKE is invoked, it substitutes the modified revisions +into the configuration being manipulated. +.PP +A final application of RCS is to use it for storing Makefiles. +Revision groups of Makefiles represent +multiple versions of configurations. +Whenever a configuration is baselined or distributed, +the best approach is to unambiguously fix +the configuration with a symbolic revision number by calling +\fIrcsfreeze\fR, +to embed that symbol into the Makefile, and to +check in the Makefile (using the same symbolic revision number). +With this approach, old configurations +can be regenerated easily and reliably. +.NH +Usage Statistics +.PP +The following usage statistics were collected on two DEC VAX-11/780 +computers of the Purdue Computer Science Department. Both machines +are mainly used for research purposes. Thus, the data +reflect an environment in which the majority of projects +involve prototyping and advanced software development, +but relatively little long-term maintenance. +.PP +For the first experiment, +the \fIci\fR and \fIco\fR operations were instrumented +to log the number of backward and forward deltas applied. +The data were collected during a 13 month period +from Dec. 1982 to Dec. 1983. +Table I summarizes the results. +.sp 0 +.nr VS 12p +.vs 12p +.TS +center,box,tab(#); +c|c|c|c|c s|c s +c|c|c|c|c s|c s +l|n|n|n|n n|n n. +Operation#Total#Total deltas#Mean deltas#Operations#Branch + #operations #applied#applied#with >1 delta#operations +_ +co # 7867# 9320#1.18#509#(6%)#203#(3%) +ci # 3468# 2207#0.64# 85#(2%)# 75#(2%) +ci & co#11335#11527#1.02#594#(5%)#278#(2%) +.TE +.ce 1 +Table I. Statistics for \fIco\fR and \fIci\fR operations. +.nr VS 18p +.vs 18p +.PP +The first two lines show statistics for check-out and check-in; +the third line shows the combination. +Recall that \fIci\fR performs an implicit check-out to obtain +a revision for computing the delta. +In all measures presented, the most recent revision (stored intact) +counts as one delta. The number of deltas applied represents +the number of passes necessary, where the first `pass' is a copying step. +.PP +Note that the check-out operation is executed more than +twice as frequently as the check-in operation. +The fourth column gives the mean number of deltas +applied in all three cases. +For \fIci\fR, the mean number of deltas applied is less +than one. +The reasons are that the initial check-in requires no delta at all, and that +the only time \fIci\fR requires more than one delta is for branches. +Column 5 shows the actual number of operations that applied more than one +delta. +The last column indicates that branches were not used often. +.PP +The last three columns demonstrate that the most recent trunk revision +is by far the most frequently accessed. +For RCS, check-out of +this revision is a simple copy operation, which is the absolute minimum +given the copy-semantics of \fIco\fR. +Access to older revisions and branches +is more common in non-academic environments, +yet even if access to older deltas were an order +of magnitude more frequent, +the combined average number of deltas applied would still be below 1.2. +Since RCS is faster than SCCS until up to 10 delta applications, +reverse deltas are clearly the method of choice. +.PP +The second experiment, conducted in March of 1984, +involved surveying the existing RCS files +on our two machines. The goal was to determine the mean number of +revisions per RCS file, as well as the space consumed by them. +Table II shows the results. (Tables I and II were produced at different +times and are unrelated.) +.sp 0 +.nr VS 12p +.vs 12p +.TS +center,box,tab(#); +c | c | c | c | c | c | c +c | c | c | c | c | c | c +l | n | n | n | n | n | n. + #Total RCS#Total#Mean#Mean size of#Mean size of#Overhead + #files#revisions#revisions#RCS files#revisions +_ +All files #8033#11133#1.39#6156#5585#1.10 +Files with#1477# 4578#3.10#8074#6041#1.34 +\(>= 2 deltas +.TE +.ce 1 +Table II. Statistics for RCS files. +.nr VS 18p +.vs 18p +.PP +The mean number of revisions per RCS file is 1.39. +Columns 5 and 6 show the mean sizes (in bytes) of an RCS file +and of the latest revision of each RCS file, respectively. +The `overhead' column contains the ratio of the mean sizes. +Assuming that all revisions in an RCS file are approximately the same size, +this ratio gives a measure of the space consumed by the extra revisions. +.PP +In our sample, over 80 per cent of the RCS files contained only a single revision. +The reason is that our +systems programmers routinely check in all source files +on the distribution tapes, even though they may never touch them again. +To get a better indication of how much space savings are possible +with deltas, all measures with those files +that contained 2 or more revisions were recomputed. Only for those files +is RCS necessary. +As shown in the second line, the average number of revisions for those files is +3.10, with an overhead of 1.34. This means that the extra 2.10 deltas +require 34 per cent extra space, or +16 per cent per extra revision. +Rochkind\u3\d +measured the space consumed by SCCS, and +reported an average of 5 revisions per group +and an overhead of 1.37 (or about 9 per cent per extra revision). +In a later paper, Glasser\u6\d +observed an average of 7 revisions per group in a single, large project, +but provided no overhead figure. +In his paper on DSEE\u5\d, +Leblang reported that delta storage combined with blank compression +results in an overhead of a mere 1\-2 per cent per revision. +Since leading blanks accounted for about 20 per cent of the surveyed Pascal +programs, a revision group with 5\-10 members was smaller +than a single cleartext copy. +.PP +The above observations demonstrate clearly that the space needed +for extra revisions is small. With delta storage, the luxury of +keeping multiple revisions online is certainly affordable. +In fact, introducing a system with delta storage may reduce +storage requirements, because programmers often save back-up copies +anyway. Since back-up copies are stored much more efficiently with deltas, +introducing a system such as RCS may +actually free a considerable amount of space. +.NH +Survey of Version Control Tools +.PP +The need to keep back-up copies of software arose when +programs and data were no longer stored on paper media, but were entered +from terminals and stored on disk. +Back-up copies are desirable for reliability, and many modern editors +automatically save a back-up copy for every file touched. +This strategy +is valuable for short-term back-ups, but not suitable for long-term +version control, since an existing back-up copy is overwritten whenever the +corresponding file is edited. +.PP +Tape archives are suitable for long-term, offline storage. +If all changed files are dumped on a back-up tape once per day, old revisions +remain accessible. However, tape archives are unsatisfactory +for version control in several ways. First, backing up the file +system every 24 hours does not capture intermediate revisions. +Secondly, the old revisions are not online, +and accessing them is tedious and time-consuming. +In particular, it is impractical to +compare several old revisions of a group, +because that may require mounting and searching several tapes. +Tape archives are important fail-safe tools in the +event of catastrophic disk failures or accidental deletions, +but they are ill-suited for version control. +Conversely, version control tools do not obviate the +need for tape archives. +.PP +A natural technique for keeping several old revisions online is +to never delete a file. +Editing a file +simply creates a new file with the same +name, but with a different sequence number. +This technique, available as an option in DEC's VMS operating system, +turns out to be inadequate for version control. +First, it is prohibitively expensive in terms of storage costs, +especially since no data compression techniques are employed. +Secondly, indiscriminately storing every change produces too many +revisions, and programmers have difficulties distinguishing them. +The proliferation of revisions forces programmers to spend much time on +finding and deleting useless files. +Thirdly, most of the support functions like locking, logging, +revision selection, +and identification described in this paper are not available. +.PP +An alternative approach is to separate editing from revision control. +The user may repeatedly edit a given revision, +until freezing it with an explicit command. +Once a revision is frozen, it is stored permanently and can no longer be modified. +(In RCS, freezing a revisions is done with \fIci\fR.) +Editing a frozen revision implicitly creates a new one, which +can again be changed repeatedly until it is frozen itself. +This approach saves exactly those revisions that the user +considers important, and keeps the number of revisions manageable. +IBM's CLEAR/CASTER\u7\d, +AT&T's SCCS\u3\d, +CMU's SDC\u8\d +and DEC's CMS\u9\d, +are examples of version control systems using this approach. +CLEAR/CASTER maintains a data base of programs, specifications, +documentation and messages, using deltas. +Its goal is to provide control over the development process from a +management viewpoint. +SCCS stores multiple revisions of source text in an ancestral tree, +records a log entry for each revision, +provides access control, and has facilities +for uniquely identifying each revision. +An efficient delta technique +reduces the space consumed by each revision group. +SDC is much simpler than SCCS because it stores not more than +two revisions. However, it maintains a complete log for all old +revisions, some of which may be on back-up tape. +CMS, like SCCS, manages tree-structured revision groups, +but offers no identification mechanism. +.PP +Tools for dealing with configurations are still in a state of flux. +SCCS, SDC and CMS can be combined with MAKE or MAKE-like programs. +Since flexible selection rules are missing from all these tools, +it is sometimes difficult +to specify precisely which revision of each group +should be passed to MAKE for building a desired configuration. +The Xerox Cedar system\u10\d +provides a `System Modeller' that can rebuild +a configuration from an arbitrary set of module revisions. +The revisions of a module are only distinguished by creation time, +and there is no tool for managing groups. +Since the selection rules are primitive, +the System Modeller appears to be somewhat tedious to use. +Apollo's DSEE\u5\d +is a sophisticated software engineering environment. +It manages revision groups in a way similar to SCCS and CMS. Configurations +are built using `configuration threads'. +A configuration thread states which revision of each group +named in a configuration should be chosen. +A configuration thread may contain dynamic specifiers +(e.g., `choose the revisions I am currently working on, +and the most recent revisions otherwise'), which are bound +automatically at build time. +It also provides a notification mechanism for alerting +maintainers about the need to rebuild a system after a change. +.PP +RCS is based on a general model for describing +multi-version/multi-configuration systems\u11\d. +The model describes systems using AND/OR graphs, where AND nodes represent +configurations, and OR nodes represent version groups. +The model gives rise to a suit of selection rules for +composing configurations, almost all of which are implemented in RCS. +The revisions selected by RCS are passed to MAKE for configuration building. +Revision group management is modelled after SCCS. +RCS retains SCCS's best features, +but offers a significantly simpler user interface, +flexible selection rules, adequate integration with MAKE +and improved identification. +A detailed comparison of RCS and SCCS appears in Reference 4. +.PP +An important component of all revision control systems +is a program for computing deltas. +SCCS and RCS use the program \fIdiff\fR\u2\d, +which first computes the longest common substring of two +revisions, and then produces the delta from that substring. +The delta is simply an edit script consisting of deletion and +insertion commands that generate one revision from the other. +.PP +A delta based on a longest common substring is not necessarily minimal, +because it does not take advantage of crossing block moves. +Crossing block moves arise if two or more blocks of lines +(e.g., procedures) +appear in a different order in two revisions. +An edit script derived from a longest common substring +first deletes the shorter of the two blocks, and then reinserts it. +Heckel\u12\d +proposed an algorithm for detecting block moves, but +since the algorithm is based on heuristics, +there are conditions +under which the generated delta is far from minimal. +DSEE uses this algorithm combined with blank compression, +apparently with satisfactory overall results. +A new algorithm that is guaranteed to produce a minimal delta based on +block moves appears in Reference 13. +A future release of RCS will use this algorithm. +.PP +\fIAcknowledgements\fR: +Many people have helped make RCS a success by contributed criticisms, suggestions, +corrections, and even whole new commands (including manual pages). +The list of people is too long to be +reproduced here, but my sincere thanks for their help and +goodwill goes to all of them. +.sp +.nr VS 12p +.vs 12p +.SH +Appendix: Synopsis of RCS Operations +.LP +.IP "\fIci\fP \fB\- check in revisions\fP" +.sp 0 +\fICi\fR stores the contents of a working file into the +corresponding RCS file as a new revision. +If the RCS file doesn't exist, \fIci\fR creates it. +\fICi\fR removes the working file, unless one of the options +\fI\-u\fR or \fI\-l\fR is present. +For each check-in, \fIci\fR asks for a commentary +describing the changes relative to the previous revision. +.sp 1 +\fICi\fR assigns the revision number given by the \fI\-r\fR option; +if that option is missing, it derives the number from the +lock held by the user; if there is no lock and locking is not strict, +\fIci\fR increments the number of the latest revision on the trunk. +A side branch can only be started by explicitly specifying its +number with the \fI\-r\fR option during check-in. +.sp 1 +\fICi\fR also determines +whether the revision to be checked in is different from the +previous one, and asks whether to proceed if not. +This facility simplifies check-in operations for large systems, +because one need not remember which files were changed. +.sp 1 +The option \fI\-k\fR searches the checked in file for identification +markers containing +the attributes +revision number, check-in date, author and state, and assigns these +to the new revision rather than computing them. This option is +useful for software distribution: Recipients of distributed software +using RCS should check in updates with the \fI\-k\fR option. +This convention guarantees that revision numbers, check-in dates, +etc., are the same at all sites. +.IP "\fIco\fP \fB\- check out revisions\fP" +.sp 0 +\fICo\fR retrieves revisions according to revision number, +date, author and state attributes. It either places the revision +into the working file, or prints it on the standard output. +\fICo\fR always expands the identification markers. +.IP "\fIident\fP \fB\- extract identification markers\fP" +.sp 0 +\fIIdent\fR extracts the identification markers expanded by \fIco\fR +from any file and prints them. +.IP "\fIrcs\fP \fB\- change RCS file attributes\fP" +.sp 0 +\fIRcs\fR is an administrative operation that changes access lists, +locks, unlocks, breaks locks, toggles the strict-locking feature, +sets state attributes and symbolic revision numbers, changes the +description, and deletes revisions. A revision can +only be deleted if it is not the fork of a side branch. +.IP "\fIrcsclean\fP \fB\- clean working directory\fP" +.sp 0 +.ne 10 +\fIRcsclean\fR removes working files that were checked out but never changed.* +.FS * +The \fIrcsclean\fP and \fIrcsfreeze\fP commands +are optional and are not always installed. +.FE +.IP "\fIrcsdiff\fP \fB\- compare revisions\fP" +.sp 0 +\fIRcsdiff\fR compares two revisions and prints their +difference, using the UNIX tool \fIdiff\fR. +One of the revisions compared may be checked out. +This command is useful for finding out about changes. +.IP "\fIrcsfreeze\fP \fB\- freeze a configuration\fP" +.sp 0 +\fIRcsfreeze\fR assigns the same symbolic revision number +to a given revision in all RCS files. +This command is useful for accurately recording a configuration.* +.IP "\fIrcsmerge\fP \fB\- merge revisions\fP" +.sp 0 +\fIRcsmerge\fR merges two revisions, \fIrev1\fR and \fIrev2\fR, +with respect to a common ancestor. +A 3-way file comparison determines the segments of lines that +are (a) the same in all three revisions, or (b) the same in 2 revisions, +or (c) different in all three. For all segments of type (b) where +\fIrev1\fR is the differing revision, +the segment in \fIrev1\fR replaces the corresponding segment of \fIrev2\fR. +Type (c) indicates an overlapping change, is flagged as an error, and requires user +intervention to select the correct alternative. +.IP "\fIrlog\fP \fB\- read log messages\fP" +.sp 0 +\fIRlog\fR prints the log messages and other information in an RCS file. +.bp +.LP +.nr VS 12p +.vs 12p +.]< +.ds [F 1 +.]- +.ds [K FELD02 +.ds [K MakeArticle +.ds [A Feldman, Stuart I. +.ds [D March 1979 +.ds [T Make\*-A Program for Maintaining Computer Programs +.ds [J Software\*-Practice & Experience +.ds [V 9 +.ds [N 3 +.ds [P 255-265 +.nr [P 1 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 1 journal-article +.ds [F 2 +.]- +.ds [K HUNT01 +.ds [T An Algorithm for Differential File Comparison +.ds [A Hunt, James W. +.as [A " and McIlroy, M. D. +.ds [I Computing Science Technical Report, Bell Laboratories +.ds [R 41 +.ds [D June 1976 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 4 tech-report +.ds [F 3 +.]- +.ds [K SCCS +.ds [A Rochkind, Marc J. +.ds [D Dec. 1975 +.ds [T The Source Code Control System +.ds [J IEEE Transactions on Software Engineering +.ds [V SE-1 +.ds [N 4 +.ds [P 364-370 +.nr [P 1 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 1 journal-article +.ds [F 4 +.]- +.ds [K TICH08 +.ds [T Design, Implementation, and Evaluation of a Revision Control System +.ds [A Tichy, Walter F. +.ds [B Proceedings of the 6th International Conference on Software Engineering +.ds [I ACM, IEEE, IPS, NBS +.ds [D September 1982 +.ds [P 58-67 +.nr [P 1 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 3 article-in-book +.ds [F 5 +.]- +.ds [K LEBL01 +.ds [A Leblang, David B. +.as [A " and Chase, Robert P. +.ds [T Computer-Aided Software Engineering in a Distributed Workstation Environment +.ds [O Proceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium +.as [O " on Practical Software Development Environments. +.ds [J SIGPLAN Notices +.ds [V 19 +.ds [N 5 +.ds [D May 1984 +.ds [P 104-112 +.nr [P 1 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 1 journal-article +.ds [F 1 +.ds [F 3 +.ds [F 6 +.]- +.ds [K SCCSEval +.ds [A Glasser, Alan L. +.ds [D Nov. 1978 +.ds [T The Evolution of a Source Code Control System +.ds [J Software Engineering Notes +.ds [V 3 +.ds [N 5 +.ds [P 122-125 +.nr [P 1 +.ds [O Proceedings of the Software Quality and Assurance Workshop. +.nr [T 0 +.nr [A 1 +.nr [O 1 +.][ 1 journal-article +.ds [F 5 +.ds [F 7 +.]- +.ds [K IBMClearCaster +.ds [A Brown, H.B. +.ds [D 1970 +.ds [T The Clear/Caster System +.ds [J Nato Conference on Software Engineering, Rome +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 1 journal-article +.ds [F 3 +.ds [F 8 +.]- +.ds [K HabermannSDC +.ds [A Habermann, A. Nico +.ds [D Jan. 1979 +.ds [T A Software Development Control System +.ds [I Technical Report, Carnegie-Mellon University, Department of Computer Science +.nr [T 0 +.nr [A 0 +.nr [O 0 +.][ 2 book +.ds [F 9 +.]- +.ds [K CMS +.ds [A DEC +.ds [T Code Management System +.ds [I Digital Equipment Corporation +.ds [O Document No.\ EA-23134-82 +.ds [D 1982 +.nr [T 0 +.nr [A 0 +.nr [O 0 +.][ 2 book +.ds [F 10 +.]- +.ds [K LAMP01 +.ds [A Lampson, Butler W. +.as [A " and Schmidt, Eric E. +.ds [T Practical Use of a Polymorphic Applicative Language +.ds [B Proceedings of the 10th Symposium on Principles of Programming Languages +.ds [I ACM +.ds [P 237-255 +.nr [P 1 +.ds [D January 1983 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 3 article-in-book +.ds [F 5 +.ds [F 11 +.]- +.ds [K TICH07 +.ds [T A Data Model for Programming Support Environments and its Application +.ds [A Tichy, Walter F. +.ds [B Automated Tools for Information System Design and Development +.ds [E Hans-Jochen Schneider and Anthony I. Wasserman +.ds [C Amsterdam +.ds [I North-Holland Publishing Company +.ds [D 1982 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 3 article-in-book +.ds [F 4 +.ds [F 2 +.ds [F 12 +.]- +.ds [K HECK01 +.ds [T A Technique for Isolating Differences Between Files +.ds [A Heckel, Paul +.ds [J Communications of the ACM +.ds [D April 1978 +.ds [V 21 +.ds [N 4 +.ds [P 264-268 +.nr [P 1 +.nr [T 0 +.nr [A 0 +.nr [O 0 +.][ 1 journal-article +.ds [F 13 +.]- +.ds [K TICH11 +.ds [T The String-to-String Correction Problem with Block Moves +.ds [A Tichy, Walter F. +.ds [D Nov. 1984 +.ds [J ACM Transactions on Computer Systems +.ds [V 2 +.ds [N 4 +.ds [P 309-321 +.nr [P 1 +.nr [T 0 +.nr [A 1 +.nr [O 0 +.][ 1 journal-article +.]> diff --git a/gnu/usr.bin/rcs/doc/rcs_func.ms b/gnu/usr.bin/rcs/doc/rcs_func.ms new file mode 100644 index 0000000..9818086 --- /dev/null +++ b/gnu/usr.bin/rcs/doc/rcs_func.ms @@ -0,0 +1,95 @@ +.SH +Functions of RCS (Revision Control System) +.PP +RCS manages software libraries. It greatly increases programmer productivity +by providing the following functions. +.IP 1. +RCS stores and retrieves multiple revisions of program and other text. +Thus, one can maintain one or more releases while developing the next +release, with a minimum of space overhead. Changes no longer destroy the +original -- previous revisions remain accessible. +.RS +.IP a. +Maintains each module as a tree of revisions. +.IP b. +Project libraries can +be organized centrally, decentralized, or any way you like. +.IP c. +RCS works for any type of text: programs, documentation, memos, papers, +graphics, VLSI layouts, form letters, etc. +.RE +.IP 2. +RCS maintains a complete history of changes. +Thus, one can find out what happened to a module easily +and quickly, without having to compare source listings or +having to track down colleagues. +.RS +.IP a. +RCS performs automatic record keeping. +.IP b. +RCS logs all changes automatically. +.IP c. +RCS guarantees project continuity. +.RE +.IP 3. +RCS manages multiple lines of development. +.IP 4. +RCS can merge multiple lines of development. +Thus, when several parallel lines of development must be consolidated +into one line, the merging of changes is automatic. +.IP 5. +RCS flags coding conflicts. +If two or more lines of development modify the same section of code, +RCS can alert programmers about overlapping changes. +.IP 6. +RCS resolves access conflicts. +When two or more programmers wish to modify the same revision, +RCS alerts the programmers and makes sure that one modification won't wipe +out the other one. +.IP 7. +RCS provides high-level retrieval functions. +Revisions can be retrieved according to ranges of revision numbers, +symbolic names, dates, authors, and states. +.IP 8. +RCS provides release and configuration control. +Revisions can be marked as released, stable, experimental, etc. +Configurations of modules can be described simply and directly. +.IP 9. +RCS performs automatic identification of modules with name, revision +number, creation time, author, etc. +Thus, it is always possible to determine which revisions of which +modules make up a given configuration. +.IP 10. +Provides high-level management visibility. +Thus, it is easy to track the status of a software project. +.RS +.IP a. +RCS provides a complete change history. +.IP b. +RCS records who did what when to which revision of which module. +.RE +.IP 11. +RCS is fully compatible with existing software development tools. +RCS is unobtrusive -- its interface to the file system is such that +all your existing software tools can be used as before. +.IP 12. +RCS' basic user interface is extremely simple. The novice need to learn +only two commands. Its more sophisticated features have been +tuned towards advanced software development environments and the +experienced software professional. +.IP 13. +RCS simplifies software distribution if customers +maintain sources with RCS also. This technique assures proper +identification of versions and configurations, and tracking of customer +modifications. Customer modifications can be merged into distributed +versions locally or by the development group. +.IP 14. +RCS needs little extra space for the revisions (only the differences). +If intermediate revisions are deleted, the corresponding +differences are compressed into the shortest possible form. +.IP 15. +RCS is implemented with reverse deltas. This means that +the latest revision, which is the one that is accessed most often, +is stored intact. All others are regenerated from the latest one +by applying reverse deltas (backward differences). This +results in fast access time for the revision needed most often. diff --git a/gnu/usr.bin/rcs/ident/Makefile b/gnu/usr.bin/rcs/ident/Makefile new file mode 100644 index 0000000..1a618e5 --- /dev/null +++ b/gnu/usr.bin/rcs/ident/Makefile @@ -0,0 +1,7 @@ +PROG= ident + +SRCS= ident.c +LDADD= -L${.CURDIR}/../lib/obj -lrcs +CFLAGS+= -I${.CURDIR}/../lib + +.include diff --git a/gnu/usr.bin/rcs/ident/ident.1 b/gnu/usr.bin/rcs/ident/ident.1 new file mode 100644 index 0000000..37c8eda --- /dev/null +++ b/gnu/usr.bin/rcs/ident/ident.1 @@ -0,0 +1,76 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.ds iD \\$3 \\$4 \\$5 \\$6 \\$7 +.. +.Id $Id: ident.1,v 5.0 1990/08/22 09:09:36 eggert Exp $ +.ds r \s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH IDENT 1 \*(Dt GNU +.SH NAME +ident \- identify files +.SH SYNOPSIS +.B ident +[ +.B \-q +] [ +.I file +\&.\|.\|. ] +.SH DESCRIPTION +.B ident +searches for all occurrences of the pattern +.BI $ keyword : .\|.\|. $ +in the named files or, if no file name appears, the standard input. +.PP +These patterns are normally inserted automatically by the \*r command +.BR co (1), +but can also be inserted manually. +The option +.B \-q +suppresses +the warning given if there are no patterns in a file. +.PP +.B ident +works on text files as well as object files and dumps. +For example, if the C program in +.B f.c +contains +.IP +\f3char rcsid[] = \&"$\&Id: f.c,v \*(iD $\&";\fP +.LP +and +.B f.c +is compiled into +.BR f.o , +then the command +.IP +.B "ident f.c f.o" +.LP +will output +.nf +.IP +.ft 3 +f.c: + $\&Id: f.c,v \*(iD $ +f.o: + $\&Id: f.c,v \*(iD $ +.ft +.fi +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990 by Paul Eggert. +.SH "SEE ALSO" +ci(1), co(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1), +rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. diff --git a/gnu/usr.bin/rcs/ident/ident.c b/gnu/usr.bin/rcs/ident/ident.c new file mode 100644 index 0000000..a2cc018 --- /dev/null +++ b/gnu/usr.bin/rcs/ident/ident.c @@ -0,0 +1,214 @@ +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +/* + * RCS identification operation + */ + +/* $Log: ident.c,v $ + * Revision 5.3 1991/09/10 22:15:46 eggert + * Open files with FOPEN_R, not FOPEN_R_WORK, + * because they might be executables, not working files. + * + * Revision 5.2 1991/08/19 03:13:55 eggert + * Report read errors immediately. + * + * Revision 5.1 1991/02/25 07:12:37 eggert + * Don't report empty keywords. Check for I/O errors. + * + * Revision 5.0 1990/08/22 08:12:37 eggert + * Don't limit output to known keywords. + * Remove arbitrary limits and lint. Ansify and Posixate. + * + * Revision 4.5 89/05/01 15:11:54 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.4 87/10/23 17:09:57 narten + * added exit(0) so exit return code would be non random + * + * Revision 4.3 87/10/18 10:23:55 narten + * Updating version numbers. Changes relative to 1.1 are actually relative + * to 4.1 + * + * Revision 1.3 87/07/09 09:20:52 trinkle + * Added check to make sure there is at least one arg before comparing argv[1] + * with "-q". This necessary on machines that don't allow dereferncing null + * pointers (i.e. Suns). + * + * Revision 1.2 87/03/27 14:21:47 jenkins + * Port to suns + * + * Revision 4.1 83/05/10 16:31:02 wft + * Added option -q and input from reading stdin. + * Marker matching is now done with trymatch() (independent of keywords). + * + * Revision 3.4 83/02/18 17:37:49 wft + * removed printing of new line after last file. + * + * Revision 3.3 82/12/04 12:48:55 wft + * Added LOCKER. + * + * Revision 3.2 82/11/28 18:24:17 wft + * removed Suffix; added ungetc to avoid skipping over trailing KDELIM. + * + * Revision 3.1 82/10/13 15:58:51 wft + * fixed type of variables receiving from getc() (char-->int). +*/ + +#include "rcsbase.h" + +static int match P((FILE*)); +static void scanfile P((FILE*,char const*,int)); + +mainProg(identId, "ident", "$Id: ident.c,v 5.3 1991/09/10 22:15:46 eggert Exp $") +/* Ident searches the named files for all occurrences + * of the pattern $keyword:...$, where the keywords are + * Author, Date, Header, Id, Log, RCSfile, Revision, Source, and State. + */ + +{ + FILE *fp; + int quiet; + int status = EXIT_SUCCESS; + + if ((quiet = argc > 1 && strcmp("-q",argv[1])==0)) { + argc--; argv++; + } + + if (argc<2) + scanfile(stdin, (char*)0, quiet); + + while ( --argc > 0 ) { + if (!(fp = fopen(*++argv, FOPEN_R))) { + VOID fprintf(stderr, "%s error: can't open %s\n", cmdid, *argv); + status = EXIT_FAILURE; + } else { + scanfile(fp, *argv, quiet); + if (argc>1) VOID putchar('\n'); + } + } + if (ferror(stdout) || fclose(stdout)!=0) { + VOID fprintf(stderr, "%s error: write error\n", cmdid); + status = EXIT_FAILURE; + } + exitmain(status); +} + +#if lint + exiting void identExit() { _exit(EXIT_FAILURE); } +#endif + + + static void +scanfile(file, name, quiet) + register FILE *file; + char const *name; + int quiet; +/* Function: scan an open file with descriptor file for keywords. + * Return false if there's a read error. + */ +{ + register int c; + + if (name) + VOID printf("%s:\n", name); + else + name = "input"; + c = 0; + for (;;) { + if (c < 0) { + if (feof(file)) + break; + if (ferror(file)) + goto read_error; + } + if (c == KDELIM) { + if ((c = match(file))) + continue; + quiet = true; + } + c = getc(file); + } + if (!quiet) + VOID fprintf(stderr, "%s warning: no id keywords in %s\n", cmdid, name); + if (fclose(file) == 0) + return; + + read_error: + VOID fprintf(stderr, "%s error: %s: read error\n", cmdid, name); + exit(EXIT_FAILURE); +} + + + + static int +match(fp) /* group substring between two KDELIM's; then do pattern match */ + register FILE *fp; +{ + char line[BUFSIZ]; + register int c; + register char * tp; + + tp = line; + while ((c = getc(fp)) != VDELIM) { + if (c < 0) + return c; + switch (ctab[c]) { + case LETTER: case Letter: + *tp++ = c; + if (tp < line+sizeof(line)-4) + break; + /* fall into */ + default: + return c ? c : '\n'/* anything but 0 or KDELIM or EOF */; + } + } + if (tp == line) + return c; + *tp++ = c; + if ((c = getc(fp)) != ' ') + return c ? c : '\n'; + *tp++ = c; + while( (c = getc(fp)) != KDELIM ) { + if (c < 0 && feof(fp) | ferror(fp)) + return c; + switch (ctab[c]) { + default: + *tp++ = c; + if (tp < line+sizeof(line)-2) + break; + /* fall into */ + case NEWLN: case UNKN: + return c ? c : '\n'; + } + } + if (tp[-1] != ' ') + return c; + *tp++ = c; /*append trailing KDELIM*/ + *tp = '\0'; + VOID fprintf(stdout, " %c%s\n", KDELIM, line); + return 0; +} diff --git a/gnu/usr.bin/rcs/lib/Makefile b/gnu/usr.bin/rcs/lib/Makefile new file mode 100644 index 0000000..b198e9e --- /dev/null +++ b/gnu/usr.bin/rcs/lib/Makefile @@ -0,0 +1,5 @@ +LIB= rcs +SRCS= maketime.c partime.c rcsedit.c rcsfcmp.c rcsfnms.c rcsgen.c rcskeep.c \ + rcskeys.c rcslex.c rcsmap.c rcsrev.c rcssyn.c rcsutil.c merger.c + +.include diff --git a/gnu/usr.bin/rcs/lib/conf.h b/gnu/usr.bin/rcs/lib/conf.h new file mode 100644 index 0000000..d29e511 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/conf.h @@ -0,0 +1,495 @@ +/* RCS compile-time configuration */ + + /* $Id: conf.sh,v 5.14 1991/11/20 18:21:10 eggert Exp $ */ + +/* + * This file is generated automatically. + * If you edit it by hand your changes may be lost. + * Instead, please try to fix conf.sh, + * and send your fixes to rcs-bugs@cs.purdue.edu. + */ + +#define exitmain(n) return n /* how to exit from main() */ +/* #define _POSIX_SOURCE */ /* Define this if Posix + strict Standard C. */ + +#include +#include +#include + +/* Comment out #include lines below that do not work. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* #include */ + +/* Define the following symbols to be 1 or 0. */ +#define has_sys_dir_h 1 /* Does #include work? */ +#define has_sys_param_h 1 /* Does #include work? */ +#define has_readlink 1 /* Does readlink() work? */ + +/* #undef NAME_MAX */ /* Uncomment this if NAME_MAX is broken. */ + +#if !defined(NAME_MAX) && !defined(_POSIX_NAME_MAX) +# if has_sys_dir_h +# include +# endif +# ifndef NAME_MAX +# ifndef MAXNAMLEN +# define MAXNAMLEN 14 +# endif +# define NAME_MAX MAXNAMLEN +# endif +#endif +#if !defined(PATH_MAX) && !defined(_POSIX_PATH_MAX) +# if has_sys_param_h +# include +# define included_sys_param_h 1 +# endif +# ifndef PATH_MAX +# ifndef MAXPATHLEN +# define MAXPATHLEN 1024 +# endif +# define PATH_MAX (MAXPATHLEN-1) +# endif +#endif +#if has_readlink && !defined(MAXSYMLINKS) +# if has_sys_param_h && !included_sys_param_h +# include +# endif +# ifndef MAXSYMLINKS +# define MAXSYMLINKS 20 /* BSD; not standard yet */ +# endif +#endif + +/* Comment out the keyword definitions below if the keywords work. */ +/* #define const */ +/* #define volatile */ + +/* Comment out the typedefs below if the types are already declared. */ +/* Fix any uncommented typedefs that are wrong. */ +/* typedef int mode_t; */ +/* typedef int pid_t; */ +typedef int sig_atomic_t; +/* typedef unsigned size_t; */ +/* typedef int ssize_t; */ +/* typedef long time_t; */ +/* typedef int uid_t; */ + +/* Define the following symbols to be 1 or 0. */ +#define has_prototypes 1 /* Do function prototypes work? */ +#define has_stdarg 1 /* Does work? */ +#define has_varargs 0 /* Does work? */ +#define va_start_args 2 /* How many args does va_start() take? */ +#if has_prototypes +# define P(params) params +#else +# define P(params) () +#endif +#if has_stdarg +# include +#else +# if has_varargs +# include +# else + typedef char *va_list; +# define va_dcl int va_alist; +# define va_start(ap) ((ap) = (va_list)&va_alist) +# define va_arg(ap,t) (((t*) ((ap)+=sizeof(t))) [-1]) +# define va_end(ap) +# endif +#endif +#if va_start_args == 2 +# define vararg_start va_start +#else +# define vararg_start(ap,p) va_start(ap) +#endif + +#define text_equals_binary_stdio 1 /* Does stdio treat text like binary? */ +#define text_work_stdio 0 /* Text i/o for working file, binary for RCS file? */ +#if text_equals_binary_stdio + /* Text and binary i/o behave the same, or binary i/o does not work. */ +# define FOPEN_R "r" +# define FOPEN_W "w" +# define FOPEN_WPLUS "w+" +#else + /* Text and binary i/o behave differently. */ + /* This is incompatible with Posix and Unix. */ +# define FOPEN_R "rb" +# define FOPEN_W "wb" +# define FOPEN_WPLUS "w+b" +#endif +#if text_work_stdio +# define FOPEN_R_WORK "r" +# define FOPEN_W_WORK "w" +# define FOPEN_WPLUS_WORK "w+" +#else +# define FOPEN_R_WORK FOPEN_R +# define FOPEN_W_WORK FOPEN_W +# define FOPEN_WPLUS_WORK FOPEN_WPLUS +#endif + +/* Define or comment out the following symbols as needed. */ +#define bad_fopen_wplus 0 /* Does fopen(f,FOPEN_WPLUS) fail to truncate f? */ +#define getlogin_is_secure 0 /* Is getlogin() secure? Usually it's not. */ +#define has_dirent 1 /* Do opendir(), readdir(), closedir() work? */ +#define has_fchmod 0 /* Does fchmod() work? */ +#define has_fputs 0 /* Does fputs() work? */ +#define has_ftruncate 1 /* Does ftruncate() work? */ +#define has_getuid 1 /* Does getuid() work? */ +#define has_getpwuid 1 /* Does getpwuid() work? */ +#define has_link 1 /* Does link() work? */ +#define has_memcmp 1 /* Does memcmp() work? */ +#define has_memcpy 1 /* Does memcpy() work? */ +#define has_memmove 1 /* Does memmove() work? */ +#define has_madvise 0 /* Does madvise() work? */ +#define has_mmap 0 /* Does mmap() work on regular files? */ +#define has_rename 1 /* Does rename() work? */ +#define bad_a_rename 0 /* Does rename(A,B) fail if A is unwritable? */ +#define bad_b_rename 0 /* Does rename(A,B) fail if B is unwritable? */ +#define VOID (void) /* 'VOID e;' discards the value of an expression 'e'. */ +#define has_seteuid 0 /* Does seteuid() work? See README. */ +#define has_setuid 1 /* Does setuid() exist? */ +#define has_signal 1 /* Does signal() work? */ +#define signal_args P((int)) /* arguments of signal handlers */ +#define signal_type void /* type returned by signal handlers */ +#define sig_zaps_handler 0 /* Must a signal handler reinvoke signal()? */ +#define has_sigaction 1 /* Does struct sigaction work? */ +/* #define has_sigblock ? */ /* Does sigblock() work? */ +/* #define sigmask(s) (1 << ((s)-1)) */ /* Yield mask for signal number. */ +#define has_sys_siglist 0 /* Does sys_siglist[] work? */ +typedef ssize_t fread_type; /* type returned by fread() and fwrite() */ +typedef size_t freadarg_type; /* type of their size arguments */ +typedef void *malloc_type; /* type returned by malloc() */ +#define has_getcwd 1 /* Does getcwd() work? */ +/* #define has_getwd ? */ /* Does getwd() work? */ +#define has_mktemp 1 /* Does mktemp() work? */ +#define has_NFS 1 /* Might NFS be used? */ +/* #define strchr index */ /* Use old-fashioned name for strchr()? */ +/* #define strrchr rindex */ /* Use old-fashioned name for strrchr()? */ +#define bad_unlink 0 /* Does unlink() fail on unwritable files? */ +#define has_vfork 0 /* Does vfork() work? */ +#define has_fork 1 /* Does fork() work? */ +#define has_spawn 0 /* Does spawn*() work? */ +#define has_wait 1 /* Does wait() work? */ +#define has_waitpid 0 /* Does waitpid() work? */ +#define RCS_SHELL "/bin/sh" /* shell to run RCS subprograms */ +#define has_vfprintf 1 /* Does vfprintf() work? */ +/* #define has__doprintf ? */ /* Does _doprintf() work? */ +/* #define has__doprnt ? */ /* Does _doprnt() work? */ +/* #undef EXIT_FAILURE */ /* Uncomment this if EXIT_FAILURE is broken. */ +#define large_memory 0 /* Can main memory hold entire RCS files? */ +/* #undef ULONG_MAX */ /* Uncomment this if ULONG_MAX is broken (e.g. < 0). */ +/* struct utimbuf { time_t actime, modtime; }; */ /* Uncomment this if needed. */ +#define CO "/usr/bin/co" /* name of 'co' program */ +#define COMPAT2 0 /* Are version 2 files supported? */ +#define DATEFORM "%.2d.%.2d.%.2d.%.2d.%.2d.%.2d" /* e.g. 01.01.01.01.01.01 */ +#define DIFF "/usr/bin/diff" /* name of 'diff' program */ +#define DIFF3 "/usr/bin/diff3" /* name of 'diff3' program */ +#define DIFF3_BIN 1 /* Is diff3 user-visible (not the /usr/lib auxiliary)? */ +#define DIFF_FLAGS , "-an" /* Make diff output suitable for RCS. */ +#define DIFF_L 1 /* Does diff -L work? */ +#define DIFF_SUCCESS 0 /* DIFF status if no differences are found */ +#define DIFF_FAILURE 1 /* DIFF status if differences are found */ +#define DIFF_TROUBLE 2 /* DIFF status if trouble */ +#define ED "/bin/ed" /* name of 'ed' program (used only if !DIFF3_BIN) */ +#define MERGE "/usr/bin/merge" /* name of 'merge' program */ +#define TMPDIR "/tmp" /* default directory for temporary files */ +#define SLASH '/' /* principal pathname separator */ +#define SLASHes '/' /* `case SLASHes:' labels all pathname separators */ +#define isSLASH(c) ((c) == SLASH) /* Is arg a pathname separator? */ +#define ROOTPATH(p) isSLASH((p)[0]) /* Is p an absolute pathname? */ +#define X_DEFAULT ",v/" /* default value for -x option */ +#define DIFF_ABSOLUTE 1 /* Is ROOTPATH(DIFF) true? */ +#define ALL_ABSOLUTE 1 /* Are all subprograms absolute pathnames? */ +#define SENDMAIL "/usr/bin/mail" /* how to send mail */ +#define TZ_must_be_set 0 /* Must TZ be set for gmtime() to work? */ + + + +/* Adjust the following declarations as needed. */ + + +#if __GNUC__ && !__STRICT_ANSI__ +# define exiting volatile /* GCC extension: function cannot return */ +#else +# define exiting +#endif + +#if has_ftruncate + int ftruncate P((int,off_t)); +#endif + +/* */ +#if has_madvise + int madvise P((caddr_t,size_t,int)); +#endif +#if has_mmap + caddr_t mmap P((caddr_t,size_t,int,int,int,off_t)); + int munmap P((caddr_t,size_t)); +#endif + + +/* Posix (ISO/IEC 9945-1: 1990 / IEEE Std 1003.1-1990) */ +/* These definitions are for the benefit of non-Posix hosts, and */ +/* Posix hosts that have Standard C compilers but traditional include files. */ +/* Unfortunately, mixed-up hosts are all too common. */ + +/* */ +#ifdef F_DUPFD + int fcntl P((int,int,...)); +#else + int dup2 P((int,int)); +#endif +#ifndef O_BINARY /* some non-Posix hosts need O_BINARY */ +# define O_BINARY 0 /* no effect on Posix */ +#endif +#ifdef O_CREAT +# define open_can_creat 1 +#else +# define open_can_creat 0 +# define O_RDONLY 0 +# define O_WRONLY 1 +# define O_RDWR 2 +# define O_CREAT 01000 +# define O_TRUNC 02000 + int creat P((char const*,mode_t)); +#endif +#ifndef O_EXCL +# define O_EXCL 0 +#endif + +/* */ +#if has_getpwuid + struct passwd *getpwuid P((uid_t)); +#endif + +/* */ +#if has_sigaction + int sigaction P((int,struct sigaction const*,struct sigaction*)); + int sigaddset P((sigset_t*,int)); + int sigemptyset P((sigset_t*)); +#else +#if has_sigblock + /* BSD */ + int sigblock P((int)); + int sigmask P((int)); + int sigsetmask P((int)); +#endif +#endif + +/* */ +FILE *fdopen P((int,char const*)); +int fileno P((FILE*)); + +/* */ +int chmod P((char const*,mode_t)); +int fstat P((int,struct stat*)); +int stat P((char const*,struct stat*)); +mode_t umask P((mode_t)); +#if has_fchmod + int fchmod P((int,mode_t)); +#endif +#ifndef S_IRUSR +# ifdef S_IREAD +# define S_IRUSR S_IREAD +# else +# define S_IRUSR 0400 +# endif +# ifdef S_IWRITE +# define S_IWUSR S_IWRITE +# else +# define S_IWUSR (S_IRUSR/2) +# endif +#endif +#ifndef S_IRGRP +# if has_getuid +# define S_IRGRP (S_IRUSR / 0010) +# define S_IWGRP (S_IWUSR / 0010) +# define S_IROTH (S_IRUSR / 0100) +# define S_IWOTH (S_IWUSR / 0100) +# else + /* single user OS -- not Posix or Unix */ +# define S_IRGRP 0 +# define S_IWGRP 0 +# define S_IROTH 0 +# define S_IWOTH 0 +# endif +#endif +#ifndef S_ISREG +# define S_ISREG(n) (((n) & S_IFMT) == S_IFREG) +#endif + +/* */ +#if has_wait + pid_t wait P((int*)); +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +# undef WIFEXITED /* Avoid 4.3BSD incompatibility with Posix. */ +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (!((stat_val) & 255)) +#endif + +/* */ +char *getlogin P((void)); +int close P((int)); +int isatty P((int)); +int link P((char const*,char const*)); +int open P((char const*,int,...)); +int unlink P((char const*)); +int _filbuf P((FILE*)); /* keeps lint quiet in traditional C */ +int _flsbuf P((int,FILE*)); /* keeps lint quiet in traditional C */ +long pathconf P((char const*,int)); +ssize_t write P((int,void const*,size_t)); +#ifndef STDIN_FILENO +# define STDIN_FILENO 0 +# define STDOUT_FILENO 1 +# define STDERR_FILENO 2 +#endif +#if has_fork +# if !has_vfork +# undef vfork +# define vfork fork +# endif + pid_t vfork P((void)); /* vfork is nonstandard but faster */ +#endif +#if has_getcwd || !has_getwd + char *getcwd P((char*,size_t)); +#else + char *getwd P((char*)); +#endif +#if has_getuid + uid_t getuid P((void)); +#endif +#if has_readlink +/* ssize_t readlink P((char const*,char*,size_t)); *//* BSD; not standard yet */ +#endif +#if has_setuid +# if !has_seteuid +# undef seteuid +# define seteuid setuid +# endif + int seteuid P((uid_t)); + uid_t geteuid P((void)); +#endif +#if has_spawn + int spawnv P((int,char const*,char*const*)); +# if ALL_ABSOLUTE +# define spawn_RCS spawnv +# else +# define spawn_RCS spawnvp + int spawnvp P((int,char const*,char*const*)); +# endif +#else + int execv P((char const*,char*const*)); +# if ALL_ABSOLUTE +# define exec_RCS execv +# else +# define exec_RCS execvp + int execvp P((char const*,char*const*)); +# endif +#endif + +/* utime.h */ +int utime P((char const*,struct utimbuf const*)); + + +/* Standard C library */ +/* These definitions are for the benefit of hosts that have */ +/* traditional C include files, possibly with Standard C compilers. */ +/* Unfortunately, mixed-up hosts are all too common. */ + +/* */ +extern int errno; + +/* */ +#ifndef ULONG_MAX + /* This does not work in #ifs, but it's good enough for us. */ +# define ULONG_MAX ((unsigned long)-1) +#endif + +/* */ +#if has_signal + signal_type (*signal P((int,signal_type(*)signal_args)))signal_args; +#endif + +/* */ +FILE *fopen P((char const*,char const*)); +fread_type fread P((void*,freadarg_type,freadarg_type,FILE*)); +fread_type fwrite P((void const*,freadarg_type,freadarg_type,FILE*)); +int fclose P((FILE*)); +int feof P((FILE*)); +int ferror P((FILE*)); +int fflush P((FILE*)); +int fprintf P((FILE*,char const*,...)); +int fputs P((char const*,FILE*)); +int fseek P((FILE*,long,int)); +int printf P((char const*,...)); +int rename P((char const*,char const*)); +int sprintf P((char*,char const*,...)); +/* long ftell P((FILE*)); */ +void clearerr P((FILE*)); +void perror P((char const*)); +#ifndef L_tmpnam +# define L_tmpnam 32 /* power of 2 > sizeof("/usr/tmp/xxxxxxxxxxxxxxx") */ +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 +#endif +#if has_mktemp + char *mktemp P((char*)); /* traditional */ +#else + char *tmpnam P((char*)); +#endif +#if has_vfprintf + int vfprintf P((FILE*,char const*,va_list)); +#else +#if has__doprintf + void _doprintf P((FILE*,char const*,va_list)); /* Minix */ +#else + void _doprnt P((char const*,va_list,FILE*)); /* BSD */ +#endif +#endif + +/* */ +char *getenv P((char const*)); +exiting void _exit P((int)); +exiting void exit P((int)); +malloc_type malloc P((size_t)); +malloc_type realloc P((malloc_type,size_t)); +void free P((malloc_type)); +#ifndef EXIT_FAILURE +# define EXIT_FAILURE 1 +#endif +#ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +#endif +#if !has_fork && !has_spawn + int system P((char const*)); +#endif + +/* */ +char *strcpy P((char*,char const*)); +char *strchr P((char const*,int)); +char *strrchr P((char const*,int)); +int memcmp P((void const*,void const*,size_t)); +int strcmp P((char const*,char const*)); +size_t strlen P((char const*)); +void *memcpy P((void*,void const*,size_t)); +#if has_memmove + void *memmove P((void*,void const*,size_t)); +#endif + +/* */ +time_t time P((time_t*)); diff --git a/gnu/usr.bin/rcs/lib/maketime.c b/gnu/usr.bin/rcs/lib/maketime.c new file mode 100644 index 0000000..c95c9f0 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/maketime.c @@ -0,0 +1,344 @@ +# +/* + * MAKETIME derive 32-bit time value from TM structure. + * + * Usage: + * int zone; Minutes west of GMT, or + * 48*60 for localtime + * time_t t; + * struct tm *tp; Pointer to TM structure from + * t = maketime(tp,zone); + * + * Returns: + * -1 if failure; parameter out of range or nonsensical. + * else time-value. + * Notes: + * This code is quasi-public; it may be used freely in like software. + * It is not to be sold, nor used in licensed software without + * permission of the author. + * For everyone's benefit, please report bugs and improvements! + * Copyright 1981 by Ken Harrenstien, SRI International. + * (ARPANET: KLH @ SRI) + */ +/* $Log: maketime.c,v $ + * Revision 5.3 1991/08/19 03:13:55 eggert + * Add setfiledate, str2time, TZ_must_be_set. + * + * Revision 5.2 1990/11/01 05:03:30 eggert + * Remove lint. + * + * Revision 5.1 1990/10/04 06:30:13 eggert + * Calculate the GMT offset of 'xxx LT' as of xxx, not as of now. + * Don't assume time_t is 32 bits. Fix bugs near epoch and near end of time. + * + * Revision 5.0 1990/08/22 08:12:38 eggert + * Switch to GMT and fix the bugs exposed thereby. + * Permit dates past 1999/12/31. Ansify and Posixate. + * + * Revision 1.8 88/11/08 13:54:53 narten + * allow negative timezones (-24h <= x <= 24h) + * + * Revision 1.7 88/08/28 14:47:52 eggert + * Allow cc -R. Remove unportable "#endif XXX"s. + * + * Revision 1.6 87/12/18 17:05:58 narten + * include rcsparam.h + * + * Revision 1.5 87/12/18 11:35:51 narten + * maketime.c: fixed USG code - you have tgo call "tzset" in order to have + * "timezone" set. ("localtime" calls it, but it's probably better not to + * count on "localtime" having been called.) + * + * Revision 1.4 87/10/18 10:26:57 narten + * Updating version numbers. Changes relative to 1.0 are actually + * relative to 1.2 + * + * Revision 1.3 87/09/24 13:58:45 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:21:48 jenkins + * Port to suns + * + * Revision 1.2 83/12/05 10:12:56 wft + * added cond. compilation for USG Unix; long timezone; + * + * Revision 1.1 82/05/06 11:38:00 wft + * Initial revision + * + */ + + +#include "rcsbase.h" + +libId(maketId, "$Id: maketime.c,v 5.3 1991/08/19 03:13:55 eggert Exp $") + +static struct tm const *time2tm P((time_t)); + +#define given(v) (0 <= (v)) /* Negative values are unspecified. */ + +static int const daytb[] = { + /* # days in year thus far, indexed by month (0-12!!) */ + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 +}; + + static time_t +maketime(atm,zone) + struct tm const *atm; + int zone; +{ + register struct tm const *tp; + register int i; + int year, yday, mon, day, hour, min, sec, leap, localzone; + int attempts; + time_t t, tres; + + attempts = 2; + localzone = zone==48*60; + tres = -1; + year = mon = day = 0; /* Keep lint happy. */ + + do { + + if (localzone || !given(atm->tm_year)) { + if (tres == -1) + if ((tres = time((time_t*)0)) == -1) + return -1; + tp = time2tm(tres); + /* Get breakdowns of default time, adjusting to zone. */ + year = tp->tm_year; /* Use to set up defaults */ + yday = tp->tm_yday; + mon = tp->tm_mon; + day = tp->tm_mday; + hour = tp->tm_hour; + min = tp->tm_min; + if (localzone) { + tp = localtime(&tres); + zone = + min - tp->tm_min + 60*( + hour - tp->tm_hour + 24*( + /* If years differ, it's by one day. */ + year - tp->tm_year + ? year - tp->tm_year + : yday - tp->tm_yday)); + } + /* Adjust the default day, month and year according to zone. */ + if ((min -= zone) < 0) { + if (hour-(59-min)/60 < 0 && --day <= 0) { + if (--mon < 0) { + --year; + mon = 11; + } + day = daytb[mon+1] - daytb[mon] + (mon==1&&!(year&3)); + } + } else + if ( + 24 <= hour+min/60 && + daytb[mon+1] - daytb[mon] + (mon==1&&!(year&3)) < ++day + ) { + if (11 < ++mon) { + ++year; + mon = 0; + } + day = 1; + } + } + if (zone < -24*60 || 24*60 < zone) + return -1; + + +#ifdef DEBUG +printf("first YMD: %d %d %d\n",year,mon,day); +#endif + tp = atm; + + /* First must find date, using specified year, month, day. + * If one of these is unspecified, it defaults either to the + * current date (if no more global spec was given) or to the + * zero-value for that spec (i.e. a more global spec was seen). + * Reject times that do not fit in time_t, + * without assuming that time_t is 32 bits or is signed. + */ + if (given(tp->tm_year)) + { + year = tp->tm_year; + mon = 0; /* Since year was given, default */ + day = 1; /* for remaining specs is zero */ + } + if (year < 69) /* 1969/12/31 OK in some timezones. */ + return -1; /* ERR: year out of range */ + leap = !(year&3) && (year%100 || !((year+300)%400)); + year -= 70; /* UNIX time starts at 1970 */ + + /* + * Find day of year. + */ + { + if (given(tp->tm_mon)) + { mon = tp->tm_mon; /* Month was specified */ + day = 1; /* so set remaining default */ + } + if (11 < (unsigned)mon) + return -1; /* ERR: bad month */ + if (given(tp->tm_mday)) day = tp->tm_mday; + if(day < 1 + || (((daytb[mon+1]-daytb[mon]) < day) + && (day!=29 || mon!=1 || !leap) )) + return -1; /* ERR: bad day */ + yday = daytb[mon] /* Add # of days in months so far */ + + ((leap /* Leap year, and past Feb? If */ + && mon>1)? 1:0) /* so, add leap day for this year */ + + day-1; /* And finally add # days this mon */ + + } + if (leap+365 <= (unsigned)yday) + return -1; /* ERR: bad YDAY */ + + if (year < 0) { + if (yday != 364) + return -1; /* ERR: too early */ + t = -1; + } else { + tres = year*365; /* Get # days of years so far */ + if (tres/365 != year) + return -1; /* ERR: overflow */ + t = tres + + ((year+1)>>2) /* plus # of leap days since 1970 */ + + yday; /* and finally add # days this year */ + if (t+4 < tres) + return -1; /* ERR: overflow */ + } + tres = t; + + if (given(i = tp->tm_wday)) /* Check WDAY if present */ + if (i != (tres+4)%7) /* 1970/01/01 was Thu = 4 */ + return -1; /* ERR: bad WDAY */ + +#ifdef DEBUG +printf("YMD: %d %d %d, T=%ld\n",year,mon,day,tres); +#endif + /* + * Now determine time. If not given, default to zeros + * (since time is always the least global spec) + */ + tres *= 86400L; /* Get # seconds (24*60*60) */ + if (tres/86400L != t) + return -1; /* ERR: overflow */ + hour = min = sec = 0; + if (given(tp->tm_hour)) hour = tp->tm_hour; + if (given(tp->tm_min )) min = tp->tm_min; + if (given(tp->tm_sec )) sec = tp->tm_sec; + if (60 <= (unsigned)min || 60 < (unsigned)sec) + return -1; /* ERR: MS out of range */ + if (24 <= (unsigned)hour) + if(hour != 24 || (min+sec) !=0) /* Allow 24:00 */ + return -1; /* ERR: H out of range */ + + t = tres; + tres += sec + 60L*(zone + min + 60*hour); + +#ifdef DEBUG +printf("HMS: %d %d %d T=%ld\n",hour,min,sec,tres); +#endif + + if (!localzone) /* check for overflow */ + return (year<0 ? (tres<0||86400L<=tres) : trestm_sec) && atm->tm_sec != tp->tm_sec) + return -1; /* If seconds don't match, we're in trouble. */ + if (!( + given(atm->tm_min) && atm->tm_min != tp->tm_min || + given(atm->tm_hour) && atm->tm_hour != tp->tm_hour || + given(atm->tm_mday) && atm->tm_mday != tp->tm_mday || + given(atm->tm_mon) && atm->tm_mon != tp->tm_mon || + given(atm->tm_year) && atm->tm_year != tp->tm_year + )) + return tres; /* Everything matches. */ + + } while (--attempts); + + return -1; +} + +/* +* Convert Unix time to struct tm format. +* Use Coordinated Universal Time (UTC) if version 5 or newer; +* use local time otherwise. +*/ + static struct tm const * +time2tm(unixtime) + time_t unixtime; +{ + struct tm const *tm; +# if TZ_must_be_set + static char const *TZ; + if (!TZ && !(TZ = getenv("TZ"))) + faterror("TZ is not set"); +# endif + if (!(tm = (RCSversiontm_year + (tm->tm_year<100 ? 0 : 1900), + tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec + ); +} + + + + static time_t +str2time(source) + char const *source; +/* Parse a free-format date in SOURCE, yielding a Unix format time. */ +{ + int zone; + time_t unixtime; + struct tm parseddate; + + if (!partime(source, &parseddate, &zone)) + faterror("can't parse date/time: %s", source); + if ((unixtime = maketime(&parseddate, zone)) == -1) + faterror("bad date/time: %s", source); + return unixtime; +} + + void +str2date(source, target) + char const *source; + char target[datesize]; +/* Parse a free-format date in SOURCE, convert it + * into RCS internal format, and store the result into TARGET. + */ +{ + time2date(str2time(source), target); +} + + int +setfiledate(file, date) + char const *file, date[datesize]; +/* Set the access and modification time of FILE to DATE. */ +{ + static struct utimbuf times; /* static so unused fields are zero */ + char datebuf[datesize]; + + if (!date) + return 0; + times.actime = times.modtime = str2time(date2str(date, datebuf)); + return utime(file, ×); +} diff --git a/gnu/usr.bin/rcs/lib/merger.c b/gnu/usr.bin/rcs/lib/merger.c new file mode 100644 index 0000000..7162ffa --- /dev/null +++ b/gnu/usr.bin/rcs/lib/merger.c @@ -0,0 +1,139 @@ +/* merger - three-way file merge internals */ + +/* Copyright 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +#include "rcsbase.h" + +libId(mergerId, "$Id: merger.c,v 1.3 1991/08/20 23:05:00 eggert Exp $") + + static char const * +normalize_arg(s, b) + char const *s; + char **b; +/* + * If S looks like an option, prepend ./ to it. Yield the result. + * Set *B to the address of any storage that was allocated.. + */ +{ + char *t; + switch (*s) { + case '-': case '+': + *b = t = testalloc(strlen(s) + 3); + VOID sprintf(t, ".%c%s", SLASH, s); + return t; + default: + *b = 0; + return s; + } +} + + int +merge(tostdout, label, argv) + int tostdout; + char const *const label[2]; + char const *const argv[3]; +/* + * Do `merge [-p] -L l0 -L l1 a0 a1 a2', + * where TOSTDOUT specifies whether -p is present, + * LABEL gives l0 and l1, and ARGV gives a0, a1, and a2. + * Yield DIFF_SUCCESS or DIFF_FAILURE. + */ +{ + register int i; + FILE *f; + RILE *rt; + char const *a[3], *t; + char *b[3]; + int s; +#if !DIFF3_BIN + char const *d[2]; +#endif + + for (i=3; 0<=--i; ) + a[i] = normalize_arg(argv[i], &b[i]); + +#if DIFF3_BIN + t = 0; + if (!tostdout) + t = maketemp(0); + s = run( + (char*)0, t, + DIFF3, "-am", "-L", label[0], "-L", label[1], + a[0], a[1], a[2], (char*)0 + ); + switch (s) { + case DIFF_SUCCESS: + break; + case DIFF_FAILURE: + if (!quietflag) + warn("overlaps during merge"); + break; + default: + exiterr(); + } + if (t) { + if (!(f = fopen(argv[0], FOPEN_W))) + efaterror(argv[0]); + if (!(rt = Iopen(t, FOPEN_R, (struct stat*)0))) + efaterror(t); + fastcopy(rt, f); + Ifclose(rt); + Ofclose(f); + } +#else + for (i=0; i<2; i++) + switch (run( + (char*)0, d[i]=maketemp(i), + DIFF, a[i], a[2], (char*)0 + )) { + case DIFF_FAILURE: case DIFF_SUCCESS: break; + default: exiterr(); + } + t = maketemp(2); + s = run( + (char*)0, t, + DIFF3, "-E", d[0], d[1], a[0], a[1], a[2], + label[0], label[1], (char*)0 + ); + if (s != DIFF_SUCCESS) { + s = DIFF_FAILURE; + if (!quietflag) + warn("overlaps or other problems during merge"); + } + if (!(f = fopen(t, "a"))) + efaterror(t); + aputs(tostdout ? "1,$p\n" : "w\n", f); + Ofclose(f); + if (run(t, (char*)0, ED, "-", a[0], (char*)0)) + exiterr(); +#endif + + tempunlink(); + for (i=3; 0<=--i; ) + if (b[i]) + tfree(b[i]); + return s; +} diff --git a/gnu/usr.bin/rcs/lib/partime.c b/gnu/usr.bin/rcs/lib/partime.c new file mode 100644 index 0000000..4751fc5 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/partime.c @@ -0,0 +1,639 @@ +/* + * PARTIME parse date/time string into a TM structure + * + * Returns: + * 0 if parsing failed + * else time values in specified TM structure and zone (unspecified values + * set to TMNULL) + * Notes: + * This code is quasi-public; it may be used freely in like software. + * It is not to be sold, nor used in licensed software without + * permission of the author. + * For everyone's benefit, please report bugs and improvements! + * Copyright 1980 by Ken Harrenstien, SRI International. + * (ARPANET: KLH @ SRI) + */ + +/* Hacknotes: + * If parsing changed so that no backup needed, could perhaps modify + * to use a FILE input stream. Need terminator, though. + * Perhaps should return 0 on success, else a non-zero error val? + */ + +/* $Log: partime.c,v $ + * Revision 5.6 1991/08/19 03:13:55 eggert + * Update timezones. + * + * Revision 5.5 1991/04/21 11:58:18 eggert + * Don't put , just before } in initializer. + * + * Revision 5.4 1990/10/04 06:30:15 eggert + * Remove date vs time heuristics that fail between 2000 and 2400. + * Check for overflow when lexing an integer. + * Parse 'Jan 10 LT' as 'Jan 10, LT', not 'Jan, 10 LT'. + * + * Revision 5.3 1990/09/24 18:56:31 eggert + * Update timezones. + * + * Revision 5.2 1990/09/04 08:02:16 eggert + * Don't parse two-digit years, because it won't work after 1999/12/31. + * Don't permit 'Aug Aug'. + * + * Revision 5.1 1990/08/29 07:13:49 eggert + * Be able to parse our own date format. Don't assume year<10000. + * + * Revision 5.0 1990/08/22 08:12:40 eggert + * Switch to GMT and fix the bugs exposed thereby. Update timezones. + * Ansify and Posixate. Fix peekahead and int-size bugs. + * + * Revision 1.4 89/05/01 14:48:46 narten + * fixed #ifdef DEBUG construct + * + * Revision 1.3 88/08/28 14:53:40 eggert + * Remove unportable "#endif XXX"s. + * + * Revision 1.2 87/03/27 14:21:53 jenkins + * Port to suns + * + * Revision 1.1 82/05/06 11:38:26 wft + * Initial revision + * + */ + +#include "rcsbase.h" + +libId(partId, "$Id: partime.c,v 5.6 1991/08/19 03:13:55 eggert Exp $") + +#define given(v) (0 <= (v)) +#define TMNULL (-1) /* Items not given are given this value */ +#define TZ_OFFSET (24*60) /* TMNULL < zone_offset - TZ_OFFSET */ + +struct tmwent { + char const *went; + short wval; + char wflgs; + char wtype; +}; + /* wflgs */ +#define TWTIME 02 /* Word is a time value (absence implies date) */ +#define TWDST 04 /* Word is a DST-type timezone */ + /* wtype */ +#define TM_MON 1 /* month name */ +#define TM_WDAY 2 /* weekday name */ +#define TM_ZON 3 /* time zone name */ +#define TM_LT 4 /* local time */ +#define TM_DST 5 /* daylight savings time */ +#define TM_12 6 /* AM, PM, NOON, or MIDNIGHT */ + /* wval (for wtype==TM_12) */ +#define T12_AM 1 +#define T12_PM 2 +#define T12_NOON 12 +#define T12_MIDNIGHT 0 + +static struct tmwent const tmwords [] = { + {"january", 0, 0, TM_MON}, + {"february", 1, 0, TM_MON}, + {"march", 2, 0, TM_MON}, + {"april", 3, 0, TM_MON}, + {"may", 4, 0, TM_MON}, + {"june", 5, 0, TM_MON}, + {"july", 6, 0, TM_MON}, + {"august", 7, 0, TM_MON}, + {"september", 8, 0, TM_MON}, + {"october", 9, 0, TM_MON}, + {"november", 10, 0, TM_MON}, + {"december", 11, 0, TM_MON}, + + {"sunday", 0, 0, TM_WDAY}, + {"monday", 1, 0, TM_WDAY}, + {"tuesday", 2, 0, TM_WDAY}, + {"wednesday", 3, 0, TM_WDAY}, + {"thursday", 4, 0, TM_WDAY}, + {"friday", 5, 0, TM_WDAY}, + {"saturday", 6, 0, TM_WDAY}, + + {"gmt", 0*60, TWTIME, TM_ZON}, /* Greenwich */ + {"utc", 0*60, TWTIME, TM_ZON}, + {"ut", 0*60, TWTIME, TM_ZON}, + {"cut", 0*60, TWTIME, TM_ZON}, + + {"nzst", -12*60, TWTIME, TM_ZON}, /* New Zealand */ + {"jst", -9*60, TWTIME, TM_ZON}, /* Japan */ + {"kst", -9*60, TWTIME, TM_ZON}, /* Korea */ + {"ist", -5*60-30, TWTIME, TM_ZON},/* India */ + {"eet", -2*60, TWTIME, TM_ZON}, /* Eastern Europe */ + {"cet", -1*60, TWTIME, TM_ZON}, /* Central Europe */ + {"met", -1*60, TWTIME, TM_ZON}, /* Middle Europe */ + {"wet", 0*60, TWTIME, TM_ZON}, /* Western Europe */ + {"nst", 3*60+30, TWTIME, TM_ZON},/* Newfoundland */ + {"ast", 4*60, TWTIME, TM_ZON}, /* Atlantic */ + {"est", 5*60, TWTIME, TM_ZON}, /* Eastern */ + {"cst", 6*60, TWTIME, TM_ZON}, /* Central */ + {"mst", 7*60, TWTIME, TM_ZON}, /* Mountain */ + {"pst", 8*60, TWTIME, TM_ZON}, /* Pacific */ + {"akst", 9*60, TWTIME, TM_ZON}, /* Alaska */ + {"hast", 10*60, TWTIME, TM_ZON}, /* Hawaii-Aleutian */ + {"hst", 10*60, TWTIME, TM_ZON}, /* Hawaii */ + {"sst", 11*60, TWTIME, TM_ZON}, /* Samoa */ + + {"nzdt", -12*60, TWTIME+TWDST, TM_ZON}, /* New Zealand */ + {"kdt", -9*60, TWTIME+TWDST, TM_ZON}, /* Korea */ + {"bst", 0*60, TWTIME+TWDST, TM_ZON}, /* Britain */ + {"ndt", 3*60+30, TWTIME+TWDST, TM_ZON}, /* Newfoundland */ + {"adt", 4*60, TWTIME+TWDST, TM_ZON}, /* Atlantic */ + {"edt", 5*60, TWTIME+TWDST, TM_ZON}, /* Eastern */ + {"cdt", 6*60, TWTIME+TWDST, TM_ZON}, /* Central */ + {"mdt", 7*60, TWTIME+TWDST, TM_ZON}, /* Mountain */ + {"pdt", 8*60, TWTIME+TWDST, TM_ZON}, /* Pacific */ + {"akdt", 9*60, TWTIME+TWDST, TM_ZON}, /* Alaska */ + {"hadt", 10*60, TWTIME+TWDST, TM_ZON}, /* Hawaii-Aleutian */ + +#if 0 + /* + * The following names are duplicates or are not well attested. + * A standard is needed. + */ + {"east", -10*60, TWTIME, TM_ZON}, /* Eastern Australia */ + {"cast", -9*60-30, TWTIME, TM_ZON},/* Central Australia */ + {"cst", -8*60, TWTIME, TM_ZON}, /* China */ + {"hkt", -8*60, TWTIME, TM_ZON}, /* Hong Kong */ + {"sst", -8*60, TWTIME, TM_ZON}, /* Singapore */ + {"wast", -8*60, TWTIME, TM_ZON}, /* Western Australia */ + {"?", -6*60-30, TWTIME, TM_ZON},/* Burma */ + {"?", -4*60-30, TWTIME, TM_ZON},/* Afghanistan */ + {"it", -3*60-30, TWTIME, TM_ZON},/* Iran */ + {"ist", -2*60, TWTIME, TM_ZON}, /* Israel */ + {"mez", -1*60, TWTIME, TM_ZON}, /* Mittel-Europaeische Zeit */ + {"ast", 1*60, TWTIME, TM_ZON}, /* Azores */ + {"fst", 2*60, TWTIME, TM_ZON}, /* Fernando de Noronha */ + {"bst", 3*60, TWTIME, TM_ZON}, /* Brazil */ + {"wst", 4*60, TWTIME, TM_ZON}, /* Western Brazil */ + {"ast", 5*60, TWTIME, TM_ZON}, /* Acre Brazil */ + {"?", 9*60+30, TWTIME, TM_ZON},/* Marquesas */ + {"?", 12*60, TWTIME, TM_ZON}, /* Kwajalein */ + + {"eadt", -10*60, TWTIME+TWDST, TM_ZON}, /* Eastern Australia */ + {"cadt", -9*60-30, TWTIME+TWDST, TM_ZON}, /* Central Australia */ + {"cdt", -8*60, TWTIME+TWDST, TM_ZON}, /* China */ + {"wadt", -8*60, TWTIME+TWDST, TM_ZON}, /* Western Australia */ + {"idt", -2*60, TWTIME+TWDST, TM_ZON}, /* Israel */ + {"eest", -2*60, TWTIME+TWDST, TM_ZON}, /* Eastern Europe */ + {"cest", -1*60, TWTIME+TWDST, TM_ZON}, /* Central Europe */ + {"mest", -1*60, TWTIME+TWDST, TM_ZON}, /* Middle Europe */ + {"mesz", -1*60, TWTIME+TWDST, TM_ZON}, /* Mittel-Europaeische Sommerzeit */ + {"west", 0*60, TWTIME+TWDST, TM_ZON}, /* Western Europe */ + {"adt", 1*60, TWTIME+TWDST, TM_ZON}, /* Azores */ + {"fdt", 2*60, TWTIME+TWDST, TM_ZON}, /* Fernando de Noronha */ + {"edt", 3*60, TWTIME+TWDST, TM_ZON}, /* Eastern Brazil */ + {"wdt", 4*60, TWTIME+TWDST, TM_ZON}, /* Western Brazil */ + {"adt", 5*60, TWTIME+TWDST, TM_ZON}, /* Acre Brazil */ +#endif + + {"lt", 0, TWTIME, TM_LT}, /* local time */ + {"dst", 1*60, TWTIME, TM_DST}, /* daylight savings time */ + {"ddst", 2*60, TWTIME, TM_DST}, /* double dst */ + + {"am", T12_AM, TWTIME, TM_12}, + {"pm", T12_PM, TWTIME, TM_12}, + {"noon", T12_NOON, TWTIME, TM_12}, + {"midnight", T12_MIDNIGHT, TWTIME, TM_12}, + + {0, 0, 0, 0} /* Zero entry to terminate searches */ +}; + +struct token { + char const *tcp;/* pointer to string */ + int tcnt; /* # chars */ + char tbrk; /* "break" char */ + char tbrkl; /* last break char */ + char tflg; /* 0 = alpha, 1 = numeric */ + union { /* Resulting value; */ + int tnum;/* either a #, or */ + struct tmwent const *ttmw;/* a ptr to a tmwent. */ + } tval; +}; + +static struct tmwent const*ptmatchstr P((char const*,int,struct tmwent const*)); +static int pt12hack P((struct tm *,int)); +static int ptitoken P((struct token *)); +static int ptstash P((int *,int)); +static int pttoken P((struct token *)); + + static int +goodzone(t, offset, am) + register struct token const *t; + int offset; + int *am; +{ + register int m; + if ( + t->tflg && + t->tcnt == 4+offset && + (m = t->tval.tnum) <= 2400 && + isdigit(t->tcp[offset]) && + (m%=100) < 60 + ) { + m += t->tval.tnum/100 * 60; + if (t->tcp[offset-1]=='+') + m = -m; + *am = m; + return 1; + } + return 0; +} + + int +partime(astr, atm, zone) +char const *astr; +register struct tm *atm; +int *zone; +{ + register int i; + struct token btoken, atoken; + int zone_offset; /* minutes west of GMT, plus TZ_OFFSET */ + register char const *cp; + register char ch; + int ord, midnoon; + int *atmfield, dst, m; + int got1 = 0; + + atm->tm_sec = TMNULL; + atm->tm_min = TMNULL; + atm->tm_hour = TMNULL; + atm->tm_mday = TMNULL; + atm->tm_mon = TMNULL; + atm->tm_year = TMNULL; + atm->tm_wday = TMNULL; + atm->tm_yday = TMNULL; + midnoon = TMNULL; /* and our own temp stuff */ + zone_offset = TMNULL; + dst = TMNULL; + btoken.tcnt = btoken.tbrk = 0; + btoken.tcp = astr; + + for (;; got1=1) { + if (!ptitoken(&btoken)) /* Get a token */ + { if(btoken.tval.tnum) return(0); /* Read error? */ + if (given(midnoon)) /* EOF, wrap up */ + if (!pt12hack(atm, midnoon)) + return 0; + if (!given(atm->tm_min)) + atm->tm_min = 0; + *zone = + (given(zone_offset) ? zone_offset-TZ_OFFSET : 0) + - (given(dst) ? dst : 0); + return got1; + } + if(btoken.tflg == 0) /* Alpha? */ + { i = btoken.tval.ttmw->wval; + switch (btoken.tval.ttmw->wtype) { + default: + return 0; + case TM_MON: + atmfield = &atm->tm_mon; + break; + case TM_WDAY: + atmfield = &atm->tm_wday; + break; + case TM_DST: + atmfield = &dst; + break; + case TM_LT: + if (ptstash(&dst, 0)) + return 0; + i = 48*60; /* local time magic number -- see maketime() */ + /* fall into */ + case TM_ZON: + i += TZ_OFFSET; + if (btoken.tval.ttmw->wflgs & TWDST) + if (ptstash(&dst, 60)) + return 0; + /* Peek ahead for offset immediately afterwards. */ + if ( + (btoken.tbrk=='-' || btoken.tbrk=='+') && + (atoken=btoken, ++atoken.tcnt, ptitoken(&atoken)) && + goodzone(&atoken, 0, &m) + ) { + i += m; + btoken = atoken; + } + atmfield = &zone_offset; + break; + case TM_12: + atmfield = &midnoon; + } + if (ptstash(atmfield, i)) + return(0); /* ERR: val already set */ + continue; + } + + /* Token is number. Lots of hairy heuristics. */ + if (!isdigit(*btoken.tcp)) { + if (!goodzone(&btoken, 1, &m)) + return 0; + zone_offset = TZ_OFFSET + m; + continue; + } + + i = btoken.tval.tnum; /* Value now known to be valid; get it. */ + if (btoken.tcnt == 3) /* 3 digits = HMM */ + { +hhmm4: if (ptstash(&atm->tm_min, i%100)) + return(0); /* ERR: min conflict */ + i /= 100; +hh2: if (ptstash(&atm->tm_hour, i)) + return(0); /* ERR: hour conflict */ + continue; + } + + if (4 < btoken.tcnt) + goto year4; /* far in the future */ + if(btoken.tcnt == 4) /* 4 digits = YEAR or HHMM */ + { if (given(atm->tm_year)) goto hhmm4; /* Already got yr? */ + if (given(atm->tm_hour)) goto year4; /* Already got hr? */ + if(btoken.tbrk == ':') /* HHMM:SS ? */ + if ( ptstash(&atm->tm_hour, i/100) + || ptstash(&atm->tm_min, i%100)) + return(0); /* ERR: hr/min clash */ + else goto coltm2; /* Go handle SS */ + if(btoken.tbrk != ',' && btoken.tbrk != '/' + && (atoken=btoken, ptitoken(&atoken)) /* Peek */ + && ( atoken.tflg + ? !isdigit(*atoken.tcp) + : atoken.tval.ttmw->wflgs & TWTIME)) /* HHMM-ZON */ + goto hhmm4; + goto year4; /* Give up, assume year. */ + } + + /* From this point on, assume tcnt == 1 or 2 */ + /* 2 digits = MM, DD, or HH (MM and SS caught at coltime) */ + if(btoken.tbrk == ':') /* HH:MM[:SS] */ + goto coltime; /* must be part of time. */ + if (31 < i) + return 0; + + /* Check for numerical-format date */ + for (cp = "/-."; ch = *cp++;) + { ord = (ch == '.' ? 0 : 1); /* n/m = D/M or M/D */ + if(btoken.tbrk == ch) /* "NN-" */ + { if(btoken.tbrkl != ch) + { + atoken = btoken; + atoken.tcnt++; + if (ptitoken(&atoken) + && atoken.tflg == 0 + && atoken.tval.ttmw->wtype == TM_MON) + goto dd2; + if(ord)goto mm2; else goto dd2; /* "NN-" */ + } /* "-NN-" */ + if (!given(atm->tm_mday) + && given(atm->tm_year)) /* If "YYYY-NN-" */ + goto mm2; /* then always MM */ + if(ord)goto dd2; else goto mm2; + } + if(btoken.tbrkl == ch /* "-NN" */ + && given(ord ? atm->tm_mon : atm->tm_mday)) + if (!given(ord ? atm->tm_mday : atm->tm_mon)) /* MM/DD */ + if(ord)goto dd2; else goto mm2; + } + + /* Now reduced to choice between HH and DD */ + if (given(atm->tm_hour)) goto dd2; /* Have hour? Assume day. */ + if (given(atm->tm_mday)) goto hh2; /* Have day? Assume hour. */ + if (given(atm->tm_mon)) goto dd2; /* Have month? Assume day. */ + if(i > 24) goto dd2; /* Impossible HH means DD */ + atoken = btoken; + if (!ptitoken(&atoken)) /* Read ahead! */ + if(atoken.tval.tnum) return(0); /* ERR: bad token */ + else goto dd2; /* EOF, assume day. */ + if ( atoken.tflg + ? !isdigit(*atoken.tcp) + : atoken.tval.ttmw->wflgs & TWTIME) + /* If next token is a time spec, assume hour */ + goto hh2; /* e.g. "3 PM", "11-EDT" */ + +dd2: if (ptstash(&atm->tm_mday, i)) /* Store day (1 based) */ + return(0); + continue; + +mm2: if (ptstash(&atm->tm_mon, i-1)) /* Store month (make zero based) */ + return(0); + continue; + +year4: if ((i-=1900) < 0 || ptstash(&atm->tm_year, i)) /* Store year-1900 */ + return(0); /* ERR: year conflict */ + continue; + + /* Hack HH:MM[[:]SS] */ +coltime: + if (ptstash(&atm->tm_hour, i)) return 0; + if (!ptitoken(&btoken)) + return(!btoken.tval.tnum); + if(!btoken.tflg) return(0); /* ERR: HH: */ + if(btoken.tcnt == 4) /* MMSS */ + if (ptstash(&atm->tm_min, btoken.tval.tnum/100) + || ptstash(&atm->tm_sec, btoken.tval.tnum%100)) + return(0); + else continue; + if(btoken.tcnt != 2 + || ptstash(&atm->tm_min, btoken.tval.tnum)) + return(0); /* ERR: MM bad */ + if (btoken.tbrk != ':') continue; /* Seconds follow? */ +coltm2: if (!ptitoken(&btoken)) + return(!btoken.tval.tnum); + if(!btoken.tflg || btoken.tcnt != 2 /* Verify SS */ + || ptstash(&atm->tm_sec, btoken.tval.tnum)) + return(0); /* ERR: SS bad */ + } +} + +/* Store date/time value, return 0 if successful. + * Fail if entry is already set. + */ + static int +ptstash(adr,val) +int *adr; +int val; +{ register int *a; + if (given(*(a=adr))) + return 1; + *a = val; + return(0); +} + +/* This subroutine is invoked for AM, PM, NOON and MIDNIGHT when wrapping up + * just prior to returning from partime. + */ + static int +pt12hack(tm, aval) +register struct tm *tm; +register int aval; +{ register int h = tm->tm_hour; + switch (aval) { + case T12_AM: + case T12_PM: + if (h > 12) + return 0; + if (h == 12) + tm->tm_hour = 0; + if (aval == T12_PM) + tm->tm_hour += 12; + break; + default: + if (0 < tm->tm_min || 0 < tm->tm_sec) + return 0; + if (!given(h) || h==12) + tm->tm_hour = aval; + else if (aval==T12_MIDNIGHT && (h==0 || h==24)) + return 0; + } + return 1; +} + +/* Get a token and identify it to some degree. + * Returns 0 on failure; token.tval will be 0 for normal EOF, otherwise + * hit error of some sort + */ + + static int +ptitoken(tkp) +register struct token *tkp; +{ + register char const *cp; + register int i, j, k; + + if (!pttoken(tkp)) +#ifdef DEBUG + { + VOID printf("EOF\n"); + return(0); + } +#else + return(0); +#endif + cp = tkp->tcp; + +#ifdef DEBUG + VOID printf("Token: \"%.*s\" ", tkp->tcnt, cp); +#endif + + if (tkp->tflg) { + i = tkp->tcnt; + if (*cp == '+' || *cp == '-') { + cp++; + i--; + } + while (0 <= --i) { + j = tkp->tval.tnum*10; + k = j + (*cp++ - '0'); + if (j/10 != tkp->tval.tnum || k < j) { + /* arithmetic overflow */ + tkp->tval.tnum = 1; + return 0; + } + tkp->tval.tnum = k; + } + } else if (!(tkp->tval.ttmw = ptmatchstr(cp, tkp->tcnt, tmwords))) + { +#ifdef DEBUG + VOID printf("Not found!\n"); +#endif + tkp->tval.tnum = 1; + return 0; + } + +#ifdef DEBUG + if(tkp->tflg) + VOID printf("Val: %d.\n",tkp->tval.tnum); + else VOID printf("Found: \"%s\", val: %d, type %d\n", + tkp->tval.ttmw->went,tkp->tval.ttmw->wval,tkp->tval.ttmw->wtype); +#endif + + return(1); +} + +/* Read token from input string into token structure */ + static int +pttoken(tkp) +register struct token *tkp; +{ + register char const *cp; + register int c; + char const *astr; + + tkp->tcp = astr = cp = tkp->tcp + tkp->tcnt; + tkp->tbrkl = tkp->tbrk; /* Set "last break" */ + tkp->tcnt = tkp->tbrk = tkp->tflg = 0; + tkp->tval.tnum = 0; + + while(c = *cp++) + { switch(c) + { case ' ': case '\t': /* Flush all whitespace */ + case '\r': case '\n': + case '\v': case '\f': + if (!tkp->tcnt) { /* If no token yet */ + tkp->tcp = cp; /* ignore the brk */ + continue; /* and go on. */ + } + /* fall into */ + case '(': case ')': /* Perhaps any non-alphanum */ + case '-': case ',': /* shd qualify as break? */ + case '+': + case '/': case ':': case '.': /* Break chars */ + if(tkp->tcnt == 0) /* If no token yet */ + { tkp->tcp = cp; /* ignore the brk */ + tkp->tbrkl = c; + continue; /* and go on. */ + } + tkp->tbrk = c; + return(tkp->tcnt); + } + if (!tkp->tcnt++) { /* If first char of token, */ + if (isdigit(c)) { + tkp->tflg = 1; + if (astrtcp--; + tkp->tcnt++; + } + } + } else if ((isdigit(c)!=0) != tkp->tflg) { /* else check type */ + tkp->tbrk = c; + return --tkp->tcnt; /* Wrong type, back up */ + } + } + return(tkp->tcnt); /* When hit EOF */ +} + + + static struct tmwent const * +ptmatchstr(astr,cnt,astruc) + char const *astr; + int cnt; + struct tmwent const *astruc; +{ + register char const *cp, *mp; + register int c; + struct tmwent const *lastptr; + int i; + + lastptr = 0; + for(;mp = astruc->went; astruc += 1) + { cp = astr; + for(i = cnt; i > 0; i--) + { + switch (*cp++ - (c = *mp++)) + { case 0: continue; /* Exact match */ + case 'A'-'a': + if (ctab[c] == Letter) + continue; + } + break; + } + if(i==0) + if (!*mp) return astruc; /* Exact match */ + else if(lastptr) return(0); /* Ambiguous */ + else lastptr = astruc; /* 1st ambig */ + } + return lastptr; +} diff --git a/gnu/usr.bin/rcs/lib/rcsbase.h b/gnu/usr.bin/rcs/lib/rcsbase.h new file mode 100644 index 0000000..c0904bb --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsbase.h @@ -0,0 +1,677 @@ + +/* + * RCS common definitions and data structures + */ +#define RCSBASE "$Id: rcsbase.h,v 5.11 1991/10/07 17:32:46 eggert Exp $" + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + +/***************************************************************************** + * INSTRUCTIONS: + * ============= + * See the Makefile for how to define C preprocessor symbols. + * If you need to change the comment leaders, update the table comtable[] + * in rcsfnms.c. (This can wait until you know what a comment leader is.) + ***************************************************************************** + */ + + +/* $Log: rcsbase.h,v $ + * Revision 5.11 1991/10/07 17:32:46 eggert + * Support piece tables even if !has_mmap. + * + * Revision 5.10 1991/09/24 00:28:39 eggert + * Remove unexported functions. + * + * Revision 5.9 1991/08/19 03:13:55 eggert + * Add piece tables and other tuneups, and NFS workarounds. + * + * Revision 5.8 1991/04/21 11:58:20 eggert + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.7 1991/02/28 19:18:50 eggert + * Try setuid() if seteuid() doesn't work. + * + * Revision 5.6 1991/02/26 17:48:37 eggert + * Support new link behavior. Move ANSI C / Posix declarations into conf.sh. + * + * Revision 5.5 1990/12/04 05:18:43 eggert + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.4 1990/11/01 05:03:35 eggert + * Don't assume that builtins are functions; they may be macros. + * Permit arbitrary data in logs. + * + * Revision 5.3 1990/09/26 23:36:58 eggert + * Port wait() to non-Posix ANSI C hosts. + * + * Revision 5.2 1990/09/04 08:02:20 eggert + * Don't redefine NAME_MAX, PATH_MAX. + * Improve incomplete line handling. Standardize yes-or-no procedure. + * + * Revision 5.1 1990/08/29 07:13:53 eggert + * Add -kkvl. Fix type typos exposed by porting. Clean old log messages too. + * + * Revision 5.0 1990/08/22 08:12:44 eggert + * Adjust ANSI C / Posix support. Add -k, -V, setuid. Don't call access(). + * Remove compile-time limits; use malloc instead. + * Ansify and Posixate. Add support for ISO 8859. + * Remove snoop and v2 support. + * + * Revision 4.9 89/05/01 15:17:14 narten + * botched previous USG fix + * + * Revision 4.8 89/05/01 14:53:05 narten + * changed #include -> string.h for USG systems. + * + * Revision 4.7 88/11/08 15:58:45 narten + * removed defs for functions loaded from libraries + * + * Revision 4.6 88/08/09 19:12:36 eggert + * Shrink stdio code size; remove lint; permit -Dhshsize=nn. + * + * Revision 4.5 87/12/18 17:06:41 narten + * made removed BSD ifdef, now uses V4_2BSD + * + * Revision 4.4 87/10/18 10:29:49 narten + * Updating version numbers + * Changes relative to 1.1 are actually relative to 4.2 + * + * Revision 1.3 87/09/24 14:02:25 narten + * changes for lint + * + * Revision 1.2 87/03/27 14:22:02 jenkins + * Port to suns + * + * Revision 4.2 83/12/20 16:04:20 wft + * merged 3.6.1.1 and 4.1 (SMALLOG, logsize). + * moved setting of STRICT_LOCKING to Makefile. + * changed DOLLAR to UNKN (conflict with KDELIM). + * + * Revision 4.1 83/05/04 09:12:41 wft + * Added markers Id and RCSfile. + * Added Dbranch for default branches. + * + * Revision 3.6.1.1 83/12/02 21:56:22 wft + * Increased logsize, added macro SMALLOG. + * + * Revision 3.6 83/01/15 16:43:28 wft + * 4.2 prerelease + * + * Revision 3.6 83/01/15 16:43:28 wft + * Replaced dbm.h with BYTESIZ, fixed definition of rindex(). + * Added variants of NCPFN and NCPPN for bsd 4.2, selected by defining V4_2BSD. + * Added macro DELNUMFORM to have uniform format for printing delta text nodes. + * Added macro DELETE to mark deleted deltas. + * + * Revision 3.5 82/12/10 12:16:56 wft + * Added two forms of DATEFORM, one using %02d, the other %.2d. + * + * Revision 3.4 82/12/04 20:01:25 wft + * added LOCKER, Locker, and USG (redefinition of rindex). + * + * Revision 3.3 82/12/03 12:22:04 wft + * Added dbm.h, stdio.h, RCSBASE, RCSSEP, RCSSUF, WORKMODE, TMPFILE3, + * PRINTDATE, PRINTTIME, map, and ctab; removed Suffix. Redefined keyvallength + * using NCPPN. Changed putc() to abort on write error. + * + * Revision 3.2 82/10/18 15:03:52 wft + * added macro STRICT_LOCKING, removed RCSUMASK. + * renamed JOINFILE[1,2] to JOINFIL[1,2]. + * + * Revision 3.1 82/10/11 19:41:17 wft + * removed NBPW, NBPC, NCPW. + * added typdef int void to aid compiling + */ + + +#include "conf.h" + + +#define EXIT_TROUBLE DIFF_TROUBLE + +#ifdef PATH_MAX +# define SIZEABLE_PATH PATH_MAX /* size of a large path; not a hard limit */ +#else +# define SIZEABLE_PATH _POSIX_PATH_MAX +#endif + +/* for traditional C hosts with unusual size arguments */ +#define Fread(p,s,n,f) fread(p, (freadarg_type)(s), (freadarg_type)(n), f) +#define Fwrite(p,s,n,f) fwrite(p, (freadarg_type)(s), (freadarg_type)(n), f) + + +/* + * Parameters + */ + +/* backwards compatibility with old versions of RCS */ +#define VERSION_min 3 /* old output RCS format supported */ +#define VERSION_max 5 /* newest output RCS format supported */ +#ifndef VERSION_DEFAULT /* default RCS output format */ +# define VERSION_DEFAULT VERSION_max +#endif +#define VERSION(n) ((n) - VERSION_DEFAULT) /* internally, 0 is the default */ + +#ifndef STRICT_LOCKING +#define STRICT_LOCKING 1 +#endif + /* 0 sets the default locking to non-strict; */ + /* used in experimental environments. */ + /* 1 sets the default locking to strict; */ + /* used in production environments. */ + +#define yearlength 16 /* (good through AD 9,999,999,999,999,999) */ +#define datesize (yearlength+16) /* size of output of DATEFORM */ +#define joinlength 20 /* number of joined revisions permitted */ +#define RCSTMPPREFIX '_' /* prefix for temp files in working dir */ +#define KDELIM '$' /* delimiter for keywords */ +#define VDELIM ':' /* separates keywords from values */ +#define DEFAULTSTATE "Exp" /* default state of revisions */ + + + +#define true 1 +#define false 0 +#define nil 0 + + +/* + * RILE - readonly file + * declarecache; - declares local cache for RILE variable(s) + * setupcache - sets up the local RILE cache, but does not initialize it + * cache, uncache - caches and uncaches the local RILE; + * (uncache,cache) is needed around functions that advance the RILE pointer + * Igeteof(f,c,s) - get a char c from f, executing statement s at EOF + * cachegeteof(c,s) - Igeteof applied to the local RILE + * Iget(f,c) - like Igeteof, except EOF is an error + * cacheget(c) - Iget applied to the local RILE + * Ifileno, Irewind, Iseek, Itell - analogs to stdio routines + */ + +#if large_memory + typedef unsigned char const *Iptr_type; + typedef struct { + Iptr_type ptr, lim; + unsigned char *base; /* for lint, not Iptr_type even if has_mmap */ +# if has_mmap +# define Ifileno(f) ((f)->fd) + int fd; +# else +# define Ifileno(f) fileno((f)->stream) + FILE *stream; + unsigned char *readlim; +# endif + } RILE; +# if has_mmap +# define declarecache register Iptr_type ptr, lim +# define setupcache(f) (lim = (f)->lim) +# define Igeteof(f,c,s) if ((f)->ptr==(f)->lim) s else (c)= *(f)->ptr++ +# define cachegeteof(c,s) if (ptr==lim) s else (c)= *ptr++ +# else +# define declarecache register Iptr_type ptr; register RILE *rRILE +# define setupcache(f) (rRILE = (f)) +# define Igeteof(f,c,s) if ((f)->ptr==(f)->readlim && !Igetmore(f)) s else (c)= *(f)->ptr++ +# define cachegeteof(c,s) if (ptr==rRILE->readlim && !Igetmore(rRILE)) s else (c)= *ptr++ +# endif +# define uncache(f) ((f)->ptr = ptr) +# define cache(f) (ptr = (f)->ptr) +# define Iget(f,c) Igeteof(f,c,Ieof();) +# define cacheget(c) cachegeteof(c,Ieof();) +# define Itell(f) ((f)->ptr) +# define Iseek(f,p) ((f)->ptr = (p)) +# define Irewind(f) Iseek(f, (f)->base) +# define cachetell() ptr +#else +# define RILE FILE +# define declarecache register FILE *ptr +# define setupcache(f) (ptr = (f)) +# define uncache(f) +# define cache(f) +# define Igeteof(f,c,s) if(((c)=getc(f))<0){testIerror(f);if(feof(f))s}else +# define cachegeteof(c,s) Igeteof(ptr,c,s) +# define Iget(f,c) if (((c)=getc(f))<0) testIeof(f); else +# define cacheget(c) Iget(ptr,c) +# define Ifileno(f) fileno(f) +#endif + +/* Print a char, but abort on write error. */ +#define aputc(c,o) if (putc(c,o)<0) testOerror(o); else + +/* Get a character from an RCS file, perhaps copying to a new RCS file. */ +#define GETCeof(o,c,s) { cachegeteof(c,s); if (o) aputc(c,o); } +#define GETC(o,c) { cacheget(c); if (o) aputc(c,o); } + + +#define WORKMODE(RCSmode, writable) ((RCSmode)&~(S_IWUSR|S_IWGRP|S_IWOTH) | ((writable)?S_IWUSR:0)) +/* computes mode of working file: same as RCSmode, but write permission */ +/* determined by writable */ + + +/* character classes and token codes */ +enum tokens { +/* classes */ DELIM, DIGIT, IDCHAR, NEWLN, LETTER, Letter, + PERIOD, SBEGIN, SPACE, UNKN, +/* tokens */ COLON, ID, NUM, SEMI, STRING +}; + +#define SDELIM '@' /* the actual character is needed for string handling*/ +/* SDELIM must be consistent with ctab[], so that ctab[SDELIM]==SBEGIN. + * there should be no overlap among SDELIM, KDELIM, and VDELIM + */ + +#define isdigit(c) ((unsigned)((c)-'0') <= 9) /* faster than ctab[c]==DIGIT */ + + + + + +/*************************************** + * Data structures for the symbol table + ***************************************/ + +/* Buffer of arbitrary data */ +struct buf { + char *string; + size_t size; +}; +struct cbuf { + char const *string; + size_t size; +}; + +/* Hash table entry */ +struct hshentry { + char const * num; /* pointer to revision number (ASCIZ) */ + char const * date; /* pointer to date of checkin */ + char const * author; /* login of person checking in */ + char const * lockedby; /* who locks the revision */ + char const * state; /* state of revision (Exp by default) */ + struct cbuf log; /* log message requested at checkin */ + struct branchhead * branches; /* list of first revisions on branches*/ + struct cbuf ig; /* ignored phrases of revision */ + struct hshentry * next; /* next revision on same branch */ + struct hshentry * nexthsh; /* next revision with same hash value */ + unsigned long insertlns;/* lines inserted (computed by rlog) */ + unsigned long deletelns;/* lines deleted (computed by rlog) */ + char selector; /* true if selected, false if deleted */ +}; + +/* list of hash entries */ +struct hshentries { + struct hshentries *rest; + struct hshentry *first; +}; + +/* list element for branch lists */ +struct branchhead { + struct hshentry * hsh; + struct branchhead * nextbranch; +}; + +/* accesslist element */ +struct access { + char const * login; + struct access * nextaccess; +}; + +/* list element for locks */ +struct lock { + char const * login; + struct hshentry * delta; + struct lock * nextlock; +}; + +/* list element for symbolic names */ +struct assoc { + char const * symbol; + char const * num; + struct assoc * nextassoc; +}; + + +#define mainArgs (argc,argv) int argc; char **argv; + +#if lint +# define libId(name,rcsid) +# define mainProg(name,cmd,rcsid) int name mainArgs +#else +# define libId(name,rcsid) char const name[] = rcsid; +# define mainProg(name,cmd,rcsid) char const copyright[] = "Copyright 1982,1988,1989 by Walter F. Tichy\nPurdue CS\nCopyright 1990,1991 by Paul Eggert", rcsbaseId[] = RCSBASE, cmdid[] = cmd; libId(name,rcsid) int main mainArgs +#endif + +/* + * Markers for keyword expansion (used in co and ident) + * Every byte must have class LETTER or Letter. + */ +#define AUTHOR "Author" +#define DATE "Date" +#define HEADER "Header" +#define IDH "Id" +#define LOCKER "Locker" +#define LOG "Log" +#define RCSFILE "RCSfile" +#define REVISION "Revision" +#define SOURCE "Source" +#define STATE "State" +#define keylength 8 /* max length of any of the above keywords */ + +enum markers { Nomatch, Author, Date, Header, Id, + Locker, Log, RCSfile, Revision, Source, State }; + /* This must be in the same order as rcskeys.c's Keyword[] array. */ + +#define DELNUMFORM "\n\n%s\n%s\n" +/* used by putdtext and scanlogtext */ + +#define EMPTYLOG "*** empty log message ***" /* used by ci and rlog */ + +/* main program */ +extern char const cmdid[]; +exiting void exiterr P((void)); + +/* maketime */ +int setfiledate P((char const*,char const[datesize])); +void str2date P((char const*,char[datesize])); +void time2date P((time_t,char[datesize])); + +/* merge */ +int merge P((int,char const*const[2],char const*const[3])); + +/* partime */ +int partime P((char const*,struct tm*,int*)); + +/* rcsedit */ +#define ciklogsize 23 /* sizeof("checked in with -k by ") */ +extern FILE *fcopy; +extern char const *resultfile; +extern char const ciklog[ciklogsize]; +extern int locker_expansion; +extern struct buf dirtfname[]; +#define newRCSfilename (dirtfname[0].string) +RILE *rcswriteopen P((struct buf*,struct stat*,int)); +char const *makedirtemp P((char const*,int)); +char const *getcaller P((void)); +int addlock P((struct hshentry*)); +int addsymbol P((char const*,char const*,int)); +int checkaccesslist P((void)); +int chnamemod P((FILE**,char const*,char const*,mode_t)); +int donerewrite P((int)); +int dorewrite P((int,int)); +int expandline P((RILE*,FILE*,struct hshentry const*,int,FILE*)); +int findlock P((int,struct hshentry**)); +void aflush P((FILE*)); +void copystring P((void)); +void dirtempunlink P((void)); +void enterstring P((void)); +void finishedit P((struct hshentry const*,FILE*,int)); +void keepdirtemp P((char const*)); +void openfcopy P((FILE*)); +void snapshotedit P((FILE*)); +void xpandstring P((struct hshentry const*)); +#if has_NFS || bad_unlink + int un_link P((char const*)); +#else +# define un_link(s) unlink(s) +#endif +#if large_memory + void edit_string P((void)); +# define editstring(delta) edit_string() +#else + void editstring P((struct hshentry const*)); +#endif + +/* rcsfcmp */ +int rcsfcmp P((RILE*,struct stat const*,char const*,struct hshentry const*)); + +/* rcsfnms */ +#define bufautobegin(b) ((void) ((b)->string = 0, (b)->size = 0)) +extern FILE *workstdout; +extern char *workfilename; +extern char const *RCSfilename; +extern char const *suffixes; +extern struct stat RCSstat; +RILE *rcsreadopen P((struct buf*,struct stat*,int)); +char *bufenlarge P((struct buf*,char const**)); +char const *basename P((char const*)); +char const *getfullRCSname P((void)); +char const *maketemp P((int)); +char const *rcssuffix P((char const*)); +int pairfilenames P((int,char**,RILE*(*)P((struct buf*,struct stat*,int)),int,int)); +size_t dirlen P((char const*)); +struct cbuf bufremember P((struct buf*,size_t)); +void bufalloc P((struct buf*,size_t)); +void bufautoend P((struct buf*)); +void bufrealloc P((struct buf*,size_t)); +void bufscat P((struct buf*,char const*)); +void bufscpy P((struct buf*,char const*)); +void tempunlink P((void)); + +/* rcsgen */ +extern int interactiveflag; +extern struct buf curlogbuf; +char const *buildrevision P((struct hshentries const*,struct hshentry*,FILE*,int)); +int getcstdin P((void)); +int ttystdin P((void)); +int yesorno P((int,char const*,...)); +struct cbuf cleanlogmsg P((char*,size_t)); +struct cbuf getsstdin P((char const*,char const*,char const*,struct buf*)); +void putdesc P((int,char*)); + +/* rcskeep */ +extern int prevkeys; +extern struct buf prevauthor, prevdate, prevrev, prevstate; +int getoldkeys P((RILE*)); + +/* rcskeys */ +extern char const *const Keyword[]; +enum markers trymatch P((char const*)); + +/* rcslex */ +extern FILE *foutptr; +extern FILE *frewrite; +extern RILE *finptr; +extern char const *NextString; +extern enum tokens nexttok; +extern int hshenter; +extern int nerror; +extern int nextc; +extern int quietflag; +extern unsigned long rcsline; +char const *getid P((void)); +exiting void efaterror P((char const*)); +exiting void enfaterror P((int,char const*)); +exiting void faterror P((char const*,...)); +exiting void fatserror P((char const*,...)); +exiting void Ieof P((void)); +exiting void Ierror P((void)); +exiting void Oerror P((void)); +char *checkid P((char*,int)); +int eoflex P((void)); +int getkeyopt P((char const*)); +int getlex P((enum tokens)); +struct cbuf getphrases P((char const*)); +struct cbuf savestring P((struct buf*)); +struct hshentry *getnum P((void)); +void Ifclose P((RILE*)); +void Izclose P((RILE**)); +void Lexinit P((void)); +void Ofclose P((FILE*)); +void Ozclose P((FILE**)); +void afputc P((int,FILE*)); +void aprintf P((FILE*,char const*,...)); +void aputs P((char const*,FILE*)); +void checksid P((char*)); +void diagnose P((char const*,...)); +void eerror P((char const*)); +void eflush P((void)); +void enerror P((int,char const*)); +void error P((char const*,...)); +void fvfprintf P((FILE*,char const*,va_list)); +void getkey P((char const*)); +void getkeystring P((char const*)); +void nextlex P((void)); +void oflush P((void)); +void printstring P((void)); +void readstring P((void)); +void redefined P((int)); +void testIerror P((FILE*)); +void testOerror P((FILE*)); +void warn P((char const*,...)); +void warnignore P((void)); +#if has_madvise && has_mmap && large_memory + void advise_access P((RILE*,int)); +# define if_advise_access(p,f,advice) if (p) advise_access(f,advice) +#else +# define advise_access(f,advice) +# define if_advise_access(p,f,advice) +#endif +#if has_mmap && large_memory + RILE *I_open P((char const*,struct stat*)); +# define Iopen(f,m,s) I_open(f,s) +#else + RILE *Iopen P((char const*,char const*,struct stat*)); +#endif +#if !large_memory + void testIeof P((FILE*)); + void Irewind P((RILE*)); +#endif + +/* rcsmap */ +extern const enum tokens ctab[]; + +/* rcsrev */ +char *partialno P((struct buf*,char const*,unsigned)); +char const *tiprev P((void)); +int cmpnum P((char const*,char const*)); +int cmpnumfld P((char const*,char const*,unsigned)); +int compartial P((char const*,char const*,unsigned)); +int expandsym P((char const*,struct buf*)); +int fexpandsym P((char const*,struct buf*,RILE*)); +struct hshentry *genrevs P((char const*,char const*,char const*,char const*,struct hshentries**)); +unsigned countnumflds P((char const*)); +void getbranchno P((char const*,struct buf*)); + +/* rcssyn */ +/* These expand modes must agree with Expand_names[] in rcssyn.c. */ +#define KEYVAL_EXPAND 0 /* -kkv `$Keyword: value $' */ +#define KEYVALLOCK_EXPAND 1 /* -kkvl `$Keyword: value locker $' */ +#define KEY_EXPAND 2 /* -kk `$Keyword$' */ +#define VAL_EXPAND 3 /* -kv `value' */ +#define OLD_EXPAND 4 /* -ko use old string, omitting expansion */ +struct diffcmd { + unsigned long + line1, /* number of first line */ + nlines, /* number of lines affected */ + adprev, /* previous 'a' line1+1 or 'd' line1 */ + dafter; /* sum of previous 'd' line1 and previous 'd' nlines */ +}; +extern char const * Dbranch; +extern struct access * AccessList; +extern struct assoc * Symbols; +extern struct cbuf Comment; +extern struct lock * Locks; +extern struct hshentry * Head; +extern int Expand; +extern int StrictLocks; +extern unsigned TotalDeltas; +extern char const *const expand_names[]; +extern char const Kdesc[]; +extern char const Klog[]; +extern char const Ktext[]; +int getdiffcmd P((RILE*,int,FILE*,struct diffcmd*)); +int putdftext P((char const*,struct cbuf,RILE*,FILE*,int)); +int putdtext P((char const*,struct cbuf,char const*,FILE*,int)); +int str2expmode P((char const*)); +void getadmin P((void)); +void getdesc P((int)); +void gettree P((void)); +void ignorephrase P((void)); +void initdiffcmd P((struct diffcmd*)); +void putadmin P((FILE*)); +void putstring P((FILE*,int,struct cbuf,int)); +void puttree P((struct hshentry const*,FILE*)); + +/* rcsutil */ +extern int RCSversion; +char *cgetenv P((char const*)); +char *fstr_save P((char const*)); +char *str_save P((char const*)); +char const *date2str P((char const[datesize],char[datesize])); +char const *getusername P((int)); +int getRCSINIT P((int,char**,char***)); +int run P((char const*,char const*,...)); +int runv P((char const**)); +malloc_type fremember P((malloc_type)); +malloc_type ftestalloc P((size_t)); +malloc_type testalloc P((size_t)); +malloc_type testrealloc P((malloc_type,size_t)); +#define ftalloc(T) ftnalloc(T,1) +#define talloc(T) tnalloc(T,1) +#if lint + extern malloc_type lintalloc; +# define ftnalloc(T,n) (lintalloc = ftestalloc(sizeof(T)*(n)), (T*)0) +# define tnalloc(T,n) (lintalloc = testalloc(sizeof(T)*(n)), (T*)0) +# define trealloc(T,p,n) (lintalloc = testrealloc((malloc_type)0, sizeof(T)*(n)), p) +# define tfree(p) +#else +# define ftnalloc(T,n) ((T*) ftestalloc(sizeof(T)*(n))) +# define tnalloc(T,n) ((T*) testalloc(sizeof(T)*(n))) +# define trealloc(T,p,n) ((T*) testrealloc((malloc_type)(p), sizeof(T)*(n))) +# define tfree(p) free((malloc_type)(p)) +#endif +void awrite P((char const*,size_t,FILE*)); +void fastcopy P((RILE*,FILE*)); +void ffree P((void)); +void ffree1 P((char const*)); +void setRCSversion P((char const*)); +#if has_signal + void catchints P((void)); + void ignoreints P((void)); + void restoreints P((void)); +#else +# define catchints() +# define ignoreints() +# define restoreints() +#endif +#if has_getuid + uid_t ruid P((void)); +# define myself(u) ((u) == ruid()) +#else +# define myself(u) true +#endif +#if has_setuid + uid_t euid P((void)); + void nosetid P((void)); + void seteid P((void)); + void setrid P((void)); +#else +# define nosetid() +# define seteid() +# define setrid() +#endif diff --git a/gnu/usr.bin/rcs/lib/rcsedit.c b/gnu/usr.bin/rcs/lib/rcsedit.c new file mode 100644 index 0000000..fab4f62 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsedit.c @@ -0,0 +1,1656 @@ +/* + * RCS stream editor + */ +/********************************************************************************** + * edits the input file according to a + * script from stdin, generated by diff -n + * performs keyword expansion + ********************************************************************************** + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + +/* $Log: rcsedit.c,v $ + * Revision 5.11 1991/11/03 01:11:44 eggert + * Move the warning about link breaking to where they're actually being broken. + * + * Revision 5.10 1991/10/07 17:32:46 eggert + * Support piece tables even if !has_mmap. Fix rare NFS bugs. + * + * Revision 5.9 1991/09/17 19:07:40 eggert + * SGI readlink() yields ENXIO, not EINVAL, for nonlinks. + * + * Revision 5.8 1991/08/19 03:13:55 eggert + * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune. + * + * Revision 5.7 1991/04/21 11:58:21 eggert + * Fix errno bugs. Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.6 1991/02/25 07:12:40 eggert + * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen. + * + * Revision 5.5 1990/12/30 05:07:35 eggert + * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL). + * + * Revision 5.4 1990/11/01 05:03:40 eggert + * Permit arbitrary data in comment leaders. + * + * Revision 5.3 1990/09/11 02:41:13 eggert + * Tune expandline(). + * + * Revision 5.2 1990/09/04 08:02:21 eggert + * Count RCS lines better. Improve incomplete line handling. + * + * Revision 5.1 1990/08/29 07:13:56 eggert + * Add -kkvl. + * Fix bug when getting revisions to files ending in incomplete lines. + * Fix bug in comment leader expansion. + * + * Revision 5.0 1990/08/22 08:12:47 eggert + * Don't require final newline. + * Don't append "checked in with -k by " to logs, + * so that checking in a program with -k doesn't change it. + * Don't generate trailing white space for empty comment leader. + * Remove compile-time limits; use malloc instead. Add -k, -V. + * Permit dates past 1999/12/31. Make lock and temp files faster and safer. + * Ansify and Posixate. Check diff's output. + * + * Revision 4.8 89/05/01 15:12:35 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.7 88/11/08 13:54:14 narten + * misplaced semicolon caused infinite loop + * + * Revision 4.6 88/08/09 19:12:45 eggert + * Shrink stdio code size; allow cc -R. + * + * Revision 4.5 87/12/18 11:38:46 narten + * Changes from the 43. version. Don't know the significance of the + * first change involving "rewind". Also, additional "lint" cleanup. + * (Guy Harris) + * + * Revision 4.4 87/10/18 10:32:21 narten + * Updating version numbers. Changes relative to version 1.1 actually + * relative to 4.1 + * + * Revision 1.4 87/09/24 13:59:29 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.3 87/09/15 16:39:39 shepler + * added an initializatin of the variables editline and linecorr + * this will be done each time a file is processed. + * (there was an obscure bug where if co was used to retrieve multiple files + * it would dump) + * fix attributed to Roy Morris @FileNet Corp ...!felix!roy + * + * Revision 1.2 87/03/27 14:22:17 jenkins + * Port to suns + * + * Revision 4.1 83/05/12 13:10:30 wft + * Added new markers Id and RCSfile; added locker to Header and Id. + * Overhauled expandline completely() (problem with $01234567890123456789@). + * Moved trymatch() and marker table to rcskeys.c. + * + * Revision 3.7 83/05/12 13:04:39 wft + * Added retry to expandline to resume after failed match which ended in $. + * Fixed truncation problem for $19chars followed by@@. + * Log no longer expands full path of RCS file. + * + * Revision 3.6 83/05/11 16:06:30 wft + * added retry to expandline to resume after failed match which ended in $. + * Fixed truncation problem for $19chars followed by@@. + * + * Revision 3.5 82/12/04 13:20:56 wft + * Added expansion of keyword Locker. + * + * Revision 3.4 82/12/03 12:26:54 wft + * Added line number correction in case editing does not start at the + * beginning of the file. + * Changed keyword expansion to always print a space before closing KDELIM; + * Expansion for Header shortened. + * + * Revision 3.3 82/11/14 14:49:30 wft + * removed Suffix from keyword expansion. Replaced fclose with ffclose. + * keyreplace() gets log message from delta, not from curlogmsg. + * fixed expression overflow in while(c=putc(GETC.... + * checked nil printing. + * + * Revision 3.2 82/10/18 21:13:39 wft + * I added checks for write errors during the co process, and renamed + * expandstring() to xpandstring(). + * + * Revision 3.1 82/10/13 15:52:55 wft + * changed type of result of getc() from char to int. + * made keyword expansion loop in expandline() portable to machines + * without sign-extension. + */ + + +#include "rcsbase.h" + +libId(editId, "$Id: rcsedit.c,v 5.11 1991/11/03 01:11:44 eggert Exp $") + +static void keyreplace P((enum markers,struct hshentry const*,FILE*)); + + +FILE *fcopy; /* result file descriptor */ +char const *resultfile; /* result file name */ +int locker_expansion; /* should the locker name be appended to Id val? */ +#if !large_memory + static RILE *fedit; /* edit file descriptor */ + static char const *editfile; /* edit pathname */ +#endif +static unsigned long editline; /* edit line counter; #lines before cursor */ +static long linecorr; /* #adds - #deletes in each edit run. */ + /*used to correct editline in case file is not rewound after */ + /* applying one delta */ + +#define DIRTEMPNAMES 2 +enum maker {notmade, real, effective}; +struct buf dirtfname[DIRTEMPNAMES]; /* unlink these when done */ +static enum maker volatile dirtfmaker[DIRTEMPNAMES]; /* if these are set */ + + +#if has_NFS || bad_unlink + int +un_link(s) + char const *s; +/* + * Remove S, even if it is unwritable. + * Ignore unlink() ENOENT failures; NFS generates bogus ones. + */ +{ +# if bad_unlink + int e; + if (unlink(s) == 0) + return 0; + e = errno; +# if has_NFS + if (e == ENOENT) + return 0; +# endif + if (chmod(s, S_IWUSR) != 0) { + errno = e; + return -1; + } +# endif +# if has_NFS + return unlink(s)==0 || errno==ENOENT ? 0 : -1; +# else + return unlink(s); +# endif +} +#endif + +#if !has_rename +# if !has_NFS +# define do_link(s,t) link(s,t) +# else + static int +do_link(s, t) + char const *s, *t; +/* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */ +{ + struct stat sb, tb; + + if (link(s,t) == 0) + return 0; + if (errno != EEXIST) + return -1; + if ( + stat(s, &sb) == 0 && + stat(t, &tb) == 0 && + sb.st_ino == tb.st_ino && + sb.st_dev == tb.st_dev + ) + return 0; + errno = EEXIST; + return -1; +} +# endif +#endif + + + static exiting void +editEndsPrematurely() +{ + fatserror("edit script ends prematurely"); +} + + static exiting void +editLineNumberOverflow() +{ + fatserror("edit script refers to line past end of file"); +} + + +#if large_memory + +#if has_memmove +# define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type)) +#else + static void +movelines(s1, s2, n) + register Iptr_type *s1; + register Iptr_type const *s2; + register unsigned long n; +{ + if (s1 < s2) + do { + *s1++ = *s2++; + } while (--n); + else { + s1 += n; + s2 += n; + do { + *--s1 = *--s2; + } while (--n); + } +} +#endif + +/* + * `line' contains pointers to the lines in the currently `edited' file. + * It is a 0-origin array that represents linelim-gapsize lines. + * line[0..gap-1] and line[gap+gapsize..linelim-1] contain pointers to lines. + * line[gap..gap+gapsize-1] contains garbage. + * + * Any @s in lines are duplicated. + * Lines are terminated by \n, or (for a last partial line only) by single @. + */ +static Iptr_type *line; +static unsigned long gap, gapsize, linelim; + + + static void +insertline(n, l) + unsigned long n; + Iptr_type l; +/* Before line N, insert line L. N is 0-origin. */ +{ + if (linelim-gapsize < n) + editLineNumberOverflow(); + if (!gapsize) + line = + !linelim ? + tnalloc(Iptr_type, linelim = gapsize = 1024) + : ( + gap = gapsize = linelim, + trealloc(Iptr_type, line, linelim <<= 1) + ); + if (n < gap) + movelines(line+n+gapsize, line+n, gap-n); + else if (gap < n) + movelines(line+gap, line+gap+gapsize, n-gap); + + line[n] = l; + gap = n + 1; + gapsize--; +} + + static void +deletelines(n, nlines) + unsigned long n, nlines; +/* Delete lines N through N+NLINES-1. N is 0-origin. */ +{ + unsigned long l = n + nlines; + if (linelim-gapsize < l || l < n) + editLineNumberOverflow(); + if (l < gap) + movelines(line+l+gapsize, line+l, gap-l); + else if (gap < n) + movelines(line+gap, line+gap+gapsize, n-gap); + + gap = n; + gapsize += nlines; +} + + static void +snapshotline(f, l) + register FILE *f; + register Iptr_type l; +{ + register int c; + do { + if ((c = *l++) == SDELIM && *l++ != SDELIM) + return; + aputc(c, f); + } while (c != '\n'); +} + + void +snapshotedit(f) + FILE *f; +/* Copy the current state of the edits to F. */ +{ + register Iptr_type *p, *lim, *l=line; + for (p=l, lim=l+gap; pdate; + RCSv = RCSversion; + + if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND) + aprintf(out, "%c%s%c%c", KDELIM, sp, VDELIM, + marker==Log && RCSvauthor, out); + break; + case Date: + aputs(date2str(date,datebuf), out); + break; + case Id: + case Header: + aprintf(out, "%s %s %s %s %s", + marker==Id || RCSvnum, + date2str(date, datebuf), + delta->author, + RCSv==VERSION(3) && delta->lockedby ? "Locked" + : delta->state + ); + if (delta->lockedby!=nil) + if (VERSION(5) <= RCSv) { + if (locker_expansion || Expand==KEYVALLOCK_EXPAND) + aprintf(out, " %s", delta->lockedby); + } else if (RCSv == VERSION(4)) + aprintf(out, " Locker: %s", delta->lockedby); + break; + case Locker: + if (delta->lockedby) + if ( + locker_expansion + || Expand == KEYVALLOCK_EXPAND + || RCSv <= VERSION(4) + ) + aputs(delta->lockedby, out); + break; + case Log: + case RCSfile: + aputs(basename(RCSfilename), out); + break; + case Revision: + aputs(delta->num, out); + break; + case Source: + aputs(getfullRCSname(), out); + break; + case State: + aputs(delta->state, out); + break; + default: + break; + } + if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND) { + afputc(' ', out); + afputc(KDELIM, out); + } + if (marker == Log) { + sp = delta->log.string; + ls = delta->log.size; + if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1)) + return; + afputc('\n', out); + cp = Comment.string; + cw = cs = Comment.size; + awrite(cp, cs, out); + /* oddity: 2 spaces between date and time, not 1 as usual */ + sp1 = strchr(date2str(date,datebuf), ' '); + aprintf(out, "Revision %s %.*s %s %s", + delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author + ); + /* Do not include state: it may change and is not updated. */ + /* Comment is the comment leader. */ + if (VERSION(5) <= RCSv) + for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw) + ; + for (;;) { + afputc('\n', out); + awrite(cp, cw, out); + if (!ls) + break; + --ls; + c = *sp++; + if (c != '\n') { + awrite(cp+cw, cs-cw, out); + do { + afputc(c,out); + if (!ls) + break; + --ls; + c = *sp++; + } while (c != '\n'); + } + } + } +} + +#if has_readlink + static int +resolve_symlink(L) + struct buf *L; +/* + * If L is a symbolic link, resolve it to the name that it points to. + * If unsuccessful, set errno and yield -1. + * If it points to an existing file, yield 1. + * Otherwise, set errno=ENOENT and yield 0. + */ +{ + char *b, a[SIZEABLE_PATH]; + int e; + size_t s; + ssize_t r; + struct buf bigbuf; + unsigned linkcount = MAXSYMLINKS + 1; + + b = a; + s = sizeof(a); + bufautobegin(&bigbuf); + while ((r = readlink(L->string,b,s)) != -1) + if (r == s) { + bufalloc(&bigbuf, s<<1); + b = bigbuf.string; + s = bigbuf.size; + } else if (!--linkcount) { + errno = ELOOP; + return -1; + } else { + /* Splice symbolic link into L. */ + b[r] = '\0'; + L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)] = '\0'; + bufscat(L, b); + } + e = errno; + bufautoend(&bigbuf); + errno = e; + switch (e) { + case ENXIO: + case EINVAL: return 1; + case ENOENT: return 0; + default: return -1; + } +} +#endif + + RILE * +rcswriteopen(RCSbuf, status, mustread) + struct buf *RCSbuf; + struct stat *status; + int mustread; +/* + * Create the lock file corresponding to RCSNAME. + * Then try to open RCSNAME for reading and yield its FILE* descriptor. + * Put its status into *STATUS too. + * MUSTREAD is true if the file must already exist, too. + * If all goes well, discard any previously acquired locks, + * and set frewrite to the FILE* descriptor of the lock file, + * which will eventually turn into the new RCS file. + */ +{ + register char *tp; + register char const *sp, *RCSname, *x; + RILE *f; + size_t l; + int e, exists, fdesc, previouslock, r; + struct buf *dirt; + struct stat statbuf; + + previouslock = frewrite != 0; + exists = +# if has_readlink + resolve_symlink(RCSbuf); +# else + stat(RCSbuf->string, &statbuf) == 0 ? 1 + : errno==ENOENT ? 0 : -1; +# endif + if (exists < (mustread|previouslock)) + /* + * There's an unusual problem with the RCS file; + * or the RCS file doesn't exist, + * and we must read or we already have a lock elsewhere. + */ + return 0; + + RCSname = RCSbuf->string; + sp = basename(RCSname); + l = sp - RCSname; + dirt = &dirtfname[previouslock]; + bufscpy(dirt, RCSname); + tp = dirt->string + l; + x = rcssuffix(RCSname); +# if has_readlink + if (!x) { + error("symbolic link to non RCS filename `%s'", RCSname); + errno = EINVAL; + return 0; + } +# endif + if (*sp == *x) { + error("RCS filename `%s' incompatible with suffix `%s'", sp, x); + errno = EINVAL; + return 0; + } + /* Create a lock file whose name is a function of the RCS filename. */ + if (*x) { + /* + * The suffix is nonempty. + * The lock filename is the first char of of the suffix, + * followed by the RCS filename with last char removed. E.g.: + * foo,v RCS filename with suffix ,v + * ,foo, lock filename + */ + *tp++ = *x; + while (*sp) + *tp++ = *sp++; + *--tp = 0; + } else { + /* + * The suffix is empty. + * The lock filename is the RCS filename + * with last char replaced by '_'. + */ + while ((*tp++ = *sp++)) + ; + tp -= 2; + if (*tp == '_') { + error("RCS filename `%s' ends with `%c'", RCSname, *tp); + errno = EINVAL; + return 0; + } + *tp = '_'; + } + + sp = tp = dirt->string; + + f = 0; + + /* + * good news: + * open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) is atomic + * according to Posix 1003.1-1990. + * bad news: + * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990. + * good news: + * (O_TRUNC,READONLY) normally guarantees atomicity even with NFS. + * bad news: + * If you're root, (O_TRUNC,READONLY) doesn't guarantee atomicity. + * good news: + * Root-over-the-wire NFS access is rare for security reasons. + * This bug has never been reported in practice with RCS. + * So we don't worry about this bug. + * + * An even rarer NFS bug can occur when clients retry requests. + * Suppose client A renames the lock file ",f," to "f,v" + * at about the same time that client B creates ",f,", + * and suppose A's first rename request is delayed, so A reissues it. + * The sequence of events might be: + * A sends rename(",f,", "f,v") + * B sends create(",f,") + * A sends retry of rename(",f,", "f,v") + * server receives, does, and acknowledges A's first rename() + * A receives acknowledgment, and its RCS program exits + * server receives, does, and acknowledges B's create() + * server receives, does, and acknowledges A's retry of rename() + * This not only wrongly deletes B's lock, it removes the RCS file! + * Most NFS implementations have idempotency caches that usually prevent + * this scenario, but such caches are finite and can be overrun. + * This problem afflicts programs that use the traditional + * Unix method of using link() and unlink() to get and release locks, + * as well as RCS's method of using open() and rename(). + * There is no easy workaround for either link-unlink or open-rename. + * Any new method based on lockf() seemingly would be incompatible with + * the old methods; besides, lockf() is notoriously buggy under NFS. + * Since this problem afflicts scads of Unix programs, but is so rare + * that nobody seems to be worried about it, we won't worry either. + */ +# define READONLY (S_IRUSR|S_IRGRP|S_IROTH) +# if !open_can_creat +# define create(f) creat(f, READONLY) +# else +# define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) +# endif + + catchints(); + ignoreints(); + + /* + * Create a lock file for an RCS file. This should be atomic, i.e. + * if two processes try it simultaneously, at most one should succeed. + */ + seteid(); + fdesc = create(sp); + e = errno; + setrid(); + + if (fdesc < 0) { + if (e == EACCES && stat(tp,&statbuf) == 0) + /* The RCS file is busy. */ + e = EEXIST; + } else { + dirtfmaker[0] = effective; + e = ENOENT; + if (exists) { + f = Iopen(RCSname, FOPEN_R, status); + e = errno; + if (f && previouslock) { + /* Discard the previous lock in favor of this one. */ + Ozclose(&frewrite); + seteid(); + if ((r = un_link(newRCSfilename)) != 0) + e = errno; + setrid(); + if (r != 0) + enfaterror(e, newRCSfilename); + bufscpy(&dirtfname[0], tp); + } + } + if (!(frewrite = fdopen(fdesc, FOPEN_W))) { + efaterror(newRCSfilename); + } + } + + restoreints(); + + errno = e; + return f; +} + + void +keepdirtemp(name) + char const *name; +/* Do not unlink name, either because it's not there any more, + * or because it has already been unlinked. + */ +{ + register int i; + for (i=DIRTEMPNAMES; 0<=--i; ) + if (dirtfname[i].string == name) { + dirtfmaker[i] = notmade; + return; + } + faterror("keepdirtemp"); +} + + char const * +makedirtemp(name, n) + register char const *name; + int n; +/* + * Have maketemp() do all the work if name is null. + * Otherwise, create a unique filename in name's dir using n and name + * and store it into the dirtfname[n]. + * Because of storage in tfnames, dirtempunlink() can unlink the file later. + * Return a pointer to the filename created. + */ +{ + register char *tp, *np; + register size_t dl; + register struct buf *bn; + + if (!name) + return maketemp(n); + dl = dirlen(name); + bn = &dirtfname[n]; + bufalloc(bn, +# if has_mktemp + dl + 9 +# else + strlen(name) + 3 +# endif + ); + bufscpy(bn, name); + np = tp = bn->string; + tp += dl; + *tp++ = '_'; + *tp++ = '0'+n; + catchints(); +# if has_mktemp + VOID strcpy(tp, "XXXXXX"); + if (!mktemp(np) || !*np) + faterror("can't make temporary file name `%.*s%c_%cXXXXXX'", + (int)dl, name, SLASH, '0'+n + ); +# else + /* + * Posix 1003.1-1990 has no reliable way + * to create a unique file in a named directory. + * We fudge here. If the working file name is abcde, + * the temp filename is _Ncde where N is a digit. + */ + name += dl; + if (*name) name++; + if (*name) name++; + VOID strcpy(tp, name); +# endif + dirtfmaker[n] = real; + return np; +} + + void +dirtempunlink() +/* Clean up makedirtemp() files. May be invoked by signal handler. */ +{ + register int i; + enum maker m; + + for (i = DIRTEMPNAMES; 0 <= --i; ) + if ((m = dirtfmaker[i]) != notmade) { + if (m == effective) + seteid(); + VOID un_link(dirtfname[i].string); + if (m == effective) + setrid(); + dirtfmaker[i] = notmade; + } +} + + + int +#if has_prototypes +chnamemod(FILE **fromp, char const *from, char const *to, mode_t mode) + /* The `#if has_prototypes' is needed because mode_t might promote to int. */ +#else + chnamemod(fromp,from,to,mode) FILE **fromp; char const *from,*to; mode_t mode; +#endif +/* + * Rename a file (with optional stream pointer *FROMP) from FROM to TO. + * FROM already exists. + * Change its mode to MODE, before renaming if possible. + * If FROMP, close and clear *FROMP before renaming it. + * Unlink TO if it already exists. + * Return -1 on error (setting errno), 0 otherwise. + */ +{ +# if bad_a_rename + /* + * This host is brain damaged. A race condition is possible + * while the lock file is temporarily writable. + * There doesn't seem to be a workaround. + */ + mode_t mode_while_renaming = mode|S_IWUSR; +# else +# define mode_while_renaming mode +# endif + if (fromp) { +# if has_fchmod + if (fchmod(fileno(*fromp), mode_while_renaming) != 0) + return -1; +# endif + Ozclose(fromp); + } +# if has_fchmod + else +# endif + if (chmod(from, mode_while_renaming) != 0) + return -1; + +# if !has_rename || bad_b_rename + VOID un_link(to); + /* + * We need not check the result; + * link() or rename() will catch it. + * No harm is done if TO does not exist. + * However, there's a short window of inconsistency + * during which TO does not exist. + */ +# endif + + return +# if !has_rename + do_link(from,to) != 0 ? -1 : un_link(from) +# else + rename(from, to) != 0 +# if has_NFS + && errno != ENOENT +# endif + ? -1 +# if bad_a_rename + : mode != mode_while_renaming ? chmod(to, mode) +# endif + : 0 +# endif + ; + +# undef mode_while_renaming +} + + + + int +findlock(delete, target) + int delete; + struct hshentry **target; +/* + * Find the first lock held by caller and return a pointer + * to the locked delta; also removes the lock if DELETE. + * If one lock, put it into *TARGET. + * Return 0 for no locks, 1 for one, 2 for two or more. + */ +{ + register struct lock *next, **trail, **found; + + found = 0; + for (trail = &Locks; (next = *trail); trail = &next->nextlock) + if (strcmp(getcaller(), next->login) == 0) { + if (found) { + error("multiple revisions locked by %s; please specify one", getcaller()); + return 2; + } + found = trail; + } + if (!found) + return 0; + next = *found; + *target = next->delta; + if (delete) { + next->delta->lockedby = nil; + *found = next->nextlock; + } + return 1; +} + + int +addlock(delta) + struct hshentry * delta; +/* + * Add a lock held by caller to DELTA and yield 1 if successful. + * Print an error message and yield -1 if no lock is added because + * DELTA is locked by somebody other than caller. + * Return 0 if the caller already holds the lock. + */ +{ + register struct lock *next; + + next=Locks; + for (next = Locks; next; next = next->nextlock) + if (cmpnum(delta->num, next->delta->num) == 0) + if (strcmp(getcaller(), next->login) == 0) + return 0; + else { + error("revision %s already locked by %s", + delta->num, next->login + ); + return -1; + } + next = ftalloc(struct lock); + delta->lockedby = next->login = getcaller(); + next->delta = delta; + next->nextlock = Locks; + Locks = next; + return 1; +} + + + int +addsymbol(num, name, rebind) + char const *num, *name; + int rebind; +/* + * Associate with revision NUM the new symbolic NAME. + * If NAME already exists and REBIND is set, associate NAME with NUM; + * otherwise, print an error message and return false; + * Return true if successful. + */ +{ + register struct assoc *next; + + for (next = Symbols; next; next = next->nextassoc) + if (strcmp(name, next->symbol) == 0) + if (rebind || strcmp(next->num,num) == 0) { + next->num = num; + return true; + } else { + error("symbolic name %s already bound to %s", + name, next->num + ); + return false; + } + next = ftalloc(struct assoc); + next->symbol = name; + next->num = num; + next->nextassoc = Symbols; + Symbols = next; + return true; +} + + + + char const * +getcaller() +/* Get the caller's login name. */ +{ +# if has_setuid + return getusername(euid()!=ruid()); +# else + return getusername(false); +# endif +} + + + int +checkaccesslist() +/* + * Return true if caller is the superuser, the owner of the + * file, the access list is empty, or caller is on the access list. + * Otherwise, print an error message and return false. + */ +{ + register struct access const *next; + + if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0) + return true; + + next = AccessList; + do { + if (strcmp(getcaller(), next->login) == 0) + return true; + } while ((next = next->nextaccess)); + + error("user %s not on the access list", getcaller()); + return false; +} + + + int +dorewrite(lockflag, changed) + int lockflag, changed; +/* + * Do nothing if LOCKFLAG is zero. + * Prepare to rewrite an RCS file if CHANGED is positive. + * Stop rewriting if CHANGED is zero, because there won't be any changes. + * Fail if CHANGED is negative. + * Return true on success. + */ +{ + int r, e; + + if (lockflag) + if (changed) { + if (changed < 0) + return false; + putadmin(frewrite); + puttree(Head, frewrite); + aprintf(frewrite, "\n\n%s%c", Kdesc, nextc); + foutptr = frewrite; + } else { + Ozclose(&frewrite); + seteid(); + ignoreints(); + r = un_link(newRCSfilename); + e = errno; + keepdirtemp(newRCSfilename); + restoreints(); + setrid(); + if (r != 0) { + enerror(e, RCSfilename); + return false; + } + } + return true; +} + + int +donerewrite(changed) + int changed; +/* + * Finish rewriting an RCS file if CHANGED is nonzero. + * Return true on success. + */ +{ + int r, e; + + if (changed && !nerror) { + if (finptr) { + fastcopy(finptr, frewrite); + Izclose(&finptr); + } + if (1 < RCSstat.st_nlink) + warn("breaking hard link to %s", RCSfilename); + seteid(); + ignoreints(); + r = chnamemod(&frewrite, newRCSfilename, RCSfilename, + RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH) + ); + e = errno; + keepdirtemp(newRCSfilename); + restoreints(); + setrid(); + if (r != 0) { + enerror(e, RCSfilename); + error("saved in %s", newRCSfilename); + dirtempunlink(); + return false; + } + } + return true; +} + + void +aflush(f) + FILE *f; +{ + if (fflush(f) != 0) + Oerror(); +} diff --git a/gnu/usr.bin/rcs/lib/rcsfcmp.c b/gnu/usr.bin/rcs/lib/rcsfcmp.c new file mode 100644 index 0000000..75a6bbc --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsfcmp.c @@ -0,0 +1,321 @@ +/* + * RCS file comparison + */ +/***************************************************************************** + * rcsfcmp() + * Testprogram: define FCMPTEST + ***************************************************************************** + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + + + +/* $Log: rcsfcmp.c,v $ + * Revision 5.9 1991/10/07 17:32:46 eggert + * Count log lines correctly. + * + * Revision 5.8 1991/08/19 03:13:55 eggert + * Tune. + * + * Revision 5.7 1991/04/21 11:58:22 eggert + * Fix errno bug. Add MS-DOS support. + * + * Revision 5.6 1991/02/28 19:18:47 eggert + * Open work file at most once. + * + * Revision 5.5 1990/11/27 09:26:05 eggert + * Fix comment leader bug. + * + * Revision 5.4 1990/11/01 05:03:42 eggert + * Permit arbitrary data in logs and comment leaders. + * + * Revision 5.3 1990/09/11 02:41:15 eggert + * Don't ignore differences inside keyword strings if -ko is set. + * + * Revision 5.1 1990/08/29 07:13:58 eggert + * Clean old log messages too. + * + * Revision 5.0 1990/08/22 08:12:49 eggert + * Don't append "checked in with -k by " log to logs, + * so that checking in a program with -k doesn't change it. + * Ansify and Posixate. Remove lint. + * + * Revision 4.5 89/05/01 15:12:42 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.4 88/08/09 19:12:50 eggert + * Shrink stdio code size. + * + * Revision 4.3 87/12/18 11:40:02 narten + * lint cleanups (Guy Harris) + * + * Revision 4.2 87/10/18 10:33:06 narten + * updting version number. Changes relative to 1.1 actually relative to + * 4.1 + * + * Revision 1.2 87/03/27 14:22:19 jenkins + * Port to suns + * + * Revision 4.1 83/05/10 16:24:04 wft + * Marker matching now uses trymatch(). Marker pattern is now + * checked precisely. + * + * Revision 3.1 82/12/04 13:21:40 wft + * Initial revision. + * + */ + +/* +#define FCMPTEST +*/ +/* Testprogram; prints out whether two files are identical, + * except for keywords + */ + +#include "rcsbase.h" + +libId(fcmpId, "$Id: rcsfcmp.c,v 5.9 1991/10/07 17:32:46 eggert Exp $") + + static int +discardkeyval(c, f) + register int c; + register RILE *f; +{ + for (;;) + switch (c) { + case KDELIM: + case '\n': + return c; + default: + Igeteof(f, c, return EOF;); + break; + } +} + + int +rcsfcmp(xfp, xstatp, ufname, delta) + register RILE *xfp; + struct stat const *xstatp; + char const *ufname; + struct hshentry const *delta; +/* Compare the files xfp and ufname. Return zero + * if xfp has the same contents as ufname and neither has keywords, + * otherwise -1 if they are the same ignoring keyword values, + * and 1 if they differ even ignoring + * keyword values. For the LOG-keyword, rcsfcmp skips the log message + * given by the parameter delta in xfp. Thus, rcsfcmp returns nonpositive + * if xfp contains the same as ufname, with the keywords expanded. + * Implementation: character-by-character comparison until $ is found. + * If a $ is found, read in the marker keywords; if they are real keywords + * and identical, read in keyword value. If value is terminated properly, + * disregard it and optionally skip log message; otherwise, compare value. + */ +{ + register int xc, uc; + char xkeyword[keylength+2]; + int eqkeyvals; + register RILE *ufp; + register int xeof, ueof; + register char * tp; + register char const *sp; + int result; + enum markers match1; + struct stat ustat; + + if (!(ufp = Iopen(ufname, FOPEN_R_WORK, &ustat))) { + efaterror(ufname); + } + xeof = ueof = false; + if (Expand==OLD_EXPAND) { + if (!(result = xstatp->st_size!=ustat.st_size)) { +# if has_mmap && large_memory + result = !!memcmp(xfp->base,ufp->base,(size_t)xstatp->st_size); +# else + for (;;) { + /* get the next characters */ + Igeteof(xfp, xc, xeof=true;); + Igeteof(ufp, uc, ueof=true;); + if (xeof | ueof) + goto eof; + if (xc != uc) + goto return1; + } +# endif + } + } else { + xc = 0; + uc = 0; /* Keep lint happy. */ + result = 0; + + for (;;) { + if (xc != KDELIM) { + /* get the next characters */ + Igeteof(xfp, xc, xeof=true;); + Igeteof(ufp, uc, ueof=true;); + if (xeof | ueof) + goto eof; + } else { + /* try to get both keywords */ + tp = xkeyword; + for (;;) { + Igeteof(xfp, xc, xeof=true;); + Igeteof(ufp, uc, ueof=true;); + if (xeof | ueof) + goto eof; + if (xc != uc) + break; + switch (xc) { + default: + if (xkeyword+keylength <= tp) + break; + *tp++ = xc; + continue; + case '\n': case KDELIM: case VDELIM: + break; + } + break; + } + if ( + (xc==KDELIM || xc==VDELIM) && (uc==KDELIM || uc==VDELIM) && + (*tp = xc, (match1 = trymatch(xkeyword)) != Nomatch) + ) { +#ifdef FCMPTEST + VOID printf("found common keyword %s\n",xkeyword); +#endif + result = -1; + for (;;) { + if (xc != uc) { + xc = discardkeyval(xc, xfp); + uc = discardkeyval(uc, ufp); + if ((xeof = xc==EOF) | (ueof = uc==EOF)) + goto eof; + eqkeyvals = false; + break; + } + switch (xc) { + default: + Igeteof(xfp, xc, xeof=true;); + Igeteof(ufp, uc, ueof=true;); + if (xeof | ueof) + goto eof; + continue; + + case '\n': case KDELIM: + eqkeyvals = true; + break; + } + break; + } + if (xc != uc) + goto return1; + if (xc==KDELIM) { + /* Skip closing KDELIM. */ + Igeteof(xfp, xc, xeof=true;); + Igeteof(ufp, uc, ueof=true;); + if (xeof | ueof) + goto eof; + /* if the keyword is LOG, also skip the log message in xfp*/ + if (match1==Log) { + /* first, compute the number of line feeds in log msg */ + unsigned lncnt; + size_t ls, ccnt; + sp = delta->log.string; + ls = delta->log.size; + if (lssize < size) { + if (b->size) + tfree(b->string); + else + b->size = sizeof(malloc_type); + while (b->size < size) + b->size <<= 1; + b->string = tnalloc(char, b->size); + } +} + + void +bufrealloc(b, size) + register struct buf *b; + size_t size; +/* like bufalloc, except *B's old contents, if any, are preserved */ +{ + if (b->size < size) { + if (!b->size) + bufalloc(b, size); + else { + while ((b->size <<= 1) < size) + ; + b->string = trealloc(char, b->string, b->size); + } + } +} + + void +bufautoend(b) + struct buf *b; +/* Free an auto buffer at block exit. */ +{ + if (b->size) + tfree(b->string); +} + + struct cbuf +bufremember(b, s) + struct buf *b; + size_t s; +/* + * Free the buffer B with used size S. + * Yield a cbuf with identical contents. + * The cbuf will be reclaimed when this input file is finished. + */ +{ + struct cbuf cb; + + if ((cb.size = s)) + cb.string = fremember(trealloc(char, b->string, s)); + else { + bufautoend(b); /* not really auto */ + cb.string = ""; + } + return cb; +} + + char * +bufenlarge(b, alim) + register struct buf *b; + char const **alim; +/* Make *B larger. Set *ALIM to its new limit, and yield the relocated value + * of its old limit. + */ +{ + size_t s = b->size; + bufrealloc(b, s + 1); + *alim = b->string + b->size; + return b->string + s; +} + + void +bufscat(b, s) + struct buf *b; + char const *s; +/* Concatenate S to B's end. */ +{ + size_t blen = b->string ? strlen(b->string) : 0; + bufrealloc(b, blen+strlen(s)+1); + VOID strcpy(b->string+blen, s); +} + + void +bufscpy(b, s) + struct buf *b; + char const *s; +/* Copy S into B. */ +{ + bufalloc(b, strlen(s)+1); + VOID strcpy(b->string, s); +} + + + char const * +basename(p) + char const *p; +/* Yield the address of the base filename of the pathname P. */ +{ + register char const *b = p, *q = p; + for (;;) + switch (*q++) { + case SLASHes: b = q; break; + case 0: return b; + } +} + + size_t +dirlen(p) + char const *p; +/* Yield the length of P's directory, including its trailing SLASH. */ +{ + return basename(p) - p; +} + + + static size_t +suffixlen(x) + char const *x; +/* Yield the length of X, an RCS filename suffix. */ +{ + register char const *p; + + p = x; + for (;;) + switch (*p) { + case 0: case SLASHes: + return p - x; + + default: + ++p; + continue; + } +} + + char const * +rcssuffix(name) + char const *name; +/* Yield the suffix of NAME if it is an RCS filename, 0 otherwise. */ +{ + char const *x, *p, *nz; + size_t dl, nl, xl; + + nl = strlen(name); + nz = name + nl; + x = suffixes; + do { + if ((xl = suffixlen(x))) { + if (xl <= nl && memcmp(p = nz-xl, x, xl) == 0) + return p; + } else { + dl = dirlen(name); + if ( + rcsdirlen < dl && + !memcmp(p = name+(dl-=rcsdirlen+1), rcsdir, rcsdirlen) && + (!dl || isSLASH(*--p)) + ) + return nz; + } + x += xl; + } while (*x++); + return 0; +} + + /*ARGSUSED*/ RILE * +rcsreadopen(RCSname, status, mustread) + struct buf *RCSname; + struct stat *status; + int mustread; +/* Open RCSNAME for reading and yield its FILE* descriptor. + * If successful, set *STATUS to its status. + * Pass this routine to pairfilenames() for read-only access to the file. */ +{ + return Iopen(RCSname->string, FOPEN_R, status); +} + + static int +finopen(rcsopen, mustread) + RILE *(*rcsopen)P((struct buf*,struct stat*,int)); + int mustread; +/* + * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read. + * Set finptr to the result and yield true if successful. + * RCSb holds the file's name. + * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno. + * Yield true if successful or if an unusual failure. + */ +{ + int interesting, preferold; + + /* + * We prefer an old name to that of a nonexisting new RCS file, + * unless we tried locking the old name and failed. + */ + preferold = RCSbuf.string[0] && (mustread||frewrite); + + finptr = (*rcsopen)(&RCSb, &RCSstat, mustread); + interesting = finptr || errno!=ENOENT; + if (interesting || !preferold) { + /* Use the new name. */ + RCSerrno = errno; + bufscpy(&RCSbuf, RCSb.string); + } + return interesting; +} + + static int +fin2open(d, dlen, base, baselen, x, xlen, rcsopen, mustread) + char const *d, *base, *x; + size_t dlen, baselen, xlen; + RILE *(*rcsopen)P((struct buf*,struct stat*,int)); + int mustread; +/* + * D is a directory name with length DLEN (including trailing slash). + * BASE is a filename with length BASELEN. + * X is an RCS filename suffix with length XLEN. + * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read. + * Yield true if successful. + * Try dRCS/basex first; if that fails and x is nonempty, try dbasex. + * Put these potential names in RCSb. + * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno. + * Yield true if successful or if an unusual failure. + */ +{ + register char *p; + + bufalloc(&RCSb, dlen + rcsdirlen + 1 + baselen + xlen + 1); + + /* Try dRCS/basex. */ + VOID memcpy(p = RCSb.string, d, dlen); + VOID memcpy(p += dlen, rcsdir, rcsdirlen); + p += rcsdirlen; + *p++ = SLASH; + VOID memcpy(p, base, baselen); + VOID memcpy(p += baselen, x, xlen); + p[xlen] = 0; + if (xlen) { + if (finopen(rcsopen, mustread)) + return true; + + /* Try dbasex. */ + /* Start from scratch, because finopen() may have changed RCSb. */ + VOID memcpy(p = RCSb.string, d, dlen); + VOID memcpy(p += dlen, base, baselen); + VOID memcpy(p += baselen, x, xlen); + p[xlen] = 0; + } + return finopen(rcsopen, mustread); +} + + int +pairfilenames(argc, argv, rcsopen, mustread, quiet) + int argc; + char **argv; + RILE *(*rcsopen)P((struct buf*,struct stat*,int)); + int mustread, quiet; +/* Function: Pairs the filenames pointed to by argv; argc indicates + * how many there are. + * Places a pointer to the RCS filename into RCSfilename, + * and a pointer to the name of the working file into workfilename. + * If both the workfilename and the RCS filename are given, and workstdout + * is set, a warning is printed. + * + * If the RCS file exists, places its status into RCSstat. + * + * If the RCS file exists, it is RCSOPENed for reading, the file pointer + * is placed into finptr, and the admin-node is read in; returns 1. + * If the RCS file does not exist and MUSTREAD, + * print an error unless QUIET and return 0. + * Otherwise, initialize the admin node and return -1. + * + * 0 is returned on all errors, e.g. files that are not regular files. + */ +{ + static struct buf tempbuf; + + register char *p, *arg, *RCS1; + char const *purefname, *pureRCSname, *x; + int paired; + size_t arglen, dlen, baselen, xlen; + + if (!(arg = *argv)) return 0; /* already paired filename */ + if (*arg == '-') { + error("%s option is ignored after file names", arg); + return 0; + } + + purefname = basename(arg); + + /* Allocate buffer temporary to hold the default paired file name. */ + p = arg; + for (;;) { + switch (*p++) { + /* Beware characters that cause havoc with ci -k. */ + case KDELIM: + error("RCS file name `%s' contains %c", arg, KDELIM); + return 0; + case ' ': case '\n': case '\t': + error("RCS file name `%s' contains white space", arg); + return 0; + default: + continue; + case 0: + break; + } + break; + } + + paired = false; + + /* first check suffix to see whether it is an RCS file or not */ + if ((x = rcssuffix(arg))) + { + /* RCS file name given*/ + RCS1 = arg; + pureRCSname = purefname; + baselen = x - purefname; + if ( + 1 < argc && + !rcssuffix(workfilename = p = argv[1]) && + baselen <= (arglen = strlen(p)) && + ((p+=arglen-baselen) == workfilename || isSLASH(p[-1])) && + memcmp(purefname, p, baselen) == 0 + ) { + argv[1] = 0; + paired = true; + } else { + bufscpy(&tempbuf, purefname); + workfilename = p = tempbuf.string; + p[baselen] = 0; + } + } else { + /* working file given; now try to find RCS file */ + workfilename = arg; + baselen = p - purefname - 1; + /* derive RCS file name*/ + if ( + 1 < argc && + (x = rcssuffix(RCS1 = argv[1])) && + baselen <= x - RCS1 && + ((pureRCSname=x-baselen)==RCS1 || isSLASH(pureRCSname[-1])) && + memcmp(purefname, pureRCSname, baselen) == 0 + ) { + argv[1] = 0; + paired = true; + } else + pureRCSname = RCS1 = 0; + } + /* now we have a (tentative) RCS filename in RCS1 and workfilename */ + /* Second, try to find the right RCS file */ + if (pureRCSname!=RCS1) { + /* a path for RCSfile is given; single RCS file to look for */ + bufscpy(&RCSbuf, RCS1); + finptr = (*rcsopen)(&RCSbuf, &RCSstat, mustread); + RCSerrno = errno; + } else { + bufscpy(&RCSbuf, ""); + if (RCS1) + /* RCS file name was given without path. */ + VOID fin2open(arg, (size_t)0, pureRCSname, baselen, + x, strlen(x), rcsopen, mustread + ); + else { + /* No RCS file name was given. */ + /* Try each suffix in turn. */ + dlen = purefname-arg; + x = suffixes; + while (! fin2open(arg, dlen, purefname, baselen, + x, xlen=suffixlen(x), rcsopen, mustread + )) { + x += xlen; + if (!*x++) + break; + } + } + } + RCSfilename = p = RCSbuf.string; + if (finptr) { + if (!S_ISREG(RCSstat.st_mode)) { + error("%s isn't a regular file -- ignored", p); + return 0; + } + Lexinit(); getadmin(); + } else { + if (RCSerrno!=ENOENT || mustread || !frewrite) { + if (RCSerrno == EEXIST) + error("RCS file %s is in use", p); + else if (!quiet || RCSerrno!=ENOENT) + enerror(RCSerrno, p); + return 0; + } + InitAdmin(); + }; +# if LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED + if (filenametoolong(p)) { + error("RCS file name %s is too long", p); + return 0; + } +# ifndef NAME_MAX + /* + * Check workfilename too, even though it cannot be longer, + * because it may reside on a different filesystem. + */ + if (filenametoolong(workfilename)) { + error("working file name %s is too long", workfilename); + return 0; + } +# endif +# endif + + if (paired && workstdout) + warn("Option -p is set; ignoring output file %s",workfilename); + + prevkeys = false; + return finptr ? 1 : -1; +} + + + char const * +getfullRCSname() +/* Function: returns a pointer to the full path name of the RCS file. + * Gets the working directory's name at most once. + * Removes leading "../" and "./". + */ +{ + static char const *wdptr; + static struct buf rcsbuf, wdbuf; + static size_t pathlength; + + register char const *realname; + register size_t parentdirlength; + register unsigned dotdotcounter; + register char *d; + register char const *wd; + + if (ROOTPATH(RCSfilename)) { + return(RCSfilename); + } else { + if (!(wd = wdptr)) { + /* Get working directory for the first time. */ + if (!(d = cgetenv("PWD"))) { + bufalloc(&wdbuf, SIZEABLE_PATH + 1); +# if !has_getcwd && has_getwd + d = getwd(wdbuf.string); +# else + while ( + !(d = getcwd(wdbuf.string, wdbuf.size)) + && errno==ERANGE + ) + bufalloc(&wdbuf, wdbuf.size<<1); +# endif + if (!d) + efaterror("working directory"); + } + parentdirlength = strlen(d); + while (parentdirlength && isSLASH(d[parentdirlength-1])) { + d[--parentdirlength] = 0; + /* Check needed because some getwd implementations */ + /* generate "/" for the root. */ + } + wdptr = wd = d; + pathlength = parentdirlength; + } + /*the following must be redone since RCSfilename may change*/ + /* Find how many `../'s to remove from RCSfilename. */ + dotdotcounter =0; + realname = RCSfilename; + while (realname[0]=='.') { + if (isSLASH(realname[1])) { + /* drop leading ./ */ + realname += 2; + } else if (realname[1]=='.' && isSLASH(realname[2])) { + /* drop leading ../ and remember */ + dotdotcounter++; + realname += 3; + } else + break; + } + /* Now remove dotdotcounter trailing directories from wd. */ + parentdirlength = pathlength; + while (dotdotcounter && parentdirlength) { + /* move pointer backwards over trailing directory */ + if (isSLASH(wd[--parentdirlength])) { + dotdotcounter--; + } + } + /* build full path name */ + bufalloc(&rcsbuf, parentdirlength+strlen(realname)+2); + d = rcsbuf.string; + VOID memcpy(d, wd, parentdirlength); + d += parentdirlength; + *d++ = SLASH; + VOID strcpy(d, realname); + return rcsbuf.string; + } +} + +#ifndef isSLASH + int +isSLASH(c) + int c; +{ + switch (c) { + case SLASHes: + return true; + default: + return false; + } +} +#endif + + +#if !has_getcwd && !has_getwd + + char * +getcwd(path, size) + char *path; + size_t size; +{ + static char const usrbinpwd[] = "/usr/bin/pwd"; +# define binpwd (usrbinpwd+4) + + register FILE *fp; + register int c; + register char *p, *lim; + int closeerrno, closeerror, e, fd[2], readerror, toolong, wstatus; + pid_t child; +# if !has_waitpid + pid_t w; +# endif + + if (!size) { + errno = EINVAL; + return 0; + } + if (pipe(fd) != 0) + return 0; + if (!(child = vfork())) { + if ( + close(fd[0]) == 0 && + (fd[1] == STDOUT_FILENO || +# ifdef F_DUPFD + (VOID close(STDOUT_FILENO), + fcntl(fd[1], F_DUPFD, STDOUT_FILENO)) +# else + dup2(fd[1], STDOUT_FILENO) +# endif + == STDOUT_FILENO && + close(fd[1]) == 0 + ) + ) { + VOID close(STDERR_FILENO); + VOID execl(binpwd, binpwd, (char *)0); + VOID execl(usrbinpwd, usrbinpwd, (char *)0); + } + _exit(EXIT_FAILURE); + } + e = errno; + closeerror = close(fd[1]); + closeerrno = errno; + fp = 0; + readerror = toolong = wstatus = 0; + p = path; + if (0 <= child) { + fp = fdopen(fd[0], "r"); + e = errno; + if (fp) { + lim = p + size; + for (p = path; ; *p++ = c) { + if ((c=getc(fp)) < 0) { + if (feof(fp)) + break; + if (ferror(fp)) { + readerror = 1; + e = errno; + break; + } + } + if (p == lim) { + toolong = 1; + break; + } + } + } +# if has_waitpid + if (waitpid(child, &wstatus, 0) < 0) + wstatus = 1; +# else + do { + if ((w = wait(&wstatus)) < 0) { + wstatus = 1; + break; + } + } while (w != child); +# endif + } + if (!fp) { + VOID close(fd[0]); + errno = e; + return 0; + } + if (fclose(fp) != 0) + return 0; + if (readerror) { + errno = e; + return 0; + } + if (closeerror) { + errno = closeerrno; + return 0; + } + if (toolong) { + errno = ERANGE; + return 0; + } + if (wstatus || p == path || *--p != '\n') { + errno = EACCES; + return 0; + } + *p = '\0'; + return path; +} +#endif + + +#ifdef PAIRTEST +/* test program for pairfilenames() and getfullRCSname() */ + +char const cmdid[] = "pair"; + +main(argc, argv) +int argc; char *argv[]; +{ + int result; + int initflag; + quietflag = initflag = false; + + while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) { + switch ((*argv)[1]) { + + case 'p': workstdout = stdout; + break; + case 'i': initflag=true; + break; + case 'q': quietflag=true; + break; + default: error("unknown option: %s", *argv); + break; + } + } + + do { + RCSfilename=workfilename=nil; + result = pairfilenames(argc,argv,rcsreadopen,!initflag,quietflag); + if (result!=0) { + diagnose("RCS file: %s; working file: %s\nFull RCS file name: %s\n", + RCSfilename,workfilename,getfullRCSname() + ); + } + switch (result) { + case 0: continue; /* already paired file */ + + case 1: if (initflag) { + error("RCS file %s exists already",RCSfilename); + } else { + diagnose("RCS file %s exists\n",RCSfilename); + } + Ifclose(finptr); + break; + + case -1:diagnose("RCS file doesn't exist\n"); + break; + } + + } while (++argv, --argc>=1); + +} + + exiting void +exiterr() +{ + dirtempunlink(); + tempunlink(); + _exit(EXIT_FAILURE); +} +#endif diff --git a/gnu/usr.bin/rcs/lib/rcsgen.c b/gnu/usr.bin/rcs/lib/rcsgen.c new file mode 100644 index 0000000..9a6072e --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsgen.c @@ -0,0 +1,432 @@ +/* + * RCS revision generation + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + +/* $Log: rcsgen.c,v $ + * Revision 5.10 1991/10/07 17:32:46 eggert + * Fix log bugs, e.g. ci -t/dev/null when has_mmap. + * + * Revision 5.9 1991/09/10 22:15:46 eggert + * Fix test for redirected stdin. + * + * Revision 5.8 1991/08/19 03:13:55 eggert + * Add piece tables. Tune. + * + * Revision 5.7 1991/04/21 11:58:24 eggert + * Add MS-DOS support. + * + * Revision 5.6 1990/12/27 19:54:26 eggert + * Fix bug: rcs -t inserted \n, making RCS file grow. + * + * Revision 5.5 1990/12/04 05:18:45 eggert + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.4 1990/11/01 05:03:47 eggert + * Add -I and new -t behavior. Permit arbitrary data in logs. + * + * Revision 5.3 1990/09/21 06:12:43 hammer + * made putdesc() treat stdin the same whether or not it was from a terminal + * by making it recognize that a single '.' was then end of the + * description always + * + * Revision 5.2 1990/09/04 08:02:25 eggert + * Fix `co -p1.1 -ko' bug. Standardize yes-or-no procedure. + * + * Revision 5.1 1990/08/29 07:14:01 eggert + * Clean old log messages too. + * + * Revision 5.0 1990/08/22 08:12:52 eggert + * Remove compile-time limits; use malloc instead. + * Ansify and Posixate. + * + * Revision 4.7 89/05/01 15:12:49 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.6 88/08/28 14:59:10 eggert + * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin() + * + * Revision 4.5 87/12/18 11:43:25 narten + * additional lint cleanups, and a bug fix from the 4.3BSD version that + * keeps "ci" from sticking a '\377' into the description if you run it + * with a zero-length file as the description. (Guy Harris) + * + * Revision 4.4 87/10/18 10:35:10 narten + * Updating version numbers. Changes relative to 1.1 actually relative to + * 4.2 + * + * Revision 1.3 87/09/24 13:59:51 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:27 jenkins + * Port to suns + * + * Revision 4.2 83/12/02 23:01:39 wft + * merged 4.1 and 3.3.1.1 (clearerr(stdin)). + * + * Revision 4.1 83/05/10 16:03:33 wft + * Changed putamin() to abort if trying to reread redirected stdin. + * Fixed getdesc() to output a prompt on initial newline. + * + * Revision 3.3.1.1 83/10/19 04:21:51 lepreau + * Added clearerr(stdin) for re-reading description from stdin. + * + * Revision 3.3 82/11/28 21:36:49 wft + * 4.2 prerelease + * + * Revision 3.3 82/11/28 21:36:49 wft + * Replaced ferror() followed by fclose() with ffclose(). + * Putdesc() now suppresses the prompts if stdin + * is not a terminal. A pointer to the current log message is now + * inserted into the corresponding delta, rather than leaving it in a + * global variable. + * + * Revision 3.2 82/10/18 21:11:26 wft + * I added checks for write errors during editing, and improved + * the prompt on putdesc(). + * + * Revision 3.1 82/10/13 15:55:09 wft + * corrected type of variables assigned to by getc (char --> int) + */ + + + + +#include "rcsbase.h" + +libId(genId, "$Id: rcsgen.c,v 5.10 1991/10/07 17:32:46 eggert Exp $") + +int interactiveflag; /* Should we act as if stdin is a tty? */ +struct buf curlogbuf; /* buffer for current log message */ + +enum stringwork { enter, copy, edit, expand, edit_expand }; +static void scandeltatext P((struct hshentry*,enum stringwork,int)); + + + + + char const * +buildrevision(deltas, target, outfile, expandflag) + struct hshentries const *deltas; + struct hshentry *target; + FILE *outfile; + int expandflag; +/* Function: Generates the revision given by target + * by retrieving all deltas given by parameter deltas and combining them. + * If outfile is set, the revision is output to it, + * otherwise written into a temporary file. + * Temporary files are allocated by maketemp(). + * if expandflag is set, keyword expansion is performed. + * Return nil if outfile is set, the name of the temporary file otherwise. + * + * Algorithm: Copy initial revision unchanged. Then edit all revisions but + * the last one into it, alternating input and output files (resultfile and + * editfile). The last revision is then edited in, performing simultaneous + * keyword substitution (this saves one extra pass). + * All this simplifies if only one revision needs to be generated, + * or no keyword expansion is necessary, or if output goes to stdout. + */ +{ + if (deltas->first == target) { + /* only latest revision to generate */ + openfcopy(outfile); + scandeltatext(target, expandflag?expand:copy, true); + if (outfile) + return 0; + else { + Ozclose(&fcopy); + return(resultfile); + } + } else { + /* several revisions to generate */ + /* Get initial revision without keyword expansion. */ + scandeltatext(deltas->first, enter, false); + while ((deltas=deltas->rest)->rest) { + /* do all deltas except last one */ + scandeltatext(deltas->first, edit, false); + } + if (expandflag || outfile) { + /* first, get to beginning of file*/ + finishedit((struct hshentry *)nil, outfile, false); + } + scandeltatext(deltas->first, expandflag?edit_expand:edit, true); + finishedit( + expandflag ? deltas->first : (struct hshentry*)nil, + outfile, true + ); + if (outfile) + return 0; + Ozclose(&fcopy); + return resultfile; + } +} + + + + static void +scandeltatext(delta, func, needlog) + struct hshentry * delta; + enum stringwork func; + int needlog; +/* Function: Scans delta text nodes up to and including the one given + * by delta. For the one given by delta, the log message is saved into + * delta->log if needlog is set; func specifies how to handle the text. + * Assumes the initial lexeme must be read in first. + * Does not advance nexttok after it is finished. + */ +{ + struct hshentry const *nextdelta; + struct cbuf cb; + + for (;;) { + if (eoflex()) + fatserror("can't find delta for revision %s", delta->num); + nextlex(); + if (!(nextdelta=getnum())) { + fatserror("delta number corrupted"); + } + getkeystring(Klog); + if (needlog && delta==nextdelta) { + cb = savestring(&curlogbuf); + delta->log = cleanlogmsg(curlogbuf.string, cb.size); + } else {readstring(); + } + nextlex(); + while (nexttok==ID && strcmp(NextString,Ktext)!=0) + ignorephrase(); + getkeystring(Ktext); + + if (delta==nextdelta) + break; + readstring(); /* skip over it */ + + } + switch (func) { + case enter: enterstring(); break; + case copy: copystring(); break; + case expand: xpandstring(delta); break; + case edit: editstring((struct hshentry *)nil); break; + case edit_expand: editstring(delta); break; + } +} + + struct cbuf +cleanlogmsg(m, s) + char *m; + size_t s; +{ + register char *t = m; + register char const *f = t; + struct cbuf r; + while (s) { + --s; + if ((*t++ = *f++) == '\n') + while (m < --t) + if (t[-1]!=' ' && t[-1]!='\t') { + *t++ = '\n'; + break; + } + } + while (m < t && (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n')) + --t; + r.string = m; + r.size = t - m; + return r; +} + + +int ttystdin() +{ + static int initialized; + if (!initialized) { + if (!interactiveflag) + interactiveflag = isatty(STDIN_FILENO); + initialized = true; + } + return interactiveflag; +} + + int +getcstdin() +{ + register FILE *in; + register int c; + + in = stdin; + if (feof(in) && ttystdin()) + clearerr(in); + c = getc(in); + if (c < 0) { + testIerror(in); + if (feof(in) && ttystdin()) + afputc('\n',stderr); + } + return c; +} + +#if has_prototypes + int +yesorno(int default_answer, char const *question, ...) +#else + /*VARARGS2*/ int + yesorno(default_answer, question, va_alist) + int default_answer; char const *question; va_dcl +#endif +{ + va_list args; + register int c, r; + if (!quietflag && ttystdin()) { + oflush(); + vararg_start(args, question); + fvfprintf(stderr, question, args); + va_end(args); + eflush(); + r = c = getcstdin(); + while (c!='\n' && !feof(stdin)) + c = getcstdin(); + if (r=='y' || r=='Y') + return true; + if (r=='n' || r=='N') + return false; + } + return default_answer; +} + + + void +putdesc(textflag, textfile) + int textflag; + char *textfile; +/* Function: puts the descriptive text into file frewrite. + * if finptr && !textflag, the text is copied from the old description. + * Otherwise, if the textfile!=nil, the text is read from that + * file, or from stdin, if textfile==nil. + * A textfile with a leading '-' is treated as a string, not a file name. + * If finptr, the old descriptive text is discarded. + * Always clears foutptr. + */ +{ + static struct buf desc; + static struct cbuf desclean; + + register FILE *txt; + register int c; + register FILE * frew; + register char *p; + register size_t s; + char const *plim; + + frew = frewrite; + if (finptr && !textflag) { + /* copy old description */ + aprintf(frew, "\n\n%s%c", Kdesc, nextc); + foutptr = frewrite; + getdesc(false); + foutptr = 0; + } else { + foutptr = 0; + /* get new description */ + if (finptr) { + /*skip old description*/ + getdesc(false); + } + aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM); + if (!textfile) + desclean = getsstdin( + "t-", "description", + "NOTE: This is NOT the log message!\n", &desc + ); + else if (!desclean.string) { + if (*textfile == '-') { + p = textfile + 1; + s = strlen(p); + } else { + if (!(txt = fopen(textfile, "r"))) + efaterror(textfile); + bufalloc(&desc, 1); + p = desc.string; + plim = p + desc.size; + for (;;) { + if ((c=getc(txt)) < 0) { + testIerror(txt); + if (feof(txt)) + break; + } + if (plim <= p) + p = bufenlarge(&desc, &plim); + *p++ = c; + } + if (fclose(txt) != 0) + Ierror(); + s = p - desc.string; + p = desc.string; + } + desclean = cleanlogmsg(p, s); + } + putstring(frew, false, desclean, true); + aputc('\n', frew); + } +} + + struct cbuf +getsstdin(option, name, note, buf) + char const *option, *name, *note; + struct buf *buf; +{ + register int c; + register char *p; + register size_t i; + register int tty = ttystdin(); + + if (tty) + aprintf(stderr, + "enter %s, terminated with single '.' or end of file:\n%s>> ", + name, note + ); + else if (feof(stdin)) + faterror("can't reread redirected stdin for %s; use -%s<%s>", + name, option, name + ); + + for ( + i = 0, p = 0; + c = getcstdin(), !feof(stdin); + bufrealloc(buf, i+1), p = buf->string, p[i++] = c + ) + if (c == '\n') + if (i && p[i-1]=='.' && (i==1 || p[i-2]=='\n')) { + /* Remove trailing '.'. */ + --i; + break; + } else if (tty) + aputs(">> ", stderr); + return cleanlogmsg(p, i); +} diff --git a/gnu/usr.bin/rcs/lib/rcskeep.c b/gnu/usr.bin/rcs/lib/rcskeep.c new file mode 100644 index 0000000..1a0c78f --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcskeep.c @@ -0,0 +1,422 @@ +/* + * RCS keyword extraction + */ +/***************************************************************************** + * main routine: getoldkeys() + * Testprogram: define KEEPTEST + ***************************************************************************** + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + +/* $Log: rcskeep.c,v $ + * Revision 5.4 1991/08/19 03:13:55 eggert + * Tune. + * + * Revision 5.3 1991/04/21 11:58:25 eggert + * Shorten names to keep them distinct on shortname hosts. + * + * Revision 5.2 1990/10/04 06:30:20 eggert + * Parse time zone offsets; future RCS versions may output them. + * + * Revision 5.1 1990/09/20 02:38:56 eggert + * ci -k now checks dates more thoroughly. + * + * Revision 5.0 1990/08/22 08:12:53 eggert + * Retrieve old log message if there is one. + * Don't require final newline. + * Remove compile-time limits; use malloc instead. Tune. + * Permit dates past 1999/12/31. Ansify and Posixate. + * + * Revision 4.6 89/05/01 15:12:56 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.5 88/08/09 19:13:03 eggert + * Remove lint and speed up by making FILE *fp local, not global. + * + * Revision 4.4 87/12/18 11:44:21 narten + * more lint cleanups (Guy Harris) + * + * Revision 4.3 87/10/18 10:35:50 narten + * Updating version numbers. Changes relative to 1.1 actually relative + * to 4.1 + * + * Revision 1.3 87/09/24 14:00:00 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:29 jenkins + * Port to suns + * + * Revision 4.1 83/05/10 16:26:44 wft + * Added new markers Id and RCSfile; extraction added. + * Marker matching with trymatch(). + * + * Revision 3.2 82/12/24 12:08:26 wft + * added missing #endif. + * + * Revision 3.1 82/12/04 13:22:41 wft + * Initial revision. + * + */ + +/* +#define KEEPTEST +*/ +/* Testprogram; prints out the keyword values found. */ + +#include "rcsbase.h" + +libId(keepId, "$Id: rcskeep.c,v 5.4 1991/08/19 03:13:55 eggert Exp $") + +static int checknum P((char const*,int)); +static int getval P((RILE*,struct buf*,int)); +static int get0val P((int,RILE*,struct buf*,int)); +static int keepdate P((RILE*)); +static int keepid P((int,RILE*,struct buf*)); +static int keeprev P((RILE*)); + +int prevkeys; +struct buf prevauthor, prevdate, prevrev, prevstate; + + int +getoldkeys(fp) + register RILE *fp; +/* Function: Tries to read keyword values for author, date, + * revision number, and state out of the file fp. + * If FNAME is nonnull, it is opened and closed instead of using FP. + * The results are placed into + * prevauthor, prevdate, prevrev, prevstate. + * Aborts immediately if it finds an error and returns false. + * If it returns true, it doesn't mean that any of the + * values were found; instead, check to see whether the corresponding arrays + * contain the empty string. + */ +{ + register int c; + char keyword[keylength+1]; + register char * tp; + int needs_closing; + + if (prevkeys) + return true; + + needs_closing = false; + if (!fp) { + if (!(fp = Iopen(workfilename, FOPEN_R_WORK, (struct stat*)0))) { + eerror(workfilename); + return false; + } + needs_closing = true; + } + + /* initialize to empty */ + bufscpy(&prevauthor, ""); + bufscpy(&prevdate, ""); + bufscpy(&prevrev, ""); + bufscpy(&prevstate, ""); + + c = '\0'; /* anything but KDELIM */ + for (;;) { + if ( c==KDELIM) { + do { + /* try to get keyword */ + tp = keyword; + for (;;) { + Igeteof(fp, c, goto ok;); + switch (c) { + default: + if (keyword+keylength <= tp) + break; + *tp++ = c; + continue; + + case '\n': case KDELIM: case VDELIM: + break; + } + break; + } + } while (c==KDELIM); + if (c!=VDELIM) continue; + *tp = c; + Igeteof(fp, c, break;); + switch (c) { + case ' ': case '\t': break; + default: continue; + } + + switch (trymatch(keyword)) { + case Author: + if (!keepid(0, fp, &prevauthor)) + return false; + c = 0; + break; + case Date: + if (!(c = keepdate(fp))) + return false; + break; + case Header: + case Id: + if (!( + getval(fp, (struct buf*)nil, false) && + keeprev(fp) && + (c = keepdate(fp)) && + keepid(c, fp, &prevauthor) && + keepid(0, fp, &prevstate) + )) + return false; + /* Skip either ``who'' (new form) or ``Locker: who'' (old). */ + if (getval(fp, (struct buf*)nil, true) && + getval(fp, (struct buf*)nil, true)) + c = 0; + else if (nerror) + return false; + else + c = KDELIM; + break; + case Locker: + case Log: + case RCSfile: + case Source: + if (!getval(fp, (struct buf*)nil, false)) + return false; + c = 0; + break; + case Revision: + if (!keeprev(fp)) + return false; + c = 0; + break; + case State: + if (!keepid(0, fp, &prevstate)) + return false; + c = 0; + break; + default: + continue; + } + if (!c) + Igeteof(fp, c, c=0;); + if (c != KDELIM) { + error("closing %c missing on keyword", KDELIM); + return false; + } + if (*prevauthor.string && *prevdate.string && *prevrev.string && *prevstate.string) { + break; + } + } + Igeteof(fp, c, break;); + } + + ok: + if (needs_closing) + Ifclose(fp); + else + Irewind(fp); + prevkeys = true; + return true; +} + + static int +badly_terminated() +{ + error("badly terminated keyword value"); + return false; +} + + static int +getval(fp, target, optional) + register RILE *fp; + struct buf *target; + int optional; +/* Reads a keyword value from FP into TARGET. + * Returns true if one is found, false otherwise. + * Does not modify target if it is nil. + * Do not report an error if OPTIONAL is set and KDELIM is found instead. + */ +{ + int c; + Igeteof(fp, c, return badly_terminated();); + return get0val(c, fp, target, optional); +} + + static int +get0val(c, fp, target, optional) + register int c; + register RILE *fp; + struct buf *target; + int optional; +/* Reads a keyword value from C+FP into TARGET, perhaps OPTIONALly. + * Same as getval, except C is the lookahead character. + */ +{ register char * tp; + char const *tlim; + register int got1; + + if (target) { + bufalloc(target, 1); + tp = target->string; + tlim = tp + target->size; + } else + tlim = tp = 0; + got1 = false; + for (;;) { + switch (c) { + default: + got1 = true; + if (tp) { + *tp++ = c; + if (tlim <= tp) + tp = bufenlarge(target, &tlim); + } + break; + + case ' ': + case '\t': + if (tp) { + *tp = 0; +# ifdef KEEPTEST + VOID printf("getval: %s\n", target); +# endif + } + if (!got1) + error("too much white space in keyword value"); + return got1; + + case KDELIM: + if (!got1 && optional) + return false; + /* fall into */ + case '\n': + case 0: + return badly_terminated(); + } + Igeteof(fp, c, return badly_terminated();); + } +} + + + static int +keepdate(fp) + RILE *fp; +/* Function: reads a date prevdate; checks format + * Return 0 on error, lookahead character otherwise. + */ +{ + struct buf prevday, prevtime, prevzone; + register char const *p; + register int c; + + c = 0; + bufautobegin(&prevday); + if (getval(fp,&prevday,false)) { + bufautobegin(&prevtime); + if (getval(fp,&prevtime,false)) { + bufautobegin(&prevzone); + bufscpy(&prevzone, ""); + Igeteof(fp, c, c=0;); + if (c=='-' || c=='+') + if (!get0val(c,fp,&prevzone,false)) + c = 0; + else + Igeteof(fp, c, c=0;); + if (c) { + p = prevday.string; + bufalloc(&prevdate, strlen(p) + strlen(prevtime.string) + strlen(prevzone.string) + 5); + VOID sprintf(prevdate.string, "%s%s %s %s", + /* Parse dates put out by old versions of RCS. */ + isdigit(p[0]) && isdigit(p[1]) && p[2]=='/' ? "19" : "", + p, prevtime.string, prevzone.string + ); + } + bufautoend(&prevzone); + } + bufautoend(&prevtime); + } + bufautoend(&prevday); + return c; +} + + static int +keepid(c, fp, b) + int c; + RILE *fp; + struct buf *b; +/* Get previous identifier from C+FP into B. */ +{ + if (!c) + Igeteof(fp, c, return false;); + if (!get0val(c, fp, b, false)) + return false; + checksid(b->string); + return true; +} + + static int +keeprev(fp) + RILE *fp; +/* Get previous revision from FP into prevrev. */ +{ + return getval(fp,&prevrev,false) && checknum(prevrev.string,-1); +} + + + static int +checknum(sp,fields) + register char const *sp; + int fields; +{ register int dotcount; + dotcount=0; + while(*sp) { + if (*sp=='.') dotcount++; + else if (!isdigit(*sp)) return false; + sp++; + } + return fields<0 ? dotcount&1 : dotcount==fields; +} + + + +#ifdef KEEPTEST + +char const cmdid[] ="keeptest"; + + int +main(argc, argv) +int argc; char *argv[]; +{ + while (*(++argv)) { + workfilename = *argv; + getoldkeys((RILE*)0); + VOID printf("%s: revision: %s, date: %s, author: %s, state: %s\n", + *argv, prevrev.string, prevdate.string, prevauthor.string, prevstate.string); + } + exitmain(EXIT_SUCCESS); +} +#endif diff --git a/gnu/usr.bin/rcs/lib/rcskeys.c b/gnu/usr.bin/rcs/lib/rcskeys.c new file mode 100644 index 0000000..82850a7 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcskeys.c @@ -0,0 +1,102 @@ +/* + * RCS keyword table and match operation + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + +/* $Log: rcskeys.c,v $ + * Revision 5.2 1991/08/19 03:13:55 eggert + * Say `T const' instead of `const T'; it's less confusing for pointer types. + * (This change was made in other source files too.) + * + * Revision 5.1 1991/04/21 11:58:25 eggert + * Don't put , just before } in initializer. + * + * Revision 5.0 1990/08/22 08:12:54 eggert + * Add -k. Ansify and Posixate. + * + * Revision 4.3 89/05/01 15:13:02 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.2 87/10/18 10:36:33 narten + * Updating version numbers. Changes relative to 1.1 actuallyt + * relative to 4.1 + * + * Revision 1.2 87/09/24 14:00:10 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 4.1 83/05/04 10:06:53 wft + * Initial revision. + * + */ + + +#include "rcsbase.h" + +libId(keysId, "$Id: rcskeys.c,v 5.2 1991/08/19 03:13:55 eggert Exp $") + + +char const *const Keyword[] = { + /* This must be in the same order as rcsbase.h's enum markers type. */ + nil, + AUTHOR, DATE, HEADER, IDH, + LOCKER, LOG, RCSFILE, REVISION, SOURCE, STATE +}; + + + + enum markers +trymatch(string) + char const *string; +/* function: Checks whether string starts with a keyword followed + * by a KDELIM or a VDELIM. + * If successful, returns the appropriate marker, otherwise Nomatch. + */ +{ + register int j; + register char const *p, *s; + for (j = sizeof(Keyword)/sizeof(*Keyword); (--j); ) { + /* try next keyword */ + p = Keyword[j]; + s = string; + while (*p++ == *s++) { + if (!*p) + switch (*s) { + case KDELIM: + case VDELIM: + return (enum markers)j; + default: + return Nomatch; + } + } + } + return(Nomatch); +} + diff --git a/gnu/usr.bin/rcs/lib/rcslex.c b/gnu/usr.bin/rcs/lib/rcslex.c new file mode 100644 index 0000000..51e31f3 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcslex.c @@ -0,0 +1,1241 @@ +/* + * RCS file input + */ +/********************************************************************************* + * Lexical Analysis. + * hashtable, Lexinit, nextlex, getlex, getkey, + * getid, getnum, readstring, printstring, savestring, + * checkid, fatserror, error, faterror, warn, diagnose + * Testprogram: define LEXDB + ********************************************************************************* + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + +/* $Log: rcslex.c,v $ + * Revision 5.11 1991/11/03 03:30:44 eggert + * Fix porting bug to ancient hosts lacking vfprintf. + * + * Revision 5.10 1991/10/07 17:32:46 eggert + * Support piece tables even if !has_mmap. + * + * Revision 5.9 1991/09/24 00:28:42 eggert + * Don't export errsay(). + * + * Revision 5.8 1991/08/19 03:13:55 eggert + * Add eoflex(), mmap support. Tune. + * + * Revision 5.7 1991/04/21 11:58:26 eggert + * Add MS-DOS support. + * + * Revision 5.6 1991/02/25 07:12:42 eggert + * Work around fputs bug. strsave -> str_save (DG/UX name clash) + * + * Revision 5.5 1990/12/04 05:18:47 eggert + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.4 1990/11/19 20:05:28 hammer + * no longer gives warning about unknown keywords if -q is specified + * + * Revision 5.3 1990/11/01 05:03:48 eggert + * When ignoring unknown phrases, copy them to the output RCS file. + * + * Revision 5.2 1990/09/04 08:02:27 eggert + * Count RCS lines better. + * + * Revision 5.1 1990/08/29 07:14:03 eggert + * Work around buggy compilers with defective argument promotion. + * + * Revision 5.0 1990/08/22 08:12:55 eggert + * Remove compile-time limits; use malloc instead. + * Report errno-related errors with perror(). + * Ansify and Posixate. Add support for ISO 8859. + * Use better hash function. + * + * Revision 4.6 89/05/01 15:13:07 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.5 88/08/28 15:01:12 eggert + * Don't loop when writing error messages to a full filesystem. + * Flush stderr/stdout when mixing output. + * Yield exit status compatible with diff(1). + * Shrink stdio code size; allow cc -R; remove lint. + * + * Revision 4.4 87/12/18 11:44:47 narten + * fixed to use "varargs" in "fprintf"; this is required if it is to + * work on a SPARC machine such as a Sun-4 + * + * Revision 4.3 87/10/18 10:37:18 narten + * Updating version numbers. Changes relative to 1.1 actually relative + * to version 4.1 + * + * Revision 1.3 87/09/24 14:00:17 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:33 jenkins + * Port to suns + * + * Revision 4.1 83/03/25 18:12:51 wft + * Only changed $Header to $Id. + * + * Revision 3.3 82/12/10 16:22:37 wft + * Improved error messages, changed exit status on error to 1. + * + * Revision 3.2 82/11/28 21:27:10 wft + * Renamed ctab to map and included EOFILE; ctab is now a macro in rcsbase.h. + * Added fflsbuf(), fputs(), and fprintf(), which abort the RCS operations + * properly in case there is an IO-error (e.g., file system full). + * + * Revision 3.1 82/10/11 19:43:56 wft + * removed unused label out:; + * made sure all calls to getc() return into an integer, not a char. + */ + + +/* +#define LEXDB +*/ +/* version LEXDB is for testing the lexical analyzer. The testprogram + * reads a stream of lexemes, enters the revision numbers into the + * hashtable, and prints the recognized tokens. Keywords are recognized + * as identifiers. + */ + + + +#include "rcsbase.h" + +libId(lexId, "$Id: rcslex.c,v 5.11 1991/11/03 03:30:44 eggert Exp $") + +static struct hshentry *nexthsh; /*pointer to next hash entry, set by lookup*/ + +enum tokens nexttok; /*next token, set by nextlex */ + +int hshenter; /*if true, next suitable lexeme will be entered */ + /*into the symbol table. Handle with care. */ +int nextc; /*next input character, initialized by Lexinit */ + +unsigned long rcsline; /*current line-number of input */ +int nerror; /*counter for errors */ +int quietflag; /*indicates quiet mode */ +RILE * finptr; /*input file descriptor */ + +FILE * frewrite; /*file descriptor for echoing input */ + +FILE * foutptr; /* copy of frewrite, but 0 to suppress echo */ + +static struct buf tokbuf; /* token buffer */ + +char const * NextString; /* next token */ + +/* + * Our hash algorithm is h[0] = 0, h[i+1] = 4*h[i] + c, + * so hshsize should be odd. + * See B J McKenzie, R Harries & T Bell, Selecting a hashing algorithm, + * Software--practice & experience 20, 2 (Feb 1990), 209-224. + */ +#ifndef hshsize +# define hshsize 511 +#endif + +static struct hshentry *hshtab[hshsize]; /*hashtable */ + +static int ignored_phrases; /* have we ignored phrases in this RCS file? */ + + void +warnignore() +{ + if (! (ignored_phrases|quietflag)) { + ignored_phrases = true; + warn("Unknown phrases like `%s ...;' are in the RCS file.", NextString); + } +} + + + + static void +lookup(str) + char const *str; +/* Function: Looks up the character string pointed to by str in the + * hashtable. If the string is not present, a new entry for it is created. + * In any case, the address of the corresponding hashtable entry is placed + * into nexthsh. + */ +{ + register unsigned ihash; /* index into hashtable */ + register char const *sp; + register struct hshentry *n, **p; + + /* calculate hash code */ + sp = str; + ihash = 0; + while (*sp) + ihash = (ihash<<2) + *sp++; + ihash %= hshsize; + + for (p = &hshtab[ihash]; ; p = &n->nexthsh) + if (!(n = *p)) { + /* empty slot found */ + *p = n = ftalloc(struct hshentry); + n->num = fstr_save(str); + n->nexthsh = nil; +# ifdef LEXDB + VOID printf("\nEntered: %s at %u ", str, ihash); +# endif + break; + } else if (strcmp(str, n->num) == 0) + /* match found */ + break; + nexthsh = n; + NextString = n->num; +} + + + + + + + void +Lexinit() +/* Function: Initialization of lexical analyzer: + * initializes the hashtable, + * initializes nextc, nexttok if finptr != 0 + */ +{ register int c; + + for (c = hshsize; 0 <= --c; ) { + hshtab[c] = nil; + } + + nerror = 0; + if (finptr) { + foutptr = 0; + hshenter = true; + ignored_phrases = false; + rcsline = 1; + bufrealloc(&tokbuf, 2); + Iget(finptr, nextc); + nextlex(); /*initial token*/ + } +} + + + + + + + + void +nextlex() + +/* Function: Reads the next token and sets nexttok to the next token code. + * Only if hshenter is set, a revision number is entered into the + * hashtable and a pointer to it is placed into nexthsh. + * This is useful for avoiding that dates are placed into the hashtable. + * For ID's and NUM's, NextString is set to the character string. + * Assumption: nextc contains the next character. + */ +{ register c; + declarecache; + register FILE *frew; + register char * sp; + char const *limit; + register enum tokens d; + register RILE *fin; + + fin=finptr; frew=foutptr; + setupcache(fin); cache(fin); + c = nextc; + + for (;;) { switch ((d = ctab[c])) { + + default: + fatserror("unknown character `%c'", c); + /*NOTREACHED*/ + + case NEWLN: + ++rcsline; +# ifdef LEXDB + afputc('\n',stdout); +# endif + /* Note: falls into next case */ + + case SPACE: + GETC(frew, c); + continue; + + case DIGIT: + sp = tokbuf.string; + limit = sp + tokbuf.size; + *sp++ = c; + for (;;) { + GETC(frew, c); + if ((d=ctab[c])!=DIGIT && d!=PERIOD) + break; + *sp++ = c; /* 1.2. and 1.2 are different */ + if (limit <= sp) + sp = bufenlarge(&tokbuf, &limit); + } + *sp = 0; + if (hshenter) + lookup(tokbuf.string); + else + NextString = fstr_save(tokbuf.string); + d = NUM; + break; + + + case LETTER: + case Letter: + sp = tokbuf.string; + limit = sp + tokbuf.size; + *sp++ = c; + for (;;) { + GETC(frew, c); + if ((d=ctab[c])!=LETTER && d!=Letter && d!=DIGIT && d!=IDCHAR) + break; + *sp++ = c; + if (limit <= sp) + sp = bufenlarge(&tokbuf, &limit); + } + *sp = 0; + NextString = fstr_save(tokbuf.string); + d = ID; /* may be ID or keyword */ + break; + + case SBEGIN: /* long string */ + d = STRING; + /* note: only the initial SBEGIN has been read*/ + /* read the string, and reset nextc afterwards*/ + break; + + case COLON: + case SEMI: + GETC(frew, c); + break; + } break; } + nextc = c; + nexttok = d; + uncache(fin); +} + + int +eoflex() +/* + * Yield true if we look ahead to the end of the input, false otherwise. + * nextc becomes undefined at end of file. + */ +{ + register int c; + declarecache; + register FILE *fout; + register RILE *fin; + + c = nextc; + fin = finptr; + fout = foutptr; + setupcache(fin); cache(fin); + + for (;;) { + switch (ctab[c]) { + default: + nextc = c; + uncache(fin); + return false; + + case NEWLN: + ++rcsline; + /* fall into */ + case SPACE: + cachegeteof(c, {uncache(fin);return true;}); + break; + } + if (fout) + aputc(c, fout); + } +} + + +int getlex(token) +enum tokens token; +/* Function: Checks if nexttok is the same as token. If so, + * advances the input by calling nextlex and returns true. + * otherwise returns false. + * Doesn't work for strings and keywords; loses the character string for ids. + */ +{ + if (nexttok==token) { + nextlex(); + return(true); + } else return(false); +} + + int +getkeyopt(key) + char const *key; +/* Function: If the current token is a keyword identical to key, + * advances the input by calling nextlex and returns true; + * otherwise returns false. + */ +{ + if (nexttok==ID && strcmp(key,NextString) == 0) { + /* match found */ + ffree1(NextString); + nextlex(); + return(true); + } + return(false); +} + + void +getkey(key) + char const *key; +/* Check that the current input token is a keyword identical to key, + * and advance the input by calling nextlex. + */ +{ + if (!getkeyopt(key)) + fatserror("missing '%s' keyword", key); +} + + void +getkeystring(key) + char const *key; +/* Check that the current input token is a keyword identical to key, + * and advance the input by calling nextlex; then look ahead for a string. + */ +{ + getkey(key); + if (nexttok != STRING) + fatserror("missing string after '%s' keyword", key); +} + + + char const * +getid() +/* Function: Checks if nexttok is an identifier. If so, + * advances the input by calling nextlex and returns a pointer + * to the identifier; otherwise returns nil. + * Treats keywords as identifiers. + */ +{ + register char const *name; + if (nexttok==ID) { + name = NextString; + nextlex(); + return name; + } else return nil; +} + + +struct hshentry * getnum() +/* Function: Checks if nexttok is a number. If so, + * advances the input by calling nextlex and returns a pointer + * to the hashtable entry. Otherwise returns nil. + * Doesn't work if hshenter is false. + */ +{ + register struct hshentry * num; + if (nexttok==NUM) { + num=nexthsh; + nextlex(); + return num; + } else return nil; +} + + struct cbuf +getphrases(key) + char const *key; +/* Get a series of phrases that do not start with KEY, yield resulting buffer. + * Stop when the next phrase starts with a token that is not an identifier, + * or is KEY. + * Assume !foutptr. + */ +{ + declarecache; + register int c; + register char *p; + char const *limit; + register char const *ki, *kn; + struct cbuf r; + struct buf b; + register RILE *fin; + + if (nexttok!=ID || strcmp(NextString,key) == 0) { + r.string = 0; + r.size = 0; + return r; + } else { + warnignore(); + fin = finptr; + setupcache(fin); cache(fin); + bufautobegin(&b); + bufscpy(&b, NextString); + ffree1(NextString); + p = b.string + strlen(b.string); + limit = b.string + b.size; + c = nextc; + for (;;) { + for (;;) { + if (limit <= p) + p = bufenlarge(&b, &limit); + *p++ = c; + switch (ctab[c]) { + default: + fatserror("unknown character `%c'", c); + /*NOTREACHED*/ + case NEWLN: + ++rcsline; + /* fall into */ + case COLON: case DIGIT: case LETTER: case Letter: + case PERIOD: case SPACE: + cacheget(c); + continue; + case SBEGIN: /* long string */ + for (;;) { + for (;;) { + if (limit <= p) + p = bufenlarge(&b, &limit); + cacheget(c); + *p++ = c; + switch (c) { + case '\n': + ++rcsline; + /* fall into */ + default: + continue; + + case SDELIM: + break; + } + break; + } + cacheget(c); + if (c != SDELIM) + break; + if (limit <= p) + p = bufenlarge(&b, &limit); + *p++ = c; + } + continue; + case SEMI: + cacheget(c); + if (ctab[c] == NEWLN) { + ++rcsline; + if (limit <= p) + p = bufenlarge(&b, &limit); + *p++ = c; + cacheget(c); + } + for (;;) { + switch (ctab[c]) { + case NEWLN: + ++rcsline; + /* fall into */ + case SPACE: + cacheget(c); + continue; + + default: break; + } + break; + } + break; + } + break; + } + switch (ctab[c]) { + case LETTER: + case Letter: + for (kn = key; c && *kn==c; kn++) + cacheget(c); + if (!*kn) + switch (ctab[c]) { + case DIGIT: case LETTER: case Letter: + break; + default: + nextc = c; + NextString = fstr_save(key); + nexttok = ID; + uncache(fin); + goto returnit; + } + for (ki=key; kistring; limit = tp + target->size; + for (;;) { + GETC(frew, c); + switch (c) { + case '\n': + ++rcsline; + break; + case SDELIM: + GETC(frew, c); + if (c != SDELIM) { + /* end of string */ + nextc=c; + r.string = target->string; + r.size = tp - r.string; + uncache(fin); + return r; + } + break; + } + if (tp == limit) + tp = bufenlarge(target, &limit); + *tp++ = c; + } +} + + + char * +checkid(id, delimiter) + register char *id; + int delimiter; +/* Function: check whether the string starting at id is an */ +/* identifier and return a pointer to the delimiter*/ +/* after the identifier. White space, delim and 0 */ +/* are legal delimiters. Aborts the program if not*/ +/* a legal identifier. Useful for checking commands*/ +/* If !delim, the only delimiter is 0. */ +{ + register enum tokens d; + register char *temp; + register char c,tc; + register char delim = delimiter; + + temp = id; + if ((d = ctab[(unsigned char)(c = *id)])==LETTER || d==Letter) { + while ((d = ctab[(unsigned char)(c = *++id)])==LETTER + || d==Letter || d==DIGIT || d==IDCHAR + ) + ; + if (c && (!delim || c!=delim && c!=' ' && c!='\t' && c!='\n')) { + /* append \0 to end of id before error message */ + tc = c; + while( (c=(*++id))!=' ' && c!='\t' && c!='\n' && c!='\0' && c!=delim) ; + *id = '\0'; + faterror("invalid character %c in identifier `%s'",tc,temp); + } + } else { + /* append \0 to end of id before error message */ + while( (c=(*++id))!=' ' && c!='\t' && c!='\n' && c!='\0' && c!=delim) ; + *id = '\0'; + faterror("identifier `%s' doesn't start with letter", temp); + } + return id; +} + + void +checksid(id) + char *id; +/* Check whether the string ID is an identifier. */ +{ + VOID checkid(id, 0); +} + + + static RILE * +#if has_mmap && large_memory +fd2_RILE(fd, filename, status) +#else +fd2RILE(fd, filename, mode, status) + char const *mode; +#endif + int fd; + char const *filename; + register struct stat *status; +{ + struct stat st; + + if (!status) + status = &st; + if (fstat(fd, status) != 0) + efaterror(filename); + if (!S_ISREG(status->st_mode)) { + error("`%s' is not a regular file", filename); + VOID close(fd); + errno = EINVAL; + return 0; + } else { + +# if ! (has_mmap && large_memory) + FILE *stream; + if (!(stream = fdopen(fd, mode))) + efaterror(filename); +# endif + +# if !large_memory + return stream; +# else +# define RILES 3 + { + static RILE rilebuf[RILES]; + + register RILE *f; + size_t s = status->st_size; + + if (s != status->st_size) + faterror("`%s' is enormous", filename); + for (f = rilebuf; f->base; f++) + if (f == rilebuf+RILES) + faterror("too many RILEs"); + if (!s) { + static unsigned char dummy; + f->base = &dummy; + } else { +# if has_mmap + if ( + (f->base = (unsigned char *)mmap( + (caddr_t)0, s, PROT_READ, MAP_SHARED, + fd, (off_t)0 + )) == (unsigned char *)-1 + ) + efaterror("mmap"); +# else + f->base = tnalloc(unsigned char, s); +# endif + } + f->ptr = f->base; + f->lim = f->base + s; +# if has_mmap + f->fd = fd; +# else + f->readlim = f->base; + f->stream = stream; +# endif + if_advise_access(s, f, MADV_SEQUENTIAL); + return f; + } +# endif + } +} + +#if !has_mmap && large_memory + int +Igetmore(f) + register RILE *f; +{ + register fread_type r; + register size_t s = f->lim - f->readlim; + + if (BUFSIZ < s) + s = BUFSIZ; + if (!(r = Fread(f->readlim, sizeof(*f->readlim), s, f->stream))) { + testIerror(f->stream); + f->lim = f->readlim; /* The file might have shrunk! */ + return 0; + } + f->readlim += r; + return 1; +} +#endif + +#if has_madvise && has_mmap && large_memory + void +advise_access(f, advice) + register RILE *f; + int advice; +{ + if (madvise((caddr_t)f->base, (size_t)(f->lim - f->base), advice) != 0) + efaterror("madvise"); +} +#endif + + RILE * +#if has_mmap && large_memory +I_open(filename, status) +#else +Iopen(filename, mode, status) + char const *mode; +#endif + char const *filename; + struct stat *status; +/* Open FILENAME for reading, yield its descriptor, and set *STATUS. */ +{ + int fd; + + if ((fd = open(filename,O_RDONLY|O_BINARY)) < 0) + return 0; +# if has_mmap && large_memory + return fd2_RILE(fd, filename, status); +# else + return fd2RILE(fd, filename, mode, status); +# endif +} + + +#if !large_memory +# define Iclose(f) fclose(f) +#else + static int + Iclose(f) + register RILE *f; + { +# if has_mmap + size_t s = f->lim - f->base; + if (s && munmap((caddr_t)f->base, s) != 0) + return -1; + f->base = 0; + return close(f->fd); +# else + tfree(f->base); + f->base = 0; + return fclose(f->stream); +# endif + } +#endif + + +static int Oerrloop; + + exiting void +Oerror() +{ + if (Oerrloop) + exiterr(); + Oerrloop = true; + efaterror("output error"); +} + +exiting void Ieof() { fatserror("unexpected end of file"); } +exiting void Ierror() { efaterror("input error"); } +void testIerror(f) FILE *f; { if (ferror(f)) Ierror(); } +void testOerror(o) FILE *o; { if (ferror(o)) Oerror(); } + +void Ifclose(f) RILE *f; { if (f && Iclose(f)!=0) Ierror(); } +void Ofclose(f) FILE *f; { if (f && fclose(f)!=0) Oerror(); } +void Izclose(p) RILE **p; { Ifclose(*p); *p = 0; } +void Ozclose(p) FILE **p; { Ofclose(*p); *p = 0; } + +#if !large_memory + void +testIeof(f) + FILE *f; +{ + testIerror(f); + if (feof(f)) + Ieof(); +} +void Irewind(f) FILE *f; { if (fseek(f,0L,SEEK_SET) != 0) Ierror(); } +#endif + +void eflush() +{ + if (fflush(stderr) != 0 && !Oerrloop) + Oerror(); +} + +void oflush() +{ + if (fflush(workstdout ? workstdout : stdout) != 0 && !Oerrloop) + Oerror(); +} + + static exiting void +fatcleanup(already_newline) + int already_newline; +{ + VOID fprintf(stderr, already_newline+"\n%s aborted\n", cmdid); + exiterr(); +} + +static void errsay() { oflush(); aprintf(stderr,"%s error: ",cmdid); nerror++; } +static void fatsay() { oflush(); VOID fprintf(stderr,"%s error: ",cmdid); } + +void eerror(s) char const *s; { enerror(errno,s); } + + void +enerror(e,s) + int e; + char const *s; +{ + errsay(); + errno = e; + perror(s); + eflush(); +} + +exiting void efaterror(s) char const *s; { enfaterror(errno,s); } + + exiting void +enfaterror(e,s) + int e; + char const *s; +{ + fatsay(); + errno = e; + perror(s); + fatcleanup(true); +} + +#if has_prototypes + void +error(char const *format,...) +#else + /*VARARGS1*/ void error(format, va_alist) char const *format; va_dcl +#endif +/* non-fatal error */ +{ + va_list args; + errsay(); + vararg_start(args, format); + fvfprintf(stderr, format, args); + va_end(args); + afputc('\n',stderr); + eflush(); +} + +#if has_prototypes + exiting void +fatserror(char const *format,...) +#else + /*VARARGS1*/ exiting void + fatserror(format, va_alist) char const *format; va_dcl +#endif +/* fatal syntax error */ +{ + va_list args; + oflush(); + VOID fprintf(stderr, "%s: %s:%lu: ", cmdid, RCSfilename, rcsline); + vararg_start(args, format); + fvfprintf(stderr, format, args); + va_end(args); + fatcleanup(false); +} + +#if has_prototypes + exiting void +faterror(char const *format,...) +#else + /*VARARGS1*/ exiting void faterror(format, va_alist) + char const *format; va_dcl +#endif +/* fatal error, terminates program after cleanup */ +{ + va_list args; + fatsay(); + vararg_start(args, format); + fvfprintf(stderr, format, args); + va_end(args); + fatcleanup(false); +} + +#if has_prototypes + void +warn(char const *format,...) +#else + /*VARARGS1*/ void warn(format, va_alist) char const *format; va_dcl +#endif +/* prints a warning message */ +{ + va_list args; + oflush(); + aprintf(stderr,"%s warning: ",cmdid); + vararg_start(args, format); + fvfprintf(stderr, format, args); + va_end(args); + afputc('\n',stderr); + eflush(); +} + + void +redefined(c) + int c; +{ + warn("redefinition of -%c option", c); +} + +#if has_prototypes + void +diagnose(char const *format,...) +#else + /*VARARGS1*/ void diagnose(format, va_alist) char const *format; va_dcl +#endif +/* prints a diagnostic message */ +/* Unlike the other routines, it does not append a newline. */ +/* This lets some callers suppress the newline, and is faster */ +/* in implementations that flush stderr just at the end of each printf. */ +{ + va_list args; + if (!quietflag) { + oflush(); + vararg_start(args, format); + fvfprintf(stderr, format, args); + va_end(args); + eflush(); + } +} + + + + void +afputc(c, f) +/* Function: afputc(c,f) acts like aputc(c,f), but is smaller and slower. + */ + int c; + register FILE *f; +{ + aputc(c,f); +} + + + void +aputs(s, iop) + char const *s; + FILE *iop; +/* Function: Put string s on file iop, abort on error. + */ +{ +#if has_fputs + if (fputs(s, iop) < 0) + Oerror(); +#else + awrite(s, strlen(s), iop); +#endif +} + + + + void +#if has_prototypes +fvfprintf(FILE *stream, char const *format, va_list args) +#else + fvfprintf(stream,format,args) FILE *stream; char *format; va_list args; +#endif +/* like vfprintf, except abort program on error */ +{ +#if has_vfprintf + if (vfprintf(stream, format, args) < 0) +#else +# if has__doprintf + _doprintf(stream, format, args); +# else +# if has__doprnt + _doprnt(format, args, stream); +# else + int *a = (int *)args; + VOID fprintf(stream, format, + a[0], a[1], a[2], a[3], a[4], + a[5], a[6], a[7], a[8], a[9] + ); +# endif +# endif + if (ferror(stream)) +#endif + Oerror(); +} + +#if has_prototypes + void +aprintf(FILE *iop, char const *fmt, ...) +#else + /*VARARGS2*/ void +aprintf(iop, fmt, va_alist) +FILE *iop; +char const *fmt; +va_dcl +#endif +/* Function: formatted output. Same as fprintf in stdio, + * but aborts program on error + */ +{ + va_list ap; + vararg_start(ap, fmt); + fvfprintf(iop, fmt, ap); + va_end(ap); +} + + + +#ifdef LEXDB +/* test program reading a stream of lexemes and printing the tokens. + */ + + + + int +main(argc,argv) +int argc; char * argv[]; +{ + cmdid="lextest"; + if (argc<2) { + aputs("No input file\n",stderr); + exitmain(EXIT_FAILURE); + } + if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) { + faterror("can't open input file %s",argv[1]); + } + Lexinit(); + while (!eoflex()) { + switch (nexttok) { + + case ID: + VOID printf("ID: %s",NextString); + break; + + case NUM: + if (hshenter) + VOID printf("NUM: %s, index: %d",nexthsh->num, nexthsh-hshtab); + else + VOID printf("NUM, unentered: %s",NextString); + hshenter = !hshenter; /*alternate between dates and numbers*/ + break; + + case COLON: + VOID printf("COLON"); break; + + case SEMI: + VOID printf("SEMI"); break; + + case STRING: + readstring(); + VOID printf("STRING"); break; + + case UNKN: + VOID printf("UNKN"); break; + + default: + VOID printf("DEFAULT"); break; + } + VOID printf(" | "); + nextlex(); + } + exitmain(EXIT_SUCCESS); +} + +exiting void exiterr() { _exit(EXIT_FAILURE); } + + +#endif diff --git a/gnu/usr.bin/rcs/lib/rcsmap.c b/gnu/usr.bin/rcs/lib/rcsmap.c new file mode 100644 index 0000000..0e7b23c --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsmap.c @@ -0,0 +1,68 @@ +/* RCS map of character types */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +#include "rcsbase.h" + +libId(mapId, "$Id: rcsmap.c,v 5.2 1991/08/19 03:13:55 eggert Exp $") + +/* map of character types */ +/* ISO 8859/1 (Latin-1) */ +enum tokens const ctab[] = { + UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, + SPACE, SPACE, NEWLN, SPACE, SPACE, SPACE, UNKN, UNKN, + UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, + UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, + SPACE, IDCHAR, IDCHAR, IDCHAR, DELIM, IDCHAR, IDCHAR, IDCHAR, + IDCHAR, IDCHAR, IDCHAR, IDCHAR, DELIM, IDCHAR, PERIOD, IDCHAR, + DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, + DIGIT, DIGIT, COLON, SEMI, IDCHAR, IDCHAR, IDCHAR, IDCHAR, + SBEGIN, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, + LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, + LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, + LETTER, LETTER, LETTER, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, + IDCHAR, Letter, Letter, Letter, Letter, Letter, Letter, Letter, + Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter, + Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter, + Letter, Letter, Letter, IDCHAR, IDCHAR, IDCHAR, IDCHAR, UNKN, + UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, + UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, + UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, + UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, + IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, + IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, + IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, + IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, + LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, + LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, + LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, IDCHAR, + LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, Letter, + Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter, + Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter, + Letter, Letter, Letter, Letter, Letter, Letter, Letter, IDCHAR, + Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter +}; diff --git a/gnu/usr.bin/rcs/lib/rcsrev.c b/gnu/usr.bin/rcs/lib/rcsrev.c new file mode 100644 index 0000000..ce11f54 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsrev.c @@ -0,0 +1,790 @@ +/* + * RCS revision number handling + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + + +/* $Log: rcsrev.c,v $ + * Revision 5.3 1991/08/19 03:13:55 eggert + * Add `-r$', `-rB.'. Remove botches like `' from messages. Tune. + * + * Revision 5.2 1991/04/21 11:58:28 eggert + * Add tiprev(). + * + * Revision 5.1 1991/02/25 07:12:43 eggert + * Avoid overflow when comparing revision numbers. + * + * Revision 5.0 1990/08/22 08:13:43 eggert + * Remove compile-time limits; use malloc instead. + * Ansify and Posixate. Tune. + * Remove possibility of an internal error. Remove lint. + * + * Revision 4.5 89/05/01 15:13:22 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.4 87/12/18 11:45:22 narten + * more lint cleanups. Also, the NOTREACHED comment is no longer necessary, + * since there's now a return value there with a value. (Guy Harris) + * + * Revision 4.3 87/10/18 10:38:42 narten + * Updating version numbers. Changes relative to version 1.1 actually + * relative to 4.1 + * + * Revision 1.3 87/09/24 14:00:37 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:37 jenkins + * Port to suns + * + * Revision 4.1 83/03/25 21:10:45 wft + * Only changed $Header to $Id. + * + * Revision 3.4 82/12/04 13:24:08 wft + * Replaced getdelta() with gettree(). + * + * Revision 3.3 82/11/28 21:33:15 wft + * fixed compartial() and compnum() for nil-parameters; fixed nils + * in error messages. Testprogram output shortenend. + * + * Revision 3.2 82/10/18 21:19:47 wft + * renamed compnum->cmpnum, compnumfld->cmpnumfld, + * numericrevno->numricrevno. + * + * Revision 3.1 82/10/11 19:46:09 wft + * changed expandsym() to check for source==nil; returns zero length string + * in that case. + */ + + + +/* +#define REVTEST +*/ +/* version REVTEST is for testing the routines that generate a sequence + * of delta numbers needed to regenerate a given delta. + */ + +#include "rcsbase.h" + +libId(revId, "$Id: rcsrev.c,v 5.3 1991/08/19 03:13:55 eggert Exp $") + +static char const *branchtip P((char const*)); +static struct hshentry *genbranch P((struct hshentry const*,char const*,unsigned,char const*,char const*,char const*,struct hshentries**)); + + + + unsigned +countnumflds(s) + char const *s; +/* Given a pointer s to a dotted number (date or revision number), + * countnumflds returns the number of digitfields in s. + */ +{ + register char const *sp; + register unsigned count; + if ((sp=s)==nil) return(0); + if (*sp == '\0') return(0); + count = 1; + do { + if (*sp++ == '.') count++; + } while (*sp); + return(count); +} + + void +getbranchno(revno,branchno) + char const *revno; + struct buf *branchno; +/* Given a non-nil revision number revno, getbranchno copies the number of the branch + * on which revno is into branchno. If revno itself is a branch number, + * it is copied unchanged. + */ +{ + register unsigned numflds; + register char *tp; + + bufscpy(branchno, revno); + numflds=countnumflds(revno); + if (!(numflds & 1)) { + tp = branchno->string; + while (--numflds) + while (*tp++ != '.') + ; + *(tp-1)='\0'; + } +} + + + +int cmpnum(num1, num2) + char const *num1, *num2; +/* compares the two dotted numbers num1 and num2 lexicographically + * by field. Individual fields are compared numerically. + * returns <0, 0, >0 if num1num2, resp. + * omitted fields are assumed to be higher than the existing ones. +*/ +{ + register char const *s1, *s2; + register size_t d1, d2; + register int r; + + s1=num1==nil?"":num1; + s2=num2==nil?"":num2; + + for (;;) { + /* Give precedence to shorter one. */ + if (!*s1) + return (unsigned char)*s2; + if (!*s2) + return -1; + + /* Strip leading zeros, then find number of digits. */ + while (*s1=='0') ++s1; for (d1=0; isdigit(s1[d1]); d1++) ; + while (*s2=='0') ++s2; for (d2=0; isdigit(s2[d2]); d2++) ; + + /* Do not convert to integer; it might overflow! */ + if (d1 != d2) + return d1string; + while (length) { + while (*r1!='.' && *r1) + ++r1; + ++r1; + length--; + } + /* eliminate last '.'*/ + *(r1-1)='\0'; + return rev1->string; +} + + + + + static void +store1(store, next) + struct hshentries ***store; + struct hshentry *next; +/* + * Allocate a new list node that addresses NEXT. + * Append it to the list that **STORE is the end pointer of. + */ +{ + register struct hshentries *p; + + p = ftalloc(struct hshentries); + p->first = next; + **store = p; + *store = &p->rest; +} + +struct hshentry * genrevs(revno,date,author,state,store) + char const *revno, *date, *author, *state; + struct hshentries **store; +/* Function: finds the deltas needed for reconstructing the + * revision given by revno, date, author, and state, and stores pointers + * to these deltas into a list whose starting address is given by store. + * The last delta (target delta) is returned. + * If the proper delta could not be found, nil is returned. + */ +{ + unsigned length; + register struct hshentry * next; + int result; + char const *branchnum; + struct buf t; + char datebuf[datesize]; + + bufautobegin(&t); + + if (!(next = Head)) { + error("RCS file empty"); + goto norev; + } + + length = countnumflds(revno); + + if (length >= 1) { + /* at least one field; find branch exactly */ + while ((result=cmpnumfld(revno,next->num,1)) < 0) { + store1(&store, next); + next = next->next; + if (!next) { + error("branch number %s too low", partialno(&t,revno,1)); + goto norev; + } + } + + if (result>0) { + absent(revno, 1); + goto norev; + } + } + if (length<=1){ + /* pick latest one on given branch */ + branchnum = next->num; /* works even for empty revno*/ + while ((next!=nil) && + (cmpnumfld(branchnum,next->num,1)==0) && + !( + (date==nil?1:(cmpnum(date,next->date)>=0)) && + (author==nil?1:(strcmp(author,next->author)==0)) && + (state ==nil?1:(strcmp(state, next->state) ==0)) + ) + ) + { + store1(&store, next); + next=next->next; + } + if ((next==nil) || + (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ { + cantfindbranch( + length ? revno : partialno(&t,branchnum,1), + date, author, state + ); + goto norev; + } else { + store1(&store, next); + } + *store = nil; + return next; + } + + /* length >=2 */ + /* find revision; may go low if length==2*/ + while ((result=cmpnumfld(revno,next->num,2)) < 0 && + (cmpnumfld(revno,next->num,1)==0) ) { + store1(&store, next); + next = next->next; + if (!next) + break; + } + + if ((next==nil) || (cmpnumfld(revno,next->num,1)!=0)) { + error("revision number %s too low", partialno(&t,revno,2)); + goto norev; + } + if ((length>2) && (result!=0)) { + absent(revno, 2); + goto norev; + } + + /* print last one */ + store1(&store, next); + + if (length>2) + return genbranch(next,revno,length,date,author,state,store); + else { /* length == 2*/ + if ((date!=nil) && (cmpnum(date,next->date)<0)){ + error("Revision %s has date %s.", + next->num, + date2str(next->date, datebuf) + ); + return nil; + } + if ((author!=nil)&&(strcmp(author,next->author)!=0)) { + error("Revision %s has author %s.",next->num,next->author); + return nil; + } + if ((state!=nil)&&(strcmp(state,next->state)!=0)) { + error("Revision %s has state %s.",next->num, + next->state==nil?"":next->state); + return nil; + } + *store=nil; + return next; + } + + norev: + bufautoend(&t); + return nil; +} + + + + + static struct hshentry * +genbranch(bpoint, revno, length, date, author, state, store) + struct hshentry const *bpoint; + char const *revno; + unsigned length; + char const *date, *author, *state; + struct hshentries **store; +/* Function: given a branchpoint, a revision number, date, author, and state, + * genbranch finds the deltas necessary to reconstruct the given revision + * from the branch point on. + * Pointers to the found deltas are stored in a list beginning with store. + * revno must be on a side branch. + * return nil on error + */ +{ + unsigned field; + register struct hshentry * next, * trail; + register struct branchhead const *bhead; + int result; + struct buf t; + char datebuf[datesize]; + + field = 3; + bhead = bpoint->branches; + + do { + if (!bhead) { + bufautobegin(&t); + error("no side branches present for %s", partialno(&t,revno,field-1)); + bufautoend(&t); + return nil; + } + + /*find branch head*/ + /*branches are arranged in increasing order*/ + while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) { + bhead = bhead->nextbranch; + if (!bhead) { + bufautobegin(&t); + error("branch number %s too high",partialno(&t,revno,field)); + bufautoend(&t); + return nil; + } + } + + if (result<0) { + absent(revno, field); + return nil; + } + + next = bhead->hsh; + if (length==field) { + /* pick latest one on that branch */ + trail=nil; + do { if ((date==nil?1:(cmpnum(date,next->date)>=0)) && + (author==nil?1:(strcmp(author,next->author)==0)) && + (state ==nil?1:(strcmp(state, next->state) ==0)) + ) trail = next; + next=next->next; + } while (next!=nil); + + if (trail==nil) { + cantfindbranch(revno, date, author, state); + return nil; + } else { /* print up to last one suitable */ + next = bhead->hsh; + while (next!=trail) { + store1(&store, next); + next=next->next; + } + store1(&store, next); + } + *store = nil; + return next; + } + + /* length > field */ + /* find revision */ + /* check low */ + if (cmpnumfld(revno,next->num,field+1)<0) { + bufautobegin(&t); + error("revision number %s too low", partialno(&t,revno,field+1)); + bufautoend(&t); + return(nil); + } + do { + store1(&store, next); + trail = next; + next = next->next; + } while ((next!=nil) && + (cmpnumfld(revno,next->num,field+1) >=0)); + + if ((length>field+1) && /*need exact hit */ + (cmpnumfld(revno,trail->num,field+1) !=0)){ + absent(revno, field+1); + return(nil); + } + if (length == field+1) { + if ((date!=nil) && (cmpnum(date,trail->date)<0)){ + error("Revision %s has date %s.", + trail->num, + date2str(trail->date, datebuf) + ); + return nil; + } + if ((author!=nil)&&(strcmp(author,trail->author)!=0)) { + error("Revision %s has author %s.",trail->num,trail->author); + return nil; + } + if ((state!=nil)&&(strcmp(state,trail->state)!=0)) { + error("Revision %s has state %s.",trail->num, + trail->state==nil?"":trail->state); + return nil; + } + } + bhead = trail->branches; + + } while ((field+=2) <= length); + * store = nil; + return trail; +} + + + static char const * +lookupsym(id) + char const *id; +/* Function: looks up id in the list of symbolic names starting + * with pointer SYMBOLS, and returns a pointer to the corresponding + * revision number. Returns nil if not present. + */ +{ + register struct assoc const *next; + next = Symbols; + while (next!=nil) { + if (strcmp(id, next->symbol)==0) + return next->num; + else next=next->nextassoc; + } + return nil; +} + +int expandsym(source, target) + char const *source; + struct buf *target; +/* Function: Source points to a revision number. Expandsym copies + * the number to target, but replaces all symbolic fields in the + * source number with their numeric values. + * Expand a branch followed by `.' to the latest revision on that branch. + * Ignore `.' after a revision. Remove leading zeros. + * returns false on error; + */ +{ + return fexpandsym(source, target, (RILE*)0); +} + + int +fexpandsym(source, target, fp) + char const *source; + struct buf *target; + RILE *fp; +/* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */ +{ + register char const *sp, *bp; + register char *tp; + char const *tlim; + register enum tokens d; + unsigned dots; + + sp = source; + bufalloc(target, 1); + tp = target->string; + if (!sp || !*sp) { /*accept nil pointer as a legal value*/ + *tp='\0'; + return true; + } + if (sp[0] == KDELIM && !sp[1]) { + if (!getoldkeys(fp)) + return false; + if (!*prevrev.string) { + error("working file lacks revision number"); + return false; + } + bufscpy(target, prevrev.string); + return true; + } + tlim = tp + target->size; + dots = 0; + + for (;;) { + switch (ctab[(unsigned char)*sp]) { + case DIGIT: + while (*sp=='0' && isdigit(sp[1])) + /* skip leading zeroes */ + sp++; + do { + if (tlim <= tp) + tp = bufenlarge(target, &tlim); + } while (isdigit(*tp++ = *sp++)); + --sp; + tp[-1] = '\0'; + break; + + case LETTER: + case Letter: + { + register char *p = tp; + register size_t s = tp - target->string; + do { + if (tlim <= p) + p = bufenlarge(target, &tlim); + *p++ = *sp++; + } while ((d=ctab[(unsigned char)*sp])==LETTER || + d==Letter || d==DIGIT || + (d==IDCHAR)); + if (tlim <= p) + p = bufenlarge(target, &tlim); + *p = 0; + tp = target->string + s; + } + bp = lookupsym(tp); + if (bp==nil) { + error("Symbolic number %s is undefined.", tp); + return false; + } + do { + if (tlim <= tp) + tp = bufenlarge(target, &tlim); + } while ((*tp++ = *bp++)); + break; + + default: + goto improper; + } + switch (*sp++) { + case '\0': return true; + case '.': break; + default: goto improper; + } + if (!*sp) { + if (dots & 1) + goto improper; + if (!(bp = branchtip(target->string))) + return false; + bufscpy(target, bp); + return true; + } + ++dots; + tp[-1] = '.'; + } + + improper: + error("improper revision number: %s", source); + return false; +} + + static char const * +branchtip(branch) + char const *branch; +{ + struct hshentry *h; + struct hshentries *hs; + + h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs); + return h ? h->num : (char const*)0; +} + + char const * +tiprev() +{ + return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0; +} + + + +#ifdef REVTEST + +char const cmdid[] = "revtest"; + + int +main(argc,argv) +int argc; char * argv[]; +{ + static struct buf numricrevno; + char symrevno[100]; /* used for input of revision numbers */ + char author[20]; + char state[20]; + char date[20]; + struct hshentries *gendeltas; + struct hshentry * target; + int i; + + if (argc<2) { + aputs("No input file\n",stderr); + exitmain(EXIT_FAILURE); + } + if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) { + faterror("can't open input file %s", argv[1]); + } + Lexinit(); + getadmin(); + + gettree(); + + getdesc(false); + + do { + /* all output goes to stderr, to have diagnostics and */ + /* errors in sequence. */ + aputs("\nEnter revision number or or '.': ",stderr); + if (!gets(symrevno)) break; + if (*symrevno == '.') break; + aprintf(stderr,"%s;\n",symrevno); + expandsym(symrevno,&numricrevno); + aprintf(stderr,"expanded number: %s; ",numricrevno.string); + aprintf(stderr,"Date: "); + gets(date); aprintf(stderr,"%s; ",date); + aprintf(stderr,"Author: "); + gets(author); aprintf(stderr,"%s; ",author); + aprintf(stderr,"State: "); + gets(state); aprintf(stderr, "%s;\n", state); + target = genrevs(numricrevno.string, *date?date:(char *)nil, *author?author:(char *)nil, + *state?state:(char*)nil, &gendeltas); + if (target!=nil) { + while (gendeltas) { + aprintf(stderr,"%s\n",gendeltas->first->num); + gendeltas = gendeltas->next; + } + } + } while (true); + aprintf(stderr,"done\n"); + exitmain(EXIT_SUCCESS); +} + +exiting void exiterr() { _exit(EXIT_FAILURE); } + +#endif diff --git a/gnu/usr.bin/rcs/lib/rcssyn.c b/gnu/usr.bin/rcs/lib/rcssyn.c new file mode 100644 index 0000000..31086c2 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcssyn.c @@ -0,0 +1,857 @@ +/* + * RCS file input + */ +/********************************************************************************* + * Syntax Analysis. + * Keyword table + * Testprogram: define SYNTEST + * Compatibility with Release 2: define COMPAT2=1 + ********************************************************************************* + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + +/* $Log: rcssyn.c,v $ + * Revision 5.8 1991/08/19 03:13:55 eggert + * Tune. + * + * Revision 5.7 1991/04/21 11:58:29 eggert + * Disambiguate names on shortname hosts. + * Fix errno bug. Add MS-DOS support. + * + * Revision 5.6 1991/02/28 19:18:51 eggert + * Fix null termination bug in reporting keyword expansion. + * + * Revision 5.5 1991/02/25 07:12:44 eggert + * Check diff output more carefully; avoid overflow. + * + * Revision 5.4 1990/11/01 05:28:48 eggert + * When ignoring unknown phrases, copy them to the output RCS file. + * Permit arbitrary data in logs and comment leaders. + * Don't check for nontext on initial checkin. + * + * Revision 5.3 1990/09/20 07:58:32 eggert + * Remove the test for non-text bytes; it caused more pain than it cured. + * + * Revision 5.2 1990/09/04 08:02:30 eggert + * Parse RCS files with no revisions. + * Don't strip leading white space from diff commands. Count RCS lines better. + * + * Revision 5.1 1990/08/29 07:14:06 eggert + * Add -kkvl. Clean old log messages too. + * + * Revision 5.0 1990/08/22 08:13:44 eggert + * Try to parse future RCS formats without barfing. + * Add -k. Don't require final newline. + * Remove compile-time limits; use malloc instead. + * Don't output branch keyword if there's no default branch, + * because RCS version 3 doesn't understand it. + * Tune. Remove lint. + * Add support for ISO 8859. Ansify and Posixate. + * Check that a newly checked-in file is acceptable as input to 'diff'. + * Check diff's output. + * + * Revision 4.6 89/05/01 15:13:32 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.5 88/08/09 19:13:21 eggert + * Allow cc -R; remove lint. + * + * Revision 4.4 87/12/18 11:46:16 narten + * more lint cleanups (Guy Harris) + * + * Revision 4.3 87/10/18 10:39:36 narten + * Updating version numbers. Changes relative to 1.1 actually relative to + * 4.1 + * + * Revision 1.3 87/09/24 14:00:49 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:40 jenkins + * Port to suns + * + * Revision 4.1 83/03/28 11:38:49 wft + * Added parsing and printing of default branch. + * + * Revision 3.6 83/01/15 17:46:50 wft + * Changed readdelta() to initialize selector and log-pointer. + * Changed puttree to check for selector==DELETE; putdtext() uses DELNUMFORM. + * + * Revision 3.5 82/12/08 21:58:58 wft + * renamed Commentleader to Commleader. + * + * Revision 3.4 82/12/04 13:24:40 wft + * Added routine gettree(), which updates keeplock after reading the + * delta tree. + * + * Revision 3.3 82/11/28 21:30:11 wft + * Reading and printing of Suffix removed; version COMPAT2 skips the + * Suffix for files of release 2 format. Fixed problems with printing nil. + * + * Revision 3.2 82/10/18 21:18:25 wft + * renamed putdeltatext to putdtext. + * + * Revision 3.1 82/10/11 19:45:11 wft + * made sure getc() returns into an integer. + */ + + + +/* version COMPAT2 reads files of the format of release 2 and 3, but + * generates files of release 3 format. Need not be defined if no + * old RCS files generated with release 2 exist. + */ +/* version SYNTEST inputs a RCS file and then prints out its internal + * data structures. +*/ + +#include "rcsbase.h" + +libId(synId, "$Id: rcssyn.c,v 5.8 1991/08/19 03:13:55 eggert Exp $") + +/* forward */ +static char const *getkeyval P((char const*,enum tokens,int)); +static int strn2expmode P((char const*,size_t)); + +/* keyword table */ + +char const + Kdesc[] = "desc", + Klog[] = "log", + Ktext[] = "text"; + +static char const + Kaccess[] = "access", + Kauthor[] = "author", + Kbranch[] = "branch", + K_branches[]= "branches", + Kcomment[] = "comment", + Kdate[] = "date", + Kexpand[] = "expand", + Khead[] = "head", + Klocks[] = "locks", + Knext[] = "next", + Kstate[] = "state", + Kstrict[] = "strict", +#if COMPAT2 + Ksuffix[] = "suffix", +#endif + Ksymbols[] = "symbols"; + +static struct buf Commleader; +static struct cbuf Ignored; +struct cbuf Comment; +struct access * AccessList; +struct assoc * Symbols; +struct lock * Locks; +int Expand; +int StrictLocks; +struct hshentry * Head; +char const * Dbranch; +unsigned TotalDeltas; + + + static void +getsemi(key) + char const *key; +/* Get a semicolon to finish off a phrase started by KEY. */ +{ + if (!getlex(SEMI)) + fatserror("missing ';' after '%s'", key); +} + + static struct hshentry * +getdnum() +/* Get a delta number. */ +{ + register struct hshentry *delta = getnum(); + if (delta && countnumflds(delta->num)&1) + fatserror("%s isn't a delta number", delta->num); + return delta; +} + + + void +getadmin() +/* Read an and initialize the appropriate global variables. */ +{ + register char const *id; + struct access * newaccess; + struct assoc * newassoc; + struct lock * newlock; + struct hshentry * delta; + struct access **LastAccess; + struct assoc **LastSymbol; + struct lock **LastLock; + struct buf b; + struct cbuf cb; + + TotalDeltas=0; + + getkey(Khead); + Head = getdnum(); + getsemi(Khead); + + Dbranch = nil; + if (getkeyopt(Kbranch)) { + if ((delta = getnum())) + Dbranch = delta->num; + getsemi(Kbranch); + } + + +#if COMPAT2 + /* read suffix. Only in release 2 format */ + if (getkeyopt(Ksuffix)) { + if (nexttok==STRING) { + readstring(); nextlex(); /* Throw away the suffix. */ + } else if (nexttok==ID) { + nextlex(); + } + getsemi(Ksuffix); + } +#endif + + getkey(Kaccess); + LastAccess = &AccessList; + while (id=getid()) { + newaccess = ftalloc(struct access); + newaccess->login = id; + *LastAccess = newaccess; + LastAccess = &newaccess->nextaccess; + } + *LastAccess = nil; + getsemi(Kaccess); + + getkey(Ksymbols); + LastSymbol = &Symbols; + while (id = getid()) { + if (!getlex(COLON)) + fatserror("missing ':' in symbolic name definition"); + if (!(delta=getnum())) { + fatserror("missing number in symbolic name definition"); + } else { /*add new pair to association list*/ + newassoc = ftalloc(struct assoc); + newassoc->symbol=id; + newassoc->num = delta->num; + *LastSymbol = newassoc; + LastSymbol = &newassoc->nextassoc; + } + } + *LastSymbol = nil; + getsemi(Ksymbols); + + getkey(Klocks); + LastLock = &Locks; + while (id = getid()) { + if (!getlex(COLON)) + fatserror("missing ':' in lock"); + if (!(delta=getdnum())) { + fatserror("missing number in lock"); + } else { /*add new pair to lock list*/ + newlock = ftalloc(struct lock); + newlock->login=id; + newlock->delta=delta; + *LastLock = newlock; + LastLock = &newlock->nextlock; + } + } + *LastLock = nil; + getsemi(Klocks); + + if ((StrictLocks = getkeyopt(Kstrict))) + getsemi(Kstrict); + + Comment.size = 0; + if (getkeyopt(Kcomment)) { + if (nexttok==STRING) { + Comment = savestring(&Commleader); + nextlex(); + } + getsemi(Kcomment); + } + + Expand = KEYVAL_EXPAND; + if (getkeyopt(Kexpand)) { + if (nexttok==STRING) { + bufautobegin(&b); + cb = savestring(&b); + if ((Expand = strn2expmode(cb.string,cb.size)) < 0) + fatserror("unknown expand mode %.*s", + (int)cb.size, cb.string + ); + bufautoend(&b); + nextlex(); + } + getsemi(Kexpand); + } + Ignored = getphrases(Kdesc); +} + +char const *const expand_names[] = { + /* These must agree with *_EXPAND in rcsbase.h. */ + "kv","kvl","k","v","o", + 0 +}; + + int +str2expmode(s) + char const *s; +/* Yield expand mode corresponding to S, or -1 if bad. */ +{ + return strn2expmode(s, strlen(s)); +} + + static int +strn2expmode(s, n) + char const *s; + size_t n; +{ + char const *const *p; + + for (p = expand_names; *p; ++p) + if (memcmp(*p,s,n) == 0 && !(*p)[n]) + return p - expand_names; + return -1; +} + + + void +ignorephrase() +/* Ignore a phrase introduced by a later version of RCS. */ +{ + warnignore(); + hshenter=false; + for (;;) { + switch (nexttok) { + case SEMI: hshenter=true; nextlex(); return; + case ID: + case NUM: ffree1(NextString); break; + case STRING: readstring(); break; + default: break; + } + nextlex(); + } +} + + + static int +getdelta() +/* Function: reads a delta block. + * returns false if the current block does not start with a number. + */ +{ + register struct hshentry * Delta, * num; + struct branchhead **LastBranch, *NewBranch; + + if (!(Delta = getdnum())) + return false; + + hshenter = false; /*Don't enter dates into hashtable*/ + Delta->date = getkeyval(Kdate, NUM, false); + hshenter=true; /*reset hshenter for revision numbers.*/ + + Delta->author = getkeyval(Kauthor, ID, false); + + Delta->state = getkeyval(Kstate, ID, true); + + getkey(K_branches); + LastBranch = &Delta->branches; + while ((num = getdnum())) { + NewBranch = ftalloc(struct branchhead); + NewBranch->hsh = num; + *LastBranch = NewBranch; + LastBranch = &NewBranch->nextbranch; + } + *LastBranch = nil; + getsemi(K_branches); + + getkey(Knext); + Delta->next = num = getdnum(); + getsemi(Knext); + Delta->lockedby = nil; + Delta->log.string = 0; + Delta->selector = true; + Delta->ig = getphrases(Kdesc); + TotalDeltas++; + return (true); +} + + + void +gettree() +/* Function: Reads in the delta tree with getdelta(), then + * updates the lockedby fields. + */ +{ + struct lock const *currlock; + + while (getdelta()); + currlock=Locks; + while (currlock) { + currlock->delta->lockedby = currlock->login; + currlock = currlock->nextlock; + } +} + + + void +getdesc(prdesc) +int prdesc; +/* Function: read in descriptive text + * nexttok is not advanced afterwards. + * If prdesc is set, the text is printed to stdout. + */ +{ + + getkeystring(Kdesc); + if (prdesc) + printstring(); /*echo string*/ + else readstring(); /*skip string*/ +} + + + + + + + static char const * +getkeyval(keyword, token, optional) + char const *keyword; + enum tokens token; + int optional; +/* reads a pair of the form + * ; + * where token is one of or . optional indicates whether + * is optional. A pointer to + * the actual character string of or is returned. + */ +{ + register char const *val = nil; + + getkey(keyword); + if (nexttok==token) { + val = NextString; + nextlex(); + } else { + if (!optional) + fatserror("missing %s", keyword); + } + getsemi(keyword); + return(val); +} + + + + + void +putadmin(fout) +register FILE * fout; +/* Function: Print the node read with getadmin() to file fout. + * Assumption: Variables AccessList, Symbols, Locks, StrictLocks, + * and Head have been set. + */ +{ + struct assoc const *curassoc; + struct lock const *curlock; + struct access const *curaccess; + + aprintf(fout, "%s\t%s;\n", Khead, Head?Head->num:""); + if (Dbranch && VERSION(4)<=RCSversion) + aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch); + + aputs(Kaccess, fout); + curaccess = AccessList; + while (curaccess) { + aprintf(fout, "\n\t%s", curaccess->login); + curaccess = curaccess->nextaccess; + } + aprintf(fout, ";\n%s", Ksymbols); + curassoc = Symbols; + while (curassoc) { + aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num); + curassoc = curassoc->nextassoc; + } + aprintf(fout, ";\n%s", Klocks); + curlock = Locks; + while (curlock) { + aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num); + curlock = curlock->nextlock; + } + if (StrictLocks) aprintf(fout, "; %s", Kstrict); + aprintf(fout, ";\n"); + if (Comment.size) { + aprintf(fout, "%s\t", Kcomment); + putstring(fout, true, Comment, false); + aprintf(fout, ";\n"); + } + if (Expand != KEYVAL_EXPAND) + aprintf(fout, "%s\t%c%s%c;\n", + Kexpand, SDELIM, expand_names[Expand], SDELIM + ); + awrite(Ignored.string, Ignored.size, fout); + aputc('\n', fout); +} + + + + + static void +putdelta(node,fout) +register struct hshentry const *node; +register FILE * fout; +/* Function: prints a node to fout; + */ +{ + struct branchhead const *nextbranch; + + if (node == nil) return; + + aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches", + node->num, + Kdate, node->date, + Kauthor, node->author, + Kstate, node->state?node->state:"" + ); + nextbranch = node->branches; + while (nextbranch) { + aprintf(fout, "\n\t%s", nextbranch->hsh->num); + nextbranch = nextbranch->nextbranch; + } + + aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:""); + awrite(node->ig.string, node->ig.size, fout); +} + + + + + void +puttree(root,fout) +struct hshentry const *root; +register FILE * fout; +/* Function: prints the delta tree in preorder to fout, starting with root. + */ +{ + struct branchhead const *nextbranch; + + if (root==nil) return; + + if (root->selector) + putdelta(root,fout); + + puttree(root->next,fout); + + nextbranch = root->branches; + while (nextbranch) { + puttree(nextbranch->hsh,fout); + nextbranch = nextbranch->nextbranch; + } +} + + + static exiting void +unexpected_EOF() +{ + faterror("unexpected EOF in diff output"); +} + +int putdtext(num,log,srcfilename,fout,diffmt) + char const *num, *srcfilename; + struct cbuf log; + FILE *fout; + int diffmt; +/* Function: write a deltatext-node to fout. + * num points to the deltanumber, log to the logmessage, and + * sourcefile contains the text. Doubles up all SDELIMs in both the + * log and the text; Makes sure the log message ends in \n. + * returns false on error. + * If diffmt is true, also checks that text is valid diff -n output. + */ +{ + RILE *fin; + int result; + if (!(fin = Iopen(srcfilename, "r", (struct stat*)0))) { + eerror(srcfilename); + return false; + } + result = putdftext(num,log,fin,fout,diffmt); + Ifclose(fin); + return result; +} + + void +putstring(out, delim, s, log) + register FILE *out; + struct cbuf s; + int delim, log; +/* + * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled. + * If LOG is set then S is a log string; append a newline if S is nonempty. + */ +{ + register char const *sp; + register size_t ss; + + if (delim) + aputc(SDELIM, out); + sp = s.string; + for (ss = s.size; ss; --ss) { + if (*sp == SDELIM) + aputc(SDELIM, out); + aputc(*sp++, out); + } + if (s.size && log) + aputc('\n', out); + aputc(SDELIM, out); +} + + int +putdftext(num,log,finfile,foutfile,diffmt) + char const *num; + struct cbuf log; + RILE *finfile; + FILE *foutfile; + int diffmt; +/* like putdtext(), except the source file is already open */ +{ + declarecache; + register FILE *fout; + register int c; + register RILE *fin; + int ed; + struct diffcmd dc; + + fout = foutfile; + aprintf(fout,DELNUMFORM,num,Klog); + /* put log */ + putstring(fout, true, log, true); + /* put text */ + aprintf(fout, "\n%s\n%c", Ktext, SDELIM); + fin = finfile; + setupcache(fin); + if (!diffmt) { + /* Copy the file */ + cache(fin); + for (;;) { + cachegeteof(c, break;); + if (c==SDELIM) aputc(SDELIM,fout); /*double up SDELIM*/ + aputc(c,fout); + } + } else { + initdiffcmd(&dc); + while (0 <= (ed = getdiffcmd(fin,false,fout,&dc))) + if (ed) { + cache(fin); + while (dc.nlines--) + do { + cachegeteof(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); }); + if (c == SDELIM) + aputc(SDELIM,fout); + aputc(c,fout); + } while (c != '\n'); + uncache(fin); + } + } + OK_EOF: + aprintf(fout, "%c\n", SDELIM); + return true; +} + + void +initdiffcmd(dc) + register struct diffcmd *dc; +/* Initialize *dc suitably for getdiffcmd(). */ +{ + dc->adprev = 0; + dc->dafter = 0; +} + + static exiting void +badDiffOutput(buf) + char const *buf; +{ + faterror("bad diff output line: %s", buf); +} + + static exiting void +diffLineNumberTooLarge(buf) + char const *buf; +{ + faterror("diff line number too large: %s", buf); +} + + int +getdiffcmd(finfile, delimiter, foutfile, dc) + RILE *finfile; + FILE *foutfile; + int delimiter; + struct diffcmd *dc; +/* Get a editing command output by 'diff -n' from fin. + * The input is delimited by SDELIM if delimiter is set, EOF otherwise. + * Copy a clean version of the command to fout (if nonnull). + * Yield 0 for 'd', 1 for 'a', and -1 for EOF. + * Store the command's line number and length into dc->line1 and dc->nlines. + * Keep dc->adprev and dc->dafter up to date. + */ +{ + register int c; + declarecache; + register FILE *fout; + register char *p; + register RILE *fin; + unsigned long line1, nlines, t; + char buf[BUFSIZ]; + + fin = finfile; + fout = foutfile; + setupcache(fin); cache(fin); + cachegeteof(c, { if (delimiter) unexpected_EOF(); return -1; } ); + if (delimiter) { + if (c==SDELIM) { + cacheget(c); + if (c==SDELIM) { + buf[0] = c; + buf[1] = 0; + badDiffOutput(buf); + } + uncache(fin); + nextc = c; + if (fout) + aprintf(fout, "%c%c", SDELIM, c); + return -1; + } + } + p = buf; + do { + if (buf+BUFSIZ-2 <= p) { + faterror("diff output command line too long"); + } + *p++ = c; + cachegeteof(c, unexpected_EOF();) ; + } while (c != '\n'); + uncache(fin); + if (delimiter) + ++rcsline; + *p = '\0'; + for (p = buf+1; (c = *p++) == ' '; ) + ; + line1 = 0; + while (isdigit(c)) { + t = line1 * 10; + if ( + ULONG_MAX/10 < line1 || + (line1 = t + (c - '0')) < t + ) + diffLineNumberTooLarge(buf); + c = *p++; + } + while (c == ' ') + c = *p++; + nlines = 0; + while (isdigit(c)) { + t = nlines * 10; + if ( + ULONG_MAX/10 < nlines || + (nlines = t + (c - '0')) < t + ) + diffLineNumberTooLarge(buf); + c = *p++; + } + if (c || !nlines) { + badDiffOutput(buf); + } + if (line1+nlines < line1) + diffLineNumberTooLarge(buf); + switch (buf[0]) { + case 'a': + if (line1 < dc->adprev) { + faterror("backward insertion in diff output: %s", buf); + } + dc->adprev = line1 + 1; + break; + case 'd': + if (line1 < dc->adprev || line1 < dc->dafter) { + faterror("backward deletion in diff output: %s", buf); + } + dc->adprev = line1; + dc->dafter = line1 + nlines; + break; + default: + badDiffOutput(buf); + } + if (fout) { + aprintf(fout, "%s\n", buf); + } + dc->line1 = line1; + dc->nlines = nlines; + return buf[0] == 'a'; +} + + + +#ifdef SYNTEST + +char const cmdid[] = "syntest"; + + int +main(argc,argv) +int argc; char * argv[]; +{ + + if (argc<2) { + aputs("No input file\n",stderr); + exitmain(EXIT_FAILURE); + } + if (!(finptr = Iopen(argv[1], FOPEN_R, (struct stat*)0))) { + faterror("can't open input file %s", argv[1]); + } + Lexinit(); + getadmin(); + putadmin(stdout); + + gettree(); + puttree(Head,stdout); + + getdesc(true); + + nextlex(); + + if (!eoflex()) { + fatserror("expecting EOF"); + } + exitmain(EXIT_SUCCESS); +} + + +exiting void exiterr() { _exit(EXIT_FAILURE); } + + +#endif + diff --git a/gnu/usr.bin/rcs/lib/rcsutil.c b/gnu/usr.bin/rcs/lib/rcsutil.c new file mode 100644 index 0000000..c523ccf1 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/rcsutil.c @@ -0,0 +1,994 @@ +/* + * RCS utilities + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + + +/* $Log: rcsutil.c,v $ + * Revision 5.10 1991/10/07 17:32:46 eggert + * Support piece tables even if !has_mmap. + * + * Revision 5.9 1991/08/19 03:13:55 eggert + * Add spawn() support. Explicate assumptions about getting invoker's name. + * Standardize user-visible dates. Tune. + * + * Revision 5.8 1991/04/21 11:58:30 eggert + * Plug setuid security hole. + * + * Revision 5.6 1991/02/26 17:48:39 eggert + * Fix setuid bug. Use fread, fwrite more portably. + * Support waitpid. Don't assume -1 is acceptable to W* macros. + * strsave -> str_save (DG/UX name clash) + * + * Revision 5.5 1990/12/04 05:18:49 eggert + * Don't output a blank line after a signal diagnostic. + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.4 1990/11/01 05:03:53 eggert + * Remove unneeded setid check. Add awrite(), fremember(). + * + * Revision 5.3 1990/10/06 00:16:45 eggert + * Don't fread F if feof(F). + * + * Revision 5.2 1990/09/04 08:02:31 eggert + * Store fread()'s result in an fread_type object. + * + * Revision 5.1 1990/08/29 07:14:07 eggert + * Declare getpwuid() more carefully. + * + * Revision 5.0 1990/08/22 08:13:46 eggert + * Add setuid support. Permit multiple locks per user. + * Remove compile-time limits; use malloc instead. + * Switch to GMT. Permit dates past 1999/12/31. + * Add -V. Remove snooping. Ansify and Posixate. + * Tune. Some USG hosts define NSIG but not sys_siglist. + * Don't run /bin/sh if it's hopeless. + * Don't leave garbage behind if the output is an empty pipe. + * Clean up after SIGXCPU or SIGXFSZ. Print name of signal that caused cleanup. + * + * Revision 4.6 89/05/01 15:13:40 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.5 88/11/08 16:01:02 narten + * corrected use of varargs routines + * + * Revision 4.4 88/08/09 19:13:24 eggert + * Check for memory exhaustion. + * Permit signal handlers to yield either 'void' or 'int'; fix oldSIGINT botch. + * Use execv(), not system(); yield exit status like diff(1)'s. + * + * Revision 4.3 87/10/18 10:40:22 narten + * Updating version numbers. Changes relative to 1.1 actually + * relative to 4.1 + * + * Revision 1.3 87/09/24 14:01:01 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:43 jenkins + * Port to suns + * + * Revision 4.1 83/05/10 15:53:13 wft + * Added getcaller() and findlock(). + * Changed catchints() to check SIGINT for SIG_IGN before setting up the signal + * (needed for background jobs in older shells). Added restoreints(). + * Removed printing of full RCS path from logcommand(). + * + * Revision 3.8 83/02/15 15:41:49 wft + * Added routine fastcopy() to copy remainder of a file in blocks. + * + * Revision 3.7 82/12/24 15:25:19 wft + * added catchints(), ignoreints() for catching and ingnoring interrupts; + * fixed catchsig(). + * + * Revision 3.6 82/12/08 21:52:05 wft + * Using DATEFORM to format dates. + * + * Revision 3.5 82/12/04 18:20:49 wft + * Replaced SNOOPDIR with SNOOPFILE; changed addlock() to update + * lockedby-field. + * + * Revision 3.4 82/12/03 17:17:43 wft + * Added check to addlock() ensuring only one lock per person. + * Addlock also returns a pointer to the lock created. Deleted fancydate(). + * + * Revision 3.3 82/11/27 12:24:37 wft + * moved rmsema(), trysema(), trydiraccess(), getfullRCSname() to rcsfnms.c. + * Introduced macro SNOOP so that snoop can be placed in directory other than + * TARGETDIR. Changed %02d to %.2d for compatibility reasons. + * + * Revision 3.2 82/10/18 21:15:11 wft + * added function getfullRCSname(). + * + * Revision 3.1 82/10/13 16:17:37 wft + * Cleanup message is now suppressed in quiet mode. + */ + + + + +#include "rcsbase.h" + +libId(utilId, "$Id: rcsutil.c,v 5.10 1991/10/07 17:32:46 eggert Exp $") + +#if !has_memcmp + int +memcmp(s1, s2, n) + void const *s1, *s2; + size_t n; +{ + register unsigned char const + *p1 = (unsigned char const*)s1, + *p2 = (unsigned char const*)s2; + register size_t i = n; + register int r = 0; + while (i-- && !(r = (*p1++ - *p2++))) + ; + return r; +} +#endif + +#if !has_memcpy + void * +memcpy(s1, s2, n) + void *s1; + void const *s2; + size_t n; +{ + register char *p1 = (char*)s1; + register char const *p2 = (char const*)s2; + while (n--) + *p1++ = *p2++; + return s1; +} +#endif + +#if lint + malloc_type lintalloc; +#endif + +/* + * list of blocks allocated with ftestalloc() + * These blocks can be freed by ffree when we're done with the current file. + * We could put the free block inside struct alloclist, rather than a pointer + * to the free block, but that would be less portable. + */ +struct alloclist { + malloc_type alloc; + struct alloclist *nextalloc; +}; +static struct alloclist *alloced; + + + static malloc_type +okalloc(p) + malloc_type p; +{ + if (!p) + faterror("out of memory"); + return p; +} + + malloc_type +testalloc(size) + size_t size; +/* Allocate a block, testing that the allocation succeeded. */ +{ + return okalloc(malloc(size)); +} + + malloc_type +testrealloc(ptr, size) + malloc_type ptr; + size_t size; +/* Reallocate a block, testing that the allocation succeeded. */ +{ + return okalloc(realloc(ptr, size)); +} + + malloc_type +fremember(ptr) + malloc_type ptr; +/* Remember PTR in 'alloced' so that it can be freed later. Yield PTR. */ +{ + register struct alloclist *q = talloc(struct alloclist); + q->nextalloc = alloced; + alloced = q; + return q->alloc = ptr; +} + + malloc_type +ftestalloc(size) + size_t size; +/* Allocate a block, putting it in 'alloced' so it can be freed later. */ +{ + return fremember(testalloc(size)); +} + + void +ffree() +/* Free all blocks allocated with ftestalloc(). */ +{ + register struct alloclist *p, *q; + for (p = alloced; p; p = q) { + q = p->nextalloc; + tfree(p->alloc); + tfree(p); + } + alloced = nil; +} + + void +ffree1(f) + register char const *f; +/* Free the block f, which was allocated by ftestalloc. */ +{ + register struct alloclist *p, **a = &alloced; + + while ((p = *a)->alloc != f) + a = &p->nextalloc; + *a = p->nextalloc; + tfree(p->alloc); + tfree(p); +} + + char * +str_save(s) + char const *s; +/* Save s in permanently allocated storage. */ +{ + return strcpy(tnalloc(char, strlen(s)+1), s); +} + + char * +fstr_save(s) + char const *s; +/* Save s in storage that will be deallocated when we're done with this file. */ +{ + return strcpy(ftnalloc(char, strlen(s)+1), s); +} + + char * +cgetenv(name) + char const *name; +/* Like getenv(), but yield a copy; getenv() can overwrite old results. */ +{ + register char *p; + + return (p=getenv(name)) ? str_save(p) : p; +} + + char const * +getusername(suspicious) + int suspicious; +/* Get the caller's login name. Trust only getwpuid if SUSPICIOUS. */ +{ + static char *name; + + if (!name) { + if ( + /* Prefer getenv() unless suspicious; it's much faster. */ +# if getlogin_is_secure + (suspicious + || + !(name = cgetenv("LOGNAME")) + && !(name = cgetenv("USER"))) + && !(name = getlogin()) +# else + suspicious + || + !(name = cgetenv("LOGNAME")) + && !(name = cgetenv("USER")) + && !(name = getlogin()) +# endif + ) { +#if has_getuid && has_getpwuid + struct passwd const *pw = getpwuid(ruid()); + if (!pw) + faterror("no password entry for userid %lu", + (unsigned long)ruid() + ); + name = pw->pw_name; +#else +#if has_setuid + faterror("setuid not supported"); +#else + faterror("Who are you? Please set LOGNAME."); +#endif +#endif + } + checksid(name); + } + return name; +} + + + + +#if has_signal + +/* + * Signal handling + * + * Standard C places too many restrictions on signal handlers. + * We obey as many of them as we can. + * Posix places fewer restrictions, and we are Posix-compatible here. + */ + +static sig_atomic_t volatile heldsignal, holdlevel; + + static signal_type +catchsig(s) + int s; +{ + char const *sname; + char buf[BUFSIZ]; + +#if sig_zaps_handler + /* If a signal arrives before we reset the signal handler, we lose. */ + VOID signal(s, SIG_IGN); +#endif + if (holdlevel) { + heldsignal = s; + return; + } + ignoreints(); + setrid(); + if (!quietflag) { + sname = nil; +#if has_sys_siglist && defined(NSIG) + if ((unsigned)s < NSIG) { +# ifndef sys_siglist + extern char const *sys_siglist[]; +# endif + sname = sys_siglist[s]; + } +#else + switch (s) { +#ifdef SIGHUP + case SIGHUP: sname = "Hangup"; break; +#endif +#ifdef SIGINT + case SIGINT: sname = "Interrupt"; break; +#endif +#ifdef SIGPIPE + case SIGPIPE: sname = "Broken pipe"; break; +#endif +#ifdef SIGQUIT + case SIGQUIT: sname = "Quit"; break; +#endif +#ifdef SIGTERM + case SIGTERM: sname = "Terminated"; break; +#endif +#ifdef SIGXCPU + case SIGXCPU: sname = "Cputime limit exceeded"; break; +#endif +#ifdef SIGXFSZ + case SIGXFSZ: sname = "Filesize limit exceeded"; break; +#endif + } +#endif + if (sname) + VOID sprintf(buf, "\nRCS: %s. Cleaning up.\n", sname); + else + VOID sprintf(buf, "\nRCS: Signal %d. Cleaning up.\n", s); + VOID write(STDERR_FILENO, buf, strlen(buf)); + } + exiterr(); +} + + void +ignoreints() +{ + ++holdlevel; +} + + void +restoreints() +{ + if (!--holdlevel && heldsignal) + VOID catchsig(heldsignal); +} + + +static int const sig[] = { +#ifdef SIGHUP + SIGHUP, +#endif +#ifdef SIGINT + SIGINT, +#endif +#ifdef SIGPIPE + SIGPIPE, +#endif +#ifdef SIGQUIT + SIGQUIT, +#endif +#ifdef SIGTERM + SIGTERM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif +}; +#define SIGS (sizeof(sig)/sizeof(*sig)) + + +#if has_sigaction + + static void + check_sig(r) + int r; + { + if (r != 0) + efaterror("signal"); + } + + static void + setup_catchsig() + { + register int i; + sigset_t blocked; + struct sigaction act; + + check_sig(sigemptyset(&blocked)); + for (i=SIGS; 0<=--i; ) + check_sig(sigaddset(&blocked, sig[i])); + for (i=SIGS; 0<=--i; ) { + check_sig(sigaction(sig[i], (struct sigaction*)nil, &act)); + if (act.sa_handler != SIG_IGN) { + act.sa_handler = catchsig; + act.sa_mask = blocked; + check_sig(sigaction(sig[i], &act, (struct sigaction*)nil)); + } + } + } + +#else +#if has_sigblock + + static void + setup_catchsig() + { + register int i; + int mask; + + mask = 0; + for (i=SIGS; 0<=--i; ) + mask |= sigmask(sig[i]); + mask = sigblock(mask); + for (i=SIGS; 0<=--i; ) + if ( + signal(sig[i], catchsig) == SIG_IGN && + signal(sig[i], SIG_IGN) != catchsig + ) + faterror("signal catcher failure"); + VOID sigsetmask(mask); + } + +#else + + static void + setup_catchsig() + { + register i; + + for (i=SIGS; 0<=--i; ) + if ( + signal(sig[i], SIG_IGN) != SIG_IGN && + signal(sig[i], catchsig) != SIG_IGN + ) + faterror("signal catcher failure"); + } + +#endif +#endif + + void +catchints() +{ + static int catching_ints; + if (!catching_ints) { + catching_ints = true; + setup_catchsig(); + } +} + +#endif /* has_signal */ + + + void +fastcopy(inf,outf) + register RILE *inf; + FILE *outf; +/* Function: copies the remainder of file inf to outf. + */ +{ +#if large_memory +# if has_mmap + awrite((char const*)inf->ptr, (size_t)(inf->lim - inf->ptr), outf); + inf->ptr = inf->lim; +# else + for (;;) { + awrite((char const*)inf->ptr, (size_t)(inf->readlim - inf->ptr), outf); + inf->ptr = inf->readlim; + if (inf->ptr == inf->lim) + break; + VOID Igetmore(inf); + } +# endif +#else + char buf[BUFSIZ*8]; + register fread_type rcount; + + /*now read the rest of the file in blocks*/ + while (!feof(inf)) { + if (!(rcount = Fread(buf,sizeof(*buf),sizeof(buf),inf))) { + testIerror(inf); + return; + } + awrite(buf, (size_t)rcount, outf); + } +#endif +} + +#ifndef SSIZE_MAX + /* This does not work in #ifs, but it's good enough for us. */ + /* Underestimating SSIZE_MAX may slow us down, but it won't break us. */ +# define SSIZE_MAX ((unsigned)-1 >> 1) +#endif + + void +awrite(buf, chars, f) + char const *buf; + size_t chars; + FILE *f; +{ + /* Posix 1003.1-1990 ssize_t hack */ + while (SSIZE_MAX < chars) { + if (Fwrite(buf, sizeof(*buf), SSIZE_MAX, f) != SSIZE_MAX) + Oerror(); + buf += SSIZE_MAX; + chars -= SSIZE_MAX; + } + + if (Fwrite(buf, sizeof(*buf), chars, f) != chars) + Oerror(); +} + + + + + + static int +movefd(old, new) + int old, new; +{ + if (old < 0 || old == new) + return old; +# ifdef F_DUPFD + new = fcntl(old, F_DUPFD, new); +# else + new = dup2(old, new); +# endif + return close(old)==0 ? new : -1; +} + + static int +fdreopen(fd, file, flags) + int fd; + char const *file; + int flags; +{ + int newfd; + VOID close(fd); + newfd = +#if !open_can_creat + flags&O_CREAT ? creat(file, S_IRUSR|S_IWUSR) : +#endif + open(file, flags, S_IRUSR|S_IWUSR); + return movefd(newfd, fd); +} + +#if !has_spawn + static void +tryopen(fd,file,flags) + int fd, flags; + char const *file; +{ + if (file && fdreopen(fd,file,flags) != fd) + efaterror(file); +} +#else + static int +tryopen(fd,file,flags) + int fd, flags; + char const *file; +{ + int newfd = -1; + if (file && ((newfd=dup(fd)) < 0 || fdreopen(fd,file,flags) != fd)) + efaterror(file); + return newfd; +} + static void +redirect(old, new) + int old, new; +{ + if (0 <= old && (close(new) != 0 || movefd(old,new) < 0)) + efaterror("spawn I/O redirection"); +} +#endif + + + +#if !has_fork && !has_spawn + static void +bufargcat(b, c, s) + register struct buf *b; + int c; + register char const *s; +/* Append to B a copy of C, plus a quoted copy of S. */ +{ + register char *p; + register char const *t; + size_t bl, sl; + + for (t=s, sl=0; *t; ) + sl += 3*(*t++=='\'') + 1; + bl = strlen(b->string); + bufrealloc(b, bl + sl + 4); + p = b->string + bl; + *p++ = c; + *p++ = '\''; + while (*s) { + if (*s == '\'') { + *p++ = '\''; + *p++ = '\\'; + *p++ = '\''; + } + *p++ = *s++; + } + *p++ = '\''; + *p = 0; +} +#endif + +/* +* Run a command specified by the strings in 'inoutargs'. +* inoutargs[0], if nonnil, is the name of the input file. +* inoutargs[1], if nonnil, is the name of the output file. +* inoutargs[2..] form the command to be run. +*/ + int +runv(inoutargs) + char const **inoutargs; +{ + register char const **p; + int wstatus; + + oflush(); + eflush(); + { +#if has_spawn + int in, out; + p = inoutargs; + in = tryopen(STDIN_FILENO, *p++, O_BINARY|O_RDONLY); + out = tryopen(STDOUT_FILENO, *p++, O_BINARY|O_CREAT|O_TRUNC|O_WRONLY); + wstatus = spawn_RCS(0, *p, (char*const*)p); + if (wstatus == -1 && errno == ENOEXEC) { + *--p = RCS_SHELL; + wstatus = spawnv(0, *p, (char*const*)p); + } + redirect(in, STDIN_FILENO); + redirect(out, STDOUT_FILENO); +#else +#if has_fork + pid_t pid; +# if !has_waitpid + pid_t w; +# endif + if (!(pid = vfork())) { + p = inoutargs; + tryopen(STDIN_FILENO, *p++, O_BINARY|O_RDONLY); + tryopen(STDOUT_FILENO, *p++, O_BINARY|O_CREAT|O_TRUNC|O_WRONLY); + VOID exec_RCS(*p, (char*const*)p); + if (errno == ENOEXEC) { + *--p = RCS_SHELL; + VOID execv(*p, (char*const*)p); + } + VOID write(STDERR_FILENO, *p, strlen(*p)); + VOID write(STDERR_FILENO, ": not found\n", 12); + _exit(EXIT_TROUBLE); + } + if (pid < 0) + efaterror("fork"); +# if has_waitpid + if (waitpid(pid, &wstatus, 0) < 0) + efaterror("waitpid"); +# else + do { + if ((w = wait(&wstatus)) < 0) + efaterror("wait"); + } while (w != pid); +# endif +#else + static struct buf b; + + /* Use system(). On many hosts system() discards signals. Yuck! */ + p = inoutargs+2; + bufscpy(&b, *p); + while (*++p) + bufargcat(&b, ' ', *p); + if (inoutargs[0]) + bufargcat(&b, '<', inoutargs[0]); + if (inoutargs[1]) + bufargcat(&b, '>', inoutargs[1]); + wstatus = system(b.string); +#endif +#endif + } + if (!WIFEXITED(wstatus)) + faterror("%s failed", inoutargs[2]); + return WEXITSTATUS(wstatus); +} + +#define CARGSMAX 20 +/* +* Run a command. +* The first two arguments are the input and output files (if nonnil); +* the rest specify the command and its arguments. +*/ + int +#if has_prototypes +run(char const *infile, char const *outfile, ...) +#else + /*VARARGS2*/ +run(infile, outfile, va_alist) + char const *infile; + char const *outfile; + va_dcl +#endif +{ + va_list ap; + char const *rgargs[CARGSMAX]; + register i = 0; + rgargs[0] = infile; + rgargs[1] = outfile; + vararg_start(ap, outfile); + for (i = 2; (rgargs[i++] = va_arg(ap, char const*)); ) + if (CARGSMAX <= i) + faterror("too many command arguments"); + va_end(ap); + return runv(rgargs); +} + + + char const * +date2str(date, datebuf) + char const date[datesize]; + char datebuf[datesize]; +/* +* Format a user-readable form of the RCS format DATE into the buffer DATEBUF. +* Yield DATEBUF. +*/ +{ + register char const *p = date; + + while (*p++ != '.') + ; + VOID sprintf(datebuf, + "19%.*s/%.2s/%.2s %.2s:%.2s:%s" + + (date[2]=='.' && VERSION(5)<=RCSversion ? 0 : 2), + (int)(p-date-1), date, + p, p+3, p+6, p+9, p+12 + ); + return datebuf; +} + + +int RCSversion; + + void +setRCSversion(str) + char const *str; +{ + static int oldversion; + + register char const *s = str + 2; + int v = VERSION_DEFAULT; + + if (oldversion) + redefined('V'); + oldversion = true; + + if (*s) { + v = 0; + while (isdigit(*s)) + v = 10*v + *s++ - '0'; + if (*s) + faterror("%s isn't a number", str); + if (v < VERSION_min || VERSION_max < v) + faterror("%s out of range %d..%d", str, VERSION_min, VERSION_max); + } + + RCSversion = VERSION(v); +} + + int +getRCSINIT(argc, argv, newargv) + int argc; + char **argv, ***newargv; +{ + register char *p, *q, **pp; + unsigned n; + + if (!(q = cgetenv("RCSINIT"))) + *newargv = argv; + else { + n = argc + 2; + /* + * Count spaces in RCSINIT to allocate a new arg vector. + * This is an upper bound, but it's OK even if too large. + */ + for (p = q; ; ) { + switch (*p++) { + default: + continue; + + case ' ': + case '\b': case '\f': case '\n': + case '\r': case '\t': case '\v': + n++; + continue; + + case '\0': + break; + } + break; + } + *newargv = pp = tnalloc(char*, n); + *pp++ = *argv++; /* copy program name */ + for (p = q; ; ) { + for (;;) { + switch (*q) { + case '\0': + goto copyrest; + + case ' ': + case '\b': case '\f': case '\n': + case '\r': case '\t': case '\v': + q++; + continue; + } + break; + } + *pp++ = p; + ++argc; + for (;;) { + switch ((*p++ = *q++)) { + case '\0': + goto copyrest; + + case '\\': + if (!*q) + goto copyrest; + p[-1] = *q++; + continue; + + default: + continue; + + case ' ': + case '\b': case '\f': case '\n': + case '\r': case '\t': case '\v': + break; + } + break; + } + p[-1] = '\0'; + } + copyrest: + while ((*pp++ = *argv++)) + ; + } + return argc; +} + + +#define cacheid(E) static uid_t i; static int s; if (!s){ s=1; i=(E); } return i + +#if has_getuid + uid_t ruid() { cacheid(getuid()); } +#endif +#if has_setuid + uid_t euid() { cacheid(geteuid()); } +#endif + + +#if has_setuid + +/* + * Setuid execution really works only with Posix 1003.1a Draft 5 seteuid(), + * because it lets us switch back and forth between arbitrary users. + * If seteuid() doesn't work, we fall back on setuid(), + * which works if saved setuid is supported, + * unless the real or effective user is root. + * This area is such a mess that we always check switches at runtime. + */ + + static void +set_uid_to(u) + uid_t u; +/* Become user u. */ +{ + static int looping; + + if (euid() == ruid()) + return; +#if (has_fork||has_spawn) && DIFF_ABSOLUTE + if (seteuid(u) != 0) + efaterror("setuid"); +#endif + if (geteuid() != u) { + if (looping) + return; + looping = true; + faterror("root setuid not supported" + (u?5:0)); + } +} + +static int stick_with_euid; + + void +/* Ignore all calls to seteid() and setrid(). */ +nosetid() +{ + stick_with_euid = true; +} + + void +seteid() +/* Become effective user. */ +{ + if (!stick_with_euid) + set_uid_to(euid()); +} + + void +setrid() +/* Become real user. */ +{ + if (!stick_with_euid) + set_uid_to(ruid()); +} +#endif diff --git a/gnu/usr.bin/rcs/merge/Makefile b/gnu/usr.bin/rcs/merge/Makefile new file mode 100644 index 0000000..d14afb2 --- /dev/null +++ b/gnu/usr.bin/rcs/merge/Makefile @@ -0,0 +1,7 @@ +PROG= merge + +SRCS= merge.c +LDADD= -L${.CURDIR}/../lib/obj -lrcs +CFLAGS+= -I${.CURDIR}/../lib + +.include diff --git a/gnu/usr.bin/rcs/merge/merge.1 b/gnu/usr.bin/rcs/merge/merge.1 new file mode 100644 index 0000000..8b1957f --- /dev/null +++ b/gnu/usr.bin/rcs/merge/merge.1 @@ -0,0 +1,102 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: merge.1,v 5.3 1991/02/28 19:18:45 eggert Exp $ +.TH MERGE 1 \*(Dt GNU +.SH NAME +merge \- three-way file merge +.SH SYNOPSIS +.B merge +[ +.B \-L +.I label1 +[ +.B \-L +.I label3 +] ] [ +.B \-p +] [ +.B \-q +] +.I "file1 file2 file3" +.SH DESCRIPTION +.B merge +incorporates all changes that lead from +.I file2 +to +.I file3 +into +.IR file1 . +The result goes to standard output if +.B \-p +is present, into +.I file1 +otherwise. +.B merge +is useful for combining separate changes to an original. Suppose +.I file2 +is the original, and both +.I file1 +and +.I file3 +are modifications of +.IR file2 . +Then +.B merge +combines both changes. +.PP +An overlap occurs if both +.I file1 +and +.I file3 +have changes in a common segment of lines. +On a few older hosts where +.B diff3 +does not support the +.B \-E +option, +.B merge +does not detect overlaps, and merely supplies the changed lines from +.I file3. +On most hosts, if overlaps occur, +.B merge +outputs a message (unless the +.B \-q +option is given), +and includes both alternatives +in the result. The alternatives are delimited as follows: +.LP +.RS +.nf +.BI <<<<<<< " file1" +.I "lines in file1" +.B "=======" +.I "lines in file3" +.BI >>>>>>> " file3" +.RE +.fi +.LP +If there are overlaps, the user should edit the result and delete one of the +alternatives. +If the +.BI \-L "\ label1" +and +.BI \-L "\ label3" +options are given, the labels are output in place of the names +.I file1 +and +.I file3 +in overlap reports. +.SH DIAGNOSTICS +Exit status is 0 for no overlaps, 1 for some overlaps, 2 for trouble. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH SEE ALSO +diff3(1), diff(1), rcsmerge(1), co(1). diff --git a/gnu/usr.bin/rcs/merge/merge.c b/gnu/usr.bin/rcs/merge/merge.c new file mode 100644 index 0000000..4067c18 --- /dev/null +++ b/gnu/usr.bin/rcs/merge/merge.c @@ -0,0 +1,97 @@ +/* merge - three-way file merge */ + +/* Copyright 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +#include "rcsbase.h" + + +static char const usage[] = + "\nmerge: usage: merge [-p] [-q] [-L label1 [-L label3]] file1 file2 file3\n"; + + static exiting void +badoption(a) + char const *a; +{ + faterror("unknown option: %s%s", a-2, usage); +} + + +mainProg(mergeId, "merge", "$Id: merge.c,v 1.2 1991/08/19 03:13:55 eggert Exp $") +{ + register char const *a; + char const *label[2], *arg[3]; + int labels, tostdout; + + labels = 0; + tostdout = false; + + while ((a = *++argv) && *a++ == '-') { + switch (*a++) { + case 'p': tostdout = true; break; + case 'q': quietflag = true; break; + case 'L': + if (1 diff --git a/gnu/usr.bin/rcs/rcs/rcs.1 b/gnu/usr.bin/rcs/rcs/rcs.1 new file mode 100644 index 0000000..9866a9c --- /dev/null +++ b/gnu/usr.bin/rcs/rcs/rcs.1 @@ -0,0 +1,397 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rcs.1,v 5.6 1991/09/26 23:16:17 eggert Exp $ +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH RCS 1 \*(Dt GNU +.SH NAME +rcs \- change RCS file attributes +.SH SYNOPSIS +.B rcs +.RI [ " options " ] " file " .\|.\|. +.SH DESCRIPTION +.B rcs +creates new \*r files or changes attributes of existing ones. +An \*r file contains multiple revisions of text, +an access list, a change log, +descriptive text, +and some control attributes. +For +.B rcs +to work, the caller's login name must be on the access list, +except if the access list is empty, the caller is the owner of the file +or the superuser, or +the +.B \-i +option is present. +.PP +Pathnames matching an \*r suffix denote \*r files; +all others denote working files. +Names are paired as explained in +.BR ci (1). +Revision numbers use the syntax described in +.BR ci (1). +.SH OPTIONS +.TP +.B \-i +Create and initialize a new \*r file, but do not deposit any revision. +If the \*r file has no path prefix, try to place it +first into the subdirectory +.BR ./RCS , +and then into the current directory. +If the \*r file +already exists, print an error message. +.TP +.BI \-a "logins" +Append the login names appearing in the comma-separated list +.I logins +to the access list of the \*r file. +.TP +.BI \-A "oldfile" +Append the access list of +.I oldfile +to the access list of the \*r file. +.TP +.BR \-e [\f2logins\fP] +Erase the login names appearing in the comma-separated list +.I logins +from the access list of the \*r file. +If +.I logins +is omitted, erase the entire access list. +.TP +.BR \-b [\f2rev\fP] +Set the default branch to +.IR rev . +If +.I rev +is omitted, the default +branch is reset to the (dynamically) highest branch on the trunk. +.TP +.BI \-c string +sets the comment leader to +.IR string . +The comment leader +is printed before every log message line generated by the keyword +.B $\&Log$ +during checkout (see +.BR co (1)). +This is useful for programming +languages without multi-line comments. +An initial +.B ci , +or an +.B "rcs\ \-i" +without +.BR \-c , +guesses the comment leader from the suffix of the working file. +.TP +.BI \-k subst +Set the default keyword substitution to +.IR subst . +The effect of keyword substitution is described in +.BR co (1). +Giving an explicit +.B \-k +option to +.BR co , +.BR rcsdiff , +and +.B rcsmerge +overrides this default. +Beware +.BR "rcs\ \-kv", +because +.B \-kv +is incompatible with +.BR "co\ \-l". +Use +.B "rcs\ \-kkv" +to restore the normal default keyword substitution. +.TP +.BR \-l [\f2rev\fP] +Lock the revision with number +.IR rev . +If a branch is given, lock the latest revision on that branch. +If +.I rev +is omitted, lock the latest revision on the default branch. +Locking prevents overlapping changes. +A lock is removed with +.B ci +or +.B "rcs\ \-u" +(see below). +.TP +.BR \-u [\f2rev\fP] +Unlock the revision with number +.IR rev . +If a branch is given, unlock the latest revision on that branch. +If +.I rev +is omitted, remove the latest lock held by the caller. +Normally, only the locker of a revision may unlock it. +Somebody else unlocking a revision breaks the lock. +This causes a mail message to be sent to the original locker. +The message contains a commentary solicited from the breaker. +The commentary is terminated by end-of-file or by a line containing +.BR \&. "\ by" +itself. +.TP +.B \-L +Set locking to +.IR strict . +Strict locking means that the owner +of an \*r file is not exempt from locking for checkin. +This option should be used for files that are shared. +.TP +.B \-U +Set locking to non-strict. Non-strict locking means that the owner of +a file need not lock a revision for checkin. +This option should +.I not +be used for files that are shared. +Whether default locking is strict is determined by your system administrator, +but it is normally strict. +.TP +\f3\-m\fP\f2rev\fP\f3:\fP\f2msg\fP +Replace revision +.IR rev 's +log message with +.IR msg . +.TP +\f3\-n\fP\f2name\fP[\f3:\fP[\f2rev\fP]] +Associate the symbolic name +.I name +with the branch or +revision +.IR rev . +Delete the symbolic name if both +.B : +and +.I rev +are omitted; otherwise, print an error message if +.I name +is already associated with +another number. +If +.I rev +is symbolic, it is expanded before association. +A +.I rev +consisting of a branch number followed by a +.B .\& +stands for the current latest revision in the branch. +A +.B : +with an empty +.I rev +stands for the current latest revision on the default branch, +normally the trunk. +For example, +.BI "rcs\ \-n" name ":\ RCS/*" +associates +.I name +with the current latest revision of all the named \*r files; +this contrasts with +.BI "rcs\ \-n" name ":$\ RCS/*" +which associates +.I name +with the revision numbers extracted from keyword strings +in the corresponding working files. +.TP +\f3\-N\fP\f2name\fP[\f3:\fP[\f2rev\fP]] +Act like +.BR \-n , +except override any previous assignment of +.IR name . +.TP +.BI \-o range +deletes (\*(lqoutdates\*(rq) the revisions given by +.IR range . +A range consisting of a single revision number means that revision. +A range consisting of a branch number means the latest revision on that +branch. +A range of the form +.IB rev1 : rev2 +means +revisions +.I rev1 +to +.I rev2 +on the same branch, +.BI : rev +means from the beginning of the branch containing +.I rev +up to and including +.IR rev , +and +.IB rev : +means +from revision +.I rev +to the end of the branch containing +.IR rev . +None of the outdated revisions may have branches or locks. +.TP +.B \-q +Run quietly; do not print diagnostics. +.TP +.B \-I +Run interactively, even if the standard input is not a terminal. +.TP +.B \-s\f2state\fP\f1[\fP:\f2rev\fP\f1]\fP +Set the state attribute of the revision +.I rev +to +.I state . +If +.I rev +is a branch number, assume the latest revision on that branch. +If +.I rev +is omitted, assume the latest revision on the default branch. +Any identifier is acceptable for +.IR state . +A useful set of states +is +.B Exp +(for experimental), +.B Stab +(for stable), and +.B Rel +(for +released). +By default, +.BR ci (1) +sets the state of a revision to +.BR Exp . +.TP +.BR \-t [\f2file\fP] +Write descriptive text from the contents of the named +.I file +into the \*r file, deleting the existing text. +The +.IR file +pathname may not begin with +.BR \- . +If +.I file +is omitted, obtain the text from standard input, +terminated by end-of-file or by a line containing +.BR \&. "\ by" +itself. +Prompt for the text if interaction is possible; see +.BR \-I . +With +.BR \-i , +descriptive text is obtained +even if +.B \-t +is not given. +.TP +.BI \-t\- string +Write descriptive text from the +.I string +into the \*r file, deleting the existing text. +.TP +.BI \-V n +Emulate \*r version +.IR n . +See +.BR co (1) +for details. +.TP +.BI \-x "suffixes" +Use +.I suffixes +to characterize \*r files. +See +.BR ci (1) +for details. +.SH COMPATIBILITY +The +.BI \-b rev +option generates an \*r file that cannot be parsed by \*r version 3 or earlier. +.PP +The +.BI \-k subst +options (except +.BR \-kkv ) +generate an \*r file that cannot be parsed by \*r version 4 or earlier. +.PP +Use +.BI "rcs \-V" n +to make an \*r file acceptable to \*r version +.I n +by discarding information that would confuse version +.IR n . +.PP +\*r version 5.5 and earlier does not support the +.B \-x +option, and requires a +.B ,v +suffix on an \*r pathname. +.SH FILES +.B rcs +accesses files much as +.BR ci (1) +does, +except that it uses the effective user for all accesses, +it does not write the working file or its directory, +and it does not even read the working file unless a revision number of +.B $ +is specified. +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +See +.BR ci (1) +for details. +.SH DIAGNOSTICS +The \*r pathname and the revisions outdated are written to +the diagnostic output. +The exit status is zero if and only if all operations were successful. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +co(1), ci(1), ident(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1), +rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.SH BUGS +The separator for revision ranges in the +.B \-o +option used to be +.B \- +instead of +.BR : , +but this leads to confusion when symbolic names contain +.BR \- . +For backwards compatibility +.B "rcs \-o" +still supports the old +.B \- +separator, but it warns about this obsolete use. +.PP +Symbolic names need not refer to existing revisions or branches. +For example, the +.B \-o +option does not remove symbolic names for the outdated revisions; you must use +.B \-n +to remove the names. +.br diff --git a/gnu/usr.bin/rcs/rcs/rcs.c b/gnu/usr.bin/rcs/rcs/rcs.c new file mode 100644 index 0000000..70e7ffc --- /dev/null +++ b/gnu/usr.bin/rcs/rcs/rcs.c @@ -0,0 +1,1554 @@ +/* + * RCS create/change operation + */ +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + + +/* $Log: rcs.c,v $ + * Revision 5.12 1991/11/20 17:58:08 eggert + * Don't read the delta tree from a nonexistent RCS file. + * + * Revision 5.11 1991/10/07 17:32:46 eggert + * Remove lint. + * + * Revision 5.10 1991/08/19 23:17:54 eggert + * Add -m, -r$, piece tables. Revision separator is `:', not `-'. Tune. + * + * Revision 5.9 1991/04/21 11:58:18 eggert + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.8 1991/02/25 07:12:38 eggert + * strsave -> str_save (DG/UX name clash) + * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability + * + * Revision 5.7 1990/12/18 17:19:21 eggert + * Fix bug with multiple -n and -N options. + * + * Revision 5.6 1990/12/04 05:18:40 eggert + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.5 1990/11/11 00:06:35 eggert + * Fix `rcs -e' core dump. + * + * Revision 5.4 1990/11/01 05:03:33 eggert + * Add -I and new -t behavior. Permit arbitrary data in logs. + * + * Revision 5.3 1990/10/04 06:30:16 eggert + * Accumulate exit status across files. + * + * Revision 5.2 1990/09/04 08:02:17 eggert + * Standardize yes-or-no procedure. + * + * Revision 5.1 1990/08/29 07:13:51 eggert + * Remove unused setuid support. Clean old log messages too. + * + * Revision 5.0 1990/08/22 08:12:42 eggert + * Don't lose names when applying -a option to multiple files. + * Remove compile-time limits; use malloc instead. Add setuid support. + * Permit dates past 1999/12/31. Make lock and temp files faster and safer. + * Ansify and Posixate. Add -V. Fix umask bug. Make linting easier. Tune. + * Yield proper exit status. Check diff's output. + * + * Revision 4.11 89/05/01 15:12:06 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.10 88/11/08 16:01:54 narten + * didn't install previous patch correctly + * + * Revision 4.9 88/11/08 13:56:01 narten + * removed include (not needed) + * minor fix for -A option + * + * Revision 4.8 88/08/09 19:12:27 eggert + * Don't access freed storage. + * Use execv(), not system(); yield proper exit status; remove lint. + * + * Revision 4.7 87/12/18 11:37:17 narten + * lint cleanups (Guy Harris) + * + * Revision 4.6 87/10/18 10:28:48 narten + * Updating verison numbers. Changes relative to 1.1 are actually + * relative to 4.3 + * + * Revision 1.4 87/09/24 13:58:52 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.3 87/03/27 14:21:55 jenkins + * Port to suns + * + * Revision 1.2 85/12/17 13:59:09 albitz + * Changed setstate to rcs_setstate because of conflict with random.o. + * + * Revision 4.3 83/12/15 12:27:33 wft + * rcs -u now breaks most recent lock if it can't find a lock by the caller. + * + * Revision 4.2 83/12/05 10:18:20 wft + * Added conditional compilation for sending mail. + * Alternatives: V4_2BSD, V6, USG, and other. + * + * Revision 4.1 83/05/10 16:43:02 wft + * Simplified breaklock(); added calls to findlock() and getcaller(). + * Added option -b (default branch). Updated -s and -w for -b. + * Removed calls to stat(); now done by pairfilenames(). + * Replaced most catchints() calls with restoreints(). + * Removed check for exit status of delivermail(). + * Directed all interactive output to stderr. + * + * Revision 3.9.1.1 83/12/02 22:08:51 wft + * Added conditional compilation for 4.2 sendmail and 4.1 delivermail. + * + * Revision 3.9 83/02/15 15:38:39 wft + * Added call to fastcopy() to copy remainder of RCS file. + * + * Revision 3.8 83/01/18 17:37:51 wft + * Changed sendmail(): now uses delivermail, and asks whether to break the lock. + * + * Revision 3.7 83/01/15 18:04:25 wft + * Removed putree(); replaced with puttree() in rcssyn.c. + * Combined putdellog() and scanlogtext(); deleted putdellog(). + * Cleaned up diagnostics and error messages. Fixed problem with + * mutilated files in case of deletions in 2 files in a single command. + * Changed marking of selector from 'D' to DELETE. + * + * Revision 3.6 83/01/14 15:37:31 wft + * Added ignoring of interrupts while new RCS file is renamed; + * Avoids deletion of RCS files by interrupts. + * + * Revision 3.5 82/12/10 21:11:39 wft + * Removed unused variables, fixed checking of return code from diff, + * introduced variant COMPAT2 for skipping Suffix on -A files. + * + * Revision 3.4 82/12/04 13:18:20 wft + * Replaced getdelta() with gettree(), changed breaklock to update + * field lockedby, added some diagnostics. + * + * Revision 3.3 82/12/03 17:08:04 wft + * Replaced getlogin() with getpwuid(), flcose() with ffclose(), + * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x). + * fixed -u for missing revno. Disambiguated structure members. + * + * Revision 3.2 82/10/18 21:05:07 wft + * rcs -i now generates a file mode given by the umask minus write permission; + * otherwise, rcs keeps the mode, but removes write permission. + * I added a check for write error, fixed call to getlogin(), replaced + * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed + * conflicting, long identifiers. + * + * Revision 3.1 82/10/13 16:11:07 wft + * fixed type of variables receiving from getc() (char -> int). + */ + + +#include "rcsbase.h" + +struct Lockrev { + char const *revno; + struct Lockrev * nextrev; +}; + +struct Symrev { + char const *revno; + char const *ssymbol; + int override; + struct Symrev * nextsym; +}; + +struct Message { + char const *revno; + struct cbuf message; + struct Message *nextmessage; +}; + +struct Status { + char const *revno; + char const *status; + struct Status * nextstatus; +}; + +enum changeaccess {append, erase}; +struct chaccess { + char const *login; + enum changeaccess command; + struct chaccess *nextchaccess; +}; + +struct delrevpair { + char const *strt; + char const *end; + int code; +}; + +static int buildeltatext P((struct hshentries const*)); +static int removerevs P((void)); +static int sendmail P((char const*,char const*)); +static struct Lockrev *rmnewlocklst P((struct Lockrev const*)); +static void breaklock P((struct hshentry const*)); +static void buildtree P((void)); +static void cleanup P((void)); +static void doaccess P((void)); +static void doassoc P((void)); +static void dolocks P((void)); +static void domessages P((void)); +static void getaccessor P((char*,enum changeaccess)); +static void getassoclst P((int,char*)); +static void getchaccess P((char const*,enum changeaccess)); +static void getdelrev P((char*)); +static void getmessage P((char*)); +static void getstates P((char*)); +static void rcs_setstate P((char const*,char const*)); +static void scanlogtext P((struct hshentry*,int)); +static void setlock P((char const*)); + +static struct buf numrev; +static char const *headstate; +static int chgheadstate, exitstatus, lockhead, unlockcaller; +static struct Lockrev *newlocklst, *rmvlocklst; +static struct Message *messagelst, *lastmessage; +static struct Status *statelst, *laststate; +static struct Symrev *assoclst, *lastassoc; +static struct chaccess *chaccess, **nextchaccess; +static struct delrevpair delrev; +static struct hshentry *cuthead, *cuttail, *delstrt; +static struct hshentries *gendeltas; + +mainProg(rcsId, "rcs", "$Id: rcs.c,v 5.12 1991/11/20 17:58:08 eggert Exp $") +{ + static char const cmdusage[] = + "\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iLU} -{nNs}name[:rev] -orange -t[file] -Vn file ..."; + + char *a, **newargv, *textfile; + char const *branchsym, *commsyml; + int branchflag, expmode, initflag; + int e, r, strictlock, strict_selected, textflag; + mode_t defaultRCSmode; /* default mode for new RCS files */ + mode_t RCSmode; + struct buf branchnum; + struct stat workstat; + struct Lockrev *curlock, * rmvlock, *lockpt; + struct Status * curstate; + + nosetid(); + + nextchaccess = &chaccess; + branchsym = commsyml = textfile = nil; + branchflag = strictlock = false; + bufautobegin(&branchnum); + curlock = rmvlock = nil; + defaultRCSmode = 0; + expmode = -1; + suffixes = X_DEFAULT; + initflag= textflag = false; + strict_selected = 0; + + /* preprocessing command options */ + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + while (a = *++argv, 0<--argc && *a++=='-') { + switch (*a++) { + + case 'i': /* initial version */ + initflag = true; + break; + + case 'b': /* change default branch */ + if (branchflag) redefined('b'); + branchflag= true; + branchsym = a; + break; + + case 'c': /* change comment symbol */ + if (commsyml) redefined('c'); + commsyml = a; + break; + + case 'a': /* add new accessor */ + getaccessor(*argv+1, append); + break; + + case 'A': /* append access list according to accessfile */ + if (!*a) { + error("missing file name after -A"); + break; + } + *argv = a; + if (0 < pairfilenames(1,argv,rcsreadopen,true,false)) { + while (AccessList) { + getchaccess(str_save(AccessList->login),append); + AccessList = AccessList->nextaccess; + } + Izclose(&finptr); + } + break; + + case 'e': /* remove accessors */ + getaccessor(*argv+1, erase); + break; + + case 'l': /* lock a revision if it is unlocked */ + if (!*a) { + /* Lock head or default branch. */ + lockhead = true; + break; + } + lockpt = talloc(struct Lockrev); + lockpt->revno = a; + lockpt->nextrev = nil; + if ( curlock ) + curlock->nextrev = lockpt; + else + newlocklst = lockpt; + curlock = lockpt; + break; + + case 'u': /* release lock of a locked revision */ + if (!*a) { + unlockcaller=true; + break; + } + lockpt = talloc(struct Lockrev); + lockpt->revno = a; + lockpt->nextrev = nil; + if (rmvlock) + rmvlock->nextrev = lockpt; + else + rmvlocklst = lockpt; + rmvlock = lockpt; + + curlock = rmnewlocklst(lockpt); + break; + + case 'L': /* set strict locking */ + if (strict_selected++) { /* Already selected L or U? */ + if (!strictlock) /* Already selected -U? */ + warn("-L overrides -U."); + } + strictlock = true; + break; + + case 'U': /* release strict locking */ + if (strict_selected++) { /* Already selected L or U? */ + if (strictlock) /* Already selected -L? */ + warn("-L overrides -U."); + } + else + strictlock = false; + break; + + case 'n': /* add new association: error, if name exists */ + if (!*a) { + error("missing symbolic name after -n"); + break; + } + getassoclst(false, (*argv)+1); + break; + + case 'N': /* add or change association */ + if (!*a) { + error("missing symbolic name after -N"); + break; + } + getassoclst(true, (*argv)+1); + break; + + case 'm': /* change log message */ + getmessage(a); + break; + + case 'o': /* delete revisions */ + if (delrev.strt) redefined('o'); + if (!*a) { + error("missing revision range after -o"); + break; + } + getdelrev( (*argv)+1 ); + break; + + case 's': /* change state attribute of a revision */ + if (!*a) { + error("state missing after -s"); + break; + } + getstates( (*argv)+1); + break; + + case 't': /* change descriptive text */ + textflag=true; + if (*a) { + if (textfile) redefined('t'); + textfile = a; + } + break; + + case 'I': + interactiveflag = true; + break; + + case 'q': + quietflag = true; + break; + + case 'x': + suffixes = a; + break; + + case 'V': + setRCSversion(*argv); + break; + + case 'k': /* set keyword expand mode */ + if (0 <= expmode) redefined('k'); + if (0 <= (expmode = str2expmode(a))) + break; + /* fall into */ + default: + faterror("unknown option: %s%s", *argv, cmdusage); + }; + } /* end processing of options */ + + if (argc<1) faterror("no input file%s", cmdusage); + if (nerror) { + diagnose("%s aborted\n",cmdid); + exitmain(EXIT_FAILURE); + } + if (initflag) { + defaultRCSmode = umask((mode_t)0); + VOID umask(defaultRCSmode); + defaultRCSmode = (S_IRUSR|S_IRGRP|S_IROTH) & ~defaultRCSmode; + } + + /* now handle all filenames */ + do { + ffree(); + + if ( initflag ) { + switch (pairfilenames(argc, argv, rcswriteopen, false, false)) { + case -1: break; /* not exist; ok */ + case 0: continue; /* error */ + case 1: error("file %s exists already", RCSfilename); + continue; + } + } + else { + switch (pairfilenames(argc, argv, rcswriteopen, true, false)) { + case -1: continue; /* not exist */ + case 0: continue; /* errors */ + case 1: break; /* file exists; ok*/ + } + } + + + /* now RCSfilename contains the name of the RCS file, and + * workfilename contains the name of the working file. + * if !initflag, finptr contains the file descriptor for the + * RCS file. The admin node is initialized. + */ + + diagnose("RCS file: %s\n", RCSfilename); + + RCSmode = defaultRCSmode; + if (initflag) { + if (stat(workfilename, &workstat) == 0) + RCSmode = workstat.st_mode; + } else { + if (!checkaccesslist()) continue; + gettree(); /* Read the delta tree. */ + RCSmode = RCSstat.st_mode; + } + RCSmode &= ~(S_IWUSR|S_IWGRP|S_IWOTH); + + /* update admin. node */ + if (strict_selected) StrictLocks = strictlock; + if (commsyml) { + Comment.string = commsyml; + Comment.size = strlen(commsyml); + } + if (0 <= expmode) Expand = expmode; + + /* update default branch */ + if (branchflag && expandsym(branchsym, &branchnum)) { + if (countnumflds(branchnum.string)) { + Dbranch = branchnum.string; + } else + Dbranch = nil; + } + + doaccess(); /* Update access list. */ + + doassoc(); /* Update association list. */ + + dolocks(); /* Update locks. */ + + domessages(); /* Update log messages. */ + + /* update state attribution */ + if (chgheadstate) { + /* change state of default branch or head */ + if (Dbranch==nil) { + if (Head==nil) + warn("can't change states in an empty tree"); + else Head->state = headstate; + } else { + rcs_setstate(Dbranch,headstate); /* Can't set directly */ + } + } + curstate = statelst; + while( curstate ) { + rcs_setstate(curstate->revno,curstate->status); + curstate = curstate->nextstatus; + } + + cuthead = cuttail = nil; + if (delrev.strt && removerevs()) { + /* rebuild delta tree if some deltas are deleted */ + if ( cuttail ) + VOID genrevs(cuttail->num, (char *)nil,(char *)nil, + (char *)nil, &gendeltas); + buildtree(); + } + + if (nerror) + continue; + + putadmin(frewrite); + if ( Head ) + puttree(Head, frewrite); + putdesc(textflag,textfile); + + if ( Head) { + if (!delrev.strt && !messagelst) { + /* No revision was deleted and no message was changed. */ + fastcopy(finptr, frewrite); + } else { + if (!cuttail || buildeltatext(gendeltas)) { + advise_access(finptr, MADV_SEQUENTIAL); + scanlogtext((struct hshentry *)nil, false); + /* copy rest of delta text nodes that are not deleted */ + } + } + } + Izclose(&finptr); + if ( ! nerror ) { /* move temporary file to RCS file if no error */ + /* update mode */ + ignoreints(); + r = chnamemod(&frewrite, newRCSfilename, RCSfilename, RCSmode); + e = errno; + keepdirtemp(newRCSfilename); + restoreints(); + if (r != 0) { + enerror(e, RCSfilename); + error("saved in %s", newRCSfilename); + dirtempunlink(); + break; + } + diagnose("done\n"); + } else { + diagnose("%s aborted; %s unchanged.\n",cmdid,RCSfilename); + } + } while (cleanup(), + ++argv, --argc >=1); + + tempunlink(); + exitmain(exitstatus); +} /* end of main (rcs) */ + + static void +cleanup() +{ + if (nerror) exitstatus = EXIT_FAILURE; + Izclose(&finptr); + Ozclose(&fcopy); + Ozclose(&frewrite); + dirtempunlink(); +} + + exiting void +exiterr() +{ + dirtempunlink(); + tempunlink(); + _exit(EXIT_FAILURE); +} + + + static void +getassoclst(flag, sp) +int flag; +char * sp; +/* Function: associate a symbolic name to a revision or branch, */ +/* and store in assoclst */ + +{ + struct Symrev * pt; + char const *temp; + int c; + + while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n') ; + temp = sp; + sp = checkid(sp, ':'); /* check for invalid symbolic name */ + c = *sp; *sp = '\0'; + while( c == ' ' || c == '\t' || c == '\n') c = *++sp; + + if ( c != ':' && c != '\0') { + error("invalid string %s after option -n or -N",sp); + return; + } + + pt = talloc(struct Symrev); + pt->ssymbol = temp; + pt->override = flag; + if (c == '\0') /* delete symbol */ + pt->revno = nil; + else { + while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ; + pt->revno = sp; + } + pt->nextsym = nil; + if (lastassoc) + lastassoc->nextsym = pt; + else + assoclst = pt; + lastassoc = pt; + return; +} + + + static void +getchaccess(login, command) + char const *login; + enum changeaccess command; +{ + register struct chaccess *pt; + + *nextchaccess = pt = talloc(struct chaccess); + pt->login = login; + pt->command = command; + pt->nextchaccess = nil; + nextchaccess = &pt->nextchaccess; +} + + + + static void +getaccessor(opt, command) + char *opt; + enum changeaccess command; +/* Function: get the accessor list of options -e and -a, */ +/* and store in chaccess */ + + +{ + register c; + register char *sp; + + sp = opt; + while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ; + if ( c == '\0') { + if (command == erase && sp-opt == 1) { + getchaccess((char const*)nil, command); + return; + } + error("missing login name after option -a or -e"); + return; + } + + while( c != '\0') { + getchaccess(sp, command); + sp = checkid(sp,','); + c = *sp; *sp = '\0'; + while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp); + } +} + + + static void +getmessage(option) + char *option; +{ + struct Message *pt; + struct cbuf cb; + char *m; + + if (!(m = strchr(option, ':'))) { + error("-m option lacks revision number"); + return; + } + *m++ = 0; + cb = cleanlogmsg(m, strlen(m)); + if (!cb.size) { + error("-m option lacks log message"); + return; + } + pt = talloc(struct Message); + pt->revno = option; + pt->message = cb; + pt->nextmessage = 0; + if (lastmessage) + lastmessage->nextmessage = pt; + else + messagelst = pt; + lastmessage = pt; +} + + + static void +getstates(sp) +char *sp; +/* Function: get one state attribute and the corresponding */ +/* revision and store in statelst */ + +{ + char const *temp; + struct Status *pt; + register c; + + while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n') ; + temp = sp; + sp = checkid(sp,':'); /* check for invalid state attribute */ + c = *sp; *sp = '\0'; + while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp; + + if ( c == '\0' ) { /* change state of def. branch or Head */ + chgheadstate = true; + headstate = temp; + return; + } + else if ( c != ':' ) { + error("missing ':' after state in option -s"); + return; + } + + while( (c = *++sp) == ' ' || c == '\t' || c == '\n') ; + pt = talloc(struct Status); + pt->status = temp; + pt->revno = sp; + pt->nextstatus = nil; + if (laststate) + laststate->nextstatus = pt; + else + statelst = pt; + laststate = pt; +} + + + + static void +getdelrev(sp) +char *sp; +/* Function: get revision range or branch to be deleted, */ +/* and place in delrev */ +{ + int c; + struct delrevpair *pt; + int separator; + + pt = &delrev; + while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ; + + /* Support old ambiguous '-' syntax; this will go away. */ + if (strchr(sp,':')) + separator = ':'; + else { + if (strchr(sp,'-') && VERSION(5) <= RCSversion) + warn("`-' is obsolete in `-o%s'; use `:' instead", sp); + separator = '-'; + } + + if (c == separator) { /* -o:rev */ + while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t') ; + pt->strt = sp; pt->code = 1; + while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp); + *sp = '\0'; + pt->end = nil; + return; + } + else { + pt->strt = sp; + while( c != ' ' && c != '\n' && c != '\t' && c != '\0' + && c != separator ) c = *++sp; + *sp = '\0'; + while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp; + if ( c == '\0' ) { /* -o rev or branch */ + pt->end = nil; pt->code = 0; + return; + } + if (c != separator) { + faterror("invalid range %s %s after -o", pt->strt, sp); + } + while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ; + if (!c) { /* -orev: */ + pt->end = nil; pt->code = 2; + return; + } + } + /* -orev1:rev2 */ + pt->end = sp; pt->code = 3; + while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp; + *sp = '\0'; +} + + + + + static void +scanlogtext(delta,edit) + struct hshentry *delta; + int edit; +/* Function: Scans delta text nodes up to and including the one given + * by delta, or up to last one present, if delta==nil. + * For the one given by delta (if delta!=nil), the log message is saved into + * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied. + * Assumes the initial lexeme must be read in first. + * Does not advance nexttok after it is finished, except if delta==nil. + */ +{ + struct hshentry const *nextdelta; + struct cbuf cb; + + for (;;) { + foutptr = 0; + if (eoflex()) { + if(delta) + faterror("can't find delta for revision %s", delta->num); + return; /* no more delta text nodes */ + } + nextlex(); + if (!(nextdelta=getnum())) + faterror("delta number corrupted"); + if (nextdelta->selector) { + foutptr = frewrite; + aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog); + } + getkeystring(Klog); + if (nextdelta == cuttail) { + cb = savestring(&curlogbuf); + if (!delta->log.string) + delta->log = cleanlogmsg(curlogbuf.string, cb.size); + } else if (nextdelta->log.string && nextdelta->selector) { + foutptr = 0; + readstring(); + foutptr = frewrite; + putstring(foutptr, false, nextdelta->log, true); + afputc(nextc, foutptr); + } else {readstring(); + } + nextlex(); + while (nexttok==ID && strcmp(NextString,Ktext)!=0) + ignorephrase(); + getkeystring(Ktext); + + if (delta==nextdelta) + break; + readstring(); /* skip over it */ + + } + /* got the one we're looking for */ + if (edit) + editstring((struct hshentry *)nil); + else + enterstring(); +} + + + + static struct Lockrev * +rmnewlocklst(which) + struct Lockrev const *which; +/* Function: remove lock to revision which->revno from newlocklst */ + +{ + struct Lockrev * pt, *pre; + + while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){ + struct Lockrev *pn = newlocklst->nextrev; + tfree(newlocklst); + newlocklst = pn; + } + + pt = pre = newlocklst; + while( pt ) { + if ( ! strcmp(pt->revno, which->revno) ) { + pre->nextrev = pt->nextrev; + tfree(pt); + pt = pre->nextrev; + } + else { + pre = pt; + pt = pt->nextrev; + } + } + return pre; +} + + + + static void +doaccess() +{ + register struct chaccess *ch; + register struct access **p, *t; + + for (ch = chaccess; ch; ch = ch->nextchaccess) { + switch (ch->command) { + case erase: + if (!ch->login) + AccessList = nil; + else + for (p = &AccessList; (t = *p); ) + if (strcmp(ch->login, t->login) == 0) + *p = t->nextaccess; + else + p = &t->nextaccess; + break; + case append: + for (p = &AccessList; ; p = &t->nextaccess) + if (!(t = *p)) { + *p = t = ftalloc(struct access); + t->login = ch->login; + t->nextaccess = nil; + break; + } else if (strcmp(ch->login, t->login) == 0) + break; + break; + } + } +} + + + static int +sendmail(Delta, who) + char const *Delta, *who; +/* Function: mail to who, informing him that his lock on delta was + * broken by caller. Ask first whether to go ahead. Return false on + * error or if user decides not to break the lock. + */ +{ +#ifdef SENDMAIL + char const *messagefile; + int old1, old2, c; + FILE * mailmess; +#endif + + + aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who); + if (!yesorno(false, "Do you want to break the lock? [ny](n): ")) + return false; + + /* go ahead with breaking */ +#ifdef SENDMAIL + messagefile = maketemp(0); + if (!(mailmess = fopen(messagefile, "w"))) { + efaterror(messagefile); + } + + aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n", + basename(RCSfilename), Delta, getfullRCSname(), getcaller() + ); + aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr); + + old1 = '\n'; old2 = ' '; + for (; ;) { + c = getcstdin(); + if (feof(stdin)) { + aprintf(mailmess, "%c\n", old1); + break; + } + else if ( c == '\n' && old1 == '.' && old2 == '\n') + break; + else { + afputc(old1, mailmess); + old2 = old1; old1 = c; + if (c=='\n') aputs(">> ", stderr); + } + } + Ozclose(&mailmess); + + if (run(messagefile, (char*)nil, SENDMAIL, who, (char*)nil)) + warn("Mail may have failed."), +#else + warn("Mail notification of broken locks is not available."), +#endif + warn("Please tell `%s' why you broke the lock.", who); + return(true); +} + + + + static void +breaklock(delta) + struct hshentry const *delta; +/* function: Finds the lock held by caller on delta, + * and removes it. + * Sends mail if a lock different from the caller's is broken. + * Prints an error message if there is no such lock or error. + */ +{ + register struct lock * next, * trail; + char const *num; + struct lock dummy; + + num=delta->num; + dummy.nextlock=next=Locks; + trail = &dummy; + while (next!=nil) { + if (strcmp(num, next->delta->num) == 0) { + if ( + strcmp(getcaller(),next->login) != 0 + && !sendmail(num, next->login) + ) { + error("%s still locked by %s", num, next->login); + return; + } + break; /* exact match */ + } + trail=next; + next=next->nextlock; + } + if (next!=nil) { + /*found one */ + diagnose("%s unlocked\n",next->delta->num); + trail->nextlock=next->nextlock; + next->delta->lockedby=nil; + Locks=dummy.nextlock; + } else { + error("no lock set on revision %s", num); + } +} + + + + static struct hshentry * +searchcutpt(object, length, store) + char const *object; + unsigned length; + struct hshentries *store; +/* Function: Search store and return entry with number being object. */ +/* cuttail = nil, if the entry is Head; otherwise, cuttail */ +/* is the entry point to the one with number being object */ + +{ + cuthead = nil; + while (compartial(store->first->num, object, length)) { + cuthead = store->first; + store = store->rest; + } + return store->first; +} + + + + static int +branchpoint(strt, tail) +struct hshentry *strt, *tail; +/* Function: check whether the deltas between strt and tail */ +/* are locked or branch point, return 1 if any is */ +/* locked or branch point; otherwise, return 0 and */ +/* mark deleted */ + +{ + struct hshentry *pt; + struct lock const *lockpt; + int flag; + + + pt = strt; + flag = false; + while( pt != tail) { + if ( pt->branches ){ /* a branch point */ + flag = true; + error("can't remove branch point %s", pt->num); + } + lockpt = Locks; + while(lockpt && lockpt->delta != pt) + lockpt = lockpt->nextlock; + if ( lockpt ) { + flag = true; + error("can't remove locked revision %s",pt->num); + } + pt = pt->next; + } + + if ( ! flag ) { + pt = strt; + while( pt != tail ) { + pt->selector = false; + diagnose("deleting revision %s\n",pt->num); + pt = pt->next; + } + } + return flag; +} + + + + static int +removerevs() +/* Function: get the revision range to be removed, and place the */ +/* first revision removed in delstrt, the revision before */ +/* delstrt in cuthead( nil, if delstrt is head), and the */ +/* revision after the last removed revision in cuttail(nil */ +/* if the last is a leaf */ + +{ + struct hshentry *target, *target2, *temp; + unsigned length; + int flag; + + flag = false; + if (!expandsym(delrev.strt, &numrev)) return 0; + target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas); + if ( ! target ) return 0; + if (cmpnum(target->num, numrev.string)) flag = true; + length = countnumflds(numrev.string); + + if (delrev.code == 0) { /* -o rev or -o branch */ + if (length & 1) + temp=searchcutpt(target->num,length+1,gendeltas); + else if (flag) { + error("Revision %s doesn't exist.", numrev.string); + return 0; + } + else + temp = searchcutpt(numrev.string, length, gendeltas); + cuttail = target->next; + if ( branchpoint(temp, cuttail) ) { + cuttail = nil; + return 0; + } + delstrt = temp; /* first revision to be removed */ + return 1; + } + + if (length & 1) { /* invalid branch after -o */ + error("invalid branch range %s after -o", numrev.string); + return 0; + } + + if (delrev.code == 1) { /* -o -rev */ + if ( length > 2 ) { + temp = searchcutpt( target->num, length-1, gendeltas); + cuttail = target->next; + } + else { + temp = searchcutpt(target->num, length, gendeltas); + cuttail = target; + while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) ) + cuttail = cuttail->next; + } + if ( branchpoint(temp, cuttail) ){ + cuttail = nil; + return 0; + } + delstrt = temp; + return 1; + } + + if (delrev.code == 2) { /* -o rev- */ + if ( length == 2 ) { + temp = searchcutpt(target->num, 1,gendeltas); + if ( flag) + cuttail = target; + else + cuttail = target->next; + } + else { + if ( flag){ + cuthead = target; + if ( !(temp = target->next) ) return 0; + } + else + temp = searchcutpt(target->num, length, gendeltas); + getbranchno(temp->num, &numrev); /* get branch number */ + target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas); + } + if ( branchpoint( temp, cuttail ) ) { + cuttail = nil; + return 0; + } + delstrt = temp; + return 1; + } + + /* -o rev1-rev2 */ + if (!expandsym(delrev.end, &numrev)) return 0; + if ( + length != countnumflds(numrev.string) + || length>2 && compartial(numrev.string, target->num, length-1) + ) { + error("invalid revision range %s-%s", target->num, numrev.string); + return 0; + } + + target2 = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas); + if ( ! target2 ) return 0; + + if ( length > 2) { /* delete revisions on branches */ + if ( cmpnum(target->num, target2->num) > 0) { + if (cmpnum(target2->num, numrev.string)) + flag = true; + else + flag = false; + temp = target; + target = target2; + target2 = temp; + } + if ( flag ) { + if ( ! cmpnum(target->num, target2->num) ) { + error("Revisions %s-%s don't exist.", delrev.strt,delrev.end); + return 0; + } + cuthead = target; + temp = target->next; + } + else + temp = searchcutpt(target->num, length, gendeltas); + cuttail = target2->next; + } + else { /* delete revisions on trunk */ + if ( cmpnum( target->num, target2->num) < 0 ) { + temp = target; + target = target2; + target2 = temp; + } + else + if (cmpnum(target2->num, numrev.string)) + flag = true; + else + flag = false; + if ( flag ) { + if ( ! cmpnum(target->num, target2->num) ) { + error("Revisions %s-%s don't exist.", delrev.strt, delrev.end); + return 0; + } + cuttail = target2; + } + else + cuttail = target2->next; + temp = searchcutpt(target->num, length, gendeltas); + } + if ( branchpoint(temp, cuttail) ) { + cuttail = nil; + return 0; + } + delstrt = temp; + return 1; +} + + + + static void +doassoc() +/* Function: add or delete(if revno is nil) association */ +/* which is stored in assoclst */ + +{ + char const *p; + struct Symrev const *curassoc; + struct assoc * pre, * pt; + + /* add new associations */ + curassoc = assoclst; + while( curassoc ) { + if ( curassoc->revno == nil ) { /* delete symbol */ + pre = pt = Symbols; + while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) { + pre = pt; + pt = pt->nextassoc; + } + if ( pt ) + if ( pre == pt ) + Symbols = pt->nextassoc; + else + pre->nextassoc = pt->nextassoc; + else + warn("can't delete nonexisting symbol %s",curassoc->ssymbol); + } + else { + if (curassoc->revno[0]) { + p = 0; + if (expandsym(curassoc->revno, &numrev)) + p = fstr_save(numrev.string); + } else if (!(p = tiprev())) + error("no latest revision to associate with symbol %s", + curassoc->ssymbol + ); + if (p) + VOID addsymbol(p, curassoc->ssymbol, curassoc->override); + } + curassoc = curassoc->nextsym; + } + +} + + + + static void +dolocks() +/* Function: remove lock for caller or first lock if unlockcaller is set; + * remove locks which are stored in rmvlocklst, + * add new locks which are stored in newlocklst, + * add lock for Dbranch or Head if lockhead is set. + */ +{ + struct Lockrev const *lockpt; + struct hshentry *target; + + if (unlockcaller) { /* find lock for caller */ + if ( Head ) { + if (Locks) { + switch (findlock(true, &target)) { + case 0: + breaklock(Locks->delta); /* remove most recent lock */ + break; + case 1: + diagnose("%s unlocked\n",target->num); + break; + } + } else { + warn("No locks are set."); + } + } else { + warn("can't unlock an empty tree"); + } + } + + /* remove locks which are stored in rmvlocklst */ + lockpt = rmvlocklst; + while( lockpt ) { + if (expandsym(lockpt->revno, &numrev)) { + target = genrevs(numrev.string, (char *)nil, (char *)nil, (char *)nil, &gendeltas); + if ( target ) + if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) + error("can't unlock nonexisting revision %s",lockpt->revno); + else + breaklock(target); + /* breaklock does its own diagnose */ + } + lockpt = lockpt->nextrev; + } + + /* add new locks which stored in newlocklst */ + lockpt = newlocklst; + while( lockpt ) { + setlock(lockpt->revno); + lockpt = lockpt->nextrev; + } + + if (lockhead) { /* lock default branch or head */ + if (Dbranch) { + setlock(Dbranch); + } else if (Head) { + if (0 <= addlock(Head)) + diagnose("%s locked\n",Head->num); + } else { + warn("can't lock an empty tree"); + } + } + +} + + + + static void +setlock(rev) + char const *rev; +/* Function: Given a revision or branch number, finds the corresponding + * delta and locks it for caller. + */ +{ + struct hshentry *target; + + if (expandsym(rev, &numrev)) { + target = genrevs(numrev.string, (char*)nil, (char*)nil, + (char*)nil, &gendeltas); + if ( target ) + if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) + error("can't lock nonexisting revision %s", numrev.string); + else + if (0 <= addlock(target)) + diagnose("%s locked\n", target->num); + } +} + + + static void +domessages() +{ + struct hshentry *target; + struct Message *p; + + for (p = messagelst; p; p = p->nextmessage) + if ( + expandsym(p->revno, &numrev) && + (target = genrevs( + numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas + )) + ) + target->log = p->message; +} + + + static void +rcs_setstate(rev,status) + char const *rev, *status; +/* Function: Given a revision or branch number, finds the corresponding delta + * and sets its state to status. + */ +{ + struct hshentry *target; + + if (expandsym(rev, &numrev)) { + target = genrevs(numrev.string, (char*)nil, (char*)nil, + (char*)nil, &gendeltas); + if ( target ) + if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) + error("can't set state of nonexisting revision %s to %s", + numrev.string, status); + else + target->state = status; + } +} + + + + + + static int +buildeltatext(deltas) + struct hshentries const *deltas; +/* Function: put the delta text on frewrite and make necessary */ +/* change to delta text */ +{ + register FILE *fcut; /* temporary file to rebuild delta tree */ + char const *cutfilename, *diffilename; + + cutfilename = nil; + cuttail->selector = false; + scanlogtext(deltas->first, false); + if ( cuthead ) { + cutfilename = maketemp(3); + if (!(fcut = fopen(cutfilename, FOPEN_W_WORK))) { + efaterror(cutfilename); + } + + while (deltas->first != cuthead) { + deltas = deltas->rest; + scanlogtext(deltas->first, true); + } + + snapshotedit(fcut); + Ofclose(fcut); + } + + while (deltas->first != cuttail) + scanlogtext((deltas = deltas->rest)->first, true); + finishedit((struct hshentry *)nil, (FILE*)0, true); + Ozclose(&fcopy); + + if ( cuthead ) { + diffilename = maketemp(0); + switch (run((char*)nil,diffilename, + DIFF DIFF_FLAGS, cutfilename, resultfile, (char*)nil + )) { + case DIFF_FAILURE: case DIFF_SUCCESS: break; + default: faterror ("diff failed"); + } + return putdtext(cuttail->num,cuttail->log,diffilename,frewrite,true); + } else + return putdtext(cuttail->num,cuttail->log,resultfile,frewrite,false); +} + + + + static void +buildtree() +/* Function: actually removes revisions whose selector field */ +/* is false, and rebuilds the linkage of deltas. */ +/* asks for reconfirmation if deleting last revision*/ +{ + struct hshentry * Delta; + struct branchhead *pt, *pre; + + if ( cuthead ) + if ( cuthead->next == delstrt ) + cuthead->next = cuttail; + else { + pre = pt = cuthead->branches; + while( pt && pt->hsh != delstrt ) { + pre = pt; + pt = pt->nextbranch; + } + if ( cuttail ) + pt->hsh = cuttail; + else if ( pt == pre ) + cuthead->branches = pt->nextbranch; + else + pre->nextbranch = pt->nextbranch; + } + else { + if ( cuttail == nil && !quietflag) { + if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) { + error("No revision deleted"); + Delta = delstrt; + while( Delta) { + Delta->selector = true; + Delta = Delta->next; + } + return; + } + } + Head = cuttail; + } + return; +} + +#if lint +/* This lets us lint everything all at once. */ + +char const cmdid[] = ""; + +#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();} + + int +main(argc, argv) + int argc; + char **argv; +{ + go(ciId, ciExit); + go(coId, coExit); + go(identId, identExit); + go(mergeId, mergeExit); + go(rcsId, exiterr); + go(rcscleanId, rcscleanExit); + go(rcsdiffId, rdiffExit); + go(rcsmergeId, rmergeExit); + go(rlogId, rlogExit); + return 0; +} +#endif diff --git a/gnu/usr.bin/rcs/rcs/rcsfile.5 b/gnu/usr.bin/rcs/rcs/rcsfile.5 new file mode 100644 index 0000000..d0dbbb8 --- /dev/null +++ b/gnu/usr.bin/rcs/rcs/rcsfile.5 @@ -0,0 +1,224 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rcsfile.5,v 5.1 1991/08/19 03:13:55 eggert Exp $ +.ds r \s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH RCSFILE 5 \*(Dt GNU +.SH NAME +rcsfile \- format of RCS file +.SH DESCRIPTION +An \*r file's +contents are described by the grammar +below. +.PP +The text is free format: space, backspace, tab, newline, vertical +tab, form feed, and carriage return (collectively, +.IR "white space") +have no significance except in strings. +However, an \*r file must end in a newline character. +.PP +Strings are enclosed by +.BR @ . +If a string contains a +.BR @ , +it must be doubled; +otherwise, strings may contain arbitrary binary data. +.PP +The meta syntax uses the following conventions: `|' (bar) separates +alternatives; `{' and `}' enclose optional phrases; `{' and `}*' enclose +phrases that may be repeated zero or more times; +`{' and '}+' enclose phrases that must appear at least once and may be +repeated; +Terminal symbols are in +.BR boldface ; +nonterminal symbols are in +.IR italics . +.LP +.nr x \w'\f3branches\fP' +.nr y \w'{ \f3comment\fP' +.if \nx<\ny .nr x \ny +.nr y \w'\f3{ branch\fP' +.if \nx<\ny .nr x \ny +.ta \w'\f2deltatext\fP 'u +\w'::= 'u +\nxu+\w' 'u +.fc ~ +.nf +\f2rcstext\fP ::= \f2admin\fP {\f2delta\fP}* \f2desc\fP {\f2deltatext\fP}* +.LP +\f2admin\fP ::= \f3head\fP {\f2num\fP}\f3;\fP + { \f3branch\fP {\f2num\fP}\f3;\fP } + \f3access\fP {\f2id\fP}*\f3;\fP + \f3symbols\fP {\f2id\fP \f3:\fP \f2num\fP}*\f3;\fP + \f3locks\fP {\f2id\fP \f3:\fP \f2num\fP}*\f3;\fP {\f3strict ;\fP} + { \f3comment\fP {\f2string\fP}\f3;\fP } + { \f3expand\fP {\f2string\fP}\f3;\fP } + { \f2newphrase\fP }* +.LP +\f2delta\fP ::= \f2num\fP + \f3date\fP \f2num\fP\f3;\fP + \f3author\fP \f2id\fP\f3;\fP + \f3state\fP {\f2id\fP}\f3;\fP + \f3branches\fP {\f2num\fP}*\f3;\fP + \f3next\fP {\f2num\fP}\f3;\fP + { \f2newphrase\fP }* +.LP +\f2desc\fP ::= \f3desc\fP \f2string\fP +.LP +\f2deltatext\fP ::= \f2num\fP + \f3log\fP \f2string\fP + { \f2newphrase\fP }* + \f3text\fP \f2string\fP +.LP +\f2num\fP ::= {\f2digit\fP{\f3.\fP}}+ +.LP +\f2digit\fP ::= \f30\fP | \f31\fP | .\|.\|. | \f39\fP +.LP +\f2id\fP ::= \f2letter\fP{\f2idchar\fP}* +.LP +\f2letter\fP ::= any letter +.LP +\f2idchar\fP ::= any visible graphic character except \f2special\fP +.LP +\f2special\fP ::= \f3$\fP | \f3,\fP | \f3.\fP | \f3:\fP | \f3;\fP | \f3@\fP +.LP +\f2string\fP ::= \f3@\fP{any character, with \f3@\fP doubled}*\f3@\fP +.LP +\f2newphrase\fP ::= \f2id\fP \f2word\fP* \f3;\fP +.LP +\f2word\fP ::= \f2id\fP | \f2num\fP | \f2string\fP | \f3:\fP +.fi +.PP +Identifiers are case sensitive. Keywords are in lower case only. +The sets of keywords and identifiers may overlap. +In most environments RCS uses the ISO 8859/1 encoding: +letters are octal codes 101\-132, 141\-172, 300\-326, 330\-366 and 370-377, +visible graphic characters are codes 041\-176 and 240\-377, +and white space characters are codes 010\-015 and 040. +.PP +The +.I newphrase +productions in the grammar are reserved for future extensions +to the format of \*r files. +No +.I newphrase +will begin with any keyword already in use. +.PP +The +.I delta +nodes form a tree. All nodes whose numbers +consist of a single pair +(e.g., 2.3, 2.1, 1.3, etc.) +are on the trunk, and are linked through the +.B next +field in order of decreasing numbers. +The +.B head +field in the +.I admin +node points to the head of that sequence (i.e., contains +the highest pair). +The +.B branch +node in the admin node indicates the default +branch (or revision) for most \*r operations. +If empty, the default +branch is the highest branch on the trunk. +.PP +All +.I delta +nodes whose numbers consist of +.RI 2 n +fields +.RI ( n >=2) +(e.g., 3.1.1.1, 2.1.2.2, etc.) +are linked as follows. +All nodes whose first +.RI 2 n \-1 +number fields are identical are linked through the +.B next +field in order of increasing numbers. +For each such sequence, +the +.I delta +node whose number is identical to the first +.RI 2 n \-2 +number fields of the deltas on that sequence is called the branchpoint. +The +.B branches +field of a node contains a list of the +numbers of the first nodes of all sequences for which it is a branchpoint. +This list is ordered in increasing numbers. +.LP +.nf +.vs 12 +.ne 38 +Example: +.if t .in +0.5i +.cs 1 20 +.eo + + Head + | + | + v / \ + --------- / \ + / \ / \ | | / \ / \ + / \ / \ | 2.1 | / \ / \ + / \ / \ | | / \ / \ +/1.2.1.3\ /1.3.1.1\ | | /1.2.2.2\ /1.2.2.1.1.1\ +--------- --------- --------- --------- ------------- + ^ ^ | ^ ^ + | | | | | + | | v | | + / \ | --------- / \ | + / \ | \ 1.3 / / \ | + / \ ---------\ / / \----------- +/1.2.1.1\ \ / /1.2.2.1\ +--------- \ / --------- + ^ | ^ + | | | + | v | + | --------- | + | \ 1.2 / | + ----------------------\ /--------- + \ / + \ / + | + | + v + --------- + \ 1.1 / + \ / + \ / + \ / + +.ec +.if t .in +.cs 1 +.ce +Fig. 1: A revision tree +.vs +.fi +.PP +.SH IDENTIFICATION +.de VL +\\$2 +.. +Author: Walter F. Tichy, +Purdue University, West Lafayette, IN, 47907. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH SEE ALSO +ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsmerge(1), rlog(1), +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. diff --git a/gnu/usr.bin/rcs/rcs/rcsintro.1 b/gnu/usr.bin/rcs/rcs/rcsintro.1 new file mode 100644 index 0000000..a76caa0 --- /dev/null +++ b/gnu/usr.bin/rcs/rcs/rcsintro.1 @@ -0,0 +1,292 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rcsintro.1,v 5.1 1991/04/21 12:00:46 eggert Exp $ +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.am SS +.LP +.. +.TH RCSINTRO 1 \*(Dt GNU +.SH NAME +rcsintro \- introduction to RCS commands +.SH DESCRIPTION +The Revision Control System (\*r) manages multiple revisions of files. +\*r automates the storing, retrieval, logging, identification, and merging +of revisions. \*r is useful for text that is revised frequently, for example +programs, documentation, graphics, papers, and form letters. +.PP +The basic user interface is extremely simple. The novice only needs +to learn two commands: +.BR ci (1) +and +.BR co (1). +.BR ci , +short for \*(lqcheck in\*(rq, deposits the contents of a +file into an archival file called an \*r file. An \*r file +contains all revisions of a particular file. +.BR co , +short for \*(lqcheck out\*(rq, retrieves revisions from an \*r file. +.SS "Functions of \*r" +.IP \(bu +Store and retrieve multiple revisions of text. \*r saves all old +revisions in a space efficient way. +Changes no longer destroy the original, because the +previous revisions remain accessible. Revisions can be retrieved according to +ranges of revision numbers, symbolic names, dates, authors, and +states. +.IP \(bu +Maintain a complete history of changes. +\*r logs all changes automatically. +Besides the text of each revision, \*r stores the author, the date and time of +check-in, and a log message summarizing the change. +The logging makes it easy to find out +what happened to a module, without having to compare +source listings or having to track down colleagues. +.IP \(bu +Resolve access conflicts. When two or more programmers wish to +modify the same revision, \*r alerts the programmers and prevents one +modification from corrupting the other. +.IP \(bu +Maintain a tree of revisions. \*r can maintain separate lines of development +for each module. It stores a tree structure that represents the +ancestral relationships among revisions. +.IP \(bu +Merge revisions and resolve conflicts. +Two separate lines of development of a module can be coalesced by merging. +If the revisions to be merged affect the same sections of code, \*r alerts the +user about the overlapping changes. +.IP \(bu +Control releases and configurations. +Revisions can be assigned symbolic names +and marked as released, stable, experimental, etc. +With these facilities, configurations of modules can be +described simply and directly. +.IP \(bu +Automatically identify each revision with name, revision number, +creation time, author, etc. +The identification is like a stamp that can be embedded at an appropriate place +in the text of a revision. +The identification makes it simple to determine which +revisions of which modules make up a given configuration. +.IP \(bu +Minimize secondary storage. \*r needs little extra space for +the revisions (only the differences). If intermediate revisions are +deleted, the corresponding deltas are compressed accordingly. +.SS "Getting Started with \*r" +Suppose you have a file +.B f.c +that you wish to put under control of \*r. +If you have not already done so, make an \*r directory with the command +.IP +.B "mkdir RCS" +.LP +Then invoke the check-in command +.IP +.B "ci f.c" +.LP +This command creates an \*r file in the +.B RCS +directory, +stores +.B f.c +into it as revision 1.1, and +deletes +.BR f.c . +It also asks you for a description. The description +should be a synopsis of the contents of the file. All later check-in +commands will ask you for a log entry, which should summarize the +changes that you made. +.PP +Files in the \*r directory are called \*r files; +the others are called working files. +To get back the working file +.B f.c +in the previous example, use the check-out +command +.IP +.B "co f.c" +.LP +This command extracts the latest revision from the \*r file +and writes +it into +.BR f.c . +If you want to edit +.BR f.c , +you must lock it as you check it out with the command +.IP +.B "co \-l f.c" +.LP +You can now edit +.BR f.c . +.PP +Suppose after some editing you want to know what changes that you have made. +The command +.IP +.B "rcsdiff f.c" +.LP +tells you the difference between the most recently checked-in version +and the working file. +You can check the file back in by invoking +.IP +.B "ci f.c" +.LP +This increments the revision number properly. +.PP +If +.B ci +complains with the message +.IP +.BI "ci error: no lock set by " "your name" +.LP +then you have tried to check in a file even though you did not +lock it when you checked it out. +Of course, it is too late now to do the check-out with locking, because +another check-out would +overwrite your modifications. Instead, invoke +.IP +.B "rcs \-l f.c" +.LP +This command will lock the latest revision for you, unless somebody +else got ahead of you already. In this case, you'll have to negotiate with +that person. +.PP +Locking assures that you, and only you, can check in the next update, and +avoids nasty problems if several people work on the same file. +Even if a revision is locked, it can still be checked out for +reading, compiling, etc. All that locking +prevents is a +.I "check-in" +by anybody but the locker. +.PP +If your \*r file is private, i.e., if you are the only person who is going +to deposit revisions into it, strict locking is not needed and you +can turn it off. +If strict locking is turned off, +the owner of the \*r file need not have a lock for check-in; all others +still do. Turning strict locking off and on is done with the commands +.IP +.BR "rcs \-U f.c" " and " "rcs \-L f.c" +.LP +If you don't want to clutter your working directory with \*r files, create +a subdirectory called +.B RCS +in your working directory, and move all your \*r +files there. \*r commands will look first into that directory to find +needed files. All the commands discussed above will still work, without any +modification. +(Actually, pairs of \*r and working files can be specified in three ways: +(a) both are given, (b) only the working file is given, (c) only the +\*r file is given. Both \*r and working files may have arbitrary path prefixes; +\*r commands pair them up intelligently.) +.PP +To avoid the deletion of the working file during check-in (in case you want to +continue editing or compiling), invoke +.IP +.BR "ci \-l f.c" " or " "ci \-u f.c" +.LP +These commands check in +.B f.c +as usual, but perform an implicit +check-out. The first form also locks the checked in revision, the second one +doesn't. Thus, these options save you one check-out operation. +The first form is useful if you want to continue editing, +the second one if you just want to read the file. +Both update the identification markers in your working file (see below). +.PP +You can give +.B ci +the number you want assigned to a checked in +revision. Assume all your revisions were numbered 1.1, 1.2, 1.3, etc., +and you would like to start release 2. +The command +.IP +.BR "ci \-r2 f.c" " or " "ci \-r2.1 f.c" +.LP +assigns the number 2.1 to the new revision. +From then on, +.B ci +will number the subsequent revisions +with 2.2, 2.3, etc. The corresponding +.B co +commands +.IP +.BR "co \-r2 f.c" " and " "co \-r2.1 f.c" +.PP +retrieve the latest revision numbered +.RI 2. x +and the revision 2.1, +respectively. +.B co +without a revision number selects +the latest revision on the +.IR trunk , +i.e. the highest +revision with a number consisting of two fields. Numbers with more than two +fields are needed for branches. +For example, to start a branch at revision 1.3, invoke +.IP +.B "ci \-r1.3.1 f.c" +.LP +This command starts a branch numbered 1 at revision 1.3, and assigns +the number 1.3.1.1 to the new revision. For more information about +branches, see +.BR rcsfile (5). +.SS "Automatic Identification" +\*r can put special strings for identification into your source and object +code. To obtain such identification, place the marker +.IP +.B "$\&Id$" +.LP +into your text, for instance inside a comment. +\*r will replace this marker with a string of the form +.IP +.BI $\&Id: " filename revision date time author state " $ +.LP +With such a marker on the first page of each module, you can +always see with which revision you are working. +\*r keeps the markers up to date automatically. +To propagate the markers into your object code, simply put +them into literal character strings. In C, this is done as follows: +.IP +.ft 3 +static char rcsid[] = \&"$\&Id$\&"; +.ft +.LP +The command +.B ident +extracts such markers from any file, even object code +and dumps. +Thus, +.B ident +lets you find out +which revisions of which modules were used in a given program. +.PP +You may also find it useful to put the marker +.B $\&Log$ +into your text, inside a comment. This marker accumulates +the log messages that are requested during check-in. +Thus, you can maintain the complete history of your file directly inside it. +There are several additional identification markers; see +.BR co (1) +for +details. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.br diff --git a/gnu/usr.bin/rcs/rcsclean/Makefile b/gnu/usr.bin/rcs/rcsclean/Makefile new file mode 100644 index 0000000..fc0c626 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsclean/Makefile @@ -0,0 +1,7 @@ +PROG= rcsclean + +SRCS= rcsclean.c +LDADD= -L${.CURDIR}/../lib/obj -lrcs +CFLAGS+= -I${.CURDIR}/../lib + +.include diff --git a/gnu/usr.bin/rcs/rcsclean/rcsclean.1 b/gnu/usr.bin/rcs/rcsclean/rcsclean.1 new file mode 100644 index 0000000..07ed722 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsclean/rcsclean.1 @@ -0,0 +1,177 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rcsclean.1,v 1.8 1991/11/03 01:09:19 eggert Exp $ +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH RCSCLEAN 1 \*(Dt GNU +.SH NAME +rcsclean \- clean up working files +.SH SYNOPSIS +.B rcsclean +.RI [ options "] [ " file " .\|.\|. ]" +.SH DESCRIPTION +.B rcsclean +removes working files that were checked out and never modified. +For each +.I file +given, +.B rcsclean +compares the working file and a revision in the corresponding +\*r file. If it finds a difference, it does nothing. +Otherwise, it first unlocks the revision if the +.B \-u +option is given, +and then removes the working file +unless the working file is writable and the revision is locked. +It logs its actions by outputting the corresponding +.B "rcs \-u" +and +.B "rm \-f" +commands on the standard output. +.PP +If no +.I file +is given, all working files in the current directory are cleaned. +Pathnames matching an \*r suffix denote \*r files; +all others denote working files. +Names are paired as explained in +.BR ci (1). +.PP +The number of the revision to which the working file is compared +may be attached to any of the options +.BR \-n , +.BR \-q , +.BR \-r , +or +.BR \-u . +If no revision number is specified, then if the +.B \-u +option is given and the caller has one revision locked, +.B rcsclean +uses that revision; otherwise +.B rcsclean +uses the latest revision on the default branch, normally the root. +.PP +.B rcsclean +is useful for +.B clean +targets in Makefiles. +See also +.BR rcsdiff (1), +which prints out the differences, +and +.BR ci (1), +which +normally asks whether to check in a file +if it was not changed. +.SH OPTIONS +.TP +.BI \-k subst +Use +.I subst +style keyword substitution when retrieving the revision for comparison. +See +.BR co (1) +for details. +.TP +.BR \-n [\f2rev\fP] +Do not actually remove any files or unlock any revisions. +Using this option will tell you what +.B rcsclean +would do without actually doing it. +.TP +.BR \-q [\f2rev\fP] +Do not log the actions taken on standard output. +.TP +.BR \-r [\f2rev\fP] +This option has no effect other than specifying the revision for comparison. +.TP +.BR \-u [\f2rev\fP] +Unlock the revision if it is locked and no difference is found. +.TP +.BI \-V n +Emulate \*r version +.IR n . +See +.BR co (1) +for details. +.TP +.BI \-x "suffixes" +Use +.I suffixes +to characterize \*r files. +See +.BR ci (1) +for details. +.SH EXAMPLES +.LP +.RS +.ft 3 +rcsclean *.c *.h +.ft +.RE +.LP +removes all working files ending in +.B .c +or +.B .h +that were not changed +since their checkout. +.LP +.RS +.ft 3 +rcsclean +.ft +.RE +.LP +removes all working files in the current directory +that were not changed since their checkout. +.SH FILES +.B rcsclean +accesses files much as +.BR ci (1) +does. +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +A backslash escapes spaces within an option. +The +.B \s-1RCSINIT\s0 +options are prepended to the argument lists of most \*r commands. +Useful +.B \s-1RCSINIT\s0 +options include +.BR \-q , +.BR \-V , +and +.BR \-x . +.SH DIAGNOSTICS +The exit status is zero if and only if all operations were successful. +Missing working files and \*r files are silently ignored. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1), +rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.SH BUGS +At least one +.I file +must be given in older Unix versions that +do not provide the needed directory scanning operations. +.br diff --git a/gnu/usr.bin/rcs/rcsclean/rcsclean.c b/gnu/usr.bin/rcs/rcsclean/rcsclean.c new file mode 100644 index 0000000..ba24ab7 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsclean/rcsclean.c @@ -0,0 +1,297 @@ +/* rcsclean - clean up working files */ + +/* Copyright 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +#include "rcsbase.h" + +#if has_dirent + static int get_directory P((char const*,char***)); +#endif + +static int unlock P((struct hshentry *)); +static void cleanup P((void)); + +static RILE *workptr; +static int exitstatus; + +mainProg(rcscleanId, "rcsclean", "$Id: rcsclean.c,v 5.1 1991/11/03 01:11:44 eggert Exp $") +{ + static char const usage[] = + "\nrcsclean: usage: rcsclean [-ksubst] [-{nqru}[rev]] [-Vn] [-xsuffixes] [file ...]"; + + static struct buf revision; + + char *a, **newargv; + char const *rev, *p; + int changelock, expmode, perform, unlocked, unlockflag, waslocked; + struct hshentries *deltas; + struct hshentry *delta; + struct stat workstat; + + setrid(); + + expmode = -1; + rev = nil; + suffixes = X_DEFAULT; + perform = true; + unlockflag = false; + + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + for (;;) { + if (--argc <= 0) { +# if has_dirent + argc = get_directory(".", &newargv); + argv = newargv; + break; +# else + faterror("no file names specified"); +# endif + } + a = *++argv; + if (*a++ != '-') + break; + switch (*a++) { + case 'k': + if (0 <= expmode) + redefined('k'); + if ((expmode = str2expmode(a)) < 0) + goto unknown; + break; + + case 'n': + perform = false; + goto handle_revision; + + case 'q': + quietflag = true; + /* fall into */ + case 'r': + handle_revision: + if (*a) { + if (rev) + warn("redefinition of revision number"); + rev = a; + } + break; + + case 'u': + unlockflag = true; + goto handle_revision; + + case 'V': + setRCSversion(*argv); + break; + + case 'x': + suffixes = a; + break; + + default: + unknown: + faterror("unknown option: %s%s", *argv, usage); + } + } + + do { + ffree(); + + if (!( + 0 < pairfilenames( + argc, argv, + unlockflag&perform ? rcswriteopen : rcsreadopen, + true, true + ) && + (workptr = Iopen(workfilename,FOPEN_R_WORK,&workstat)) + )) + continue; + + gettree(); + + p = 0; + if (rev) { + if (!fexpandsym(rev, &revision, workptr)) + continue; + p = revision.string; + } else if (Head) + switch (unlockflag ? findlock(false,&delta) : 0) { + default: + continue; + case 0: + p = Dbranch ? Dbranch : ""; + break; + case 1: + p = delta->num; + break; + } + delta = 0; + deltas = 0; /* Keep lint happy. */ + if (p && !(delta = genrevs(p,(char*)0,(char*)0,(char*)0,&deltas))) + continue; + + waslocked = delta && delta->lockedby; + locker_expansion = unlock(delta); + unlocked = locker_expansion & unlockflag; + changelock = unlocked & perform; + if (unlockednum, RCSfilename); + + if_advise_access(changelock && deltas->first != delta, + finptr, MADV_SEQUENTIAL + ); + if (!donerewrite(changelock)) + continue; + + if (!quietflag) + aprintf(stdout, "rm -f %s\n", workfilename); + Izclose(&workptr); + if (perform && un_link(workfilename) != 0) + eerror(workfilename); + + } while (cleanup(), ++argv, 0 < --argc); + + tempunlink(); + if (!quietflag) + Ofclose(stdout); + exitmain(exitstatus); +} + + static void +cleanup() +{ + if (nerror) exitstatus = EXIT_FAILURE; + Izclose(&finptr); + Izclose(&workptr); + Ozclose(&fcopy); + Ozclose(&frewrite); + dirtempunlink(); +} + +#if lint +# define exiterr rcscleanExit +#endif + exiting void +exiterr() +{ + dirtempunlink(); + tempunlink(); + _exit(EXIT_FAILURE); +} + + static int +unlock(delta) + struct hshentry *delta; +{ + register struct lock **al, *l; + + if (delta && delta->lockedby && strcmp(getcaller(),delta->lockedby)==0) + for (al = &Locks; (l = *al); al = &l->nextlock) + if (l->delta == delta) { + *al = l->nextlock; + delta->lockedby = 0; + return true; + } + return false; +} + +#if has_dirent + static int +get_directory(dirname, aargv) + char const *dirname; + char ***aargv; +/* + * Put a vector of all DIRNAME's directory entries names into *AARGV. + * Ignore names of RCS files. + * Yield the number of entries found. Terminate the vector with 0. + * Allocate the storage for the vector and entry names. + * Do not sort the names. Do not include '.' and '..'. + */ +{ + int i, entries = 0, entries_max = 64; + size_t chars = 0, chars_max = 1024; + size_t *offset = tnalloc(size_t, entries_max); + char *a = tnalloc(char, chars_max), **p; + DIR *d; + struct dirent *e; + + if (!(d = opendir(dirname))) + efaterror(dirname); + while ((errno = 0, e = readdir(d))) { + char const *en = e->d_name; + size_t s = strlen(en) + 1; + if (en[0]=='.' && (!en[1] || en[1]=='.' && !en[2])) + continue; + if (rcssuffix(en)) + continue; + while (chars_max < s + chars) + a = trealloc(char, a, chars_max<<=1); + if (entries == entries_max) + offset = trealloc(size_t, offset, entries_max<<=1); + offset[entries++] = chars; + VOID strcpy(a+chars, en); + chars += s; + } + if (errno || closedir(d) != 0) + efaterror(dirname); + if (chars) + a = trealloc(char, a, chars); + else + tfree(a); + *aargv = p = tnalloc(char*, entries+1); + for (i=0; i diff --git a/gnu/usr.bin/rcs/rcsdiff/rcsdiff.1 b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.1 new file mode 100644 index 0000000..b78bbdd --- /dev/null +++ b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.1 @@ -0,0 +1,152 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rcsdiff.1,v 5.3 1991/04/21 12:00:46 eggert Exp $ +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH RCSDIFF 1 \*(Dt GNU +.SH NAME +rcsdiff \- compare RCS revisions +.SH SYNOPSIS +.B rcsdiff +[ +.BI \-k subst +] [ +.B \-q +] [ +.BI \-r rev1 +[ +.BI \-r rev2 +] ] [ +.BI \-V n +] [ +.BI \-x suffixes +] [ +.I "diff options" +] +.I "file .\|.\|." +.SH DESCRIPTION +.B rcsdiff +runs +.BR diff (1) +to compare two revisions of each \*r file given. +.PP +Pathnames matching an \*r suffix denote \*r files; +all others denote working files. +Names are paired as explained in +.BR ci (1). +.PP +The option +.B \-q +suppresses diagnostic output. +Zero, one, or two revisions may be specified with +.BR \-r . +The option +.BI \-k subst +affects keyword substitution when extracting +revisions, as described in +.BR co (1); +for example, +.B "\-kk\ \-r1.1\ \-r1.2" +ignores differences in keyword values when comparing revisions +.B 1.1 +and +.BR 1.2 . +To avoid excess output from locker name substitution, +.B \-kkvl +is assumed if (1) at most one revision option is given, +(2) no +.B \-k +option is given, (3) +.B \-kkv +is the default keyword substitution, and +(4) the working file's mode would be produced by +.BR "co\ \-l". +See +.BR co (1) +for details +about +.B \-V +and +.BR \-x . +Otherwise, all options of +.BR diff (1) +that apply to regular files are accepted, with the same meaning as for +.BR diff . +.PP +If both +.I rev1 +and +.I rev2 +are omitted, +.B rcsdiff +compares the latest revision on the +default branch (by default the trunk) +with the contents of the corresponding working file. This is useful +for determining what you changed since the last checkin. +.PP +If +.I rev1 +is given, but +.I rev2 +is omitted, +.B rcsdiff +compares revision +.I rev1 +of the \*r file with +the contents of the corresponding working file. +.PP +If both +.I rev1 +and +.I rev2 +are given, +.B rcsdiff +compares revisions +.I rev1 +and +.I rev2 +of the \*r file. +.PP +Both +.I rev1 +and +.I rev2 +may be given numerically or symbolically. +.SH EXAMPLE +The command +.LP +.B " rcsdiff f.c" +.LP +compares the latest revision on the default branch of the \*r file +to the contents of the working file +.BR f.c . +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +See +.BR ci (1) +for details. +.SH DIAGNOSTICS +Exit status is 0 for no differences during any comparison, +1 for some differences, 2 for trouble. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +ci(1), co(1), diff(1), ident(1), rcs(1), rcsintro(1), rcsmerge(1), rlog(1) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.br diff --git a/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c new file mode 100644 index 0000000..7155c8d --- /dev/null +++ b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c @@ -0,0 +1,422 @@ +/* + * RCS rcsdiff operation + */ +/***************************************************************************** + * generate difference between RCS revisions + ***************************************************************************** + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + + +/* $Log: rcsdiff.c,v $ + * Revision 5.10 1991/10/07 17:32:46 eggert + * Remove lint. + * + * Revision 5.9 1991/08/19 03:13:55 eggert + * Add RCSINIT, -r$. Tune. + * + * Revision 5.8 1991/04/21 11:58:21 eggert + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.7 1990/12/13 06:54:07 eggert + * GNU diff 1.15 has -u. + * + * Revision 5.6 1990/11/01 05:03:39 eggert + * Remove unneeded setid check. + * + * Revision 5.5 1990/10/04 06:30:19 eggert + * Accumulate exit status across files. + * + * Revision 5.4 1990/09/27 01:31:43 eggert + * Yield 1, not EXIT_FAILURE, when diffs are found. + * + * Revision 5.3 1990/09/11 02:41:11 eggert + * Simplify -kkvl test. + * + * Revision 5.2 1990/09/04 17:07:19 eggert + * Diff's argv was too small by 1. + * + * Revision 5.1 1990/08/29 07:13:55 eggert + * Add -kkvl. + * + * Revision 5.0 1990/08/22 08:12:46 eggert + * Add -k, -V. Don't use access(). Add setuid support. + * Remove compile-time limits; use malloc instead. + * Don't pass arguments with leading '+' to diff; GNU DIFF treats them as options. + * Add GNU diff's flags. Make lock and temp files faster and safer. + * Ansify and Posixate. + * + * Revision 4.6 89/05/01 15:12:27 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.5 88/08/09 19:12:41 eggert + * Use execv(), not system(); yield exit status like diff(1)s; allow cc -R. + * + * Revision 4.4 87/12/18 11:37:46 narten + * changes Jay Lepreau made in the 4.3 BSD version, to add support for + * "-i", "-w", and "-t" flags and to permit flags to be bundled together, + * merged in. + * + * Revision 4.3 87/10/18 10:31:42 narten + * Updating version numbers. Changes relative to 1.1 actually + * relative to 4.1 + * + * Revision 1.3 87/09/24 13:59:21 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:15 jenkins + * Port to suns + * + * Revision 4.1 83/05/03 22:13:19 wft + * Added default branch, option -q, exit status like diff. + * Added fterror() to replace faterror(). + * + * Revision 3.6 83/01/15 17:52:40 wft + * Expanded mainprogram to handle multiple RCS files. + * + * Revision 3.5 83/01/06 09:33:45 wft + * Fixed passing of -c (context) option to diff. + * + * Revision 3.4 82/12/24 15:28:38 wft + * Added call to catchsig(). + * + * Revision 3.3 82/12/10 16:08:17 wft + * Corrected checking of return code from diff; improved error msgs. + * + * Revision 3.2 82/12/04 13:20:09 wft + * replaced getdelta() with gettree(). Changed diagnostics. + * + * Revision 3.1 82/11/28 19:25:04 wft + * Initial revision. + * + */ +#include "rcsbase.h" + +#if DIFF_L +static char const *setup_label P((struct buf*,char const*,char const[datesize])); +#endif +static void cleanup P((void)); + +static int exitstatus; +static RILE *workptr; +static struct stat workstat; + +mainProg(rcsdiffId, "rcsdiff", "$Id: rcsdiff.c,v 5.10 1991/10/07 17:32:46 eggert Exp $") +{ + static char const cmdusage[] = + "\nrcsdiff usage: rcsdiff [-q] [-rrev1 [-rrev2]] [-Vn] [diff options] file ..."; + + int revnums; /* counter for revision numbers given */ + char const *rev1, *rev2; /* revision numbers from command line */ + char const *xrev1, *xrev2; /* expanded revision numbers */ + char const *expandarg, *lexpandarg, *versionarg; +#if DIFF_L + static struct buf labelbuf[2]; + int file_labels; + char const **diff_label1, **diff_label2; + char date2[datesize]; +#endif + char const *cov[9]; + char const **diffv, **diffp; /* argv for subsidiary diff */ + char const **pp, *p, *diffvstr; + struct buf commarg; + struct buf numericrev; /* expanded revision number */ + struct hshentries *gendeltas; /* deltas to be generated */ + struct hshentry * target; + char *a, *dcp, **newargv; + register c; + + exitstatus = DIFF_SUCCESS; + + bufautobegin(&commarg); + bufautobegin(&numericrev); + revnums = 0; + rev1 = rev2 = xrev2 = nil; +#if DIFF_L + file_labels = 0; +#endif + expandarg = versionarg = 0; + suffixes = X_DEFAULT; + + /* Room for args + 2 i/o [+ 2 labels] + 1 file + 1 trailing null. */ + diffp = diffv = tnalloc(char const*, argc + 4 + 2*DIFF_L); + *diffp++ = nil; + *diffp++ = nil; + *diffp++ = DIFF; + + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + while (a = *++argv, 0<--argc && *a++=='-') { + dcp = a; + while (c = *a++) switch (c) { + case 'r': + switch (++revnums) { + case 1: rev1=a; break; + case 2: rev2=a; break; + default: faterror("too many revision numbers"); + } + goto option_handled; +#if DIFF_L + case 'L': + if (++file_labels == 2) + faterror("too many -L options"); + /* fall into */ +#endif + case 'C': case 'D': case 'F': case 'I': + *dcp++ = c; + if (*a) + do *dcp++ = *a++; + while (*a); + else { + if (!--argc) + faterror("-%c needs following argument%s", + c, cmdusage + ); + *diffp++ = *argv++; + } + break; + case 'B': case 'H': case 'T': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + 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': + *dcp++ = c; + break; + case 'q': + quietflag=true; + break; + case 'x': + suffixes = *argv + 2; + goto option_handled; + case 'V': + versionarg = *argv; + setRCSversion(versionarg); + goto option_handled; + case 'k': + expandarg = *argv; + if (0 <= str2expmode(expandarg+2)) + goto option_handled; + /* fall into */ + default: + faterror("unknown option: %s%s", *argv, cmdusage); + }; + option_handled: + if (dcp != *argv+1) { + *dcp = 0; + *diffp++ = *argv; + } + } /* end of option processing */ + + if (argc<1) faterror("no input file%s", cmdusage); + + for (pp = diffv+3, c = 0; ppnum; + + if (!fexpandsym(rev1, &numericrev, workptr)) continue; + if (!(target=genrevs(numericrev.string,(char *)nil,(char *)nil,(char *)nil,&gendeltas))) continue; + xrev1=target->num; +#if DIFF_L + if (diff_label1) + *diff_label1 = setup_label(&labelbuf[0], target->num, target->date); +#endif + + lexpandarg = expandarg; + if (revnums==2) { + if (!fexpandsym( + *rev2 ? rev2 : Dbranch ? Dbranch : Head->num, + &numericrev, + workptr + )) + continue; + if (!(target=genrevs(numericrev.string,(char *)nil,(char *)nil,(char *)nil,&gendeltas))) continue; + xrev2=target->num; + } else if ( + target->lockedby + && !lexpandarg + && Expand == KEYVAL_EXPAND + && WORKMODE(RCSstat.st_mode,true) == workstat.st_mode + ) + lexpandarg = "-kkvl"; + Izclose(&workptr); +#if DIFF_L + if (diff_label2) + if (revnums == 2) + *diff_label2 = setup_label(&labelbuf[1], target->num, target->date); + else { + time2date(workstat.st_mtime, date2); + *diff_label2 = setup_label(&labelbuf[1], workfilename, date2); + } +#endif + + diagnose("retrieving revision %s\n", xrev1); + bufscpy(&commarg, "-p"); + bufscat(&commarg, xrev1); + + cov[1] = diffp[0] = maketemp(0); + pp = &cov[4]; + *pp++ = commarg.string; + if (lexpandarg) + *pp++ = lexpandarg; + if (versionarg) + *pp++ = versionarg; + *pp++ = RCSfilename; + *pp = 0; + + if (runv(cov)) { + error("co failed"); + continue; + } + if (!rev2) { + diffp[1] = workfilename; + if (workfilename[0] == '+') { + /* Some diffs have options with leading '+'. */ + char *dp = ftnalloc(char, strlen(workfilename)+3); + diffp[1] = dp; + *dp++ = '.'; + *dp++ = SLASH; + VOID strcpy(dp, workfilename); + } + } else { + diagnose("retrieving revision %s\n",xrev2); + bufscpy(&commarg, "-p"); + bufscat(&commarg, xrev2); + cov[1] = diffp[1] = maketemp(1); + cov[4] = commarg.string; + if (runv(cov)) { + error("co failed"); + continue; + } + } + if (!rev2) + diagnose("diff%s -r%s %s\n", diffvstr, xrev1, workfilename); + else + diagnose("diff%s -r%s -r%s\n", diffvstr, xrev1, xrev2); + + switch (runv(diffv)) { + case DIFF_SUCCESS: + break; + case DIFF_FAILURE: + if (exitstatus == DIFF_SUCCESS) + exitstatus = DIFF_FAILURE; + break; + default: + error("diff failed"); + } + } while (cleanup(), + ++argv, --argc >=1); + + + tempunlink(); + exitmain(exitstatus); +} + + static void +cleanup() +{ + if (nerror) exitstatus = DIFF_TROUBLE; + Izclose(&finptr); + Izclose(&workptr); +} + +#if lint +# define exiterr rdiffExit +#endif + exiting void +exiterr() +{ + tempunlink(); + _exit(DIFF_TROUBLE); +} + +#if DIFF_L + static char const * +setup_label(b, name, date) + struct buf *b; + char const *name; + char const date[datesize]; +{ + char *p; + size_t l = strlen(name) + 3; + bufalloc(b, l+datesize); + p = b->string; + VOID sprintf(p, "-L%s\t", name); + VOID date2str(date, p+l); + return p; +} +#endif diff --git a/gnu/usr.bin/rcs/rcsfreeze/Makefile b/gnu/usr.bin/rcs/rcsfreeze/Makefile new file mode 100644 index 0000000..825d4bf --- /dev/null +++ b/gnu/usr.bin/rcs/rcsfreeze/Makefile @@ -0,0 +1,7 @@ +# Do nothing for the following +obj clean cleandir depend rcsfreeze all: + @echo No need to make $@ for rcsfreeze\; ignored + +install: + install -c -o bin -g bin -m 555 rcsfreeze.sh /usr/bin/rcsfreeze + install -c -o bin -g bin -m 444 rcsfreeze.1 /usr/share/man/man1 diff --git a/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1 b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1 new file mode 100644 index 0000000..be669a9 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1 @@ -0,0 +1,68 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rcsfreeze.1,v 4.4 1990/11/13 15:43:42 hammer Exp $ +.ds r \s-1RCS\s0 +.TH RCSFREEZE 1 \*(Dt GNU +.SH NAME +rcsfreeze \- freeze a configuration of sources checked in under RCS +.SH SYNOPSIS +.B rcsfreeze +.RI [ "name" ] +.SH DESCRIPTION +.B rcsfreeze +assigns a symbolic revision +number to a set of \*r files that form a valid configuration. +.PP +The idea is to run +.B rcsfreeze +each time a new version is checked +in. A unique symbolic name (\c +.BI C_ number, +where +.I number +is increased each time +.B rcsfreeze +is run) is then assigned to the most +recent revision of each \*r file of the main trunk. +.PP +An optional +.I name +argument to +.B rcsfreeze +gives a symbolic name to the configuration. +The unique identifier is still generated +and is listed in the log file but it will not appear as +part of the symbolic revision name in the actual \*r files. +.PP +A log message is requested from the user for future reference. +.PP +The shell script works only on all \*r files at one time. +All changed files must be checked in already. +Run +.IR rcsclean (1) +first and see whether any sources remain in the current directory. +.SH FILES +.TP +.B RCS/.rcsfreeze.ver +version number +.TP +.B RCS/.rcsfreeze.log +log messages, most recent first +.SH AUTHOR +Stephan v. Bechtolsheim +.SH "SEE ALSO" +co(1), rcs(1), rcsclean(1), rlog(1) +.SH BUGS +.B rcsfreeze +does not check whether any sources are checked out and modified. +.PP +Although both source file names and RCS file names are accepted, +they are not paired as usual with RCS commands. +.PP +Error checking is rudimentary. +.PP +.B rcsfreeze +is just an optional example shell script, and should not be taken too seriously. +See \s-1CVS\s0 for a more complete solution. diff --git a/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh new file mode 100644 index 0000000..4219979 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh @@ -0,0 +1,100 @@ +#! /bin/sh + +# rcsfreeze - assign a symbolic revision number to a configuration of RCS files + +# $Id: rcsfreeze.sh,v 4.4 1991/04/21 11:58:24 eggert Exp $ + +# The idea is to run rcsfreeze each time a new version is checked +# in. A unique symbolic revision number (C_[number], where number +# is increased each time rcsfreeze is run) is then assigned to the most +# recent revision of each RCS file of the main trunk. +# +# If the command is invoked with an argument, then this +# argument is used as the symbolic name to freeze a configuration. +# The unique identifier is still generated +# and is listed in the log file but it will not appear as +# part of the symbolic revision name in the actual RCS file. +# +# A log message is requested from the user which is saved for future +# references. +# +# The shell script works only on all RCS files at one time. +# It is important that all changed files are checked in (there are +# no precautions against any error in this respect). +# file names: +# {RCS/}.rcsfreeze.ver version number +# {RCS/}.rscfreeze.log log messages, most recent first + +PATH=/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:/usr/ucb:$PATH +export PATH + +DATE=`date` || exit +# Check whether we have an RCS subdirectory, so we can have the right +# prefix for our paths. +if [ -d RCS ] +then RCSDIR=RCS/ +else RCSDIR= +fi + +# Version number stuff, log message file +VERSIONFILE=${RCSDIR}.rcsfreeze.ver +LOGFILE=${RCSDIR}.rcsfreeze.log +# Initialize, rcsfreeze never run before in the current directory +[ -r $VERSIONFILE ] || { echo 0 >$VERSIONFILE && >>$LOGFILE; } || exit + +# Get Version number, increase it, write back to file. +VERSIONNUMBER=`cat $VERSIONFILE` && +VERSIONNUMBER=`expr $VERSIONNUMBER + 1` && +echo $VERSIONNUMBER >$VERSIONFILE || exit + +# Symbolic Revision Number +SYMREV=C_$VERSIONNUMBER +# Allow the user to give a meaningful symbolic name to the revision. +SYMREVNAME=${1-$SYMREV} +echo >&2 "rcsfreeze: symbolic revision number computed: \"${SYMREV}\" +rcsfreeze: symbolic revision number used: \"${SYMREVNAME}\" +rcsfreeze: the two differ only when rcsfreeze invoked with argument +rcsfreeze: give log message, summarizing changes (end with EOF or single '.')" \ + || exit + +# Stamp the logfile. Because we order the logfile the most recent +# first we will have to save everything right now in a temporary file. +TMPLOG=/tmp/rcsfrz$$ +trap 'rm -f $TMPLOG; exit 1' 1 2 13 15 +# Now ask for a log message, continously add to the log file +( + echo "Version: $SYMREVNAME($SYMREV), Date: $DATE +-----------" || exit + while read MESS + do + case $MESS in + .) break + esac + echo " $MESS" || exit + done + echo "----------- +" && + cat $LOGFILE +) >$TMPLOG && + +# combine old and new logfiles +cp $TMPLOG $LOGFILE && +rm -f $TMPLOG || exit +trap 1 2 13 15 + +# Now the real work begins by assigning a symbolic revision number +# to each rcs file. Take the most recent version of the main trunk. + +status= + +for FILE in ${RCSDIR}* +do +# get the revision number of the most recent revision + HEAD=`rlog -h $FILE` && + REV=`echo "$HEAD" | sed -n 's/^head:[ ]*//p'` && +# assign symbolic name to it. + echo >&2 "rcsfreeze: $REV $FILE" && + rcs -q -n$SYMREVNAME:$REV $FILE || status=$? +done + +exit $status diff --git a/gnu/usr.bin/rcs/rcsmerge/Makefile b/gnu/usr.bin/rcs/rcsmerge/Makefile new file mode 100644 index 0000000..0c1f643 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsmerge/Makefile @@ -0,0 +1,7 @@ +PROG= rcsmerge + +SRCS= rcsmerge.c +LDADD= -L${.CURDIR}/../lib/obj -lrcs +CFLAGS+= -I${.CURDIR}/../lib + +.include diff --git a/gnu/usr.bin/rcs/rcsmerge/rcsmerge.1 b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.1 new file mode 100644 index 0000000..82871b0 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.1 @@ -0,0 +1,140 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rcsmerge.1,v 5.3 1991/08/19 03:13:55 eggert Exp $ +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH RCSMERGE 1 \*(Dt GNU +.SH NAME +rcsmerge \- merge RCS revisions +.SH SYNOPSIS +.B rcsmerge +.RI [ options ] " file" +.SH DESCRIPTION +.B rcsmerge +incorporates the changes between two revisions +of an \*r file into the corresponding working file. +.PP +Pathnames matching an \*r suffix denote \*r files; +all others denote working files. +Names are paired as explained in +.BR ci (1). +.PP +At least one revision must be specified with one of the options +described below, usually +.BR \-r . +At most two revisions may be specified. +If only one revision is specified, the latest +revision on the default branch (normally the highest branch on the trunk) +is assumed for the second revision. +Revisions may be specified numerically or symbolically. +.PP +.B rcsmerge +prints a warning if there are overlaps, and delimits +the overlapping regions as explained in +.BR merge (1). +The command is useful for incorporating changes into a checked-out revision. +.SH OPTIONS +.TP +.BI \-k subst +Use +.I subst +style keyword substitution. +See +.BR co (1) +for details. +For example, +.B "\-kk\ \-r1.1\ \-r1.2" +ignores differences in keyword values when merging the changes from +.B 1.1 +to +.BR 1.2 . +.TP +.BR \-p [\f2rev\fP] +Send the result to standard output instead of overwriting the working file. +.TP +.BR \-q [\f2rev\fP] +Run quietly; do not print diagnostics. +.TP +.BR \-r [\f2rev\fP] +Merge with respect to revision +.IR rev . +Here an empty +.I rev +stands for the latest revision on the default branch, normally the head. +.TP +.BI \-V n +Emulate \*r version +.IR n . +See +.BR co (1) +for details. +.TP +.BI \-x "suffixes" +Use +.I suffixes +to characterize \*r files. +See +.BR ci (1) +for details. +.SH EXAMPLES +Suppose you have released revision 2.8 of +.BR f.c . +Assume +furthermore that after you complete an unreleased revision 3.4, you receive +updates to release 2.8 from someone else. +To combine the updates to 2.8 and your changes between 2.8 and 3.4, +put the updates to 2.8 into file f.c and execute +.LP +.B " rcsmerge \-p \-r2.8 \-r3.4 f.c >f.merged.c" +.PP +Then examine +.BR f.merged.c . +Alternatively, if you want to save the updates to 2.8 in the \*r file, +check them in as revision 2.8.1.1 and execute +.BR "co \-j": +.LP +.B " ci \-r2.8.1.1 f.c" +.br +.B " co \-r3.4 \-j2.8:2.8.1.1 f.c" +.PP +As another example, the following command undoes the changes +between revision 2.4 and 2.8 in your currently checked out revision +in +.BR f.c . +.LP +.B " rcsmerge \-r2.8 \-r2.4 f.c" +.PP +Note the order of the arguments, and that +.B f.c +will be +overwritten. +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +See +.BR ci (1) +for details. +.SH DIAGNOSTICS +Exit status is 0 for no overlaps, 1 for some overlaps, 2 for trouble. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +ci(1), co(1), ident(1), merge(1), rcs(1), rcsdiff(1), rcsintro(1), rlog(1), +rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.br diff --git a/gnu/usr.bin/rcs/rcsmerge/rcsmerge.c b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.c new file mode 100644 index 0000000..e5d4394 --- /dev/null +++ b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.c @@ -0,0 +1,252 @@ +/* + * rcsmerge operation + */ +/***************************************************************************** + * join 2 revisions with respect to a third + ***************************************************************************** + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + +/* $Log: rcsmerge.c,v $ + * Revision 5.7 1991/11/20 17:58:09 eggert + * Don't Iopen(f, "r+"); it's not portable. + * + * Revision 5.6 1991/08/19 03:13:55 eggert + * Add -r$. Tune. + * + * Revision 5.5 1991/04/21 11:58:27 eggert + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.4 1991/02/25 07:12:43 eggert + * Merging a revision to itself is no longer an error. + * + * Revision 5.3 1990/11/01 05:03:50 eggert + * Remove unneeded setid check. + * + * Revision 5.2 1990/09/04 08:02:28 eggert + * Check for I/O error when reading working file. + * + * Revision 5.1 1990/08/29 07:14:04 eggert + * Add -q. Pass -L options to merge. + * + * Revision 5.0 1990/08/22 08:13:41 eggert + * Propagate merge's exit status. + * Remove compile-time limits; use malloc instead. + * Make lock and temp files faster and safer. Ansify and Posixate. Add -V. + * Don't use access(). Tune. + * + * Revision 4.5 89/05/01 15:13:16 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.4 88/08/09 19:13:13 eggert + * Beware merging into a readonly file. + * Beware merging a revision to itself (no change). + * Use execv(), not system(); yield exit status like diff(1)'s. + * + * Revision 4.3 87/10/18 10:38:02 narten + * Updating version numbers. Changes relative to version 1.1 + * actually relative to 4.1 + * + * Revision 1.3 87/09/24 14:00:31 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:36 jenkins + * Port to suns + * + * Revision 4.1 83/03/28 11:14:57 wft + * Added handling of default branch. + * + * Revision 3.3 82/12/24 15:29:00 wft + * Added call to catchsig(). + * + * Revision 3.2 82/12/10 21:32:02 wft + * Replaced getdelta() with gettree(); improved error messages. + * + * Revision 3.1 82/11/28 19:27:44 wft + * Initial revision. + * + */ +#include "rcsbase.h" + +static char const co[] = CO; + +mainProg(rcsmergeId, "rcsmerge", "$Id: rcsmerge.c,v 5.7 1991/11/20 17:58:09 eggert Exp $") +{ + static char const cmdusage[] = + "\nrcsmerge usage: rcsmerge -rrev1 [-rrev2] [-p] [-Vn] file"; + static char const quietarg[] = "-q"; + + register int i; + char *a, **newargv; + char const *arg[3]; + char const *rev[2]; /*revision numbers*/ + char const *expandarg, *versionarg; + int tostdout; + int status; + RILE *workptr; + struct buf commarg; + struct buf numericrev; /* holds expanded revision number */ + struct hshentries *gendeltas; /* deltas to be generated */ + struct hshentry * target; + + bufautobegin(&commarg); + bufautobegin(&numericrev); + rev[0] = rev[1] = nil; + status = 0; /* Keep lint happy. */ + tostdout = false; + expandarg = versionarg = quietarg; /* i.e. a no-op */ + suffixes = X_DEFAULT; + + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + while (a = *++argv, 0<--argc && *a++=='-') { + switch (*a++) { + case 'p': + tostdout=true; + goto revno; + + case 'q': + quietflag = true; + revno: + if (!*a) + break; + /* falls into -r */ + case 'r': + if (!rev[0]) + rev[0] = a; + else if (!rev[1]) + rev[1] = a; + else + faterror("too many revision numbers"); + break; + case 'x': + suffixes = a; + break; + case 'V': + versionarg = *argv; + setRCSversion(versionarg); + break; + + case 'k': + expandarg = *argv; + if (0 <= str2expmode(expandarg+2)) + break; + /* fall into */ + default: + faterror("unknown option: %s%s", *argv, cmdusage); + }; + } /* end of option processing */ + + if (argc<1) faterror("no input file%s", cmdusage); + if (!rev[0]) faterror("no base revision number given"); + + /* now handle all filenames */ + + if (0 < pairfilenames(argc, argv, rcsreadopen, true, false)) { + + if (argc>2 || (argc==2&&argv[1]!=nil)) + warn("too many arguments"); + diagnose("RCS file: %s\n", RCSfilename); + if (!(workptr = Iopen(workfilename, + FOPEN_R_WORK, + (struct stat*)0 + ))) + efaterror(workfilename); + + gettree(); /* reads in the delta tree */ + + if (Head==nil) faterror("no revisions present"); + + if (!*rev[0]) + rev[0] = Dbranch ? Dbranch : Head->num; + if (!fexpandsym(rev[0], &numericrev, workptr)) + goto end; + if (!(target=genrevs(numericrev.string, (char *)nil, (char *)nil, (char *)nil,&gendeltas))) goto end; + rev[0] = target->num; + if (!rev[1] || !*rev[1]) + rev[1] = Dbranch ? Dbranch : Head->num; + if (!fexpandsym(rev[1], &numericrev, workptr)) + goto end; + if (!(target=genrevs(numericrev.string, (char *)nil, (char *)nil, (char *)nil,&gendeltas))) goto end; + rev[1] = target->num; + + if (strcmp(rev[0],rev[1]) == 0) { + if (tostdout) { + FILE *o; +# if text_equals_binary_stdio || text_work_stdio + o = stdout; +# else + if (!(o=fdopen(STDOUT_FILENO,FOPEN_W_WORK))) + efaterror("stdout"); +# endif + fastcopy(workptr,o); + Ofclose(o); + } + goto end; + } + Izclose(&workptr); + + for (i=0; i<2; i++) { + diagnose("retrieving revision %s\n", rev[i]); + bufscpy(&commarg, "-p"); + bufscat(&commarg, rev[i]); + if (run( + (char*)0, + /* Do not collide with merger.c maketemp(). */ + arg[i+1] = maketemp(i+3), + co, quietarg, commarg.string, expandarg, + versionarg, RCSfilename, (char*)0 + )) + faterror("co failed"); + } + diagnose("Merging differences between %s and %s into %s%s\n", + rev[0], rev[1], workfilename, + tostdout?"; result to stdout":""); + + arg[0] = rev[0] = workfilename; + status = merge(tostdout, rev, arg); + } + +end: + Izclose(&workptr); + tempunlink(); + exitmain(nerror ? DIFF_TROUBLE : status); +} + +#if lint +# define exiterr rmergeExit +#endif + exiting void +exiterr() +{ + tempunlink(); + _exit(DIFF_TROUBLE); +} diff --git a/gnu/usr.bin/rcs/rcstest b/gnu/usr.bin/rcs/rcstest new file mode 100755 index 0000000..e0b6c82 --- /dev/null +++ b/gnu/usr.bin/rcs/rcstest @@ -0,0 +1,397 @@ +#!/bin/sh + +# Test RCS's functions. +# The RCS commands are searched for in the PATH as usual; +# to test the working directory's commands, prepend . to your PATH. + +# Test RCS by creating files RCS/a.* and RCS/a.c. +# If all goes well, output nothing, and remove the temporary files. +# Otherwise, send a message to standard output. +# Exit status is 0 if OK, 1 if an RCS bug is found, and 2 if scaffolding fails. +# With the -v option, output more debugging info. + +# If diff outputs `No differences encountered' when comparing identical files, +# then rcstest may also output these noise lines; ignore them. + +# The current directory and ./RCS must be readable, writable, and searchable. + +# $Id: rcstest,v 5.8 1991/11/20 17:58:10 eggert Exp $ + + +# Copyright 1990, 1991 by Paul Eggert +# Distributed under license by the Free Software Foundation, Inc. +# +# This file is part of RCS. +# +# RCS 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. +# +# RCS 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 RCS; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +# +# Report problems and direct all questions to: +# +# rcs-bugs@cs.purdue.edu + +RCSINIT=-x +export RCSINIT + +SLASH=/ +RCSfile=RCS${SLASH}a.c +RCS_alt=RCS${SLASH}a.d +lockfile=RCS${SLASH}a._ + +case $1 in +-v) q=; set -x;; +'') q=-q;; +*) echo >&2 "$0: usage: $0 [-v]"; exit 2 +esac + +test -d RCS || { + echo >&2 "$0: RCS: not a directory; please \`mkdir RCS' first." + exit 1 +} + +rm -f a.* $RCSfile $RCS_alt $lockfile && +echo 1.1 >a.11 && +echo 1.1.1.1 >a.3x1 && +echo 1.2 >a.12 || { echo "#initialization failed"; exit 2; } + +case `diff -c a.11 a.3x1` in +*'! 1.1.1.1') + diff='diff -c';; +*) + echo "#warning: diff -c does not work, so diagnostics may be cryptic" + diff=diff +esac + +rcs -i -L -ta.11 $q a.c && +<$RCSfile || { + echo "#rcs -i -L failed; perhaps RCS is not properly installed." + exit 1 +} + +rlog a.c >/dev/null || { echo "#rlog failed on empty RCS file"; exit 1; } +rm -f $RCSfile || exit 2 + +cp a.11 a.c && +ci -ta.11 -mm $q a.c && +<$RCSfile && +rcs -L $q a.c || { echo "#ci+rcs -L failed"; exit 1; } +test ! -f a.c || { echo "#ci did not remove working file"; exit 1; } +for l in '' '-l' +do + co $l $q a.c && + test -f a.c || { echo '#co' $l did not create working file; exit 1; } + $diff a.11 a.c || { echo '#ci' followed by co $l is not a no-op; exit 1; } +done + +cp a.12 a.c && +ci -mm $q a.c && +co $q a.c && +$diff a.12 a.c || { echo "#ci+co failed"; exit 1; } + +co -r1.1 $q a.c && +$diff a.11 a.c || { echo "#can't retrieve first revision"; exit 1; } + +rm -f a.c && +cp a.3x1 a.c && +ci -r1.1.1 -mm $q a.c && +co -r1.1.1.1 $q a.c && +$diff a.3x1 a.c || { echo "#branches failed"; exit 1; } + +co -l $q a.c && +ci -f -mm $q a.c && +co -r1.3 $q a.c && +$diff a.12 a.c || { echo "#(co -l; ci -f) failed"; exit 1; } + +co -l $q a.c && +echo 1.4 >a.c && +ci -l -mm $q a.c && +echo error >a.c && +ci -mm $q a.c || { echo "#ci -l failed"; exit 1; } + +co -l $q a.c && +echo 1.5 >a.c && +ci -u -mm $q a.c && +a.c || exit 2 +ci -mm $q a.c 2>/dev/null && { echo "#ci -u didn't unlock the file"; exit 1; } + +rm -f a.c && +rcs -l $q a.c && +co -u $q a.c || { echo "#rcs -l + co -u failed"; exit 1; } +rm -f a.c && +echo error >a.c || exit 2 +ci -mm $q a.c 2>/dev/null && { echo "#co -u didn't unlock the file"; exit 1; } + +rm -f a.c && +cp a.11 a.c && +co -f $q a.c || { echo "#co -f failed"; exit 1; } +$diff a.11 a.c >/dev/null && { echo "#co -f had no effect"; exit 1; } + +co -p1.1 $q a.c >a.t && +$diff a.11 a.t || { echo "#co -p failed"; exit 1; } + +for n in n N +do + rm -f a.c && + co -l $q a.c && + echo $n >a.$n && + cp a.$n a.c && + ci -${n}n -mm $q a.c && + co -rn $q a.c && + $diff a.$n a.c || { echo "#ci -$n failed"; exit 1; } +done + +case $LOGNAME in +?*) me=$LOGNAME;; +*) + case $USER in + ?*) me=$USER;; + *) + me=`who am i` || exit 2 + me=`echo "$me" | sed -e 's/ .*//' -e 's/.*!//'` + case $me in + '') echo >&2 "$0: cannot deduce user name"; exit 2 + esac + esac +esac +date=`date -u 2>/dev/null` || +date=`TZ=GMT0 date 2>/dev/null` || +date=`TZ= date` || exit 2 +set $date +case $2 in +Jan) m=01;; Feb) m=02;; Mar) m=03;; Apr) m=04;; May) m=05;; Jun) m=06;; +Jul) m=07;; Aug) m=08;; Sep) m=09;; Oct) m=10;; Nov) m=11;; Dec) m=12;; +*) echo >&2 "$0: $2: unknown month name"; exit 2 +esac +case $3 in +?) d=0$3;; +*) d=$3 +esac +case $6 in +[0-9][0-9][0-9][0-9]*) D=$6/$m/$d;; +*) + case $5 in + [0-9][0-9][0-9][0-9]*) D=$5/$m/$d;; + *) echo >&2 "$0: bad date format: $date"; exit 2 + esac +esac +T=$4 +case $PWD in +'') PWD=`pwd` +esac && +co -l $q a.c && +sed 's/@/$/g' >a.kv <a.k && +sed -e 's/w s [$]/w s '"$me"' $/' -e 's/[$]Locker: /&'"$me/" a.kv >a.kvl && +sed -e '/^\$/!d' -e 's/\$$/: old $/' a.k >a.o && +sed -e 's/\$[^ ]*: //' -e 's/ \$//' a.kv >a.v && +cp a.o a.c && +ci -d"$date" -ss -ww -u2.1 -mm $q a.c && +$diff a.kv a.c || { echo "#keyword expansion failed"; exit 1; } +co -p -ko $q a.c >a.oo && +$diff a.o a.oo || { echo "#co -p -ko failed"; exit 1; } +cp a.kv a.o || exit 2 +rcs -o2.1 $q a.c && +rcs -l $q a.c && +ci -k -u $q a.c && +$diff a.kv a.c || { echo "#ci -k failed"; exit 1; } +sed '/^[^$]/d' a.kv >a.i && +ident a.c >a.i1 && +sed -e 1d -e 's/^[ ]*//' a.i1 >a.i2 && +$diff a.i a.i2 || { echo "#ident failed"; exit 1; } + +rcs -i $q a.c 2>/dev/null && { echo "#rcs -i permitted existing file"; exit 1; } + +co -l $q a.c && +echo 2.2 >a.c && +ci -mm $q a.c && +echo 1.1.1.2 >a.c && +rcs -l1.1.1 $q a.c && +ci -r1.1.1.2 -mm $q a.c && +rcs -b1.1.1 $q a.c && +test " `co -p $q a.c`" = ' 1.1.1.2' || { echo "#rcs -b1.1.1 failed"; exit 1; } +rcs -b $q a.c && +test " `co -p $q a.c`" = ' 2.2' || { echo "#rcs -b failed"; exit 1; } + +echo 2.3 >a.c || exit 2 +rcs -U $q a.c || { echo "#rcs -U failed"; exit 1; } +ci -mm $q a.c || { echo "#rcs -U didn't unset strict locking"; exit 1; } +rcs -L $q a.c || { echo "#rcs -L failed"; exit 1; } +echo error >a.c || exit 2 +ci -mm $q a.c 2>/dev/null && { echo "#ci retest failed"; exit 1; } + +rm -f a.c && +log0=`rlog -h a.c` && +co -l $q a.c && +ci -mm $q a.c && +log1=`rlog -h a.c` && +test " $log0" = " $log1" || { echo "#unchanged ci didn't revert"; exit 1; } + +rm -f a.c && +rcs -nN:1.1 $q a.c && +co -rN $q a.c && +$diff a.11 a.c || { echo "#rcs -n failed"; exit 1; } + +rcs -NN:2.1 $q a.c && +co -rN $q a.c && +$diff a.kv a.c || { echo "#rcs -N failed"; exit 1; } + +co -l $q a.c && +rcs -c':::' $q a.c && +echo '$''Log$' >a.c && +ci -u -mm $q a.c && +test " `sed '$!d' a.c`" = ' :::' || { echo "#rcs -c failed"; exit 1; } + +rcs -o2.2: $q a.c && +co $q a.c && +$diff a.kv a.c || { echo "#rcs -o failed"; exit 1; } + +rcsdiff -r1.1 -r2.1 $q a.c >a.0 +case $? in +1) ;; +*) echo "#rcsdiff bad status"; exit 1 +esac +diff a.11 a.kv >a.1 +$diff a.0 a.1 || { echo "#rcsdiff failed"; exit 1; } + +rcs -l2.1 $q a.c || { echo "#rcs -l2.1 failed"; exit 1; } +for i in k kv kvl o v +do + rm -f a.c && + cp a.$i a.c && + rcsdiff -k$i $q a.c || { echo "#rcsdiff -k$i failed"; exit 1; } +done +co -p1.1 -ko $q a.c >a.t && +$diff a.11 a.t || { echo "#co -p1.1 -ko failed"; exit 1; } +rcs -u2.1 $q a.c || { echo "#rcs -u2.1 failed"; exit 1; } + +rm -f a.c && +co -l $q a.c && +cat >a.c <<'EOF' +2.2 +a +b +c +d +EOF +test $? = 0 && +ci -l -mm $q a.c && +co -p2.2 $q a.c | sed -e s/2.2/2.3/ -e s/b/b1/ >a.c && +ci -l -mm $q a.c && +co -p2.2 $q a.c | sed -e s/2.2/new/ -e s/d/d1/ >a.c || exit 2 +cat >a.0 <<'EOF' +2.3 +a +b1 +c +d1 +EOF +cat >a.1 <<'EOF' +<<<<<<< a.c +new +======= +2.3 +>>>>>>> 2.3 +a +b1 +c +d1 +EOF +rcsmerge -r2.2 -r2.3 $q a.c +case $? in +0) + if $diff a.0 a.c >/dev/null + then echo "#warning: diff3 -E does not work, " \ + "so merge and rcsmerge ignore overlaps and suppress overlap lines." + else + $diff a.1 a.c || { echo "#rcsmerge failed (status 0)"; exit 1; } + echo "#warning: The diff3 lib program exit status ignores overlaps," \ + "so rcsmerge does not warn about overlap lines that it generates." + fi + ;; +1) + $diff a.1 a.c || { echo "#rcsmerge failed (status 1)"; exit 1; } + ;; +*) + echo "#rcsmerge bad status"; exit 1 +esac + +nl=' +' +{ + co -p $q a.c | tr "$nl" '\200' >a.24 && + cp a.24 a.c && + ciOut=`(ci -l -mm $q a.c 2>&1)` && + case $ciOut in + ?*) echo >&2 "$ciOut" + esac && + co -p $q a.c | tr '\200' "$nl" >a.c && + rcsdiff -r2.3 $q a.c >/dev/null && + + echo 2.5 >a.c && + ci -l -mm $q a.c && + cp a.24 a.c && + rcsdiff -r2.4 $q a.c >/dev/null +} || echo "#warning: Traditional diff is used, so RCS is limited to text files." + +rcs -u -o2.4: $q a.c || { echo "#rcs -u -o failed"; exit 1; } + +rcs -i -Aa.c -t- $q a.d || { echo "#rcs -i -A failed"; exit 1; } + +rlog -r2.1 a.c >a.t && +grep '^checked in with -k' a.t >/dev/null && +sed '/^checked in with -k/d' a.t >a.u && +$diff - a.u < diff --git a/gnu/usr.bin/rcs/rlog/rlog.1 b/gnu/usr.bin/rcs/rlog/rlog.1 new file mode 100644 index 0000000..fa627ff --- /dev/null +++ b/gnu/usr.bin/rcs/rlog/rlog.1 @@ -0,0 +1,260 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $Id: rlog.1,v 5.3 1991/08/22 06:50:48 eggert Exp $ +.ds g \&\s-1UTC\s0 +.ds r \&\s-1RCS\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH RLOG 1 \*(Dt GNU +.SH NAME +rlog \- print log messages and other information about RCS files +.SH SYNOPSIS +.B rlog +.RI [ " options " ] " file " .\|.\|. +.SH DESCRIPTION +.B rlog +prints information about \*r files. +.PP +Pathnames matching an \*r suffix denote \*r files; +all others denote working files. +Names are paired as explained in +.BR ci (1). +.PP +.B rlog +prints the following information for each +\*r file: \*r pathname, working pathname, head (i.e., the number +of the latest revision on the trunk), default branch, access list, locks, +symbolic names, suffix, total number of revisions, +number of revisions selected for printing, and +descriptive text. This is followed by entries for the selected revisions in +reverse chronological order for each branch. For each revision, +.B rlog +prints revision number, author, date/time, state, number of +lines added/deleted (with respect to the previous revision), +locker of the revision (if any), and log message. +All times are displayed in Coordinated Universal Time (\*g). +Without options, +.B rlog +prints complete information. +The options below restrict this output. +.nr n \w'\f3\-V\fP\f2n\fP '+1n-1/1n +.TP \nn +.B \-L +Ignore \*r files that have no locks set. +This is convenient in combination with +.BR \-h , +.BR \-l , +and +.BR \-R . +.TP +.B \-R +Print only the name of the \*r file. +This is convenient for translating a +working pathname into an \*r pathname. +.TP +.B \-h +Print only the \*r pathname, working pathname, head, +default branch, access list, locks, +symbolic names, and suffix. +.TP +.B \-t +Print the same as +.BR \-h , +plus the descriptive text. +.TP +.B \-b +Print information about the revisions on the default branch, normally +the highest branch on the trunk. +.TP +.BI \-d "dates" +Print information about revisions with a checkin date/time in the ranges given by +the semicolon-separated list of +.IR dates . +A range of the form +.IB d1 < d2 +or +.IB d2 > d1 +selects the revisions that were deposited between +.I d1 +and +.I d2 +inclusive. +A range of the form +.BI < d +or +.IB d > +selects +all revisions dated +.I d +or earlier. +A range of the form +.IB d < +or +.BI > d +selects +all revisions dated +.I d +or later. +A range of the form +.I d +selects the single, latest revision dated +.I d +or earlier. +The date/time strings +.IR d , +.IR d1 , +and +.I d2 +are in the free format explained in +.BR co (1). +Quoting is normally necessary, especially for +.B < +and +.BR > . +Note that the separator is +a semicolon. +.TP +.BR \-l [\f2lockers\fP] +Print information about locked revisions only. +In addition, if the comma-separated list +.I lockers +of login names is given, +ignore all locks other than those held by the +.IR lockers . +For example, +.B "rlog\ \-L\ \-R\ \-lwft\ RCS/*" +prints the name of \*r files locked by the user +.BR wft . +.TP +.BR \-r [\f2revisions\fP] +prints information about revisions given in the comma-separated list +.I revisions +of revisions and ranges. +A range +.IB rev1 : rev2 +means revisions +.I rev1 +to +.I rev2 +on the same branch, +.BI : rev +means revisions from the beginning of the branch up to and including +.IR rev , +and +.IB rev : +means revisions starting with +.I rev +to the end of the branch containing +.IR rev . +An argument that is a branch means all +revisions on that branch. +A range of branches means all revisions +on the branches in that range. +A branch followed by a +.B .\& +means the latest revision in that branch. +A bare +.B \-r +with no +.I revisions +means the latest revision on the default branch, normally the trunk. +.TP +.BI \-s states +prints information about revisions whose state attributes match one of the +states given in the comma-separated list +.IR states . +.TP +.BR \-w [\f2logins\fP] +prints information about revisions checked in by users with +login names appearing in the comma-separated list +.IR logins . +If +.I logins +is omitted, the user's login is assumed. +.TP +.BI \-V n +Emulate \*r version +.I n +when generating logs. +See +.BR co (1) +for more. +.TP +.BI \-x "suffixes" +Use +.I suffixes +to characterize \*r files. +See +.BR ci (1) +for details. +.PP +.B rlog +prints the intersection of the revisions selected with +the options +.BR \-d , +.BR \-l , +.BR \-s , +and +.BR \-w , +intersected +with the union of the revisions selected by +.B \-b +and +.BR \-r . +.SH EXAMPLES +.LP +.nf +.B " rlog \-L \-R RCS/*" +.B " rlog \-L \-h RCS/*" +.B " rlog \-L \-l RCS/*" +.B " rlog RCS/*" +.fi +.LP +The first command prints the names of all \*r files in the subdirectory +.B RCS +that have locks. The second command prints the headers of those files, +and the third prints the headers plus the log messages of the locked revisions. +The last command prints complete information. +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +See +.BR ci (1) +for details. +.SH DIAGNOSTICS +The exit status is zero if and only if all operations were successful. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Revision Number: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 by Walter F. Tichy. +.br +Copyright \(co 1990, 1991 by Paul Eggert. +.SH "SEE ALSO" +ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), +rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.SH BUGS +The separator for revision ranges in the +.B \-r +option used to be +.B \- +instead of +.BR : , +but this leads to confusion when symbolic names contain +.BR \- . +For backwards compatibility +.B "rlog \-r" +still supports the old +.B \- +separator, but it warns about this obsolete use. +.br diff --git a/gnu/usr.bin/rcs/rlog/rlog.c b/gnu/usr.bin/rcs/rlog/rlog.c new file mode 100644 index 0000000..b18b0c9 --- /dev/null +++ b/gnu/usr.bin/rcs/rlog/rlog.c @@ -0,0 +1,1204 @@ +/* + * RLOG operation + */ +/***************************************************************************** + * print contents of RCS files + ***************************************************************************** + */ + +/* Copyright (C) 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991 by Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + + + + +/* $Log: rlog.c,v $ + * Revision 5.9 1991/09/17 19:07:40 eggert + * Getscript() didn't uncache partial lines. + * + * Revision 5.8 1991/08/19 03:13:55 eggert + * Revision separator is `:', not `-'. + * Check for missing and duplicate logs. Tune. + * Permit log messages that do not end in newline (including empty logs). + * + * Revision 5.7 1991/04/21 11:58:31 eggert + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.6 1991/02/26 17:07:17 eggert + * Survive RCS files with missing logs. + * strsave -> str_save (DG/UX name clash) + * + * Revision 5.5 1990/11/01 05:03:55 eggert + * Permit arbitrary data in logs and comment leaders. + * + * Revision 5.4 1990/10/04 06:30:22 eggert + * Accumulate exit status across files. + * + * Revision 5.3 1990/09/11 02:41:16 eggert + * Plug memory leak. + * + * Revision 5.2 1990/09/04 08:02:33 eggert + * Count RCS lines better. + * + * Revision 5.0 1990/08/22 08:13:48 eggert + * Remove compile-time limits; use malloc instead. Add setuid support. + * Switch to GMT. + * Report dates in long form, to warn about dates past 1999/12/31. + * Change "added/del" message to make room for the longer dates. + * Don't generate trailing white space. Add -V. Ansify and Posixate. + * + * Revision 4.7 89/05/01 15:13:48 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.6 88/08/09 19:13:28 eggert + * Check for memory exhaustion; don't access freed storage. + * Shrink stdio code size; remove lint. + * + * Revision 4.5 87/12/18 11:46:38 narten + * more lint cleanups (Guy Harris) + * + * Revision 4.4 87/10/18 10:41:12 narten + * Updating version numbers + * Changes relative to 1.1 actually relative to 4.2 + * + * Revision 1.3 87/09/24 14:01:10 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:22:45 jenkins + * Port to suns + * + * Revision 4.2 83/12/05 09:18:09 wft + * changed rewriteflag to external. + * + * Revision 4.1 83/05/11 16:16:55 wft + * Added -b, updated getnumericrev() accordingly. + * Replaced getpwuid() with getcaller(). + * + * Revision 3.7 83/05/11 14:24:13 wft + * Added options -L and -R; + * Fixed selection bug with -l on multiple files. + * Fixed error on dates of the form -d'>date' (rewrote getdatepair()). + * + * Revision 3.6 82/12/24 15:57:53 wft + * shortened output format. + * + * Revision 3.5 82/12/08 21:45:26 wft + * removed call to checkaccesslist(); used DATEFORM to format all dates; + * removed unused variables. + * + * Revision 3.4 82/12/04 13:26:25 wft + * Replaced getdelta() with gettree(); removed updating of field lockedby. + * + * Revision 3.3 82/12/03 14:08:20 wft + * Replaced getlogin with getpwuid(), %02d with %.2d, fancydate with PRINTDATE. + * Fixed printing of nil, removed printing of Suffix, + * added shortcut if no revisions are printed, disambiguated struct members. + * + * Revision 3.2 82/10/18 21:09:06 wft + * call to curdir replaced with getfullRCSname(), + * fixed call to getlogin(), cosmetic changes on output, + * changed conflicting long identifiers. + * + * Revision 3.1 82/10/13 16:07:56 wft + * fixed type of variables receiving from getc() (char -> int). + */ + + + +#include "rcsbase.h" + +struct lockers { /* lockers in locker option; stored */ + char const * login; /* lockerlist */ + struct lockers * lockerlink; + } ; + +struct stateattri { /* states in state option; stored in */ + char const * status; /* statelist */ + struct stateattri * nextstate; + } ; + +struct authors { /* login names in author option; */ + char const * login; /* stored in authorlist */ + struct authors * nextauthor; + } ; + +struct Revpairs{ /* revision or branch range in -r */ + unsigned numfld; /* option; stored in revlist */ + char const * strtrev; + char const * endrev; + struct Revpairs * rnext; + } ; + +struct Datepairs{ /* date range in -d option; stored in */ + char strtdate[datesize]; /* duelst and datelist */ + char enddate[datesize]; + struct Datepairs * dnext; + }; + +static char extractdelta P((struct hshentry const*)); +static int checkrevpair P((char const*,char const*)); +static struct hshentry const *readdeltalog P((void)); +static unsigned extdate P((struct hshentry*)); +static void cleanup P((void)); +static void exttree P((struct hshentry*)); +static void getauthor P((char*)); +static void getdatepair P((char*)); +static void getlocker P((char*)); +static void getnumericrev P((void)); +static void getrevpairs P((char*)); +static void getscript P((struct hshentry*)); +static void getstate P((char*)); +static void putabranch P((struct hshentry const*)); +static void putadelta P((struct hshentry const*,struct hshentry const*,int)); +static void putforest P((struct branchhead const*)); +static void putree P((struct hshentry const*)); +static void putrunk P((void)); +static void recentdate P((struct hshentry const*,struct Datepairs*)); +static void trunclocks P((void)); + +static char const *insDelFormat; +static int branchflag; /*set on -b */ +static int exitstatus; +static int lockflag; +static struct Datepairs *datelist, *duelst; +static struct Revpairs *revlist, *Revlst; +static struct authors *authorlist; +static struct lockers *lockerlist; +static struct stateattri *statelist; + + +mainProg(rlogId, "rlog", "$Id: rlog.c,v 5.9 1991/09/17 19:07:40 eggert Exp $") +{ + static char const cmdusage[] = + "\nrlog usage: rlog -{bhLRt} -ddates -l[lockers] -rrevs -sstates -w[logins] -Vn file ..."; + + register FILE *out; + char *a, **newargv; + struct Datepairs *currdate; + char const *accessListString, *accessFormat, *commentFormat; + char const *headFormat, *symbolFormat; + struct access const *curaccess; + struct assoc const *curassoc; + struct hshentry const *delta; + struct lock const *currlock; + int descflag, selectflag; + int onlylockflag; /* print only files with locks */ + int onlyRCSflag; /* print only RCS file name */ + unsigned revno; + + descflag = selectflag = true; + onlylockflag = onlyRCSflag = false; + out = stdout; + suffixes = X_DEFAULT; + + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + while (a = *++argv, 0<--argc && *a++=='-') { + switch (*a++) { + + case 'L': + onlylockflag = true; + break; + + case 'R': + onlyRCSflag =true; + break; + + case 'l': + lockflag = true; + getlocker(a); + break; + + case 'b': + branchflag = true; + break; + + case 'r': + getrevpairs(a); + break; + + case 'd': + getdatepair(a); + break; + + case 's': + getstate(a); + break; + + case 'w': + getauthor(a); + break; + + case 'h': + descflag = false; + break; + + case 't': + selectflag = false; + break; + + case 'q': + /* This has no effect; it's here for consistency. */ + quietflag = true; + break; + + case 'x': + suffixes = a; + break; + + case 'V': + setRCSversion(*argv); + break; + + default: + faterror("unknown option: %s%s", *argv, cmdusage); + + }; + } /* end of option processing */ + + if (argc<1) faterror("no input file%s", cmdusage); + + if (! (descflag|selectflag)) { + warn("-t overrides -h."); + descflag = true; + } + + if (RCSversion < VERSION(5)) { + accessListString = "\naccess list: "; + accessFormat = " %s"; + commentFormat = "\ncomment leader: \""; + headFormat = "\nRCS file: %s; Working file: %s\nhead: %s%s\nbranch: %s%s\nlocks: "; + insDelFormat = " lines added/del: %lu/%lu"; + symbolFormat = " %s: %s;"; + } else { + accessListString = "\naccess list:"; + accessFormat = "\n\t%s"; + commentFormat = "\ncomment leader: \""; + headFormat = "\nRCS file: %s\nWorking file: %s\nhead:%s%s\nbranch:%s%s\nlocks:%s"; + insDelFormat = " lines: +%lu -%lu"; + symbolFormat = "\n\t%s: %s"; + } + + /* now handle all filenames */ + do { + ffree(); + + if (pairfilenames(argc, argv, rcsreadopen, true, false) <= 0) + continue; + + /* now RCSfilename contains the name of the RCS file, and finptr + * the file descriptor. Workfilename contains the name of the + * working file. + */ + + /* Keep only those locks given by -l. */ + if (lockflag) + trunclocks(); + + /* do nothing if -L is given and there are no locks*/ + if (onlylockflag && !Locks) + continue; + + if ( onlyRCSflag ) { + aprintf(out, "%s\n", RCSfilename); + continue; + } + /* print RCS filename , working filename and optional + administrative information */ + /* could use getfullRCSname() here, but that is very slow */ + aprintf(out, headFormat, RCSfilename, workfilename, + Head ? " " : "", Head ? Head->num : "", + Dbranch ? " " : "", Dbranch ? Dbranch : "", + StrictLocks ? " strict" : "" + ); + currlock = Locks; + while( currlock ) { + aprintf(out, symbolFormat, currlock->login, + currlock->delta->num); + currlock = currlock->nextlock; + } + if (StrictLocks && RCSversionlogin); + curaccess = curaccess->nextaccess; + } + + aputs("\nsymbolic names:", out); /* print symbolic names */ + for (curassoc=Symbols; curassoc; curassoc=curassoc->nextassoc) + aprintf(out, symbolFormat, curassoc->symbol, curassoc->num); + aputs(commentFormat, out); + awrite(Comment.string, Comment.size, out); + aputs("\"\n", out); + if (VERSION(5)<=RCSversion || Expand != KEYVAL_EXPAND) + aprintf(out, "keyword substitution: %s\n", + expand_names[Expand] + ); + + gettree(); + + aprintf(out, "total revisions: %u", TotalDeltas); + + revno = 0; + + if (Head && selectflag & descflag) { + + getnumericrev(); /* get numeric revision or branch names */ + + exttree(Head); + + /* get most recently date of the dates pointed by duelst */ + currdate = duelst; + while( currdate) { + VOID sprintf(currdate->strtdate,DATEFORM,0,0,0,0,0,0); + recentdate(Head, currdate); + currdate = currdate->dnext; + } + + revno = extdate(Head); + + aprintf(out, ";\tselected revisions: %u", revno); + } + + afputc('\n',out); + if (descflag) { + aputs("description:\n", out); + getdesc(true); + } + if (revno) { + while (! (delta = readdeltalog())->selector || --revno) + ; + if (delta->next && countnumflds(delta->num)==2) + /* Read through delta->next to get its insertlns. */ + while (readdeltalog() != delta->next) + ; + putrunk(); + putree(Head); + } + aputs("=============================================================================\n",out); + } while (cleanup(), + ++argv, --argc >= 1); + Ofclose(out); + exitmain(exitstatus); +} + + static void +cleanup() +{ + if (nerror) exitstatus = EXIT_FAILURE; + Izclose(&finptr); +} + +#if lint +# define exiterr rlogExit +#endif + exiting void +exiterr() +{ + _exit(EXIT_FAILURE); +} + + + + static void +putrunk() +/* function: print revisions chosen, which are in trunk */ + +{ + register struct hshentry const *ptr; + + for (ptr = Head; ptr; ptr = ptr->next) + putadelta(ptr, ptr->next, true); +} + + + + static void +putree(root) + struct hshentry const *root; +/* function: print delta tree (not including trunk) in reverse + order on each branch */ + +{ + if ( root == nil ) return; + + putree(root->next); + + putforest(root->branches); +} + + + + + static void +putforest(branchroot) + struct branchhead const *branchroot; +/* function: print branches that has the same direct ancestor */ +{ + + if ( branchroot == nil ) return; + + putforest(branchroot->nextbranch); + + putabranch(branchroot->hsh); + putree(branchroot->hsh); +} + + + + + static void +putabranch(root) + struct hshentry const *root; +/* function : print one branch */ + +{ + + if ( root == nil) return; + + putabranch(root->next); + + putadelta(root, root, false); +} + + + + + + static void +putadelta(node,editscript,trunk) + register struct hshentry const *node, *editscript; + int trunk; +/* function: Print delta node if node->selector is set. */ +/* editscript indicates where the editscript is stored */ +/* trunk indicated whether this node is in trunk */ +{ + static char emptych[] = EMPTYLOG; + + register FILE *out; + char const *s; + size_t n; + struct branchhead const *newbranch; + struct buf branchnum; + char datebuf[datesize]; + + if (!node->selector) + return; + + out = stdout; + aprintf(out, + "----------------------------\nrevision %s", node->num + ); + if ( node->lockedby ) + aprintf(out, "\tlocked by: %s;", node->lockedby); + + aprintf(out, "\ndate: %s; author: %s; state: %s;", + date2str(node->date, datebuf), + node->author, node->state + ); + + if ( editscript ) + if(trunk) + aprintf(out, insDelFormat, + editscript->deletelns, editscript->insertlns); + else + aprintf(out, insDelFormat, + editscript->insertlns, editscript->deletelns); + + newbranch = node->branches; + if ( newbranch ) { + bufautobegin(&branchnum); + aputs("\nbranches:", out); + while( newbranch ) { + getbranchno(newbranch->hsh->num, &branchnum); + aprintf(out, " %s;", branchnum.string); + newbranch = newbranch->nextbranch; + } + bufautoend(&branchnum); + } + + afputc('\n', out); + s = node->log.string; + if (!(n = node->log.size)) { + s = emptych; + n = sizeof(emptych)-1; + } + awrite(s, n, out); + if (s[n-1] != '\n') + afputc('\n', out); +} + + + + + + static struct hshentry const * +readdeltalog() +/* Function : get the log message and skip the text of a deltatext node. + * Return the delta found. + * Assumes the current lexeme is not yet in nexttok; does not + * advance nexttok. + */ +{ + register struct hshentry * Delta; + struct buf logbuf; + struct cbuf cb; + + if (eoflex()) + fatserror("missing delta log"); + nextlex(); + if (!(Delta = getnum())) + fatserror("delta number corrupted"); + getkeystring(Klog); + if (Delta->log.string) + fatserror("duplicate delta log"); + bufautobegin(&logbuf); + cb = savestring(&logbuf); + Delta->log = bufremember(&logbuf, cb.size); + + nextlex(); + while (nexttok==ID && strcmp(NextString,Ktext)!=0) + ignorephrase(); + getkeystring(Ktext); + Delta->insertlns = Delta->deletelns = 0; + if ( Delta != Head) + getscript(Delta); + else + readstring(); + return Delta; +} + + + static void +getscript(Delta) +struct hshentry * Delta; +/* function: read edit script of Delta and count how many lines added */ +/* and deleted in the script */ + +{ + int ed; /* editor command */ + declarecache; + register RILE *fin; + register int c; + register unsigned long i; + struct diffcmd dc; + + fin = finptr; + setupcache(fin); + initdiffcmd(&dc); + while (0 <= (ed = getdiffcmd(fin,true,(FILE *)0,&dc))) + if (!ed) + Delta->deletelns += dc.nlines; + else { + /* skip scripted lines */ + i = dc.nlines; + Delta->insertlns += i; + cache(fin); + do { + for (;;) { + cacheget(c); + switch (c) { + default: + continue; + case SDELIM: + cacheget(c); + if (c == SDELIM) + continue; + if (--i) + fatserror("unexpected end to edit script"); + nextc = c; + uncache(fin); + return; + case '\n': + break; + } + break; + } + ++rcsline; + } while (--i); + uncache(fin); + } +} + + + + + + + + static void +exttree(root) +struct hshentry *root; +/* function: select revisions , starting with root */ + +{ + struct branchhead const *newbranch; + + if (root == nil) return; + + root->selector = extractdelta(root); + root->log.string = nil; + exttree(root->next); + + newbranch = root->branches; + while( newbranch ) { + exttree(newbranch->hsh); + newbranch = newbranch->nextbranch; + } +} + + + + + static void +getlocker(argv) +char * argv; +/* function : get the login names of lockers from command line */ +/* and store in lockerlist. */ + +{ + register char c; + struct lockers * newlocker; + argv--; + while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' || + c == '\n' || c == ';') ; + if ( c == '\0') { + lockerlist=nil; + return; + } + + while( c != '\0' ) { + newlocker = talloc(struct lockers); + newlocker->lockerlink = lockerlist; + newlocker->login = argv; + lockerlist = newlocker; + while ( ( c = (*++argv)) != ',' && c != '\0' && c != ' ' + && c != '\t' && c != '\n' && c != ';') ; + *argv = '\0'; + if ( c == '\0' ) return; + while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' || + c == '\n' || c == ';') ; + } +} + + + + static void +getauthor(argv) +char *argv; +/* function: get the author's name from command line */ +/* and store in authorlist */ + +{ + register c; + struct authors * newauthor; + + argv--; + while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' || + c == '\n' || c == ';') ; + if ( c == '\0' ) { + authorlist = talloc(struct authors); + authorlist->login = getusername(false); + authorlist->nextauthor = nil; + return; + } + + while( c != '\0' ) { + newauthor = talloc(struct authors); + newauthor->nextauthor = authorlist; + newauthor->login = argv; + authorlist = newauthor; + while( ( c = *++argv) != ',' && c != '\0' && c != ' ' + && c != '\t' && c != '\n' && c != ';') ; + * argv = '\0'; + if ( c == '\0') return; + while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' || + c == '\n' || c == ';') ; + } +} + + + + + static void +getstate(argv) +char * argv; +/* function : get the states of revisions from command line */ +/* and store in statelist */ + +{ + register char c; + struct stateattri *newstate; + + argv--; + while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' || + c == '\n' || c == ';') ; + if ( c == '\0'){ + warn("missing state attributes after -s options"); + return; + } + + while( c != '\0' ) { + newstate = talloc(struct stateattri); + newstate->nextstate = statelist; + newstate->status = argv; + statelist = newstate; + while( (c = (*++argv)) != ',' && c != '\0' && c != ' ' + && c != '\t' && c != '\n' && c != ';') ; + *argv = '\0'; + if ( c == '\0' ) return; + while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' || + c == '\n' || c == ';') ; + } +} + + + + static void +trunclocks() +/* Function: Truncate the list of locks to those that are held by the */ +/* id's on lockerlist. Do not truncate if lockerlist empty. */ + +{ + struct lockers const *plocker; + struct lock * plocked, * nextlocked; + + if ( (lockerlist == nil) || (Locks == nil)) return; + + /* shorten Locks to those contained in lockerlist */ + plocked = Locks; + Locks = nil; + while( plocked != nil) { + plocker = lockerlist; + while((plocker != nil) && ( strcmp(plocker->login, plocked->login)!=0)) + plocker = plocker->lockerlink; + nextlocked = plocked->nextlock; + if ( plocker != nil) { + plocked->nextlock = Locks; + Locks = plocked; + } + plocked = nextlocked; + } +} + + + + static void +recentdate(root, pd) + struct hshentry const *root; + struct Datepairs *pd; +/* function: Finds the delta that is closest to the cutoff date given by */ +/* pd among the revisions selected by exttree. */ +/* Successively narrows down the interval given by pd, */ +/* and sets the strtdate of pd to the date of the selected delta */ +{ + struct branchhead const *newbranch; + + if ( root == nil) return; + if (root->selector) { + if ( cmpnum(root->date, pd->strtdate) >= 0 && + cmpnum(root->date, pd->enddate) <= 0) + VOID strcpy(pd->strtdate, root->date); + } + + recentdate(root->next, pd); + newbranch = root->branches; + while( newbranch) { + recentdate(newbranch->hsh, pd); + newbranch = newbranch->nextbranch; + } +} + + + + + + + static unsigned +extdate(root) +struct hshentry * root; +/* function: select revisions which are in the date range specified */ +/* in duelst and datelist, start at root */ +/* Yield number of revisions selected, including those already selected. */ +{ + struct branchhead const *newbranch; + struct Datepairs const *pdate; + unsigned revno; + + if (!root) + return 0; + + if ( datelist || duelst) { + pdate = datelist; + while( pdate ) { + if ( (pdate->strtdate)[0] == '\0' || cmpnum(root->date,pdate->strtdate) >= 0){ + if ((pdate->enddate)[0] == '\0' || cmpnum(pdate->enddate,root->date) >= 0) + break; + } + pdate = pdate->dnext; + } + if ( pdate == nil) { + pdate = duelst; + for (;;) { + if (!pdate) { + root->selector = false; + break; + } + if ( cmpnum(root->date, pdate->strtdate) == 0) + break; + pdate = pdate->dnext; + } + } + } + revno = root->selector + extdate(root->next); + + newbranch = root->branches; + while( newbranch ) { + revno += extdate(newbranch->hsh); + newbranch = newbranch->nextbranch; + } + return revno; +} + + + + static char +extractdelta(pdelta) + struct hshentry const *pdelta; +/* function: compare information of pdelta to the authorlist, lockerlist,*/ +/* statelist, revlist and yield true if pdelta is selected. */ + +{ + struct lock const *plock; + struct stateattri const *pstate; + struct authors const *pauthor; + struct Revpairs const *prevision; + unsigned length; + + if ((pauthor = authorlist)) /* only certain authors wanted */ + while (strcmp(pauthor->login, pdelta->author) != 0) + if (!(pauthor = pauthor->nextauthor)) + return false; + if ((pstate = statelist)) /* only certain states wanted */ + while (strcmp(pstate->status, pdelta->state) != 0) + if (!(pstate = pstate->nextstate)) + return false; + if (lockflag) /* only locked revisions wanted */ + for (plock = Locks; ; plock = plock->nextlock) + if (!plock) + return false; + else if (plock->delta == pdelta) + break; + if ((prevision = Revlst)) /* only certain revs or branches wanted */ + for (;;) { + length = prevision->numfld; + if ( + countnumflds(pdelta->num) == length+(length&1) && + 0 <= compartial(pdelta->num, prevision->strtrev, length) && + 0 <= compartial(prevision->endrev, pdelta->num, length) + ) + break; + if (!(prevision = prevision->rnext)) + return false; + } + return true; +} + + + + static void +getdatepair(argv) + char * argv; +/* function: get time range from command line and store in datelist if */ +/* a time range specified or in duelst if a time spot specified */ + +{ + register char c; + struct Datepairs * nextdate; + char const * rawdate; + int switchflag; + + argv--; + while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' || + c == '\n' || c == ';') ; + if ( c == '\0' ) { + warn("missing date/time after -d"); + return; + } + + while( c != '\0' ) { + switchflag = false; + nextdate = talloc(struct Datepairs); + if ( c == '<' ) { /* case: -d strtdate)[0] = '\0'; + } else if (c == '>') { /* case: -d'>date' */ + c = *++argv; + (nextdate->enddate)[0] = '\0'; + switchflag = true; + } else { + rawdate = argv; + while( c != '<' && c != '>' && c != ';' && c != '\0') + c = *++argv; + *argv = '\0'; + if ( c == '>' ) switchflag=true; + str2date(rawdate, + switchflag ? nextdate->enddate : nextdate->strtdate); + if ( c == ';' || c == '\0') { /* case: -d date */ + VOID strcpy(nextdate->enddate,nextdate->strtdate); + nextdate->dnext = duelst; + duelst = nextdate; + goto end; + } else { + /* case: -d date< or -d date>; see switchflag */ + while ( (c= *++argv) == ' ' || c=='\t' || c=='\n'); + if ( c == ';' || c == '\0') { + /* second date missing */ + if (switchflag) + *nextdate->strtdate= '\0'; + else + *nextdate->enddate= '\0'; + nextdate->dnext = datelist; + datelist = nextdate; + goto end; + } + } + } + rawdate = argv; + while( c != '>' && c != '<' && c != ';' && c != '\0') + c = *++argv; + *argv = '\0'; + str2date(rawdate, + switchflag ? nextdate->strtdate : nextdate->enddate); + nextdate->dnext = datelist; + datelist = nextdate; + end: + if ( c == '\0') return; + while( (c = *++argv) == ';' || c == ' ' || c == '\t' || c =='\n'); + } +} + + + + static void +getnumericrev() +/* function: get the numeric name of revisions which stored in revlist */ +/* and then stored the numeric names in Revlst */ +/* if branchflag, also add default branch */ + +{ + struct Revpairs * ptr, *pt; + unsigned n; + struct buf s, e; + char const *lrev; + struct buf const *rstart, *rend; + + Revlst = nil; + ptr = revlist; + bufautobegin(&s); + bufautobegin(&e); + while( ptr ) { + n = 0; + rstart = &s; + rend = &e; + + switch (ptr->numfld) { + + case 1: /* -r rev */ + if (expandsym(ptr->strtrev, &s)) { + rend = &s; + n = countnumflds(s.string); + if (!n && (lrev = tiprev())) { + bufscpy(&s, lrev); + n = countnumflds(lrev); + } + } + break; + + case 2: /* -r rev- */ + if (expandsym(ptr->strtrev, &s)) { + bufscpy(&e, s.string); + n = countnumflds(s.string); + (n<2 ? e.string : strrchr(e.string,'.'))[0] = 0; + } + break; + + case 3: /* -r -rev */ + if (expandsym(ptr->endrev, &e)) { + if ((n = countnumflds(e.string)) < 2) + bufscpy(&s, ".1"); + else { + bufscpy(&s, e.string); + VOID strcpy(strrchr(s.string,'.'), ".1"); + } + } + break; + + default: /* -r rev1-rev2 */ + if ( + expandsym(ptr->strtrev, &s) + && expandsym(ptr->endrev, &e) + && checkrevpair(s.string, e.string) + ) { + n = countnumflds(s.string); + /* Swap if out of order. */ + if (compartial(s.string,e.string,n) > 0) { + rstart = &e; + rend = &s; + } + } + break; + } + + if (n) { + pt = ftalloc(struct Revpairs); + pt->numfld = n; + pt->strtrev = fstr_save(rstart->string); + pt->endrev = fstr_save(rend->string); + pt->rnext = Revlst; + Revlst = pt; + } + ptr = ptr->rnext; + } + /* Now take care of branchflag */ + if (branchflag && (Dbranch||Head)) { + pt = ftalloc(struct Revpairs); + pt->strtrev = pt->endrev = + Dbranch ? Dbranch : fstr_save(partialno(&s,Head->num,1)); + pt->rnext=Revlst; Revlst=pt; + pt->numfld = countnumflds(pt->strtrev); + } + bufautoend(&s); + bufautoend(&e); +} + + + + static int +checkrevpair(num1,num2) + char const *num1, *num2; +/* function: check whether num1, num2 are legal pair,i.e. + only the last field are different and have same number of + fields( if length <= 2, may be different if first field) */ + +{ + unsigned length = countnumflds(num1); + + if ( + countnumflds(num2) != length + || 2 < length && compartial(num1, num2, length-1) != 0 + ) { + error("invalid branch or revision pair %s : %s", num1, num2); + return false; + } + + return true; +} + + + + static void +getrevpairs(argv) +register char * argv; +/* function: get revision or branch range from command line, and */ +/* store in revlist */ + +{ + register char c; + struct Revpairs * nextrevpair; + int separator; + + c = *argv; + + /* Support old ambiguous '-' syntax; this will go away. */ + if (strchr(argv,':')) + separator = ':'; + else { + if (strchr(argv,'-') && VERSION(5) <= RCSversion) + warn("`-' is obsolete in `-r%s'; use `:' instead", argv); + separator = '-'; + } + + for (;;) { + while (c==' ' || c=='\t' || c=='\n') + c = *++argv; + nextrevpair = talloc(struct Revpairs); + nextrevpair->rnext = revlist; + revlist = nextrevpair; + nextrevpair->numfld = 1; + nextrevpair->strtrev = argv; + for (;; c = *++argv) { + switch (c) { + default: + continue; + case '\0': case ' ': case '\t': case '\n': + case ',': case ';': + break; + case ':': case '-': + if (c == separator) + break; + continue; + } + break; + } + *argv = '\0'; + while (c==' ' || c=='\t' || c=='\n') + c = *++argv; + if (c == separator) { + while( (c =(*++argv)) == ' ' || c == '\t' || c =='\n') ; + nextrevpair->endrev = argv; + for (;; c = *++argv) { + switch (c) { + default: + continue; + case '\0': case ' ': case '\t': case '\n': + case ',': case ';': + break; + case ':': case '-': + if (c == separator) + continue; + break; + } + break; + } + *argv = '\0'; + while (c==' ' || c=='\t' || c =='\n') + c = *++argv; + nextrevpair->numfld = + !nextrevpair->endrev[0] ? 2 /* -rrev- */ : + !nextrevpair->strtrev[0] ? 3 /* -r-rev */ : + 4 /* -rrev1-rev2 */; + } + if (!c) + break; + if (c!=',' && c!=';') + error("missing `,' near `%c%s'", c, argv+1); + } +} -- cgit v1.1