summaryrefslogtreecommitdiff
path: root/gnu/usr.bin/rcs/co
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/usr.bin/rcs/co')
-rw-r--r--gnu/usr.bin/rcs/co/Makefile9
-rw-r--r--gnu/usr.bin/rcs/co/co.1689
-rw-r--r--gnu/usr.bin/rcs/co/co.c821
3 files changed, 1519 insertions, 0 deletions
diff --git a/gnu/usr.bin/rcs/co/Makefile b/gnu/usr.bin/rcs/co/Makefile
new file mode 100644
index 00000000000..c1eb6415a2f
--- /dev/null
+++ b/gnu/usr.bin/rcs/co/Makefile
@@ -0,0 +1,9 @@
+# $Id: Makefile,v 1.1 1995/10/18 08:40:59 deraadt Exp $
+
+PROG= co
+CFLAGS+= -I${.CURDIR}/../lib
+LDADD= ${LIBRCS} -lgnumalloc
+DPADD= ${LIBRCS} /usr/lib/libgnumalloc.a
+
+.include "../../Makefile.inc"
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/co/co.1 b/gnu/usr.bin/rcs/co/co.1
new file mode 100644
index 00000000000..efa4705a626
--- /dev/null
+++ b/gnu/usr.bin/rcs/co/co.1
@@ -0,0 +1,689 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: co.1,v 1.1 1995/10/18 08:40:59 deraadt Exp $
+.ds i \&\s-1ISO\s0
+.ds r \&\s-1RCS\s0
+.ds u \&\s-1UTC\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 can 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 can 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 can 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
+.IR 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.
+If
+.I rev
+begins with a period,
+then the default branch (normally the trunk) is prepended to it.
+If
+.I rev
+is a branch number followed by a period,
+then the latest revision on that branch is used.
+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.
+Log messages are inserted after
+.B $\&Log$
+keywords even if
+.B \-kk
+is specified,
+since this tends to be more useful when merging changes.
+.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
+.IR date .
+The date and time can 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 (\*u):
+.RS
+.LP
+.RS
+.nf
+.ta \w'\f3Thu, 11 Jan 1990 20:00:00 \-0800\fP 'u
+.ne 10
+\f38:00 pm lt\fP
+\f34:00 AM, Jan. 12, 1990\fP default is \*u
+\f31990-01-12 04:00:00+0000\fP \*i 8601 (\*u)
+\f31990-01-11 20:00:00\-0800\fP \*i 8601 (local time)
+\f31990/01/12 04:00:00\fP traditional \*r 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 Internet RFC 822
+\f312-January-1990, 04:00 WET\fP
+.ta 4n +4n +4n +4n
+.fi
+.RE
+.LP
+Most fields in the date and time can be defaulted.
+The default time zone is normally \*u, but this can be overridden by the
+.B \-z
+option.
+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, without
+.BR \-z ,
+the date
+.B "20, 10:30"
+defaults to
+10:30:00 \*u of the 20th of the \*u 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
+.IR state .
+.TP
+.B \-T
+Preserve the modification time on the \*r file
+even if the \*r file changes because a lock is added or removed.
+This option can suppress extensive recompilation caused by a
+.BR make (1)
+dependency of some other copy of the working file on the \*r file.
+Use this option with care; it can suppress recompilation even when it is needed,
+i.e. when the change of lock
+would mean a change to keyword strings in the other working file.
+.TP
+.BR \-w [\f2login\fP]
+retrieves the latest revision on the selected branch which was checked in
+by the user with login name
+.IR 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
+.IR 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
+.IR rev2 .
+This means that all changes that transform
+.I rev2
+into
+.I rev1
+are applied to a copy of
+.IR 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
+can 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
+.IR rev1 .
+.RE
+.TP
+.BI \-V
+Print \*r's version number.
+.TP
+.BI \-V n
+Emulate \*r version
+.I n,
+where
+.I n
+can be
+.BR 3 ,
+.BR 4 ,
+or
+.BR 5 .
+This can 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
+.BR "rcs \-V" ;
+this works with newer versions of \*r.
+If it doesn't work, 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 loses its default branch.
+An \*r revision generated while emulating version 4 or earlier has
+a time stamp that is off by up to 13 hours.
+A revision extracted while emulating version 4 or earlier contains
+abbreviated dates of the form
+.IB yy / mm / dd
+and can also contain different white space and line prefixes
+in the substitution for
+.BR $\&Log$ .
+.TP
+.BI \-x "suffixes"
+Use
+.I suffixes
+to characterize \*r files.
+See
+.BR ci (1)
+for details.
+.TP
+.BI \-z zone
+specifies the date output format in keyword substitution,
+and specifies the default time zone for
+.I date
+in the
+.BI \-d date
+option.
+The
+.I zone
+should be empty, a numeric \*u offset, or the special string
+.B LT
+for local time.
+The default is an empty
+.IR zone ,
+which uses the traditional \*r format of \*u without any time zone indication
+and with slashes separating the parts of the date;
+otherwise, times are output in \*i 8601 format with time zone indication.
+For example, if local time is January 11, 1990, 8pm Pacific Standard Time,
+eight hours west of \*u,
+then the time is output as follows:
+.RS
+.LP
+.RS
+.nf
+.ta \w'\f3\-z+0530\fP 'u +\w'\f31990-01-11 09:30:00+0530\fP 'u
+.ne 4
+\f2option\fP \f2time output\fP
+\f3\-z\fP \f31990/01/11 04:00:00\fP \f2(default)\fP
+\f3\-zLT\fP \f31990-01-11 20:00:00\-0800\fP
+\f3\-z+0530\fP \f31990-01-11 09:30:00+0530\fP
+.ta 4n +4n +4n +4n
+.fi
+.RE
+.LP
+The
+.B \-z
+option does not affect dates stored in \*r files,
+which are always \*u.
+.RE
+.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 can 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 the revision was checked in.
+With
+.BI \-z zone
+a numeric time zone offset is appended; otherwise, the date is \*u.
+.TP
+.B $\&Header$
+A standard header containing the full pathname of the \*r file, the
+revision number, the date and time, the author, the state,
+and the locker (if locked).
+With
+.BI \-z zone
+a numeric time zone offset is appended to the date; otherwise, the date is \*u.
+.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
+and time.
+With
+.BI \-z zone
+a numeric time zone offset is appended; otherwise, the date is \*u.
+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.
+Each inserted line is prefixed by the string that prefixes the
+.B $\&Log$
+line. For example, if the
+.B $\&Log$
+line is
+.RB \*(lq "//\ $\&Log: tan.cc\ $" \*(rq,
+\*r prefixes each line of the log with
+.RB \*(lq "//\ " \*(rq.
+This is useful for programming languages without multi-line comments.
+.TP
+.B $\&Name$
+The symbolic name used to check out the revision, if any.
+For example,
+.B "co\ \-rJoe"
+generates
+.BR "$\&Name:\ Joe\ $" .
+Plain
+.B co
+generates just
+.BR "$\&Name:\ \ $" .
+.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).
+.PP
+The following characters in keyword values are represented by escape sequences
+to keep keyword strings well-formed.
+.LP
+.RS
+.nf
+.ne 6
+.ta \w'newline 'u
+\f2char escape sequence\fP
+tab \f3\et\fP
+newline \f3\en\fP
+space \f3\e040
+$ \e044
+\e \e\e\fP
+.fi
+.RE
+.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
+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, 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
+Manual Page Revision: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991, 1992, 1993, 1994 Paul Eggert.
+.SH "SEE ALSO"
+rcsintro(1), ci(1), ctime(3), date(1), ident(1), make(1),
+rcs(1), rcsclean(1), rcsdiff(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.
+.br
diff --git a/gnu/usr.bin/rcs/co/co.c b/gnu/usr.bin/rcs/co/co.c
new file mode 100644
index 00000000000..75d81effc62
--- /dev/null
+++ b/gnu/usr.bin/rcs/co/co.c
@@ -0,0 +1,821 @@
+/* Check out working files from revisions of RCS files. */
+
+/* Copyright 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991, 1992, 1993, 1994 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: co.c,v $
+ * Revision 1.1 1995/10/18 08:41:00 deraadt
+ * Initial revision
+ *
+ * Revision 1.3 1995/02/24 02:07:49 mycroft
+ * RCS 5.6.7.4
+ *
+ * Revision 5.16 1994/03/17 14:05:48 eggert
+ * Move buffer-flushes out of critical sections, since they aren't critical.
+ * Use ORCSerror to clean up after a fatal error. Remove lint.
+ * Specify subprocess input via file descriptor, not file name.
+ *
+ * Revision 5.15 1993/11/09 17:40:15 eggert
+ * -V now prints version on stdout and exits. Don't print usage twice.
+ *
+ * Revision 5.14 1993/11/03 17:42:27 eggert
+ * Add -z. Generate a value for the Name keyword.
+ * Don't arbitrarily limit the number of joins.
+ * Improve quality of diagnostics.
+ *
+ * Revision 5.13 1992/07/28 16:12:44 eggert
+ * Add -V. Check that working and RCS files are distinct.
+ *
+ * Revision 5.12 1992/02/17 23:02:08 eggert
+ * Add -T.
+ *
+ * Revision 5.11 1992/01/24 18:44:19 eggert
+ * Add support for bad_creat0. lint -> RCS_lint
+ *
+ * Revision 5.10 1992/01/06 02:42:34 eggert
+ * Update usage string.
+ *
+ * Revision 5.9 1991/10/07 17:32:46 eggert
+ * -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 *addjoin P((char*));
+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 *join;
+static char const *expandarg, *suffixarg, *versionarg, *zonearg;
+static char const **joinlist; /* revisions to be joined */
+static int joinlength;
+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 1.1 1995/10/18 08:41:00 deraadt Exp $")
+{
+ static char const cmdusage[] =
+ "\nco usage: co -{fIlMpqru}[rev] -ddate -jjoins -ksubst -sstate -T -w[who] -Vn -xsuff -zzone file ...";
+
+ char *a, **newargv;
+ char const *author, *date, *rev, *state;
+ char const *joinname, *newdate, *neworkname;
+ int changelock; /* 1 if a lock has been changed, -1 if error */
+ int expmode, r, tostdout, workstatstat;
+ int Ttimeflag;
+ struct buf numericrev; /* expanded revision number */
+ char finaldate[datesize];
+
+ setrid();
+ author = date = rev = state = 0;
+ bufautobegin(&numericrev);
+ expmode = -1;
+ suffixes = X_DEFAULT;
+ tostdout = false;
+ Ttimeflag = 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("-u overridden by -l.");
+ }
+ lockflag = 1;
+ goto revno;
+
+ case 'u':
+ if (0 < lockflag) {
+ warn("-l overridden by -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 'T':
+ if (*a)
+ goto unknown;
+ Ttimeflag = true;
+ 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 'z':
+ zonearg = *argv;
+ zone_set(a);
+ break;
+
+ case 'k': /* set keyword expand mode */
+ expandarg = *argv;
+ if (0 <= expmode) redefined('k');
+ if (0 <= (expmode = str2expmode(a)))
+ break;
+ /* fall into */
+ default:
+ unknown:
+ error("unknown option: %s%s", *argv, cmdusage);
+
+ };
+ } /* end of option processing */
+
+ if (tostdout)
+# if text_equals_binary_stdio || text_work_stdio
+ workstdout = stdout;
+# else
+ if (!(workstdout = fdopen(STDOUT_FILENO, FOPEN_W_WORK)))
+ efaterror("standard output");
+# endif
+
+ /* Now handle all pathnames. */
+ if (nerror) cleanup();
+ else if (argc < 1) faterror("no input file%s", cmdusage);
+ else for (; 0 < argc; cleanup(), ++argv, --argc) {
+ ffree();
+
+ if (pairnames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false) <= 0)
+ continue;
+
+ /*
+ * RCSname contains the name of the RCS file, and finptr
+ * points at it. workname contains the name of the working file.
+ * Also, RCSstat has been set.
+ */
+ diagnose("%s --> %s\n", RCSname, tostdout?"standard output":workname);
+
+ workstatstat = -1;
+ if (tostdout) {
+ neworkname = 0;
+ neworkptr = workstdout;
+ } else {
+ workstatstat = stat(workname, &workstat);
+ if (workstatstat == 0 && same_file(RCSstat, workstat, 0)) {
+ rcserror("RCS file is the same as working file %s.",
+ workname
+ );
+ continue;
+ }
+ neworkname = makedirtemp(1);
+ if (!(neworkptr = fopen(neworkname, FOPEN_W_WORK))) {
+ if (errno == EACCES)
+ workerror("permission denied on parent directory");
+ else
+ eerror(neworkname);
+ continue;
+ }
+ }
+
+ gettree(); /* reads in the delta tree */
+
+ if (!Head) {
+ /* no revisions; create empty file */
+ diagnose("no revisions present; generating empty revision 0.0\n");
+ if (lockflag)
+ warn(
+ "no revisions, so nothing can be %slocked",
+ lockflag < 0 ? "un" : ""
+ );
+ Ozclose(&fcopy);
+ if (workstatstat == 0)
+ if (!rmworkfile()) continue;
+ changelock = 0;
+ newdate = 0;
+ } else {
+ int locks = lockflag ? findlock(false, &targetdelta) : 0;
+ if (rev) {
+ /* expand symbolic revision number */
+ if (!expandsym(rev, &numericrev))
+ continue;
+ } else {
+ switch (locks) {
+ 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, true);
+
+ if (
+ changelock < 0
+ || (changelock && !checkaccesslist())
+ || dorewrite(lockflag, changelock) != 0
+ )
+ continue;
+
+ if (0 <= expmode)
+ Expand = expmode;
+ if (0 < lockflag && Expand == VAL_EXPAND) {
+ rcserror("cannot combine -kv and -l");
+ continue;
+ }
+
+ if (join && !preparejoin()) continue;
+
+ diagnose("revision %s%s\n",targetdelta->num,
+ 0<lockflag ? " (locked)" :
+ lockflag<0 ? " (unlocked)" : "");
+
+ /* Prepare to remove old working file if necessary. */
+ if (workstatstat == 0)
+ if (!rmworkfile()) continue;
+
+ /* skip description */
+ getdesc(false); /* don't echo*/
+
+ locker_expansion = 0 < lockflag;
+ targetdelta->name = namedrev(rev, targetdelta);
+ joinname = buildrevision(
+ gendeltas, targetdelta,
+ join&&tostdout ? (FILE*)0 : neworkptr,
+ Expand!=OLD_EXPAND
+ );
+# if !large_memory
+ if (fcopy == neworkptr)
+ fcopy = 0; /* Don't close it twice. */
+# endif
+ if_advise_access(changelock && gendeltas->first!=targetdelta,
+ finptr, MADV_SEQUENTIAL
+ );
+
+ if (donerewrite(changelock,
+ Ttimeflag ? RCSstat.st_mtime : (time_t)-1
+ ) != 0)
+ continue;
+
+ if (changelock) {
+ locks += lockflag;
+ if (1 < locks)
+ rcswarn("You now have %d locks.", locks);
+ }
+
+ newdate = targetdelta->date;
+ if (join) {
+ newdate = 0;
+ if (!joinname) {
+ aflush(neworkptr);
+ joinname = neworkname;
+ }
+ if (!buildjoin(joinname))
+ continue;
+ }
+ }
+ if (!tostdout) {
+ mode_t m = WORKMODE(RCSstat.st_mode,
+ ! (Expand==VAL_EXPAND || (lockflag<=0 && StrictLocks))
+ );
+ time_t t = mtimeflag&&newdate ? date2time(newdate) : (time_t)-1;
+ aflush(neworkptr);
+ ignoreints();
+ r = chnamemod(&neworkptr, neworkname, workname, 1, m, t);
+ keepdirtemp(neworkname);
+ restoreints();
+ if (r != 0) {
+ eerror(workname);
+ error("see %s", neworkname);
+ continue;
+ }
+ diagnose("done\n");
+ }
+ }
+
+ tempunlink();
+ Ofclose(workstdout);
+ exitmain(exitstatus);
+
+} /* end of main (co) */
+
+ static void
+cleanup()
+{
+ if (nerror) exitstatus = EXIT_FAILURE;
+ Izclose(&finptr);
+ ORCSclose();
+# if !large_memory
+ if (fcopy!=workstdout) Ozclose(&fcopy);
+# endif
+ if (neworkptr!=workstdout) Ozclose(&neworkptr);
+ dirtempunlink();
+}
+
+#if RCS_lint
+# define exiterr coExit
+#endif
+ void
+exiterr()
+{
+ ORCSerror();
+ dirtempunlink();
+ tempunlink();
+ _exit(EXIT_FAILURE);
+}
+
+
+/*****************************************************************
+ * The following routines are auxiliary routines
+ *****************************************************************/
+
+ static int
+rmworkfile()
+/*
+ * Prepare to remove workname, 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): ",
+ workname,
+ myself(workstat.st_uid) ? "" : ", and you do not own it"
+ )) {
+ error(!quietflag && ttystdin()
+ ? "checkout aborted"
+ : "writable %s exists; checkout aborted", workname);
+ 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) {
+ 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)) {
+ rcserror("revision %s locked by %s; use co -r or rcs -u",
+ num, next->login
+ );
+ return -1;
+ }
+ trail=next;
+ next=next->nextlock;
+ }
+ if (next) {
+ /*found one; delete it */
+ trail->nextlock=next->nextlock;
+ Locks=dummy.nextlock;
+ next->delta->lockedby = 0;
+ return 1; /*success*/
+ } else return 0; /*no lock on delta*/
+}
+
+
+
+
+/*****************************************************************
+ * The rest of the routines are for handling joins
+ *****************************************************************/
+
+
+ static char *
+addjoin(joinrev)
+ char *joinrev;
+/* Add joinrev's number to joinlist, yielding address of char past joinrev,
+ * or 0 if no such revision exists.
+ */
+{
+ register char *j;
+ register struct hshentry *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*)0,(char*)0,(char*)0,&joindeltas);
+ bufautoend(&numrev);
+ *j = terminator;
+ if (d) {
+ joinlist[++lastjoin] = d->num;
+ return j;
+ }
+ return 0;
+}
+
+ static int
+preparejoin()
+/* Function: Parses a join list pointed to by join and places pointers to the
+ * revision numbers into joinlist.
+ */
+{
+ register char *j;
+
+ j=join;
+ lastjoin= -1;
+ for (;;) {
+ while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
+ if (*j=='\0') break;
+ if (lastjoin>=joinlength-2) {
+ joinlist =
+ (joinlength *= 2) == 0
+ ? tnalloc(char const *, joinlength = 16)
+ : trealloc(char const *, joinlist, joinlength);
+ }
+ 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 {
+ rcsfaterror("join pair incomplete");
+ }
+ } 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 {
+ rcsfaterror("join pair incomplete");
+ }
+ }
+ }
+ if (lastjoin < 1)
+ rcsfaterror("empty join");
+ return true;
+}
+
+
+
+ static char const *
+getancestor(r1, r2)
+ char const *r1, *r2;
+/* Yield the common ancestor of r1 and r2 if successful, 0 otherwise.
+ * Work reliably only if r1 and r2 are not branch numbers.
+ */
+{
+ static struct buf t1, t2;
+
+ int l1, l2, l3;
+ char const *r;
+
+ l1 = countnumflds(r1);
+ l2 = countnumflds(r2);
+ if ((2<l1 || 2<l2) && cmpnum(r1,r2)!=0) {
+ /* not on main trunk or identical */
+ l3 = 0;
+ while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
+ l3 += 2;
+ /* This will terminate since r1 and r2 are not the same; see above. */
+ if (l3==0) {
+ /* no common prefix; common ancestor on main trunk */
+ VOID partialno(&t1, r1, l1>2 ? 2 : l1);
+ VOID partialno(&t2, r2, l2>2 ? 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);
+ }
+ rcserror("common ancestor of %s and %s undefined", r1, r2);
+ return 0;
+}
+
+
+
+ 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[11];
+ char const **p;
+
+ bufautobegin(&commarg);
+ bufautobegin(&subs);
+ rev2 = maketemp(0);
+ rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
+
+ cov[1] = CO;
+ /* cov[2] setup below */
+ p = &cov[3];
+ if (expandarg) *p++ = expandarg;
+ if (suffixarg) *p++ = suffixarg;
+ if (versionarg) *p++ = versionarg;
+ if (zonearg) *p++ = zonearg;
+ *p++ = quietarg;
+ *p++ = RCSname;
+ *p = 0;
+
+ mergev[1] = MERGE;
+ mergev[2] = mergev[4] = "-L";
+ /* rest of mergev setup below */
+
+ i=0;
+ while (i<lastjoin) {
+ /*prepare marker for merge*/
+ if (i==0)
+ bufscpy(&subs, targetdelta->num);
+ 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[2] = commarg.string;
+ if (runv(-1, rev2, cov))
+ goto badmerge;
+ diagnose("revision %s\n",joinlist[i+1]);
+ bufscpy(&commarg, "-p");
+ bufscat(&commarg, joinlist[i+1]);
+ cov[2] = commarg.string;
+ if (runv(-1, rev3, cov))
+ goto badmerge;
+ diagnose("merging...\n");
+ mergev[3] = subs.string;
+ mergev[5] = joinlist[i+1];
+ p = &mergev[6];
+ if (quietflag) *p++ = quietarg;
+ if (lastjoin<=i+2 && workstdout) *p++ = "-p";
+ *p++ = initialfile;
+ *p++ = rev2;
+ *p++ = rev3;
+ *p = 0;
+ switch (runv(-1, (char*)0, 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;
+}