summaryrefslogtreecommitdiff
path: root/gnu
diff options
context:
space:
mode:
authorchristos <christos@NetBSD.org>2000-09-04 21:47:02 +0000
committerchristos <christos@NetBSD.org>2000-09-04 21:47:02 +0000
commit5c984f3d998e2c70cb63be34997f0bbec88ccf3e (patch)
treeb1eba87db109d0fcc25e992d3a467a01e80e6909 /gnu
parent7803a4e4eff3e1fcfd26156a9c448fae2e3ef3c3 (diff)
Initial import of cvs-1.10.8 from ftp.cvshome.org minus the os support.
Diffstat (limited to 'gnu')
-rw-r--r--gnu/dist/cvs/contrib/.cvsignore14
-rw-r--r--gnu/dist/cvs/contrib/ChangeLog406
-rw-r--r--gnu/dist/cvs/contrib/Makefile.in127
-rw-r--r--gnu/dist/cvs/contrib/README104
-rw-r--r--gnu/dist/cvs/contrib/clmerge.pl152
-rw-r--r--gnu/dist/cvs/contrib/cln_hist.pl91
-rw-r--r--gnu/dist/cvs/contrib/commit_prep.pl215
-rw-r--r--gnu/dist/cvs/contrib/cvs2vendor.sh142
-rw-r--r--gnu/dist/cvs/contrib/cvs_acls.pl141
-rw-r--r--gnu/dist/cvs/contrib/cvscheck.man52
-rw-r--r--gnu/dist/cvs/contrib/cvscheck.sh83
-rw-r--r--gnu/dist/cvs/contrib/cvshelp.man561
-rw-r--r--gnu/dist/cvs/contrib/descend.man114
-rw-r--r--gnu/dist/cvs/contrib/descend.sh115
-rw-r--r--gnu/dist/cvs/contrib/dirfns.shar481
-rw-r--r--gnu/dist/cvs/contrib/intro.doc112
-rw-r--r--gnu/dist/cvs/contrib/log.pl177
-rw-r--r--gnu/dist/cvs/contrib/log_accum.pl571
-rw-r--r--gnu/dist/cvs/contrib/mfpipe.pl85
-rw-r--r--gnu/dist/cvs/contrib/rcs-to-cvs.sh184
-rw-r--r--gnu/dist/cvs/contrib/rcs2log.sh677
-rw-r--r--gnu/dist/cvs/contrib/rcs2sccs.sh141
-rw-r--r--gnu/dist/cvs/contrib/rcslock.pl235
-rw-r--r--gnu/dist/cvs/contrib/sccs2rcs.csh275
-rw-r--r--gnu/dist/cvs/doc/.cvsignore34
-rw-r--r--gnu/dist/cvs/doc/ChangeLog3365
-rw-r--r--gnu/dist/cvs/doc/ChangeLog.fsf38
-rw-r--r--gnu/dist/cvs/doc/Makefile.in197
-rw-r--r--gnu/dist/cvs/doc/RCSFILES276
-rw-r--r--gnu/dist/cvs/doc/cvs-paper.ms1069
-rw-r--r--gnu/dist/cvs/doc/cvs.texinfo13561
-rw-r--r--gnu/dist/cvs/doc/cvsclient.texi2073
-rw-r--r--gnu/dist/cvs/man/.cvsignore1
-rw-r--r--gnu/dist/cvs/man/ChangeLog237
-rw-r--r--gnu/dist/cvs/man/Makefile.in91
-rw-r--r--gnu/dist/cvs/man/cvs.12129
-rw-r--r--gnu/dist/cvs/man/cvs.5325
-rw-r--r--gnu/dist/cvs/man/cvsbug.8243
-rwxr-xr-xgnu/dist/cvs/src/build_src.com68
-rw-r--r--gnu/dist/cvs/src/diff.c996
-rw-r--r--gnu/dist/cvs/src/edit.c1125
-rw-r--r--gnu/dist/cvs/src/entries.c1185
-rw-r--r--gnu/dist/cvs/src/error.c236
-rw-r--r--gnu/dist/cvs/src/expand_path.c335
-rw-r--r--gnu/dist/cvs/src/fileattr.c652
-rw-r--r--gnu/dist/cvs/src/filesubr.c973
-rw-r--r--gnu/dist/cvs/src/find_names.c425
-rw-r--r--gnu/dist/cvs/src/hardlink.c305
-rw-r--r--gnu/dist/cvs/src/hash.c517
-rw-r--r--gnu/dist/cvs/src/history.c1587
-rw-r--r--gnu/dist/cvs/src/ignore.c500
-rw-r--r--gnu/dist/cvs/src/import.c1596
-rw-r--r--gnu/dist/cvs/src/lock.c941
-rw-r--r--gnu/dist/cvs/src/log.c1466
-rw-r--r--gnu/dist/cvs/src/login.c502
-rw-r--r--gnu/dist/cvs/src/logmsg.c886
-rw-r--r--gnu/dist/cvs/src/main.c1151
-rw-r--r--gnu/dist/cvs/src/mkmodules.c946
-rw-r--r--gnu/dist/cvs/src/modules.c1074
-rw-r--r--gnu/dist/cvs/src/myndbm.c309
-rw-r--r--gnu/dist/cvs/src/no_diff.c93
-rw-r--r--gnu/dist/cvs/src/parseinfo.c409
-rw-r--r--gnu/dist/cvs/src/patch.c792
-rw-r--r--gnu/dist/cvs/src/rcs.c8926
-rw-r--r--gnu/dist/cvs/src/rcscmds.c667
-rw-r--r--gnu/dist/cvs/src/recurse.c1209
-rw-r--r--gnu/dist/cvs/src/release.c299
-rw-r--r--gnu/dist/cvs/src/remove.c277
-rw-r--r--gnu/dist/cvs/src/repos.c206
-rw-r--r--gnu/dist/cvs/src/root.c574
-rw-r--r--gnu/dist/cvs/src/rtag.c766
-rw-r--r--gnu/dist/cvs/src/run.c439
-rw-r--r--gnu/dist/cvs/src/scramble.c245
-rw-r--r--gnu/dist/cvs/src/server.c6376
-rw-r--r--gnu/dist/cvs/src/status.c356
-rw-r--r--gnu/dist/cvs/src/subr.c780
-rw-r--r--gnu/dist/cvs/src/tag.c929
-rw-r--r--gnu/dist/cvs/src/update.c2861
-rw-r--r--gnu/dist/cvs/src/vers_ts.c406
-rw-r--r--gnu/dist/cvs/src/version.c29
-rw-r--r--gnu/dist/cvs/src/watch.c529
-rw-r--r--gnu/dist/cvs/src/wrapper.c614
-rw-r--r--gnu/dist/cvs/src/zlib.c662
83 files changed, 75113 insertions, 0 deletions
diff --git a/gnu/dist/cvs/contrib/.cvsignore b/gnu/dist/cvs/contrib/.cvsignore
new file mode 100644
index 00000000000..deb5efc5de7
--- /dev/null
+++ b/gnu/dist/cvs/contrib/.cvsignore
@@ -0,0 +1,14 @@
+Makefile
+clmerge
+cln_hist
+commit_prep
+cvs2vendor
+cvs_acls
+cvscheck
+log
+log_accum
+mfpipe
+rcs-to-cvs
+rcs2log
+rcslock
+sccs2rcs
diff --git a/gnu/dist/cvs/contrib/ChangeLog b/gnu/dist/cvs/contrib/ChangeLog
new file mode 100644
index 00000000000..46cf007051b
--- /dev/null
+++ b/gnu/dist/cvs/contrib/ChangeLog
@@ -0,0 +1,406 @@
+1999-01-19 Graham Stoney <greyham@research.canon.com.au>
+
+ * log.pl: The author commited the canonical perl "localtime" Y2K
+ offence, of printing "19$year" instead of (1900 + $year). Of
+ course, the result is non-compliance in year 2000. Fix it.
+
+1998-10-14 Jim Kingdon
+
+ * ccvs-rsh.pl: Removed; it was not in DISTFILES so it didn't
+ actually get distributed. I'm going to move it to the web on the
+ theory that the web is a better place for such things.
+ * README: Don't mention it.
+
+ * Makefile.in (dist-dir, distclean): Remove references to elib.
+ * elib: Remove this subdirectory and all its contents. It went
+ with pcl-cvs, which is no longer distributed with CVS.
+
+1998-09-22 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * pvcs_to_rcs: Removed; it was not in DISTFILES so it didn't
+ actually get distributed. I'm going to move it to the web on the
+ theory that the web is a better place for such things.
+ * README: Don't mention it.
+
+1998-09-10 Jim Kingdon
+
+ Check in Paul Eggert <eggert@twinsun.com>'s submission of
+ 1998-08-15. I also ran "cvs admin -ko" on this file so that his
+ version number would be intact (not an ideal solution, because
+ people will import it into other repositories, but I don't feel
+ like hacking the master version).
+ * rcs2log.sh: Sync with master version at gnu.org.
+
+1998-08-15 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Don't mention listener, since it was removed a while
+ ago.
+ * listen2.c, listen2.mak: Removed; because there is no easy way to
+ pass a socket (as opposed to file descriptor) from one process to
+ another on Windows, this isn't a promising approach (at least not
+ in this form).
+ * Makefile.in (DISTFILES): Remove them.
+ * .cvsignore: Remove listen2.ncb listen2.mdp Debug.
+
+1998-05-11 W. Bradley Rubenstein
+
+ * log.pl: Check for errors from open and exec.
+
+Sat Feb 21 21:59:45 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * Makefile.in (clean): Change "/bin/rm" to "rm".
+
+Thu Aug 7 22:42:23 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * pvcs_to_rcs: Remove RCS keywords. Remove $Log and move the data
+ to this ChangeLog (below). Add paragraph that David Martin
+ emailed along with the script.
+
+ Revision 1.6 1997/03/07 16:21:28 divad
+ Need to explicitly state archive name in PVCS get command for
+ those cases where the case of the workfile and the case of the
+ archive file are different (OS/2)
+
+ Revision 1.5 1997/03/07 00:31:04 divad
+ Added capitalized extensions and framemaker files as binaries;
+ also overriding any path specification for workfiles at PVCS
+ checkout (most annoying).
+
+ Revision 1.4 1997/03/06 21:04:55 divad
+ Added \n to the end of each comment line to prevent multi-line
+ comments for a single revision from "merging"
+
+ Revision 1.3 1997/03/06 19:50:25 divad
+ Corrected bug in binary extensions; correcting processing
+ comment strings with double quotes
+
+ Revision 1.2 1997/03/06 17:29:10 divad
+ Provided list of extensions (rather than using Unix file
+ command) to determine which files are binary; also printing
+ version label as they are applied
+
+ Revision 1.1 1997/02/26 00:04:29 divad
+ Perl script to convert pvcs archives to rcs archives
+
+ * README: mention pvcs_to_rcs.
+ * pvcs_to_rcs: New file. This is the file as I got it from David
+ Martin. Will be checking in the tweaks shortly.
+
+17 May 1997 Jim Kingdon
+
+ * listen2.c: Failed attempt at making this do what it was
+ intended to do. Will need to rethink the approach.
+ * listen2.mak: The usual involuntary tweaks.
+ * .cvsignore: Add listen2.ncb listen2.mdp.
+
+Mon May 12 11:59:23 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * listener.c: Removed; see ../ChangeLog for rationale.
+
+10 May 1997 Jim Kingdon
+
+ * listen2.c, listen2.mak: New files.
+ * Makefile.in (DISTFILES): Add them.
+ * .cvsignore: Add Debug.
+
+Thu Feb 20 22:43:45 1997 David J MacKenzie <djm@va.pubnix.com>
+
+ * rcs-to-cvs.sh: Put temporary files in /var/tmp or /usr/tmp
+ whichever one exists. Just call "vi" not "/usr/ucb/vi".
+
+Mon Feb 17 08:51:37 1997 Greg A. Woods <woods@most.weird.com>
+
+ * .cvsignore: added 'cvs2vendor' target from Feb. 12 changes.
+
+ * log_accum.pl (build_header): added "Repository:" to the report
+ header to show the first argument supplied to the script by CVS.
+ [[this value seems spuriously to be wrong when client is used]]
+ ($hostdomain): correct order of initialization from the Feb. 12
+ changes.
+ ($modulename): add more commentary about using '-M' to to get a
+ meaningful string here.
+ Tweak a few other comments from the Feb. 12 changes.
+
+Wed Feb 12 10:27:48 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cln_hist.pl, commit_prep.pl, cvs2vendor.sh, cvs_acls.pl,
+ cvscheck.man, cvscheck.sh, cvshelp.man, descend.man, descend.sh,
+ log_accum.pl, mfpipe.pl, rcs-to-cvs.sh, rcs2log.sh, rcs2sccs.sh,
+ sccs2rcs.csh: Remove $Id; we decided to get rid of these some
+ time ago.
+
+Wed Feb 12 00:24:33 1997 Greg A. Woods <woods@most.weird.com>
+
+ * cvs2vendor.sh: new script.
+ * README: noted new cvs2vendor script.
+ * Makefile.in (DISTFILES): added cvs2vendor.sh.
+ (CONTRIB_PROGS): added cvs2vendor.
+
+ * log_accum.pl (show_wd): new variable, initialized to 0.
+ - set $show_wd if '-w' option found while parsing @ARGV.
+ - don't add 'In directory' line to report header unless $show_wd
+ is set.
+ (domainname): prepend a leading '.' if none there so that
+ concatenation with $hostname works (those with a FQDN hostname
+ *and* a domainname still lose).
+ (mail_notification): don't set a "From:" header -- the mailer will.
+
+Wed Jan 8 14:48:58 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in, README, log.pl: Remove CVSid; we decided to get rid
+ of these some time ago.
+
+Thu Jan 2 13:30:56 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in: Remove "675" paragraph; see ../ChangeLog for rationale.
+
+Thu Oct 17 18:28:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * patch-2.1-.new-fix: Removed; it was not in DISTFILES so it never
+ made it into distributions. It also isn't clear what it has to do
+ with CVS. It is available from
+ ftp://ftp.weird.com/pub/patch-2.1-.new-fix
+ * README: Remove entry for patch-2.1-.new-fix.
+
+Wed Oct 16 10:22:44 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * rcs2log.sh: Change date output format to something CVS 1.9
+ accepts. I think this breaks the Sep 29 change, but I don't have
+ a copy of CVS 1.5 handy, so I can't find a format that works with
+ both, and I think it's more important that it work with the
+ version it's distributed with.
+
+Sat Oct 12 21:18:19 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Don't mention pcl-cvs; it isn't here any more.
+
+Sun Sep 29 19:45:19 1996 Greg A. Woods <woods@most.weird.com>
+
+ * README: add entry for patch-2.1-.new-fix.
+
+ * README: re-write the top section a bit.
+
+ * patch-2.1-.new-fix: re-generated using fixed "cvs patch" command.
+
+ * patch-2.1-.new-fix: new file.
+
+Sun Sep 29 14:25:28 1996 Dave Love <d.love@dl.ac.uk>
+
+ * rcs2log.sh (month_data): Make default date format acceptable to
+ CVS post v1.8 as well as earlier CVSs and RCS.
+ Message-Id: <199609291546.QAA25531@mserv1.dl.ac.uk>
+ To: bug-gnu-emacs@prep.ai.mit.edu
+
+Thu Aug 29 11:58:03 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * rcs2log: Update FSF address.
+
+ * rcs2log: Be more aggressive about finding the author's full
+ name; try nismatch and ypmatch.
+
+ * rcs2log: If the hostname appears not to be fully qualified, see
+ if domainname provides any useful information.
+
+Fri Aug 16 16:02:36 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * Makefile.in (installdirs): support this target
+
+Mon May 6 13:04:57 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (install): Don't tell user to run cvsinit. It isn't
+ called cvsinit anymore, and it isn't necessary (repositories are,
+ and need to be, compatible between cvs versions).
+
+Sun Apr 14 11:30:36 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * Removed pcl-cvs/ subdir; see tools/ subdir in the top-level from
+ now on.
+ Added elib/ subdir.
+
+ * Makefile.in (dist-dir): Removed all references to pcl-cvs/
+ subdir.
+
+Wed Mar 6 10:20:28 1996 Greg A. Woods <woods@most.weird.com>
+
+ * log_accum.pl: ($MAILER): use sendmail directly to allow other
+ headers to be included
+ * log_accum.pl (mail_notification): add support to allow settting
+ of Reply-To and Date header fields in the sent mail; remove $mailto
+ argument and use the global variable (as with $replyto).
+ * log_accum.pl: add -R option for mail_notification()'s optional
+ Reply-To value [default to $login]
+
+Fri Mar 1 01:51:56 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * listener.c: added as mentioned in ../README.VMS
+
+Mon Feb 19 13:37:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Don't just tell people "we don't want your script"; tell
+ them what to do instead.
+
+Thu Feb 1 14:28:16 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * Makefile.in (DISTFILES): added `rcs2sccs.sh', as mentioned in
+ README.
+
+Thu Jan 18 09:39:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Talk about submitting changes to contrib directory.
+
+Tue Nov 14 15:28:25 1995 Greg A. Woods <woods@most.weird.com>
+
+ * README: fix some spelling and other typos
+
+ * Makefile.in: if I need reminding to run cvsinit....
+
+Tue Nov 14 13:47:40 1995 Greg A. Woods <woods@most.weird.com>
+
+ * log_accum.pl:
+ - Fix 'cvs status' to use global -Qq options
+ - fix up a couple of comments, incl., my proper address
+
+ * log.pl: add a CVSid and fix a couple of comments
+
+Sun Oct 1 02:02:57 1995 Peter Wemm <peter@haywire.dialix.com>
+
+ * Makefile.in: supply a suffix rule to deal with .sh "source"
+
+Sat Jul 29 17:29:13 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * log.pl: Use global options -Qq, not command options -Qq.
+
+ * Makefile.in (install): Look for $(PROGS) and
+ $(CONTRIB_PROGS) in build dir, not srcdir.
+
+Fri Jul 28 19:48:45 1995 Paul Eggert <eggert@twinsun.com>
+
+ * rcs2log.sh: Sync with latest Emacs snapshot.
+
+Thu Jul 27 20:29:30 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * rcs2log.sh: import of initial WNT port work
+
+Fri Jul 14 22:38:44 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * rcs-to-cvs.sh: Changes from David J. Mackenzie.
+ Set permissions on new repository files correctly.
+ Ignore *~ files.
+
+Thu Jul 13 23:04:12 CDT 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (.pl, .csh): *Never* redirect output directly to
+ the target (usu $@) of a rule. Instead, redirect to a temporary
+ file, and then move that temporary to the target. I chose to
+ name temporary files $@-t. Remember to be careful that the length
+ of the temporary file name not exceed the 14-character limit.
+
+Sun Jul 9 21:16:53 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ These are actually Greg Woods' changes:
+
+ * clmerge.pl, cvscheck.sh, descend.sh, dirfns.shar, rcs-to-cvs.sh,
+ rcs2log.sh, sccs2rcs.csh: renamed from the corresponding files
+ sans extensions.
+
+ * rcs2sccs.sh: new file.
+
+Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * rcs2log.sh: oops, one more thing that should not have been
+ there.
+ - fix interpreter file syntax.
+ - remove "fix" for separating filenames and comments
+
+ * Makefile.in: hmm... thought rcs2log was in RCS-5.7 for some
+ reason -- it's not, so we'll install it from here....
+ - fix typo -- that's what you get for re-doing changes by hand!
+ - updates to support proper transformation and installation of
+ renamed files (from previous local changes)
+
+ * .cvsignore: one more target noted...
+
+ * sccs2rcs.csh: set up the interpreter file for updating by
+ Makefile (from previous local changes)
+
+ * log_accum.pl, log.pl, commit_prep.pl:
+ - set up the interpreter file for updating by Makefile
+ - various modifications, updates, and enhancements
+ (from previous local changes)
+
+ * rcslock.pl, mfpipe.pl, cvs_acls.pl, cln_hist.pl, clmerge.pl:
+ - set up the interpreter file for updating by Makefile
+ (from previous local changes)
+ - include changes from 1.5 here too, if any
+
+ * README:
+ - remove extensions from filenames to match installed names
+ (from previous local changes)
+
+ * .cvsignore: - added $(CONTRIB_PROGS) (from previous local changes)
+
+
+Thu Jun 29 10:43:07 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (distclean): Also remove pcl-cvs/Makefile.
+
+Thu Jun 8 15:32:29 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * intro.doc: Added.
+ * Makefile.in (DISTFILES): Add intro.doc.
+
+Sat May 27 08:46:00 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (Makefile): Regenerate only Makefile in current
+ directory when Makefile.in is out of date. Depend on ../config.status.
+
+Mon May 8 13:06:29 1995 Bryan O'Sullivan <bos@serpentine.com>
+
+ * README: added an entry for ccvs-rsh.pl.
+
+Sun Apr 30 23:50:32 1995 Bryan O'Sullivan <bos@serpentine.com>
+
+ * ccvs-rsh.pl: fixed a typo and added more flexible use of
+ CVS_PROXY_USER.
+
+Sun Apr 30 14:56:21 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * clmerge: Changes from Tom Tromey --- fix bug in date comparison
+ function.
+
+Sat Apr 29 20:53:08 1995 Bryan O'Sullivan <bos@serpentine.com>
+
+ * ccvs-rsh.pl: created. See the file itself for documentation.
+
+ * Makefile.in (DISTFILES): added ccvs-rsh.pl to the list of
+ files to install.
+
+Fri Apr 28 22:32:45 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (DISTFILES): Brought up-to-date with current
+ directory contents.
+ (dist-dir): Renamed from dist-dir; use DISTDIR variable, passed
+ from parent.
+
+Mon Feb 13 13:32:07 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * rcs2log: rcs2log was originally in this tree; how did it get
+ deleted? Anyway, this is the version distributed with Emacs
+ 19.28, hacked to support CVS and Remote CVS.
+
+Mon Jul 26 13:18:23 1993 David J. Mackenzie (djm@thepub.cygnus.com)
+
+ * rcs-to-cvs: Rewrite in sh.
+
+Wed Jul 14 21:16:40 1993 David J. Mackenzie (djm@thepub.cygnus.com)
+
+ * rcs-to-cvs: Don't source .cshrc or hardcode paths.
+ Make respository dir if needed. Don't suppress errors
+ (such as prompts) from co.
+
+Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * Makefile.in, configure.in: removed traces of namesubdir,
+ -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced
+ copyrights to '92, changed some from Cygnus to FSF.
+
diff --git a/gnu/dist/cvs/contrib/Makefile.in b/gnu/dist/cvs/contrib/Makefile.in
new file mode 100644
index 00000000000..1c56339042f
--- /dev/null
+++ b/gnu/dist/cvs/contrib/Makefile.in
@@ -0,0 +1,127 @@
+# Makefile for GNU CVS contributed sources.
+# Do not use this makefile directly, but only from `../Makefile'.
+# Copyright (C) 1986, 1988-1990 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+# Where to install the executables.
+bindir = $(exec_prefix)/bin
+
+# Where to put the system-wide .cvsrc file
+libdir = $(prefix)/lib
+
+# Where to put the manual pages.
+mandir = $(prefix)/man
+
+# where to find command interpreters
+perl_path = @perl_path@
+csh_path = @csh_path@
+
+# Use cp if you don't have install.
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+
+DISTFILES = \
+ ChangeLog README .cvsignore intro.doc \
+ Makefile.in clmerge.pl cln_hist.pl commit_prep.pl cvs2vendor.sh \
+ cvs_acls.pl cvscheck.sh cvscheck.man cvshelp.man descend.sh \
+ descend.man dirfns.shar log.pl log_accum.pl mfpipe.pl rcs-to-cvs.sh \
+ rcs2log.sh rcslock.pl sccs2rcs.csh rcs2sccs.sh
+
+# files installed in $(libdir)/cvs/contrib
+#
+CONTRIB_FILES = README intro.doc cvscheck.man
+
+# things we actually build and install....
+#
+PROGS = rcs2log
+CONTRIB_PROGS = clmerge cln_hist commit_prep cvs2vendor cvs_acls cvscheck \
+ log log_accum mfpipe rcs-to-cvs rcs2log rcslock sccs2rcs
+
+.SUFFIXES: .pl .sh .csh
+
+.pl:
+ rm -f $@
+ sed -e 's,xPERL_PATHx,$(perl_path),' $< > $@-t
+ mv $@-t $@
+ chmod +x $@
+
+.csh:
+ rm -f $@
+ sed -e 's,xCSH_PATHx,$(csh_path),' $< > $@-t
+ mv $@-t $@
+ chmod +x $@
+
+.sh:
+ rm -f $@
+ cp $< $@
+ chmod +x $@
+
+all: Makefile $(PROGS) $(CONTRIB_PROGS)
+.PHONY: all
+
+install: all installdirs
+ for f in $(CONTRIB_FILES) ; do\
+ $(INSTALL_DATA) $(srcdir)/$$f $(libdir)/cvs/contrib/$$f; \
+ done
+ for f in $(CONTRIB_PROGS) ; do\
+ $(INSTALL_PROGRAM) $$f $(libdir)/cvs/contrib/$$f; \
+ done
+ for f in $(PROGS) ; do\
+ $(INSTALL_PROGRAM) $$f $(bindir)/$$f; \
+ done
+.PHONY: install
+
+installdirs:
+ $(top_srcdir)/mkinstalldirs $(libdir)/cvs/contrib
+.PHONY: installdirs
+
+tags:
+.PHONY: tags
+
+TAGS:
+.PHONY: TAGS
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+clean:
+ rm -f *.o core
+.PHONY: clean
+
+distclean: clean
+ rm -f Makefile $(PROGS) $(CONTRIB_PROGS)
+.PHONY: distclean
+
+realclean: distclean
+.PHONY: realclean
+
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+.PHONY: dist-dir
+
+subdir = contrib
+Makefile: ../config.status Makefile.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
diff --git a/gnu/dist/cvs/contrib/README b/gnu/dist/cvs/contrib/README
new file mode 100644
index 00000000000..8a93f8f689b
--- /dev/null
+++ b/gnu/dist/cvs/contrib/README
@@ -0,0 +1,104 @@
+This "contrib" directory is a place holder for code/scripts sent to me
+by contributors around the world. This README file will be kept
+up-to-date from release to release. BUT, we must point out that these
+contributions are really, REALLY UNSUPPORTED. In fact, we probably
+don't even know what some of them really do. We certainly do not
+guarantee to have tried them, or ported them to work with this CVS
+distribution. If you have questions, your best bet is to contact the
+original author, but you should not necessarily expect a reply, since
+the author may not be available at the address given.
+
+USE AT YOUR OWN RISK -- and all that stuff.
+
+"Unsupported" also means that no one has volunteered to accept and check
+in changes to this directory. So submissions for new scripts to add
+here are unlikely to be accepted. Suggested changes to the existing
+scripts here conceivably might, but that isn't clear either, unless of
+course they come from the original author of the script.
+
+If you have some software that works with CVS that you wish to offer it
+is suggested that you make it available by FTP or HTTP and then announce
+it on the info-cvs mailing list.
+
+There is a web page of software related to CVS at the following URL which
+would presumably be willing to list your software.
+
+ http://www.loria.fr/~molli/cvs-index.html
+
+An attempt at a table of Contents for this directory:
+
+ README This file.
+ log A perl script suitable for including in your
+ $CVSROOT/CVSROOT/loginfo file for logging commit
+ changes. Includes the RCS revision of the change
+ as part of the log.
+ Contributed by Kevin Samborn <samborn@sunrise.com>.
+ commit_prep A perl script, to be combined with log_accum.pl, to
+ log_accum provide for a way to combine the individual log
+ messages of a multi-directory "commit" into a
+ single log message, and mail the result somewhere.
+ Can also do other checks for $Id and that you are
+ committing the correct revision of the file.
+ Read the comments carefully.
+ Contributed by David Hampton <hampton@cisco.com>.
+ mfpipe Another perl script for logging. Allows you to
+ pipe the log message to a file and/or send mail
+ to some alias.
+ Contributed by John Clyne <clyne@niwot.scd.ucar.edu>.
+ rcs-to-cvs Script to import sources that may have been under
+ RCS control already.
+ Contributed by Per Cederqvist <ceder@lysator.liu.se>.
+ cvscheck Identifies files added, changed, or removed in a
+ cvscheck.man checked out CVS tree; also notices unknown files.
+ Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+ cvshelp.man An introductory manual page written by Lowell Skoog
+ <fluke!lowell@uunet.uu.net>. It is most likely
+ out-of-date relative to CVS 1.3, but still may be
+ useful.
+ dirfns A shar file which contains some code that might
+ help your system support opendir/readdir/closedir,
+ if it does not already.
+ Copied from the C-News distribution.
+ rcslock A perl script that can be added to your commitinfo
+ file that tries to determine if your RCS file is
+ currently locked by someone else, as might be the
+ case for a binary file.
+ Contributed by John Rouillard <rouilj@cs.umb.edu>.
+ cvs_acls A perl script that implements Access Control Lists
+ by using the "commitinfo" hook provided with the
+ "cvs commit" command.
+ Contributed by David G. Grubbs <dgg@ksr.com>.
+ descend A shell script that can be used to recursively
+ descend.man descend through a directory. In CVS 1.2, this was
+ very useful, since many of the commands were not
+ recursive. In CVS 1.3 (and later), however, most of
+ the commands are recursive. However, this may still
+ come in handy.
+ Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+ cln_hist A perl script to compress your
+ $CVSROOT/CVSROOT/history file, as it can grow quite
+ large after extended use.
+ Contributed by David G. Grubbs <dgg@ksr.com>
+ sccs2rcs A C-shell script that can convert (some) SCCS files
+ into RCS files, retaining the info contained in the
+ SCCS file (like dates, author, and log message).
+ Contributed by Ken Cox <kenstir@viewlogic.com>.
+ intro.doc A user's view of what you need to know to get
+ started with CVS.
+ Contributed by <Steven.Pemberton@cwi.nl>.
+ rcs2sccs A shell script to convert simple RCS files into
+ SCCS files, originally gleaned off the network
+ somewhere (originally by "kenc") and modified by
+ Jerry Jelinek <jerry@rmtc.Central.Sun.COM> and
+ Brian Berliner <berliner@sun.com> to increase
+ robustness and add support for one-level of branches.
+ rcs2log A shell script to create a ChangeLog-format file
+ given only a set of RCS files.
+ Contributed by Paul Eggert <eggert@twinsun.com>.
+ clmerge A perl script to handle merge conflicts in GNU
+ style ChangeLog files .
+ Contributed by Tom Tromey <tromey@busco.lanl.gov>.
+ cvs2vendor A shell script to move changes from a repository
+ that was started without a vendor branch to one
+ that has a vendor branch.
+ Contributed by Greg A. Woods <woods@planix.com>
diff --git a/gnu/dist/cvs/contrib/clmerge.pl b/gnu/dist/cvs/contrib/clmerge.pl
new file mode 100644
index 00000000000..ac813713a82
--- /dev/null
+++ b/gnu/dist/cvs/contrib/clmerge.pl
@@ -0,0 +1,152 @@
+#! xPERL_PATHx
+
+# Merge conflicted ChangeLogs
+# tromey Mon Aug 15 1994
+
+# Usage is:
+#
+# cl-merge [-i] file ...
+#
+# With -i, it works in place (backups put in a ~ file). Otherwise the
+# merged ChangeLog is printed to stdout.
+
+# Please report any bugs to me. I wrote this yesterday, so there are no
+# guarantees about its performance. I recommend checking its output
+# carefully. If you do send a bug report, please include the failing
+# ChangeLog, so I can include it in my test suite.
+#
+# Tom
+# ---
+# tromey@busco.lanl.gov Member, League for Programming Freedom
+# Sadism and farce are always inexplicably linked.
+# -- Alexander Theroux
+
+
+# Month->number mapping. Used for sorting.
+%months = ('Jan', 0,
+ 'Feb', 1,
+ 'Mar', 2,
+ 'Apr', 3,
+ 'May', 4,
+ 'Jun', 5,
+ 'Jul', 6,
+ 'Aug', 7,
+ 'Sep', 8,
+ 'Oct', 9,
+ 'Nov', 10,
+ 'Dec', 11);
+
+# If '-i' is given, do it in-place.
+if ($ARGV[0] eq '-i') {
+ shift (@ARGV);
+ $^I = '~';
+}
+
+$lastkey = '';
+$lastval = '';
+$conf = 0;
+%conflist = ();
+
+$tjd = 0;
+
+# Simple state machine. The states:
+#
+# 0 Not in conflict. Just copy input to output.
+# 1 Beginning an entry. Next non-blank line is key.
+# 2 In entry. Entry beginner transitions to state 1.
+while (<>) {
+ if (/^<<<</ || /^====/) {
+ # Start of a conflict.
+
+ # Copy last key into array.
+ if ($lastkey ne '') {
+ $conflist{$lastkey} = $lastval;
+
+ $lastkey = '';
+ $lastval = '';
+ }
+
+ $conf = 1;
+ } elsif (/^>>>>/) {
+ # End of conflict. Output.
+
+ # Copy last key into array.
+ if ($lastkey ne '') {
+ $conflist{$lastkey} = $lastval;
+
+ $lastkey = '';
+ $lastval = '';
+ }
+
+ foreach (reverse sort clcmp keys %conflist) {
+ print STDERR "doing $_" if $tjd;
+ print $_;
+ print $conflist{$_};
+ }
+
+ $lastkey = '';
+ $lastval = '';
+ $conf = 0;
+ %conflist = ();
+ } elsif ($conf == 1) {
+ # Beginning an entry. Skip empty lines. Error if not a real
+ # beginner.
+ if (/^$/) {
+ # Empty line; just skip at this point.
+ } elsif (/^[MTWFS]/) {
+ # Looks like the name of a day; assume opener and move to
+ # "in entry" state.
+ $lastkey = $_;
+ $conf = 2;
+ print STDERR "found $_" if $tjd;
+ } else {
+ die ("conflict crosses entry boundaries: $_");
+ }
+ } elsif ($conf == 2) {
+ # In entry. Copy into variable until we see beginner line.
+ if (/^[MTWFS]/) {
+ # Entry beginner line.
+
+ # Copy last key into array.
+ if ($lastkey ne '') {
+ $conflist{$lastkey} = $lastval;
+
+ $lastkey = '';
+ $lastval = '';
+ }
+
+ $lastkey = $_;
+ print STDERR "found $_" if $tjd;
+ $lastval = '';
+ } else {
+ $lastval .= $_;
+ }
+ } else {
+ # Just copy.
+ print;
+ }
+}
+
+# Compare ChangeLog time strings like <=>.
+#
+# 0 1 2 3
+# Thu Aug 11 13:22:42 1994 Tom Tromey (tromey@creche.colorado.edu)
+# 0123456789012345678901234567890
+#
+sub clcmp {
+ # First check year.
+ $r = substr ($a, 20, 4) <=> substr ($b, 20, 4);
+
+ # Now check month.
+ $r = $months{substr ($a, 4, 3)} <=> $months{substr ($b, 4, 3)} if !$r;
+
+ # Now check day.
+ $r = substr ($a, 8, 2) <=> substr ($b, 8, 2) if !$r;
+
+ # Now check time (3 parts).
+ $r = substr ($a, 11, 2) <=> substr ($b, 11, 2) if !$r;
+ $r = substr ($a, 14, 2) <=> substr ($b, 14, 2) if !$r;
+ $r = substr ($a, 17, 2) <=> substr ($b, 17, 2) if !$r;
+
+ $r;
+}
diff --git a/gnu/dist/cvs/contrib/cln_hist.pl b/gnu/dist/cvs/contrib/cln_hist.pl
new file mode 100644
index 00000000000..19a0b815b78
--- /dev/null
+++ b/gnu/dist/cvs/contrib/cln_hist.pl
@@ -0,0 +1,91 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+# Contributed by David G. Grubbs <dgg@ksr.com>
+#
+# Clean up the history file. 10 Record types: MAR OFT WUCG
+#
+# WUCG records are thrown out.
+# MAR records are retained.
+# T records: retain only last tag with same combined tag/module.
+#
+# Two passes: Walk through the first time and remember the
+# 1. Last Tag record with same "tag" and "module" names.
+# 2. Last O record with unique user/module/directory, unless followed
+# by a matching F record.
+#
+
+$r = $ENV{"CVSROOT"};
+$c = "$r/CVSROOT";
+$h = "$c/history";
+
+eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
+ while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
+exit 255 if $die; # process any variable=value switches
+
+%tags = ();
+%outs = ();
+
+#
+# Move history file to safe place and re-initialize a new one.
+#
+rename($h, "$h.bak");
+open(XX, ">$h");
+close(XX);
+
+#
+# Pass1 -- remember last tag and checkout.
+#
+open(HIST, "$h.bak");
+while (<HIST>) {
+ next if /^[MARWUCG]/;
+
+ # Save whole line keyed by tag|module
+ if (/^T/) {
+ @tmp = split(/\|/, $_);
+ $tags{$tmp[4] . '|' . $tmp[5]} = $_;
+ }
+ # Save whole line
+ if (/^[OF]/) {
+ @tmp = split(/\|/, $_);
+ $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} = $_;
+ }
+}
+
+#
+# Pass2 -- print out what we want to save.
+#
+open(SAVE, ">$h.work");
+open(HIST, "$h.bak");
+while (<HIST>) {
+ next if /^[FWUCG]/;
+
+ # If whole line matches saved (i.e. "last") one, print it.
+ if (/^T/) {
+ @tmp = split(/\|/, $_);
+ next if $tags{$tmp[4] . '|' . $tmp[5]} ne $_;
+ }
+ # Save whole line
+ if (/^O/) {
+ @tmp = split(/\|/, $_);
+ next if $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} ne $_;
+ }
+
+ print SAVE $_;
+}
+
+#
+# Put back the saved stuff
+#
+system "cat $h >> $h.work";
+
+if (-s $h) {
+ rename ($h, "$h.interim");
+ print "history.interim has non-zero size.\n";
+} else {
+ unlink($h);
+}
+
+rename ("$h.work", $h);
+
+exit(0);
diff --git a/gnu/dist/cvs/contrib/commit_prep.pl b/gnu/dist/cvs/contrib/commit_prep.pl
new file mode 100644
index 00000000000..bf0ce92f22f
--- /dev/null
+++ b/gnu/dist/cvs/contrib/commit_prep.pl
@@ -0,0 +1,215 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+#
+# Perl filter to handle pre-commit checking of files. This program
+# records the last directory where commits will be taking place for
+# use by the log_accum.pl script. For new files, it forces the
+# existence of a RCS "Id" keyword in the first ten lines of the file.
+# For existing files, it checks version number in the "Id" line to
+# prevent losing changes because an old version of a file was copied
+# into the direcory.
+#
+# Possible future enhancements:
+#
+# Check for cruft left by unresolved conflicts. Search for
+# "^<<<<<<<$", "^-------$", and "^>>>>>>>$".
+#
+# Look for a copyright and automagically update it to the
+# current year. [[ bad idea! -- woods ]]
+#
+#
+# Contributed by David Hampton <hampton@cisco.com>
+#
+# Hacked on lots by Greg A. Woods <woods@web.net>
+
+#
+# Configurable options
+#
+
+# Constants (remember to protect strings from RCS keyword substitution)
+#
+$LAST_FILE = "/tmp/#cvs.lastdir"; # must match name in log_accum.pl
+$ENTRIES = "CVS/Entries";
+
+# Patterns to find $Log keywords in files
+#
+$LogString1 = "\\\$\\Log: .* \\\$";
+$LogString2 = "\\\$\\Log\\\$";
+$NoLog = "%s - contains an RCS \$Log keyword. It must not!\n";
+
+# pattern to match an RCS Id keyword line with an existing ID
+#
+$IDstring = "\"@\\(#\\)[^:]*:.*\\\$\Id: .*\\\$\"";
+$NoId = "
+%s - Does not contain a properly formatted line with the keyword \"Id:\".
+ I.e. no lines match \"" . $IDstring . "\".
+ Please see the template files for an example.\n";
+
+# pattern to match an RCS Id keyword line for a new file (i.e. un-expanded)
+#
+$NewId = "\"@(#)[^:]*:.*\\$\Id\\$\"";
+
+$NoName = "
+%s - The ID line should contain only \"@(#)module/path:\$Name\$:\$\Id\$\"
+ for a newly created file.\n";
+
+$BadName = "
+%s - The file name '%s' in the ID line does not match
+ the actual filename.\n";
+
+$BadVersion = "
+%s - How dare you!!! You replaced your copy of the file '%s',
+ which was based upon version %s, with an %s version based
+ upon %s. Please move your '%s' out of the way, perform an
+ update to get the current version, and them merge your changes
+ into that file, then try the commit again.\n";
+
+#
+# Subroutines
+#
+
+sub write_line {
+ local($filename, $line) = @_;
+ open(FILE, ">$filename") || die("Cannot open $filename, stopped");
+ print(FILE $line, "\n");
+ close(FILE);
+}
+
+sub check_version {
+ local($i, $id, $rname, $version);
+ local($filename, $cvsversion) = @_;
+
+ open(FILE, "<$filename") || return(0);
+
+ @all_lines = ();
+ $idpos = -1;
+ $newidpos = -1;
+ for ($i = 0; <FILE>; $i++) {
+ chop;
+ push(@all_lines, $_);
+ if ($_ =~ /$IDstring/) {
+ $idpos = $i;
+ }
+ if ($_ =~ /$NewId/) {
+ $newidpos = $i;
+ }
+ }
+
+ if (grep(/$LogString1/, @all_lines) || grep(/$LogString2/, @all_lines)) {
+ print STDERR sprintf($NoLog, $filename);
+ return(1);
+ }
+
+ if ($debug != 0) {
+ print STDERR sprintf("file = %s, version = %d.\n", $filename, $cvsversion{$filename});
+ }
+
+ if ($cvsversion{$filename} == 0) {
+ if ($newidpos != -1 && $all_lines[$newidpos] !~ /$NewId/) {
+ print STDERR sprintf($NoName, $filename);
+ return(1);
+ }
+ return(0);
+ }
+
+ if ($idpos == -1) {
+ print STDERR sprintf($NoId, $filename);
+ return(1);
+ }
+
+ $line = $all_lines[$idpos];
+ $pos = index($line, "Id: ");
+ if ($debug != 0) {
+ print STDERR sprintf("%d in '%s'.\n", $pos, $line);
+ }
+ ($id, $rname, $version) = split(' ', substr($line, $pos));
+ if ($rname ne "$filename,v") {
+ print STDERR sprintf($BadName, $filename, substr($rname, 0, length($rname)-2));
+ return(1);
+ }
+ if ($cvsversion{$filename} < $version) {
+ print STDERR sprintf($BadVersion, $filename, $filename, $cvsversion{$filename},
+ "newer", $version, $filename);
+ return(1);
+ }
+ if ($cvsversion{$filename} > $version) {
+ print STDERR sprintf($BadVersion, $filename, $filename, $cvsversion{$filename},
+ "older", $version, $filename);
+ return(1);
+ }
+ return(0);
+}
+
+#
+# Main Body
+#
+
+$id = getpgrp(); # You *must* use a shell that does setpgrp()!
+
+# Check each file (except dot files) for an RCS "Id" keyword.
+#
+$check_id = 0;
+
+# Record the directory for later use by the log_accumulate stript.
+#
+$record_directory = 0;
+
+# parse command line arguments
+#
+while (@ARGV) {
+ $arg = shift @ARGV;
+
+ if ($arg eq '-d') {
+ $debug = 1;
+ print STDERR "Debug turned on...\n";
+ } elsif ($arg eq '-c') {
+ $check_id = 1;
+ } elsif ($arg eq '-r') {
+ $record_directory = 1;
+ } else {
+ push(@files, $arg);
+ }
+}
+
+$directory = shift @files;
+
+if ($debug != 0) {
+ print STDERR "dir - ", $directory, "\n";
+ print STDERR "files - ", join(":", @files), "\n";
+ print STDERR "id - ", $id, "\n";
+}
+
+# Suck in the CVS/Entries file
+#
+open(ENTRIES, $ENTRIES) || die("Cannot open $ENTRIES.\n");
+while (<ENTRIES>) {
+ local($filename, $version) = split('/', substr($_, 1));
+ $cvsversion{$filename} = $version;
+}
+
+# Now check each file name passed in, except for dot files. Dot files
+# are considered to be administrative files by this script.
+#
+if ($check_id != 0) {
+ $failed = 0;
+ foreach $arg (@files) {
+ if (index($arg, ".") == 0) {
+ next;
+ }
+ $failed += &check_version($arg);
+ }
+ if ($failed) {
+ print STDERR "\n";
+ exit(1);
+ }
+}
+
+# Record this directory as the last one checked. This will be used
+# by the log_accumulate script to determine when it is processing
+# the final directory of a multi-directory commit.
+#
+if ($record_directory != 0) {
+ &write_line("$LAST_FILE.$id", $directory);
+}
+exit(0);
diff --git a/gnu/dist/cvs/contrib/cvs2vendor.sh b/gnu/dist/cvs/contrib/cvs2vendor.sh
new file mode 100644
index 00000000000..234f4d95e5d
--- /dev/null
+++ b/gnu/dist/cvs/contrib/cvs2vendor.sh
@@ -0,0 +1,142 @@
+#! /bin/sh
+#
+# cvs2vendor - move revsisions from files in A to files in B
+#
+# The primary reason for this script is to move deltas from a
+# non-vendor branched repository onto a fresh vendor branched one,
+# skipping the initial checkin in assumption that it is the same in
+# both repositories. This way you can take a project that was moved
+# into CVS without the benefit of the vendor branch and for all
+# intents and purposes add the vendor branch underneath the existing
+# deltas.
+#
+# This script is also a decent example of repository maintenance using
+# raw RCS commands (if I do say so myself! ;-).
+#
+# Tags are preserved.
+#
+# The timestamp of the initial vendor branch revision will be adjusted
+# to be the same as the 1.1 revision of each source file.
+#
+# Extra branches in the source directory will cause breakage.
+#
+# Intermediate files are created in the current working directory
+# where this script is started.
+#
+# Written by Greg A. Woods <woods@planix.com>, based on rcs2sccs
+# (retains some of the rlog parsing from it).
+#
+# The copyright is in the Public Domain.
+#
+
+if [ $# -ne 2 ]; then
+ echo USAGE: $0 srcdir dstdir
+ exit 2
+fi
+tsrcdir=$1
+tdstdir=$2
+
+revfile=/tmp/cvs2vendor_$$_rev
+rm -f $revfile
+
+commentfile=/tmp/cvs2vendor_$$_comment
+rm -f $commentfile
+
+srcdirs=`cd $tsrcdir && find . -type d -print | sed 's~^\.[/]*~~'`
+
+# the "" is a trick to get $tsrcdir itself without resorting to '.'
+for ldir in "" $srcdirs; do
+
+ srcdir=$tsrcdir/$ldir
+ dstdir=$tdstdir/$ldir
+
+ # Loop over every RCS file in srcdir
+ #
+ for vfile in $srcdir/*,v; do
+ # get rid of the ",v" at the end of the name
+ file=`echo $vfile | sed -e 's/,v$//'`
+ bfile=`basename $file`
+
+ if [ ! -d $dstdir ]; then
+ echo "making locally added directory $dstdir"
+ mkdir -p $dstdir
+ fi
+ if [ ! -f $dstdir/$bfile,v ]; then
+ echo "copying locally added file $dstdir/$bfile ..."
+ cp $vfile $dstdir
+ continue;
+ fi
+
+ # work on each rev of that file in ascending order
+ rlog $file | grep "^revision [0-9][0-9]*\." | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile
+
+ for rev in `cat $revfile`; do
+
+ case "$rev" in
+ 1.1)
+ newdate=`rlog -r$rev $file | grep "^date: " | awk '{printf("%s.%s\n",$2,$3); exit}' | sed -e 's~/~.~g' -e 's/:/./g' -e 's/;//' -e 's/^19//'`
+ olddate=`rlog -r1.1.1.1 $dstdir/$bfile | grep "^date: " | awk '{printf("%s.%s\n",$2,$3); exit}' | sed -e 's~/~.~g' -e 's/:/./g' -e 's/;//' -e 's/^19//'`
+ sed "s/$olddate/$newdate/" < $dstdir/$bfile,v > $dstdir/$bfile.x
+ mv -f $dstdir/$bfile.x $dstdir/$bfile,v
+ chmod -w $dstdir/$bfile,v
+ symname=`rlog -h $file | sed -e '1,/^symbolic names:/d' -e 's/[ ]*//g' | awk -F: '$2 == "'"$rev"'" {printf("-n%s:1.1.1.1\n",$1)}'`
+ if [ -n "$symname" ]; then
+ echo "tagging $file with $symname ..."
+ rcs $symname $dstdir/$bfile,v
+ if [ $? != 0 ]; then
+ echo ERROR - rcs $symname $dstdir/$bfile,v
+ exit 1
+ fi
+ fi
+ continue # skip first rev....
+ ;;
+ esac
+
+ # get a lock on the destination local branch tip revision
+ co -r1 -l $dstdir/$bfile
+ if [ $? != 0 ]; then
+ echo ERROR - co -r1 -l $dstdir/$bfile
+ exit 1
+ fi
+ rm -f $dstdir/$bfile
+
+ # get file into current dir and get stats
+ date=`rlog -r$rev $file | grep "^date: " | awk '{printf("%s %s\n",$2,$3); exit}' | sed -e 's/;//'`
+ author=`rlog -r$rev $file | grep "^date: " | awk '{print $5; exit}' | sed -e 's/;//'`
+
+ symname=`rlog -h $file | sed -e '1,/^symbolic names:/d' -e 's/[ ]*//g' | awk -F: '$2 == "'"$rev"'" {printf("-n%s\n",$1)}'`
+
+ rlog -r$rev $file | sed -e '/^branches: /d' -e '1,/^date: /d' -e '/^===========/d' | awk '{if ((total += length($0) + 1) < 510) print $0}' > $commentfile
+
+ echo "==> file $file, rev=$rev, date=$date, author=$author $symname"
+
+ co -p -r$rev $file > $bfile
+ if [ $? != 0 ]; then
+ echo ERROR - co -p -r$rev $file
+ exit 1
+ fi
+
+ # check file into vendor repository...
+ ci -f -m"`cat $commentfile`" -d"$date" $symname -w"$author" $bfile $dstdir/$bfile,v
+ if [ $? != 0 ]; then
+ echo ERROR - ci -f -m"`cat $commentfile`" -d"$date" $symname -w"$author" $bfile $dstdir/$bfile,v
+ exit 1
+ fi
+ rm -f $bfile
+
+ # set the default branch to the trunk...
+ # XXX really only need to do this once....
+ rcs -b1 $dstdir/$bfile
+ if [ $? != 0 ]; then
+ echo ERROR - rcs -b1 $dstdir/$bfile
+ exit 1
+ fi
+ done
+ done
+done
+
+echo cleaning up...
+rm -f $commentfile
+echo " Conversion Completed Successfully"
+
+exit 0
diff --git a/gnu/dist/cvs/contrib/cvs_acls.pl b/gnu/dist/cvs/contrib/cvs_acls.pl
new file mode 100644
index 00000000000..c1d64e94877
--- /dev/null
+++ b/gnu/dist/cvs/contrib/cvs_acls.pl
@@ -0,0 +1,141 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+# Access control lists for CVS. dgg@ksr.com (David G. Grubbs)
+#
+# CVS "commitinfo" for matching repository names, running the program it finds
+# on the same line. More information is available in the CVS man pages.
+#
+# ==== INSTALLATION:
+#
+# To use this program as I intended, do the following four things:
+#
+# 0. Install PERL. :-)
+#
+# 1. Put one line, as the *only* non-comment line, in your commitinfo file:
+#
+# DEFAULT /usr/local/bin/cvs_acls
+#
+# 2. Install this file as /usr/local/bin/cvs_acls and make it executable.
+#
+# 3. Create a file named $CVSROOT/CVSROOT/avail.
+#
+# ==== FORMAT OF THE avail FILE:
+#
+# The avail file determines whether you may commit files. It contains lines
+# read from top to bottom, keeping track of a single "bit". The "bit"
+# defaults to "on". It can be turned "off" by "unavail" lines and "on" by
+# "avail" lines. ==> Last one counts.
+#
+# Any line not beginning with "avail" or "unavail" is ignored.
+#
+# Lines beginning with "avail" or "unavail" are assumed to be '|'-separated
+# triples: (All spaces and tabs are ignored in a line.)
+#
+# {avail.*,unavail.*} [| user,user,... [| repos,repos,...]]
+#
+# 1. String starting with "avail" or "unavail".
+# 2. Optional, comma-separated list of usernames.
+# 3. Optional, comma-separated list of repository pathnames.
+# These are pathnames relative to $CVSROOT. They can be directories or
+# filenames. A directory name allows access to all files and
+# directories below it.
+#
+# Example: (Text from the ';;' rightward may not appear in the file.)
+#
+# unavail ;; Make whole repository unavailable.
+# avail|dgg ;; Except for user "dgg".
+# avail|fred, john|bin/ls ;; Except when "fred" or "john" commit to
+# ;; the module whose repository is "bin/ls"
+#
+# PROGRAM LOGIC:
+#
+# CVS passes to @ARGV an absolute directory pathname (the repository
+# appended to your $CVSROOT variable), followed by a list of filenames
+# within that directory.
+#
+# We walk through the avail file looking for a line that matches both
+# the username and repository.
+#
+# A username match is simply the user's name appearing in the second
+# column of the avail line in a space-or-comma separate list.
+#
+# A repository match is either:
+# - One element of the third column matches $ARGV[0], or some
+# parent directory of $ARGV[0].
+# - Otherwise *all* file arguments ($ARGV[1..$#ARGV]) must be
+# in the file list in one avail line.
+# - In other words, using directory names in the third column of
+# the avail file allows committing of any file (or group of
+# files in a single commit) in the tree below that directory.
+# - If individual file names are used in the third column of
+# the avail file, then files must be committed individually or
+# all files specified in a single commit must all appear in
+# third column of a single avail line.
+#
+
+$debug = 0;
+$cvsroot = $ENV{'CVSROOT'};
+$availfile = $cvsroot . "/CVSROOT/avail";
+$myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"});
+
+eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
+ while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
+exit 255 if $die; # process any variable=value switches
+
+die "Must set CVSROOT\n" if !$cvsroot;
+($repos = shift) =~ s:^$cvsroot/::;
+grep($_ = $repos . '/' . $_, @ARGV);
+
+print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;
+
+$exit_val = 0; # Good Exit value
+
+$universal_off = 0;
+open (AVAIL, $availfile) || exit(0); # It is ok for avail file not to exist
+while (<AVAIL>) {
+ chop;
+ next if /^\s*\#/;
+ next if /^\s*$/;
+ ($flagstr, $u, $m) = split(/[\s,]*\|[\s,]*/, $_);
+
+ # Skip anything not starting with "avail" or "unavail" and complain.
+ (print "Bad avail line: $_\n"), next
+ if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/);
+
+ # Set which bit we are playing with. ('0' is OK == Available).
+ $flag = (($& eq "avail") ? 0 : 1);
+
+ # If we find a "universal off" flag (i.e. a simple "unavail") remember it
+ $universal_off = 1 if ($flag && !$u && !$m);
+
+ # $myname considered "in user list" if actually in list or is NULL
+ $in_user = (!$u || grep ($_ eq $myname, split(/[\s,]+/,$u)));
+ print "$$ \$myname($myname) in user list: $_\n" if $debug && $in_user;
+
+ # Module matches if it is a NULL module list in the avail line. If module
+ # list is not null, we check every argument combination.
+ if (!($in_repo = !$m)) {
+ @tmp = split(/[\s,]+/,$m);
+ for $j (@tmp) {
+ # If the repos from avail is a parent(or equal) dir of $repos, OK
+ $in_repo = 1, last if ($repos eq $j || $repos =~ /^$j\//);
+ }
+ if (!$in_repo) {
+ $in_repo = 1;
+ for $j (@ARGV) {
+ last if !($in_repo = grep ($_ eq $j, @tmp));
+ }
+ }
+ }
+ print "$$ \$repos($repos) in repository list: $_\n" if $debug && $in_repo;
+
+ $exit_val = $flag if ($in_user && $in_repo);
+ print "$$ ==== \$exit_val = $exit_val\n$$ ==== \$flag = $flag\n" if $debug;
+}
+close(AVAIL);
+print "$$ ==== \$exit_val = $exit_val\n" if $debug;
+print "**** Access denied: Insufficient Karma ($myname|$repos)\n" if $exit_val;
+print "**** Access allowed: Personal Karma exceeds Environmental Karma.\n"
+ if $universal_off && !$exit_val;
+exit($exit_val);
diff --git a/gnu/dist/cvs/contrib/cvscheck.man b/gnu/dist/cvs/contrib/cvscheck.man
new file mode 100644
index 00000000000..2b90b49a14c
--- /dev/null
+++ b/gnu/dist/cvs/contrib/cvscheck.man
@@ -0,0 +1,52 @@
+.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+.TH CVSCHECK LOCAL "4 March 1991" FLUKE
+.SH NAME
+cvscheck \- identify files added, changed, or removed in a CVS working
+directory
+.SH SYNOPSIS
+.B cvscheck
+.SH DESCRIPTION
+This command is a housekeeping aid. It should be run in a working
+directory that has been checked out using CVS. It identifies files
+that have been added, changed, or removed in the working directory, but
+not CVS
+.BR commit ted.
+It also determines whether the files have been CVS
+.BR add ed
+or CVS
+.BR remove d.
+For directories, this command determines only whether they have been
+.BR add ed.
+It operates in the current directory only.
+.LP
+This command provides information that is available using CVS
+.B status
+and CVS
+.BR diff .
+The advantage of
+.B cvscheck
+is that its output is very concise. It saves you the strain (and
+potential error) of interpreting the output of CVS
+.B status
+and
+.BR diff .
+.LP
+See
+.BR cvs (local)
+or
+.BR cvshelp (local)
+for instructions on how to add or remove a file or directory in a
+CVS-controlled package.
+.SH DIAGNOSTICS
+The exit status is 0 if no files have been added, changed, or removed
+from the current directory. Otherwise, the command returns a count of
+the adds, changes, and deletes.
+.SH SEE ALSO
+.BR cvs (local),
+.BR cvshelp (local)
+.SH AUTHOR
+Lowell Skoog
+.br
+Software Technology Group
+.br
+Technical Computing
diff --git a/gnu/dist/cvs/contrib/cvscheck.sh b/gnu/dist/cvs/contrib/cvscheck.sh
new file mode 100644
index 00000000000..f711b430e0f
--- /dev/null
+++ b/gnu/dist/cvs/contrib/cvscheck.sh
@@ -0,0 +1,83 @@
+#! /bin/sh
+#
+# cvscheck - identify files added, changed, or removed
+# in CVS working directory
+#
+# Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+#
+# This program should be run in a working directory that has been
+# checked out using CVS. It identifies files that have been added,
+# changed, or removed in the working directory, but not "cvs
+# committed". It also determines whether the files have been "cvs
+# added" or "cvs removed". For directories, it is only practical to
+# determine whether they have been added.
+
+name=cvscheck
+changes=0
+
+# If we can't run CVS commands in this directory
+cvs status . > /dev/null 2>&1
+if [ $? != 0 ] ; then
+
+ # Bail out
+ echo "$name: there is no version here; bailing out" 1>&2
+ exit 1
+fi
+
+# Identify files added to working directory
+for file in .* * ; do
+
+ # Skip '.' and '..'
+ if [ $file = '.' -o $file = '..' ] ; then
+ continue
+ fi
+
+ # If a regular file
+ if [ -f $file ] ; then
+ if cvs status $file | grep -s '^From:[ ]*New file' ; then
+ echo "file added: $file - not CVS committed"
+ changes=`expr $changes + 1`
+ elif cvs status $file | grep -s '^From:[ ]*no entry for' ; then
+ echo "file added: $file - not CVS added, not CVS committed"
+ changes=`expr $changes + 1`
+ fi
+
+ # Else if a directory
+ elif [ -d $file -a $file != CVS.adm ] ; then
+
+ # Move into it
+ cd $file
+
+ # If CVS commands don't work inside
+ cvs status . > /dev/null 2>&1
+ if [ $? != 0 ] ; then
+ echo "directory added: $file - not CVS added"
+ changes=`expr $changes + 1`
+ fi
+
+ # Move back up
+ cd ..
+ fi
+done
+
+# Identify changed files
+changedfiles=`cvs diff | egrep '^diff' | awk '{print $3}'`
+for file in $changedfiles ; do
+ echo "file changed: $file - not CVS committed"
+ changes=`expr $changes + 1`
+done
+
+# Identify files removed from working directory
+removedfiles=`cvs status | egrep '^File:[ ]*no file' | awk '{print $4}'`
+
+# Determine whether each file has been cvs removed
+for file in $removedfiles ; do
+ if cvs status $file | grep -s '^From:[ ]*-' ; then
+ echo "file removed: $file - not CVS committed"
+ else
+ echo "file removed: $file - not CVS removed, not CVS committed"
+ fi
+ changes=`expr $changes + 1`
+done
+
+exit $changes
diff --git a/gnu/dist/cvs/contrib/cvshelp.man b/gnu/dist/cvs/contrib/cvshelp.man
new file mode 100644
index 00000000000..b166af69528
--- /dev/null
+++ b/gnu/dist/cvs/contrib/cvshelp.man
@@ -0,0 +1,561 @@
+.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+.\" Full space in nroff; half space in troff
+.de SP
+.if n .sp
+.if t .sp .5
+..
+.\" Start a command example
+.de XS
+.SP
+.in +.5i
+.ft B
+.nf
+..
+.\" End a command example
+.de XE
+.fi
+.ft P
+.in -.5i
+.SP
+..
+.TH CVSHELP LOCAL "17 March 1991" FLUKE
+.SH NAME
+cvshelp \- advice on using the Concurrent Versions System
+.SH DESCRIPTION
+This man page is based on experience using CVS.
+It is bound to change as we gain more experience.
+If you come up with better advice than is found here,
+contact the Software Technology
+Group and we will add it to this page.
+.SS "Getting Started"
+Use the following steps to prepare to use CVS:
+.TP
+\(bu
+Take a look at the CVS manual page to see what it can do for you, and
+if it fits your environment (or can possibly be made to fit your
+environment).
+.XS
+man cvs
+.XE
+If things look good, continue on...
+.TP
+\(bu
+Setup the master source repository. Choose a directory with
+ample disk space available for source files. This is where the RCS
+`,v' files will be stored. Say you choose
+.B /src/master
+as the root
+of your source repository. Make the
+.SB CVSROOT.adm
+directory in the root of the source repository:
+.XS
+mkdir /src/master/CVSROOT.adm
+.XE
+.TP
+\(bu
+Populate this directory with the
+.I loginfo
+and
+.I modules
+files from the
+.B "/usr/doc/local/cvs"
+directory. Edit these files to reflect your local source repository
+environment \- they may be quite small initially, but will grow as
+sources are added to your source repository. Turn these files into
+RCS controlled files:
+.XS
+cd /src/master/CVSROOT.adm
+ci \-m'Initial loginfo file' loginfo
+ci \-m'Initial modules file' modules
+.XE
+.TP
+\(bu
+Run the command:
+.XS
+mkmodules /src/master/CVSROOT.adm
+.XE
+This will build the
+.BR ndbm (3)
+file for the modules database.
+.TP
+\(bu
+Remember to edit the
+.I modules
+file manually when sources are checked
+in with
+.B checkin
+or CVS
+.BR add .
+A copy of the
+.I modules
+file for editing can be retrieved with the command:
+.XS
+cvs checkout CVSROOT.adm
+.XE
+.TP
+\(bu
+Have all users of the CVS system set the
+.SM CVSROOT
+environment variable appropriately to reflect the placement of your
+source repository. If the above example is used, the following
+commands can be placed in a
+.I .login
+or
+.I .profile
+file:
+.XS
+setenv CVSROOT /src/master
+.XE
+for csh users, and
+.XS
+CVSROOT=/src/master; export CVSROOT
+.XE
+for sh users.
+.SS "Placing Locally Written Sources Under CVS Control"
+Say you want to place the `whizbang' sources under
+CVS control. Say further that the sources have never
+been under revision control before.
+.TP
+\(bu
+Move the source hierarchy (lock, stock, and barrel)
+into the master source repository:
+.XS
+mv ~/whizbang $CVSROOT
+.XE
+.TP
+\(bu
+Clean out unwanted object files:
+.XS
+cd $CVSROOT/whizbang
+make clean
+.XE
+.TP
+\(bu
+Turn every file in the hierarchy into an RCS controlled file:
+.XS
+descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-nV\fR\fIx\fR\fB_\fR\fIy\fR\fB *'
+.XE
+In this example, the initial release tag is \fBV\fIx\fB_\fIy\fR,
+representing version \fIx\fR.\fIy\fR.
+.LP
+You can use CVS on sources that are already under RCS control.
+The following example shows how.
+In this example, the source package is called `skunkworks'.
+.TP
+\(bu
+Move the source hierarchy into the master source
+repository:
+.XS
+mv ~/skunkworks $CVSROOT
+.XE
+.TP
+\(bu
+Clean out unwanted object files:
+.XS
+cd $CVSROOT/skunkworks
+make clean
+.XE
+.TP
+\(bu
+Clean out unwanted working files, leaving only the RCS `,v' files:
+.XS
+descend \-r rcsclean
+.XE
+Note: If any working files have been checked out and changed,
+.B rcsclean
+will fail. Check in the modified working files
+and run the command again.
+.TP
+\(bu
+Get rid of
+.SB RCS
+subdirectories. CVS does not use them.
+.XS
+descend \-r \-f 'mv RCS/*,v .'
+descend \-r \-f 'rmdir RCS'
+.XE
+.TP
+\(bu
+Delete any unwanted files that remain in the source hierarchy. Then
+make sure all files are under RCS control:
+.XS
+descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-n\fR\fItag\fR\fB *'
+.XE
+.I tag
+is the latest symbolic revision tag that you applied to your package
+(if any). Note: This command will probably generate lots of error
+messages (for directories and existing RCS files) that you can
+ignore.
+.SS "Placing a Third-Party Source Distribution Under CVS Control"
+The
+.B checkin
+command checks third-party sources into CVS. The
+difference between third-party sources and locally
+written sources is that third-party sources must be checked into a
+separate branch (called the
+.IR "vendor branch" )
+of the RCS tree. This makes it possible to merge local changes to
+the sources with later releases from the vendor.
+.TP
+\(bu
+Save the original distribution kit somewhere. For example, if the
+master source repository is
+.B /src/master
+the distribution kit could be saved in
+.BR /src/dist .
+Organize the distribution directory so that each release
+is clearly identifiable.
+.TP
+\(bu
+Unpack the package in a scratch directory, for example
+.BR ~/scratch .
+.TP
+\(bu
+Create a repository for the package.
+In this example, the package is called `Bugs-R-Us 4.3'.
+.XS
+mkdir $CVSROOT/bugs
+.XE
+.TP
+\(bu
+Check in the unpacked files:
+.XS
+cd ~/scratch
+checkin \-m 'Bugs-R-Us 4.3 distribution' bugs VENDOR V4_3
+.XE
+There is nothing magic about the tag `VENDOR', which is applied to
+the vendor branch. You can use whatever tag you want. `VENDOR' is a
+useful convention.
+.TP
+\(bu
+Never modify vendor files before checking them in.
+Check in the files
+.I exactly
+as you unpacked them.
+If you check in locally modified files, future vendor releases may
+wipe out your local changes.
+.SS "Working With CVS-Controlled Sources"
+To use or edit the sources, you must check out a private copy.
+For the following examples, the master files are assumed to reside in
+.BR "$CVSROOT/behemoth" .
+The working directory is
+.BR "~/work" .
+See
+.BR cvs (local)
+for more details on the commands mentioned below.
+.TP
+.I "To Check Out Working Files
+Use CVS
+.BR checkout :
+.XS
+cd ~/work
+cvs checkout behemoth
+.XE
+There is nothing magic about the working directory. CVS will check
+out sources anywhere you like. Once you have a working copy of the
+sources, you can compile or edit them as desired.
+.TP
+.I "To Display Changes You Have Made"
+Use CVS
+.BR diff
+to display detailed changes, equivalent to
+.BR rcsdiff (local).
+You can also use
+.BR cvscheck (local)
+to list files added, changed, and removed in
+the directory, but not yet
+.BR commit ted.
+You must be in a directory containing working files.
+.TP
+.I "To Display Revision Information"
+Use CVS
+.BR log ,
+which is equivalent to
+.BR rlog (local).
+You must be in a directory containing working files.
+.TP
+.I "To Update Working Files"
+Use CVS
+.BR update
+in a directory containing working files.
+This command brings your working files up
+to date with changes checked into the
+master repository since you last checked out or updated
+your files.
+.TP
+.I "To Check In Your Changes"
+Use CVS
+.BR commit
+in a directory containing working files.
+This command checks your changes into the master repository.
+You can specify files by name or use
+.XS
+cvs commit \-a
+.XE
+to
+.B commit
+all the files you have changed.
+.TP
+.I "To Add a File"
+Add the file to the working directory.
+Use CVS
+.B add
+to mark the file as added.
+Use CVS
+.B commit
+to add the file to the master repository.
+.TP
+.I "To Remove a File"
+Remove the file from the working directory.
+Use CVS
+.B remove
+to mark the file as removed.
+Use CVS
+.B commit
+to move the file from its current location in the master repository
+to the CVS
+.IR Attic
+directory.
+.TP
+.I "To Add a Directory"
+Add the directory to the working directory.
+Use CVS
+.B add
+to add the directory to the master repository.
+.TP
+.I "To Remove a Directory"
+.br
+You shouldn't remove directories under CVS. You should instead remove
+their contents and then prune them (using the
+.B \-f
+and
+.B \-p
+options) when you
+.B checkout
+or
+.B update
+your working files.
+.TP
+.I "To Tag a Release"
+Use CVS
+.B tag
+to apply a symbolic tag to the latest revision of each file in the
+master repository. For example:
+.XS
+cvs tag V2_1 behemoth
+.XE
+.TP
+.I "To Retrieve an Exact Copy of a Previous Release"
+During a CVS
+.B checkout
+or
+.BR update ,
+use the
+.B \-r
+option to retrieve revisions associated with a symbolic tag.
+Use the
+.B \-f
+option to ignore all RCS files that do not contain the
+tag.
+Use the
+.B \-p
+option to prune directories that wind up empty because none
+of their files matched the tag. Example:
+.XS
+cd ~/work
+cvs checkout \-r V2_1 \-f \-p behemoth
+.XE
+.SS "Logging Changes"
+It is a good idea to keep a change log together with the
+sources. As a minimum, the change log should name and describe each
+tagged release. The change log should also be under CVS control and
+should be tagged along with the sources.
+.LP
+.BR cvslog (local)
+can help. This command logs
+changes reported during CVS
+.B commit
+operations. It automatically
+updates a change log file in your working directory. When you are
+finished making changes, you (optionally) edit the change log file and
+then commit it to the master repository.
+.LP
+Note: You must edit the change log to describe a new release
+and
+.B commit
+it to the master repository
+.I before
+.BR tag ging
+the release using CVS. Otherwise, the release description will not be
+included in the tagged package.
+.LP
+See
+.BR cvslog (local)
+for more information.
+.SS "Merging a Subsequent Third-Party Distribution"
+The initial steps in this process are identical to placing a
+third-party distribution under CVS for the first time: save the
+distribution kit and unpack the package in a scratch directory. From
+that point the steps diverge.
+The following example considers release 5.0 of the
+Bugs-R-Us package.
+.TP
+\(bu
+Check in the sources after unpacking them:
+.XS
+cd ~/scratch
+checkin \-m 'Bugs-R-Us 5.0 distribution' bugs VENDOR V5_0 \\
+ | tee ~/WARNINGS
+.XE
+It is important to save the output of
+.B checkin
+in a file
+because it lists the sources that have been locally modified.
+It is best to save the file in a different directory (for example,
+your home directory). Otherwise,
+.B checkin
+will try to check it into the master repository.
+.TP
+\(bu
+In your usual working directory, check out a fresh copy of the
+distribution that you just checked in.
+.XS
+cd ~/work
+cvs checkout \-r VENDOR bugs
+.XE
+The
+.B checkout
+command shown above retrieves the latest revision on the vendor branch.
+.TP
+\(bu
+See the `WARNINGS' file for a list of all locally modified
+sources.
+For each locally modified source,
+look at the differences between
+the new distribution and the latest local revision:
+.XS
+cvs diff \-r \fR\fILocalRev file\fR\fB
+.XE
+In this command,
+.I LocalRev
+is the latest
+numeric or symbolic revision
+on the RCS trunk of
+.IR file .
+You can use CVS
+.B log
+to get the revision history.
+.TP
+\(bu
+If your local modifications to a file have been incorporated into
+the vendor's distribution, then you should reset the default RCS
+branch for that file to the vendor branch. CVS doesn't provide a
+mechanism to do this. You have to do it by hand in the master
+repository:
+.XS
+rcs \-bVENDOR \fR\fIfile\fR\fB,v
+.XE
+.TP
+\(bu
+If your local modifications need to be merged with the
+new distribution, use CVS
+.B join
+to do it:
+.XS
+cvs join \-r VENDOR \fR\fIfile\fR\fB
+.XE
+The resulting file will be placed in your working directory.
+Edit it to resolve any overlaps.
+.TP
+\(bu
+Test the merged package.
+.TP
+\(bu
+Commit all modified files to the repository:
+.XS
+cvs commit \-a
+.XE
+.TP
+\(bu
+Tag the repository with a new local tag.
+.SS "Applying Patches to Third-Party Sources"
+Patches are handled in a manner very similar to complete
+third-party distributions. This example considers patches applied to
+Bugs-R-Us release 5.0.
+.TP
+\(bu
+Save the patch files together with the distribution kit
+to which they apply.
+The patch file names should clearly indicate the patch
+level.
+.TP
+\(bu
+In a scratch directory, check out the last `clean' vendor copy \- the
+highest revision on the vendor branch with
+.IR "no local changes" :
+.XS
+cd ~/scratch
+cvs checkout \-r VENDOR bugs
+.XE
+.TP
+\(bu
+Use
+.BR patch (local)
+to apply the patches. You should now have an image of the
+vendor's software just as though you had received a complete,
+new release.
+.TP
+\(bu
+Proceed with the steps described for merging a subsequent third-party
+distribution.
+.TP
+\(bu
+Note: When you get to the step that requires you
+to check out the new distribution after you have
+checked it into the vendor branch, you should move to a different
+directory. Do not attempt to
+.B checkout
+files in the directory in
+which you applied the patches. If you do, CVS will try to merge the
+changes that you made during patching with the version being checked
+out and things will get very confusing. Instead,
+go to a different directory (like your working directory) and
+check out the files there.
+.SS "Advice to Third-Party Source Hackers"
+As you can see from the preceding sections, merging local changes
+into third-party distributions remains difficult, and probably
+always will. This fact suggests some guidelines:
+.TP
+\(bu
+Minimize local changes.
+.I Never
+make stylistic changes.
+Change makefiles only as much as needed for installation. Avoid
+overhauling anything. Pray that the vendor does the same.
+.TP
+\(bu
+Avoid renaming files or moving them around.
+.TP
+\(bu
+Put independent, locally written files like help documents, local
+tools, or man pages in a sub-directory called `local-additions'.
+Locally written files that are linked into an existing executable
+should be added right in with the vendor's sources (not in a
+`local-additions' directory).
+If, in the future,
+the vendor distributes something
+equivalent to your locally written files
+you can CVS
+.B remove
+the files from the `local-additions' directory at that time.
+.SH SEE ALSO
+.BR cvs (local),
+.BR checkin (local),
+.BR cvslog (local),
+.BR cvscheck (local)
+.SH AUTHOR
+Lowell Skoog
+.br
+Software Technology Group
+.br
+Technical Computing
diff --git a/gnu/dist/cvs/contrib/descend.man b/gnu/dist/cvs/contrib/descend.man
new file mode 100644
index 00000000000..0434ca8b43c
--- /dev/null
+++ b/gnu/dist/cvs/contrib/descend.man
@@ -0,0 +1,114 @@
+.TH DESCEND 1 "31 March 1992"
+.SH NAME
+descend \- walk directory tree and execute a command at each node
+.SH SYNOPSIS
+.B descend
+[
+.B \-afqrv
+]
+.I command
+[
+.I directory
+\&.\|.\|.
+]
+.SH DESCRIPTION
+.B descend
+walks down a directory tree and executes a command at each node. It
+is not as versatile as
+.BR find (1),
+but it has a simpler syntax. If no
+.I directory
+is specified,
+.B descend
+starts at the current one.
+.LP
+Unlike
+.BR find ,
+.B descend
+can be told to skip the special directories associated with RCS,
+CVS, and SCCS. This makes
+.B descend
+especially handy for use with these packages. It can be used with
+other commands too, of course.
+.LP
+.B descend
+is a poor man's way to make any command recursive. Note:
+.B descend
+does not follow symbolic links to directories unless they are
+specified on the command line.
+.SH OPTIONS
+.TP 15
+.B \-a
+.I All.
+Descend into directories that begin with '.'.
+.TP
+.B \-f
+.I Force.
+Ignore errors during descent. Normally,
+.B descend
+quits when an error occurs.
+.TP
+.B \-q
+.I Quiet.
+Suppress the message `In directory
+.IR directory '
+that is normally printed during the descent.
+.TP
+.B \-r
+.I Restricted.
+Don't descend into the special directories
+.SB RCS,
+.SB CVS,
+.SB CVS.adm,
+and
+.SB SCCS.
+.TP
+.B \-v
+.I Verbose.
+Print
+.I command
+before executing it.
+.SH EXAMPLES
+.TP 15
+.B "descend ls"
+Cheap substitute for `ls -R'.
+.TP 15
+.B "descend -f 'rm *' tree"
+Strip `tree' of its leaves. This command descends the `tree'
+directory, removing all regular files. Since
+.BR rm (1)
+does not remove directories, this command leaves the directory
+structure of `tree' intact, but denuded. The
+.B \-f
+option is required to keep
+.B descend
+from quitting. You could use `rm \-f' instead.
+.TP
+.B "descend -r 'co RCS/*'" /project/src/
+Check out every RCS file under the directory
+.BR "/project/src" .
+.TP
+.B "descend -r 'cvs diff'"
+Perform CVS `diff' operation on every directory below (and including)
+the current one.
+.SH DIAGNOSTICS
+Returns 1 if errors occur (and the
+.B \-f
+option is not used). Otherwise returns 0.
+.SH SEE ALSO
+.BR find (1),
+.BR rcsintro (1),
+.BR cvs (1),
+.BR sccs (1)
+.SH AUTHOR
+Lowell Skoog
+.br
+Software Technology Group
+.br
+John Fluke Mfg. Co., Inc.
+.SH BUGS
+Shell metacharacters in
+.I command
+may have bizarre effects. In particular, compound commands
+(containing ';', '[', and ']' characters) will not work. It is best
+to enclose complicated commands in single quotes \(aa\ \(aa.
diff --git a/gnu/dist/cvs/contrib/descend.sh b/gnu/dist/cvs/contrib/descend.sh
new file mode 100644
index 00000000000..039a7a3d3ee
--- /dev/null
+++ b/gnu/dist/cvs/contrib/descend.sh
@@ -0,0 +1,115 @@
+#! /bin/sh
+#
+# descend - walk down a directory tree and execute a command at each node
+
+fullname=$0
+name=descend
+usage="Usage: $name [-afqrv] command [directory ...]\n
+\040\040-a\040\040All: descend into directories starting with '.'\n
+\040\040-f\040\040Force: ignore errors during descent\n
+\040\040-q\040\040Quiet: don't print directory names\n
+\040\040-r\040\040Restricted: don't descend into RCS, CVS.adm, SCCS directories\n
+\040\040-v\040\040Verbose: print command before executing it"
+
+# Scan for options
+while getopts afqrv option; do
+ case $option in
+ a)
+ alldirs=$option
+ options=$options" "-$option
+ ;;
+ f)
+ force=$option
+ options=$options" "-$option
+ ;;
+ q)
+ verbose=
+ quiet=$option
+ options=$options" "-$option
+ ;;
+ r)
+ restricted=$option
+ options=$options" "-$option
+ ;;
+ v)
+ verbose=$option
+ quiet=
+ options=$options" "-$option
+ ;;
+ \?)
+ /usr/5bin/echo $usage 1>&2
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+# Get command to execute
+if [ $# -lt 1 ] ; then
+ /usr/5bin/echo $usage 1>&2
+ exit 1
+else
+ command=$1
+ shift
+fi
+
+# If no directory specified, use '.'
+if [ $# -lt 1 ] ; then
+ default_dir=.
+fi
+
+# For each directory specified
+for dir in $default_dir "$@" ; do
+
+ # Spawn sub-shell so we return to starting directory afterward
+ (cd $dir
+
+ # Execute specified command
+ if [ -z "$quiet" ] ; then
+ echo In directory `hostname`:`pwd`
+ fi
+ if [ -n "$verbose" ] ; then
+ echo $command
+ fi
+ eval "$command" || if [ -z "$force" ] ; then exit 1; fi
+
+ # Collect dot file names if necessary
+ if [ -n "$alldirs" ] ; then
+ dotfiles=.*
+ else
+ dotfiles=
+ fi
+
+ # For each file in current directory
+ for file in $dotfiles * ; do
+
+ # Skip '.' and '..'
+ if [ "$file" = "." -o "$file" = ".." ] ; then
+ continue
+ fi
+
+ # If a directory but not a symbolic link
+ if [ -d "$file" -a ! -h "$file" ] ; then
+
+ # If not skipping this type of directory
+ if [ \( "$file" != "RCS" -a \
+ "$file" != "SCCS" -a \
+ "$file" != "CVS" -a \
+ "$file" != "CVS.adm" \) \
+ -o -z "$restricted" ] ; then
+
+ # Recursively descend into it
+ $fullname $options "$command" "$file" \
+ || if [ -z "$force" ] ; then exit 1; fi
+ fi
+
+ # Else if a directory AND a symbolic link
+ elif [ -d "$file" -a -h "$file" ] ; then
+
+ if [ -z "$quiet" ] ; then
+ echo In directory `hostname`:`pwd`/$file: symbolic link: skipping
+ fi
+ fi
+ done
+ ) || if [ -z "$force" ] ; then exit 1; fi
+done
diff --git a/gnu/dist/cvs/contrib/dirfns.shar b/gnu/dist/cvs/contrib/dirfns.shar
new file mode 100644
index 00000000000..8324c4198e3
--- /dev/null
+++ b/gnu/dist/cvs/contrib/dirfns.shar
@@ -0,0 +1,481 @@
+echo 'directory.3':
+sed 's/^X//' >'directory.3' <<'!'
+X.TH DIRECTORY 3 imported
+X.DA 9 Oct 1985
+X.SH NAME
+Xopendir, readdir, telldir, seekdir, rewinddir, closedir \- high-level directory operations
+X.SH SYNOPSIS
+X.B #include <sys/types.h>
+X.br
+X.B #include <ndir.h>
+X.PP
+X.SM
+X.B DIR
+X.B *opendir(filename)
+X.br
+X.B char *filename;
+X.PP
+X.SM
+X.B struct direct
+X.B *readdir(dirp)
+X.br
+X.B DIR *dirp;
+X.PP
+X.SM
+X.B long
+X.B telldir(dirp)
+X.br
+X.B DIR *dirp;
+X.PP
+X.SM
+X.B seekdir(dirp, loc)
+X.br
+X.B DIR *dirp;
+X.br
+X.B long loc;
+X.PP
+X.SM
+X.B rewinddir(dirp)
+X.br
+X.B DIR *dirp;
+X.PP
+X.SM
+X.B closedir(dirp)
+X.br
+X.B DIR *dirp;
+X.SH DESCRIPTION
+XThis library provides high-level primitives for directory scanning,
+Xsimilar to those available for 4.2BSD's (very different) directory system.
+X.\"The purpose of this library is to simulate
+X.\"the new flexible length directory names of 4.2bsd UNIX
+X.\"on top of the old directory structure of v7.
+XIt incidentally provides easy portability to and from 4.2BSD (insofar
+Xas such portability is not compromised by other 4.2/VAX dependencies).
+X.\"It allows programs to be converted immediately
+X.\"to the new directory access interface,
+X.\"so that they need only be relinked
+X.\"when moved to 4.2bsd.
+X.\"It is obtained with the loader option
+X.\".BR \-lndir .
+X.PP
+X.I Opendir
+Xopens the directory named by
+X.I filename
+Xand associates a
+X.I directory stream
+Xwith it.
+X.I Opendir
+Xreturns a pointer to be used to identify the
+X.I directory stream
+Xin subsequent operations.
+XThe pointer
+X.SM
+X.B NULL
+Xis returned if
+X.I filename
+Xcannot be accessed or is not a directory.
+X.PP
+X.I Readdir
+Xreturns a pointer to the next directory entry.
+XIt returns
+X.B NULL
+Xupon reaching the end of the directory or detecting
+Xan invalid
+X.I seekdir
+Xoperation.
+X.PP
+X.I Telldir
+Xreturns the current location associated with the named
+X.I directory stream.
+X.PP
+X.I Seekdir
+Xsets the position of the next
+X.I readdir
+Xoperation on the
+X.I directory stream.
+XThe new position reverts to the one associated with the
+X.I directory stream
+Xwhen the
+X.I telldir
+Xoperation was performed.
+XValues returned by
+X.I telldir
+Xare good only for the lifetime of the DIR pointer from
+Xwhich they are derived.
+XIf the directory is closed and then reopened,
+Xthe
+X.I telldir
+Xvalue may be invalidated
+Xdue to undetected directory compaction in 4.2BSD.
+XIt is safe to use a previous
+X.I telldir
+Xvalue immediately after a call to
+X.I opendir
+Xand before any calls to
+X.I readdir.
+X.PP
+X.I Rewinddir
+Xresets the position of the named
+X.I directory stream
+Xto the beginning of the directory.
+X.PP
+X.I Closedir
+Xcauses the named
+X.I directory stream
+Xto be closed,
+Xand the structure associated with the DIR pointer to be freed.
+X.PP
+XA
+X.I direct
+Xstructure is as follows:
+X.PP
+X.RS
+X.nf
+Xstruct direct {
+X /* unsigned */ long d_ino; /* inode number of entry */
+X unsigned short d_reclen; /* length of this record */
+X unsigned short d_namlen; /* length of string in d_name */
+X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */
+X};
+X.fi
+X.RE
+X.PP
+XThe
+X.I d_reclen
+Xfield is meaningless in non-4.2BSD systems and should be ignored.
+XThe use of a
+X.I long
+Xfor
+X.I d_ino
+Xis also a 4.2BSDism;
+X.I ino_t
+X(see
+X.IR types (5))
+Xshould be used elsewhere.
+XThe macro
+X.I DIRSIZ(dp)
+Xgives the minimum memory size needed to hold the
+X.I direct
+Xvalue pointed to by
+X.IR dp ,
+Xwith the minimum necessary allocation for
+X.IR d_name .
+X.PP
+XThe preferred way to search the current directory for entry ``name'' is:
+X.PP
+X.RS
+X.nf
+X len = strlen(name);
+X dirp = opendir(".");
+X if (dirp == NULL) {
+X fprintf(stderr, "%s: can't read directory .\\n", argv[0]);
+X return NOT_FOUND;
+X }
+X while ((dp = readdir(dirp)) != NULL)
+X if (dp->d_namlen == len && strcmp(dp->d_name, name) == 0) {
+X closedir(dirp);
+X return FOUND;
+X }
+X closedir(dirp);
+X return NOT_FOUND;
+X.RE
+X.\".SH LINKING
+X.\"This library is accessed by specifying ``-lndir'' as the
+X.\"last argument to the compile line, e.g.:
+X.\".PP
+X.\" cc -I/usr/include/ndir -o prog prog.c -lndir
+X.SH "SEE ALSO"
+Xopen(2),
+Xclose(2),
+Xread(2),
+Xlseek(2)
+X.SH HISTORY
+XWritten by
+XKirk McKusick at Berkeley (ucbvax!mckusick).
+XMiscellaneous bug fixes from elsewhere.
+XThe size of the data structure has been decreased to avoid excessive
+Xspace waste under V7 (where filenames are 14 characters at most).
+XFor obscure historical reasons, the include file is also available
+Xas
+X.IR <ndir/sys/dir.h> .
+XThe Berkeley version lived in a separate library (\fI\-lndir\fR),
+Xwhereas ours is
+Xpart of the C library, although the separate library is retained to
+Xmaximize compatibility.
+X.PP
+XThis manual page has been substantially rewritten to be informative in
+Xthe absence of a 4.2BSD manual.
+X.SH BUGS
+XThe
+X.I DIRSIZ
+Xmacro actually wastes a bit of space due to some padding requirements
+Xthat are an artifact of 4.2BSD.
+X.PP
+XThe returned value of
+X.I readdir
+Xpoints to a static area that will be overwritten by subsequent calls.
+X.PP
+XThere are some unfortunate name conflicts with the \fIreal\fR V7
+Xdirectory structure definitions.
+!
+echo 'dir.h':
+sed 's/^X//' >'dir.h' <<'!'
+X/* dir.h 4.4 82/07/25 */
+X
+X/*
+X * A directory consists of some number of blocks of DIRBLKSIZ
+X * bytes, where DIRBLKSIZ is chosen such that it can be transferred
+X * to disk in a single atomic operation (e.g. 512 bytes on most machines).
+X *
+X * Each DIRBLKSIZ byte block contains some number of directory entry
+X * structures, which are of variable length. Each directory entry has
+X * a struct direct at the front of it, containing its inode number,
+X * the length of the entry, and the length of the name contained in
+X * the entry. These are followed by the name padded to a 4 byte boundary
+X * with null bytes. All names are guaranteed null terminated.
+X * The maximum length of a name in a directory is MAXNAMLEN.
+X *
+X * The macro DIRSIZ(dp) gives the amount of space required to represent
+X * a directory entry. Free space in a directory is represented by
+X * entries which have dp->d_reclen >= DIRSIZ(dp). All DIRBLKSIZ bytes
+X * in a directory block are claimed by the directory entries. This
+X * usually results in the last entry in a directory having a large
+X * dp->d_reclen. When entries are deleted from a directory, the
+X * space is returned to the previous entry in the same directory
+X * block by increasing its dp->d_reclen. If the first entry of
+X * a directory block is free, then its dp->d_ino is set to 0.
+X * Entries other than the first in a directory do not normally have
+X * dp->d_ino set to 0.
+X */
+X#define DIRBLKSIZ 512
+X#ifdef VMUNIX
+X#define MAXNAMLEN 255
+X#else
+X#define MAXNAMLEN 14
+X#endif
+X
+Xstruct direct {
+X /* unsigned */ long d_ino; /* inode number of entry */
+X unsigned short d_reclen; /* length of this record */
+X unsigned short d_namlen; /* length of string in d_name */
+X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */
+X};
+X
+X/*
+X * The DIRSIZ macro gives the minimum record length which will hold
+X * the directory entry. This requires the amount of space in struct direct
+X * without the d_name field, plus enough space for the name with a terminating
+X * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary.
+X */
+X#undef DIRSIZ
+X#define DIRSIZ(dp) \
+X ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3))
+X
+X#ifndef KERNEL
+X/*
+X * Definitions for library routines operating on directories.
+X */
+Xtypedef struct _dirdesc {
+X int dd_fd;
+X long dd_loc;
+X long dd_size;
+X char dd_buf[DIRBLKSIZ];
+X} DIR;
+X#ifndef NULL
+X#define NULL 0
+X#endif
+Xextern DIR *opendir();
+Xextern struct direct *readdir();
+Xextern long telldir();
+X#ifdef void
+Xextern void seekdir();
+Xextern void closedir();
+X#endif
+X#define rewinddir(dirp) seekdir((dirp), (long)0)
+X#endif KERNEL
+!
+echo 'makefile':
+sed 's/^X//' >'makefile' <<'!'
+XDIR = closedir.o opendir.o readdir.o seekdir.o telldir.o
+XCFLAGS=-O -I. -Dvoid=int
+XDEST=..
+X
+Xall: $(DIR)
+X
+Xmv: $(DIR)
+X mv $(DIR) $(DEST)
+X
+Xcpif: dir.h
+X cp dir.h /usr/include/ndir.h
+X
+Xclean:
+X rm -f *.o
+!
+echo 'closedir.c':
+sed 's/^X//' >'closedir.c' <<'!'
+Xstatic char sccsid[] = "@(#)closedir.c 4.2 3/10/82";
+X
+X#include <sys/types.h>
+X#include <dir.h>
+X
+X/*
+X * close a directory.
+X */
+Xvoid
+Xclosedir(dirp)
+X register DIR *dirp;
+X{
+X close(dirp->dd_fd);
+X dirp->dd_fd = -1;
+X dirp->dd_loc = 0;
+X free((char *)dirp);
+X}
+!
+echo 'opendir.c':
+sed 's/^X//' >'opendir.c' <<'!'
+X/* Copyright (c) 1982 Regents of the University of California */
+X
+Xstatic char sccsid[] = "@(#)opendir.c 4.4 11/12/82";
+X
+X#include <sys/types.h>
+X#include <sys/stat.h>
+X#include <dir.h>
+X
+X/*
+X * open a directory.
+X */
+XDIR *
+Xopendir(name)
+X char *name;
+X{
+X register DIR *dirp;
+X register int fd;
+X struct stat statbuf;
+X char *malloc();
+X
+X if ((fd = open(name, 0)) == -1)
+X return NULL;
+X if (fstat(fd, &statbuf) == -1 || !(statbuf.st_mode & S_IFDIR)) {
+X close(fd);
+X return NULL;
+X }
+X if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
+X close (fd);
+X return NULL;
+X }
+X dirp->dd_fd = fd;
+X dirp->dd_loc = 0;
+X dirp->dd_size = 0; /* so that telldir will work before readdir */
+X return dirp;
+X}
+!
+echo 'readdir.c':
+sed 's/^X//' >'readdir.c' <<'!'
+X/* Copyright (c) 1982 Regents of the University of California */
+X
+Xstatic char sccsid[] = "@(#)readdir.c 4.3 8/8/82";
+X
+X#include <sys/types.h>
+X#include <dir.h>
+X
+X/*
+X * read an old stlye directory entry and present it as a new one
+X */
+X#define ODIRSIZ 14
+X
+Xstruct olddirect {
+X ino_t od_ino;
+X char od_name[ODIRSIZ];
+X};
+X
+X/*
+X * get next entry in a directory.
+X */
+Xstruct direct *
+Xreaddir(dirp)
+X register DIR *dirp;
+X{
+X register struct olddirect *dp;
+X static struct direct dir;
+X
+X for (;;) {
+X if (dirp->dd_loc == 0) {
+X dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf,
+X DIRBLKSIZ);
+X if (dirp->dd_size <= 0) {
+X dirp->dd_size = 0;
+X return NULL;
+X }
+X }
+X if (dirp->dd_loc >= dirp->dd_size) {
+X dirp->dd_loc = 0;
+X continue;
+X }
+X dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc);
+X dirp->dd_loc += sizeof(struct olddirect);
+X if (dp->od_ino == 0)
+X continue;
+X dir.d_ino = dp->od_ino;
+X strncpy(dir.d_name, dp->od_name, ODIRSIZ);
+X dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */
+X dir.d_namlen = strlen(dir.d_name);
+X dir.d_reclen = DIRBLKSIZ;
+X return (&dir);
+X }
+X}
+!
+echo 'seekdir.c':
+sed 's/^X//' >'seekdir.c' <<'!'
+Xstatic char sccsid[] = "@(#)seekdir.c 4.9 3/25/83";
+X
+X#include <sys/param.h>
+X#include <dir.h>
+X
+X/*
+X * seek to an entry in a directory.
+X * Only values returned by "telldir" should be passed to seekdir.
+X */
+Xvoid
+Xseekdir(dirp, loc)
+X register DIR *dirp;
+X long loc;
+X{
+X long curloc, base, offset;
+X struct direct *dp;
+X extern long lseek();
+X
+X curloc = telldir(dirp);
+X if (loc == curloc)
+X return;
+X base = loc & ~(DIRBLKSIZ - 1);
+X offset = loc & (DIRBLKSIZ - 1);
+X (void) lseek(dirp->dd_fd, base, 0);
+X dirp->dd_size = 0;
+X dirp->dd_loc = 0;
+X while (dirp->dd_loc < offset) {
+X dp = readdir(dirp);
+X if (dp == NULL)
+X return;
+X }
+X}
+!
+echo 'telldir.c':
+sed 's/^X//' >'telldir.c' <<'!'
+Xstatic char sccsid[] = "@(#)telldir.c 4.1 2/21/82";
+X
+X#include <sys/types.h>
+X#include <dir.h>
+X
+X/*
+X * return a pointer into a directory
+X */
+Xlong
+Xtelldir(dirp)
+X DIR *dirp;
+X{
+X long lseek();
+X
+X return (lseek(dirp->dd_fd, 0L, 1) - dirp->dd_size + dirp->dd_loc);
+X}
+!
+echo done
diff --git a/gnu/dist/cvs/contrib/intro.doc b/gnu/dist/cvs/contrib/intro.doc
new file mode 100644
index 00000000000..a6d4ec12338
--- /dev/null
+++ b/gnu/dist/cvs/contrib/intro.doc
@@ -0,0 +1,112 @@
+Date: Tue, 16 Jun 1992 17:05:23 +0200
+From: Steven.Pemberton@cwi.nl
+Message-Id: <9206161505.AA06927.steven@sijs.cwi.nl>
+To: berliner@Sun.COM
+Subject: cvs
+
+INTRODUCTION TO USING CVS
+
+ CVS is a system that lets groups of people work simultaneously on
+ groups of files (for instance program sources).
+
+ It works by holding a central 'repository' of the most recent version
+ of the files. You may at any time create a personal copy of these
+ files; if at a later date newer versions of the files are put in the
+ repository, you can 'update' your copy.
+
+ You may edit your copy of the files freely. If new versions of the
+ files have been put in the repository in the meantime, doing an update
+ merges the changes in the central copy into your copy.
+ (It can be that when you do an update, the changes in the
+ central copy clash with changes you have made in your own
+ copy. In this case cvs warns you, and you have to resolve the
+ clash in your copy.)
+
+ When you are satisfied with the changes you have made in your copy of
+ the files, you can 'commit' them into the central repository.
+ (When you do a commit, if you haven't updated to the most
+ recent version of the files, cvs tells you this; then you have
+ to first update, resolve any possible clashes, and then redo
+ the commit.)
+
+USING CVS
+
+ Suppose that a number of repositories have been stored in
+ /usr/src/cvs. Whenever you use cvs, the environment variable
+ CVSROOT must be set to this (for some reason):
+
+ CVSROOT=/usr/src/cvs
+ export CVSROOT
+
+TO CREATE A PERSONAL COPY OF A REPOSITORY
+
+ Suppose you want a copy of the files in repository 'views' to be
+ created in your directory src. Go to the place where you want your
+ copy of the directory, and do a 'checkout' of the directory you
+ want:
+
+ cd $HOME/src
+ cvs checkout views
+
+ This creates a directory called (in this case) 'views' in the src
+ directory, containing a copy of the files, which you may now work
+ on to your heart's content.
+
+TO UPDATE YOUR COPY
+
+ Use the command 'cvs update'.
+
+ This will update your copy with any changes from the central
+ repository, telling you which files have been updated (their names
+ are displayed with a U before them), and which have been modified
+ by you and not yet committed (preceded by an M). You will be
+ warned of any files that contain clashes, the clashes will be
+ marked in the file surrounded by lines of the form <<<< and >>>>.
+
+TO COMMIT YOUR CHANGES
+
+ Use the command 'cvs commit'.
+
+ You will be put in an editor to make a message that describes the
+ changes that you have made (for future reference). Your changes
+ will then be added to the central copy.
+
+ADDING AND REMOVING FILES
+
+ It can be that the changes you want to make involve a completely
+ new file, or removing an existing one. The commands to use here
+ are:
+
+ cvs add <filename>
+ cvs remove <filename>
+
+ You still have to do a commit after these commands. You may make
+ any number of new files in your copy of the repository, but they
+ will not be committed to the central copy unless you do a 'cvs add'.
+
+OTHER USEFUL COMMANDS AND HINTS
+
+ To see the commit messages for files, and who made them, use:
+
+ cvs log [filenames]
+
+ To see the differences between your version and the central version:
+
+ cvs diff [filenames]
+
+ To give a file a new name, rename it and do an add and a remove.
+
+ To lose your changes and go back to the version from the
+ repository, delete the file and do an update.
+
+ After an update where there have been clashes, your original
+ version of the file is saved as .#file.version.
+
+ All the cvs commands mentioned accept a flag '-n', that doesn't do
+ the action, but lets you see what would happen. For instance, you
+ can use 'cvs -n update' to see which files would be updated.
+
+MORE INFORMATION
+
+ This is necessarily a very brief introduction. See the manual page
+ (man cvs) for full details.
diff --git a/gnu/dist/cvs/contrib/log.pl b/gnu/dist/cvs/contrib/log.pl
new file mode 100644
index 00000000000..3a8ad992b60
--- /dev/null
+++ b/gnu/dist/cvs/contrib/log.pl
@@ -0,0 +1,177 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+# XXX: FIXME: handle multiple '-f logfile' arguments
+#
+# XXX -- I HATE Perl! This *will* be re-written in shell/awk/sed soon!
+#
+
+# Usage: log.pl [[-m user] ...] [-s] -f logfile 'dirname file ...'
+#
+# -m user - for each user to receive cvs log reports
+# (multiple -m's permitted)
+# -s - to prevent "cvs status -v" messages
+# -f logfile - for the logfile to append to (mandatory,
+# but only one logfile can be specified).
+
+# here is what the output looks like:
+#
+# From: woods@kuma.domain.top
+# Subject: CVS update: testmodule
+#
+# Date: Wednesday November 23, 1994 @ 14:15
+# Author: woods
+#
+# Update of /local/src-CVS/testmodule
+# In directory kuma:/home/kuma/woods/work.d/testmodule
+#
+# Modified Files:
+# test3
+# Added Files:
+# test6
+# Removed Files:
+# test4
+# Log Message:
+# - wow, what a test
+#
+# (and for each file the "cvs status -v" output is appended unless -s is used)
+#
+# ==================================================================
+# File: test3 Status: Up-to-date
+#
+# Working revision: 1.41 Wed Nov 23 14:15:59 1994
+# Repository revision: 1.41 /local/src-CVS/cvs/testmodule/test3,v
+# Sticky Options: -ko
+#
+# Existing Tags:
+# local-v2 (revision: 1.7)
+# local-v1 (revision: 1.1.1.2)
+# CVS-1_4A2 (revision: 1.1.1.2)
+# local-v0 (revision: 1.2)
+# CVS-1_4A1 (revision: 1.1.1.1)
+# CVS (branch: 1.1.1)
+
+$cvsroot = $ENV{'CVSROOT'};
+
+# turn off setgid
+#
+$) = $(;
+
+$dostatus = 1;
+
+# parse command line arguments
+#
+while (@ARGV) {
+ $arg = shift @ARGV;
+
+ if ($arg eq '-m') {
+ $users = "$users " . shift @ARGV;
+ } elsif ($arg eq '-f') {
+ ($logfile) && die "Too many '-f' args";
+ $logfile = shift @ARGV;
+ } elsif ($arg eq '-s') {
+ $dostatus = 0;
+ } else {
+ ($donefiles) && die "Too many arguments!\n";
+ $donefiles = 1;
+ @files = split(/ /, $arg);
+ }
+}
+
+# the first argument is the module location relative to $CVSROOT
+#
+$modulepath = shift @files;
+
+$mailcmd = "| Mail -s 'CVS update: $modulepath'";
+
+# Initialise some date and time arrays
+#
+@mos = (January,February,March,April,May,June,July,August,September,
+ October,November,December);
+@days = (Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday);
+
+($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;
+$year += 1900;
+
+# get a login name for the guy doing the commit....
+#
+$login = getlogin || (getpwuid($<))[0] || "nobody";
+
+# open log file for appending
+#
+open(OUT, ">>" . $logfile) || die "Could not open(" . $logfile . "): $!\n";
+
+# send mail, if there's anyone to send to!
+#
+if ($users) {
+ $mailcmd = "$mailcmd $users";
+ open(MAIL, $mailcmd) || die "Could not Exec($mailcmd): $!\n";
+}
+
+# print out the log Header
+#
+print OUT "\n";
+print OUT "****************************************\n";
+print OUT "Date:\t$days[$wday] $mos[$mon] $mday, $year @ $hour:" . sprintf("%02d", $min) . "\n";
+print OUT "Author:\t$login\n\n";
+
+if (MAIL) {
+ print MAIL "\n";
+ print MAIL "Date:\t$days[$wday] $mos[$mon] $mday, $year @ $hour:" . sprintf("%02d", $min) . "\n";
+ print MAIL "Author:\t$login\n\n";
+}
+
+# print the stuff from logmsg that comes in on stdin to the logfile
+#
+open(IN, "-");
+while (<IN>) {
+ print OUT $_;
+ if (MAIL) {
+ print MAIL $_;
+ }
+}
+close(IN);
+
+print OUT "\n";
+
+# after log information, do an 'cvs -Qq status -v' on each file in the arguments.
+#
+if ($dostatus != 0) {
+ while (@files) {
+ $file = shift @files;
+ if ($file eq "-") {
+ print OUT "[input file was '-']\n";
+ if (MAIL) {
+ print MAIL "[input file was '-']\n";
+ }
+ last;
+ }
+ $pid = open(RCS, "-|");
+ if ( !defined $pid )
+ {
+ die "fork failed: $!";
+ }
+ if ($pid == 0)
+ {
+ exec 'cvs', '-nQq', 'status', '-v', $file;
+ die "cvs exec failed: $!";
+ }
+ while (<RCS>) {
+ print OUT;
+ if (MAIL) {
+ print MAIL;
+ }
+ }
+ close(RCS);
+ }
+}
+
+close(OUT);
+die "Write to $logfile failed" if $?;
+
+close(MAIL);
+die "Pipe to $mailcmd failed" if $?;
+
+## must exit cleanly
+##
+exit 0;
diff --git a/gnu/dist/cvs/contrib/log_accum.pl b/gnu/dist/cvs/contrib/log_accum.pl
new file mode 100644
index 00000000000..d299fa62b53
--- /dev/null
+++ b/gnu/dist/cvs/contrib/log_accum.pl
@@ -0,0 +1,571 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+# Perl filter to handle the log messages from the checkin of files in
+# a directory. This script will group the lists of files by log
+# message, and mail a single consolidated log message at the end of
+# the commit.
+#
+# This file assumes a pre-commit checking program that leaves the
+# names of the first and last commit directories in a temporary file.
+#
+# Contributed by David Hampton <hampton@cisco.com>
+#
+# hacked greatly by Greg A. Woods <woods@planix.com>
+
+# Usage: log_accum.pl [-d] [-s] [-M module] [[-m mailto] ...] [[-R replyto] ...] [-f logfile]
+# -d - turn on debugging
+# -m mailto - send mail to "mailto" (multiple)
+# -R replyto - set the "Reply-To:" to "replyto" (multiple)
+# -M modulename - set module name to "modulename"
+# -f logfile - write commit messages to logfile too
+# -s - *don't* run "cvs status -v" for each file
+# -w - show working directory with log message
+
+#
+# Configurable options
+#
+
+# set this to something that takes a whole message on stdin
+$MAILER = "/usr/lib/sendmail -t";
+
+#
+# End user configurable options.
+#
+
+# Constants (don't change these!)
+#
+$STATE_NONE = 0;
+$STATE_CHANGED = 1;
+$STATE_ADDED = 2;
+$STATE_REMOVED = 3;
+$STATE_LOG = 4;
+
+$LAST_FILE = "/tmp/#cvs.lastdir";
+
+$CHANGED_FILE = "/tmp/#cvs.files.changed";
+$ADDED_FILE = "/tmp/#cvs.files.added";
+$REMOVED_FILE = "/tmp/#cvs.files.removed";
+$LOG_FILE = "/tmp/#cvs.files.log";
+
+$FILE_PREFIX = "#cvs.files";
+
+#
+# Subroutines
+#
+
+sub cleanup_tmpfiles {
+ local($wd, @files);
+
+ $wd = `pwd`;
+ chdir("/tmp") || die("Can't chdir('/tmp')\n");
+ opendir(DIR, ".");
+ push(@files, grep(/^$FILE_PREFIX\..*\.$id$/, readdir(DIR)));
+ closedir(DIR);
+ foreach (@files) {
+ unlink $_;
+ }
+ unlink $LAST_FILE . "." . $id;
+
+ chdir($wd);
+}
+
+sub write_logfile {
+ local($filename, @lines) = @_;
+
+ open(FILE, ">$filename") || die("Cannot open log file $filename.\n");
+ print FILE join("\n", @lines), "\n";
+ close(FILE);
+}
+
+sub append_to_logfile {
+ local($filename, @lines) = @_;
+
+ open(FILE, ">$filename") || die("Cannot open log file $filename.\n");
+ print FILE join("\n", @lines), "\n";
+ close(FILE);
+}
+
+sub format_names {
+ local($dir, @files) = @_;
+ local(@lines);
+
+ $format = "\t%-" . sprintf("%d", length($dir)) . "s%s ";
+
+ $lines[0] = sprintf($format, $dir, ":");
+
+ if ($debug) {
+ print STDERR "format_names(): dir = ", $dir, "; files = ", join(":", @files), ".\n";
+ }
+ foreach $file (@files) {
+ if (length($lines[$#lines]) + length($file) > 65) {
+ $lines[++$#lines] = sprintf($format, " ", " ");
+ }
+ $lines[$#lines] .= $file . " ";
+ }
+
+ @lines;
+}
+
+sub format_lists {
+ local(@lines) = @_;
+ local(@text, @files, $lastdir);
+
+ if ($debug) {
+ print STDERR "format_lists(): ", join(":", @lines), "\n";
+ }
+ @text = ();
+ @files = ();
+ $lastdir = shift @lines; # first thing is always a directory
+ if ($lastdir !~ /.*\/$/) {
+ die("Damn, $lastdir doesn't look like a directory!\n");
+ }
+ foreach $line (@lines) {
+ if ($line =~ /.*\/$/) {
+ push(@text, &format_names($lastdir, @files));
+ $lastdir = $line;
+ @files = ();
+ } else {
+ push(@files, $line);
+ }
+ }
+ push(@text, &format_names($lastdir, @files));
+
+ @text;
+}
+
+sub append_names_to_file {
+ local($filename, $dir, @files) = @_;
+
+ if (@files) {
+ open(FILE, ">>$filename") || die("Cannot open file $filename.\n");
+ print FILE $dir, "\n";
+ print FILE join("\n", @files), "\n";
+ close(FILE);
+ }
+}
+
+sub read_line {
+ local($line);
+ local($filename) = @_;
+
+ open(FILE, "<$filename") || die("Cannot open file $filename.\n");
+ $line = <FILE>;
+ close(FILE);
+ chop($line);
+ $line;
+}
+
+sub read_logfile {
+ local(@text);
+ local($filename, $leader) = @_;
+
+ open(FILE, "<$filename");
+ while (<FILE>) {
+ chop;
+ push(@text, $leader.$_);
+ }
+ close(FILE);
+ @text;
+}
+
+sub build_header {
+ local($header);
+ local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+ $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\nRepository:\t%s\nChanges by:\t%s@%s\t%02d/%02d/%02d %02d:%02d:%02d",
+ $cvsroot,
+ $modulename,
+ $dir,
+ $login, $hostdomain,
+ $year%100, $mon+1, $mday,
+ $hour, $min, $sec);
+}
+
+sub mail_notification {
+ local(@text) = @_;
+
+ # if only we had strftime()... stuff stolen from perl's ctime.pl:
+ local($[) = 0;
+
+ @DoW = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
+ @MoY = ('Jan','Feb','Mar','Apr','May','Jun',
+ 'Jul','Aug','Sep','Oct','Nov','Dec');
+
+ # Determine what time zone is in effect.
+ # Use GMT if TZ is defined as null, local time if TZ undefined.
+ # There's no portable way to find the system default timezone.
+ #
+ $TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : '';
+
+ # Hack to deal with 'PST8PDT' format of TZ
+ # Note that this can't deal with all the esoteric forms, but it
+ # does recognize the most common: [:]STDoff[DST[off][,rule]]
+ #
+ if ($TZ =~ /^([^:\d+\-,]{3,})([+-]?\d{1,2}(:\d{1,2}){0,2})([^\d+\-,]{3,})?/) {
+ $TZ = $isdst ? $4 : $1;
+ $tzoff = sprintf("%05d", -($2) * 100);
+ }
+
+ # perl-4.036 doesn't have the $zone or $gmtoff...
+ ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst, $zone, $gmtoff) =
+ ($TZ eq 'GMT') ? gmtime(time) : localtime(time);
+
+ $year += ($year < 70) ? 2000 : 1900;
+
+ if ($gmtoff != 0) {
+ $tzoff = sprintf("%05d", ($gmtoff / 60) * 100);
+ }
+ if ($zone ne '') {
+ $TZ = $zone;
+ }
+
+ # ok, let's try....
+ $rfc822date = sprintf("%s, %2d %s %4d %2d:%02d:%02d %s (%s)",
+ $DoW[$wday], $mday, $MoY[$mon], $year,
+ $hour, $min, $sec, $tzoff, $TZ);
+
+ open(MAIL, "| $MAILER");
+ print MAIL "Date: " . $rfc822date . "\n";
+ print MAIL "Subject: CVS Update: " . $modulename . "\n";
+ print MAIL "To: " . $mailto . "\n";
+ print MAIL "Reply-To: " . $replyto . "\n";
+ print MAIL "\n";
+ print MAIL join("\n", @text), "\n";
+ close(MAIL);
+}
+
+sub write_commitlog {
+ local($logfile, @text) = @_;
+
+ open(FILE, ">>$logfile");
+ print FILE join("\n", @text), "\n";
+ close(FILE);
+}
+
+#
+# Main Body
+#
+
+# Initialize basic variables
+#
+$debug = 0;
+$id = getpgrp(); # note, you *must* use a shell which does setpgrp()
+$state = $STATE_NONE;
+$login = getlogin || (getpwuid($<))[0] || "nobody";
+chop($hostname = `hostname`);
+chop($domainname = `domainname`);
+if ($domainname !~ '^\..*') {
+ $domainname = '.' . $domainname;
+}
+$hostdomain = $hostname . $domainname;
+$cvsroot = $ENV{'CVSROOT'};
+$do_status = 1; # moderately useful
+$show_wd = 0; # useless in client/server
+$modulename = "";
+
+# parse command line arguments (file list is seen as one arg)
+#
+while (@ARGV) {
+ $arg = shift @ARGV;
+
+ if ($arg eq '-d') {
+ $debug = 1;
+ print STDERR "Debug turned on...\n";
+ } elsif ($arg eq '-m') {
+ if ($mailto eq '') {
+ $mailto = shift @ARGV;
+ } else {
+ $mailto = $mailto . ", " . shift @ARGV;
+ }
+ } elsif ($arg eq '-R') {
+ if ($replyto eq '') {
+ $replyto = shift @ARGV;
+ } else {
+ $replyto = $replyto . ", " . shift @ARGV;
+ }
+ } elsif ($arg eq '-M') {
+ $modulename = shift @ARGV;
+ } elsif ($arg eq '-s') {
+ $do_status = 0;
+ } elsif ($arg eq '-w') {
+ $show_wd = 1;
+ } elsif ($arg eq '-f') {
+ ($commitlog) && die("Too many '-f' args\n");
+ $commitlog = shift @ARGV;
+ } else {
+ ($donefiles) && die("Too many arguments! Check usage.\n");
+ $donefiles = 1;
+ @files = split(/ /, $arg);
+ }
+}
+($mailto) || die("No mail recipient specified (use -m)\n");
+if ($replyto eq '') {
+ $replyto = $login;
+}
+
+# for now, the first "file" is the repository directory being committed,
+# relative to the $CVSROOT location
+#
+@path = split('/', $files[0]);
+
+# XXX There are some ugly assumptions in here about module names and
+# XXX directories relative to the $CVSROOT location -- really should
+# XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since
+# XXX we have to parse it backwards.
+# XXX
+# XXX Fortunately it's relatively easy for the user to specify the
+# XXX module name as appropriate with a '-M' via the directory
+# XXX matching in loginfo.
+#
+if ($modulename eq "") {
+ $modulename = $path[0]; # I.e. the module name == top-level dir
+}
+if ($#path == 0) {
+ $dir = ".";
+} else {
+ $dir = join('/', @path);
+}
+$dir = $dir . "/";
+
+if ($debug) {
+ print STDERR "module - ", $modulename, "\n";
+ print STDERR "dir - ", $dir, "\n";
+ print STDERR "path - ", join(":", @path), "\n";
+ print STDERR "files - ", join(":", @files), "\n";
+ print STDERR "id - ", $id, "\n";
+}
+
+# Check for a new directory first. This appears with files set as follows:
+#
+# files[0] - "path/name/newdir"
+# files[1] - "-"
+# files[2] - "New"
+# files[3] - "directory"
+#
+if ($files[2] =~ /New/ && $files[3] =~ /directory/) {
+ local(@text);
+
+ @text = ();
+ push(@text, &build_header());
+ push(@text, "");
+ push(@text, $files[0]);
+ push(@text, "");
+
+ while (<STDIN>) {
+ chop; # Drop the newline
+ push(@text, $_);
+ }
+
+ &mail_notification($mailto, @text);
+
+ exit 0;
+}
+
+# Check for an import command. This appears with files set as follows:
+#
+# files[0] - "path/name"
+# files[1] - "-"
+# files[2] - "Imported"
+# files[3] - "sources"
+#
+if ($files[2] =~ /Imported/ && $files[3] =~ /sources/) {
+ local(@text);
+
+ @text = ();
+ push(@text, &build_header());
+ push(@text, "");
+ push(@text, $files[0]);
+ push(@text, "");
+
+ while (<STDIN>) {
+ chop; # Drop the newline
+ push(@text, $_);
+ }
+
+ &mail_notification(@text);
+
+ exit 0;
+}
+
+# Iterate over the body of the message collecting information.
+#
+while (<STDIN>) {
+ chop; # Drop the newline
+
+ if (/^In directory/) {
+ if ($show_wd) { # useless in client/server mode
+ push(@log_lines, $_);
+ push(@log_lines, "");
+ }
+ next;
+ }
+
+ if (/^Modified Files/) { $state = $STATE_CHANGED; next; }
+ if (/^Added Files/) { $state = $STATE_ADDED; next; }
+ if (/^Removed Files/) { $state = $STATE_REMOVED; next; }
+ if (/^Log Message/) { $state = $STATE_LOG; next; }
+
+ s/^[ \t\n]+//; # delete leading whitespace
+ s/[ \t\n]+$//; # delete trailing whitespace
+
+ if ($state == $STATE_CHANGED) { push(@changed_files, split); }
+ if ($state == $STATE_ADDED) { push(@added_files, split); }
+ if ($state == $STATE_REMOVED) { push(@removed_files, split); }
+ if ($state == $STATE_LOG) { push(@log_lines, $_); }
+}
+
+# Strip leading and trailing blank lines from the log message. Also
+# compress multiple blank lines in the body of the message down to a
+# single blank line.
+#
+while ($#log_lines > -1) {
+ last if ($log_lines[0] ne "");
+ shift(@log_lines);
+}
+while ($#log_lines > -1) {
+ last if ($log_lines[$#log_lines] ne "");
+ pop(@log_lines);
+}
+for ($i = $#log_lines; $i > 0; $i--) {
+ if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) {
+ splice(@log_lines, $i, 1);
+ }
+}
+
+if ($debug) {
+ print STDERR "Searching for log file index...";
+}
+# Find an index to a log file that matches this log message
+#
+for ($i = 0; ; $i++) {
+ local(@text);
+
+ last if (! -e "$LOG_FILE.$i.$id"); # the next available one
+ @text = &read_logfile("$LOG_FILE.$i.$id", "");
+ last if ($#text == -1); # nothing in this file, use it
+ last if (join(" ", @log_lines) eq join(" ", @text)); # it's the same log message as another
+}
+if ($debug) {
+ print STDERR " found log file at $i.$id, now writing tmp files.\n";
+}
+
+# Spit out the information gathered in this pass.
+#
+&append_names_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files);
+&append_names_to_file("$ADDED_FILE.$i.$id", $dir, @added_files);
+&append_names_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files);
+&write_logfile("$LOG_FILE.$i.$id", @log_lines);
+
+# Check whether this is the last directory. If not, quit.
+#
+if ($debug) {
+ print STDERR "Checking current dir against last dir.\n";
+}
+$_ = &read_line("$LAST_FILE.$id");
+
+if ($_ ne $cvsroot . "/" . $files[0]) {
+ if ($debug) {
+ print STDERR sprintf("Current directory %s is not last directory %s.\n", $cvsroot . "/" .$files[0], $_);
+ }
+ exit 0;
+}
+if ($debug) {
+ print STDERR sprintf("Current directory %s is last directory %s -- all commits done.\n", $files[0], $_);
+}
+
+#
+# End Of Commits!
+#
+
+# This is it. The commits are all finished. Lump everything together
+# into a single message, fire a copy off to the mailing list, and drop
+# it on the end of the Changes file.
+#
+
+#
+# Produce the final compilation of the log messages
+#
+@text = ();
+@status_txt = ();
+push(@text, &build_header());
+push(@text, "");
+
+for ($i = 0; ; $i++) {
+ last if (! -e "$LOG_FILE.$i.$id"); # we're done them all!
+ @lines = &read_logfile("$CHANGED_FILE.$i.$id", "");
+ if ($#lines >= 0) {
+ push(@text, "Modified files:");
+ push(@text, &format_lists(@lines));
+ }
+ @lines = &read_logfile("$ADDED_FILE.$i.$id", "");
+ if ($#lines >= 0) {
+ push(@text, "Added files:");
+ push(@text, &format_lists(@lines));
+ }
+ @lines = &read_logfile("$REMOVED_FILE.$i.$id", "");
+ if ($#lines >= 0) {
+ push(@text, "Removed files:");
+ push(@text, &format_lists(@lines));
+ }
+ if ($#text >= 0) {
+ push(@text, "");
+ }
+ @lines = &read_logfile("$LOG_FILE.$i.$id", "\t");
+ if ($#lines >= 0) {
+ push(@text, "Log message:");
+ push(@text, @lines);
+ push(@text, "");
+ }
+ if ($do_status) {
+ local(@changed_files);
+
+ @changed_files = ();
+ push(@changed_files, &read_logfile("$CHANGED_FILE.$i.$id", ""));
+ push(@changed_files, &read_logfile("$ADDED_FILE.$i.$id", ""));
+ push(@changed_files, &read_logfile("$REMOVED_FILE.$i.$id", ""));
+
+ if ($debug) {
+ print STDERR "main: pre-sort changed_files = ", join(":", @changed_files), ".\n";
+ }
+ sort(@changed_files);
+ if ($debug) {
+ print STDERR "main: post-sort changed_files = ", join(":", @changed_files), ".\n";
+ }
+
+ foreach $dofile (@changed_files) {
+ if ($dofile =~ /\/$/) {
+ next; # ignore the silly "dir" entries
+ }
+ if ($debug) {
+ print STDERR "main(): doing 'cvs -nQq status -v $dofile'\n";
+ }
+ open(STATUS, "-|") || exec 'cvs', '-nQq', 'status', '-v', $dofile;
+ while (<STATUS>) {
+ chop;
+ push(@status_txt, $_);
+ }
+ }
+ }
+}
+
+# Write to the commitlog file
+#
+if ($commitlog) {
+ &write_commitlog($commitlog, @text);
+}
+
+if ($#status_txt >= 0) {
+ push(@text, @status_txt);
+}
+
+# Mailout the notification.
+#
+&mail_notification(@text);
+
+# cleanup
+#
+if (! $debug) {
+ &cleanup_tmpfiles();
+}
+
+exit 0;
diff --git a/gnu/dist/cvs/contrib/mfpipe.pl b/gnu/dist/cvs/contrib/mfpipe.pl
new file mode 100644
index 00000000000..c74d7156490
--- /dev/null
+++ b/gnu/dist/cvs/contrib/mfpipe.pl
@@ -0,0 +1,85 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+# From: clyne@niwot.scd.ucar.EDU (John Clyne)
+# Date: Fri, 28 Feb 92 09:54:21 MST
+#
+# BTW, i wrote a perl script that is similar to 'nfpipe' except that in
+# addition to logging to a file it provides a command line option for mailing
+# change notices to a group of users. Obviously you probably wouldn't want
+# to mail every change. But there may be certain directories that are commonly
+# accessed by a group of users who would benefit from an email notice.
+# Especially if they regularly beat on the same directory. Anyway if you
+# think anyone would be interested here it is.
+#
+# File: mfpipe
+#
+# Author: John Clyne
+# National Center for Atmospheric Research
+# PO 3000, Boulder, Colorado
+#
+# Date: Wed Feb 26 18:34:53 MST 1992
+#
+# Description: Tee standard input to mail a list of users and to
+# a file. Used by CVS logging.
+#
+# Usage: mfpipe [-f file] [user@host...]
+#
+# Environment: CVSROOT
+# Path to CVS root.
+#
+# Files:
+#
+#
+# Options: -f file
+# Capture output to 'file'
+#
+
+$header = "Log Message:\n";
+
+$mailcmd = "| mail -s 'CVS update notice'";
+$whoami = `whoami`;
+chop $whoami;
+$date = `date`;
+chop $date;
+
+$cvsroot = $ENV{'CVSROOT'};
+
+while (@ARGV) {
+ $arg = shift @ARGV;
+
+ if ($arg eq '-f') {
+ $file = shift @ARGV;
+ }
+ else {
+ $users = "$users $arg";
+ }
+}
+
+if ($users) {
+ $mailcmd = "$mailcmd $users";
+ open(MAIL, $mailcmd) || die "Execing $mail: $!\n";
+}
+
+if ($file) {
+ $logfile = "$cvsroot/LOG/$file";
+ open(FILE, ">> $logfile") || die "Opening $logfile: $!\n";
+}
+
+print FILE "$whoami $date--------BEGIN LOG ENTRY-------------\n" if ($logfile);
+
+while (<>) {
+ print FILE $log if ($log && $logfile);
+
+ print FILE $_ if ($logfile);
+ print MAIL $_ if ($users);
+
+ $log = "log: " if ($_ eq $header);
+}
+
+close FILE;
+die "Write failed" if $?;
+close MAIL;
+die "Mail failed" if $?;
+
+exit 0;
diff --git a/gnu/dist/cvs/contrib/rcs-to-cvs.sh b/gnu/dist/cvs/contrib/rcs-to-cvs.sh
new file mode 100644
index 00000000000..66a62a9da5e
--- /dev/null
+++ b/gnu/dist/cvs/contrib/rcs-to-cvs.sh
@@ -0,0 +1,184 @@
+#! /bin/sh
+#
+# Based on the CVS 1.0 checkin csh script.
+# Contributed by Per Cederqvist <ceder@signum.se>.
+# Rewritten in sh by David MacKenzie <djm@cygnus.com>.
+#
+# Copyright (c) 1989, Brian Berliner
+#
+# You may distribute under the terms of the GNU General Public License.
+#
+#############################################################################
+#
+# Check in sources that previously were under RCS or no source control system.
+#
+# The repository is the directory where the sources should be deposited.
+#
+# Traverses the current directory, ensuring that an
+# identical directory structure exists in the repository directory. It
+# then checks the files in in the following manner:
+#
+# 1) If the file doesn't yet exist, check it in as revision 1.1
+#
+# The script also is somewhat verbose in letting the user know what is
+# going on. It prints a diagnostic when it creates a new file, or updates
+# a file that has been modified on the trunk.
+#
+# Bugs: doesn't put the files in branch 1.1.1
+# doesn't put in release and vendor tags
+#
+#############################################################################
+
+usage="Usage: rcs-to-cvs [-v] [-m message] [-f message_file] repository"
+vbose=0
+message=""
+if [ -d /var/tmp ]; then message_file=/var/tmp/checkin.$$; else message_file=/usr/tmp/checkin.$$; fi
+got_one=0
+
+if [ $# -lt 1 ]; then
+ echo "$usage" >&2
+ exit 1
+fi
+
+while [ $# -ne 0 ]; do
+ case "$1" in
+ -v)
+ vbose=1
+ ;;
+ -m)
+ shift
+ echo $1 > $message_file
+ got_one=1
+ ;;
+ -f)
+ shift
+ message_file=$1
+ got_one=2
+ ;;
+ *)
+ break
+ esac
+ shift
+done
+
+if [ $# -lt 1 ]; then
+ echo "$usage" >&2
+ exit 1
+fi
+
+repository=$1
+shift
+
+if [ -z "$CVSROOT" ]; then
+ echo "Please the environmental variable CVSROOT to the root" >&2
+ echo " of the tree you wish to update" >&2
+ exit 1
+fi
+
+if [ $got_one -eq 0 ]; then
+ echo "Please Edit this file to contain the RCS log information" >$message_file
+ echo "to be associated with this directory (please remove these lines)">>$message_file
+ ${EDITOR-vi} $message_file
+ got_one=1
+fi
+
+# Ya gotta share.
+umask 0
+
+update_dir=${CVSROOT}/${repository}
+[ ! -d ${update_dir} ] && mkdir $update_dir
+
+if [ -d SCCS ]; then
+ echo SCCS files detected! >&2
+ exit 1
+fi
+if [ -d RCS ]; then
+ co RCS/*
+fi
+
+for name in * .[a-zA-Z0-9]*
+do
+ case "$name" in
+ RCS | *~ | \* | .\[a-zA-Z0-9\]\* ) continue ;;
+ esac
+ echo $name
+ if [ $vbose -ne 0 ]; then
+ echo "Updating ${repository}/${name}"
+ fi
+ if [ -d "$name" ]; then
+ if [ ! -d "${update_dir}/${name}" ]; then
+ echo "WARNING: Creating new directory ${repository}/${name}"
+ mkdir "${update_dir}/${name}"
+ if [ $? -ne 0 ]; then
+ echo "ERROR: mkdir failed - aborting" >&2
+ exit 1
+ fi
+ fi
+ cd "$name"
+ if [ $? -ne 0 ]; then
+ echo "ERROR: Couldn\'t cd to $name - aborting" >&2
+ exit 1
+ fi
+ if [ $vbose -ne 0 ]; then
+ $0 -v -f $message_file "${repository}/${name}"
+ else
+ $0 -f $message_file "${repository}/${name}"
+ fi
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
+ cd ..
+ else # if not directory
+ if [ ! -f "$name" ]; then
+ echo "WARNING: $name is neither a regular file"
+ echo " nor a directory - ignored"
+ continue
+ fi
+ file="${update_dir}/${name},v"
+ comment=""
+ if grep -s '\$Log.*\$' "${name}"; then # If $Log keyword
+ myext=`echo $name | sed 's,.*\.,,'`
+ [ "$myext" = "$name" ] && myext=
+ case "$myext" in
+ c | csh | e | f | h | l | mac | me | mm | ms | p | r | red | s | sh | sl | cl | ml | el | tex | y | ye | yr | "" )
+ ;;
+
+ * )
+ echo "For file ${file}:"
+ grep '\$Log.*\$' "${name}"
+ echo -n "Please insert a comment leader for file ${name} > "
+ read comment
+ ;;
+ esac
+ fi
+ if [ ! -f "$file" ]; then # If not exists in repository
+ if [ ! -f "${update_dir}/Attic/${name},v" ]; then
+ echo "WARNING: Creating new file ${repository}/${name}"
+ if [ -f RCS/"${name}",v ]; then
+ echo "MSG: Copying old rcs file."
+ cp RCS/"${name}",v "$file"
+ else
+ if [ -n "${comment}" ]; then
+ rcs -q -i -c"${comment}" -t${message_file} -m'.' "$file"
+ fi
+ ci -q -u1.1 -t${message_file} -m'.' "$file"
+ if [ $? -ne 0 ]; then
+ echo "ERROR: Initial check-in of $file failed - aborting" >&2
+ exit 1
+ fi
+ fi
+ else
+ file="${update_dir}/Attic/${name},v"
+ echo "WARNING: IGNORED: ${repository}/Attic/${name}"
+ continue
+ fi
+ else # File existed
+ echo "ERROR: File exists in repository: Ignored: $file"
+ continue
+ fi
+ fi
+done
+
+[ $got_one -eq 1 ] && rm -f $message_file
+
+exit 0
diff --git a/gnu/dist/cvs/contrib/rcs2log.sh b/gnu/dist/cvs/contrib/rcs2log.sh
new file mode 100644
index 00000000000..37cfed80a1a
--- /dev/null
+++ b/gnu/dist/cvs/contrib/rcs2log.sh
@@ -0,0 +1,677 @@
+#! /bin/sh
+
+# RCS to ChangeLog generator
+
+# Generate a change log prefix from RCS files (perhaps in the CVS repository)
+# and the ChangeLog (if any).
+# Output the new prefix to standard output.
+# You can edit this prefix by hand, and then prepend it to ChangeLog.
+
+# Ignore log entries that start with `#'.
+# Clump together log entries that start with `{topic} ',
+# where `topic' contains neither white space nor `}'.
+
+Help='The default FILEs are the files registered under the working directory.
+Options:
+
+ -c CHANGELOG Output a change log prefix to CHANGELOG (default ChangeLog).
+ -h HOSTNAME Use HOSTNAME in change log entries (default current host).
+ -i INDENT Indent change log lines by INDENT spaces (default 8).
+ -l LENGTH Try to limit log lines to LENGTH characters (default 79).
+ -R If no FILEs are given and RCS is used, recurse through working directory.
+ -r OPTION Pass OPTION to subsidiary log command.
+ -t TABWIDTH Tab stops are every TABWIDTH characters (default 8).
+ -u "LOGIN<tab>FULLNAME<tab>MAILADDR" Assume LOGIN has FULLNAME and MAILADDR.
+ -v Append RCS revision to file names in log lines.
+ --help Output help.
+ --version Output version number.
+
+Report bugs to <bug-gnu-emacs@gnu.org>.'
+
+Id='$Id: rcs2log.sh,v 1.1.1.1 2000/09/04 21:51:07 christos Exp $'
+
+# Copyright 1992, 93, 94, 95, 96, 97, 1998 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+Copyright='Copyright 1998 Free Software Foundation, Inc.
+This program comes with NO WARRANTY, to the extent permitted by law.
+You may redistribute copies of this program
+under the terms of the GNU General Public License.
+For more information about these matters, see the files named COPYING.
+Author: Paul Eggert <eggert@twinsun.com>'
+
+tab=' '
+nl='
+'
+
+# Parse options.
+
+# defaults
+: ${AWK=awk}
+: ${TMPDIR=/tmp}
+changelog=ChangeLog # change log file name
+datearg= # rlog date option
+hostname= # name of local host (if empty, will deduce it later)
+indent=8 # indent of log line
+length=79 # suggested max width of log line
+logins= # login names for people we know fullnames and mailaddrs of
+loginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets
+logTZ= # time zone for log dates (if empty, use local time)
+recursive= # t if we want recursive rlog
+revision= # t if we want revision numbers
+rlog_options= # options to pass to rlog
+tabwidth=8 # width of horizontal tab
+
+while :
+do
+ case $1 in
+ -c) changelog=${2?}; shift;;
+ -i) indent=${2?}; shift;;
+ -h) hostname=${2?}; shift;;
+ -l) length=${2?}; shift;;
+ -[nu]) # -n is obsolescent; it is replaced by -u.
+ case $1 in
+ -n) case ${2?}${3?}${4?} in
+ *"$tab"* | *"$nl"*)
+ echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed"
+ exit 1
+ esac
+ case $loginFullnameMailaddrs in
+ '') loginFullnameMailaddrs=$2$tab$3$tab$4;;
+ ?*) loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4
+ esac
+ shift; shift; shift;;
+ -u)
+ # If $2 is not tab-separated, use colon for separator.
+ case ${2?} in
+ *"$nl"*)
+ echo >&2 "$0: -u '$2': newlines not allowed"
+ exit 1;;
+ *"$tab"*)
+ t=$tab;;
+ *)
+ t=:
+ esac
+ case $2 in
+ *"$t"*"$t"*"$t"*)
+ echo >&2 "$0: -u '$2': too many fields"
+ exit 1;;
+ *"$t"*"$t"*)
+ ;;
+ *)
+ echo >&2 "$0: -u '$2': not enough fields"
+ exit 1
+ esac
+ case $loginFullnameMailaddrs in
+ '') loginFullnameMailaddrs=$2;;
+ ?*) loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2
+ esac
+ shift
+ esac
+ case $logins in
+ '') logins=$login;;
+ ?*) logins=$logins$nl$login
+ esac
+ ;;
+ -r)
+ case $rlog_options in
+ '') rlog_options=${2?};;
+ ?*) rlog_options=$rlog_options$nl${2?}
+ esac
+ shift;;
+ -R) recursive=t;;
+ -t) tabwidth=${2?}; shift;;
+ -v) revision=t;;
+ --version)
+ set $Id
+ rcs2logVersion=$3
+ echo >&2 "rcs2log (GNU Emacs) $rcs2logVersion$nl$Copyright"
+ exit 0;;
+ -*) echo >&2 "Usage: $0 [OPTION]... [FILE ...]$nl$Help"
+ case $1 in
+ --help) exit 0;;
+ *) exit 1
+ esac;;
+ *) break
+ esac
+ shift
+done
+
+month_data='
+ m[0]="Jan"; m[1]="Feb"; m[2]="Mar"
+ m[3]="Apr"; m[4]="May"; m[5]="Jun"
+ m[6]="Jul"; m[7]="Aug"; m[8]="Sep"
+ m[9]="Oct"; m[10]="Nov"; m[11]="Dec"
+'
+
+
+# Put rlog output into $rlogout.
+
+# If no rlog options are given,
+# log the revisions checked in since the first ChangeLog entry.
+# Since ChangeLog is only by date, some of these revisions may be duplicates of
+# what's already in ChangeLog; it's the user's responsibility to remove them.
+case $rlog_options in
+'')
+ if test -s "$changelog"
+ then
+ e='
+ /^[0-9]+-[0-9][0-9]-[0-9][0-9]/{
+ # ISO 8601 date
+ print $1
+ exit
+ }
+ /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
+ # old-fashioned date and time (Emacs 19.31 and earlier)
+ '"$month_data"'
+ year = $5
+ for (i=0; i<=11; i++) if (m[i] == $2) break
+ dd = $3
+ printf "%d-%02d-%02d\n", year, i+1, dd
+ exit
+ }
+ '
+ d=`$AWK "$e" <"$changelog"` || exit
+ case $d in
+ ?*) datearg="-d>$d"
+ esac
+ fi
+esac
+
+# Use TZ specified by ChangeLog local variable, if any.
+if test -s "$changelog"
+then
+ extractTZ='
+ /^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*"\([^"]*\)".*/{
+ s//\1/; p; q
+ }
+ /^.*change-log-time-zone-rule['"$tab"' ]*:['"$tab"' ]*t.*/{
+ s//UTC0/; p; q
+ }
+ '
+ logTZ=`tail "$changelog" | sed -n "$extractTZ"`
+ case $logTZ in
+ ?*) TZ=$logTZ; export TZ
+ esac
+fi
+
+# If CVS is in use, examine its repository, not the normal RCS files.
+if test ! -f CVS/Repository
+then
+ rlog=rlog
+ repository=
+else
+ rlog='cvs -q log'
+ repository=`sed 1q <CVS/Repository` || exit
+ test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit
+ case $CVSROOT in
+ *:/*)
+ # remote repository
+ ;;
+ *)
+ # local repository
+ case $repository in
+ /*) ;;
+ *) repository=${CVSROOT?}/$repository
+ esac
+ if test ! -d "$repository"
+ then
+ echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
+ exit 1
+ fi
+ esac
+fi
+
+# Use $rlog's -zLT option, if $rlog supports it.
+case `$rlog -zLT 2>&1` in
+*' option'*) ;;
+*)
+ case $rlog_options in
+ '') rlog_options=-zLT;;
+ ?*) rlog_options=-zLT$nl$rlog_options
+ esac
+esac
+
+# With no arguments, examine all files under the RCS directory.
+case $# in
+0)
+ case $repository in
+ '')
+ oldIFS=$IFS
+ IFS=$nl
+ case $recursive in
+ t)
+ RCSdirs=`find . -name RCS -type d -print`
+ filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||'
+ files=`
+ {
+ case $RCSdirs in
+ ?*) find $RCSdirs \
+ -type f \
+ ! -name '*_' \
+ ! -name ',*,' \
+ ! -name '.*_' \
+ ! -name .rcsfreeze.log \
+ ! -name .rcsfreeze.ver \
+ -print
+ esac
+ find . -name '*,v' -print
+ } |
+ sort -u |
+ sed "$filesFromRCSfiles"
+ `;;
+ *)
+ files=
+ for file in RCS/.* RCS/* .*,v *,v
+ do
+ case $file in
+ RCS/. | RCS/.. | RCS/,*, | RCS/*_) continue;;
+ RCS/.rcsfreeze.log | RCS/.rcsfreeze.ver) continue;;
+ RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue;;
+ RCS/*,v | RCS/.*,v) ;;
+ RCS/* | RCS/.*) test -f "$file" || continue
+ esac
+ case $files in
+ '') files=$file;;
+ ?*) files=$files$nl$file
+ esac
+ done
+ case $files in
+ '') exit 0
+ esac
+ esac
+ set x $files
+ shift
+ IFS=$oldIFS
+ esac
+esac
+
+llogout=$TMPDIR/rcs2log$$l
+rlogout=$TMPDIR/rcs2log$$r
+trap exit 1 2 13 15
+trap "rm -f $llogout $rlogout; exit 1" 0
+
+case $datearg in
+?*) $rlog $rlog_options "$datearg" ${1+"$@"} >$rlogout;;
+'') $rlog $rlog_options ${1+"$@"} >$rlogout
+esac || exit
+
+
+# Get the full name of each author the logs mention, and set initialize_fullname
+# to awk code that initializes the `fullname' awk associative array.
+# Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
+# you have to fix the resulting output by hand.
+
+initialize_fullname=
+initialize_mailaddr=
+
+case $loginFullnameMailaddrs in
+?*)
+ case $loginFullnameMailaddrs in
+ *\"* | *\\*)
+ sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
+$loginFullnameMailaddrs
+EOF
+ loginFullnameMailaddrs=`cat $llogout`
+ esac
+
+ oldIFS=$IFS
+ IFS=$nl
+ for loginFullnameMailaddr in $loginFullnameMailaddrs
+ do
+ case $loginFullnameMailaddr in
+ *"$tab"*) IFS=$tab;;
+ *) IFS=:
+ esac
+ set x $loginFullnameMailaddr
+ login=$2
+ fullname=$3
+ mailaddr=$4
+ initialize_fullname="$initialize_fullname
+ fullname[\"$login\"] = \"$fullname\""
+ initialize_mailaddr="$initialize_mailaddr
+ mailaddr[\"$login\"] = \"$mailaddr\""
+ done
+ IFS=$oldIFS
+esac
+
+case $llogout in
+?*) sort -u -o $llogout <<EOF || exit
+$logins
+EOF
+esac
+output_authors='/^date: / {
+ if ($2 ~ /^[0-9]*[-\/][0-9][0-9][-\/][0-9][0-9]$/ && $3 ~ /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9][-+0-9:]*;$/ && $4 == "author:" && $5 ~ /^[^;]*;$/) {
+ print substr($5, 1, length($5)-1)
+ }
+}'
+authors=`
+ $AWK "$output_authors" <$rlogout |
+ case $llogout in
+ '') sort -u;;
+ ?*) sort -u | comm -23 - $llogout
+ esac
+`
+case $authors in
+?*)
+ cat >$llogout <<EOF || exit
+$authors
+EOF
+ initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
+ initialize_author=`sed -e "$initialize_author_script" <$llogout`
+ awkscript='
+ BEGIN {
+ alphabet = "abcdefghijklmnopqrstuvwxyz"
+ ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ '"$initialize_author"'
+ }
+ {
+ if (author[$1]) {
+ fullname = $5
+ if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
+ # Remove the junk from fullnames like "0000-Admin(0000)".
+ fullname = substr(fullname, index(fullname, "-") + 1)
+ fullname = substr(fullname, 1, index(fullname, "(") - 1)
+ }
+ if (fullname ~ /,[^ ]/) {
+ # Some sites put comma-separated junk after the fullname.
+ # Remove it, but leave "Bill Gates, Jr" alone.
+ fullname = substr(fullname, 1, index(fullname, ",") - 1)
+ }
+ abbr = index(fullname, "&")
+ if (abbr) {
+ a = substr($1, 1, 1)
+ A = a
+ i = index(alphabet, a)
+ if (i) A = substr(ALPHABET, i, 1)
+ fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
+ }
+
+ # Quote quotes and backslashes properly in full names.
+ # Do not use gsub; traditional awk lacks it.
+ quoted = ""
+ rest = fullname
+ for (;;) {
+ p = index(rest, "\\")
+ q = index(rest, "\"")
+ if (p) {
+ if (q && q<p) p = q
+ } else {
+ if (!q) break
+ p = q
+ }
+ quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
+ rest = substr(rest, p+1)
+ }
+
+ printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
+ author[$1] = 0
+ }
+ }
+ '
+
+ initialize_fullname=`
+ {
+ (getent passwd $authors) ||
+ (
+ cat /etc/passwd
+ for author in $authors
+ do NIS_PATH= nismatch $author passwd.org_dir
+ done
+ ypmatch $authors passwd
+ )
+ } 2>/dev/null |
+ $AWK -F: "$awkscript"
+ `$initialize_fullname
+esac
+
+
+# Function to print a single log line.
+# We don't use awk functions, to stay compatible with old awk versions.
+# `Log' is the log message (with \n replaced by \001).
+# `files' contains the affected files.
+printlogline='{
+
+ # Following the GNU coding standards, rewrite
+ # * file: (function): comment
+ # to
+ # * file (function): comment
+ if (Log ~ /^\([^)]*\): /) {
+ i = index(Log, ")")
+ files = files " " substr(Log, 1, i)
+ Log = substr(Log, i+3)
+ }
+
+ # If "label: comment" is too long, break the line after the ":".
+ sep = " "
+ if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, SOH)) sep = "\n" indent_string
+
+ # Print the label.
+ printf "%s*%s:", indent_string, files
+
+ # Print each line of the log, transliterating \001 to \n.
+ while ((i = index(Log, SOH)) != 0) {
+ logline = substr(Log, 1, i-1)
+ if (logline ~ /[^'"$tab"' ]/) {
+ printf "%s%s\n", sep, logline
+ } else {
+ print ""
+ }
+ sep = indent_string
+ Log = substr(Log, i+1)
+ }
+}'
+
+# Pattern to match the `revision' line of rlog output.
+rlog_revision_pattern='^revision [0-9]+\.[0-9]+(\.[0-9]+\.[0-9]+)*(['"$tab"' ]+locked by: [^'"$tab"' $,.0-9:;@]*[^'"$tab"' $,:;@][^'"$tab"' $,.0-9:;@]*;)?['"$tab"' ]*$'
+
+case $hostname in
+'')
+ hostname=`(
+ hostname || uname -n || uuname -l || cat /etc/whoami
+ ) 2>/dev/null` || {
+ echo >&2 "$0: cannot deduce hostname"
+ exit 1
+ }
+
+ case $hostname in
+ *.*) ;;
+ *)
+ domainname=`(domainname) 2>/dev/null` &&
+ case $domainname in
+ *.*) hostname=$hostname.$domainname
+ esac
+ esac
+esac
+
+
+# Process the rlog output, generating ChangeLog style entries.
+
+# First, reformat the rlog output so that each line contains one log entry.
+# Transliterate \n to \001 so that multiline entries fit on a single line.
+# Discard irrelevant rlog output.
+$AWK <$rlogout '
+ BEGIN { repository = "'"$repository"'" }
+ /^RCS file:/ {
+ if (repository != "") {
+ filename = $3
+ if (substr(filename, 1, length(repository) + 1) == repository "/") {
+ filename = substr(filename, length(repository) + 2)
+ }
+ if (filename ~ /,v$/) {
+ filename = substr(filename, 1, length(filename) - 2)
+ }
+ if (filename ~ /(^|\/)Attic\/[^\/]*$/) {
+ i = length(filename)
+ while (substr(filename, i, 1) != "/") i--
+ filename = substr(filename, 1, i - 6) substr(filename, i + 1)
+ }
+ }
+ rev = "?"
+ }
+ /^Working file:/ { if (repository == "") filename = $3 }
+ /'"$rlog_revision_pattern"'/, /^(-----------*|===========*)$/ {
+ line = $0
+ if (line ~ /'"$rlog_revision_pattern"'/) {
+ rev = $2
+ next
+ }
+ if (line ~ /^date: [0-9][- +\/0-9:]*;/) {
+ date = $2
+ if (date ~ /\//) {
+ # This is a traditional RCS format date YYYY/MM/DD.
+ # Replace "/"s with "-"s to get ISO format.
+ newdate = ""
+ while ((i = index(date, "/")) != 0) {
+ newdate = newdate substr(date, 1, i-1) "-"
+ date = substr(date, i+1)
+ }
+ date = newdate date
+ }
+ time = substr($3, 1, length($3) - 1)
+ author = substr($5, 1, length($5)-1)
+ printf "%s %s %s %s %s %c", filename, rev, date, time, author, 1
+ rev = "?"
+ next
+ }
+ if (line ~ /^branches: /) { next }
+ if (line ~ /^(-----------*|===========*)$/) { print ""; next }
+ if (line == "Initial revision" || line ~ /^file .+ was initially added on branch .+\.$/) {
+ line = "New file."
+ }
+ printf "%s%c", line, 1
+ }
+' |
+
+# Now each line is of the form
+# FILENAME REVISION YYYY-MM-DD HH:MM:SS[+-TIMEZONE] AUTHOR \001LOG
+# where \001 stands for a carriage return,
+# and each line of the log is terminated by \001 instead of \n.
+# Sort the log entries, first by date+time (in reverse order),
+# then by author, then by log entry, and finally by file name and revision
+# (just in case).
+sort +2 -4r +4 +0 |
+
+# Finally, reformat the sorted log entries.
+$AWK '
+ BEGIN {
+ logTZ = "'"$logTZ"'"
+ revision = "'"$revision"'"
+
+ # Some awk variants do not understand "\001", so we have to
+ # put the char directly in the file.
+ SOH="" # <-- There is a single SOH (octal code 001) here.
+
+ # Initialize the fullname and mailaddr associative arrays.
+ '"$initialize_fullname"'
+ '"$initialize_mailaddr"'
+
+ # Initialize indent string.
+ indent_string = ""
+ i = '"$indent"'
+ if (0 < '"$tabwidth"')
+ for (; '"$tabwidth"' <= i; i -= '"$tabwidth"')
+ indent_string = indent_string "\t"
+ while (1 <= i--)
+ indent_string = indent_string " "
+ }
+
+ {
+ newlog = substr($0, 1 + index($0, SOH))
+
+ # Ignore log entries prefixed by "#".
+ if (newlog ~ /^#/) { next }
+
+ if (Log != newlog || date != $3 || author != $5) {
+
+ # The previous log and this log differ.
+
+ # Print the old log.
+ if (date != "") '"$printlogline"'
+
+ # Logs that begin with "{clumpname} " should be grouped together,
+ # and the clumpname should be removed.
+ # Extract the new clumpname from the log header,
+ # and use it to decide whether to output a blank line.
+ newclumpname = ""
+ sep = "\n"
+ if (date == "") sep = ""
+ if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
+ i = index(newlog, "}")
+ newclumpname = substr(newlog, 1, i)
+ while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
+ newlog = substr(newlog, i+1)
+ if (clumpname == newclumpname) sep = ""
+ }
+ printf sep
+ clumpname = newclumpname
+
+ # Get ready for the next log.
+ Log = newlog
+ if (files != "")
+ for (i in filesknown)
+ filesknown[i] = 0
+ files = ""
+ }
+ if (date != $3 || author != $5) {
+ # The previous date+author and this date+author differ.
+ # Print the new one.
+ date = $3
+ time = $4
+ author = $5
+
+ zone = ""
+ if (logTZ && ((i = index(time, "-")) || (i = index(time, "+"))))
+ zone = " " substr(time, i)
+
+ # Print "date[ timezone] fullname <email address>".
+ # Get fullname and email address from associative arrays;
+ # default to author and author@hostname if not in arrays.
+ if (fullname[author])
+ auth = fullname[author]
+ else
+ auth = author
+ printf "%s%s %s ", date, zone, auth
+ if (mailaddr[author])
+ printf "<%s>\n\n", mailaddr[author]
+ else
+ printf "<%s@%s>\n\n", author, "'"$hostname"'"
+ }
+ if (! filesknown[$1]) {
+ filesknown[$1] = 1
+ if (files == "") files = " " $1
+ else files = files ", " $1
+ if (revision && $2 != "?") files = files " " $2
+ }
+ }
+ END {
+ # Print the last log.
+ if (date != "") {
+ '"$printlogline"'
+ printf "\n"
+ }
+ }
+' &&
+
+
+# Exit successfully.
+
+exec rm -f $llogout $rlogout
+
+# Local Variables:
+# tab-width:4
+# End:
diff --git a/gnu/dist/cvs/contrib/rcs2sccs.sh b/gnu/dist/cvs/contrib/rcs2sccs.sh
new file mode 100644
index 00000000000..b089dfef065
--- /dev/null
+++ b/gnu/dist/cvs/contrib/rcs2sccs.sh
@@ -0,0 +1,141 @@
+#! /bin/sh
+#
+#
+
+############################################################
+# Error checking
+#
+if [ ! -d SCCS ] ; then
+ mkdir SCCS
+fi
+
+logfile=/tmp/rcs2sccs_$$_log
+rm -f $logfile
+tmpfile=/tmp/rcs2sccs_$$_tmp
+rm -f $tmpfile
+emptyfile=/tmp/rcs2sccs_$$_empty
+echo -n "" > $emptyfile
+initialfile=/tmp/rcs2sccs_$$_init
+echo "Initial revision" > $initialfile
+sedfile=/tmp/rcs2sccs_$$_sed
+rm -f $sedfile
+revfile=/tmp/rcs2sccs_$$_rev
+rm -f $revfile
+commentfile=/tmp/rcs2sccs_$$_comment
+rm -f $commentfile
+
+# create the sed script
+cat > $sedfile << EOF
+s,;Id;,%Z%%M% %I% %E%,g
+s,;SunId;,%Z%%M% %I% %E%,g
+s,;RCSfile;,%M%,g
+s,;Revision;,%I%,g
+s,;Date;,%E%,g
+s,;Id:.*;,%Z%%M% %I% %E%,g
+s,;SunId:.*;,%Z%%M% %I% %E%,g
+s,;RCSfile:.*;,%M%,g
+s,;Revision:.*;,%I%,g
+s,;Date:.*;,%E%,g
+EOF
+sed -e 's/;/\\$/g' $sedfile > $tmpfile
+cp $tmpfile $sedfile
+############################################################
+# Loop over every RCS file in RCS dir
+#
+for vfile in *,v; do
+ # get rid of the ",v" at the end of the name
+ file=`echo $vfile | sed -e 's/,v$//'`
+
+ # work on each rev of that file in ascending order
+ firsttime=1
+ rlog $file | grep "^revision [0-9][0-9]*\." | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile
+ for rev in `cat $revfile`; do
+ if [ $? != 0 ]; then
+ echo ERROR - revision
+ exit
+ fi
+ # get file into current dir and get stats
+ date=`rlog -r$rev $file | grep "^date: " | awk '{print $2; exit}' | sed -e 's/^19//'`
+ time=`rlog -r$rev $file | grep "^date: " | awk '{print $3; exit}' | sed -e 's/;//'`
+ author=`rlog -r$rev $file | grep "^date: " | awk '{print $5; exit}' | sed -e 's/;//'`
+ date="$date $time"
+ echo ""
+ rlog -r$rev $file | sed -e '/^branches: /d' -e '1,/^date: /d' -e '/^===========/d' -e 's/$/\\/' | awk '{if ((total += length($0) + 1) < 510) print $0}' > $commentfile
+ echo "==> file $file, rev=$rev, date=$date, author=$author"
+ rm -f $file
+ co -r$rev $file >> $logfile 2>&1
+ if [ $? != 0 ]; then
+ echo ERROR - co
+ exit
+ fi
+ echo checked out of RCS
+
+ # add SCCS keywords in place of RCS keywords
+ sed -f $sedfile $file > $tmpfile
+ if [ $? != 0 ]; then
+ echo ERROR - sed
+ exit
+ fi
+ echo performed keyword substitutions
+ rm -f $file
+ cp $tmpfile $file
+
+ # check file into SCCS
+ if [ "$firsttime" = "1" ]; then
+ firsttime=0
+ echo about to do sccs admin
+ echo sccs admin -n -i$file $file < $commentfile
+ sccs admin -n -i$file $file < $commentfile >> $logfile 2>&1
+ if [ $? != 0 ]; then
+ echo ERROR - sccs admin
+ exit
+ fi
+ echo initial rev checked into SCCS
+ else
+ case $rev in
+ *.*.*.*)
+ brev=`echo $rev | sed -e 's/\.[0-9]*$//'`
+ sccs admin -fb $file 2>>$logfile
+ echo sccs get -e -p -r$brev $file
+ sccs get -e -p -r$brev $file >/dev/null 2>>$logfile
+ ;;
+ *)
+ echo sccs get -e -p $file
+ sccs get -e -p $file >/dev/null 2>> $logfile
+ ;;
+ esac
+ if [ $? != 0 ]; then
+ echo ERROR - sccs get
+ exit
+ fi
+ sccs delta $file < $commentfile >> $logfile 2>&1
+ if [ $? != 0 ]; then
+ echo ERROR - sccs delta -r$rev $file
+ exit
+ fi
+ echo checked into SCCS
+ fi
+ sed -e "s;^d D $rev ../../.. ..:..:.. [^ ][^ ]*;d D $rev $date $author;" SCCS/s.$file > $tmpfile
+ rm -f SCCS/s.$file
+ cp $tmpfile SCCS/s.$file
+ chmod 444 SCCS/s.$file
+ sccs admin -z $file
+ if [ $? != 0 ]; then
+ echo ERROR - sccs admin -z
+ exit
+ fi
+ done
+ rm -f $file
+done
+
+
+############################################################
+# Clean up
+#
+echo cleaning up...
+rm -f $tmpfile $emptyfile $initialfile $sedfile $commentfile
+echo ===================================================
+echo " Conversion Completed Successfully"
+echo ===================================================
+
+rm -f *,v
diff --git a/gnu/dist/cvs/contrib/rcslock.pl b/gnu/dist/cvs/contrib/rcslock.pl
new file mode 100644
index 00000000000..01e349ff025
--- /dev/null
+++ b/gnu/dist/cvs/contrib/rcslock.pl
@@ -0,0 +1,235 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+
+# Author: John Rouillard (rouilj@cs.umb.edu)
+# Supported: Yeah right. (Well what do you expect for 2 hours work?)
+# Blame-to: rouilj@cs.umb.edu
+# Complaints to: Anybody except Brian Berliner, he's blameless for
+# this script.
+# Acknowlegements: The base code for this script has been acquired
+# from the log.pl script.
+
+# rcslock.pl - A program to prevent commits when a file to be ckecked
+# in is locked in the repository.
+
+# There are times when you need exclusive access to a file. This
+# often occurs when binaries are checked into the repository, since
+# cvs's (actually rcs's) text based merging mechanism won't work. This
+# script allows you to use the rcs lock mechanism (rcs -l) to make
+# sure that no changes to a repository are able to be committed if
+# those changes would result in a locked file being changed.
+
+# WARNING:
+# This script will work only if locking is set to strict.
+#
+
+# Setup:
+# Add the following line to the commitinfo file:
+
+# ALL /local/location/for/script/lockcheck [options]
+
+# Where ALL is replaced by any suitable regular expression.
+# Options are -v for verbose info, or -d for debugging info.
+# The %s will provide the repository directory name and the names of
+# all changed files.
+
+# Use:
+# When a developer needs exclusive access to a version of a file, s/he
+# should use "rcs -l" in the repository tree to lock the version they
+# are working on. CVS will automagically release the lock when the
+# commit is performed.
+
+# Method:
+# An "rlog -h" is exec'ed to give info on all about to be
+# committed files. This (header) information is parsed to determine
+# if any locks are outstanding and what versions of the file are
+# locked. This filename, version number info is used to index an
+# associative array. All of the files to be committed are checked to
+# see if any locks are outstanding. If locks are outstanding, the
+# version number of the current file (taken from the CVS/Entries
+# subdirectory) is used in the key to determine if that version is
+# locked. If the file being checked in is locked by the person doing
+# the checkin, the commit is allowed, but if the lock is held on that
+# version of a file by another person, the commit is not allowed.
+
+$ext = ",v"; # The extension on your rcs files.
+
+$\="\n"; # I hate having to put \n's at the end of my print statements
+$,=' '; # Spaces should occur between arguments to print when printed
+
+# turn off setgid
+#
+$) = $(;
+
+#
+# parse command line arguments
+#
+require 'getopts.pl';
+
+&Getopts("vd"); # verbose or debugging
+
+# Verbose is useful when debugging
+$opt_v = $opt_d if defined $opt_d;
+
+# $files[0] is really the name of the subdirectory.
+# @files = split(/ /,$ARGV[0]);
+@files = @ARGV[0..$#ARGV];
+$cvsroot = $ENV{'CVSROOT'};
+
+#
+# get login name
+#
+$login = getlogin || (getpwuid($<))[0] || "nobody";
+
+#
+# save the current directory since we have to return here to parse the
+# CVS/Entries file if a lock is found.
+#
+$pwd = `/bin/pwd`;
+chop $pwd;
+
+print "Starting directory is $pwd" if defined $opt_d ;
+
+#
+# cd to the repository directory and check on the files.
+#
+print "Checking directory ", $files[0] if defined $opt_v ;
+
+if ( $files[0] =~ /^\// )
+{
+ print "Directory path is $files[0]" if defined $opt_d ;
+ chdir $files[0] || die "Can't change to repository directory $files[0]" ;
+}
+else
+{
+ print "Directory path is $cvsroot/$files[0]" if defined $opt_d ;
+ chdir ($cvsroot . "/" . $files[0]) ||
+ die "Can't change to repository directory $files[0] in $cvsroot" ;
+}
+
+
+# Open the rlog process and apss all of the file names to that one
+# process to cut down on exec overhead. This may backfire if there
+# are too many files for the system buffer to handle, but if there are
+# that many files, chances are that the cvs repository is not set up
+# cleanly.
+
+print "opening rlog -h @files[1..$#files] |" if defined $opt_d;
+
+open( RLOG, "rlog -h @files[1..$#files] |") || die "Can't run rlog command" ;
+
+# Create the locks associative array. The elements in the array are
+# of two types:
+#
+# The name of the RCS file with a value of the total number of locks found
+# for that file,
+# or
+#
+# The name of the rcs file concatenated with the version number of the lock.
+# The value of this element is the name of the locker.
+
+# The regular expressions used to split the rcs info may have to be changed.
+# The current ones work for rcs 5.6.
+
+$lock = 0;
+
+while (<RLOG>)
+{
+ chop;
+ next if /^$/; # ditch blank lines
+
+ if ( $_ =~ /^RCS file: (.*)$/ )
+ {
+ $curfile = $1;
+ next;
+ }
+
+ if ( $_ =~ /^locks: strict$/ )
+ {
+ $lock = 1 ;
+ next;
+ }
+
+ if ( $lock )
+ {
+ # access list: is the line immediately following the list of locks.
+ if ( /^access list:/ )
+ { # we are done getting lock info for this file.
+ $lock = 0;
+ }
+ else
+ { # We are accumulating lock info.
+
+ # increment the lock count
+ $locks{$curfile}++;
+ # save the info on the version that is locked. $2 is the
+ # version number $1 is the name of the locker.
+ $locks{"$curfile" . "$2"} = $1
+ if /[ ]*([a-zA-Z._]*): ([0-9.]*)$/;
+
+ print "lock by $1 found on $curfile version $2" if defined $opt_d;
+
+ }
+ }
+}
+
+# Lets go back to the starting directory and see if any locked files
+# are ones we are interested in.
+
+chdir $pwd;
+
+# fo all of the file names (remember $files[0] is the directory name
+foreach $i (@files[1..$#files])
+{
+ if ( defined $locks{$i . $ext} )
+ { # well the file has at least one lock outstanding
+
+ # find the base version number of our file
+ &parse_cvs_entry($i,*entry);
+
+ # is our version of this file locked?
+ if ( defined $locks{$i . $ext . $entry{"version"}} )
+ { # if so, it is by us?
+ if ( $login ne ($by = $locks{$i . $ext . $entry{"version"}}) )
+ {# crud somebody else has it locked.
+ $outstanding_lock++ ;
+ print "$by has file $i locked for version " , $entry{"version"};
+ }
+ else
+ { # yeah I have it locked.
+ print "You have a lock on file $i for version " , $entry{"version"}
+ if defined $opt_v;
+ }
+ }
+ }
+}
+
+exit $outstanding_lock;
+
+
+### End of main program
+
+sub parse_cvs_entry
+{ # a very simple minded hack at parsing an entries file.
+local ( $file, *entry ) = @_;
+local ( @pp );
+
+
+open(ENTRIES, "< CVS/Entries") || die "Can't open entries file";
+
+while (<ENTRIES>)
+ {
+ if ( $_ =~ /^\/$file\// )
+ {
+ @pp = split('/');
+
+ $entry{"name"} = $pp[1];
+ $entry{"version"} = $pp[2];
+ $entry{"dates"} = $pp[3];
+ $entry{"name"} = $pp[4];
+ $entry{"name"} = $pp[5];
+ $entry{"sticky"} = $pp[6];
+ return;
+ }
+ }
+}
diff --git a/gnu/dist/cvs/contrib/sccs2rcs.csh b/gnu/dist/cvs/contrib/sccs2rcs.csh
new file mode 100644
index 00000000000..a1dea01b19e
--- /dev/null
+++ b/gnu/dist/cvs/contrib/sccs2rcs.csh
@@ -0,0 +1,275 @@
+#! xCSH_PATHx -f
+#
+# Sccs2rcs is a script to convert an existing SCCS
+# history into an RCS history without losing any of
+# the information contained therein.
+# It has been tested under the following OS's:
+# SunOS 3.5, 4.0.3, 4.1
+# Ultrix-32 2.0, 3.1
+#
+# Things to note:
+# + It will NOT delete or alter your ./SCCS history under any circumstances.
+#
+# + Run in a directory where ./SCCS exists and where you can
+# create ./RCS
+#
+# + /usr/local/bin is put in front of the default path.
+# (SCCS under Ultrix is set-uid sccs, bad bad bad, so
+# /usr/local/bin/sccs here fixes that)
+#
+# + Date, time, author, comments, branches, are all preserved.
+#
+# + If a command fails somewhere in the middle, it bombs with
+# a message -- remove what it's done so far and try again.
+# "rm -rf RCS; sccs unedit `sccs tell`; sccs clean"
+# There is no recovery and exit is far from graceful.
+# If a particular module is hanging you up, consider
+# doing it separately; move it from the current area so that
+# the next run will have a better chance or working.
+# Also (for the brave only) you might consider hacking
+# the s-file for simpler problems: I've successfully changed
+# the date of a delta to be in sync, then run "sccs admin -z"
+# on the thing.
+#
+# + After everything finishes, ./SCCS will be moved to ./old-SCCS.
+#
+# This file may be copied, processed, hacked, mutilated, and
+# even destroyed as long as you don't tell anyone you wrote it.
+#
+# Ken Cox
+# Viewlogic Systems, Inc.
+# kenstir@viewlogic.com
+# ...!harvard!cg-atla!viewlog!kenstir
+#
+# Various hacks made by Brian Berliner before inclusion in CVS contrib area.
+
+
+#we'll assume the user set up the path correctly
+# for the Pmax, /usr/ucb/sccs is suid sccs, what a pain
+# /usr/local/bin/sccs should override /usr/ucb/sccs there
+set path = (/usr/local/bin $path)
+
+
+############################################################
+# Error checking
+#
+if (! -w .) then
+ echo "Error: ./ not writeable by you."
+ exit 1
+endif
+if (! -d SCCS) then
+ echo "Error: ./SCCS directory not found."
+ exit 1
+endif
+set edits = (`sccs tell`)
+if ($#edits) then
+ echo "Error: $#edits file(s) out for edit...clean up before converting."
+ exit 1
+endif
+if (-d RCS) then
+ echo "Warning: RCS directory exists"
+ if (`ls -a RCS | wc -l` > 2) then
+ echo "Error: RCS directory not empty
+ exit 1
+ endif
+else
+ mkdir RCS
+endif
+
+sccs clean
+
+set logfile = /tmp/sccs2rcs_$$_log
+rm -f $logfile
+set tmpfile = /tmp/sccs2rcs_$$_tmp
+rm -f $tmpfile
+set emptyfile = /tmp/sccs2rcs_$$_empty
+echo -n "" > $emptyfile
+set initialfile = /tmp/sccs2rcs_$$_init
+echo "Initial revision" > $initialfile
+set sedfile = /tmp/sccs2rcs_$$_sed
+rm -f $sedfile
+set revfile = /tmp/sccs2rcs_$$_rev
+rm -f $revfile
+
+# the quotes surround the dollar signs to fool RCS when I check in this script
+set sccs_keywords = (\
+ '%W%[ ]*%G%'\
+ '%W%[ ]*%E%'\
+ '%W%'\
+ '%Z%%M%[ ]*%I%[ ]*%G%'\
+ '%Z%%M%[ ]*%I%[ ]*%E%'\
+ '%M%[ ]*%I%[ ]*%G%'\
+ '%M%[ ]*%I%[ ]*%E%'\
+ '%M%'\
+ '%I%'\
+ '%G%'\
+ '%E%'\
+ '%U%')
+set rcs_keywords = (\
+ '$'Id'$'\
+ '$'Id'$'\
+ '$'Id'$'\
+ '$'SunId'$'\
+ '$'SunId'$'\
+ '$'Id'$'\
+ '$'Id'$'\
+ '$'RCSfile'$'\
+ '$'Revision'$'\
+ '$'Date'$'\
+ '$'Date'$'\
+ '')
+
+
+############################################################
+# Get some answers from user
+#
+echo ""
+echo "Do you want to be prompted for a description of each"
+echo "file as it is checked in to RCS initially?"
+echo -n "(y=prompt for description, n=null description) [y] ?"
+set ans = $<
+if ((_$ans == _) || (_$ans == _y) || (_$ans == _Y)) then
+ set nodesc = 0
+else
+ set nodesc = 1
+endif
+echo ""
+echo "The default keyword substitutions are as follows and are"
+echo "applied in the order specified:"
+set i = 1
+while ($i <= $#sccs_keywords)
+# echo ' '\"$sccs_keywords[$i]\"' ==> '\"$rcs_keywords[$i]\"
+ echo " $sccs_keywords[$i] ==> $rcs_keywords[$i]"
+ @ i = $i + 1
+end
+echo ""
+echo -n "Do you want to change them [n] ?"
+set ans = $<
+if ((_$ans != _) && (_$ans != _n) && (_$ans != _N)) then
+ echo "You can't always get what you want."
+ echo "Edit this script file and change the variables:"
+ echo ' $sccs_keywords'
+ echo ' $rcs_keywords'
+else
+ echo "good idea."
+endif
+
+# create the sed script
+set i = 1
+while ($i <= $#sccs_keywords)
+ echo "s,$sccs_keywords[$i],$rcs_keywords[$i],g" >> $sedfile
+ @ i = $i + 1
+end
+
+onintr ERROR
+
+############################################################
+# Loop over every s-file in SCCS dir
+#
+foreach sfile (SCCS/s.*)
+ # get rid of the "s." at the beginning of the name
+ set file = `echo $sfile:t | sed -e "s/^..//"`
+
+ # work on each rev of that file in ascending order
+ set firsttime = 1
+ sccs prs $file | grep "^D " | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile
+ foreach rev (`cat $revfile`)
+ if ($status != 0) goto ERROR
+
+ # get file into current dir and get stats
+ set date = `sccs prs -r$rev $file | grep "^D " | awk '{printf("19%s %s", $3, $4); exit}'`
+ set author = `sccs prs -r$rev $file | grep "^D " | awk '{print $5; exit}'`
+ echo ""
+ echo "==> file $file, rev=$rev, date=$date, author=$author"
+ sccs edit -r$rev $file >>& $logfile
+ if ($status != 0) goto ERROR
+ echo checked out of SCCS
+
+ # add RCS keywords in place of SCCS keywords
+ sed -f $sedfile $file > $tmpfile
+ if ($status != 0) goto ERROR
+ echo performed keyword substitutions
+ cp $tmpfile $file
+
+ # check file into RCS
+ if ($firsttime) then
+ set firsttime = 0
+ if ($nodesc) then
+ echo about to do ci
+ echo ci -f -r$rev -d"$date" -w$author -t$emptyfile $file
+ ci -f -r$rev -d"$date" -w$author -t$emptyfile $file < $initialfile >>& $logfile
+ if ($status != 0) goto ERROR
+ echo initial rev checked into RCS without description
+ else
+ echo ""
+ echo Enter a brief description of the file $file \(end w/ Ctrl-D\):
+ cat > $tmpfile
+ ci -f -r$rev -d"$date" -w$author -t$tmpfile $file < $initialfile >>& $logfile
+ if ($status != 0) goto ERROR
+ echo initial rev checked into RCS
+ endif
+ else
+ # get RCS lock
+ set lckrev = `echo $rev | sed -e 's/\.[0-9]*$//'`
+ if ("$lckrev" =~ [0-9]*.*) then
+ # need to lock the brach -- it is OK if the lock fails
+ rcs -l$lckrev $file >>& $logfile
+ else
+ # need to lock the trunk -- must succeed
+ rcs -l $file >>& $logfile
+ if ($status != 0) goto ERROR
+ endif
+ echo got lock
+ sccs prs -r$rev $file | grep "." > $tmpfile
+ # it's OK if grep fails here and gives status == 1
+ # put the delta message in $tmpfile
+ ed $tmpfile >>& $logfile <<EOF
+/COMMENTS
+1,.d
+w
+q
+EOF
+ ci -f -r$rev -d"$date" -w$author $file < $tmpfile >>& $logfile
+ if ($status != 0) goto ERROR
+ echo checked into RCS
+ endif
+ sccs unedit $file >>& $logfile
+ if ($status != 0) goto ERROR
+ end
+ rm -f $file
+end
+
+
+############################################################
+# Clean up
+#
+echo cleaning up...
+mv SCCS old-SCCS
+rm -f $tmpfile $emptyfile $initialfile $sedfile
+echo ===================================================
+echo " Conversion Completed Successfully"
+echo ""
+echo " SCCS history now in old-SCCS/"
+echo ===================================================
+set exitval = 0
+goto cleanup
+
+ERROR:
+foreach f (`sccs tell`)
+ sccs unedit $f
+end
+echo ""
+echo ""
+echo Danger\! Danger\!
+echo Some command exited with a non-zero exit status.
+echo Log file exists in $logfile.
+echo ""
+echo Incomplete history in ./RCS -- remove it
+echo Original unchanged history in ./SCCS
+set exitval = 1
+
+cleanup:
+# leave log file
+rm -f $tmpfile $emptyfile $initialfile $sedfile $revfile
+
+exit $exitval
diff --git a/gnu/dist/cvs/doc/.cvsignore b/gnu/dist/cvs/doc/.cvsignore
new file mode 100644
index 00000000000..ecbac0e8a4f
--- /dev/null
+++ b/gnu/dist/cvs/doc/.cvsignore
@@ -0,0 +1,34 @@
+*.dvi
+Makefile
+CVSvn.texi
+cvs-paper.ps
+cvs.aux
+cvs.cp
+cvs.cps
+cvs.dvi
+cvs.fn
+cvs.fns
+cvs.info
+cvs.info-*
+cvs.ky
+cvs.log
+cvs.pg
+cvs.ps
+cvs.toc
+cvs.tp
+cvs.vr
+cvsclient.info
+cvsclient.aux
+cvsclient.cp
+cvsclient.cps
+cvsclient.dvi
+cvsclient.fn
+cvsclient.info
+cvsclient.info-*
+cvsclient.ky
+cvsclient.log
+cvsclient.pg
+cvsclient.ps
+cvsclient.toc
+cvsclient.tp
+cvsclient.vr
diff --git a/gnu/dist/cvs/doc/ChangeLog b/gnu/dist/cvs/doc/ChangeLog
new file mode 100644
index 00000000000..d0faacc739a
--- /dev/null
+++ b/gnu/dist/cvs/doc/ChangeLog
@@ -0,0 +1,3365 @@
+2000-01-11 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo: Add index entries for "Compression" and "Gzip".
+ Correct typography in many index entries (English phrases should
+ have initial caps, subcommands/files/etc. should be as-is).
+
+2000-01-10 Karl Fogel <kfogel@red-bean.com>
+
+ * cvs.texinfo (loginfo): correctly describe CVSROOT/loginfo's
+ %-expansion behavior. Thanks to Karl Heinz Marbaise
+ <kama@hippo.fido.de> for noticing the error.
+
+2000-01-07 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo (Password authentication server): Use -f in example
+ inetd.conf line.
+ (Connection): Add advice about using shell script or env to avoid
+ problems with inetd setting HOME in the server's environment.
+ (various): Use @file for inetd.conf.
+
+2000-01-02 John P Cavanaugh <cavanaug@sr.hp.com>
+
+ * cvs.texinfo: document new -C option to update, now that it works
+ both remotely and locally.
+ (Re-applied by Karl Fogel <kfogel@red-bean.com>.)
+
+1999-12-11 Karl Fogel <kfogel@red-bean.com>
+
+ * Revert previous change -- it doesn't work remotely yet.
+
+1999-12-10 John P Cavanaugh <cavanaug@sr.hp.com>
+
+ * cvs.texinfo: document new -C option to update.
+ (Applied by Karl Fogel <kfogel@red-bean.com>.)
+
+1999-11-20 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo(history options): Document -f, -n, and -z.
+
+1999-11-09 Jim Kingdon <http://developer.redhat.com/>
+
+ * cvsclient.texi (Requests): Document the arguments to "log", now
+ that I've changed log.c to be more specific in terms of what it
+ will send.
+
+1999-11-05 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo: Revert Karl's change once again since the code is now
+ fixed. Add "Variables" and "User variables" to index.
+
+1999-11-04 Karl Fogel <kfogel@red-bean.com>
+
+ * log.c (log_usage): Revert Jim Kingdon's reversion of my change
+ of 1999-11-03. Allowing a space between option and argument
+ results in lossage; here is a reproduction recipe: run this from
+ the top of a remote copy of the cvs source tree
+
+ cvs log -d '>1999-03-01' > log-out.with-space
+
+ and then run this (note there's no space after -d now):
+
+ cvs log -d'>1999-03-01' > log-out.no-space
+
+ The resulting files differ; furthermore, a glance at the output of
+ cvs shows that the first command failed to recurse into
+ subdirectories. Until this misbehavior can be fixed in the source
+ code, the documentation should reflect the true state of affairs:
+ if one simply omits the space, everything works fine.
+
+1999-11-04 Jim Kingdon <http://developer.redhat.com/>
+
+ * cvs.texinfo (log options): Revert Karl's change regarding -d and
+ -s. A space is allowed (see sanity.sh for example).
+
+ * cvs.texinfo (Password authentication server): The name of the
+ file is "passwd" not "password".
+
+ * cvsclient.texi (Top): Add @dircategory and @direntry.
+
+1999-11-04 Karl Fogel <kfogel@red-bean.com>
+
+ * cvs.texinfo (Password authentication server, Password
+ authentication client): Rewritten to accommodate the [new]
+ possibility of empty passwords.
+
+1999-11-03 Karl Fogel <kfogel@red-bean.com>
+
+ * cvs.texinfo (Invoking CVS): correct documentation for -d and -s
+ options (as did elsewhere, earlier today).
+
+1999-11-03 Karl Fogel <kfogel@red-bean.com>
+
+ * cvs.texinfo (Setting a watch): describe `watch off' behavior
+ more accurately.
+
+1999-11-03 Karl Fogel <kfogel@red-bean.com>
+
+ * cvs.texinfo (log options): correct documentation for -d and -s
+ options. There can be no space between these options and their
+ arguments.
+
+ Also, make sure all @sc{cvs} codes refer to "cvs" in lower case;
+ this avoids makeinfo warnings. And use @code for the CVSEDITOR
+ environment variable, not @sc.
+
+1999-09-24 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo: Misc. formatting cleanups.
+
+1999-07-16 Tom Tromey <tromey@cygnus.com>
+
+ * cvs.texinfo (admin): Mention admin -k exception. Add cvsadmin
+ to index.
+
+1999-07-14 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo (Password authentication server): Note inetd limits
+ and suggest using shell script to avoid.
+
+1999-06-01 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Requests): For the import command, the
+ repository given to the Directory requests is ignored.
+
+1999-05-27 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Requests): Clarify that Modified, Is-modified,
+ Notify and Unchanged must specify a file within the current
+ directory.
+
+1999-05-24 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (checkoutlist): New node, contains more complete
+ documentation of this feature.
+ (CVSROOT storage): Refer to the new node when mentioning
+ checkoutlist.
+ (Administrative files): Update the menu entry for Wrappers.
+
+1999-05-17 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Requests): For Notify request, strike duplicate
+ "Response expected: no" and fix "a edit" -> "an edit".
+
+1999-05-14 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): Try to be more clear
+ about the conflict field.
+
+1999-05-11 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (config): Use comma after @xref (thanks to Pavel
+ Roskin for the report/fix).
+
+1999-05-10 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Requests): Document restrictions on characters
+ in Notify requests.
+
+1999-05-04 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Password authentication security): Remove sentence
+ about how no one has audited pserver for holes; a lot of holes
+ have been closed, looking for, &c, since that was written.
+ In the summary, reword to reflect the fact that sniffing a
+ readonly password does not imply general system access (as far as
+ I know, of course).
+
+ * cvs.texinfo (Connection): Also suggest inetd -d.
+
+1999-04-28 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Requests): Say what goes in the "watches" field
+ of the "Notify" request.
+
+ * cvs.texinfo (Common options): -r is for branches too.
+
+ * cvs.texinfo (Error messages): Add "no such tag" message.
+ (Common options): -f does not override val-tags check.
+
+1999-04-26 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Locks): #cvs.rfl locks must start with "#cvs.rfl."
+ not just "#cvs.rfl". As far as I know CVS has always implemented
+ the former behavior, and this just fixes the documentation.
+
+1999-04-23 Yoshiki Hayashi of u-tokyo.ac.jp
+
+ * cvs.texinfo (verifymsg): Correct wrong file name (bugid.edit ->
+ bugid.verify).
+
+1999-04-22 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Responses): The text in the "M" response is not
+ designed for machine parsing. Likewise for "error" in regular
+ protocol. Likewise for "E" and "error" in authentication protocol.
+
+1999-04-19 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "Cannot check out files into
+ the repository itself".
+
+1999-04-16 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Other problems): Add the Windows problem with home
+ directory ending in a slash.
+
+1999-04-14 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (CVS in repository): Include the format of the
+ fileattr file here, rather than referring to the CVS source code.
+
+1999-04-09 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): Whether the timestamp
+ in CVS/Entries is local or universal actually depends on the system.
+
+1999-04-05 Derek Price
+ <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+ * cvs.texinfo (export options): Remove notation that the -r
+ tag is sticky. 'cvs export' doesn't store that data.
+
+1999-04-08 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "EOF in RCS file" and
+ "unexpected EOF" (in RCS file) messages.
+
+1999-03-25 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (admin options): Say there can be no space between
+ -e and its argument (since the previous sentence said the argument
+ can be omitted, this is the only possibility).
+
+1999-02-26 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Merging and keywords): When including conflict
+ markers, put @asis{} at the start of the line, in case this file
+ itself is in CVS. Thanks to Derek Price for pointing this out.
+
+1999-02-25 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo: Refer to "keywords" not "RCS keywords". We had
+ only used the latter term in a few places, and it seems like a
+ somewhat odd term in that this style of keyword is by no means
+ specific to RCS.
+ (Merging a branch): Remove spurious ")". Use ref, not xref, after
+ "see".
+ (Merging a branch, Substitution modes): Make sure that @ref is
+ followed by comma, since info wants that.
+ (Merging and keywords): Use samp not code for "-kk". Something of
+ a judgement call, but the rest of the manual uses samp and that
+ seems better to me.
+ (Merging and keywords): Rewrite, to (a) better motivate the
+ discussion based on what the user wants to do, (b) fix up lots of
+ convoluted sentences, (c) move the discussion of the binary files
+ to the end, that is get across the basic idea first and then
+ embellish it. Remove a few unnecessary index entries. Expand
+ example. Just tell people to avoid -kk with binary files (comment
+ out the discussion of using -A after the commit).
+
+1999-01-29 Derek Price
+ <http://www-personal.engin.umich.edu/~oberon/resume.html>
+
+ * cvs.texinfo: Added new node/section on merging and keywords. It
+ contains advice on how to avoid RCS keyword conflicts when merging
+ and avoid corrupting your binary files while doing it.
+
+1999-02-24 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Request intro): Add paragraph about transmitting
+ more than one command.
+
+1999-01-29 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo: Use EXAMPLE.COM EXAMPLE.ORG and EXAMPLE.NET instead
+ of domains which might conflict with actual (current or future)
+ domains. The EXAMPLE domains are registered for this purpose.
+
+1999-01-22 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Sticky tags): Refer to -j as the better way to undo
+ a change.
+ (Merging two revisions): Also talk about undoing removals and
+ adds. Move the index entries to here.
+
+1999-01-21 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "waiting for USER's lock".
+
+1999-01-16 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Comment out all the -t/-f documentation,
+ since that feature is currently disabled.
+
+1999-01-14 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs.texinfo (Connecting via rsh): Add some more index entries so
+ that people who want to use SSH and such are slightly less lost.
+
+1999-01-12 Jim Kingdon <http://www.cyclic.com>
+
+ * cvs-paper.ms: Remove comments which contained the FSF's old
+ address; it has changed.
+
+1998-12-29 Jim Kingdon <http://www.cyclic.com>
+
+ * cvsclient.texi (Dates): Numeric timezones are preferred.
+ Also mention the Checkin-time request.
+
+1998-12-23 Jim Kingdon <http://www.cyclic.com>
+
+ * RCSFILES: Add clarification about certain character set issues
+ from Paul Eggert, the RCS maintainer. The last paragraph and the
+ change from Shift-JIS to JIS as an example of a character set
+ which contains 0x40 bytes which are not '@' characters are mine;
+ the rest is directly from Paul Eggert.
+
+1998-12-22 Martin Buchholz <martin@xemacs.org>
+
+ * cvs.texinfo: Fixed various trivial typos.
+
+1998-12-17 Jim Kingdon
+
+ * cvsclient.texi (Responses): Explicitly say that Mod-time need
+ not be sent for all files.
+
+1998-12-16 Jim Kingdon
+
+ Thanks to Ram Rajadhyaksha of the MacCVS Pro team for raising the
+ following issues.
+ * cvs.texinfo (Working directory storage): The deal about storing
+ files as text files applies to all the CVS/* files, not just
+ CVS/Entries. State the rationale too.
+ Document CVSROOT/Emptydir in CVS/Repository.
+ There is no set order in CVS/Entries.
+ Explicitly say that writing Entries.Log is optional.
+
+1998-12-03 Jim Kingdon
+
+ * cvs.texinfo (Error messages): Add "unrecognized auth response".
+ (Password authentication server): Remove comment about
+ "unrecognized auth response" and link to the troubleshooting
+ section.
+
+1998-12-02 Jim Kingdon
+
+ * cvs.texinfo (Multiple repositories): Add an example.
+
+1998-11-18 Jim Kingdon
+
+ * cvs.texinfo (Invoking CVS): Change "-r tag" to "-r rev". We
+ already use "tag" as the name of the tag we are adding.
+
+1998-11-13 Jim Kingdon
+
+ * cvs.texinfo (CVS commands): Add comment about whether part of
+ the manual should be organized by command.
+
+1998-11-06 Jim Kingdon
+
+ Clean up various confusions between modules and directories:
+ * cvs.texinfo: In "are you sure you want to release" message,
+ change module to directory. CVS was changed some time ago.
+ (Tags): "working copy of the module" -> "working directory".
+ (Merging two revisions): Remove unnecessary text "that make up a
+ module".
+ (Recursive behavior): Change "module" to "directory".
+ (Removing files): Likewise.
+ (Tracking sources): Remove "a module" from titles.
+ (Moving directories): Change "module" to "parent-dir".
+ (Inside): Remove "of the module".
+ (Inside): Change "module" to "dir".
+ (Rename by copying): Change "module" to "dir".
+ (Rename by copying): Remove "of the module".
+ (Moving directories): "copy of the module" -> "checked out copy of
+ the directory"; remove second "of the module". Change "check out
+ the module" to " check out again".
+ (Moving directories): Remove "of the module".
+ (Keyword substitution): "your working copy of a module" -> "a
+ working directory".
+ (CVS commands): Change "module" to "directory".
+ (release examples): "module" -> "tc directory".
+ (commitinfo): "relative path to the module" -> "directory in the
+ repository".
+ (verifymsg): Change "module" to "directory".
+ (Updating a file): "working copy of a module" -> "working directory".
+
+1998-10-25 Jim Kingdon
+
+ * cvs.texinfo (Branches and revisions): Fix error in branch
+ numbering which was introduced with change of 4 May 1997.
+
+1998-10-20 Jim Kingdon
+
+ * cvs.texinfo (Tags): Point to Invoking CVS node so people aren't
+ left wondering what the syntax is. When introducing -r option,
+ warn people about sticky tags right off.
+ (Tagging the working directory, Tagging by date/tag, Modifying
+ tags, Tagging add/remove): New sections.
+ (Invoking CVS): Adjust tag and rtag to point to the new sections,
+ and to add tag -c which had been omitted. Delete tag -n; there is
+ no such option.
+ (rtag, tag): Removed; no longer needed.
+ (commit examples): Update xref.
+
+1998-10-15 Jim Kingdon
+
+ * cvsclient.texi (Requests): It is OK to send Set before Root.
+
+1998-10-13 Jim Kingdon
+
+ * cvsclient.texi (Protocol Notes): Remove item about "cvs update"
+ sending modified files to the server; there are some better ideas
+ at http://www.cyclic.com/cvs/dev-update.txt
+ Add mention of www.cyclic.com.
+
+1998-09-30 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Committing your changes, Environment variables):
+ Document VISUAL.
+
+1998-09-27 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication server): Say explicitly
+ that you edit passwd directly, many users get confused by this.
+
+1998-09-24 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Connecting via fork): :fork: may be of interest to
+ users, for example those who prefer CVS to prompt for one log
+ message per checkin, rather than one per directory.
+ (Connecting via fork): Document CVS_SERVER.
+
+1998-09-24 Noel Cragg <noel@swish.red-bean.com>
+
+ * cvs.texinfo (Connecting via fork): new node about the fork
+ access method.
+
+1998-09-22 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Environment variables): Document
+ CVS_IGNORE_REMOTE_ROOT in the CVS 1.10 context.
+ (Moving a repository): Update comments concerning surgery on
+ CVS/Root and CVS/Repository files.
+
+1998-09-21 Noel Cragg <noel@swish.red-bean.com>
+
+ * cvs.texinfo (Environment variables): remove information about
+ CVS_IGNORE_REMOTE_ROOT, since it's no longer used.
+
+1998-09-21 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (config): Mention that CVS 1.10 doesn't have
+ LockDir.
+
+1998-09-18 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keyword list): Describe $Name and checking out with
+ a revision.
+
+1998-09-16 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: RFC2346 is out; update comment.
+
+1998-09-13 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keyword list, Substitution modes): In describing
+ $Locker and -kkvl, refer to cvs admin -l.
+
+ * cvsclient.texi (Requests): Re-word description of Sticky to
+ allow room for "Ntagname" (or other, future, values).
+
+ * cvs.texinfo (tag): Remove confusing wording about supplying
+ revision numbers "implicitly".
+
+1998-09-10 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (rdiff options): Thanks to the diff library, -u is
+ supported regardless of your diff program.
+
+1998-09-07 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (config): Add LockDir.
+
+1998-09-01 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): "Directory" and "Argument" are
+ requests, not commands. Likewise for "other-request". A command,
+ roughly, is a request that uses "Argument"s, but we might want to
+ phase out the use of that term more so than codify it, I'm not sure.
+
+1998-09-01 Noel Cragg <noel@swish.red-bean.com>
+
+ * cvsclient.texi (Requests): added a detailed explanation of the
+ Directory request and how it is handled, both for pre-1.10 and
+ post-1.10 servers.
+
+1998-09-01 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Multiple repositories): Also describe the CVS 1.10
+ behavior. Looking at a mismatched version of the manual seems to
+ be a reasonably common occurrence.
+
+ * cvs.texinfo (Environment variables): Revert change regarding
+ CVS_SERVER_SLEEP*; having that kind of debugging code in the main
+ CVS is getting out of hand.
+
+1998-09-01 Noel Cragg <noel@swish.red-bean.com>
+
+ * cvs.texinfo (Multiple repositories): brief mention that cvs now
+ handles a working directory composed of multiple repositories.
+ (Environment variables): add note about CVS_SERVER_SLEEP2.
+
+1998-08-21 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvsclient.texi (Text tags): Document importmergecmd tag.
+
+1998-08-20 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): Replace out of date URL concerning
+ ISO8601 dates with a more general statement and a few comments.
+
+1998-08-18 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Add "Checkin-time" request.
+
+Sun Jul 26 02:42:20 1998 Noel Cragg <noel@swish.red-bean.com>
+
+ * cvs.texinfo (config): TopLevelAdmin variable.
+
+ * cvsclient.texi (Requests): fix typo.
+
+1998-07-14 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): "remove" is like "add" in the sense
+ that it is the "ci" request which does most of the work.
+
+1998-06-23 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Excluding directories): Fix order of
+ "!first-dir/sdir" and "first-dir" to match what CVS actually
+ accepts. Reported by Tim McIntosh of sterling.com.
+
+1998-06-09 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Using keywords): Rewrite to be less specific to
+ source code in C. The old text was worse than that; it was
+ specific to certain versions of GCC (not even current GCC's, I
+ don't think) (reported most recently by Mitchell Perilstein;
+ if memory serves by others before that).
+
+1998-06-08 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Concurrency): Also mention #cvs.lock. Don't
+ mention #cvs.tfl; it is quite old (before CVS 1.5).
+ (Locks, Backing up, Concurrency): Add more index entries.
+
+1998-06-03 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (Tracking sources): Clarify that the vendor branch
+ is only made the head revision when you import a new file, not any
+ time you import a file.
+
+1998-05-23 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (What is CVS?): info-cvs-request is now at gnu.org
+ and is no longer handled by a human (hallelujah).
+
+1998-05-12 Jim Meyering <meyering@ascend.com>
+
+ * cvs.texinfo: Add an info dir entry.
+ Remove trailing white space.
+
+1998-05-05 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Be more explicit that -m 'COPY' has no
+ effect on binary files.
+
+1998-05-02 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * RCSFILES: Add more discussion of the order of the revisions.
+
+1998-04-27 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (loginfo example): Also give example of sending
+ mail. Use internal variable $USER rather than expecting CVS to
+ set the environment variable $USER. Change unnecessary 'sed'
+ invocation to 'cat' (it suffered from the same problem in terms of
+ internal variables versus environment variables).
+
+ * cvs.texinfo (Error messages): Add "conflict: removed FILE was
+ modified by second party".
+
+1998-04-20 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): Update comment about meaning of
+ HEAD in cvs diff.
+
+1998-04-12 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Dates): Also mention log -d.
+
+ * cvs.texinfo (Invoking CVS): No space is allowed between -r or -w
+ and its argument, for the log command.
+
+1998-04-11 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Dates): New section, explaining the deal with
+ date formats.
+
+1998-04-09 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Global options, Invoking CVS): Fix typo
+ ("files files" -> "files").
+ (Invoking CVS): Make -q and -Q more concise.
+ (Invoking CVS): Use @var for metavariables in "diff -r".
+
+1998-03-17 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (~/.cvsrc): In example, put "checkout" rather than
+ "co" into .cvsrc; we just finished explaining that only the former
+ works! Thanks to Lenny Foner for reporting this.
+
+ * cvs.texinfo (Copying): Remove this node. This basically
+ restores the status quo prior to 18 Oct 1996 (before then the node
+ existed but was empty).
+ (before Top): Adjust copyright notice accordingly.
+
+1998-03-12 Tim Pierce <twp@skepsis.com>
+
+ * RCSFILES: Updated description of `hardlinks' newphrases.
+
+1998-03-07 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Tags, Sticky tags, Creating a branch, Accessing
+ branches): Rename release-0-1 tag to rel-0-1 and likewise for
+ release-0-1-patches and release-0-4. This fixes an overfull hbox.
+ (diff options): Reformat table to fix underfull hboxes and such.
+
+1998-03-07 Tim Pierce <twp@skepsis.com>
+
+ * cvs.texinfo (Editing files, Special Files): Document hardlinks.
+ Various cleanups to PreservePermissions text.
+ * RCSFILES: Document PreservePermissions newphrases.
+
+1998-03-04 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Special Files): Add notes about client/server CVS
+ and hard links across directories.
+
+1998-03-01 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keeping a checked out copy): The magic loginfo
+ incantation isn't too likely to work except on unix.
+
+1998-02-23 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (user-defined logging): Double "@" literal.
+
+1998-02-18 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (user-defined logging): Add taginfo example.
+
+1998-02-04 Tim Pierce <twp@skepsis.com>
+
+ * cvs.texinfo (config): PreservePermissions variable.
+ (Special Files): New.
+ (Editing files): Add note about PreservePermissions.
+
+Tue Feb 10 18:07:35 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Connection): New node.
+
+ * cvsclient.texi (Protocol): Fix typo (lots -> lost).
+
+Sun Feb 8 21:39:22 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Invoking CVS): For admin -b, point to the section
+ where we talk about reverting to vendor branch.
+
+ * cvs.texinfo (Invoking CVS, rdiff options): Document rdiff -V
+ option as obsolete, since it was made a fatal error some time ago.
+
+ * cvs.texinfo (Invoking CVS): Add global options, keywords, and
+ keyword substitution modes. Wording fix in reference to --help
+ and Index.
+
+Wed Jan 28 23:09:39 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Excluding directories): Add index entry for "!".
+
+28 Jan 1998 Karl Fogel and Jim Kingdon
+
+ * cvsclient.texi (Requests, Responses): document
+ "wrapper-sendme-rcsOptions" and "Wrapper-rcsOption".
+
+Tue Jan 27 18:37:37 1998 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (Excluding directories): New node, documenting how
+ to exclude directories using ! in an alias module.
+
+Sun Jan 18 18:23:02 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Add Kopt request.
+
+Thu Jan 1 17:36:42 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (BUGS, Credits): Change @unnumbered to @appendix now
+ that these are moved from the start to the end.
+
+Sat Dec 27 10:06:56 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "Too many arguments!".
+
+Fri Dec 26 18:30:26 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (What is CVS?): Just point to the two canonical web
+ sites (Pascal Molli and Cyclic) concerning CVS downloads. The GNU
+ URL was out of date and GNU only has source distributions anyway.
+
+ * cvs.texinfo: Change bug-cvs address to gnu.org per email from
+ Martin Hamilton.
+
+Tue Dec 23 18:04:09 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Sticky tags): Further cleanups. Fix thinko
+ (s/subsequent cvs update/& commands/). Remove "vi driver.c" and
+ commit from example (totally vestigial). Reword start of
+ paragraph on non-branch sticky tags, so that it better alludes
+ to branch sticky tags. When introducing sticky tags, make it
+ clear that even people who aren't trying to use sticky tags
+ may need to know how to avoid them. Restore comment about
+ CVS/Tag files.
+ (Accessing branches): Don't xref to merging here; that is a much
+ more advanced topic and the "but see" wording didn't tell us what
+ to see the xref about.
+
+Tue Dec 23 14:39:08 1997 Karl Fogel <kfogel@floss.red-bean.com>
+ and Jim Kingdon
+
+ * cvs.texinfo (Creating a branch): Rewritten. Introduce with
+ `tag', then discuss `rtag' and `-r'.
+
+Tue Dec 23 10:03:37 1997 Karl Fogel <kfogel@floss.red-bean.com>
+ and Jim Kingdon
+
+ * cvs.texinfo: Changes to dehairify the "Sticky tags" situation:
+ (Revisions): "Sticky tags" moved here, description in menu changed
+ to be a little more informative.
+ (Sticky tags): Moved from "Branching and merging" to "Revisions".
+ (Accessing branches): New node in "Branching and merging",
+ explains how to use checkout vs update to retrieve a branch.
+ Text and example inherited from "Sticky tags", but text mostly
+ rewritten.
+ (Sticky tags): Moved under "Revisions", rewritten somewhat (more
+ rewrites to follow).
+ Don't use "-v" in "cvs status" example.
+
+Mon Dec 22 11:46:05 1997 Karl Fogel <kfogel@floss.red-bean.com>
+ and Jim Kingdon
+
+ Cleanups related to recent separation of revisions from
+ branching/merging:
+ * cvs.texinfo (Revisions): Take paragraph introducing branches,
+ rewrite it and move it to "Branching and merging".
+ (Branching and merging): Also rewrite merging intro.
+ (Revision numbers): Don't go into detail about branch revision
+ numbers here, just mention that they happen and refer to new
+ node "Branches and revisions".
+ (Branches and revisions): New node under "Branching and merging",
+ inherits text from "Revision numbers".
+ (Creating a branch): Refer to "Branches and revisions" now, not
+ "Revision numbers".
+ (Binary why): Rewrite sentence which refers to merging, so that
+ it isn't specific to branch merging.
+ (Branches motivation): Fix typo (select -> elect). Add comment
+ about what this node is accomplishing, in general.
+
+Sun Dec 21 20:57:24 1997 Karl Fogel <kfogel@floss.red-bean.com>
+ and Jim Kingdon
+
+ This is just moving text; related cleanups to follow.
+ * cvs.texinfo: Changes to put branching and merging together, and
+ keep it all separate from revisions:
+ (Revisions): Renamed from "Revisions and branches".
+ (Branching and merging): Renamed from "Merging".
+ (Branches motivation, Creating a branch, Sticky tags, Magic branch
+ numbers): these subnodes moved to "Branching and merging" from
+ "Revisions".
+ everywhere: Adjusted cross-references to cope with above.
+
+Sun Dec 21 20:36:39 1997 Karl Fogel <kfogel@floss.red-bean.com>
+ and Jim Kingdon
+
+ Note that this is just moving text, not changing it:
+ * cvs.texinfo: divide top-level menu into sections.
+ (Multiple developers, Builds, Tracking sources, Keyword
+ substitution): moved to be in "CVS and the Real World" section.
+ (Compatibility): moved to be in "References" section.
+
+Mon Dec 22 08:54:31 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Example): In comment, in citing the BNF style
+ used in many RFCs, cite RFC2234 not RFC822 (now that the former is
+ out).
+
+Sun Dec 21 17:42:22 1997 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * cvs.texinfo (Overview): New node.
+ (What is CVS?, A sample session): Put under Overview.
+ (What is CVS not?): New node under Overview.
+ [text previously was part of "What is CVS?" -kingdon]
+ (Preface): Removed this node and its contents.
+ (Checklist): Removed this node and its contents.
+ (Credits): Now toward end of top-level menu (was under Preface).
+ (BUGS): Now toward end of top-level menu (was under Preface).
+
+Sun Dec 14 10:14:25 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Add MT response.
+ (Text tags): New node.
+
+ * cvs.texinfo (loginfo): Add comment about which commands run
+ loginfo.
+
+Sat Dec 13 08:41:13 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Connection and Authentication): State that
+ GSSAPI is preferred to kserver. Try to be clearer about what
+ the term "pserver" means. Introduce GSSAPI and cite the relevant
+ RFCs. Discuss the limitations of the existing features in
+ preventing hijacking.
+
+ * cvs.texinfo (GSSAPI authenticated, Kerberos authenticated):
+ Briefly introduce what GSSAPI and Kerberos are. Be slightly more
+ emphatic about protecting against downgrade attacks.
+
+Fri Dec 12 17:36:46 1997 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (GSSAPI authenticated): New node.
+ (Global options): Document -a. Mention GSSAPI in -x
+ documentation.
+ * cvsclient.texi (Connection and Authentication): Document GSSAPI
+ authentication.
+ (Requests): Add Gssapi-encrypt and Gssapi-authenticate.
+
+Fri Dec 12 09:27:38 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (cvsignore): Add note about comments and the
+ space-separated nature of the syntax.
+
+Sun Dec 7 09:33:11 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (checkout): Clarify issues regarding updating
+ existing working directories.
+
+Sun Nov 30 20:38:17 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Add comment: we don't document %s.
+
+Mon Nov 24 23:00:09 1997 Karl Fogel <kfogel@floss.red-bean.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi: Move Protocol Notes node to the end.
+
+ * cvsclient.texi (Request intro): new node/section.
+ (Protocol): added some introductory material.
+ Rearranged menu into General Conventions, Protocol specification,
+ and Example etc sections.
+ (File Modes): replaces Modes, for consistency.
+
+Sat Nov 22 12:29:58 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Entries Lines): Clarify options in entries line.
+
+Tue Nov 18 09:23:15 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Be more explicit about "export" and
+ entries lines.
+
+ * Makefile.in (DISTFILES): Remove DIFFUTILS-2.7-BUG.
+
+Mon Nov 17 18:20:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (tag options): Expand comment with reference to FAQ.
+
+Fri Nov 14 11:02:37 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Update discussion of "dying gasps".
+
+ * cvs.texinfo (tag options): Add FIXME comment about renaming tags.
+
+Thu Nov 13 10:20:39 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): Remove also has a -f option with a
+ different meaning than most.
+
+Wed Nov 12 21:57:40 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (File permissions, Connecting via rsh, Environment
+ variables): When putting an environment variable in the index, say
+ it is an environment variable. Don't index the same name twice.
+
+ * cvs.texinfo: Many edits to reflect the fact that CVS no longer
+ invokes external RCS programs.
+
+Tue Nov 11 15:15:49 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Locks, CVS in repository): New nodes, document the
+ locking scheme and briefly outline CVS and CVS/fileattr.
+
+Sun Nov 9 17:39:41 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * DIFFUTILS-2.7-BUG: Removed; the bug is fixed and the testcases
+ are incorporated into sanity.sh.
+
+Sat Nov 8 09:49:38 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Binary why): Try to be a little clearer about how
+ merges fit into CVS. Say it may be error prone to have developers
+ doing merges manually.
+
+Tue Nov 4 13:02:22 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (admin options): Add discussion of what happens if
+ there are tags.
+
+Fri Oct 31 00:04:09 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (admin options): Rewrite discussion of -o to
+ hopefully be clearer and to also document the new :: syntax.
+ (admin examples): Removed; incorporated into admin options.
+ (Invoking CVS): Wording fix for admin -o.
+
+ * cvs.texinfo (Binary why): New node, talks about diff and merge.
+ (Binary howto): Renamed from Binary files.
+ (Binary files): Now just contains an introduction.
+
+ * cvs.texinfo (Error messages): Add "could not merge" message. In
+ discussion of "Binary files . . . differ" message, mention that
+ this is only an issue with old verisons of CVS.
+
+Thu Oct 30 15:55:21 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "authorization failed" message.
+
+Wed Oct 29 11:52:05 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Remove fake RCSid; we decided to remove rcsid's a
+ while ago. Cleanups suggested by Stephen Gildea (CVSROOT/passwd
+ has 2 or 3 fields; /user -> /usr; noone -> no one; in used -> in
+ use). Add comment about making compilers happy about rcsids.
+
+Sat Oct 25 00:58:24 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * RCSFILES: rcsfile.5 is correct about {num} after next being
+ optional.
+
+Wed Oct 22 10:08:27 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add message about unrecognized
+ response from cvs server.
+
+1997-10-11 Noel Cragg <noel@swish.red-bean.com>
+
+ * cvs.texinfo (checkout options): describe how the `-d' and `-N'
+ flags really work. Give examples.
+ (export options): refer the reader to the descriptions for `-d'
+ and `-N' in checkout options, since the behavior is the same.
+
+Thu Oct 9 12:01:35 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (log options): Add comment about "cvs log -r".
+
+Wed Oct 8 10:24:19 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (rtag options): Add comment about how this is
+ confusing.
+
+Tue Sep 30 12:31:25 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): Add comment about
+ Entries.Static.
+
+Thu Sep 25 23:52:57 1997 Noel Cragg <noel@swish.red-bean.com>
+
+ * cvsclient.texi (Responses): description of Module-expansion was
+ missing a carriage return after the @item clause.
+
+Wed Sep 24 12:04:42 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Remote repositories): Add comment about pserver
+ vs. having users create their own repositories.
+
+Sat Sep 20 00:59:53 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keyword list): Change title from "RCS Keywords" to
+ "Keyword list" as it is CVS that expands them.
+ (Avoiding substitution): Change "rcs" to "cvs", in the context of
+ the program which expands keywords.
+
+Fri Sep 19 22:57:24 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * RCSFILES: Grammar fix in first paragraph. Re-word section on
+ dead newphrase. Add item about what it means if "expand" is omitted.
+
+ * cvs.texinfo (Magic branch numbers): Change example branch number
+ from 1.2.3 to 1.2.4; CVS assigns even branch numbers and I don't
+ think vendor branches are very relevant to this example.
+
+Wed Sep 17 17:21:33 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (admin options): Add comment about "cvs admin -b"
+ (with no argument to the -b).
+
+ * RCSFILES: "next" is optional, not required.
+
+Tue Sep 16 15:13:22 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Binary files): Add comment about another possible
+ way to auto-detect binary files.
+
+Sun Sep 14 12:38:56 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Conflicts example): Adjust text and comments
+ regarding conflict markers to reflect change in CVS.
+
+Wed Sep 10 12:44:04 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Server requirements): Add comment about server
+ disk usage in /tmp.
+
+ * cvs.texinfo (Common options): More comments about date formats:
+ "now", "yesterday", and the "3 weeks ago" family.
+
+Tue Sep 9 13:09:58 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * DIFFUTILS-2.7-BUG: Eggert patch is preferred to Rittle one.
+
+Sun Sep 7 18:38:23 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (history options): Revise -e to say that it includes
+ future record types (and remove out of date list of what record
+ types it implies).
+
+ * cvs.texinfo (Environment variables): Expand/correct discussion
+ of HOME, HOMEDRIVE, and HOMEPATH.
+ (Error messages): Add "could not find out home directory".
+
+ * cvs.texinfo (update options): Reword -r doc to hopefully be
+ clearer that it takes either numeric or symbolic revision.
+
+ * cvs.texinfo (syntax): Add comment about how regexp syntax may
+ be, er, creatively altered, by configure.in.
+
+Sat Sep 6 11:29:15 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): Document Baserev and
+ Baserev.tmp.
+ (Working directory storage): Adjust comment regarding CVS/* being
+ text files.
+
+Fri Sep 5 14:42:39 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (BUGS): Remove mention of unsupported resources page
+ on http://www.cyclic.com, as it might go away in a future
+ reorganization.
+
+ * DIFFUTILS-2.7-BUG: Further info from Eggert.
+
+1997-09-05 Paul Eggert <eggert@twinsun.com>
+
+ * DIFFUTILS-2.7-BUG: Explain how this bug will probably be
+ fixed in the next diffutils release.
+
+Thu Sep 4 17:09:57 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Binary files): Reword the section on what you need
+ to do with cvs admin -kb to hopefully be a bit clearer. Still not
+ ideal (see comment).
+
+ * cvs.texinfo (modules): Break node into separate nodes for alias
+ modules, regular modules, ampersand modules, and options. Expand
+ text with more examples and explanations. Add index entries.
+
+Wed Sep 3 14:49:43 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Multiple developers): Add idea about cvs editors
+ and reserved checkouts.
+
+Sun Aug 31 19:36:21 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Rewrite paragraph on cvs add on a
+ filename containing '/'.
+
+Thu Aug 28 14:13:50 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (diff options): Add comment about "cvs diff"
+ vs. "cvs diff -r HEAD".
+
+ * cvs.texinfo (Global options): Add comment about -w not
+ overriding cvs watch on.
+
+Wed Aug 27 08:09:31 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication server): Grammar fix ("under
+ as the username" -> "as the username").
+
+ * cvs.texinfo: Fix doubled 'the the' typos. Reported by
+ karlb@atg.com.
+
+Tue Aug 26 12:25:42 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Checklist): Reword xref to point to Binary files
+ rather than Keyword expansion. Credit goes to jeff@alum.mit.edu
+ (Jeff Breidenbach) for reporting the problem.
+
+Mon Aug 18 17:23:18 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (modules): Suggest taginfo instead of -t. Add
+ comment with some of the reasons. Add comment about -u and -i
+ problems.
+
+Sat Aug 16 10:19:06 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add note about how "could not
+ check out foo.c" seems to also have been observed on Irix.
+
+Fri Aug 15 17:28:01 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "could not check out foo.c".
+
+Thu Aug 14 23:57:53 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Document new -m 'COPY' behavior.
+
+Tue Aug 12 20:56:40 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Sticky tags): Add comment about how we should be
+ documenting sticky tags.
+
+Fri Aug 8 10:01:03 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (File status): Add comment about "working revision"
+ in cvs status for a locally removed file.
+
+Thu Aug 7 22:53:45 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (From other version control systems): Mention
+ pvcs_to_rcs alongside sccs2rcs.
+
+Tue Aug 5 17:22:50 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Compatibility): Add comment about how CVS probably
+ could be detecting the case of dead files killed by CVS 1.3.
+
+ * cvs.texinfo (From other version control systems): Add paragraph
+ about converting from systems which don't export RCS files.
+
+Sun Aug 3 21:03:14 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Cite RFC1321 for MD5.
+
+ * cvs.texinfo (A sample session): Nuke index entry for "A sample
+ session". The fact that this isn't "sample session" is totally
+ bogus, but in general the table of contents is probably better for
+ this entry.
+
+ * cvs.texinfo (Error messages): Add comment about wording of error
+ concerning unknown -x option.
+
+ * cvs.texinfo (Wrappers): Add comment about absolute filter pathname.
+
+Thu Jul 31 14:40:15 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Use @ref not @xref when reference is not at the
+ start of a sentence. Avoids capitalizing "See" when we shouldn't.
+ Fixes to other similar xref problems.
+
+Wed Jul 30 19:30:31 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Connection and Authentication): Don't use @samp
+ on BEGIN AUTH REQUEST and friends. Avoids overfull hbox.
+
+Fri Jul 25 10:40:22 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Remove obsolete sentence regarding
+ using Directory instead of Repository enabling alternate response
+ syntax.
+
+ * cvsclient.texi (Response intro): Add discussing of file updating
+ responses and file update modifying responses.
+ (Responses): Refer to this description rather than trying to
+ describe it in each place. The descriptions in each place were
+ somewhat incomplete and didn't get updated when new file updating
+ responses were added.
+
+ * cvsclient.texi: Split node Responses into Response intro,
+ Response pathnames, and Responses.
+
+Thu Jul 24 23:13:24 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (config): Document SystemAuth.
+ (Password authentication server): Mention SystemAuth.
+
+Mon Jul 21 08:57:04 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (DISTFILES): Add DIFFUTILS-2.7-BUG.
+
+Sun Jul 20 17:55:52 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (admin options): For options with optional
+ arguments, specify that there can be no space between the option
+ and its argument. For -N, add xref to Magic branch numbers. For
+ -t, talk about reading from stdin. Comment changes.
+
+Sat Jul 19 22:28:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Preface): Make section titles more verbose.
+ Likewise for the menu.
+
+Fri Jul 18 08:41:11 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): No need for an external patch if
+ server and client are current. Add comment with more thoughts
+ about messages specific to old versions of CVS.
+
+ * cvs.texinfo (Error messages): Add "cannot start server via rcmd".
+
+ * cvs.texinfo (Error messages): Add "cannot open CVS/Root" for cvs
+ init.
+
+ * cvs.texinfo (Error messages): Add "missing author".
+
+Tue Jul 15 16:47:08 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keyword list): Fix documentation of $Log to reflect
+ the fact that we no longer use the comment leader.
+ (admin options): Fix documentation of $Log.
+ (admin examples): Remove example concerning comment leader, since
+ the example no longer does what it claims to.
+ (admin, admin options): Fix various parts of the documentation to
+ not refer to this being implemented via RCS. Say nastier things
+ about -I and -x. Add comments about options to "rcs" which we
+ don't document.
+
+Mon Jul 14 00:04:32 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): The "cannot change permissions on
+ temporary directory" error has been happening in various test cases.
+
+Sat Jul 12 11:12:18 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Repository files): Further comments about leading
+ "-" in filenames.
+
+Fri Jul 11 21:30:11 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Repository files): Add comment about legal
+ filenames.
+
+Wed Jul 9 18:05:26 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Add Mbinary response.
+
+Mon Jul 7 12:04:01 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Goals): Add previously unwritten goal about only
+ one way to do each operation.
+
+ * cvs.texinfo (File permissions): Rewrite paragraph on setuid to
+ be more verbose and less unix-specific.
+
+Sat Jul 5 03:16:38 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Connection and Authentication): When we said to
+ "ignore" an unrecogized code we mean to treat it as nonspecific,
+ not to ignore the response.
+
+ * cvsclient.texi (Example): Refer to RFC2119 when referring to
+ terminology of MUST, SHALL, &c.
+
+ * cvs.texinfo (Windows permissions): New node.
+
+Fri Jul 4 15:27:43 1997 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (Common options): Fix typo (avaliable for
+ available).
+
+Tue Jul 1 09:19:02 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Server requirements): Discuss memory used by diff.
+
+ * cvs.texinfo (Substitution modes): Add comment about -A resetting
+ both sticky tags/dates and sticky options.
+
+ * cvs.texinfo (File permissions): Add paragraph concerning
+ ownership of the RCS files.
+
+ * cvs.texinfo (Working directory storage): Relative repositories
+ in CVS/Repository are legal.
+
+Mon Jun 30 10:48:21 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Top): Add menu item for Password scrambling.
+
+ * cvs.texinfo (Committing your changes): Add comment concerning
+ documentation of message prompting.
+
+Fri Jun 27 11:20:34 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Password scrambling): New node.
+ (Connection and Authentication): Adjust accordingly.
+ (Protocol Notes): Add long discussion of character sets and
+ password scrambling.
+
+ * cvs.texinfo (Repository files): Also mention doc/RCSFILES in
+ documenting RCS file format.
+ (CVSROOT, storage of files): New node.
+
+Thu Jun 26 09:18:15 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (File permissions): xref to the pserver thing about
+ permissions in CVSROOT.
+ (Kerberos authenticated): Explicitly mention kerberos rsh.
+ Add various index entries for "security, <foo>".
+
+Wed Jun 25 13:39:16 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): Rewrite comments concerning HEAD
+ and testcases and solution. Changing HEAD might be too big a
+ change; might be better to phase it out.
+ (Common options, Tags): Add index entries for HEAD and BASE.
+
+Tue Jun 24 09:37:26 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add assertion failed.
+
+ * cvsclient.texi (Connection and Authentication): Add "E" and
+ "error" as responses in authentication protocol. The server
+ already was in the (formerly bad) habit of sending them, and we
+ might as well implement this in the client and document it.
+
+ * cvs.texinfo (Password authentication security): Note about
+ permissions on $CVSROOT also applies to its parent and so on up to
+ /.
+
+Mon Jun 23 18:28:18 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Creating a repository): xref to Server requirements
+ for more details on memory, CPU.
+ (Server requirements): Add xref to Creating a repository regarding
+ disk space.
+
+ * cvs.texinfo (Read-only access, Password authentication
+ security): The known holes which let a read-only user execute
+ arbitrary programs on the server are gone.
+
+ * cvsclient.texi (Protocol Notes): Remove multisite item; it is
+ replaced by item 186 in TODO. Add a general reference to TODO.
+ Rewrite accordingly the sentence about multisite in the item
+ concerning sending modified files in "cvs update".
+
+Fri Jun 20 17:00:20 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "binary files differ" when
+ trying to check in a binary file.
+
+Fri Jun 20 14:01:23 1997 David J MacKenzie <djm@va.pubnix.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Fix various formatting, spelling, stylistic, and
+ factual errors.
+
+Thu Jun 19 07:11:33 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (config): New node.
+ (Password authentication server): Talk about RCSBIN in config as
+ an alternative to -b global option.
+ * cvsclient.texi (Requests): Specify when Root can/must be used.
+
+ * cvs.texinfo (Error messages): Add
+ "*PANIC* administration files missing".
+
+ * cvs.texinfo (Password authentication server): Mention
+ permissions on $CVSROOT and $CVSROOT/CVSROOT as part of the
+ installation process.
+ (Password authentication security): Clarify that permissions issue
+ applies to $CVSROOT as well as $CVSROOT/CVSROOT.
+
+Wed Jun 18 00:03:25 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication security): Add paragraph
+ on write permissions of $CVSROOT/CVSROOT.
+
+ * cvs.texinfo (Adding and removing): New node. Move Adding files,
+ Removing files, Removing directories, Moving files, and Moving
+ directories under it.
+
+ * cvs.texinfo (Removing directories): Add sentence about how
+ one doesn't remove the directory itself.
+
+ * cvs.texinfo (Password authentication server): Document
+ --allow-root.
+
+Tue Jun 17 09:58:03 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "unknown option" from RCS.
+
+Fri Jun 13 12:11:09 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Global options): Add note about how -n might affect
+ CVS's output.
+
+Thu Jun 12 09:33:40 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Other problems): New node. Add discussion of
+ problem with old rcsmerge.
+
+ * cvs.texinfo (Environment variables): Add CVSUMASK.
+
+Mon Jun 2 18:39:57 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Moving a repository): New node.
+
+Tue May 27 18:27:57 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): Add comment about
+ timestamps.
+ * cvsclient.texi (Responses): Add Mod-time.
+
+Mon May 26 10:04:32 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Add comment concerning -t/-f and
+ client/server.
+
+Sun May 25 00:08:39 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Multiple vendor branches): New node.
+ (First import, import options, Invoking CVS): xref to it.
+
+Sat May 24 23:47:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (File permissions): Add comment about group
+ ownership in repository and setgid bit on directories.
+
+Fri May 23 17:14:05 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * RCSFILES: Fix typo in dead newphrase description ("an" -> "a").
+
+Fri May 23 16:33:38 1997 Ian Lance Taylor <ian@cygnus.com>
+
+ * RCSFILES: Mention dead as a newphrase.
+
+Fri May 23 09:45:39 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Builds): In comment, update URL of mk.
+
+Thu May 22 09:25:56 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add comment about yet another way
+ to produce a "cannot open CVS/Entries for reading" error.
+
+Tue May 20 17:54:55 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add item about EINVAL in rename.
+
+Mon May 19 00:21:49 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keywords in imports): New node.
+ (Tracking sources): Add comment about what a "vendor" is.
+
+ * cvs.texinfo (Keyword substitution): Where it refers to RCS
+ having a certain behavior, rewrite to not pass the buck like
+ that. Saying "RCS file" is still OK; that is a legit CVS
+ concept. A few other minor edits.
+
+Sun May 18 10:24:57 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * RCSFILES: Add list of known newphrase extensions.
+
+ * cvs.texinfo (From other version control systems): Fix typo
+ ("systesm" -> "systems").
+
+ * cvs.texinfo (Exit status): New node.
+ (diff): Replace text on exit status with an xref to that node.
+ The previous text documented a behavior which CVS no longer
+ implements.
+ (user-defined logging, commitinfo, verifymsg, Error messages):
+ Add index entries for "exit status, of <something which CVS invokes>".
+
+ * cvs.texinfo (Administrative files): Add comment concerning
+ writing triggers and particularly performance issues.
+
+ * cvs.texinfo (rtag options, tag options): Don't discuss what old
+ versions did with respect to the behavior now controlled by -F; we
+ don't try to document old versions here. Add comments concerning
+ how -F should be documented. Add index entries for "renaming
+ tags" and such pointing to "tag -F".
+
+Wed May 14 12:16:19 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Binary files): Add text and comment about
+ automatically detecting binary files.
+
+Mon May 12 11:55:07 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Connection and Authentication): Add item about
+ future expansion.
+
+Thu May 8 11:08:34 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Update imports): Add comment about wdiff
+ vs. fsf/wdiff in example.
+
+Wed May 7 13:52:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (checkout): Add comment about need for example
+ regarding what the "module" argument means.
+
+Tue May 6 18:02:27 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (History browsing): Add comment about looking at old
+ revisions.
+
+Tue May 06 15:05:00 1997 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo: More additions/corrections for -R due to recent
+ changes.
+
+Mon Dec 16 15:18:00 1996 Larry Jones <larry.jones@sdrc.com>
+
+ * cvs.texinfo: Added/corrected documentation for -R. (Minor edits
+ by Jim Kingdon to reflect recent changes in cvs.texinfo)
+
+Sun May 4 14:38:35 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Compatibility): Add comment about "D" lines in
+ Entries.
+
+ * cvs.texinfo (CVS commands, diff): Change "run diffs" to "show
+ differences"; the former is jargon.
+ (CVS commands): Don't refer to "rlog" in describing what log does.
+
+ * Makefile.in (cvsclient.dvi cvsclient.aux): Run texi2dvi rather
+ than (poorly) emulating it ourself.
+
+ Fix overfull and underfull hboxes:
+ * cvs.texinfo (What is CVS?): Add words "the newsgroup" before
+ "comp.sources.unix".
+ (Credits): Put list of people in @display.
+ (Repository files): Put /usr/local/cvsroot in @example.
+ (Connecting via rsh): Change "anklet" to "toe" in example.
+ (Kerberos authenticated, Password authentication client, Password
+ authentication server): Change "brickyard" to "yard" in example.
+ (Read-only access): Use @example and refer to files with a shorter
+ pathname.
+ (Server temporary directory): Use @example for pathname.
+ (Watches Compatibility): Add phony line break.
+ (Revision numbers): Remove revision 1.2.2.2 and tighten up the
+ spacing for "the main trunk".
+ (Tags, Creating a branch): Change /usr/local/cvsroot to /u/cvsroot.
+ (Merging more than once): Tighten up spacing for "the main trunk".
+ (Recursive behavior): Put long command in @example.
+ (First import): Remove word "called".
+ (Common options): Put long URL in @example.
+ (loginfo example): Use fewer hyphens in example.
+ (Variables): Put long command name in @example.
+ (Copying): Add line break.
+ (Administrative files): Remove "the" from title.
+ (Copying): Change "@unnumberedsec" to two "@heading"s.
+ * cvsclient.texi (Requests): Change /home/kingdon/zwork/cvsroot to
+ /u/cvsroot.
+ (Example): Add word "file".
+ (Example): Change line breaks in example log message.
+ (Example): Change /home/kingdon/testing/cvsroot to /u/cvsroot.
+
+ * cvs.texinfo (Credits): Don't refer to appendix A and B, they
+ have been renumbered. Reword so that it works whether the text in
+ question has since been rewritten or not.
+
+ * cvs.texinfo (BUGS): Rewrite to reflect the many different ways
+ that one might want to handle bugs. Move information on Signum
+ and Cyclic from Preface to here. Remove information on known
+ deficiencies in the manual (some of them I'm not sure were really
+ things in need of improvement; others were too general to be
+ useful). For the most part FIXME comments are probably better for
+ this. Remove "Linkoping, October 1993, Per Cederqvist"--many
+ parts of the manual are now from other people, dates, and places.
+ (What is CVS): For the most part, just refer to BUGS concerning
+ bug-cvs. Also tell people how to subscribe to bug-cvs.
+ (Credits): Say that list is not comprehensive and refer to
+ ChangeLog.
+
+Sat May 3 10:51:58 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (rcsinfo): Add comment about checkoutlist and
+ related topics.
+
+ * cvs.texinfo (Server temporary directory): New node.
+
+ * cvs.texinfo (Backing up): New node.
+
+ * cvs.texinfo (Repository): Be more explicit about the repository
+ and the working directory not being subdirectories of each other.
+
+Mon Apr 28 11:12:56 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Removing files): Use "*.c" not "?.c" in example;
+ the former should be good for both unix and DOS-like operating
+ systems. Document -f option. Refer to Invoking CVS for a full
+ list of options. Add a few comments.
+
+ * cvs.texinfo (Invoking CVS): For checkout and update, call them
+ "sticky options" not "sticky kopts".
+
+ * cvs.texinfo (Editing files): Add additional comments on get
+ vs. checkout.
+
+Sun Apr 27 16:17:06 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (commit): Only document the current flags (where -f
+ is force and -F file gets the message from a log file). We had
+ partly made this change on 9 Feb 1997, but some places got missed.
+
+ * RCSFILES: Add discussion of the common concern regarding
+ applying deltas to get to a branch head.
+
+ * DIFFUTILS-2.7-BUG: New file.
+
+ * cvs.texinfo (File status): Refer to "Invoking CVS", not
+ "status", for status options. Add paragraph about how "cvs -n -q
+ update" is another way to display file status.
+ (update examples): Removed; it had contained the "cvs -n -q
+ update" material.
+ (Invoking CVS): xref to "File status" and "Tags", not "status" and
+ "status options".
+ (status, status options): Removed.
+ (update options, checkout options): xref to "Invoking CVS"
+ not "status".
+
+ * cvsclient.texi (Requests): Clarify how long-lived Sticky and
+ Static-directory are.
+
+ * cvs.texinfo: Add @finalout.
+
+ * cvs.texinfo (Error messages): Add "cannot change permissions on
+ temporary directory" message.
+
+Wed Apr 23 12:53:45 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Document "add" in much more detail.
+
+Wed Apr 23 00:38:17 1997 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvsclient.texi (Requests): Correct small typo (`a' for `as').
+
+Tue Apr 22 14:23:32 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Protocol Notes): Expand ideas on multisite
+ features somewhat. Add items about the network turnarounds for
+ pserver authentication and for protocol negotiation.
+
+Mon Apr 21 08:54:48 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): Describe what to do
+ with Entries.Log in more detail.
+
+ * cvsclient.texi (Responses): Say "CVS 1.9 and earlier" rather
+ than "pre version 1.10". The latter increases confusion by
+ referring to a version which doesn't exist yet.
+
+Mon Apr 21 01:02:53 1997 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvsclient.texi (Responses): Document Rcs-diff. Indicate that
+ Patched is now deprecated in favor of Rcs-diff.
+
+Sun Apr 20 23:42:03 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): Add note about format
+ of timestamp and the "Result of merge" concept.
+
+Sat Apr 19 13:42:33 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): It is OK for Copy-file to implement
+ a rename instead of a copy.
+
+Fri Apr 18 12:05:48 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Assigning revisions): Say that -r implies -f.
+
+Thu Apr 17 16:34:14 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (From other version control systems): Add comment
+ about CMZ and PATCHY.
+
+Wed Apr 16 12:35:25 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Add paragraph describing how
+ Copy-file relates to Merged.
+ (Responses): Add paragraph about how it is the server which
+ worries about not clobbering the user's file.
+
+Tue Apr 15 00:57:31 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * RCSFILES: Add notes on keyword expansion.
+
+ * cvs.texinfo (Rename by copying): Comment out seemingly erroneous
+ text regarding the revision number that the new file starts with.
+
+Mon Apr 14 12:37:35 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Clients should try to send
+ notifications right away.
+
+ * cvsclient.texi (Requests): For Notify request, clarify a few
+ future expansion situations. Specify the format of the time.
+
+ * cvsclient.texi (Requests): Clarify that arguments to co, rdiff,
+ and rtag are module names (and how that differs from file/directory
+ names).
+
+ * cvsclient.texi (Responses): Say that servers need to create
+ directories one at a time.
+
+Sat Apr 12 09:32:58 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Committing your changes): Say that editor default
+ is notepad (not vi) for Windows NT/95. Be more clear about what
+ "cvs commit" does. Add paragraph about timestamps.
+ (Environment variables, Global options, editinfo):
+ Add xrefs to that node.
+
+Thu Apr 10 15:48:39 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add "could not patch; will refetch".
+
+Wed Apr 9 15:21:11 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Working directory storage): New node.
+
+ * cvs.texinfo (Error messages): Add comment about "cvs co ." on
+ NT.
+
+Tue Apr 8 14:44:26 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Add diff3 usage message.
+
+Sun Apr 6 19:03:01 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Removing files): Add comment about undoing a "cvs
+ remove".
+
+ * cvsclient.texi (Requests): Explicitly mention the idea of
+ deferring "Notify" requests.
+
+Tue Apr 1 07:51:38 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Add paragraph about directory
+ creation and empty directories.
+
+ * cvs.texinfo (Binary files): Add comment about binary files and
+ merges.
+
+ * cvsclient.texi (Requests): Add discussion of when to send
+ Is-modified.
+
+ * cvsclient.texi (Requests): Sending Is-modified is enough to
+ prevent the file from being considered "lost".
+
+Sun Mar 30 00:31:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Add Is-modified request. Clarify
+ order of Entry relative to Unchanged or Is-modified (might as well
+ specify the same thing vis-a-vis Modified while we are at it).
+
+Sat Mar 29 12:32:40 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi: Change "newline" to "linefeed". Most of the
+ document already reads "linefeed" and that is what is intended.
+ (File transmissions): New node, moved here from Requests.
+ (Goals, Filenames, File transmissions, new node Strings): Add
+ discussion of character sets and what we expect from the transport
+ protocol we run on.
+
+ * cvsclient.texi (Requests): Add paragraph about each Directory
+ request specifying a new local-directory and repository.
+
+ * cvsclient.texi (Requests): Add paragraph about renaming
+ local-directory in Directory request. Use "local-directory"
+ consistently instead of "working directory", for clarity.
+
+Fri Mar 28 13:59:59 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Make it clear that there is no
+ guarantee that one will get Clear-sticky instead of another
+ response. Also clarify that clients will tend to store the
+ repository in a long-term way.
+
+ * cvsclient.texi (Requests): Further clarify Directory example.
+
+ * cvsclient.texi (Requests): Add example and further explanation
+ of what expand-modules is for.
+
+ * cvsclient.texi (Requests): Add example, hopefully making it
+ clearer what REPOSITORY and LOCAL-DIRECTORY mean to Directory.
+
+ * cvs.texinfo (Attic): New node.
+ (rtag options): Adjust discussion of -a accordingly.
+ (Repository files): Adjust accordingly.
+
+Thu Mar 27 09:57:05 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): Give exact wording of broken pipe
+ error message.
+
+ * cvs.texinfo (history database): Add comment about various
+ problems with the history file.
+
+ * cvs.texinfo (Common options): The ISO8601 web page we had
+ mentioned in a comment is no more. Replace it with a new one.
+
+ * cvs.texinfo (Common options): "cvs history" also outputs dates.
+
+Wed Mar 26 10:54:21 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): "cvs editors" also outputs dates.
+
+ * cvs.texinfo (Outside): Fix paragraph which said that revision
+ numbers start at 1.0. First of all, it is 1.1. Second of all, it
+ is sometimes 2.1, 3.1, etc. Third of all, the xref should be to
+ Assigning revisions not commit options.
+
+ * cvs.texinfo (Outside): Comment out sentence which incorrectly
+ stated that "cvs add" can operate on "foo/bar.c".
+
+Tue Mar 25 22:21:29 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Error messages): New node.
+ (Magic branch numbers): Move from Troubleshooting to Revisions and
+ branches. The former placement never made any sense to me.
+ (Revision numbers): Remove "Main trunk (intro)" index entry now
+ that this node is right next to the other "main trunk" index
+ entry.
+ (BUGS): Very briefly mention reporting bugs in CVS.
+
+ * cvs.texinfo (Compatibility): Add comment about "Nfoo" in CVS/Tag.
+
+Mon Mar 24 13:50:24 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Creating a branch): Add comment about -r in branch
+ example.
+
+ * cvsclient.texi (Responses): Discuss meaning of tagspec and
+ future expansion in Set-sticky. The behavior described is the one
+ which CVS has always implemented.
+
+Fri Mar 21 14:19:05 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Revise meaning of "Case" per change
+ to CVS.
+
+Tue Mar 18 15:50:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ The following reorganization hopefully presents numeric revisions
+ in a slightly more coherent fashion. The only new material is the
+ paragraph about assigning revisions for added files.
+ * cvs.texinfo (A sample session): Bring in a sentence from Basic
+ concepts node, defining a repository.
+ (Revisions and branches): Renamed from Branches (it has always
+ covered non-branch tags too). Bring in nodes "Revision numbers" and
+ "Versions revisions releases" from Basic concepts, the former in
+ particular was way too detailed for an intro section.
+ (A sample session): Add comment about how we need an introduction
+ and what might go into one. Also bring in the paragraph from
+ Basic concepts introducing modules, but comment it out.
+ (Viewing differences): Add comment about
+ (Basic concepts): Removed; its content has been farmed out as
+ described above, and as the comment said, it was fundamentally
+ flawed.
+ (Assigning revisions): New node. Incorporates the "New major
+ release number" subsubsec which was in "commit examples". Add
+ paragraph concerning how CVS assigns revisions on added files.
+ (commit options): Refer to that node under -r.
+ (Invoking CVS): Add comment about text for -r.
+
+Tue Mar 18 13:04:30 1997 Jim Meyering <meyering@totoro.cyclic.com>
+
+ * Makefile.in: (install-info): Depend on installdirs.
+
+Sun Mar 16 12:37:12 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (File permissions): CVSUMASK now works for RCS
+ files; but it is (still) awkward for client/server CVS.
+
+Sat Mar 15 17:41:12 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Magic branch numbers): Add comment about where this
+ should go.
+
+Thu Mar 13 09:11:36 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Credits): Fix grammatical mistake ("manual about"
+ -> "manual is about"). Reported by Philippe De Muyter.
+
+Sun Mar 9 09:06:40 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (File permissions): Add comment about val-tags and
+ CVSUMASK.
+
+Sun Mar 2 12:33:26 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (From scratch): Add comment about creating
+ directories with add rather than import.
+
+ * cvs.texinfo (Creating a repository): Add comment about how this
+ somewhat duplicates Server requirements.
+
+ * cvs.texinfo (Connecting via rsh): Add comment about rsh
+ vs. remsh. Also wording fix ("incorrect" -> "inapplicable").
+
+ * cvs.texinfo (Outside): Add comment about renames and annotate.
+
+ * cvs.texinfo (Server requirements): New node.
+
+Thu Feb 27 15:20:49 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Multiple developers): Reword section on "cvs admin
+ -l". As nearly as I can tell based on when it came up on info-cvs
+ and other contexts, people who are into reserved checkouts
+ generally find that cvs admin -l is OK. Add a bunch more notes
+ (inside @ignore) about reserved checkout implementation ideas.
+
+Sun Feb 23 16:12:03 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): Add various additional comments
+ about date formats.
+
+ * RCSFILES: Remove diff for Id and explain it in words instead.
+ The previous values for Id had been clobbered by keyword expansion
+ on the RCSFILES file itself.
+
+Sat Feb 22 14:16:28 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (DISTFILES): Fix typo (missing backslash).
+
+Fri Feb 21 23:08:38 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * RCSFILES: New file.
+ * Makefile.in (DISTFILES): Add RCSFILES.
+
+20 Feb 1997 Lenny Foner <foner@media.mit.edu>
+
+ * cvs.texinfo (Checklist): Fix typo ("keword" -> "keyword").
+
+Thu Feb 20 21:57:05 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keeping a checked out copy): Add "web" to index.
+
+Wed Feb 12 18:44:16 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication client, Invoking CVS):
+ Document "cvs logout" command.
+
+Tue Feb 11 20:42:45 1997 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (commit options): Document that the -f option to
+ commit disables recursion.
+
+Sun Feb 9 13:58:59 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (diff options): Document all the options we pass
+ through to diff. Remove paragraph about -D sometimes meaning
+ --ifdef since that is no longer true.
+
+ * cvs.texinfo (Multiple developers): Add lengthy comment about
+ reserved checkout design issues.
+
+ * cvs.texinfo (Wrappers): Add paragraph about timestamps.
+
+ * cvs.texinfo (commit options): Don't try to document what CVS 1.3
+ does with -f and how recent versions differ: 1.3 is pretty old
+ anyway, we generally only try to document the current version, and
+ the way it was described here was pretty confusing.
+ (Environment variables): Likewise for CVSEDITOR.
+
+ * cvs.texinfo (import output): Add index entries for symbolic
+ links. Add brief mention of whether behavior should be
+ different. Add comments on other symbolic link issues.
+
+Wed Feb 5 13:02:37 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Concurrency): Add comment about commit/commit
+ atomicity.
+
+Mon Feb 3 10:55:41 1997 joel boutros <nihilis@moral.addiction.com>
+
+ * cvs.texinfo (Connecting via rsh): Fix typo (programs -> problems).
+
+Fri Jan 31 12:18:47 1997 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvsclient.texi (Connection and Authentication): Correct typo
+ (``sent'' for ``send''), and rewrite sentence for clarity.
+
+Fri Jan 24 10:31:57 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (File status): Change "Unresolved Conflict" to "File
+ had conflicts on merge" per change to CVS.
+
+Sun Jan 19 16:21:17 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (admin): Add comments about "group" and "compiled in
+ value". At least one info-cvs poster was confused by this.
+
+Thu Jan 16 17:54:51 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): It is just -t/-f which doesn't work
+ client/server. -k *does* (well, except for the problem with
+ import noted in BUGS). -m I don't know and I doubt anyone cares.
+
+Mon Jan 13 15:41:02 1997 Karl Fogel <kfogel@ynu38.ynu.edu.cn>
+
+ * cvs.texinfo (Read-only access): rephrase to imply that there may
+ be other administrative files, besides history and locks, which
+ read-only users can also affect (in the future, for example, the
+ `passwd' file).
+
+Wed Jan 8 14:50:47 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in: Remove CVSid; we decided to get rid
+ of these some time ago.
+
+Wed Jan 8 09:08:36 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Connection and Authentication): Document
+ restriction that cvs root sent in the cvs protocol and in the
+ pserver authentication protocol must be identical.
+
+Thu Jan 2 13:30:56 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in, cvs.texinfo: Remove "675" paragraph;
+ see ../ChangeLog for rationale.
+
+Thu Jan 2 09:34:51 1997 Karl Fogel <kfogel@ynu38.ynu.edu.cn>
+
+ * cvs.texinfo (Read-only access): new node.
+ (Repository): new menu item for above new node.
+ (Password authentication server): document the user-aliasing
+ feature. Why was this undocumented before?
+
+Wed Jan 1 18:12:11 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Conflicts example): Use @asis in example to prevent
+ starting a line with a conflict marker. This means that when
+ maintaining the file with CVS itself, CVS will not think there is
+ a conflict merely because of the conflict marker in the example.
+ IMHO, this is totally bogus and CVS needs a better way of figuring
+ out whether a conflict is resolved (see comments elsewhere in this
+ node), but until then.... Credit to Fred Fish for reporting the
+ problem.
+
+ * cvs.texinfo (cvsignore): Add paragraph about how .cvsignore
+ files in the sources being imported by "cvs import" override
+ "-I !". Credit goes to Fred Fish for pointing out this problem.
+
+Thu Dec 19 12:36:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Credits): Update Roland Pesch email address per his
+ request.
+
+Tue Dec 17 12:57:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (verifymsg): In example, remove text "and reedit if
+ necessary"; it was copied from editinfo and doesn't apply here.
+ Fix syntax of if statement; remove unnecessary attempt at loop;
+ don't use -n with echo. Add @appendixsec at start of node.
+ Add note about how verifymsg cannot change log message.
+ (editinfo): In paragraph saying editinfo is obsolete, fix various
+ typos and formatting glitches. Mention -e as well as EDITOR.
+ (editinfo): In saying that editinfo doesn't get consulted with -m,
+ -F or client/server, recommend verifymsg. Remove comment which
+ says, in effect, "we need a feature like verifymsg".
+ (editinfo example): Change "verifymsg" back to "editinfo" here;
+ the example is of editinfo not verifymsg.
+
+Tue Dec 17 12:45:32 1996 Abe Feldman <feldman@cyclic.com>
+
+ * cvs.texinfo (verifymsg): New node.
+ various places: Say that editinfo is obsolete, or refer to
+ verifymsg instead of editinfo
+
+Wed Dec 11 08:55:26 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Compatibility): Add comment about 1.3 and file death.
+
+ * cvs.texinfo (update output, release output): Document "P" as
+ well as "U".
+
+Tue Dec 10 16:23:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Builds): Change "make" to "implement" and "build";
+ in this context "make" is ambiguous.
+ (Builds): Add new URL of mk web page.
+
+Mon Dec 9 11:03:37 1996 Jim Blandy <jimb@floss.cyclic.com>
+
+ * cvs.texinfo (Password authentication client, Environment
+ variables): Remove mention of CVS_PASSWORD.
+
+Sun Dec 8 22:38:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Repository files): Mention differences between RCS
+ files in RCS and in CVS.
+ (Tags): Tag names must start with a letter.
+
+Fri Dec 6 09:08:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (syntax): Expand discussion of regular expression
+ syntax.
+
+Fri Nov 29 09:06:41 1996 fnf@ninemoons.com (Fred Fish)
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo, cvsclient.texi: Make sure @ref and friends are
+ followed by "," or "." as described in the texinfo manual. This
+ is a dubious practice as texi2html and texinfo.tex don't require
+ it, and makeinfo could insert them as needed, but since makeinfo
+ doesn't do that yet, cope.
+
+ * cvs.texinfo (From files): Suggest "diff -r" rather than "ls -R"
+ as the way to see that the sources seem to have been imported
+ correctly.
+ (Common options): -k is also available with import.
+ (admin options): Fix typo ("interrested" -> "interested").
+
+Mon Nov 25 10:03:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): Add comments about two digit
+ years, year 2000, and ambiguous/nonexistent dates.
+
+Sun Nov 24 17:27:24 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (First import): Don't say what the wdiff program we
+ are using as an example does--that is confusing. Also don't show
+ untarring it--people might be familiar with cpio, ZIP, VMS BACKUP,
+ etc., instead of tar.
+
+ * cvs.texinfo (Adding files): Update comment about "cvs add -m".
+
+ * cvs.texinfo (Common options): Remove -H; -H is not a command
+ option.
+ (Global options): Also list --help and --version. Don't say that
+ -H gives a list of commands; it doesn't any more (directly).
+
+ * cvs.texinfo: Add comment pointing to paper size web page.
+
+ * cvs.texinfo (Common options): Rewrite section on date formats.
+ Executive summary is that RFC822 and ISO8601 are now preferred.
+
+Wed Nov 20 08:39:45 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Getting Notified): Add paragraph clarifying that
+ watches happen per user, not per working directory.
+
+Tue Nov 19 09:39:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Tags): Suggest that future special tag names might
+ start with ".". Fix typo.
+
+ * cvs.texinfo (Removing directories): -P is also available with
+ export.
+ (Moving directories): Rewrite first paragraph; now says that you
+ must use -P for the directory to disappear from working
+ directories. Thanks to Martin Lorentzon
+ <Martin.Lorentzson@emw.ericsson.se> for reporting this bug.
+ (various): Where we mention -P, point to Removing directories
+ node.
+
+Sat Nov 16 18:03:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Example): Rewrite to actually be based on a real
+ live example (and therefore reflect the way the protocol currently
+ works). Add comment about formatting of the document itself.
+
+Thu Nov 14 10:22:58 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Introduction): Use @ref, not @xref, after "see".
+ (Goals): Rewrite items about locking, about uploading in big
+ chunks, and about atomicity to be focused more on the protocol
+ than the current implementation.
+ (Notes): Remove this node. The attempt to describe the basic
+ model has pretty much been replaced by the Introduction.
+ The material about how to start the client is incomplete and
+ better left to cvs.texinfo. And the item about the lack of
+ SERVER_FLOWCONTROL is obsolete now that SERVER_FLOWCONTROL is the
+ default.
+ (Protocol Notes): Add comment about multisite features.
+ (Requirements): Use @code for requests and responses.
+
+ * cvs.texinfo (Remote repositories): Add a few sentences defining
+ "client" and "server"; before we had been using the terms without
+ defining them.
+
+ * cvs.texinfo (What is CVS?): Add paragraph about reporting bugs.
+ Reword and expand comp.software.config-mgmt description (and add
+ comments about other newsgroup facts). Point people at GNU list
+ of FTP sites rather than directly at prep.ai.mit.edu.
+
+Wed Nov 6 09:45:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Tracking sources): Add comment regarding added and
+ removed files.
+
+Tue Nov 5 14:00:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Rename node "Invoking CVS" to "CVS commands".
+ Rewrite the intro and comments to reflect addition of the new
+ Invoking CVS.
+ (Invoking CVS): New node, a quick summary of each command.
+ (annotate): Don't list the options; refer to Invoking CVS and
+ Common options instead.
+
+Sun Nov 3 21:22:42 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Compatibility): New node, moved from ../README.
+
+ * cvs.texinfo (Common options): Add comment about how tar manual
+ contains documentation for getdate date formats.
+
+Fri Nov 1 14:00:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (commit examples): Rewrite "New major release
+ number" section to tighten up the wording, better motivate the
+ discussion, and replace the term "rcs revision number" with
+ "numeric revision".
+
+Fri Oct 25 07:49:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (loginfo): Don't say "a la printf"; the syntax is
+ only vaguely similar to printf.
+
+ * cvs.texinfo (loginfo): To get just the repository name, suggest
+ %{} instead of % "standing alone"; the latter is now an error.
+
+Tue Oct 22 13:08:54 1996 Noel Cragg <noel@gargle.rain.org>
+
+ * cvs.texinfo (loginfo): add information on the new loginfo format
+ string specification.
+
+Mon Oct 21 17:33:44 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Builds): New node.
+ (What is CVS?): Refer to it.
+
+Sat Oct 19 14:32:21 1996 Jim Meyering <meyering@asic.sc.ti.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Choosing a model): Wording/grammar fix.
+
+Sat Oct 19 14:32:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Obsolete): New node.
+ (Requests): Remove Repository and Lost and adjust Directory,
+ UseUnchanged, and other places accordingly.
+ (Required): Directory and Unchanged are now required.
+
+ * cvs.texinfo (Removing files): Don't talk about modules; they are
+ not relevant in this context.
+ (Removing directories): New node.
+ (Common options): Refer to it instead of duplicating information.
+
+Fri Oct 18 11:05:06 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (First import, import): Add paragraph about the fact
+ that import doesn't modify the directory which it imports from.
+
+ * cvs.texinfo (Creating a repository): Add paragraph about
+ resource requirements.
+
+ * cvs.texinfo (Copying): Replace empty node with a copy of the GPL.
+
+Thu Oct 17 12:10:55 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Adding files): Revise comment to more accurately
+ reflect the functioning/nonfunctioning status of cvs add -m.
+
+ * cvs.texinfo (Reverting local changes): New node, somewhat based
+ on the version of this node from 30 Sep 96 change.
+ (admin options): Refer to it.
+
+ * cvs.texinfo: Reinstate 30 Sep 96 change from A4 to US letter.
+
+ * cvs.texinfo (Concurrency): When telling people how to clean up
+ locks, tell them to make sure the locks are owned by the person
+ who has the stale locks.
+ (update output, release output): Remove text about how CVS doesn't
+ print "? foo" for directories; CVS has since been changed (see
+ conflicts-130 in sanity.sh).
+
+Wed Oct 16 15:01:42 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (history options): Mention new option -x E.
+
+Mon Oct 14 15:21:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Tags): Add paragraph on choosing a convention for
+ naming tags.
+
+Thu Oct 10 16:05:26 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (modules): Describe what & does.
+
+Mon Oct 7 17:20:11 1996 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (Removing files): Correct apparent cut and paste
+ error: refer to the removed file, not the added file.
+
+Tue Oct 1 14:15:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Revert all recent changes (the last unscathed one
+ is the CVSUMASK one from Sunday). For the most part said changes
+ are for new features which are not appropriate at this stage of
+ the release process. None of the changes being reverted need to
+ go into 1.9, that is for sure.
+
+Mon Sep 30 18:17:34 1996 Greg A. Woods <woods@most.weird.com>
+
+ * cvs.texinfo (Credits): add comment asking if we should update.
+ Add more detail about printing Letter on A4.
+ Add some comments about internal comments.
+ (From files): describe "cvs import -b 1" for importing existing
+ projects onto the main branch.
+ (First import): add a couple of helpful hints about naming vendor
+ and release tags, etc., and regularize the examples with this.
+ (Tracking sources): noted some reasons why you might use vendor
+ branches with "cvs import".
+ (Update imports): mention using "update" in place of "checkout" if
+ you have an existing working directory.
+ (Binary files in imports): add sub-menu separator comment.
+ (Tracking sources): new menu entry "Reverting to vendor release".
+ (Reverting to vendor release): new node to describe reverting
+ local changes and optionally using patch(1) to move local changes
+ forward.
+ (Global options): describe -D and -g, as well as DIFFBIN and
+ GREPBIN.
+ (export examples): add one.
+ (import options): describe the effect of '-b 1'.
+
+Mon Sep 30 08:09:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Adjust comments concerning A4 vs. US letter,
+ referring to ../README.
+
+ * cvs.texinfo (Common options): Add comment about dates which CVS
+ uses in output.
+
+Sun Sep 29 11:14:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keyword list): Don't mention Name twice.
+
+ * cvs.texinfo (File permissions): Expand CVSUMASK stuff a bit.
+ (Setting a watch, Environment variables, Global options): Update
+ index entries for "read-only files, and ...".
+
+ * cvsclient.texi (Requests): State that Gzip-stream is preferred
+ to gzip-file-contents. Cite RFC1952/1951 rather than just "gzip".
+ Say that RFC1950/1951 compression is also known as "zlib".
+
+Sat Sep 28 09:31:45 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Repository): Move all information about the
+ internal structure of the repository to User modules node. Rename
+ it to "Repository storage" ("User modules" wasn't particularly
+ clear). Mention CVSUMASK. Much clarification and
+ reorganization.
+ (Basic concepts): Remove material which duplicates what is now in
+ Repository. Rewrite paragraph introducing modules.
+
+ * cvs.texinfo (Starting a new project): In discussing difficulty
+ in renaming files, don't refer to "cvs 1.x"--there is no
+ non-vaporous "cvs 2.x". Reword to reflect that part of the reason
+ to avoid renames (where possible) is not because of CVS at all, and
+ to try to give a general impression of how bad CVS issues involved in
+ renaming are.
+
+Fri Sep 27 04:23:44 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Adding files): Talk about directories, not modules,
+ since that is what is meant. Suggest using -kb option to add
+ rather than running cvs admin after the fact and xref to Binary
+ files not admin examples. Incorporate information which had been
+ in "add" node (there was a lot of duplication). Don't document
+ use of "add" on a directory to take the place of "cvs update -d";
+ the latter is simpler and more logical.
+ (add, add options, add examples): Removed.
+ (release output, release options): Update xrefs accordingly.
+ (Adding files, Removing files): Mention the fact that adds and
+ removes are branch-specific.
+ (Merging adds and removals): New node.
+
+ * cvs.texinfo (Concurrency): When mentioning RCS locks, use the
+ term reserved checkouts and xref to the place where we discuss
+ them in more depth.
+
+Thu Sep 26 08:26:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (log): Add comments about timezones.
+ (log, Common options): Add index entries for timezone and zone, time.
+
+Wed Sep 25 11:05:30 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (log options): Add xref to where we describe the
+ date formats that -d accepts.
+ (Common options): Don't refer to date formats accepted by co(1);
+ CVS's rules have never been the same. Add long whiny comment
+ about what a mess date formats are.
+
+Tue Sep 24 11:49:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (From other version control systems): The RCS file
+ must not be locked when you copy it to the CVS repository.
+
+ * cvs.texinfo (Editing files): Also discuss how to revert in the
+ non-watch case. Add some index entries.
+
+ * cvs.texinfo (update output): Add comment about how we *should*
+ be handling .# files. Mention fact that it is different under
+ VMS. Add .# to index.
+
+Fri Sep 20 13:08:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Multiple developers): Revise text on reserved
+ versus unreserved checkouts extensively. Move index entries for
+ "reserved checkouts" and "RCS-style locking" to here. Add
+ cross-reference to cvs admin -l. Add new section "Choosing a
+ model".
+ (Editing files): Add note about use of the word "checkout".
+
+Tue Sep 17 00:54:57 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Defining the module): Don't suggest "cvs co
+ modules"; that depends on a "modules" module being defined which
+ is not the default which is created by "cvs init". Instead
+ suggest "cvs co CVSROOT/modules" which should always work.
+
+Tue Sep 17 00:43:49 1996 VaX#n8 <vax@linkdead.paranoia.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Rename by copying): Suggest "cvs tag -d" on the file
+ "new", not on everything. Also don't suggest deleting branch tags.
+
+Tue Sep 17 00:34:39 1996 David A. Swierczek <swierczekd@med.ge.com>
+
+ * Makefile.in (install-info): Note whether files are in srcdir and
+ deal with it rather than cd'ing into srcdir.
+
+Mon Sep 16 23:33:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Add comment about using wrappers to
+ compress files in the repository.
+
+ * cvs.texinfo (modules): Add comments about how we should be
+ documenting how -i and friends operate in client/server CVS.
+
+ * cvs.texinfo (File permissions): Describe the need for write
+ permissions for locks and val-tags.
+
+ * cvs.texinfo (commitinfo): Add comment about using commitinfo to
+ enforce who has access.
+
+Wed Jul 24 17:01:41 1996 Larry Jones <larry.jones@sdrc.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (checkout): Refer to "update output" node.
+ (import): Add new import output node.
+ (release): Correct release output menu entry (used to be
+ release options instead).
+ (update output): Say this is output from checkout as well as
+ update.
+
+Mon Sep 16 16:18:38 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Common options): Clarify that CVS uses MM/DD/YY dates.
+
+ * cvs.texinfo (Common options): Add comment about what HEAD means.
+
+Mon Sep 16 10:52:04 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.texinfo (Global options): Document global '-T' option.
+
+Sat Sep 14 10:46:58 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keeping a checked out copy): New node.
+
+Fri Sep 13 23:55:42 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Magic branch numbers): Delete song and dance about
+ how cvs log can't cope with magic branches because rlog doesn't
+ know about them; cvs log no longer calls rlog. Delete item about
+ how you can't specify a symbolic branch to cvs log; that is fixed.
+
+Wed Sep 11 22:48:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication server): Add comments
+ regarding port numbers and troubleshooting.
+
+Tue Sep 10 10:36:00 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (What is CVS?): Reword text regarding info-cvs,
+ to avoid overfull hbox.
+
+ * cvs.texinfo (Binary files): Add comment about further issues
+ with recovering from failure to use -kb.
+
+ * cvs.texinfo (Conflicts example): Describe the "feature" by which
+ CVS won't check in files with conflicts.
+ (File status): Expand and revise to document all the possible
+ statuses from cvs status. Also document "Working revision" and
+ "Repository revision". Refer to other sections for other aspects
+ of cvs status.
+ (status options): Refer to other sections as appropriate.
+ (update output): Refer user to Conflicts example node. Add
+ comment regarding purging of .# files.
+
+Fri Sep 6 11:47:14 1996 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (Kerberos authenticated): Mention need for
+ --enable-encryption option in order to use encryption.
+ (Global options): Likewise, in description of -x option.
+
+Thu Sep 5 14:31:42 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Connecting via rsh): Discuss :ext:, :server:, and
+ CVS_RSH.
+ (Remote repositories): Mention what default is if no access method
+ is specified.
+ (Environment variables): Don't discuss CVS_RSH at length here;
+ rely on reference to "Connecting via rsh" node.
+
+Mon Aug 26 15:39:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Protocol Notes): When talking about having the
+ client know the original contents of files, suggest cvs edit as a
+ solution.
+
+Thu Aug 22 10:44:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Keyword list): Document Name keyword.
+
+ * cvs.texinfo (Tags): Revise comment regarding legal tag names.
+
+Mon Aug 12 14:58:54 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication security): Add comment
+ about how some of this is not pserver-specific.
+
+Tue Aug 6 16:48:53 1996 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (log, log options): Update for changes to cvs log
+ now that it no longer invokes rlog.
+
+Thu Jul 25 10:10:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Fix typo (Kerberos-request ->
+ Kerberos-encrypt).
+
+Wed Jul 24 18:53:13 1996 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.texinfo (Kerberos authenticated): Change the note that the
+ Kerberos connection is not encrypted.
+ (Global options): Add documentation for -x.
+ * cvsclient.texi (Protocol Notes): Remove enhancement note about
+ Kerberos encryption.
+ (Requests): Add documentation for Kerberos-encrypt request.
+
+Thu Jul 18 18:27:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Creating a repository): Mention need to be able to
+ create lock files in the repository.
+
+ * cvsclient.texi (Responses): In F response, make at least a
+ minimal attempt to define "flush".
+
+ * cvs.texinfo (Wrappers): Document -k.
+ (From files, Binary files in imports): Say that imports can deal
+ with binary files and refer to Wrappers node for details.
+ (Binary files): Likewise for imports and adds.
+
+Sat Jul 13 18:29:10 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Binary files): Add paragraph concerning the fact
+ that the keyword expansion mode is not versioned, and why this is
+ a problem.
+
+Fri Jul 12 18:55:06 1996 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvsclient.texi (Requests): Document Gzip-stream.
+
+Thu Jul 11 21:51:45 1996 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvsclient.texi (Responses): Document new "F" response.
+
+Wed Jul 10 18:46:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (log): Don't document "rlog"; it is deprecated.
+
+Sat Jul 6 22:07:45 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Environment variables): Document more temp
+ directory nonsense, this time with "patch".
+
+Fri Jul 5 23:27:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Add comment regarding "/." ending.
+
+Fri Sep 13 10:52:09 1996 Greg A. Woods <woods@clapton.seachange.com>
+
+ * cvs.texinfo: don't force afourpaper -- Letter prints much better
+ on A4 than the other way around, believe you me!
+ (rdiff options): describe -k and new -K.
+ (RCS keywords): add description of $Name.
+ (Using keywords): add description of #ident and example of using
+ $Name.
+ - also fixed cross references to Substitution modes in various
+ places.
+ (import options): mention that -b 1 imports to the trunk.
+
+Tue Jul 2 22:40:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Sticky tags): Update to reflect change in
+ "resurrected" message.
+
+Fri Jun 28 10:48:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Connecting via rsh): Add comment about what we
+ might be saying about troubleshooting.
+
+Sun Jun 23 10:07:45 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication security): Add comment
+ regarding anoncvs as practised by OpenBSD.
+
+Wed Jun 19 15:41:11 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Administrative files): Add xref to Intro
+ administrative files.
+ (Intro administrative files): Add comment suggesting future
+ reorganizations of this material.
+ (syntax): Add comment regarding this node.
+ (Getting Notified): Actually document the notify file. It hadn't
+ really been documented to speak of.
+ (editinfo,loginfo,rcsfino,cvsignore): Make the index entries
+ follow the standard "foo (admin file)" format.
+
+Fri Jun 14 18:14:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (editinfo): Discuss the way editinfo falls down in
+ the face of -m or -F options to commit, or remote CVS.
+
+Thu Jun 13 15:08:27 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Watches): Add comment discussing the
+ fact that using cvs edit instead of chmod is not enforced.
+
+ * cvs.texinfo (Setting up): Add index entry for "init (subcommand)".
+ (Creating a repository): Move contents of node Setting up here...
+ (Setting up): ...and remove this node.
+ (Creating a repository): Don't refer to the INSTALL file (it just
+ refers back to us!).
+
+ * cvsclient.texi (Responses): Document the fact that the server
+ should send data only when the client is expecting responses.
+
+Wed Jun 12 16:04:48 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Entries Lines): Add comment regarding specifying
+ the meaning of "any other" data, in the conflict field.
+ (Example): Make it clear that using a separate connection for each
+ command is not required by the protocol. Add some comments
+ regarding ways in which the example is out of date or wrong.
+
+Fri Jun 7 18:02:36 1996 Ian Lance Taylor <ian@cygnus.com>
+ and Jim Kingdon <kingdon@cyclic.com>
+
+ * cvs.texinfo (annotate): Document new -r, -D, and -f options.
+
+Fri Jun 7 16:59:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Invoking CVS): Add comment describing why only some
+ commands are listed here.
+ (Structure, Environment variables): Don't describe CVS as a
+ front-end to RCS.
+
+Tue Jun 4 21:19:42 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Document Created and Update-existing.
+
+Mon Jun 3 17:01:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Clarify "diff -c" versus "diff -u"
+ format in Patched response. Don't specify how the client must
+ implement its patch-applying functionality.
+
+Sun May 26 17:12:24 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.texinfo (tag options) Document option "-c".
+
+Thu May 23 21:11:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Credits): Rewrite section on FAQ to reflect the
+ fact that FAQ is no longer maintained.
+ (What is CVS?): Mention comp.software.config-mgmt as well as
+ info-cvs. Mention the fact that info-cvs-request can be slow in
+ responding.
+ (What is CVS?): Rather than say that cvs is not a configuration
+ mangement system, say specifically what it lacks (change control,
+ etc.). I added process control (which was sorely lacking from the
+ list of configuration management functionality), and deleted some
+ functions such as tape construction which are not provided by the
+ well-known configuration management systems.
+
+ * cvs.texinfo (checkout options): Add comment regarding
+ subdirectories (lack of clarity pointed out by ian@cygnus.com).
+ Add comment about that infernal "short as possible" wording.
+
+ * cvs.texinfo (Global options): Fix error ("diff" -> "log")
+ (reported by ian@cygnus.com).
+ Remove footnote "Yes, this really should be fixed, and it's being
+ worked on"--it isn't clear what "this" is, and I doubt anyone is
+ working on it.
+
+Tue May 21 17:22:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Clarify Directory with "." as local
+ directory, and that filename for Questionable cannot contain "/".
+
+Mon May 20 13:15:25 1996 Greg A. Woods <woods@most.weird.com>
+
+ * cvs.texinfo (rdiff): description from main.c:cmd_usage
+ (rtag): description from main.c:cmd_usage
+ (status): description from main.c:cmd_usage
+ (tag): description from main.c:cmd_usage
+ [all for the sake of consistency]
+
+Fri May 17 11:42:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Add index entries for :local:, etc.
+ (Password authentication server): Revert erroneous change
+ regarding the format of CVSROOT/passwd file.
+
+Thu May 16 17:06:46 1996 Noel Cragg <noel@gargle.rain.org>
+
+ * cvsclient.texi (Notes): Removed paragraphs about various server
+ invocations which are now described in full in node "Connection
+ and Authentication."
+ (Requests): Include a note that "gzip-file-contents" doesn't
+ follow the upper/lowercase convention and that unknown reqests
+ always elicit a response, regardless of capitalization.
+
+ * cvs.texinfo (Kerberos authenticated): Removed bogus version
+ number.
+ (Repository): explain the ":local:" access method.
+
+Wed May 15 23:43:04 1996 Noel Cragg <noel@gargle.rain.org>
+
+ * cvsclient.texi (Goals): mention access methods.
+ (Requests): add note about convention: requests starting with a
+ captial letter don't have any expected response. Made sure each
+ request has a "Response expected" note.
+
+ * cvs.texinfo (Remote repositories): add info about access
+ methods; fix pserver info.
+
+Tue May 14 08:56:41 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Environment variables): Try to document somewhat
+ more accurately where we put temporary files.
+
+ * cvs.texinfo (From files): Say directory tree instead of module
+ where that is what we mean. Use @var{wdir} and @var{rdir} in the
+ example instead of using @var{dir} for two different things.
+ (From files): Say directory tree instead of module
+ where that is what we mean.
+ (Binary files): When using cvs admin -kb, one needs an extra
+ commit step on non-unix systems.
+ (Binary files in imports): New node.
+ (Wrappers): Add comment regarding indent example.
+ (Top): Don't refer to modules when that is not what we mean.
+
+Fri May 10 09:39:49 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Sticky tags): Explain what sticky dates and
+ non-branch sticky tags are good for.
+
+ * cvs.texinfo (Repository): Document that -d overrides CVS/Root.
+
+Wed May 1 15:38:26 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Tags): Document un-revision of all-uppercase tag
+ names.
+
+Wed Apr 24 08:41:51 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication security): Rewrite sentence
+ on complex and unknown security bugs to clarify that it is
+ referring to people who have been give access to cvs, not to holes
+ in the authentication method (which is relatively simple).
+
+Tue Apr 23 09:31:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Talk about what -m does (and does not
+ do). Other minor edits.
+
+Wed Apr 17 15:27:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (rcsinfo): Rewrite paragraph concerning remote CVS.
+ * cvsclient.texi (Responses): Document Template response.
+
+Sun Apr 14 16:01:39 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * .cvsignore: added CVSvn.texi.
+
+Wed Apr 10 16:56:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (~/.cvsrc): Mention setting global options with "cvs".
+
+ * cvs.texinfo (release): Change "modules" to "directories".
+ Release does not take module names as arguments.
+
+ * cvs.texinfo (Creating a branch): Add comments about how we
+ should better document tagging the branchpoint.
+
+Tue Apr 9 19:59:45 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Top): Use @value{CVSVN}, not a vague refenece to 1.4.
+
+ * cvs.texinfo (From other version control systems): New node.
+
+Mon Apr 8 15:59:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Connection and Authentication): Revise kerberos
+ and pserver sections to reflect the fact that port 2401 is now
+ officially registered.
+
+Thu Mar 28 09:51:13 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (History browsing): Reinstate this node. Try to get
+ it into some minimally useful state (it still needs a lot of
+ work).
+ (annotate): New node, subnode of History browing.
+
+ * cvsclient.texi (Requests): Add annotate request.
+
+Tue Mar 26 08:46:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: In various examples, change tag names to avoid tag
+ names reserved to CVS.
+
+ * cvs.texinfo (Tags): Document what is a valid tag name.
+
+ * cvs.texinfo (Substitution modes): Try to describe how the
+ various keyword expansion settings interract.
+ (Binary files): Suggest cvs update -A, not removing file and then
+ updating it, to get effect of new keyword expansion options.
+
+ * cvs.texinfo (admin options): Mention CVS's use of `dead' state.
+
+Thu Mar 21 08:25:17 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Environment variables): Expand introduction to RCS
+ environment variables. Expand and correct CVS_SERVER_SLEEP.
+
+ * cvs.texinfo (Environment variables): Remove POSIXLY_CORRECT; cvs
+ requires options to precede arguments regardless of it.
+
+Thu Mar 21 08:18:42 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.texinfo: Remove paragrahps about a forthcoming CVS
+ newsgroup and about sending patches to think.com.
+ (Environment): Document some more (all?) used environment
+ variables.
+
+Wed Mar 20 09:44:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Introduction): New node.
+ * Makefile.in: Add cruft to reflect fact that cvsclient.texi now
+ uses CVSvn.texi.
+
+Mon Mar 18 14:43:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Add Case request.
+
+Wed Mar 13 16:01:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Connection and Authentication): New node.
+
+ * cvsclient.texi (Requests): Expand discussion of Root a bit.
+
+ * cvs.texinfo (Setting up): Don't refer to INSTALL file; revise to
+ reflect some information which had been in the INSTALL file.
+
+ * cvs.texinfo (history file): Update to reflect cvsinit -> cvs
+ init. Adjust discussion of whether history file format is
+ documented.
+ (Setting up): Update to reflect cvsinit -> cvs init.
+
+ * cvsclient.texi (Requests): Document init request.
+
+Thu Feb 29 10:08:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (loginfo example): Adjust example to reflect the way
+ that CVS actually works. Add comments questioning whether that is
+ the best behavior.
+
+ * cvs.texinfo (cvsignore): Document additions to default ignore list.
+
+Mon Feb 26 13:48:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Filenames): New node, documents / vs \, etc.
+
+Wed Feb 24 1996 Marcus Daniels <marcus@sayre.sysc.pdx.edu>
+
+ * cvs.texinfo (Password authentication server): Mention
+ support for imaginary usernames.
+
+Thu Feb 15 16:34:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Variables): Add new internal variable $USER.
+
+Wed Feb 14 22:52:58 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (export, admin): Document -k option to cvs export.
+
+ * cvs.texinfo (admin options): Mention using -l, -u, -L, and -U in
+ conjunction with rcslock.pl.
+
+Mon Feb 12 16:38:41 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Remove references to mkmodules.
+
+Sun Feb 11 12:31:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi: Add Set request.
+
+ * cvs.texinfo (Variables): Rewrite to reflect user variables
+ replacing environment variables; motivate the discussion better.
+ (Global options): Add -s option.
+
+Sat Feb 10 11:18:37 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * cvs.texinfo (Variables): Fix @table commands.
+
+Fri Feb 9 17:31:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Variables): New node.
+
+ * Makefile.in (CVSvn.texi): New rule.
+ (OBJDIR_DISTFILES): Add CVSvn.texi.
+ (cvs.dvi,cvs.info): Add cruft to deal with it being in build dir
+ or srcdir.
+ * cvs.texinfo: Include CVSvn.texi and use the version number from
+ it instead of a hardcoded version number and date.
+
+Thu Feb 1 13:28:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Sticky tags): Expand so it really documents the
+ features it is talking about rather than referring to "Appendix
+ A". Add example of how to restore the old version of a dead
+ file. In various other parts of the manual refer to this node, in
+ some cases deleting duplicative text. In the case of cvs admin
+ -b, mention vendor branch usage.
+ (Removing files): Discuss removing files (in user-visible terms,
+ not in terms of the Attic and such).
+ (remove): Remove node; merge contents into Removing files.
+
+Tue Jan 30 17:52:06 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * cvs.texinfo: Tweak @top node, to make file compatible with both
+ makeinfo and texinfo-format-buffer. Perhaps we should fix the
+ formatters to agree on what constitutes valid texinfo.
+
+Mon Jan 29 16:38:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requirements): New node, to talk about required
+ versus optional parts of the protocol.
+
+Sun Jan 28 09:00:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Modes): Add discussion what what the mode really
+ means (across diverse operating systems).
+
+Tue Jan 23 12:54:57 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Per mail from Per Cederqvist, change author to "Per
+ Cederqvist et al". Also remove sentence about Signum shipping
+ hardcopy manuals and add information on Cyclic. Change version
+ number to 1.6.87.
+
+Fri Jan 12 15:29:39 1996 Vince Demarco <vdemarco@bou.shl.com>
+
+ * cvs.texinfo: Fix the documentation for the com/uncom change
+ to wrap/unwrap. make everything consistant
+
+Wed Jan 10 16:11:54 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Concurrency): Add index entries; minor clarification.
+
+Tue Jan 9 16:03:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Getting Notified): Document users file.
+
+ * cvs.texinfo (cvsignore): Add *.obj to list of ignored files.
+
+Wed Jan 3 17:01:58 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (import): Adjust list of ignored files to match
+ recent change to CVS (CVS* -> CVS CVS.adm). Consolidate
+ discussion of ignored files in one place (with xrefs from others).
+
+ * cvsclient.texi: Remove How To node. It was out of date
+ (again!), and I am getting sick of trying to update it (internals
+ documentation should be in the comments, where it at least has a
+ fighting chance of staying up to date).
+ (Protocol): Say what \n and \t mean in this document.
+
+Tue Jan 2 23:39:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Wrappers): Change comb/uncom to wrap/unwrap.
+
+Mon Jan 2 23:00:00 1996 Vince Demarco <vdemarco@bou.shl.com>
+
+ * cvs.texinfo: update the Wrappers documentation so it isn't
+ so NEXTSTEP centric. The wrappers code has alot of other
+ general uses. The new version of the documentation tryes
+ to show that to the reader.
+
+Mon Jan 1 13:09:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Clarify that Module-expansion is not
+ suitable for passing to co.
+
+Sun Dec 31 10:53:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Password authentication server): Suggest specifying
+ -b in inetd.conf.
+
+ * cvs.texinfo (Password authentication): Variety of cleanups and
+ minor fixes, including shorter node names.
+
+Sun Dec 24 02:37:51 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.texinfo (Using the client with password authentication):
+ tixed fypos.
+
+Sun Dec 24 00:00:16 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.texinfo (Remote repositories): use @code{rsh} most places,
+ because it is the name of a program, and because I am a pedant.
+ Refer to new node "Password authenticated".
+ (Password authenticated): new node.
+ (Setting up the server for password authentication): new node.
+ (Using the client with password authentication): new node.
+ (Security considerations with password authentication): new node.
+
+ These are all really long node names, but it seems necessary that
+ they be descriptive in case they're referenced elsewhere. If you
+ can think of a way out of this, please change them.
+
+Thu Dec 21 12:09:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Requests): Add Questionable. Revise
+ documentation of export and update to explain role of -I option.
+
+Tue Dec 19 16:44:18 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Update binary files info for -kb.
+
+Mon Dec 11 12:20:55 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Responses): Add Notified and Mode.
+ (Requests): Add Notify, noop, watch-on, watch-off, watch-add,
+ watch-remove, watchers, and editors.
+ * cvs.texinfo (Watches): New node, to describe new developer
+ communication features.
+
+Thu Nov 23 08:59:09 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (admin options): In saying that cvs admin -o is not
+ such a good way to undo a change, refer to the section which
+ describes the preferred way.
+
+Thu Nov 13 16:39:03 1995 Fred Fish <fnf@cygnus.com>
+
+ * Makefile.in: Remove extraneous tab from empty line.
+
+Mon Nov 13 15:00:26 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Concurrency): New node, to describe user-visible
+ behaviors associated with cvs locks.
+
+ * cvs.texinfo (Remote repositories): Add more details of how to
+ set things up (with rsh and kerberos).
+
+Thu Nov 9 11:41:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Remove -Q and -q options from command synopses.
+
+Wed Nov 8 09:38:00 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (Notes): Revise paragraph on server memory use
+ problem.
+
+Tue Nov 7 16:26:39 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Document merging more than once from a branch;
+ miscellaneous cleanups.
+
+Mon Oct 30 13:12:53 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (modules): Document -e.
+
+Thu Oct 26 11:15:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Tags): Update "version" vs. "revision" for CVS 1.5.
+ (Index,BUGS): Change bug reporting address from Per Cederqvist to
+ bug-cvs@prep.ai.mit.edu.
+
+Wed Oct 25 15:37:05 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Miscellaneous minor changes (clean up CVS/Root
+ stuff, don't say release requires a module entry, etc.).
+
+Tue Oct 24 11:01:22 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: More precisely describe scope of document.
+ * cvsclient.texi: Describe scope of document
+
+Thu Oct 12 11:25:40 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * cvs.texinfo: cover page now refers to CVS 1.6, and "last
+ updated" date has been upped to today.
+
+Wed Oct 11 22:30:10 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (info): Look for *.info* either in build dir or in
+ srcdir.
+
+Mon Oct 2 17:10:49 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.texinfo (admin): Describe usage of CVS_ADMIN_GROUP to
+ restrict usage of admin.
+
+Fri Oct 6 21:17:50 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (~/.cvsrc): Document change to command name matching.
+
+Thu Oct 5 18:03:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (install-info): Add comment about srcdir.
+
+Wed Sep 13 12:45:53 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Moving files): Rewrite "Outside" node to clarify
+ that history is still there and describe how to get it. Assorted
+ cleanups.
+
+Tue Sep 12 19:02:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo (Removing files): Remove section on limitations
+ which are gone now that we have death support.
+
+Wed Aug 30 12:32:29 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.texinfo (Remote Repositories): new node, referred to from
+ `Basics' and `Repository'.
+ (Repository): documented new `-d' vs. `$CVSROOT' vs. `CVS/Root'
+ behavior.
+ (commitinfo): document client/server-case behavior.
+ (editinfo): document client/server-case behavior.
+ (loginfo): document client/server-case behavior.
+ (rcsinfo): document client/server-case behavior.
+
+Mon Aug 21 00:23:45 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsclient.texi (How To): The way to force rsh is to set
+ CVS_CLIENT_PORT to -1, not to some bogus value.
+
+Tue Aug 15 17:12:08 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.texinfo
+ (Basic concepts): talk about remote repositories.
+ (Repository): same.
+
+Mon Jul 24 19:09:12 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.texinfo: Remove references to -q and -Q command options.
+
+Fri Jul 21 10:33:07 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * cvs.texinfo: Changes for CVSEDITOR and wrappers.
+
+Thu Jul 13 23:04:12 CDT 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (cvs-paper.ps): *Never* redirect output directly to
+ the target (usu $@) of a rule. Instead, redirect to a temporary
+ file, and then move that temporary to the target. I chose to
+ name temporary files $@-t. Remember to be careful that the length
+ of the temporary file name not exceed the 14-character limit.
+
+Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * doc/cvs.texinfo:
+ - document '-q' for 'cvs status'
+ - correction to regexp use in *info files
+ - correction to use of 'cvsinit' script
+ (from previous local changes)
+
+Tue Jun 20 18:57:55 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (dist-dir): Depend on $(OBJDIR_DISTFILES).
+
+Fri Jun 16 21:56:16 1995 Karl Fogel <kfogel@cyclic.com>
+ and Jim Meyering <meyering@comco.com>
+
+ * update.c (update_file_proc): If noexec, just write 'C', don't merge.
+
+Fri Jun 16 07:56:04 1995 Jim Kingdon (kingdon@cyclic.com)
+
+ * cvs-paper.ps: Added.
+
+Sat May 27 08:46:00 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (Makefile): Regenerate only Makefile in current
+ directory when Makefile.in is out of date. Depend on ../config.status.
+
+Sat May 27 08:08:18 1995 Jim Meyering (meyering@comco.com)
+
+ * doc/Makefile.in (realclean): Remove more postscript and info files.
+
+Fri Apr 28 22:44:06 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (DISTFILES): Updated.
+ (doc): Depend on cvsclient.ps too.
+ (cvs.aux, cvsclient.aux): Add target.
+ (cvsclient.dvi): Don't nuke the aux file. They're small and
+ helpful.
+ (cvsclient.ps): New target.
+ (dist-dir): Renamed from dist; changed to work with DISTDIR
+ variable from parent.
+
+Sun Apr 23 22:13:18 1995 Noel Cragg <noel@vo.com>
+
+ * Makefile: Added more files to the `clean' target.
+ * .cvsignore: Added the same files.
+
+Mon Nov 28 10:22:46 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Notes): Remove item about commit options; now
+ fixed. Rewrite paragraph about server memory usage.
+
+ * cvsclient.texi (Responses): Add Set-checkin-prog and
+ Set-update-prog.
+ (Requests): Add Checkin-prog and Update-prog.
+ * cvsclient.texi (TODO): Remove last item (it is fixed) and node.
+
+Fri Nov 18 16:51:36 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Requests): Add Max-dotdot.
+
+Thu Nov 3 07:04:24 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Add Directory request.
+ (TODO): Remove item about renaming directories.
+ (Protocol): Change @subheading to @node/@section.
+
+Fri Oct 28 07:51:13 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Add expand-module request and
+ Module-expansion response.
+ (Protocol Notes, TODO): Remove items about cvs co funkiness.
+
+Wed Oct 12 19:49:36 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Add Copy-file response.
+
+ * cvsclient.texi (How To): Correct item about where declaration
+ of cvs commands go.
+
+ * cvsclient.texi (Protocol): Add new commands. Merge description
+ of how commands work which was duplicated among the various
+ commands. Formatting cleanups.
+ (TODO): Remove item about bad error message on checking in a
+ nonexistent file; this works now (presumably fixed by the
+ Unchanged stuff).
+ (Notes): Remove thing about trying unsupported commands via NFS,
+ rdist, etc. Also remove item about some commands not being
+ supported. There are no unsupported commands anymore.
+
+Tue Sep 13 13:28:52 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Document New-entry response.
+
+Mon Sep 12 06:35:15 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Clarify that checksum is of patched
+ file, not patch itself. Fix typo (valid-requests -> Valid-requests).
+
+ * cvsclient.texi (Protocol): Document Sticky request and
+ Set-sticky and Clear-sticky responses.
+ (Notes): Remove sticky tags from todo list.
+
+Thu Sep 8 14:23:58 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Document Static-directory requests
+ and Set-static-directory and Clear-static-directory responses.
+ (Notes): Remove Entries.Static support from todo list.
+
+ * cvsclient.texi (Protocol): Document Unchanged and UseUnchanged
+ requests. Update documentation of Entry and Lost accordingly.
+
+Mon Aug 22 14:08:21 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Goals): Remove mention of rsh.
+ (Protocol Notes, TODO): Remove compression item.
+ (Protocol): Document "status" request.
+ (TODO): Remove suggestion to add "cvs status".
+
+Tue Jul 19 10:02:53 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * Makefile.in (install-info): Do not depend upon installdirs.
+
+Fri Jul 15 12:56:53 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * Makefile.in (all): Do not depend upon info.
+ (install): Do not depend upon install-info.
+
+Thu Jul 7 20:43:12 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * cvsclient.texi (Protocol): Add Checksum response.
+
+Thu Jun 30 15:16:50 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Add Global_option request.
+
+Wed Jun 29 14:09:42 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * cvsclient.texi: Describe sending patches, including the dummy
+ update-patches request and the Patched response. Mention Kerberos
+ authentication using ``cvs kserver''. Some other minor changes.
+
+Tue Jun 28 15:21:06 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol Notes): Remove note about sending diffs
+ in Updated; Ian did it. Remove note about adding encryption to rsh.
+
+Sat May 7 10:44:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi (Protocol): Document Modified without Entry. Add
+ `add' and `remove' and `Remove-entry'. Formatting cleanups.
+
+Tue Apr 19 01:29:04 1994 John Gilmore (gnu@cygnus.com)
+
+ * cvsclient.texi: New node How To; cleanups throughout.
+ * Makefile.in: Add dependencies on cvsclient.texi.
+
diff --git a/gnu/dist/cvs/doc/ChangeLog.fsf b/gnu/dist/cvs/doc/ChangeLog.fsf
new file mode 100644
index 00000000000..2f140999caf
--- /dev/null
+++ b/gnu/dist/cvs/doc/ChangeLog.fsf
@@ -0,0 +1,38 @@
+Thu Sep 15 14:19:50 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * Makefile.in: define TEXI2DVI
+
+Sat Dec 18 01:23:39 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * cvs.texinfo: document -k SUBST options to 'cvs import';
+ regularize use @sc{cvs}
+
+ * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead
+ (install-info): grab all info files, not just *.info
+
+Mon Oct 11 16:23:54 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvsclient.texi: New node TODO; various other changes.
+
+Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * Makefile.in, configure.in: removed traces of namesubdir,
+ -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced
+ copyrights to '92, changed some from Cygnus to FSF.
+
+Tue Dec 10 04:07:10 1991 K. Richard Pixley (rich at rtl.cygnus.com)
+
+ * Makefile.in: infodir belongs in datadir.
+
+Thu Dec 5 22:46:01 1991 K. Richard Pixley (rich at rtl.cygnus.com)
+
+ * Makefile.in: idestdir and ddestdir go away. Added copyrights
+ and shift gpl to v2. Added ChangeLog if it didn't exist. docdir
+ and mandir now keyed off datadir by default.
+
+Wed Nov 27 02:45:18 1991 K. Richard Pixley (rich at sendai)
+
+ * brought Makefile.in's up to standards.text.
+
+ * fresh changelog.
+
diff --git a/gnu/dist/cvs/doc/Makefile.in b/gnu/dist/cvs/doc/Makefile.in
new file mode 100644
index 00000000000..29eb565d46a
--- /dev/null
+++ b/gnu/dist/cvs/doc/Makefile.in
@@ -0,0 +1,197 @@
+# Makefile for GNU CVS documentation.
+# Do not use this makefile directly, but only from `../Makefile'.
+# Copyright (C) 1986, 1988-1990 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+infodir = $(prefix)/info
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+
+DISTFILES = \
+ .cvsignore ChangeLog ChangeLog.fsf Makefile.in \
+ RCSFILES \
+ cvs-paper.ms cvs-paper.ps \
+ cvs.texinfo \
+ cvsclient.texi
+
+OBJDIR_DISTFILES = cvs.ps cvs.info cvs.aux \
+ cvsclient.ps cvsclient.info cvsclient.aux CVSvn.texi
+
+# these are part of the texinfo distribution
+MAKEINFO=makeinfo
+TEXI2DVI = texi2dvi
+
+# where to find texinfo;
+TEXIDIR=${gdbdir}/../texinfo
+
+SET_TEXINPUTS = TEXINPUTS=.:$(srcdir):$$TEXINPUTS
+
+# Don Knuth's TeX formatter
+TEX = tex
+
+# auxiliary program for sorting Texinfo indices
+TEXINDEX = texindex
+
+DVIPS = dvips
+DVIPSFLAGS =
+
+ROFF = groff
+
+# CYGNUS LOCAL: all does not depend upon info
+all:
+.PHONY: all
+
+# CYGNUS LOCAL: install does not depend on install-info
+install: all
+.PHONY: install
+
+doc: cvs.ps cvs-paper.ps cvsclient.ps
+
+info: cvs.info cvsclient.info
+
+cvs.info: cvs.texinfo CVSvn.texi
+ if [ ! -f ./CVSvn.texi ]; then \
+ ln -s $(srcdir)/CVSvn.texi . || \
+ ln $(srcdir)/CVSvn.texi . || \
+ cp $(srcdir)/CVSvn.texi . ; else true; fi
+ $(MAKEINFO) $(srcdir)/cvs.texinfo -o cvs.info
+
+cvsclient.info: cvsclient.texi CVSvn.texi
+ if [ ! -f ./CVSvn.texi ]; then \
+ ln -s $(srcdir)/CVSvn.texi . || \
+ ln $(srcdir)/CVSvn.texi . || \
+ cp $(srcdir)/CVSvn.texi . ; else true; fi
+ $(MAKEINFO) $(srcdir)/cvsclient.texi -o cvsclient.info
+
+# Version of the protocol suitable for emailing
+cvsclient.txt: cvsclient.texi CVSvn.texi
+ if [ ! -f ./CVSvn.texi ]; then \
+ ln -s $(srcdir)/CVSvn.texi . || \
+ ln $(srcdir)/CVSvn.texi . || \
+ cp $(srcdir)/CVSvn.texi . ; else true; fi
+ $(MAKEINFO) $(srcdir)/cvsclient.texi --no-headers -o cvsclient.txt
+
+# If the user gets a distribution (which contains *.info), unpacks
+# it, and builds it in a seperate build dir, then *.info* are in srcdir.
+# If the user builds *.info (e.g. after editing *.texi), then *.info* are
+# in the build dir.
+# (Note: don't solve this problem with "cd"; INSTALL_DATA might be a
+# relative path to install-sh).
+install-info: info installdirs
+ if test -f cvs.info ; then docdir=.; else docdir=$(srcdir);fi; \
+ for i in $$docdir/*.info* ; do \
+ $(INSTALL_DATA) $$i $(infodir)/`basename $$i` ; \
+ done
+
+installdirs:
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(infodir)
+.PHONY: installdirs
+
+dvi: cvs.dvi cvsclient.dvi
+
+CVSvn.texi: $(top_srcdir)/src/version.c
+ echo "@set CVSVN `sed < $(top_srcdir)/src/version.c \
+ -e '/version_string/!d' \
+ -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \
+ -e q`" >CVSvn.new
+ mv CVSvn.new CVSvn.texi
+
+cvs.dvi cvs.aux: cvs.texinfo CVSvn.texi
+ if [ ! -f ./CVSvn.texi ]; then \
+ ln -s $(srcdir)/CVSvn.texi . || \
+ ln $(srcdir)/CVSvn.texi . || \
+ cp $(srcdir)/CVSvn.texi . ; else true; fi
+ $(TEXI2DVI) $(srcdir)/cvs.texinfo
+
+cvsclient.dvi cvsclient.aux: cvsclient.texi CVSvn.texi
+ if [ ! -f ./CVSvn.texi ]; then \
+ ln -s $(srcdir)/CVSvn.texi . || \
+ ln $(srcdir)/CVSvn.texi . || \
+ cp $(srcdir)/CVSvn.texi . ; else true; fi
+ $(TEXI2DVI) $(srcdir)/cvsclient.texi
+ rm -f cvsclient.?? cvsclient.log cvsclient.toc cvsclient.??s
+
+cvs.ps: cvs.dvi
+ $(DVIPS) $(DVIPSFLAGS) cvs.dvi -o cvs.ps
+
+cvs-paper.ps: cvs-paper.ms
+ $(ROFF) -t -p -ms -Tps $(srcdir)/cvs-paper.ms > $@-t
+ mv $@-t $@
+
+cvsclient.ps: cvsclient.dvi
+ $(DVIPS) $(DVIPSFLAGS) cvsclient.dvi -o cvsclient.ps
+
+tags:
+.PHONY: tags
+
+TAGS:
+.PHONY: TAGS
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+clean:
+ rm -f *.o core
+ rm -f cvs.cp cvs.fn cvs.ky cvs.pg cvs.tp cvs.vr
+ rm -f cvs.cps cvs.fns cvs.kys cvs.pgs cvs.tps cvs.vrs
+ rm -f cvs.aux cvs.dvi cvs.log cvs.toc
+ rm -f cvsclient.cp cvsclient.fn cvsclient.ky cvsclient.pg
+ rm -f cvsclient.tp cvsclient.vr cvsclient.cps cvsclient.fns
+ rm -f cvsclient.kys cvsclient.pgs cvsclient.tps cvsclient.vrs
+ rm -f cvsclient.aux cvsclient.dvi cvsclient.log cvsclient.toc
+
+.PHONY: clean
+
+distclean: clean
+ rm -f Makefile
+.PHONY: distclean
+
+realclean: distclean
+ rm -f cvs.info* cvs.ps cvs-paper.ps cvsclient.info* cvsclient.ps
+.PHONY: realclean
+
+dist-dir: $(DISTFILES) $(OBJDIR_DISTFILES)
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+ ln ${OBJDIR_DISTFILES} ${DISTDIR}
+ if [ -f cvs.info-1 ]; \
+ then ln -f cvs.info-* ${DISTDIR}; \
+ else : Pacify Ultrix sh; \
+ fi
+ if [ -f cvsclient.info-1 ]; \
+ then ln -f cvsclient.info-* ${DISTDIR}; \
+ else : Pacify Ultrix sh; \
+ fi
+.PHONY: dist-dir
+
+subdir = doc
+Makefile: ../config.status Makefile.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
+
+#../config.status: ../configure
+# cd .. ; $(SHELL) config.status --recheck
+
+#../configure: ../configure.in
+# cd $(top_srcdir) ; autoconf
diff --git a/gnu/dist/cvs/doc/RCSFILES b/gnu/dist/cvs/doc/RCSFILES
new file mode 100644
index 00000000000..46503377901
--- /dev/null
+++ b/gnu/dist/cvs/doc/RCSFILES
@@ -0,0 +1,276 @@
+It would be nice if the RCS file format (which is implemented by a
+great many tools, both free and non-free, both by calling GNU RCS and
+by reimplementing access to RCS files) were documented in some
+standard separate from any one tool. But as far as I know no such
+standard exists. Hence this file.
+
+The place to start is the rcsfile.5 manpage in the GNU RCS 5.7
+distribution. Then look at the diff at the end of this file (which
+contains a few fixes and clarifications to that manpage).
+
+If you are interested in MKS RCS, src/ci.c in GNU RCS 5.7 has a
+comment about their date format. However, as far as we know there
+isn't really any document describing MKS's changes to the RCS file
+format.
+
+The rcsfile.5 manpage does not document what goes in the "text" field
+for each revision. The answer is that the head revision contains the
+contents of that revision and every other revision contain a bunch of
+edits to produce that revision ("a" and "d" lines). The GNU diff
+manual (the version I looked at was for GNU diff 2.4) documents this
+format somewhat (as the "RCS output format"), but the presentation is
+a bit confusing as it is all tangled up with the documentation of
+several other output formats. If you just want some source code to
+look at, the part of CVS which applies these is RCS_deltas in
+src/rcs.c.
+
+The rcsfile.5 documentation only _very_ briefly touches on the order
+of the revisions. The order _is_ important and CVS relies on it.
+Here is an example of what I was able to find, based on the join3
+sanity.sh testcase (and the behavior I am documenting here seems to be
+the same for RCS 5.7 and CVS 1.9.27):
+
+ 1.1 -----------------> 1.2
+ \---> 1.1.2.1 \---> 1.2.2.1
+
+Here is how this shows up in the RCS file (omitting irrelevant parts):
+
+ admin: head 1.2;
+ deltas:
+ 1.2 branches 1.2.2.1; next 1.1;
+ 1.1 branches 1.1.2.1; next;
+ 1.1.2.1 branches; next;
+ 1.2.2.1 branches; next;
+ deltatexts:
+ 1.2
+ 1.2.2.1
+ 1.1
+ 1.1.2.1
+
+Yes, the order seems to differ between the deltas and the deltatexts.
+I have no idea how much of this should actually be considered part of
+the RCS file format, and how much programs reading it should expect to
+encounter any order.
+
+The rcsfile.5 grammar shows the {num} after "next" as optional; if it
+is omitted then there is no next delta node (for example 1.1 or the
+head of a branch will typically have no next).
+
+There is one case where CVS uses CVS-specific, non-compatible changes
+to the RCS file format, and this is magic branches. See cvs.texinfo
+for more information on them. CVS also sets the RCS state to "dead"
+to indicate that a file does not exist in a given revision (this is
+stored just as any other RCS state is).
+
+The RCS file format allows quite a variety of extensions to be added
+in a compatible manner by use of the "newphrase" feature documented in
+rcsfile.5. We won't try to document extensions not used by CVS in any
+detail, but we will briefly list them. Each occurrence of a newphrase
+begins with an identifier, which is what we list here. Future
+designers of extensions are strongly encouraged to pick
+non-conflicting identifiers. Note that newphrase occurs several
+places in the RCS grammar, and a given extension may not be legal in
+all locations. However, it seems better to reserve a particular
+identifier for all locations, to avoid confusion and complicated
+rules.
+
+ Identifier Used by
+ ---------- -------
+ namespace RCS library done at Silicon Graphics Inc. (SGI) in 1996
+ (a modified RCS 5.7--not sure it has any other name).
+ dead A set of RCS patches developed by Rich Pixley at
+ Cygnus about 1992. These were for CVS, and predated
+ the current CVS death support, which uses a state "dead"
+ rather than a "dead" newphrase.
+
+CVS does use newphrases to implement the `PreservePermissions'
+extension introduced in CVS 1.9.26. The following new keywords are
+defined when PreservePermissions=yes:
+
+ owner
+ group
+ permissions
+ special
+ symlink
+ hardlinks
+
+The contents of the `owner' and `group' field should be a numeric uid
+and a numeric gid, respectively, representing the user and group who
+own the file. The `permissions' field contains an octal integer,
+representing the permissions that should be applied to the file. The
+`special' field contains two words; the first must be either `block'
+or `character', and the second is the file's device number. The
+`symlink' field should be present only in files which are symbolic
+links to other files, and absent on all regular files. The
+`hardlinks' field contains a list of filenames to which the current
+file is linked, in alphabetical order. Because files often contain
+characters special to RCS, like `.' and sometimes even contain spaces
+or eight-bit characters, the filenames in the hardlinks field will
+usually be enclosed in RCS strings. For example:
+
+ hardlinks README @install.txt@ @Installation Notes@;
+
+The hardlinks field should always include the name of the current
+file. That is, in the repository file README,v, any hardlinks fields
+in the delta nodes should include `README'; CVS will not operate
+properly if this is not done.
+
+The rules regarding keyword expansion are not documented along with
+the rest of the RCS file format; they are documented in the co(1)
+manpage in the RCS 5.7 distribution. See also the "Keyword
+substitution" chapter of cvs.texinfo. The co(1) manpage refers to
+special behavior if the log prefix for the $Log keyword is /* or (*.
+RCS 5.7 produces a warning whenever it behaves that way, and current
+versions of CVS do not handle this case in a special way (CVS 1.9 and
+earlier invoke RCS to perform keyword expansion).
+
+Note that if the "expand" keyword is omitted from the RCS file, the
+default is "kv".
+
+Note that the "comment {string};" syntax from rcsfile.5 specifies a
+comment leader, which affects expansion of the $Log keyword for old
+versions of RCS. The comment leader is not used by RCS 5.7 or current
+versions of CVS.
+
+Both RCS 5.7 and current versions of CVS handle the $Log keyword in a
+different way if the log message starts with "checked in with -k by ".
+I don't think this behavior is documented anywhere.
+
+Here is a clarification regarding characters versus bytes in certain
+character sets like JIS and Big5:
+
+ The RCS file format, as described in the rcsfile(5) man page, is
+ actually byte-oriented, not character-oriented, despite hints to
+ the contrary in the man page. This distinction is important for
+ multibyte characters. For example, if a multibyte character
+ contains a `@' byte, the `@' must be doubled within strings in RCS
+ files, since RCS uses `@' bytes as escapes.
+
+ This point is not an issue for encodings like ISO 8859, which do
+ not have multibyte characters. Nor is it an issue for encodings
+ like UTF-8 and EUC-JIS, which never uses ASCII bytes within a
+ multibyte character. It is an issue only for multibyte encodings
+ like JIS and BIG5, which _do_ usurp ASCII bytes.
+
+ If `@' doubling occurs within a multibyte char, the resulting RCS
+ file is not a properly encoded text file. Instead, it is a byte
+ stream that does not use a consistent character encoding that can
+ be understood by the usual text tools, since doubling `@' messes
+ up the encoding. This point affects only programs that examine
+ the RCS files -- it doesn't affect the external RCS interface, as
+ the RCS commands always give you the properly encoded text files
+ and logs (assuming that you always check in properly encoded
+ text).
+
+ CVS 1.10 (and earlier) probably has some bugs in this area on
+ systems where a C "char" is signed and where the data contains
+ bytes with the eighth bit set.
+
+One common concern about the RCS file format is the fact that to get
+the head of a branch, one must apply deltas from the head of the trunk
+to the branchpoint, and then from the branchpoint to the head of the
+branch. While more detailed analyses might be worth doing, we will
+note:
+
+ * The performance bottleneck for CVS generally is figuring out which
+ files to operate on and that sort of thing, not applying deltas.
+
+ * Here is one quick test (probably not a very good test; a better test
+ would use a normally sized file (say 50-200K) instead of a small one):
+
+ I just did a quick test with a small file (on a Sun Ultra 1/170E
+ running Solaris 5.5.1), with 1000 revisions on the main branch and
+ 1000 revisions on branch that forked at the root (i.e., RCS revisions
+ 1.1, 1.2, ..., 1.1000, and branch revisions 1.1.1.1, 1.1.1.2, ...,
+ 1.1.1.1000). It took about 0.15 seconds real time to check in the
+ first revision, and about 0.6 seconds to check in and 0.3 seconds to
+ retrieve revision 1.1.1.1000 (the worst case).
+
+ * Any attempt to "fix" this problem should be careful not to interfere
+ with other features, such as lightweight creation of branches
+ (particularly using CVS magic branches).
+
+Diff follows:
+
+(Note that in the following diff the old value for the Id keyword was:
+ Id: rcsfile.5in,v 5.6 1995/06/05 08:28:35 eggert Exp
+and the new one was:
+ Id: rcsfile.5in,v 5.7 1996/12/09 17:31:44 eggert Exp
+but since this file itself might be subject to keyword expansion I
+haven't included a diff for that fact).
+
+===================================================================
+RCS file: RCS/rcsfile.5in,v
+retrieving revision 5.6
+retrieving revision 5.7
+diff -u -r5.6 -r5.7
+--- rcsfile.5in 1995/06/05 08:28:35 5.6
++++ rcsfile.5in 1996/12/09 17:31:44 5.7
+@@ -85,7 +85,8 @@
+ .LP
+ \f2sym\fP ::= {\f2digit\fP}* \f2idchar\fP {\f2idchar\fP | \f2digit\fP}*
+ .LP
+-\f2idchar\fP ::= any visible graphic character except \f2special\fP
++\f2idchar\fP ::= any visible graphic character,
++ except \f2digit\fP or \f2special\fP
+ .LP
+ \f2special\fP ::= \f3$\fP | \f3,\fP | \f3.\fP | \f3:\fP | \f3;\fP | \f3@\fP
+ .LP
+@@ -119,12 +120,23 @@
+ the minute (00\-59),
+ and
+ .I ss
+-the second (00\-60).
++the second (00\-59).
++If
+ .I Y
+-contains just the last two digits of the year
+-for years from 1900 through 1999,
+-and all the digits of years thereafter.
+-Dates use the Gregorian calendar; times use UTC.
++contains exactly two digits,
++they are the last two digits of a year from 1900 through 1999;
++otherwise,
++.I Y
++contains all the digits of the year.
++Dates use the Gregorian calendar.
++Times use UTC, except that for portability's sake leap seconds are not allowed;
++implementations that support leap seconds should output
++.B 59
++for
++.I ss
++during an inserted leap second, and should accept
++.B 59
++for a deleted leap second.
+ .PP
+ The
+ .I newphrase
+@@ -144,16 +156,23 @@
+ 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
++field points to the head of that sequence (i.e., contains
+ the highest pair).
+ The
+ .B branch
+-node in the admin node indicates the default
++field indicates the default
+ branch (or revision) for most \*r operations.
+ If empty, the default
+ branch is the highest branch on the trunk.
++The
++.B symbols
++field associates symbolic names with revisions.
++For example, if the file contains
++.B "symbols rr:1.1;"
++then
++.B rr
++is a name for revision
++.BR 1.1 .
+ .PP
+ All
+ .I delta
+
diff --git a/gnu/dist/cvs/doc/cvs-paper.ms b/gnu/dist/cvs/doc/cvs-paper.ms
new file mode 100644
index 00000000000..ea9445ad168
--- /dev/null
+++ b/gnu/dist/cvs/doc/cvs-paper.ms
@@ -0,0 +1,1069 @@
+.\" soelim cvs.ms | pic | tbl | troff -ms
+.\" @(#)cvs.ms 1.2 92/01/30
+.\"
+.\" troff source to the cvs USENIX article, Winter 1990, Washington, D.C.
+.\" Copyright (c) 1989, Brian Berliner
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation; either version 1, or (at your option)
+.\" any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+.\" GNU General Public License for more details.
+.\"
+.\" The author can be reached at: berliner@prisma.com
+.\"
+.de SP
+.if n .sp
+.if t .sp .5
+..
+.de hl
+.br
+.in +0.5i
+\l'\\n(LLu-1i'
+.in -0.5i
+.sp
+..
+.OH ""
+.nr PS 11
+.nr PO 1.25i
+.pl -0.2i
+.TL
+.ps 14
+.ft B
+.nf
+CVS II:
+Parallelizing Software Development
+.fi
+.ft
+.ps
+.AU
+.ps 12
+.ft I
+Brian Berliner
+.ft
+.ps
+.AI
+.ps 12
+.ft I
+Prisma, Inc.
+5465 Mark Dabling Blvd.
+Colorado Springs, CO 80918
+berliner@prisma.com
+.ft
+.ps
+.AB
+The program described in this paper fills a need in the UNIX
+community for a freely available tool to manage software revision and
+release control in a multi-developer, multi-directory, multi-group
+environment.
+This tool also addresses the increasing need for tracking third-party vendor
+source distributions while trying to maintain local modifications to
+earlier releases.
+.AE
+.NH
+Background
+.PP
+In large software development projects, it is usually necessary for more
+than one software developer to be modifying (usually different) modules of the
+code at the same time.
+Some of these code modifications are done in an
+experimental sense, at least until the code functions correctly, and some
+testing of the entire program is usually necessary.
+Then, the modifications are returned to a master source repository
+so that others in the project can
+enjoy the new bug-fix or functionality.
+In order to manage such a project, some sort of revision control system is
+necessary.
+.PP
+Specifically, UNIX\**
+.FS
+UNIX is a registered trademark of AT&T.
+.FE
+kernel development is an excellent example of the
+problems that an adequate revision control system must address.
+The SunOS\**
+.FS
+SunOS is a trademark of Sun Microsystems, Inc.
+.FE
+kernel is composed of over a thousand files spread across a
+hierarchy of dozens of directories.\**
+.FS
+Yes, the SunOS 4.0 kernel is composed of over a \fIthousand\fP files!
+.FE
+Pieces of the kernel must be edited
+by many software developers within an organization.
+While undesirable in
+theory, it is not uncommon to have two or more people making
+modifications to the same file within the kernel sources in
+order to facilitate a desired change.
+Existing revision control systems like
+.SM
+RCS
+.LG
+[Tichy] or
+.SM
+SCCS
+.LG
+[Bell] serialize file modifications by
+allowing only one developer to have a writable copy of a particular file at
+any one point in time.
+That developer is said to
+have \*Qlocked\*U the file for his exclusive use, and no other developer is
+allowed to check out a writable copy of the file until the locking
+developer has finished impeding others' productivity.
+Development pressures of productivity and deadlines
+often force organizations to require that multiple developers be able to
+simultaneously edit
+copies of the same revision controlled file.
+.PP
+The necessity for multiple developers to modify the same file concurrently
+questions the value of serialization-based policies in traditional revision
+control.
+This paper discusses the approach that
+Prisma took in adapting a standard revision control system,
+.SM
+RCS\c
+.LG
+, along with an existing public-domain collection of shell scripts that sits
+atop
+.SM
+RCS
+.LG
+and provides the basic conflict-resolution algorithms.
+The resulting
+program, \fBcvs\fP, addresses not only the issue of conflict-resolution in
+a multi-developer open-editing environment, but also the issues of
+software release control and vendor source support and integration.
+.NH
+The CVS Program
+.PP
+\fBcvs\fP
+(Concurrent Versions System)
+is a front end to the
+.SM
+RCS
+.LG
+revision control system which extends
+the notion of revision control from a collection of files in a single
+directory to a hierarchical collection of directories each containing
+revision controlled files.
+Directories and files in the \fBcvs\fP system can be combined together in
+many ways to form a software release.
+\fBcvs\fP
+provides the functions necessary to manage these software releases and to
+control the concurrent editing of source files among multiple software
+developers.
+.PP
+The six major features of \fBcvs\fP are listed below, and will be
+described in more detail in the following sections:
+.RS
+.IP 1.
+Concurrent access and conflict-resolution algorithms to guarantee that
+source changes are not \*Qlost.\*U
+.IP 2.
+Support for tracking third-party vendor source distributions while
+maintaining the local modifications made to those sources.
+.IP 3.
+A flexible module database that provides a symbolic mapping of names to
+components of a larger software distribution.
+This symbolic mapping provides for location independence within the software
+release and, for example, allows one to check out a copy of the \*Qdiff\*U
+program without ever knowing that the sources to \*Qdiff\*U actually reside
+in the \*Qbin/diff\*U directory.
+.IP 4.
+Configurable logging support allows all \*Qcommitted\*U source file changes
+to be logged using an arbitrary program to save the log messages in a file,
+notesfile, or news database.
+.IP 5.
+A software release can be symbolically tagged and checked out at any time
+based on that tag.
+An exact copy of a previous software release can be checked out at
+any time, \fIregardless\fP of whether files or directories have been
+added/removed from the \*Qcurrent\*U software release.
+As well,
+a \*Qdate\*U can be used to check out the \fIexact\fP version of the software
+release as of the specified date.
+.IP 6.
+A \*Qpatch\*U format file [Wall] can be produced between two software
+releases, even if the releases span multiple directories.
+.RE
+.PP
+The sources maintained by \fBcvs\fP are kept within a single directory
+hierarchy known as the \*Qsource repository.\*U
+This \*Qsource repository\*U holds the actual
+.SM
+RCS
+.LG
+\*Q,v\*U files directly, as well as a special per-repository directory
+(\c
+.SM
+CVSROOT.adm\c
+.LG
+) which contains a small number of administrative files that describe the
+repository and how it can be accessed.
+See Figure 1 for a picture of the \fBcvs\fP tree.
+.KF
+.hl
+.DS B
+.PS
+line from 4.112,9.200 to 5.550,8.887
+line from 5.447,8.884 to 5.550,8.887 to 5.458,8.933
+line from 4.112,9.200 to 4.550,8.950
+line from 4.451,8.978 to 4.550,8.950 to 4.476,9.021
+line from 4.112,9.200 to 3.737,8.887
+line from 3.798,8.971 to 3.737,8.887 to 3.830,8.932
+line from 3.612,8.762 to 4.737,8.137
+line from 4.638,8.164 to 4.737,8.137 to 4.662,8.208
+line from 3.612,8.762 to 3.737,8.137
+line from 3.693,8.231 to 3.737,8.137 to 3.742,8.240
+line from 3.612,8.762 to 2.612,8.200
+line from 2.687,8.271 to 2.612,8.200 to 2.712,8.227
+line from 2.362,9.262 to 2.737,8.950
+line from 2.645,8.995 to 2.737,8.950 to 2.677,9.033
+line from 2.362,9.262 to 1.925,8.950
+line from 1.992,9.028 to 1.925,8.950 to 2.021,8.988
+line from 3.362,9.762 to 4.050,9.387
+line from 3.950,9.413 to 4.050,9.387 to 3.974,9.457
+line from 3.362,9.762 to 2.487,9.387
+line from 2.570,9.450 to 2.487,9.387 to 2.589,9.404
+.ps 11
+"newfs.c,v" at 4.487,8.043 ljust
+.ps 11
+"mkfs.c,v" at 3.487,8.043 ljust
+.ps 11
+"Makefile,v" at 2.237,8.043 ljust
+.ps 11
+"newfs" at 3.487,8.793 ljust
+.ps 11
+"halt.c,v" at 5.487,8.793 ljust
+.ps 11
+"Makefile,v" at 4.237,8.793 ljust
+.ps 11
+"modules,v" at 2.487,8.793 ljust
+.ps 11
+"loginfo,v" at 1.488,8.793 ljust
+.ps 11
+"etc" at 3.987,9.293 ljust
+.ps 11
+"CVSROOT.adm" at 1.988,9.293 ljust
+.ps 11
+"/src/master" at 2.987,9.793 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 1.\fP
+.SM
+\fBcvs\fP Source Repository
+.ce 0
+.sp
+.KE
+.NH 2
+Software Conflict Resolution\**
+.FS
+The basic conflict-resolution algorithms
+used in the \fBcvs\fP program find their roots
+in the original work done by Dick Grune at Vrije Universiteit in Amsterdam
+and posted to \fBcomp.sources.unix\fP in the volume 6 release sometime in 1986.
+This original version of \fBcvs\fP was a collection of shell scripts that
+combined to form a front end to the
+.SM
+RCS
+.LG
+programs.
+.FE
+.PP
+\fBcvs\fP allows several software developers to edit personal copies of a
+revision controlled file concurrently.
+The revision number of each checked out file is maintained independently
+for each user, and \fBcvs\fP forces the checked out file to be current with
+the \*Qhead\*U revision before it can be \*Qcommitted\*U as a permanent change.
+A checked out file is brought up-to-date with the \*Qhead\*U revision using
+the \*Qupdate\*U command of \fBcvs\fP.
+This command compares the \*Qhead\*U revision number with that of the user's
+file and performs an
+.SM
+RCS
+.LG
+merge operation if they are not the same.
+The result of the merge is a file that contains the user's modifications
+and those modifications that were \*Qcommitted\*U after the user
+checked out his version of the file (as well as a backup copy of the
+user's original file).
+\fBcvs\fP points out any conflicts during the merge.
+It is the user's responsibility to resolve these conflicts
+and to \*Qcommit\*U his/her changes when ready.
+.PP
+Although the \fBcvs\fP conflict-resolution algorithm was defined in 1986,
+it is remarkably similar to the \*QCopy-Modify-Merge\*U scenario included
+with NSE\**
+.FS
+NSE is the Network Software Environment, a product of Sun Microsystems, Inc.
+.FE
+and described in [Honda] and [Courington].
+The following explanation from [Honda] also applies to \fBcvs\fP:
+.QP
+Simply stated, a developer copies an object without locking it, modifies
+the copy, and then merges the modified copy with the original.
+This paradigm allows developers to work in isolation from one another since
+changes are made to copies of objects.
+Because locks are not used, development is not serialized and can proceed
+in parallel.
+Developers, however, must merge objects after the changes have been made.
+In particular, a developer must resolve conflicts when the same object has
+been modified by someone else.
+.PP
+In practice, Prisma has found that conflicts that occur when the same
+object has been modified by someone else are quite rare.
+When they do happen, the changes made by the other developer are usually
+easily resolved.
+This practical use has shown that the \*QCopy-Modify-Merge\*U paradigm is a
+correct and useful one.
+.NH 2
+Tracking Third-Party Source Distributions
+.PP
+Currently, a large amount of software is based on source
+distributions from a third-party distributor.
+It is often the case that local modifications are to be made to this
+distribution, \fIand\fP that the vendor's future releases should be
+tracked.
+Rolling your local modifications forward into the new vendor release is a
+time-consuming task, but \fBcvs\fP can ease this burden somewhat.
+The \fBcheckin\fP program of \fBcvs\fP initially sets up a source
+repository by integrating the source modules directly from the vendor's
+release, preserving the directory hierarchy of the vendor's distribution.
+The branch support of
+.SM
+RCS
+.LG
+is used to build this vendor release as a branch of the main
+.SM
+RCS
+.LG
+trunk.
+Figure 2 shows how the \*Qhead\*U tracks a sample vendor
+branch when no local modifications have been made to the file.
+.KF
+.hl
+.DS B
+.PS
+ellipse at 3.237,6.763 wid 1.000 ht 0.500
+dashwid = 0.050i
+line dashed from 3.237,7.513 to 3.737,7.513 to 3.737,9.762 to 4.237,9.762
+line from 4.138,9.737 to 4.237,9.762 to 4.138,9.787
+line dashed from 2.237,8.262 to 3.237,8.262 to 3.237,7.013
+line from 3.212,7.112 to 3.237,7.013 to 3.262,7.112
+line from 3.737,6.763 to 4.237,6.763
+line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788
+line from 2.237,6.763 to 2.737,6.763
+line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788
+line from 1.738,6.013 to 1.738,6.513
+line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413
+line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013
+line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012
+line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012
+line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013
+line from 4.737,7.013 to 4.737,7.513
+line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413
+line from 4.737,8.012 to 4.737,8.512
+line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412
+line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012
+line from 4.737,9.012 to 4.737,9.512
+line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412
+line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013
+.ps 11
+"\"HEAD\"" at 1.550,8.231 ljust
+.ps 11
+"'SunOS'" at 2.987,6.293 ljust
+.ps 11
+"1.1.1" at 3.050,6.793 ljust
+.ps 11
+"1.1" at 1.613,6.793 ljust
+.ps 11
+"1.1.1.1" at 4.487,6.793 ljust
+.ps 11
+"1.1.1.2" at 4.487,7.793 ljust
+.ps 11
+"1.1.1.3" at 4.487,8.793 ljust
+.ps 11
+"1.1.1.4" at 4.487,9.793 ljust
+.ps 11
+"'SunOS_4_0'" at 5.487,6.793 ljust
+.ps 11
+"'SunOS_4_0_1'" at 5.487,7.793 ljust
+.ps 11
+"'YAPT_5_5C'" at 5.487,8.793 ljust
+.ps 11
+"'SunOS_4_0_3'" at 5.487,9.793 ljust
+.ps 11
+"rcsfile.c,v" at 2.987,5.543 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 2.\fP
+.SM
+\fBcvs\fP Vendor Branch Example
+.ce 0
+.sp .3
+.KE
+Once this is done, developers can check out files and make local changes to
+the vendor's source distribution.
+These local changes form a new branch to the tree which is then used as the
+source for future check outs.
+Figure 3 shows how the \*Qhead\*U moves to the main
+.SM
+RCS
+.LG
+trunk when a local modification is made.
+.KF
+.hl
+.DS B
+.PS
+ellipse at 3.237,6.763 wid 1.000 ht 0.500
+dashwid = 0.050i
+line dashed from 2.800,9.075 to 1.738,9.075 to 1.738,8.012
+line from 1.713,8.112 to 1.738,8.012 to 1.762,8.112
+line from 1.738,7.013 to 1.738,7.513
+line from 1.762,7.413 to 1.738,7.513 to 1.713,7.413
+line from 1.238,8.012 to 2.237,8.012 to 2.237,7.513 to 1.238,7.513 to 1.238,8.012
+line from 3.737,6.763 to 4.237,6.763
+line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788
+line from 2.237,6.763 to 2.737,6.763
+line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788
+line from 1.738,6.013 to 1.738,6.513
+line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413
+line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013
+line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012
+line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012
+line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013
+line from 4.737,7.013 to 4.737,7.513
+line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413
+line from 4.737,8.012 to 4.737,8.512
+line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412
+line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012
+line from 4.737,9.012 to 4.737,9.512
+line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412
+line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013
+.ps 11
+"1.2" at 1.613,7.793 ljust
+.ps 11
+"\"HEAD\"" at 2.862,9.043 ljust
+.ps 11
+"'SunOS'" at 2.987,6.293 ljust
+.ps 11
+"1.1.1" at 3.050,6.793 ljust
+.ps 11
+"1.1" at 1.613,6.793 ljust
+.ps 11
+"1.1.1.1" at 4.487,6.793 ljust
+.ps 11
+"1.1.1.2" at 4.487,7.793 ljust
+.ps 11
+"1.1.1.3" at 4.487,8.793 ljust
+.ps 11
+"1.1.1.4" at 4.487,9.793 ljust
+.ps 11
+"'SunOS_4_0'" at 5.487,6.793 ljust
+.ps 11
+"'SunOS_4_0_1'" at 5.487,7.793 ljust
+.ps 11
+"'YAPT_5_5C'" at 5.487,8.793 ljust
+.ps 11
+"'SunOS_4_0_3'" at 5.487,9.793 ljust
+.ps 11
+"rcsfile.c,v" at 2.987,5.543 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 3.\fP
+.SM
+\fBcvs\fP Local Modification to Vendor Branch
+.ce 0
+.sp
+.KE
+.PP
+When a new version of the vendor's source distribution arrives, the
+\fBcheckin\fP program adds the new and changed vendor's files to the
+already existing source repository.
+For files that have not been changed locally, the new file from the
+vendor becomes the current \*Qhead\*U revision.
+For files that have been modified locally, \fBcheckin\fP warns that the
+file must be merged with the new vendor release.
+The \fBcvs\fP \*Qjoin\*U command is a useful tool that aids this process by
+performing the necessary
+.SM
+RCS
+.LG
+merge, as is done above when performing an \*Qupdate.\*U
+.PP
+There is also limited support for \*Qdual\*U derivations for source files.
+See Figure 4 for a sample dual-derived file.
+.KF
+.hl
+.DS B
+.PS
+ellipse at 2.337,8.575 wid 0.700 ht 0.375
+ellipse at 2.312,9.137 wid 0.700 ht 0.375
+line from 1.225,9.012 to 1.225,9.363
+line from 1.250,9.263 to 1.225,9.363 to 1.200,9.263
+line from 0.875,9.725 to 1.600,9.725 to 1.600,9.363 to 0.875,9.363 to 0.875,9.725
+line from 0.875,9.012 to 1.600,9.012 to 1.600,8.650 to 0.875,8.650 to 0.875,9.012
+line from 4.050,10.200 to 4.775,10.200 to 4.775,9.850 to 4.050,9.850 to 4.050,10.200
+line from 4.050,9.475 to 4.775,9.475 to 4.775,9.113 to 4.050,9.113 to 4.050,9.475
+line from 4.050,8.762 to 4.775,8.762 to 4.775,8.400 to 4.050,8.400 to 4.050,8.762
+line from 4.425,8.762 to 4.425,9.113
+line from 4.450,9.013 to 4.425,9.113 to 4.400,9.013
+line from 4.425,9.475 to 4.425,9.850
+line from 4.450,9.750 to 4.425,9.850 to 4.400,9.750
+line from 3.050,10.000 to 3.775,10.000 to 3.775,9.637 to 3.050,9.637 to 3.050,10.000
+line from 3.050,9.312 to 3.775,9.312 to 3.775,8.950 to 3.050,8.950 to 3.050,9.312
+line from 0.713,7.325 to 0.713,8.075 to 4.925,8.075 to 4.925,7.325 to 0.713,7.325
+line from 1.238,8.075 to 1.238,8.637
+line from 1.262,8.537 to 1.238,8.637 to 1.213,8.537
+line from 1.613,8.825 to 1.975,8.575
+line from 1.878,8.611 to 1.975,8.575 to 1.907,8.652
+line from 2.675,8.575 to 4.050,8.575
+line from 3.950,8.550 to 4.050,8.575 to 3.950,8.600
+line from 2.675,9.137 to 3.050,9.137
+line from 2.950,9.112 to 3.050,9.137 to 2.950,9.162
+line from 3.425,9.325 to 3.425,9.637
+line from 3.450,9.537 to 3.425,9.637 to 3.400,9.537
+line from 1.613,8.825 to 1.925,9.137
+line from 1.872,9.049 to 1.925,9.137 to 1.837,9.084
+.ps 11
+"'BSD'" at 2.138,9.481 ljust
+.ps 11
+"1.2" at 1.113,9.543 ljust
+.ps 11
+"1.1" at 1.125,8.831 ljust
+.ps 11
+"1.1.1.1" at 4.175,8.543 ljust
+.ps 11
+"1.1.1.2" at 4.175,9.281 ljust
+.ps 11
+"1.1.1.3" at 4.175,9.993 ljust
+.ps 11
+"1.1.2.2" at 3.175,9.793 ljust
+.ps 11
+"1.1.2.1" at 3.175,9.106 ljust
+.ps 11
+"rcsfile.c,v" at 2.425,7.706 ljust
+.ps 11
+"1.1.1" at 2.175,8.568 ljust
+.ps 11
+"'SunOS'" at 2.125,8.243 ljust
+.ps 11
+"1.1.2" at 2.163,9.131 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 4.\fP
+.SM
+\fBcvs\fP Support For \*QDual\*U Derivations
+.ce 0
+.sp
+.KE
+This example tracks the SunOS distribution but includes major changes from
+Berkeley.
+These BSD files are saved directly in the
+.SM
+RCS
+.LG
+file off a new branch.
+.NH 2
+Location Independent Module Database
+.PP
+\fBcvs\fP contains support for a simple, yet powerful, \*Qmodule\*U database.
+For reasons of efficiency, this database is stored in \fBndbm\fP\|(3) format.
+The module database is used to apply names to collections of directories
+and files as a matter of convenience for checking out pieces of a large
+software distribution.
+The database records the physical location of the sources as a form of
+information hiding, allowing one to check out whole directory hierarchies
+or individual files without regard for their actual location within the
+global source distribution.
+.PP
+Consider the following small sample of a module database, which must be
+tailored manually to each specific source repository environment:
+.DS
+\f(CW #key [-option argument] directory [files...]
+ diff bin/diff
+ libc lib/libc
+ sys -o sys/tools/make_links sys
+ modules -i mkmodules CVSROOT.adm modules
+ kernel -a sys lang/adb
+ ps bin Makefile ps.c\fP
+.DE
+.PP
+The \*Qdiff\*U and \*Qlibc\*U modules refer to whole directory hierarchies that
+are extracted on check out.
+The \*Qsys\*U module extracts the \*Qsys\*U hierarchy, and runs the
+\*Qmake_links\*U program at the end of the check out process (the \fI-o\fP
+option specifies a program to run on check\fIo\fPut).
+The \*Qmodules\*U module allows one to edit the module database file and
+runs the \*Qmkmodules\*U program on check\fIi\fPn to regenerate the
+\fBndbm\fP database that \fBcvs\fP uses.
+The \*Qkernel\*U module is an alias (as the \fI-a\fP option specifies)
+which causes the remaining arguments after the \fI-a\fP to be interpreted
+exactly as if they had been specified on the command line.
+This is useful for objects that require shared pieces of code from far away
+places to be compiled (as is the case with the kernel debugger, \fBkadb\fP,
+which shares code with the standard \fBadb\fP debugger).
+The \*Qps\*U module shows that the source for \*Qps\*U lives in the \*Qbin\*U
+directory, but only \fIMakefile\fP and \fIps.c\fP are required to build the
+object.
+.PP
+The module database at Prisma is now populated for the entire UNIX
+distribution and thereby allows us to issue the
+following convenient commands to check out components of the UNIX
+distribution without regard for their actual location within the master source
+repository:
+.DS
+\f(CW example% cvs checkout diff
+ example% cvs checkout libc ps
+ example% cd diff; make\fP
+.DE
+.PP
+In building the module database file, it is quite possible to have name
+conflicts within a global software distribution.
+For example, SunOS provides two \fBcat\fP programs:
+one for the standard environment, \fI/bin/cat\fP, and one for the System V
+environment, \fI/usr/5bin/cat\fP.
+We resolved this conflict by naming the standard \fBcat\fP module
+\*Qcat\*U, and the System V \fBcat\fP module \*Q5cat\*U.
+Similar name modifications must be applied to other conflicting names, as
+might be found between a utility program and a library function, though
+Prisma chose not to include individual library functions within the module
+database at this time.
+.NH 2
+Configurable Logging Support
+.PP
+The \fBcvs\fP \*Qcommit\*U command is used to make a permanent change to the
+master source repository (where the
+.SM
+RCS
+.LG
+\*Q,v\*U files live).
+Whenever a \*Qcommit\*U is done, the log message for the change is carefully
+logged by an arbitrary program (in a file, notesfile, news database, or
+mail).
+For example, a collection of these updates can be used to produce release
+notices.
+\fBcvs\fP can be configured to send log updates through one or more filter
+programs, based on a regular expression match on the directory that is
+being changed.
+This allows multiple related or unrelated projects to exist within a single
+\fBcvs\fP source repository tree, with each different project sending its
+\*Qcommit\*U reports to a unique log device.
+.PP
+A sample logging configuration file might look as follows:
+.DS
+\f(CW #regex filter-program
+ DEFAULT /usr/local/bin/nfpipe -t %s utils.updates
+ ^diag /usr/local/bin/nfpipe -t %s diag.updates
+ ^local /usr/local/bin/nfpipe -t %s local.updates
+ ^perf /usr/local/bin/nfpipe -t %s perf.updates
+ ^sys /usr/local/bin/nfpipe -t %s kernel.updates\fP
+.DE
+.PP
+This sample allows the diagnostics and performance groups to
+share the same source repository with the kernel and utilities groups.
+Changes that they make are sent directly to their own notesfile [Essick]
+through the \*Qnfpipe\*U program.
+A sufficiently simple title is substituted for the \*Q%s\*U argument before
+the filter program is executed.
+This logging configuration file is tailored manually to each specific
+source repository environment.
+.NH 2
+Tagged Releases and Dates
+.PP
+Any release can be given a symbolic tag name that is stored directly in the
+.SM
+RCS
+.LG
+files.
+This tag can be used at any time to get an exact copy of any previous
+release.
+With equal ease, one can also extract an exact copy of the source files as
+of any arbitrary date in the past as well.
+Thus, all that's required to tag the current kernel, and to tag the kernel
+as of the Fourth of July is:
+.DS
+\f(CW example% cvs tag TEST_KERNEL kernel
+ example% cvs tag -D 'July 4' PATRIOTIC_KERNEL kernel\fP
+.DE
+The following command would retrieve an exact copy of the test kernel at
+some later date:
+.DS
+\f(CW example% cvs checkout -fp -rTEST_KERNEL kernel\fP
+.DE
+The \fI-f\fP option causes only files that match the specified tag to be
+extracted, while the \fI-p\fP option automatically prunes empty directories.
+Consequently, directories added to the kernel after the test kernel was
+tagged are not included in the newly extracted copy of the test kernel.
+.PP
+The \fBcvs\fP date support has exactly the same interface as that provided
+with
+.SM
+RCS\c
+.LG
+, however \fBcvs\fP must process the \*Q,v\*U files directly due to the
+special handling required by the vendor branch support.
+The standard
+.SM
+RCS
+.LG
+date handling only processes one branch (or the main trunk) when checking
+out based on a date specification.
+\fBcvs\fP must instead process the current \*Qhead\*U branch and, if a
+match is not found, proceed to look for a match on the vendor branch.
+This, combined with reasons of performance, is why \fBcvs\fP processes
+revision (symbolic and numeric) and date specifications directly from the
+\*Q,v\*U files.
+.NH 2
+Building \*Qpatch\*U Source Distributions
+.PP
+\fBcvs\fP can produce a \*Qpatch\*U format [Wall] output file which can be
+used to bring a previously released software distribution current with the
+newest release.
+This patch file supports an entire directory hierarchy within a single
+patch, as well as being able to add whole new files to the previous
+release.
+One can combine symbolic revisions and dates together to display changes in
+a very generic way:
+.DS
+\f(CW example% cvs patch -D 'December 1, 1988' \e
+ -D 'January 1, 1989' sys\fP
+.DE
+This example displays the kernel changes made in the month of December,
+1988.
+To release a patch file, for example, to take the \fBcvs\fP distribution
+from version 1.0 to version 1.4 might be done as follows:
+.DS
+\f(CW example% cvs patch -rCVS_1_0 -rCVS_1_4 cvs\fP
+.DE
+.NH
+CVS Experience
+.NH 2
+Statistics
+.PP
+A quick summary of the scale that \fBcvs\fP is addressing today
+can be found in Table 1.
+.KF
+.TS
+box center tab(:);
+c s
+c s
+c | c
+l | n .
+\fB\s+2Revision Control Statistics at Prisma
+as of 11/11/89\fP\s-2
+_
+How Many...:Total
+=
+Files:17243
+Directories:1005
+Lines of code:3927255
+Removed files:131
+Software developers:14
+Software groups:6
+Megabytes of source:128
+.TE
+.ce 100
+.LG
+\fBTable 1.\fP
+.SM
+\fBcvs\fP Statistics
+.ce 0
+.sp .3
+.KE
+Table 2 shows the history of files changed or added and the number
+of source lines affected by the change at Prisma.
+Only changes made to the kernel sources are included.
+.KF
+.TS
+box center tab(:);
+c s s s s
+c s s s s
+c || c | c || c | c
+c || c | c || c | c
+l || n | n || n | n.
+\fB\s+2Prisma Kernel Source File Changes
+By Month, 1988-1989\fP\s-2
+_
+Month:# Changed:# Lines:# Added:# Lines
+\^:Files:Changed:Files:Added
+=
+Dec:87:3619:68:9266
+Jan:39:4324:0:0
+Feb:73:1578:5:3550
+Mar:99:5301:18:11461
+Apr:112:7333:11:5759
+May:138:5371:17:13986
+Jun:65:2261:27:12875
+Jul:34:2000:1:58
+Aug:65:6378:8:4724
+Sep:266:23410:113:39965
+Oct:22:621:1:155
+Total:1000:62196:269:101799
+.TE
+.ce 100
+.LG
+\fBTable 2.\fP
+.SM
+\fBcvs\fP Usage History for the Kernel
+.ce 0
+.sp
+.KE
+The large number of source file changes made in September are the result of
+merging the SunOS 4.0.3 sources into the kernel.
+This merge process is described in section 3.3.
+.NH 2
+Performance
+.PP
+The performance of \fBcvs\fP is currently quite reasonable.
+Little effort has been expended on tuning \fBcvs\fP, although performance
+related decisions were made during the \fBcvs\fP design.
+For example, \fBcvs\fP parses the
+.SM
+RCS
+.LG
+\*Q,v\*U files directly instead of running an
+.SM
+RCS
+.LG
+process.
+This includes following branches as well as integrating with the vendor
+source branches and the main trunk when checking out files based on a date.
+.PP
+Checking out the entire kernel source tree (1223 files/59 directories)
+currently takes 16 wall clock minutes on a Sun-4/280.
+However, bringing the tree up-to-date with the current kernel sources, once
+it has been checked out, takes only 1.5 wall clock minutes.
+Updating the \fIcomplete\fP 128 MByte source tree under \fBcvs\fP control
+(17243 files/1005 directories) takes roughly 28 wall clock minutes and
+utilizes one-third of the machine.
+For now this is entirely acceptable; improvements on these numbers will
+possibly be made in the future.
+.NH 2
+The SunOS 4.0.3 Merge
+.PP
+The true test of the \fBcvs\fP vendor branch support came with the arrival
+of the SunOS 4.0.3 source upgrade tape.
+As described above, the \fBcheckin\fP program was used to install the new
+sources and the resulting output file listed the files that had been
+locally modified, needing to be merged manually.
+For the kernel, there were 94 files in conflict.
+The \fBcvs\fP \*Qjoin\*U command was used on each of the 94 conflicting
+files, and the remaining conflicts were resolved.
+.PP
+The \*Qjoin\*U command performs an \fBrcsmerge\fP operation.
+This in turn uses \fI/usr/lib/diff3\fP to produce a three-way diff file.
+As it happens, the \fBdiff3\fP program has a hard-coded limit of 200
+source-file changes maximum.
+This proved to be too small for a few of the kernel files that needed
+merging by hand, due to the large number of local changes that Prisma had
+made.
+The \fBdiff3\fP problem was solved by increasing the hard-coded limit by an
+order of magnitude.
+.PP
+The SunOS 4.0.3 kernel source upgrade distribution contained
+346 files, 233 of which were modifications to previously released files,
+and 113 of which were newly added files.
+\fBcheckin\fP added the 113 new files to the source repository
+without intervention.
+Of the 233 modified files, 139 dropped in cleanly by \fBcheckin\fP, since
+Prisma had not made any local changes to them, and 94 required manual
+merging due to local modifications.
+The 233 modified files consisted of 20,766 lines of differences.
+It took one developer two days to manually merge the 94 files using the
+\*Qjoin\*U command and resolving conflicts manually.
+An additional day was required for kernel debugging.
+The entire process of merging over 20,000 lines of differences was
+completed in less than a week.
+This one time-savings alone was justification enough for the \fBcvs\fP
+development effort; we expect to gain even more when tracking future SunOS
+releases.
+.NH
+Future Enhancements and Current Bugs
+.PP
+Since \fBcvs\fP was designed to be incomplete, for reasons of design
+simplicity, there are naturally a good
+number of enhancements that can be made to make it more useful.
+As well, some nuisances exist in the current implementation.
+.RS
+.IP \(bu 3
+\fBcvs\fP does not currently \*Qremember\*U who has a checked out a copy of a
+module.
+As a result, it is impossible to know who might be working on the same
+module that you are.
+A simple-minded database that is updated nightly would likely suffice.
+.IP \(bu 3
+Signal processing, keyboard interrupt handling in particular, is currently
+somewhat weak.
+This is due to the heavy use of the \fBsystem\fP\|(3) library
+function to execute
+.SM
+RCS
+.LG
+programs like \fBco\fP and \fBci\fP.
+It sometimes takes multiple interrupts to make \fBcvs\fP quit.
+This can be fixed by using a home-grown \fBsystem\fP\|() replacement.
+.IP \(bu 3
+Security of the source repository is currently not dealt with directly.
+The usual UNIX approach of user-group-other security permissions through
+the file system is utilized, but nothing else.
+\fBcvs\fP could likely be a set-group-id executable that checks a
+protected database to verify user access permissions for particular objects
+before allowing any operations to affect those objects.
+.IP \(bu 3
+With every checked-out directory, \fBcvs\fP maintains some administrative
+files that record the current revision numbers of the checked-out files as
+well as the location of the respective source repository.
+\fBcvs\fP does not recover nicely at all if these administrative files are
+removed.
+.IP \(bu 3
+The source code for \fBcvs\fP has been tested extensively on Sun-3 and
+Sun-4 systems, all running SunOS 4.0 or later versions of the operating
+system.
+Since the code has not yet been compiled under other platforms, the overall
+portability of the code is still questionable.
+.IP \(bu 3
+As witnessed in the previous section, the \fBcvs\fP method for tracking
+third party vendor source distributions can work quite nicely.
+However, if the vendor changes the directory structure or the file names
+within the source distribution, \fBcvs\fP has no way of matching the old
+release with the new one.
+It is currently unclear as to how to solve this, though it is certain to
+happen in practice.
+.RE
+.NH
+Availability
+.PP
+The \fBcvs\fP program sources can be found in a recent posting to the
+\fBcomp.sources.unix\fP newsgroup.
+It is also currently available via anonymous ftp from \*Qprisma.com\*U.
+Copying rights for \fBcvs\fP will be covered by the GNU General Public
+License.
+.NH
+Summary
+.PP
+Prisma has used \fBcvs\fP since December, 1988.
+It has evolved to meet our specific needs of revision and release control.
+We will make our code freely available so that others can
+benefit from our work, and can enhance \fBcvs\fP to meet broader needs yet.
+.PP
+Many of the other software release and revision control systems, like the
+one described in [Glew], appear to use a collection of tools that are
+geared toward specific environments \(em one set of tools for the kernel,
+one set for \*Qgeneric\*U software, one set for utilities, and one set for
+kernel and utilities.
+Each of these tool sets apparently handle some specific aspect of the
+problem uniquely.
+\fBcvs\fP took a somewhat different approach.
+File sharing through symbolic or hard links is not addressed; instead, the
+disk space is simply burned since it is \*Qcheap.\*U
+Support for producing objects for multiple architectures is not addressed;
+instead, a parallel checked-out source tree must be used for each
+architecture, again wasting disk space to simplify complexity and ease of
+use \(em punting on this issue allowed \fIMakefile\fPs to remain
+unchanged, unlike the approach taken in [Mahler], thereby maintaining closer
+compatibility with the third-party vendor sources.
+\fBcvs\fP is essentially a source-file server, making no assumptions or
+special handling of the sources that it controls.
+To \fBcvs\fP:
+.QP
+A source is a source, of course, of course, unless of course the source is
+Mr. Ed.\**
+.FS
+\fBcvs\fP, of course, does not really discriminate against Mr. Ed.\**
+.FE
+.FS
+Yet.
+.FE
+.LP
+Sources are maintained, saved, and retrievable at any time based on
+symbolic or numeric revision or date in the past.
+It is entirely up to \fBcvs\fP wrapper programs to provide for release
+environments and such.
+.PP
+The major advantage of \fBcvs\fP over the
+many other similar systems that have already been designed is the
+simplicity of \fBcvs\fP.
+\fBcvs\fP contains only three programs that do all the work of release
+and revision control, and two manually-maintained administrative
+files for each source repository.
+Of course, the deciding factor of any tool is whether people use it, and if
+they even \fIlike\fP to use it.
+At Prisma, \fBcvs\fP prevented members of the kernel
+group from killing each other.
+.NH
+Acknowledgements
+.PP
+Many thanks to Dick Grune at Vrije Universiteit in Amsterdam for his work
+on the original version of \fBcvs\fP and for making it available to the
+world.
+Thanks to Jeff Polk of Prisma for helping with the design of the module
+database, vendor branch support, and for writing the \fBcheckin\fP shell
+script.
+Thanks also to the entire software group at Prisma for taking the
+time to review the paper and correct my grammar.
+.NH
+References
+.IP [Bell] 12
+Bell Telephone Laboratories.
+\*QSource Code Control System User's Guide.\*U
+\fIUNIX System III Programmer's Manual\fP, October 1981.
+.IP [Courington] 12
+Courington, W.
+\fIThe Network Software Environment\fP,
+Sun Technical Report FE197-0, Sun Microsystems Inc, February 1989.
+.IP [Essick] 12
+Essick, Raymond B. and Robert Bruce Kolstad.
+\fINotesfile Reference Manual\fP,
+Department of Computer Science Technical Report #1081,
+University of Illinois at Urbana-Champaign, Urbana, Illinois,
+1982, p. 26.
+.IP [Glew] 12
+Glew, Andy.
+\*QBoxes, Links, and Parallel Trees:
+Elements of a Configuration Management System.\*U
+\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX,
+New Orleans, April 1989.
+.IP [Grune] 12
+Grune, Dick.
+Distributed the original shell script version of \fBcvs\fP in the
+\fBcomp.sources.unix\fP volume 6 release in 1986.
+.IP [Honda] 12
+Honda, Masahiro and Terrence Miller.
+\*QSoftware Management Using a CASE Environment.\*U
+\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX,
+New Orleans, April 1989.
+.IP [Mahler] 12
+Mahler, Alex and Andreas Lampen.
+\*QAn Integrated Toolset for Engineering Software Configurations.\*U
+\fIProceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on
+Practical Software Development Environments\fP, ACM, Boston, November 1988.
+Described is the \fBshape\fP toolkit posted to the
+\fBcomp.sources.unix\fP newsgroup in the volume 19 release.
+.IP [Tichy] 12
+Tichy, Walter F.
+\*QDesign, Implementation, and Evaluation of a Revision Control System.\*U
+\fIProceedings of the 6th International Conference on Software
+Engineering\fP, IEEE, Tokyo, September 1982.
+.IP [Wall] 12
+Wall, Larry.
+The \fBpatch\fP program is an indispensable tool for applying a diff file
+to an original.
+Can be found on uunet.uu.net in ~ftp/pub/patch.tar.
diff --git a/gnu/dist/cvs/doc/cvs.texinfo b/gnu/dist/cvs/doc/cvs.texinfo
new file mode 100644
index 00000000000..3768fe91742
--- /dev/null
+++ b/gnu/dist/cvs/doc/cvs.texinfo
@@ -0,0 +1,13561 @@
+\input texinfo @c -*-texinfo-*-
+@comment Documentation for CVS.
+@comment Copyright (C) 1992, 1993, 1999 Signum Support AB
+@comment Copyright (C) 1993 Free Software Foundation, Inc.
+
+@comment This file is part of the CVS distribution.
+
+@comment CVS is free software; you can redistribute it and/or modify
+@comment it under the terms of the GNU General Public License as published by
+@comment the Free Software Foundation; either version 2, or (at your option)
+@comment any later version.
+
+@comment CVS is distributed in the hope that it will be useful,
+@comment but WITHOUT ANY WARRANTY; without even the implied warranty of
+@comment MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+@comment GNU General Public License for more details.
+
+@c See ../README for A4 vs. US letter size.
+@c When we provided A4 postscript, and people tried to
+@c print it on US letter, the usual complaint was that the
+@c page numbers would get cut off.
+@c If one prints US letter on A4, reportedly there is
+@c some extra space at the top and/or bottom, and the side
+@c margins are a bit narrow, but no text is lost.
+@c
+@c See
+@c http://www.ft.uni-erlangen.de/~mskuhn/iso-paper.html
+@c for more on paper sizes. Insuring that margins are
+@c big enough to print on either A4 or US letter does
+@c indeed seem to be the usual approach (RFC2346).
+
+@c This document seems to get overfull hboxes with some
+@c frequency (probably because the tendency is to
+@c sanity-check it with "make info" and run TeX less
+@c often). The big ugly boxes just seem to add insult
+@c to injury, and I'm not aware of them helping to fix
+@c the overfull hboxes at all.
+@finalout
+
+@setfilename cvs.info
+@include CVSvn.texi
+@settitle CVS---Concurrent Versions System
+@setchapternewpage odd
+
+@c -- TODO list:
+@c -- Fix all lines that match "^@c -- "
+@c -- Also places marked with FIXME should be manual
+@c problems (as opposed to FIXCVS for CVS problems).
+
+@ifinfo
+@format
+START-INFO-DIR-ENTRY
+* CVS: (cvs). Concurrent Versions System
+END-INFO-DIR-ENTRY
+@end format
+@end ifinfo
+
+@ifinfo
+Copyright @copyright{} 1992, 1993 Signum Support AB
+Copyright @copyright{} 1993, 1994 Free Software Foundation, Inc.
+
+Permission is granted to make and distribute verbatim copies of
+this manual provided the copyright notice and this permission notice
+are preserved on all copies.
+
+@ignore
+Permission is granted to process this file through Tex and print the
+results, provided the printed document carries copying permission
+notice identical to this one except for the removal of this paragraph
+(this paragraph not being relevant to the printed manual).
+
+@end ignore
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided also that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that this permission notice may be stated in a translation
+approved by the Free Software Foundation.
+@end ifinfo
+
+@comment The titlepage section does not appear in the Info file.
+@titlepage
+@sp 4
+@comment The title is printed in a large font.
+@center @titlefont{Version Management}
+@sp
+@center @titlefont{with}
+@sp
+@center @titlefont{CVS}
+@sp 2
+@center for @sc{cvs} @value{CVSVN}
+@comment -release-
+@sp 3
+@center Per Cederqvist et al
+
+@comment The following two commands start the copyright page
+@comment for the printed manual. This will not appear in the Info file.
+@page
+@vskip 0pt plus 1filll
+Copyright @copyright{} 1992, 1993 Signum Support AB
+
+Permission is granted to make and distribute verbatim copies of
+this manual provided the copyright notice and this permission notice
+are preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided also that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that this permission notice may be stated in a translation
+approved by the Free Software Foundation.
+@end titlepage
+
+@comment ================================================================
+@comment The real text starts here
+@comment ================================================================
+
+@ifinfo
+@c ---------------------------------------------------------------------
+@node Top
+@top
+@c Note: there is a space after that @top command.
+@c The texinfo-format-buffer Emacs function and
+@c the makeinfo shell command disagree on what arguments
+@c @top takes; @top followed by a single space is
+@c something they can both cope with.
+
+This info manual describes how to use and administer
+@sc{cvs} version @value{CVSVN}.
+@end ifinfo
+
+@c This menu is pretty long. Not sure how easily that
+@c can be fixed (no brilliant ideas right away)...
+@menu
+* Overview:: An introduction to CVS
+* Repository:: Where all your sources are stored
+* Starting a new project:: Starting a project with CVS
+* Revisions:: Numeric and symbolic names for revisions
+* Branching and merging:: Diverging/rejoining branches of development
+* Recursive behavior:: CVS descends directories
+* Adding and removing:: Adding/removing/renaming files/directories
+* History browsing:: Viewing the history of files in various ways
+
+CVS and the Real World.
+-----------------------
+* Binary files:: CVS can handle binary files
+* Multiple developers:: How CVS helps a group of developers
+* Revision management:: Policy questions for revision management
+* Keyword substitution:: CVS can include the revision inside the file
+* Tracking sources:: Tracking third-party sources
+* Builds:: Issues related to CVS and builds
+* Special Files:: Devices, links and other non-regular files
+
+References.
+-----------
+* CVS commands:: CVS commands share some things
+* Invoking CVS:: Quick reference to CVS commands
+* Administrative files:: Reference manual for the Administrative files
+* Environment variables:: All environment variables which affect CVS
+* Compatibility:: Upgrading CVS versions
+* Troubleshooting:: Some tips when nothing works
+* Credits:: Some of the contributors to this manual
+* BUGS:: Dealing with bugs in CVS or this manual
+* Index:: Index
+@end menu
+
+@c ---------------------------------------------------------------------
+@node Overview
+@chapter Overview
+@cindex Overview
+
+This chapter is for people who have never used
+@sc{cvs}, and perhaps have never used version control
+software before.
+
+If you are already familiar with @sc{cvs} and are just
+trying to learn a particular feature or remember a
+certain command, you can probably skip everything here.
+
+@menu
+* What is CVS?:: What you can do with @sc{cvs}
+* What is CVS not?:: Problems @sc{cvs} doesn't try to solve
+* A sample session:: A tour of basic @sc{cvs} usage
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node What is CVS?
+@section What is CVS?
+@cindex What is CVS?
+@cindex Introduction to CVS
+@cindex CVS, introduction to
+
+@sc{cvs} is a version control system. Using it, you can
+record the history of your source files.
+
+@c -- ///
+@c -- ///Those who cannot remember the past are condemned to repeat it.
+@c -- /// -- George Santayana
+@c -- //////
+
+@c -- Insert history quote here!
+For example, bugs sometimes creep in when
+software is modified, and you might not detect the bug
+until a long time after you make the modification.
+With @sc{cvs}, you can easily retrieve old versions to see
+exactly which change caused the bug. This can
+sometimes be a big help.
+
+You could of course save every version of every file
+you have ever created. This would
+however waste an enormous amount of disk space. @sc{cvs}
+stores all the versions of a file in a single file in a
+clever way that only stores the differences between
+versions.
+
+@sc{cvs} also helps you if you are part of a group of people working
+on the same project. It is all too easy to overwrite
+each others' changes unless you are extremely careful.
+Some editors, like @sc{gnu} Emacs, try to make sure that
+the same file is never modified by two people at the
+same time. Unfortunately, if someone is using another
+editor, that safeguard will not work. @sc{cvs} solves this problem
+by insulating the different developers from each other. Every
+developer works in his own directory, and @sc{cvs} merges
+the work when each developer is done.
+
+@cindex History of CVS
+@cindex CVS, history of
+@cindex Credits (CVS program)
+@cindex Contributors (CVS program)
+@sc{cvs} started out as a bunch of shell scripts written by
+Dick Grune, posted to the newsgroup
+@code{comp.sources.unix} in the volume 6
+release of December, 1986. While no actual code from
+these shell scripts is present in the current version
+of @sc{cvs} much of the @sc{cvs} conflict resolution algorithms
+come from them.
+
+In April, 1989, Brian Berliner designed and coded @sc{cvs}.
+Jeff Polk later helped Brian with the design of the @sc{cvs}
+module and vendor branch support.
+
+@cindex Source, getting CVS source
+You can get @sc{cvs} in a variety of ways, including
+free download from the internet. For more information
+on downloading @sc{cvs} and other @sc{cvs} topics, see:
+
+@example
+http://www.cyclic.com/
+http://www.loria.fr/~molli/cvs-index.html
+@end example
+
+@cindex Mailing list
+@cindex List, mailing list
+@cindex Newsgroups
+There is a mailing list, known as @w{@code{info-cvs}},
+devoted to @sc{cvs}. To subscribe or
+unsubscribe
+write to
+@w{@code{info-cvs-request@@gnu.org}}.
+If you prefer a usenet group, the right
+group is @code{comp.software.config-mgmt} which is for
+@sc{cvs} discussions (along with other configuration
+management systems). In the future, it might be
+possible to create a
+@code{comp.software.config-mgmt.cvs}, but probably only
+if there is sufficient @sc{cvs} traffic on
+@code{comp.software.config-mgmt}.
+@c Other random data is that past attempts to create a
+@c gnu.* group have failed (the relevant authorities
+@c say they'll do it, but don't), and that tale was very
+@c skeptical of comp.software.config-mgmt.cvs when the
+@c subject came up around 1995 or so (for one
+@c thing, because creating it would be a "reorg" which
+@c would need to take a more comprehensive look at the
+@c whole comp.software.config-mgmt.* hierarchy).
+
+You can also subscribe to the bug-cvs mailing list,
+described in more detail in @ref{BUGS}. To subscribe
+send mail to bug-cvs-request@@gnu.org.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node What is CVS not?
+@section What is CVS not?
+@cindex What is CVS not?
+
+@sc{cvs} can do a lot of things for you, but it does
+not try to be everything for everyone.
+
+@table @asis
+@item @sc{cvs} is not a build system.
+
+Though the structure of your repository and modules
+file interact with your build system
+(e.g. @file{Makefile}s), they are essentially
+independent.
+
+@sc{cvs} does not dictate how you build anything. It
+merely stores files for retrieval in a tree structure
+you devise.
+
+@sc{cvs} does not dictate how to use disk space in the
+checked out working directories. If you write your
+@file{Makefile}s or scripts in every directory so they
+have to know the relative positions of everything else,
+you wind up requiring the entire repository to be
+checked out.
+
+If you modularize your work, and construct a build
+system that will share files (via links, mounts,
+@code{VPATH} in @file{Makefile}s, etc.), you can
+arrange your disk usage however you like.
+
+But you have to remember that @emph{any} such system is
+a lot of work to construct and maintain. @sc{cvs} does
+not address the issues involved.
+
+Of course, you should place the tools created to
+support such a build system (scripts, @file{Makefile}s,
+etc) under @sc{cvs}.
+
+Figuring out what files need to be rebuilt when
+something changes is, again, something to be handled
+outside the scope of @sc{cvs}. One traditional
+approach is to use @code{make} for building, and use
+some automated tool for generating the dependencies which
+@code{make} uses.
+
+See @ref{Builds}, for more information on doing builds
+in conjunction with @sc{cvs}.
+
+@item @sc{cvs} is not a substitute for management.
+
+Your managers and project leaders are expected to talk
+to you frequently enough to make certain you are aware
+of schedules, merge points, branch names and release
+dates. If they don't, @sc{cvs} can't help.
+
+@sc{cvs} is an instrument for making sources dance to
+your tune. But you are the piper and the composer. No
+instrument plays itself or writes its own music.
+
+@item @sc{cvs} is not a substitute for developer communication.
+
+When faced with conflicts within a single file, most
+developers manage to resolve them without too much
+effort. But a more general definition of ``conflict''
+includes problems too difficult to solve without
+communication between developers.
+
+@sc{cvs} cannot determine when simultaneous changes
+within a single file, or across a whole collection of
+files, will logically conflict with one another. Its
+concept of a @dfn{conflict} is purely textual, arising
+when two changes to the same base file are near enough
+to spook the merge (i.e. @code{diff3}) command.
+
+@sc{cvs} does not claim to help at all in figuring out
+non-textual or distributed conflicts in program logic.
+
+For example: Say you change the arguments to function
+@code{X} defined in file @file{A}. At the same time,
+someone edits file @file{B}, adding new calls to
+function @code{X} using the old arguments. You are
+outside the realm of @sc{cvs}'s competence.
+
+Acquire the habit of reading specs and talking to your
+peers.
+
+
+@item @sc{cvs} does not have change control
+
+Change control refers to a number of things. First of
+all it can mean @dfn{bug-tracking}, that is being able
+to keep a database of reported bugs and the status of
+each one (is it fixed? in what release? has the bug
+submitter agreed that it is fixed?). For interfacing
+@sc{cvs} to an external bug-tracking system, see the
+@file{rcsinfo} and @file{verifymsg} files
+(@pxref{Administrative files}).
+
+Another aspect of change control is keeping track of
+the fact that changes to several files were in fact
+changed together as one logical change. If you check
+in several files in a single @code{cvs commit}
+operation, @sc{cvs} then forgets that those files were
+checked in together, and the fact that they have the
+same log message is the only thing tying them
+together. Keeping a @sc{gnu} style @file{ChangeLog}
+can help somewhat.
+@c FIXME: should have an xref to a section which talks
+@c more about keeping ChangeLog's with CVS, but that
+@c section hasn't been written yet.
+
+Another aspect of change control, in some systems, is
+the ability to keep track of the status of each
+change. Some changes have been written by a developer,
+others have been reviewed by a second developer, and so
+on. Generally, the way to do this with @sc{cvs} is to
+generate a diff (using @code{cvs diff} or @code{diff})
+and email it to someone who can then apply it using the
+@code{patch} utility. This is very flexible, but
+depends on mechanisms outside @sc{cvs} to make sure
+nothing falls through the cracks.
+
+@item @sc{cvs} is not an automated testing program
+
+It should be possible to enforce mandatory use of a
+testsuite using the @code{commitinfo} file. I haven't
+heard a lot about projects trying to do that or whether
+there are subtle gotchas, however.
+
+@item @sc{cvs} does not have a builtin process model
+
+Some systems provide ways to ensure that changes or
+releases go through various steps, with various
+approvals as needed. Generally, one can accomplish
+this with @sc{cvs} but it might be a little more work.
+In some cases you'll want to use the @file{commitinfo},
+@file{loginfo}, @file{rcsinfo}, or @file{verifymsg}
+files, to require that certain steps be performed
+before cvs will allow a checkin. Also consider whether
+features such as branches and tags can be used to
+perform tasks such as doing work in a development tree
+and then merging certain changes over to a stable tree
+only once they have been proven.
+@end table
+
+@c ---------------------------------------------------------------------
+@node A sample session
+@section A sample session
+@cindex Example of a work-session
+@cindex Getting started
+@cindex Work-session, example of
+@cindex tc, Trivial Compiler (example)
+@cindex Trivial Compiler (example)
+
+@c I think an example is a pretty good way to start. But
+@c somewhere in here, maybe after the sample session,
+@c we need something which is kind of
+@c a "roadmap" which is more directed at sketching out
+@c the functionality of CVS and pointing people to
+@c various other parts of the manual. As it stands now
+@c people who read in order get dumped right into all
+@c manner of hair regarding remote repositories,
+@c creating a repository, etc.
+@c
+@c The following was in the old Basic concepts node. I don't
+@c know how good a job it does at introducing modules,
+@c or whether they need to be introduced so soon, but
+@c something of this sort might go into some
+@c introductory material somewhere.
+@ignore
+@cindex Modules (intro)
+The repository contains directories and files, in an
+arbitrary tree. The @dfn{modules} feature can be used
+to group together a set of directories or files into a
+single entity (@pxref{modules}). A typical usage is to
+define one module per project.
+@end ignore
+
+As a way of introducing @sc{cvs}, we'll go through a
+typical work-session using @sc{cvs}. The first thing
+to understand is that @sc{cvs} stores all files in a
+centralized @dfn{repository} (@pxref{Repository}); this
+section assumes that a repository is set up.
+@c I'm not sure that the sentence concerning the
+@c repository quite tells the user what they need to
+@c know at this point. Might need to expand on "centralized"
+@c slightly (maybe not here, maybe further down in the example?)
+
+Suppose you are working on a simple compiler. The source
+consists of a handful of C files and a @file{Makefile}.
+The compiler is called @samp{tc} (Trivial Compiler),
+and the repository is set up so that there is a module
+called @samp{tc}.
+
+@menu
+* Getting the source:: Creating a workspace
+* Committing your changes:: Making your work available to others
+* Cleaning up:: Cleaning up
+* Viewing differences:: Viewing differences
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Getting the source
+@subsection Getting the source
+@cindex Getting the source
+@cindex Checking out source
+@cindex Fetching source
+@cindex Source, getting from CVS
+@cindex Checkout, example
+
+The first thing you must do is to get your own working copy of the
+source for @samp{tc}. For this, you use the @code{checkout} command:
+
+@example
+$ cvs checkout tc
+@end example
+
+@noindent
+This will create a new directory called @file{tc} and populate it with
+the source files.
+
+@example
+$ cd tc
+$ ls
+CVS Makefile backend.c driver.c frontend.c parser.c
+@end example
+
+The @file{CVS} directory is used internally by
+@sc{cvs}. Normally, you should not modify or remove
+any of the files in it.
+
+You start your favorite editor, hack away at @file{backend.c}, and a couple
+of hours later you have added an optimization pass to the compiler.
+A note to @sc{rcs} and @sc{sccs} users: There is no need to lock the files that
+you want to edit. @xref{Multiple developers}, for an explanation.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Committing your changes
+@subsection Committing your changes
+@cindex Committing changes
+@cindex Log message entry
+@cindex CVSEDITOR, environment variable
+@cindex EDITOR, environment variable
+
+When you have checked that the compiler is still compilable you decide
+to make a new version of @file{backend.c}. This will
+store your new @file{backend.c} in the repository and
+make it available to anyone else who is using that same
+repository.
+
+@example
+$ cvs commit backend.c
+@end example
+
+@noindent
+@sc{cvs} starts an editor, to allow you to enter a log
+message. You type in ``Added an optimization pass.'',
+save the temporary file, and exit the editor.
+
+The environment variable @code{$CVSEDITOR} determines
+which editor is started. If @code{$CVSEDITOR} is not
+set, then if the environment variable @code{$EDITOR} is
+set, it will be used. If both @code{$CVSEDITOR} and
+@code{$EDITOR} are not set then there is a default
+which will vary with your operating system, for example
+@code{vi} for unix or @code{notepad} for Windows
+NT/95.
+
+@cindex VISUAL, environment variable
+In addition, @sc{cvs} checks the @code{$VISUAL} environment
+variable. Opinions vary on whether this behavior is desirable and
+whether future releases of @sc{cvs} should check @code{$VISUAL} or
+ignore it. You will be OK either way if you make sure that
+@code{$VISUAL} is either unset or set to the same thing as
+@code{$EDITOR}.
+
+@c This probably should go into some new node
+@c containing detailed info on the editor, rather than
+@c the intro. In fact, perhaps some of the stuff with
+@c CVSEDITOR and -m and so on should too.
+When @sc{cvs} starts the editor, it includes a list of
+files which are modified. For the @sc{cvs} client,
+this list is based on comparing the modification time
+of the file against the modification time that the file
+had when it was last gotten or updated. Therefore, if
+a file's modification time has changed but its contents
+have not, it will show up as modified. The simplest
+way to handle this is simply not to worry about it---if
+you proceed with the commit @sc{cvs} will detect that
+the contents are not modified and treat it as an
+unmodified file. The next @code{update} will clue
+@sc{cvs} in to the fact that the file is unmodified,
+and it will reset its stored timestamp so that the file
+will not show up in future editor sessions.
+@c FIXCVS: Might be nice if "commit" and other commands
+@c would reset that timestamp too, but currently commit
+@c doesn't.
+@c FIXME: Need to talk more about the process of
+@c prompting for the log message. Like show an example
+@c of what it pops up in the editor, for example. Also
+@c a discussion of how to get the "a)bort, c)ontinue,
+@c e)dit" prompt and what to do with it. Might also
+@c work in the suggestion that if you want a diff, you
+@c should make it before running commit (someone
+@c suggested that the diff pop up in the editor. I'm
+@c not sure that is better than telling people to run
+@c "cvs diff" first if that is what they want, but if
+@c we want to tell people that, the manual possibly
+@c should say it).
+
+If you want to avoid
+starting an editor you can specify the log message on
+the command line using the @samp{-m} flag instead, like
+this:
+
+@example
+$ cvs commit -m "Added an optimization pass" backend.c
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Cleaning up
+@subsection Cleaning up
+@cindex Cleaning up
+@cindex Working copy, removing
+@cindex Removing your working copy
+@cindex Releasing your working copy
+
+Before you turn to other tasks you decide to remove your working copy of
+tc. One acceptable way to do that is of course
+
+@example
+$ cd ..
+$ rm -r tc
+@end example
+
+@noindent
+but a better way is to use the @code{release} command (@pxref{release}):
+
+@example
+$ cd ..
+$ cvs release -d tc
+M driver.c
+? tc
+You have [1] altered files in this repository.
+Are you sure you want to release (and delete) directory `tc': n
+** `release' aborted by user choice.
+@end example
+
+The @code{release} command checks that all your modifications have been
+committed. If history logging is enabled it also makes a note in the
+history file. @xref{history file}.
+
+When you use the @samp{-d} flag with @code{release}, it
+also removes your working copy.
+
+In the example above, the @code{release} command wrote a couple of lines
+of output. @samp{? tc} means that the file @file{tc} is unknown to @sc{cvs}.
+That is nothing to worry about: @file{tc} is the executable compiler,
+and it should not be stored in the repository. @xref{cvsignore},
+for information about how to make that warning go away.
+@xref{release output}, for a complete explanation of
+all possible output from @code{release}.
+
+@samp{M driver.c} is more serious. It means that the
+file @file{driver.c} has been modified since it was
+checked out.
+
+The @code{release} command always finishes by telling
+you how many modified files you have in your working
+copy of the sources, and then asks you for confirmation
+before deleting any files or making any note in the
+history file.
+
+You decide to play it safe and answer @kbd{n @key{RET}}
+when @code{release} asks for confirmation.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Viewing differences
+@subsection Viewing differences
+@cindex Viewing differences
+@cindex Diff
+
+You do not remember modifying @file{driver.c}, so you want to see what
+has happened to that file.
+
+@example
+$ cd tc
+$ cvs diff driver.c
+@end example
+
+This command runs @code{diff} to compare the version of @file{driver.c}
+that you checked out with your working copy. When you see the output
+you remember that you added a command line option that enabled the
+optimization pass. You check it in, and release the module.
+@c FIXME: we haven't yet defined the term "check in".
+
+@example
+$ cvs commit -m "Added an optimization pass" driver.c
+Checking in driver.c;
+/usr/local/cvsroot/tc/driver.c,v <-- driver.c
+new revision: 1.2; previous revision: 1.1
+done
+$ cd ..
+$ cvs release -d tc
+? tc
+You have [0] altered files in this repository.
+Are you sure you want to release (and delete) directory `tc': y
+@end example
+
+@c ---------------------------------------------------------------------
+@node Repository
+@chapter The Repository
+@cindex Repository (intro)
+@cindex Repository, example
+@cindex Layout of repository
+@cindex Typical repository
+@cindex /usr/local/cvsroot, as example repository
+@cindex cvsroot
+
+The @sc{cvs} @dfn{repository} stores a complete copy of
+all the files and directories which are under version
+control.
+
+Normally, you never access any of the files in the
+repository directly. Instead, you use @sc{cvs}
+commands to get your own copy of the files into a
+@dfn{working directory}, and then
+work on that copy. When you've finished a set of
+changes, you check (or @dfn{commit}) them back into the
+repository. The repository then contains the changes
+which you have made, as well as recording exactly what
+you changed, when you changed it, and other such
+information. Note that the repository is not a
+subdirectory of the working directory, or vice versa;
+they should be in separate locations.
+@c Need some example, e.g. repository
+@c /usr/local/cvsroot; working directory
+@c /home/joe/sources. But this node is too long
+@c as it is; need a little reorganization...
+
+@cindex :local:, setting up
+@sc{cvs} can access a repository by a variety of
+means. It might be on the local computer, or it might
+be on a computer across the room or across the world.
+To distinguish various ways to access a repository, the
+repository name can start with an @dfn{access method}.
+For example, the access method @code{:local:} means to
+access a repository directory, so the repository
+@code{:local:/usr/local/cvsroot} means that the
+repository is in @file{/usr/local/cvsroot} on the
+computer running @sc{cvs}. For information on other
+access methods, see @ref{Remote repositories}.
+
+@c Can se say this more concisely? Like by passing
+@c more of the buck to the Remote repositories node?
+If the access method is omitted, then if the repository
+does not contain @samp{:}, then @code{:local:} is
+assumed. If it does contain @samp{:} then either
+@code{:ext:} or @code{:server:} is assumed. For
+example, if you have a local repository in
+@file{/usr/local/cvsroot}, you can use
+@code{/usr/local/cvsroot} instead of
+@code{:local:/usr/local/cvsroot}. But if (under
+Windows NT, for example) your local repository is
+@file{c:\src\cvsroot}, then you must specify the access
+method, as in @code{:local:c:\src\cvsroot}.
+
+@c This might appear to go in Repository storage, but
+@c actually it is describing something which is quite
+@c user-visible, when you do a "cvs co CVSROOT". This
+@c isn't necessary the perfect place for that, though.
+The repository is split in two parts. @file{$CVSROOT/CVSROOT} contains
+administrative files for @sc{cvs}. The other directories contain the actual
+user-defined modules.
+
+@menu
+* Specifying a repository:: Telling CVS where your repository is
+* Repository storage:: The structure of the repository
+* Working directory storage:: The structure of working directories
+* Intro administrative files:: Defining modules
+* Multiple repositories:: Multiple repositories
+* Creating a repository:: Creating a repository
+* Backing up:: Backing up a repository
+* Moving a repository:: Moving a repository
+* Remote repositories:: Accessing repositories on remote machines
+* Read-only access:: Granting read-only access to the repository
+* Server temporary directory:: The server creates temporary directories
+@end menu
+
+@node Specifying a repository
+@section Telling CVS where your repository is
+
+There are several ways to tell @sc{cvs}
+where to find the repository. You can name the
+repository on the command line explicitly, with the
+@code{-d} (for "directory") option:
+
+@example
+cvs -d /usr/local/cvsroot checkout yoyodyne/tc
+@end example
+
+@cindex .profile, setting CVSROOT in
+@cindex .cshrc, setting CVSROOT in
+@cindex .tcshrc, setting CVSROOT in
+@cindex .bashrc, setting CVSROOT in
+@cindex CVSROOT, environment variable
+ Or you can set the @code{$CVSROOT} environment
+variable to an absolute path to the root of the
+repository, @file{/usr/local/cvsroot} in this example.
+To set @code{$CVSROOT}, @code{csh} and @code{tcsh}
+users should have this line in their @file{.cshrc} or
+@file{.tcshrc} files:
+
+@example
+setenv CVSROOT /usr/local/cvsroot
+@end example
+
+@noindent
+@code{sh} and @code{bash} users should instead have these lines in their
+@file{.profile} or @file{.bashrc}:
+
+@example
+CVSROOT=/usr/local/cvsroot
+export CVSROOT
+@end example
+
+@cindex Root file, in CVS directory
+@cindex CVS/Root file
+ A repository specified with @code{-d} will
+override the @code{$CVSROOT} environment variable.
+Once you've checked a working copy out from the
+repository, it will remember where its repository is
+(the information is recorded in the
+@file{CVS/Root} file in the working copy).
+
+The @code{-d} option and the @file{CVS/Root} file both
+override the @code{$CVSROOT} environment variable. If
+@code{-d} option differs from @file{CVS/Root}, the
+former is used. Of course, for proper operation they
+should be two ways of referring to the same repository.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Repository storage
+@section How data is stored in the repository
+@cindex Repository, how data is stored
+
+For most purposes it isn't important @emph{how}
+@sc{cvs} stores information in the repository. In
+fact, the format has changed in the past, and is likely
+to change in the future. Since in almost all cases one
+accesses the repository via @sc{cvs} commands, such
+changes need not be disruptive.
+
+However, in some cases it may be necessary to
+understand how @sc{cvs} stores data in the repository,
+for example you might need to track down @sc{cvs} locks
+(@pxref{Concurrency}) or you might need to deal with
+the file permissions appropriate for the repository.
+
+@menu
+* Repository files:: What files are stored in the repository
+* File permissions:: File permissions
+* Windows permissions:: Issues specific to Windows
+* Attic:: Some files are stored in the Attic
+* CVS in repository:: Additional information in CVS directory
+* Locks:: CVS locks control concurrent accesses
+* CVSROOT storage:: A few things about CVSROOT are different
+@end menu
+
+@node Repository files
+@subsection Where files are stored within the repository
+
+@c @cindex Filenames, legal
+@c @cindex Legal filenames
+@c Somewhere we need to say something about legitimate
+@c characters in filenames in working directory and
+@c repository. Not "/" (not even on non-unix). And
+@c here is a specific set of issues:
+@c Files starting with a - are handled inconsistently. They can not
+@c be added to a repository with an add command, because it they are
+@c interpreted as a switch. They can appear in a repository if they are
+@c part of a tree that is imported. They can not be removed from the tree
+@c once they are there.
+@c Note that "--" *is* supported (as a
+@c consequence of using GNU getopt). Should document
+@c this somewhere ("Common options"?). The other usual technique,
+@c "./-foo", isn't as effective, at least for "cvs add"
+@c which doesn't support pathnames containing "/".
+
+The overall structure of the repository is a directory
+tree corresponding to the directories in the working
+directory. For example, supposing the repository is in
+
+@example
+/usr/local/cvsroot
+@end example
+
+@noindent
+here is a possible directory tree (showing only the
+directories):
+
+@example
+@t{/usr}
+ |
+ +--@t{local}
+ | |
+ | +--@t{cvsroot}
+ | | |
+ | | +--@t{CVSROOT}
+ | (administrative files)
+ |
+ +--@t{gnu}
+ | |
+ | +--@t{diff}
+ | | (source code to @sc{gnu} diff)
+ | |
+ | +--@t{rcs}
+ | | (source code to @sc{rcs})
+ | |
+ | +--@t{cvs}
+ | (source code to @sc{cvs})
+ |
+ +--@t{yoyodyne}
+ |
+ +--@t{tc}
+ | |
+ | +--@t{man}
+ | |
+ | +--@t{testing}
+ |
+ +--(other Yoyodyne software)
+@end example
+
+With the directories are @dfn{history files} for each file
+under version control. The name of the history file is
+the name of the corresponding file with @samp{,v}
+appended to the end. Here is what the repository for
+the @file{yoyodyne/tc} directory might look like:
+@c FIXME: Should also mention CVS (CVSREP)
+@c FIXME? Should we introduce Attic with an xref to
+@c Attic? Not sure whether that is a good idea or not.
+@example
+ @code{$CVSROOT}
+ |
+ +--@t{yoyodyne}
+ | |
+ | +--@t{tc}
+ | | |
+ +--@t{Makefile,v}
+ +--@t{backend.c,v}
+ +--@t{driver.c,v}
+ +--@t{frontend.c,v}
+ +--@t{parser.c,v}
+ +--@t{man}
+ | |
+ | +--@t{tc.1,v}
+ |
+ +--@t{testing}
+ |
+ +--@t{testpgm.t,v}
+ +--@t{test2.t,v}
+@end example
+
+@cindex History files
+@cindex RCS history files
+@c The first sentence, about what history files
+@c contain, is kind of redundant with our intro to what the
+@c repository does in node Repository....
+The history files contain, among other things, enough
+information to recreate any revision of the file, a log
+of all commit messages and the user-name of the person
+who committed the revision. The history files are
+known as @dfn{RCS files}, because the first program to
+store files in that format was a version control system
+known as @sc{rcs}. For a full
+description of the file format, see the @code{man} page
+@cite{rcsfile(5)}, distributed with @sc{rcs}, or the
+file @file{doc/RCSFILES} in the @sc{cvs} source
+distribution. This
+file format has become very common---many systems other
+than @sc{cvs} or @sc{rcs} can at least import history
+files in this format.
+@c FIXME: Think about including documentation for this
+@c rather than citing it? In the long run, getting
+@c this to be a standard (not sure if we can cope with
+@c a standards process as formal as IEEE/ANSI/ISO/etc,
+@c though...) is the way to go, so maybe citing is
+@c better.
+
+The @sc{rcs} files used in @sc{cvs} differ in a few
+ways from the standard format. The biggest difference
+is magic branches; for more information see @ref{Magic
+branch numbers}. Also in @sc{cvs} the valid tag names
+are a subset of what @sc{rcs} accepts; for @sc{cvs}'s
+rules see @ref{Tags}.
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node File permissions
+@subsection File permissions
+@c -- Move this to @node Creating a repository or similar
+@cindex Security, file permissions in repository
+@cindex File permissions, general
+@cindex Permissions, general
+@c FIXME: we need to somehow reflect "permissions in
+@c repository" versus "permissions in working
+@c directory" in the index entries.
+@cindex Group
+@cindex Read-only files, in repository
+All @samp{,v} files are created read-only, and you
+should not change the permission of those files. The
+directories inside the repository should be writable by
+the persons that have permission to modify the files in
+each directory. This normally means that you must
+create a UNIX group (see group(5)) consisting of the
+persons that are to edit the files in a project, and
+set up the repository so that it is that group that
+owns the directory.
+@c See also comment in commitinfo node regarding cases
+@c which are really awkward with unix groups.
+
+This means that you can only control access to files on
+a per-directory basis.
+
+Note that users must also have write access to check
+out files, because @sc{cvs} needs to create lock files
+(@pxref{Concurrency}).
+
+@c CVS seems to use CVSUMASK in picking permissions for
+@c val-tags, but maybe we should say more about this.
+@c Like val-tags gets created by someone who doesn't
+@c have CVSUMASK set right?
+Also note that users must have write access to the
+@file{CVSROOT/val-tags} file. @sc{cvs} uses it to keep
+track of what tags are valid tag names (it is sometimes
+updated when tags are used, as well as when they are
+created).
+
+Each @sc{rcs} file will be owned by the user who last
+checked it in. This has little significance; what
+really matters is who owns the directories.
+
+@cindex CVSUMASK, environment variable
+@cindex Umask, for repository files
+@sc{cvs} tries to set up reasonable file permissions
+for new directories that are added inside the tree, but
+you must fix the permissions manually when a new
+directory should have different permissions than its
+parent directory. If you set the @code{CVSUMASK}
+environment variable that will control the file
+permissions which @sc{cvs} uses in creating directories
+and/or files in the repository. @code{CVSUMASK} does
+not affect the file permissions in the working
+directory; such files have the permissions which are
+typical for newly created files, except that sometimes
+@sc{cvs} creates them read-only (see the sections on
+watches, @ref{Setting a watch}; -r, @ref{Global
+options}; or @code{CVSREAD}, @ref{Environment variables}).
+@c FIXME: Need more discussion of which
+@c group should own the file in the repository.
+@c Include a somewhat detailed example of the usual
+@c case where CVSUMASK is 007, the developers are all
+@c in a group, and that group owns stuff in the
+@c repository. Need to talk about group ownership of
+@c newly-created directories/files (on some unices,
+@c such as SunOS4, setting the setgid bit on the
+@c directories will make files inherit the directory's
+@c group. On other unices, your mileage may vary. I
+@c can't remember what POSIX says about this, if
+@c anything).
+
+Note that using the client/server @sc{cvs}
+(@pxref{Remote repositories}), there is no good way to
+set @code{CVSUMASK}; the setting on the client machine
+has no effect. If you are connecting with @code{rsh}, you
+can set @code{CVSUMASK} in @file{.bashrc} or @file{.cshrc}, as
+described in the documentation for your operating
+system. This behavior might change in future versions
+of @sc{cvs}; do not rely on the setting of
+@code{CVSUMASK} on the client having no effect.
+@c FIXME: need to explain what a umask is or cite
+@c someplace which does.
+@c
+@c There is also a larger (largely separate) issue
+@c about the meaning of CVSUMASK in a non-unix context.
+@c For example, whether there is
+@c an equivalent which fits better into other
+@c protection schemes like POSIX.6, VMS, &c.
+@c
+@c FIXME: Need one place which discusses this
+@c read-only files thing. Why would one use -r or
+@c CVSREAD? Why would one use watches? How do they
+@c interact?
+@c
+@c FIXME: We need to state
+@c whether using CVSUMASK removes the need for manually
+@c fixing permissions (in fact, if we are going to mention
+@c manually fixing permission, we better document a lot
+@c better just what we mean by "fix").
+
+Using pserver, you will generally need stricter
+permissions on the @sc{cvsroot} directory and
+directories above it in the tree; see @ref{Password
+authentication security}.
+
+@cindex Setuid
+@cindex Setgid
+@cindex Security, setuid
+@cindex Installed images (VMS)
+Some operating systems have features which allow a
+particular program to run with the ability to perform
+operations which the caller of the program could not.
+For example, the set user ID (setuid) or set group ID
+(setgid) features of unix or the installed image
+feature of VMS. CVS was not written to use such
+features and therefore attempting to install CVS in
+this fashion will provide protection against only
+accidental lapses; anyone who is trying to circumvent
+the measure will be able to do so, and depending on how
+you have set it up may gain access to more than just
+CVS. You may wish to instead consider pserver. It
+shares some of the same attributes, in terms of
+possibly providing a false sense of security or opening
+security holes wider than the ones you are trying to
+fix, so read the documentation on pserver security
+carefully if you are considering this option
+(@ref{Password authentication security}).
+
+@node Windows permissions
+@subsection File Permission issues specific to Windows
+@cindex Windows, and permissions
+@cindex File permissions, Windows-specific
+@cindex Permissions, Windows-specific
+
+Some file permission issues are specific to Windows
+operating systems (Windows 95, Windows NT, and
+presumably future operating systems in this family.
+Some of the following might apply to OS/2 but I'm not
+sure).
+
+If you are using local CVS and the repository is on a
+networked file system which is served by the Samba SMB
+server, some people have reported problems with
+permissions. Enabling WRITE=YES in the samba
+configuration is said to fix/workaround it.
+Disclaimer: I haven't investigated enough to know the
+implications of enabling that option, nor do I know
+whether there is something which CVS could be doing
+differently in order to avoid the problem. If you find
+something out, please let us know as described in
+@ref{BUGS}.
+
+@node Attic
+@subsection The attic
+@cindex Attic
+
+You will notice that sometimes @sc{cvs} stores an
+@sc{rcs} file in the @code{Attic}. For example, if the
+@sc{cvsroot} is @file{/usr/local/cvsroot} and we are
+talking about the file @file{backend.c} in the
+directory @file{yoyodyne/tc}, then the file normally
+would be in
+
+@example
+/usr/local/cvsroot/yoyodyne/tc/backend.c,v
+@end example
+
+but if it goes in the attic, it would be in
+
+@example
+/usr/local/cvsroot/yoyodyne/tc/Attic/backend.c,v
+@end example
+
+@cindex Dead state
+instead. It should not matter from a user point of
+view whether a file is in the attic; @sc{cvs} keeps
+track of this and looks in the attic when it needs to.
+But in case you want to know, the rule is that the RCS
+file is stored in the attic if and only if the head
+revision on the trunk has state @code{dead}. A
+@code{dead} state means that file has been removed, or
+never added, for that revision. For example, if you
+add a file on a branch, it will have a trunk revision
+in @code{dead} state, and a branch revision in a
+non-@code{dead} state.
+@c Probably should have some more concrete examples
+@c here, or somewhere (not sure exactly how we should
+@c arrange the discussion of the dead state, versus
+@c discussion of the attic).
+
+@node CVS in repository
+@subsection The CVS directory in the repository
+@cindex CVS directory, in repository
+
+The @file{CVS} directory in each repository directory
+contains information such as file attributes (in a file
+called @file{CVS/fileattr}. In the
+future additional files may be added to this directory,
+so implementations should silently ignore additional
+files.
+
+This behavior is implemented only by @sc{cvs} 1.7 and
+later; for details see @ref{Watches Compatibility}.
+
+The format of the fileattr file is a series of entries
+of the following form (where @samp{@{} and @samp{@}}
+means the text between the braces can be repeated zero
+or more times):
+
+@var{ent-type} @var{filename} <tab> @var{attrname} = @var{attrval}
+ @{; @var{attrname} = @var{attrval}@} <linefeed>
+
+@var{ent-type} is @samp{F} for a file, in which case the entry specifies the
+attributes for that file.
+
+@var{ent-type} is @samp{D},
+and @var{filename} empty, to specify default attributes
+to be used for newly added files.
+
+Other @var{ent-type} are reserved for future expansion. CVS 1.9 and older
+will delete them any time it writes file attributes.
+CVS 1.10 and later will preserve them.
+
+Note that the order of the lines is not significant;
+a program writing the fileattr file may
+rearrange them at its convenience.
+
+There is currently no way of quoting tabs or linefeeds in the
+filename, @samp{=} in @var{attrname},
+@samp{;} in @var{attrval}, etc. Note: some implementations also
+don't handle a NUL character in any of the fields, but
+implementations are encouraged to allow it.
+
+By convention, @var{attrname} starting with @samp{_} is for an attribute given
+special meaning by CVS; other @var{attrname}s are for user-defined attributes
+(or will be, once implementations start supporting user-defined attributes).
+
+Builtin attributes:
+
+@table @code
+@item _watched
+Present means the file is watched and should be checked out
+read-only.
+
+@item _watchers
+Users with watches for this file. Value is
+@var{watcher} > @var{type} @{ , @var{watcher} > @var{type} @}
+where @var{watcher} is a username, and @var{type}
+is zero or more of edit,unedit,commit separated by
+@samp{+} (that is, nothing if none; there is no "none" or "all" keyword).
+
+@item _editors
+Users editing this file. Value is
+@var{editor} > @var{val} @{ , @var{editor} > @var{val} @}
+where @var{editor} is a username, and @var{val} is
+@var{time}+@var{hostname}+@var{pathname}, where
+@var{time} is when the @code{cvs edit} command (or
+equivalent) happened,
+and @var{hostname} and @var{pathname} are for the working directory.
+@end table
+
+Example:
+
+@c FIXME: sanity.sh should contain a similar test case
+@c so we can compare this example from something from
+@c Real Life(TM). See cvsclient.texi (under Notify) for more
+@c discussion of the date format of _editors.
+@example
+Ffile1 _watched=;_watchers=joe>edit,mary>commit
+Ffile2 _watched=;_editors=sue>8 Jan 1975+workstn1+/home/sue/cvs
+D _watched=
+@end example
+
+means that the file @file{file1} should be checked out
+read-only. Furthermore, joe is watching for edits and
+mary is watching for commits. The file @file{file2}
+should be checked out read-only; sue started editing it
+on 8 Jan 1975 in the directory @file{/home/sue/cvs} on
+the machine @code{workstn1}. Future files which are
+added should be checked out read-only. To represent
+this example here, we have shown a space after
+@samp{D}, @samp{Ffile1}, and @samp{Ffile2}, but in fact
+there must be a single tab character there and no spaces.
+
+@node Locks
+@subsection CVS locks in the repository
+
+@cindex #cvs.rfl, technical details
+@cindex #cvs.wfl, technical details
+@cindex #cvs.lock, technical details
+@cindex Locks, cvs, technical details
+For an introduction to CVS locks focusing on
+user-visible behavior, see @ref{Concurrency}. The
+following section is aimed at people who are writing
+tools which want to access a CVS repository without
+interfering with other tools acessing the same
+repository. If you find yourself confused by concepts
+described here, like @dfn{read lock}, @dfn{write lock},
+and @dfn{deadlock}, you might consult the literature on
+operating systems or databases.
+
+@cindex #cvs.tfl
+Any file in the repository with a name starting
+with @file{#cvs.rfl.} is a read lock. Any file in
+the repository with a name starting with
+@file{#cvs.wfl} is a write lock. Old versions of CVS
+(before CVS 1.5) also created files with names starting
+with @file{#cvs.tfl}, but they are not discussed here.
+The directory @file{#cvs.lock} serves as a master
+lock. That is, one must obtain this lock first before
+creating any of the other locks.
+
+To obtain a readlock, first create the @file{#cvs.lock}
+directory. This operation must be atomic (which should
+be true for creating a directory under most operating
+systems). If it fails because the directory already
+existed, wait for a while and try again. After
+obtaining the @file{#cvs.lock} lock, create a file
+whose name is @file{#cvs.rfl.} followed by information
+of your choice (for example, hostname and process
+identification number). Then remove the
+@file{#cvs.lock} directory to release the master lock.
+Then proceed with reading the repository. When you are
+done, remove the @file{#cvs.rfl} file to release the
+read lock.
+
+To obtain a writelock, first create the
+@file{#cvs.lock} directory, as with a readlock. Then
+check that there are no files whose names start with
+@file{#cvs.rfl.}. If there are, remove
+@file{#cvs.lock}, wait for a while, and try again. If
+there are no readers, then create a file whose name is
+@file{#cvs.wfl} followed by information of your choice
+(for example, hostname and process identification
+number). Hang on to the @file{#cvs.lock} lock. Proceed
+with writing the repository. When you are done, first
+remove the @file{#cvs.wfl} file and then the
+@file{#cvs.lock} directory. Note that unlike the
+@file{#cvs.rfl} file, the @file{#cvs.wfl} file is just
+informational; it has no effect on the locking operation
+beyond what is provided by holding on to the
+@file{#cvs.lock} lock itself.
+
+Note that each lock (writelock or readlock) only locks
+a single directory in the repository, including
+@file{Attic} and @file{CVS} but not including
+subdirectories which represent other directories under
+version control. To lock an entire tree, you need to
+lock each directory (note that if you fail to obtain
+any lock you need, you must release the whole tree
+before waiting and trying again, to avoid deadlocks).
+
+Note also that @sc{cvs} expects writelocks to control
+access to individual @file{foo,v} files. @sc{rcs} has
+a scheme where the @file{,foo,} file serves as a lock,
+but @sc{cvs} does not implement it and so taking out a
+@sc{cvs} writelock is recommended. See the comments at
+rcs_internal_lockfile in the @sc{cvs} source code for
+further discussion/rationale.
+
+@node CVSROOT storage
+@subsection How files are stored in the CVSROOT directory
+@cindex CVSROOT, storage of files
+
+The @file{$CVSROOT/CVSROOT} directory contains the
+various administrative files. In some ways this
+directory is just like any other directory in the
+repository; it contains @sc{rcs} files whose names end
+in @samp{,v}, and many of the @sc{cvs} commands operate
+on it the same way. However, there are a few
+differences.
+
+For each administrative file, in addition to the
+@sc{rcs} file, there is also a checked out copy of the
+file. For example, there is an @sc{rcs} file
+@file{loginfo,v} and a file @file{loginfo} which
+contains the latest revision contained in
+@file{loginfo,v}. When you check in an administrative
+file, @sc{cvs} should print
+
+@example
+cvs commit: Rebuilding administrative file database
+@end example
+
+@noindent
+and update the checked out copy in
+@file{$CVSROOT/CVSROOT}. If it does not, there is
+something wrong (@pxref{BUGS}). To add your own files
+to the files to be updated in this fashion, you can add
+them to the @file{checkoutlist} administrative file
+(@pxref{checkoutlist}).
+
+@cindex modules.db
+@cindex modules.pag
+@cindex modules.dir
+By default, the @file{modules} file behaves as
+described above. If the modules file is very large,
+storing it as a flat text file may make looking up
+modules slow (I'm not sure whether this is as much of a
+concern now as when @sc{cvs} first evolved this
+feature; I haven't seen benchmarks). Therefore, by
+making appropriate edits to the @sc{cvs} source code
+one can store the modules file in a database which
+implements the @code{ndbm} interface, such as Berkeley
+db or GDBM. If this option is in use, then the modules
+database will be stored in the files @file{modules.db},
+@file{modules.pag}, and/or @file{modules.dir}.
+@c I think fileattr also will use the database stuff.
+@c Anything else?
+
+For information on the meaning of the various
+administrative files, see @ref{Administrative files}.
+
+@node Working directory storage
+@section How data is stored in the working directory
+
+@c FIXME: Somewhere we should discuss timestamps (test
+@c case "stamps" in sanity.sh). But not here. Maybe
+@c in some kind of "working directory" chapter which
+@c would encompass the "Builds" one? But I'm not sure
+@c whether that is a good organization (is it based on
+@c what the user wants to do?).
+
+@cindex CVS directory, in working directory
+While we are discussing @sc{cvs} internals which may
+become visible from time to time, we might as well talk
+about what @sc{cvs} puts in the @file{CVS} directories
+in the working directories. As with the repository,
+@sc{cvs} handles this information and one can usually
+access it via @sc{cvs} commands. But in some cases it
+may be useful to look at it, and other programs, such
+as the @code{jCVS} graphical user interface or the
+@code{VC} package for emacs, may need to look at it.
+Such programs should follow the recommendations in this
+section if they hope to be able to work with other
+programs which use those files, including future
+versions of the programs just mentioned and the
+command-line @sc{cvs} client.
+
+The @file{CVS} directory contains several files.
+Programs which are reading this directory should
+silently ignore files which are in the directory but
+which are not documented here, to allow for future
+expansion.
+
+The files are stored according to the text file
+convention for the system in question. This means that
+working directories are not portable between systems
+with differing conventions for storing text files.
+This is intentional, on the theory that the files being
+managed by CVS probably will not be portable between
+such systems either.
+
+@table @file
+@item Root
+This file contains the current @sc{cvs} root, as
+described in @ref{Specifying a repository}.
+
+@cindex Repository file, in CVS directory
+@cindex CVS/Repository file
+@item Repository
+This file contains the directory within the repository
+which the current directory corresponds with. It can
+be either an absolute pathname or a relative pathname;
+@sc{cvs} has had the ability to read either format
+since at least version 1.3 or so. The relative
+pathname is relative to the root, and is the more
+sensible approach, but the absolute pathname is quite
+common and implementations should accept either. For
+example, after the command
+
+@example
+cvs -d :local:/usr/local/cvsroot checkout yoyodyne/tc
+@end example
+
+@file{Root} will contain
+
+@example
+:local:/usr/local/cvsroot
+@end example
+
+and @file{Repository} will contain either
+
+@example
+/usr/local/cvsroot/yoyodyne/tc
+@end example
+
+@noindent
+or
+
+@example
+yoyodyne/tc
+@end example
+
+If the particular working directory does not correspond
+to a directory in the repository, then @file{Repository}
+should contain @file{CVSROOT/Emptydir}.
+
+@cindex Entries file, in CVS directory
+@cindex CVS/Entries file
+@item Entries
+This file lists the files and directories in the
+working directory.
+The first character of each line indicates what sort of
+line it is. If the character is unrecognized, programs
+reading the file should silently skip that line, to
+allow for future expansion.
+
+If the first character is @samp{/}, then the format is:
+
+@example
+/@var{name}/@var{revision}/@var{timestamp}[+@var{conflict}]/@var{options}/@var{tagdate}
+@end example
+
+where @samp{[} and @samp{]} are not part of the entry,
+but instead indicate that the @samp{+} and conflict
+marker are optional. @var{name} is the name of the
+file within the directory. @var{revision} is the
+revision that the file in the working derives from, or
+@samp{0} for an added file, or @samp{-} followed by a
+revision for a removed file. @var{timestamp} is the
+timestamp of the file at the time that @sc{cvs} created
+it; if the timestamp differs with the actual
+modification time of the file it means the file has
+been modified. It is stored in
+the format used by the ISO C asctime() function (for
+example, @samp{Sun Apr 7 01:29:26 1996}). One may
+write a string which is not in that format, for
+example, @samp{Result of merge}, to indicate that the
+file should always be considered to be modified. This
+is not a special case; to see whether a file is
+modified a program should take the timestamp of the file
+and simply do a string compare with @var{timestamp}.
+If there was a conflict, @var{conflict} can be set to
+the modification time of the file after the file has been
+written with conflict markers (@pxref{Conflicts example}).
+Thus if @var{conflict} is subsequently the same as the actual
+modification time of the file it means that the user
+has obviously not resolved the conflict. @var{options}
+contains sticky options (for example @samp{-kb} for a
+binary file). @var{tagdate} contains @samp{T} followed
+by a tag name, or @samp{D} for a date, followed by a
+sticky tag or date. Note that if @var{timestamp}
+contains a pair of timestamps separated by a space,
+rather than a single timestamp, you are dealing with a
+version of @sc{cvs} earlier than @sc{cvs} 1.5 (not
+documented here).
+
+The timezone on the timestamp in CVS/Entries (local or
+universal) should be the same as the operating system
+stores for the timestamp of the file itself. For
+example, on Unix the file's timestamp is in universal
+time (UT), so the timestamp in CVS/Entries should be
+too. On @sc{vms}, the file's timestamp is in local
+time, so @sc{cvs} on @sc{vms} should use local time.
+This rule is so that files do not appear to be modified
+merely because the timezone changed (for example, to or
+from summer time).
+@c See comments and calls to gmtime() and friends in
+@c src/vers_ts.c (function time_stamp).
+
+If the first character of a line in @file{Entries} is
+@samp{D}, then it indicates a subdirectory. @samp{D}
+on a line all by itself indicates that the program
+which wrote the @file{Entries} file does record
+subdirectories (therefore, if there is such a line and
+no other lines beginning with @samp{D}, one knows there
+are no subdirectories). Otherwise, the line looks
+like:
+
+@example
+D/@var{name}/@var{filler1}/@var{filler2}/@var{filler3}/@var{filler4}
+@end example
+
+where @var{name} is the name of the subdirectory, and
+all the @var{filler} fields should be silently ignored,
+for future expansion. Programs which modify
+@code{Entries} files should preserve these fields.
+
+The lines in the @file{Entries} file can be in any order.
+
+@cindex Entries.Log file, in CVS directory
+@cindex CVS/Entries.Log file
+@item Entries.Log
+This file does not record any information beyond that
+in @file{Entries}, but it does provide a way to update
+the information without having to rewrite the entire
+@file{Entries} file, including the ability to preserve
+the information even if the program writing
+@file{Entries} and @file{Entries.Log} abruptly aborts.
+Programs which are reading the @file{Entries} file
+should also check for @file{Entries.Log}. If the latter
+exists, they should read @file{Entries} and then apply
+the changes mentioned in @file{Entries.Log}. After
+applying the changes, the recommended practice is to
+rewrite @file{Entries} and then delete @file{Entries.Log}.
+The format of a line in @file{Entries.Log} is a single
+character command followed by a space followed by a
+line in the format specified for a line in
+@file{Entries}. The single character command is
+@samp{A} to indicate that the entry is being added,
+@samp{R} to indicate that the entry is being removed,
+or any other character to indicate that the entire line
+in @file{Entries.Log} should be silently ignored (for
+future expansion). If the second character of the line
+in @file{Entries.Log} is not a space, then it was
+written by an older version of @sc{cvs} (not documented
+here).
+
+Programs which are writing rather than reading can
+safely ignore @file{Entries.Log} if they so choose.
+
+@cindex Entries.Backup file, in CVS directory
+@cindex CVS/Entries.Backup file
+@item Entries.Backup
+This is a temporary file. Recommended usage is to
+write a new entries file to @file{Entries.Backup}, and
+then to rename it (atomically, where possible) to @file{Entries}.
+
+@cindex Entries.Static file, in CVS directory
+@cindex CVS/Entries.Static file
+@item Entries.Static
+The only relevant thing about this file is whether it
+exists or not. If it exists, then it means that only
+part of a directory was gotten and @sc{cvs} will
+not create additional files in that directory. To
+clear it, use the @code{update} command with the
+@samp{-d} option, which will get the additional files
+and remove @file{Entries.Static}.
+@c FIXME: This needs to be better documented, in places
+@c other than Working Directory Storage.
+@c FIXCVS: The fact that this setting exists needs to
+@c be more visible to the user. For example "cvs
+@c status foo", in the case where the file would be
+@c gotten except for Entries.Static, might say
+@c something to distinguish this from other cases.
+@c One thing that periodically gets suggested is to
+@c have "cvs update" print something when it skips
+@c files due to Entries.Static, but IMHO that kind of
+@c noise pretty much makes the Entries.Static feature
+@c useless.
+
+@cindex Tag file, in CVS directory
+@cindex CVS/Tag file
+@cindex Sticky tags/dates, per-directory
+@cindex Per-directory sticky tags/dates
+@item Tag
+This file contains per-directory sticky tags or dates.
+The first character is @samp{T} for a branch tag,
+@samp{N} for a non-branch tag, or @samp{D} for a date,
+or another character to mean the file should be
+silently ignored, for future expansion. This character
+is followed by the tag or date. Note that
+per-directory sticky tags or dates are used for things
+like applying to files which are newly added; they
+might not be the same as the sticky tags or dates on
+individual files. For general information on sticky
+tags and dates, see @ref{Sticky tags}.
+@c FIXME: This needs to be much better documented,
+@c preferably not in the context of "working directory
+@c storage".
+@c FIXME: The Sticky tags node needs to discuss, or xref to
+@c someplace which discusses, per-directory sticky
+@c tags and the distinction with per-file sticky tags.
+
+@cindex Checkin.prog file, in CVS directory
+@cindex CVS/Checkin.prog file
+@cindex Update.prog file, in CVS directory
+@cindex CVS/Update.prog file
+@item Checkin.prog
+@itemx Update.prog
+These files store the programs specified by the
+@samp{-i} and @samp{-u} options in the modules file,
+respectively.
+
+@cindex Notify file, in CVS directory
+@cindex CVS/Notify file
+@item Notify
+This file stores notifications (for example, for
+@code{edit} or @code{unedit}) which have not yet been
+sent to the server. Its format is not yet documented
+here.
+
+@cindex Notify.tmp file, in CVS directory
+@cindex CVS/Notify.tmp file
+@item Notify.tmp
+This file is to @file{Notify} as @file{Entries.Backup}
+is to @file{Entries}. That is, to write @file{Notify},
+first write the new contents to @file{Notify.tmp} and
+then (atomically where possible), rename it to
+@file{Notify}.
+
+@cindex Base directory, in CVS directory
+@cindex CVS/Base directory
+@item Base
+If watches are in use, then an @code{edit} command
+stores the original copy of the file in the @file{Base}
+directory. This allows the @code{unedit} command to
+operate even if it is unable to communicate with the
+server.
+
+@cindex Baserev file, in CVS directory
+@cindex CVS/Baserev file
+@item Baserev
+The file lists the revision for each of the files in
+the @file{Base} directory. The format is:
+
+@example
+B@var{name}/@var{rev}/@var{expansion}
+@end example
+
+where @var{expansion} should be ignored, to allow for
+future expansion.
+
+@cindex Baserev.tmp file, in CVS directory
+@cindex CVS/Baserev.tmp file
+@item Baserev.tmp
+This file is to @file{Baserev} as @file{Entries.Backup}
+is to @file{Entries}. That is, to write @file{Baserev},
+first write the new contents to @file{Baserev.tmp} and
+then (atomically where possible), rename it to
+@file{Baserev}.
+
+@cindex Template file, in CVS directory
+@cindex CVS/Template file
+@item Template
+This file contains the template specified by the
+@file{rcsinfo} file (@pxref{rcsinfo}). It is only used
+by the client; the non-client/server @sc{cvs} consults
+@file{rcsinfo} directly.
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Intro administrative files
+@section The administrative files
+@cindex Administrative files (intro)
+@cindex Modules file
+@cindex CVSROOT, module name
+@cindex Defining modules (intro)
+
+@c FIXME: this node should be reorganized into "general
+@c information about admin files" and put the "editing
+@c admin files" stuff up front rather than jumping into
+@c the details of modules right away. Then the
+@c Administrative files node can go away, the information
+@c on each admin file distributed to a place appropriate
+@c to its function, and this node can contain a table
+@c listing each file and a @ref to its detailed description.
+
+The directory @file{$CVSROOT/CVSROOT} contains some @dfn{administrative
+files}. @xref{Administrative files}, for a complete description.
+You can use @sc{cvs} without any of these files, but
+some commands work better when at least the
+@file{modules} file is properly set up.
+
+The most important of these files is the @file{modules}
+file. It defines all modules in the repository. This
+is a sample @file{modules} file.
+
+@c FIXME: The CVSROOT line is a goofy example now that
+@c mkmodules doesn't exist.
+@example
+CVSROOT CVSROOT
+modules CVSROOT modules
+cvs gnu/cvs
+rcs gnu/rcs
+diff gnu/diff
+tc yoyodyne/tc
+@end example
+
+The @file{modules} file is line oriented. In its
+simplest form each line contains the name of the
+module, whitespace, and the directory where the module
+resides. The directory is a path relative to
+@code{$CVSROOT}. The last four lines in the example
+above are examples of such lines.
+
+@c FIXME: might want to introduce the concept of options in modules file
+@c (the old example which was here, -i mkmodules, is obsolete).
+
+The line that defines the module called @samp{modules}
+uses features that are not explained here.
+@xref{modules}, for a full explanation of all the
+available features.
+
+@c FIXME: subsection without node is bogus
+@subsection Editing administrative files
+@cindex Editing administrative files
+@cindex Administrative files, editing them
+
+You edit the administrative files in the same way that you would edit
+any other module. Use @samp{cvs checkout CVSROOT} to get a working
+copy, edit it, and commit your changes in the normal way.
+
+It is possible to commit an erroneous administrative
+file. You can often fix the error and check in a new
+revision, but sometimes a particularly bad error in the
+administrative file makes it impossible to commit new
+revisions.
+@c @xref{Bad administrative files} for a hint
+@c about how to solve such situations.
+@c -- administrative file checking--
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Multiple repositories
+@section Multiple repositories
+@cindex Multiple repositories
+@cindex Repositories, multiple
+@cindex Many repositories
+@cindex Parallel repositories
+@cindex Disjoint repositories
+@cindex CVSROOT, multiple repositories
+
+In some situations it is a good idea to have more than
+one repository, for instance if you have two
+development groups that work on separate projects
+without sharing any code. All you have to do to have
+several repositories is to specify the appropriate
+repository, using the @code{CVSROOT} environment
+variable, the @samp{-d} option to @sc{cvs}, or (once
+you have checked out a working directory) by simply
+allowing @sc{cvs} to use the repository that was used
+to check out the working directory
+(@pxref{Specifying a repository}).
+
+The big advantage of having multiple repositories is
+that they can reside on different servers. With @sc{cvs}
+version 1.10, a single command cannot recurse into
+directories from different repositories. With development
+versions of @sc{cvs}, you can check out code from multiple
+servers into your working directory. @sc{cvs} will
+recurse and handle all the details of making
+connections to as many server machines as necessary to
+perform the requested command. Here is an example of
+how to set up a working directory:
+
+@example
+cvs -d server1:/cvs co dir1
+cd dir1
+cvs -d server2:/root co sdir
+cvs update
+@end example
+
+The @code{cvs co} commands set up the working
+directory, and then the @code{cvs update} command will
+contact server2, to update the dir1/sdir subdirectory,
+and server1, to update everything else.
+
+@c FIXME: Does the FAQ have more about this? I have a
+@c dim recollection, but I'm too lazy to check right now.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Creating a repository
+@section Creating a repository
+
+@cindex Repository, setting up
+@cindex Creating a repository
+@cindex Setting up a repository
+
+To set up a @sc{cvs} repository, first choose the
+machine and disk on which you want to store the
+revision history of the source files. CPU and memory
+requirements are modest, so most machines should be
+adequate. For details see @ref{Server requirements}.
+@c Possible that we should be providing a quick rule of
+@c thumb, like the 32M memory for the server. That
+@c might increase the number of people who are happy
+@c with the answer, without following the xref.
+
+To estimate disk space
+requirements, if you are importing RCS files from
+another system, the size of those files is the
+approximate initial size of your repository, or if you
+are starting without any version history, a rule of
+thumb is to allow for the server approximately three
+times the size of the code to be under CVS for the
+repository (you will eventually outgrow this, but not
+for a while). On the machines on which the developers
+will be working, you'll want disk space for
+approximately one working directory for each developer
+(either the entire tree or a portion of it, depending
+on what each developer uses).
+
+The repository should be accessible
+(directly or via a networked file system) from all
+machines which want to use @sc{cvs} in server or local
+mode; the client machines need not have any access to
+it other than via the @sc{cvs} protocol. It is not
+possible to use @sc{cvs} to read from a repository
+which one only has read access to; @sc{cvs} needs to be
+able to create lock files (@pxref{Concurrency}).
+
+@cindex init (subcommand)
+To create a repository, run the @code{cvs init}
+command. It will set up an empty repository in the
+@sc{cvs} root specified in the usual way
+(@pxref{Repository}). For example,
+
+@example
+cvs -d /usr/local/cvsroot init
+@end example
+
+@code{cvs init} is careful to never overwrite any
+existing files in the repository, so no harm is done if
+you run @code{cvs init} on an already set-up
+repository.
+
+@code{cvs init} will enable history logging; if you
+don't want that, remove the history file after running
+@code{cvs init}. @xref{history file}.
+
+@node Backing up
+@section Backing up a repository
+@cindex Repository, backing up
+@cindex Backing up, repository
+
+There is nothing particularly magical about the files
+in the repository; for the most part it is possible to
+back them up just like any other files. However, there
+are a few issues to consider.
+
+@cindex Locks, cvs, and backups
+@cindex #cvs.rfl, and backups
+The first is that to be paranoid, one should either not
+use @sc{cvs} during the backup, or have the backup
+program lock @sc{cvs} while doing the backup. To not
+use @sc{cvs}, you might forbid logins to machines which
+can access the repository, turn off your @sc{cvs}
+server, or similar mechanisms. The details would
+depend on your operating system and how you have
+@sc{cvs} set up. To lock @sc{cvs}, you would create
+@file{#cvs.rfl} locks in each repository directory.
+See @ref{Concurrency}, for more on @sc{cvs} locks.
+Having said all this, if you just back up without any
+of these precautions, the results are unlikely to be
+particularly dire. Restoring from backup, the
+repository might be in an inconsistent state, but this
+would not be particularly hard to fix manually.
+
+When you restore a repository from backup, assuming
+that changes in the repository were made after the time
+of the backup, working directories which were not
+affected by the failure may refer to revisions which no
+longer exist in the repository. Trying to run @sc{cvs}
+in such directories will typically produce an error
+message. One way to get those changes back into the
+repository is as follows:
+
+@itemize @bullet
+@item
+Get a new working directory.
+
+@item
+Copy the files from the working directory from before
+the failure over to the new working directory (do not
+copy the contents of the @file{CVS} directories, of
+course).
+
+@item
+Working in the new working directory, use commands such
+as @code{cvs update} and @code{cvs diff} to figure out
+what has changed, and then when you are ready, commit
+the changes into the repository.
+@end itemize
+
+@node Moving a repository
+@section Moving a repository
+@cindex Repository, moving
+@cindex Moving a repository
+@cindex Copying a repository
+
+Just as backing up the files in the repository is
+pretty much like backing up any other files, if you
+need to move a repository from one place to another it
+is also pretty much like just moving any other
+collection of files.
+
+The main thing to consider is that working directories
+point to the repository. The simplest way to deal with
+a moved repository is to just get a fresh working
+directory after the move. Of course, you'll want to
+make sure that the old working directory had been
+checked in before the move, or you figured out some
+other way to make sure that you don't lose any
+changes. If you really do want to reuse the existing
+working directory, it should be possible with manual
+surgery on the @file{CVS/Repository} files. You can
+see @ref{Working directory storage}, for information on
+the @file{CVS/Repository} and @file{CVS/Root} files, but
+unless you are sure you want to bother, it probably
+isn't worth it.
+@c FIXME: Surgery on CVS/Repository should be avoided
+@c by making RELATIVE_REPOS the default.
+@c FIXME-maybe: might want some documented way to
+@c change the CVS/Root files in some particular tree.
+@c But then again, I don't know, maybe just having
+@c people do this in perl/shell/&c isn't so bad...
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Remote repositories
+@section Remote repositories
+@cindex Repositories, remote
+@cindex Remote repositories
+@cindex Client/Server Operation
+@cindex Server, CVS
+
+ Your working copy of the sources can be on a
+different machine than the repository. Using @sc{cvs}
+in this manner is known as @dfn{client/server}
+operation. You run @sc{cvs} on a machine which can
+mount your working directory, known as the
+@dfn{client}, and tell it to communicate to a machine
+which can mount the repository, known as the
+@dfn{server}. Generally, using a remote
+repository is just like using a local one, except that
+the format of the repository name is:
+
+@example
+:@var{method}:@var{user}@@@var{hostname}:/path/to/repository
+@end example
+
+The details of exactly what needs to be set up depend
+on how you are connecting to the server.
+
+If @var{method} is not specified, and the repository
+name contains @samp{:}, then the default is @code{ext}
+or @code{server}, depending on your platform; both are
+described in @ref{Connecting via rsh}.
+@c Should we try to explain which platforms are which?
+@c Platforms like unix and VMS, which only allow
+@c privileged programs to bind to sockets <1024 lose on
+@c :server:
+@c Platforms like Mac and VMS, whose rsh program is
+@c unusable or nonexistent, lose on :ext:
+@c Platforms like OS/2 and NT probably could plausibly
+@c default either way (modulo -b troubles).
+
+@c FIXME: We need to have a better way of explaining
+@c what method to use. This presentation totally
+@c obscures the fact that :ext: and CVS_RSH is the way to
+@c use SSH, for example. Plus it incorrectly implies
+@c that you need an @code{rsh} binary on the client to use
+@c :server:.
+@c Also note that rsh not pserver is the right choice if you want
+@c users to be able to create their own repositories
+@c (because of the --allow-root related issues).
+@menu
+* Server requirements:: Memory and other resources for servers
+* Connecting via rsh:: Using the @code{rsh} program to connect
+* Password authenticated:: Direct connections using passwords
+* GSSAPI authenticated:: Direct connections using GSSAPI
+* Kerberos authenticated:: Direct connections with kerberos
+* Connecting via fork:: Using a forked @code{cvs server} to connect
+@end menu
+
+@node Server requirements
+@subsection Server requirements
+
+The quick answer to what sort of machine is suitable as
+a server is that requirements are modest---a server
+with 32M of memory or even less can handle a fairly
+large source tree with a fair amount of activity.
+@c Say something about CPU speed too? I'm even less sure
+@c what to say on that subject...
+
+The real answer, of course, is more complicated.
+Estimating the known areas of large memory consumption
+should be sufficient to estimate memory requirements.
+There are two such areas documented here; other memory
+consumption should be small by comparison (if you find
+that is not the case, let us know, as described in
+@ref{BUGS}, so we can update this documentation).
+
+The first area of big memory consumption is large
+checkouts, when using the @sc{cvs} server. The server
+consists of two processes for each client that it is
+serving. Memory consumption on the child process
+should remain fairly small. Memory consumption on the
+parent process, particularly if the network connection
+to the client is slow, can be expected to grow to
+slightly more than the size of the sources in a single
+directory, or two megabytes, whichever is larger.
+@c "two megabytes" of course is SERVER_HI_WATER. But
+@c we don't mention that here because we are
+@c documenting the default configuration of CVS. If it
+@c is a "standard" thing to change that value, it
+@c should be some kind of run-time configuration.
+@c
+@c See cvsclient.texi for more on the design decision
+@c to not have locks in place while waiting for the
+@c client, which is what results in memory consumption
+@c as high as this.
+
+Multiplying the size of each @sc{cvs} server by the
+number of servers which you expect to have active at
+one time should give an idea of memory requirements for
+the server. For the most part, the memory consumed by
+the parent process probably can be swap space rather
+than physical memory.
+@c Has anyone verified that notion about swap space?
+@c I say it based pretty much on guessing that the
+@c ->text of the struct buffer_data only gets accessed
+@c in a first in, first out fashion, but I haven't
+@c looked very closely.
+
+@c What about disk usage in /tmp on the server? I think that
+@c it can be substantial, but I haven't looked at this
+@c again and tried to figure it out ("cvs import" is
+@c probably the worst case...).
+
+The second area of large memory consumption is
+@code{diff}, when checking in large files. This is
+required even for binary files. The rule of thumb is
+to allow about ten times the size of the largest file
+you will want to check in, although five times may be
+adequate. For example, if you want to check in a file
+which is 10 megabytes, you should have 100 megabytes of
+memory on the machine doing the checkin (the server
+machine for client/server, or the machine running
+@sc{cvs} for non-client/server). This can be swap
+space rather than physical memory. Because the memory
+is only required briefly, there is no particular need
+to allow memory for more than one such checkin at a
+time.
+@c The 5-10 times rule of thumb is from Paul Eggert for
+@c GNU diff. I don't think it is in the GNU diff
+@c manual or anyplace like that.
+@c
+@c Probably we could be saying more about
+@c non-client/server CVS.
+@c I would guess for non-client/server CVS in an NFS
+@c environment the biggest issues are the network and
+@c the NFS server.
+
+Resource consumption for the client is even more
+modest---any machine with enough capacity to run the
+operating system in question should have little
+trouble.
+@c Is that true? I think the client still wants to
+@c (bogusly) store entire files in memory at times.
+
+For information on disk space requirements, see
+@ref{Creating a repository}.
+
+@node Connecting via rsh
+@subsection Connecting with rsh
+
+@cindex rsh
+CVS uses the @file{rsh} protocol to perform these
+operations, so the remote user host needs to have a
+@file{.rhosts} file which grants access to the local
+user.
+
+For example, suppose you are the user @file{mozart} on
+the local machine @file{toe.example.com}, and the
+server machine is @file{faun.example.org}. On
+faun, put the following line into the file
+@file{.rhosts} in @file{bach}'s home directory:
+
+@example
+toe.example.com mozart
+@end example
+
+Then test that @code{rsh} is working with
+
+@example
+rsh -l bach faun.example.org 'echo $PATH'
+@end example
+
+@cindex CVS_SERVER, environment variable
+Next you have to make sure that @code{rsh} will be able
+to find the server. Make sure that the path which
+@code{rsh} printed in the above example includes the
+directory containing a program named @code{cvs} which
+is the server. You need to set the path in
+@file{.bashrc}, @file{.cshrc}, etc., not @file{.login}
+or @file{.profile}. Alternately, you can set the
+environment variable @code{CVS_SERVER} on the client
+machine to the filename of the server you want to use,
+for example @file{/usr/local/bin/cvs-1.6}.
+@c FIXME: there should be a way to specify the
+@c program in CVSROOT, not CVS_SERVER, so that one can use
+@c different ones for different roots. e.g. ":server;cvs=cvs-1.6:"
+@c instead of ":server:".
+
+There is no need to edit @file{inetd.conf} or start a
+@sc{cvs} server daemon.
+
+@cindex :server:, setting up
+@cindex :ext:, setting up
+@cindex Kerberos, using kerberized rsh
+@cindex SSH (rsh replacement)
+@cindex rsh replacements (Kerberized, SSH, &c)
+There are two access methods that you use in @code{CVSROOT}
+for rsh. @code{:server:} specifies an internal rsh
+client, which is supported only by some CVS ports.
+@code{:ext:} specifies an external rsh program. By
+default this is @code{rsh} but you may set the
+@code{CVS_RSH} environment variable to invoke another
+program which can access the remote server (for
+example, @code{remsh} on HP-UX 9 because @code{rsh} is
+something different). It must be a program which can
+transmit data to and from the server without modifying
+it; for example the Windows NT @code{rsh} is not
+suitable since it by default translates between CRLF
+and LF. The OS/2 CVS port has a hack to pass @samp{-b}
+to @code{rsh} to get around this, but since this could
+potentially cause problems for programs other than the
+standard @code{rsh}, it may change in the future. If
+you set @code{CVS_RSH} to @code{SSH} or some other rsh
+replacement, the instructions in the rest of this
+section concerning @file{.rhosts} and so on are likely
+to be inapplicable; consult the documentation for your rsh
+replacement.
+@c FIXME: there should be a way to specify the
+@c program in CVSROOT, not CVS_RSH, so that one can use
+@c different ones for different roots. e.g. ":ext;rsh=remsh:"
+@c instead of ":ext:".
+@c See also the comment in src/client.c for rationale
+@c concerning "rsh" being the default and never
+@c "remsh".
+
+Continuing our example, supposing you want to access
+the module @file{foo} in the repository
+@file{/usr/local/cvsroot/}, on machine
+@file{faun.example.org}, you are ready to go:
+
+@example
+cvs -d :ext:bach@@faun.example.org:/usr/local/cvsroot checkout foo
+@end example
+
+(The @file{bach@@} can be omitted if the username is
+the same on both the local and remote hosts.)
+
+@c Should we mention "rsh host echo hi" and "rsh host
+@c cat" (the latter followed by typing text and ^D)
+@c as troubleshooting techniques? Probably yes
+@c (people tend to have trouble setting this up),
+@c but this kind of thing can be hard to spell out.
+
+@node Password authenticated
+@subsection Direct connection with password authentication
+
+The @sc{cvs} client can also connect to the server
+using a password protocol. This is particularly useful
+if using @code{rsh} is not feasible (for example,
+the server is behind a firewall), and Kerberos also is
+not available.
+
+ To use this method, it is necessary to make
+some adjustments on both the server and client sides.
+
+@menu
+* Password authentication server:: Setting up the server
+* Password authentication client:: Using the client
+* Password authentication security:: What this method does and does not do
+@end menu
+
+@node Password authentication server
+@subsubsection Setting up the server for password authentication
+
+First of all, you probably want to tighten the
+permissions on the @file{$CVSROOT} and
+@file{$CVSROOT/CVSROOT} directories. See @ref{Password
+authentication security}, for more details.
+
+@cindex pserver (subcommand)
+@cindex Password server, setting up
+@cindex Authenticating server, setting up
+@c FIXME: this isn't quite right regarding port
+@c numbers; CVS looks up "cvspserver" in
+@c /etc/services (on unix, but what about non-unix?).
+On the server side, the file @file{/etc/inetd.conf}
+needs to be edited so @code{inetd} knows to run the
+command @code{cvs pserver} when it receives a
+connection on the right port. By default, the port
+number is 2401; it would be different if your client
+were compiled with @code{CVS_AUTH_PORT} defined to
+something else, though.
+
+ If your @code{inetd} allows raw port numbers in
+@file{/etc/inetd.conf}, then the following (all on a
+single line in @file{inetd.conf}) should be sufficient:
+
+@example
+2401 stream tcp nowait root /usr/local/bin/cvs
+cvs -f --allow-root=/usr/cvsroot pserver
+@end example
+
+You could also use the
+@samp{-T} option to specify a temporary directory.
+
+The @samp{--allow-root} option specifies the allowable
+@sc{cvsroot} directory. Clients which attempt to use a
+different @sc{cvsroot} directory will not be allowed to
+connect. If there is more than one @sc{cvsroot}
+directory which you want to allow, repeat the option.
+(Unfortunately, many versions of @code{inetd} have very small
+limits on the number of arguments and/or the total length
+of the command. The usual solution to this problem is
+to have @code{inetd} run a shell script which then invokes
+@sc{cvs} with the necessary arguments.)
+
+ If your @code{inetd} wants a symbolic service
+name instead of a raw port number, then put this in
+@file{/etc/services}:
+
+@example
+cvspserver 2401/tcp
+@end example
+
+ and put @code{cvspserver} instead of
+@code{2401} in @file{inetd.conf}.
+
+ Once the above is taken care of, restart your
+@code{inetd}, or do whatever is necessary to force it
+to reread its initialization files.
+
+If you are having trouble setting this up, see
+@ref{Connection}.
+
+@cindex CVS passwd file
+@cindex passwd (admin file)
+Because the client stores and transmits passwords in
+cleartext (almost---see @ref{Password authentication
+security}, for details), a separate @sc{cvs} password
+file is generally used, so people don't compromise
+their regular passwords when they access the
+repository. This file is
+@file{$CVSROOT/CVSROOT/passwd} (@pxref{Intro
+administrative files}). It uses a colon-separated
+format, similar to @file{/etc/passwd} on Unix systems,
+except that it has fewer fields: @sc{cvs} username,
+optional password, and an optional system username for
+@sc{cvs} to run as if authentication succeeds. Here is
+an example @file{passwd} file with five entries:
+
+@example
+anonymous:
+bach:ULtgRLXo7NRxs
+spwang:1sOp854gDF3DY
+melissa:tGX1fS8sun6rY:pubcvs
+qproj:XR4EZcEs0szik:pubcvs
+@end example
+
+(The passwords are encrypted according to the standard
+Unix @code{crypt()} function, so it is possible to
+paste in passwords directly from regular Unix
+@file{/etc/passwd} files.)
+
+The first line in the example will grant access to any
+@sc{cvs} client attempting to authenticate as user
+@code{anonymous}, no matter what password they use,
+including an empty password. (This is typical for
+sites granting anonymous read-only access; for
+information on how to do the "read-only" part, see
+@xref{Read-only access}.)
+
+The second and third lines will grant access to
+@code{bach} and @code{spwang} if they supply their
+respective plaintext passwords.
+
+@cindex User aliases
+The fourth line will grant access to @code{melissa}, if
+she supplies the correct password, but her @sc{cvs}
+operations will actually run on the server side under
+the system user @code{pubcvs}. Thus, there need not be
+any system user named @code{melissa}, but there
+@emph{must} be one named @code{pubcvs}.
+
+The fifth line shows that system user identities can be
+shared: any client who successfully authenticates as
+@code{qproj} will actually run as @code{pubcvs}, just
+as @code{melissa} does. That way you could create a
+single, shared system user for each project in your
+repository, and give each developer their own line in
+the @file{$CVSROOT/CVSROOT/passwd} file. The @sc{cvs}
+username on each line would be different, but the
+system username would be the same. The reason to have
+different @sc{cvs} usernames is that CVS will log their
+actions under those names: when @code{melissa} commits
+a change to a project, the checkin is recorded in the
+project's history under the name @code{melissa}, not
+@code{pubcvs}. And the reason to have them share a
+system username is so that you can arrange permissions
+in the relevant area of the repository such that only
+that account has write-permission there.
+
+If the system-user field is present, all
+password-authenticated @sc{cvs} commands run as that
+user; if no system user is specified, @sc{cvs} simply
+takes the @sc{cvs} username as the system username and
+runs commands as that user. In either case, if there
+is no such user on the system, then the @sc{cvs}
+operation will fail (regardless of whether the client
+supplied a valid password).
+
+The password and system-user fields can both be omitted
+(and if the system-user field is omitted, then also
+omit the colon that would have separated it from the
+encrypted password). For example, this would be a
+valid @file{$CVSROOT/CVSROOT/passwd} file:
+
+@example
+anonymous::pubcvs
+fish:rKa5jzULzmhOo:kfogel
+sussman:1sOp854gDF3DY
+@end example
+
+When the password field is omitted or empty, then the
+client's authentication attempt will succeed with any
+password, including the empty string. However, the
+colon after the @sc{cvs} username is always necessary,
+even if the password is empty.
+
+CVS can also fall back to use system authentication.
+When authenticating a password, the server first checks
+for the user in the @file{$CVSROOT/CVSROOT/passwd}
+file. If it finds the user, it will use that entry for
+authentication as described above. But if it does not
+find the user, or if the @sc{cvs} @file{passwd} file
+does not exist, then the server can try to authenticate
+the username and password using the operating system's
+user-lookup routines (this "fallback" behavior can be
+disabled by setting @code{SystemAuth=no} in the
+@sc{cvs} @file{config} file, @pxref{config}). Be
+aware, however, that falling back to system
+authentication might be a security risk: @sc{cvs}
+operations would then be authenticated with that user's
+regular login password, and the password flies across
+the network in plaintext. See @ref{Password
+authentication security} for more on this.
+
+Right now, the only way to put a password in the
+@sc{cvs} @file{passwd} file is to paste it there from
+somewhere else. Someday, there may be a @code{cvs
+passwd} command.
+
+Unlike many of the files in @file{$CVSROOT/CVSROOT}, it
+is normal to edit the @file{passwd} file in-place,
+rather than via @sc{cvs}. This is because of the
+possible security risks of having the @file{passwd}
+file checked out to people's working copies. If you do
+want to include the @file{passwd} file in checkouts of
+@file{$CVSROOT/CVSROOT}, see @xref{checkoutlist}.
+
+@c We might also suggest using the @code{htpasswd} command
+@c from freely available web servers as well, but that
+@c would open up a can of worms in that the users next
+@c questions are likely to be "where do I get it?" and
+@c "how do I use it?"
+@c Also note that htpasswd, at least the version I had,
+@c likes to clobber the third field.
+
+@node Password authentication client
+@subsubsection Using the client with password authentication
+@cindex Login (subcommand)
+@cindex Password client, using
+@cindex Authenticated client, using
+@cindex :pserver:, setting up
+To run a @sc{cvs} command on a remote repository via
+the password-authenticating server, one specifies the
+@code{pserver} protocol, username, repository host, and
+path to the repository. For example:
+
+@example
+cvs -d :pserver:bach@@faun.example.org:/usr/local/cvsroot checkout someproj
+@end example
+
+or
+
+@example
+CVSROOT=:pserver:bach@@faun.example.org:/usr/local/cvsroot
+cvs checkout someproj
+@end example
+
+However, unless you're connecting to a public-access
+repository (i.e., one where that username doesn't
+require a password), you'll need to @dfn{log in} first.
+Logging in verifies your password with the repository.
+It's done with the @code{login} command, which will
+prompt you interactively for the password:
+
+@example
+cvs -d :pserver:bach@@faun.example.org:/usr/local/cvsroot login
+CVS password:
+@end example
+
+After you enter the password, @sc{cvs} verifies it with
+the server. If the verification succeeds, then that
+combination of username, host, repository, and password
+is permanently recorded, so future transactions with
+that repository won't require you to run @code{cvs
+login}. (If verification fails, @sc{cvs} will exit
+complaining that the password was incorrect, and
+nothing will be recorded.)
+
+The records are stored, by default, in the file
+@file{$HOME/.cvspass}. That file's format is
+human-readable, and to a degree human-editable, but
+note that the passwords are not stored in
+cleartext---they are trivially encoded to protect them
+from "innocent" compromise (i.e., inadvertent viewing
+by a system administrator or other non-malicious
+person).
+
+@cindex CVS_PASSFILE, environment variable
+You can change the default location of this file by
+setting the @code{CVS_PASSFILE} environment variable.
+If you use this variable, make sure you set it
+@emph{before} @code{cvs login} is run. If you were to
+set it after running @code{cvs login}, then later
+@sc{cvs} commands would be unable to look up the
+password for transmission to the server.
+
+Once you have logged in, all @sc{cvs} commands using
+that remote repository and username will authenticate
+with the stored password. So, for example
+
+@example
+cvs -d :pserver:bach@@faun.example.org:/usr/local/cvsroot checkout foo
+@end example
+
+should just work (unless the password changes on the
+server side, in which case you'll have to re-run
+@code{cvs login}).
+
+Note that if the @samp{:pserver:} were not present in
+the repository specification, @sc{cvs} would assume it
+should use @code{rsh} to connect with the server
+instead (@pxref{Connecting via rsh}).
+
+Of course, once you have a working copy checked out and
+are running @sc{cvs} commands from within it, there is
+no longer any need to specify the repository
+explicitly, because @sc{cvs} can deduce the repository
+from the working copy's @file{CVS} subdirectory.
+
+@c FIXME: seems to me this needs somewhat more
+@c explanation.
+@cindex Logout (subcommand)
+The password for a given remote repository can be
+removed from the @code{CVS_PASSFILE} by using the
+@code{cvs logout} command.
+
+@node Password authentication security
+@subsubsection Security considerations with password authentication
+
+@cindex Security, of pserver
+The passwords are stored on the client side in a
+trivial encoding of the cleartext, and transmitted in
+the same encoding. The encoding is done only to
+prevent inadvertent password compromises (i.e., a
+system administrator accidentally looking at the file),
+and will not prevent even a naive attacker from gaining
+the password.
+
+@c FIXME: The bit about "access to the repository
+@c implies general access to the system is *not* specific
+@c to pserver; it applies to kerberos and SSH and
+@c everything else too. Should reorganize the
+@c documentation to make this clear.
+The separate @sc{cvs} password file (@pxref{Password
+authentication server}) allows people
+to use a different password for repository access than
+for login access. On the other hand, once a user has
+non-read-only
+access to the repository, she can execute programs on
+the server system through a variety of means. Thus, repository
+access implies fairly broad system access as well. It
+might be possible to modify @sc{cvs} to prevent that,
+but no one has done so as of this writing.
+@c OpenBSD uses chroot() and copies the repository to
+@c provide anonymous read-only access (for details see
+@c http://www.openbsd.org/anoncvs.shar). While this
+@c closes the most obvious holes, I'm not sure it
+@c closes enough holes to recommend it (plus it is
+@c *very* easy to accidentally screw up a setup of this
+@c type).
+
+Note that because the @file{$CVSROOT/CVSROOT} directory
+contains @file{passwd} and other files which are used
+to check security, you must control the permissions on
+this directory as tightly as the permissions on
+@file{/etc}. The same applies to the @file{$CVSROOT}
+directory itself and any directory
+above it in the tree. Anyone who has write access to
+such a directory will have the ability to become any
+user on the system. Note that these permissions are
+typically tighter than you would use if you are not
+using pserver.
+@c TODO: Would be really nice to document/implement a
+@c scheme where the CVS server can run as some non-root
+@c user, e.g. "cvs". CVSROOT/passwd would contain a
+@c bunch of entries of the form foo:xxx:cvs (or the "cvs"
+@c would be implicit). This would greatly reduce
+@c security risks such as those hinted at in the
+@c previous paragraph. I think minor changes to CVS
+@c might be required but mostly this would just need
+@c someone who wants to play with it, document it, &c.
+
+In summary, anyone who gets the password gets
+repository access (which may imply some measure of general system
+access as well). The password is available to anyone
+who can sniff network packets or read a protected
+(i.e., user read-only) file. If you want real
+security, get Kerberos.
+
+@node GSSAPI authenticated
+@subsection Direct connection with GSSAPI
+
+@cindex GSSAPI
+@cindex Security, GSSAPI
+@cindex :gserver:, setting up
+@cindex Kerberos, using :gserver:
+GSSAPI is a generic interface to network security
+systems such as Kerberos 5.
+If you have a working GSSAPI library, you can have
+@sc{cvs} connect via a direct @sc{tcp} connection,
+authenticating with GSSAPI.
+
+To do this, @sc{cvs} needs to be compiled with GSSAPI
+support; when configuring @sc{cvs} it tries to detect
+whether GSSAPI libraries using kerberos version 5 are
+present. You can also use the @file{--with-gssapi}
+flag to configure.
+
+The connection is authenticated using GSSAPI, but the
+message stream is @emph{not} authenticated by default.
+You must use the @code{-a} global option to request
+stream authentication.
+
+The data transmitted is @emph{not} encrypted by
+default. Encryption support must be compiled into both
+the client and the server; use the
+@file{--enable-encrypt} configure option to turn it on.
+You must then use the @code{-x} global option to
+request encryption.
+
+GSSAPI connections are handled on the server side by
+the same server which handles the password
+authentication server; see @ref{Password authentication
+server}. If you are using a GSSAPI mechanism such as
+Kerberos which provides for strong authentication, you
+will probably want to disable the ability to
+authenticate via cleartext passwords. To do so, create
+an empty @file{CVSROOT/passwd} password file, and set
+@code{SystemAuth=no} in the config file
+(@pxref{config}).
+
+The GSSAPI server uses a principal name of
+cvs/@var{hostname}, where @var{hostname} is the
+canonical name of the server host. You will have to
+set this up as required by your GSSAPI mechanism.
+
+To connect using GSSAPI, use @samp{:gserver:}. For
+example,
+
+@example
+cvs -d :gserver:faun.example.org:/usr/local/cvsroot checkout foo
+@end example
+
+@node Kerberos authenticated
+@subsection Direct connection with kerberos
+
+@cindex Kerberos, using :kserver:
+@cindex Security, kerberos
+@cindex :kserver:, setting up
+The easiest way to use kerberos is to use the kerberos
+@code{rsh}, as described in @ref{Connecting via rsh}.
+The main disadvantage of using rsh is that all the data
+needs to pass through additional programs, so it may be
+slower. So if you have kerberos installed you can
+connect via a direct @sc{tcp} connection,
+authenticating with kerberos.
+
+This section concerns the kerberos network security
+system, version 4. Kerberos version 5 is supported via
+the GSSAPI generic network security interface, as
+described in the previous section.
+
+To do this, @sc{cvs} needs to be compiled with kerberos
+support; when configuring @sc{cvs} it tries to detect
+whether kerberos is present or you can use the
+@file{--with-krb4} flag to configure.
+
+The data transmitted is @emph{not} encrypted by
+default. Encryption support must be compiled into both
+the client and server; use the
+@file{--enable-encryption} configure option to turn it
+on. You must then use the @code{-x} global option to
+request encryption.
+
+@cindex CVS_CLIENT_PORT
+You need to edit @file{inetd.conf} on the server
+machine to run @code{cvs kserver}. The client uses
+port 1999 by default; if you want to use another port
+specify it in the @code{CVS_CLIENT_PORT} environment
+variable on the client.
+
+@cindex kinit
+When you want to use @sc{cvs}, get a ticket in the
+usual way (generally @code{kinit}); it must be a ticket
+which allows you to log into the server machine. Then
+you are ready to go:
+
+@example
+cvs -d :kserver:faun.example.org:/usr/local/cvsroot checkout foo
+@end example
+
+Previous versions of @sc{cvs} would fall back to a
+connection via rsh; this version will not do so.
+
+@node Connecting via fork
+@subsection Connecting with fork
+
+@cindex fork, access method
+@cindex :fork:, setting up
+This access method allows you to connect to a
+repository on your local disk via the remote protocol.
+In other words it does pretty much the same thing as
+@code{:local:}, but various quirks, bugs and the like are
+those of the remote @sc{cvs} rather than the local
+@sc{cvs}.
+
+For day-to-day operations you might prefer either
+@code{:local:} or @code{:fork:}, depending on your
+preferences. Of course @code{:fork:} comes in
+particularly handy in testing or
+debugging @code{cvs} and the remote protocol.
+Specifically, we avoid all of the network-related
+setup/configuration, timeouts, and authentication
+inherent in the other remote access methods but still
+create a connection which uses the remote protocol.
+
+To connect using the @code{fork} method, use
+@samp{:fork:} and the pathname to your local
+repository. For example:
+
+@example
+cvs -d :fork:/usr/local/cvsroot checkout foo
+@end example
+
+@cindex CVS_SERVER, and :fork:
+As with @code{:ext:}, the server is called @samp{cvs}
+by default, or the value of the @code{CVS_SERVER}
+environment variable.
+
+@c ---------------------------------------------------------------------
+@node Read-only access
+@section Read-only repository access
+@cindex Read-only repository access
+@cindex readers (admin file)
+@cindex writers (admin file)
+
+ It is possible to grant read-only repository
+access to people using the password-authenticated
+server (@pxref{Password authenticated}). (The
+other access methods do not have explicit support for
+read-only users because those methods all assume login
+access to the repository machine anyway, and therefore
+the user can do whatever local file permissions allow
+her to do.)
+
+ A user who has read-only access can do only
+those @sc{cvs} operations which do not modify the
+repository, except for certain ``administrative'' files
+(such as lock files and the history file). It may be
+desirable to use this feature in conjunction with
+user-aliasing (@pxref{Password authentication server}).
+
+Unlike with previous versions of @sc{cvs}, read-only
+users should be able merely to read the repository, and
+not to execute programs on the server or otherwise gain
+unexpected levels of access. Or to be more accurate,
+the @emph{known} holes have been plugged. Because this
+feature is new and has not received a comprehensive
+security audit, you should use whatever level of
+caution seems warranted given your attitude concerning
+security.
+
+ There are two ways to specify read-only access
+for a user: by inclusion, and by exclusion.
+
+ "Inclusion" means listing that user
+specifically in the @file{$CVSROOT/CVSROOT/readers}
+file, which is simply a newline-separated list of
+users. Here is a sample @file{readers} file:
+
+@example
+melissa
+splotnik
+jrandom
+@end example
+
+ (Don't forget the newline after the last user.)
+
+ "Exclusion" means explicitly listing everyone
+who has @emph{write} access---if the file
+
+@example
+$CVSROOT/CVSROOT/writers
+@end example
+
+@noindent
+exists, then only
+those users listed in it have write access, and
+everyone else has read-only access (of course, even the
+read-only users still need to be listed in the
+@sc{cvs} @file{passwd} file). The
+@file{writers} file has the same format as the
+@file{readers} file.
+
+ Note: if your @sc{cvs} @file{passwd}
+file maps cvs users onto system users (@pxref{Password
+authentication server}), make sure you deny or grant
+read-only access using the @emph{cvs} usernames, not
+the system usernames. That is, the @file{readers} and
+@file{writers} files contain cvs usernames, which may
+or may not be the same as system usernames.
+
+ Here is a complete description of the server's
+behavior in deciding whether to grant read-only or
+read-write access:
+
+ If @file{readers} exists, and this user is
+listed in it, then she gets read-only access. Or if
+@file{writers} exists, and this user is NOT listed in
+it, then she also gets read-only access (this is true
+even if @file{readers} exists but she is not listed
+there). Otherwise, she gets full read-write access.
+
+ Of course there is a conflict if the user is
+listed in both files. This is resolved in the more
+conservative way, it being better to protect the
+repository too much than too little: such a user gets
+read-only access.
+
+@node Server temporary directory
+@section Temporary directories for the server
+@cindex Temporary directories, and server
+@cindex Server, temporary directories
+
+While running, the @sc{cvs} server creates temporary
+directories. They are named
+
+@example
+cvs-serv@var{pid}
+@end example
+
+@noindent
+where @var{pid} is the process identification number of
+the server. They are located in the directory
+specified by the @code{TMPDIR} environment variable
+(@pxref{Environment variables}), the @samp{-T} global
+option (@pxref{Global options}), or failing that
+@file{/tmp}.
+
+In most cases the server will remove the temporary
+directory when it is done, whether it finishes normally
+or abnormally. However, there are a few cases in which
+the server does not or cannot remove the temporary
+directory, for example:
+
+@itemize @bullet
+@item
+If the server aborts due to an internal server error,
+it may preserve the directory to aid in debugging
+
+@item
+If the server is killed in a way that it has no way of
+cleaning up (most notably, @samp{kill -KILL} on unix).
+
+@item
+If the system shuts down without an orderly shutdown,
+which tells the server to clean up.
+@end itemize
+
+In cases such as this, you will need to manually remove
+the @file{cvs-serv@var{pid}} directories. As long as
+there is no server running with process identification
+number @var{pid}, it is safe to do so.
+
+@c ---------------------------------------------------------------------
+@node Starting a new project
+@chapter Starting a project with CVS
+@cindex Starting a project with CVS
+@cindex Creating a project
+
+@comment --moduledb--
+Because renaming files and moving them between
+directories is somewhat inconvenient, the first thing
+you do when you start a new project should be to think
+through your file organization. It is not impossible
+to rename or move files, but it does increase the
+potential for confusion and @sc{cvs} does have some
+quirks particularly in the area of renaming
+directories. @xref{Moving files}.
+
+What to do next depends on the situation at hand.
+
+@menu
+* Setting up the files:: Getting the files into the repository
+* Defining the module:: How to make a module of the files
+@end menu
+@c -- File permissions!
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Setting up the files
+@section Setting up the files
+
+The first step is to create the files inside the repository. This can
+be done in a couple of different ways.
+
+@c -- The contributed scripts
+@menu
+* From files:: This method is useful with old projects
+ where files already exists.
+* From other version control systems:: Old projects where you want to
+ preserve history from another system.
+* From scratch:: Creating a directory tree from scratch.
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node From files
+@subsection Creating a directory tree from a number of files
+@cindex Importing files
+
+When you begin using @sc{cvs}, you will probably already have several
+projects that can be
+put under @sc{cvs} control. In these cases the easiest way is to use the
+@code{import} command. An example is probably the easiest way to
+explain how to use it. If the files you want to install in
+@sc{cvs} reside in @file{@var{wdir}}, and you want them to appear in the
+repository as @file{$CVSROOT/yoyodyne/@var{rdir}}, you can do this:
+
+@example
+$ cd @var{wdir}
+$ cvs import -m "Imported sources" yoyodyne/@var{rdir} yoyo start
+@end example
+
+Unless you supply a log message with the @samp{-m}
+flag, @sc{cvs} starts an editor and prompts for a
+message. The string @samp{yoyo} is a @dfn{vendor tag},
+and @samp{start} is a @dfn{release tag}. They may fill
+no purpose in this context, but since @sc{cvs} requires
+them they must be present. @xref{Tracking sources}, for
+more information about them.
+
+You can now verify that it worked, and remove your
+original source directory.
+@c FIXME: Need to say more about "verify that it
+@c worked". What should the user look for in the output
+@c from "diff -r"?
+
+@example
+$ cd ..
+$ mv @var{dir} @var{dir}.orig
+$ cvs checkout yoyodyne/@var{dir} # @r{Explanation below}
+$ diff -r @var{dir}.orig yoyodyne/@var{dir}
+$ rm -r @var{dir}.orig
+@end example
+
+@noindent
+Erasing the original sources is a good idea, to make sure that you do
+not accidentally edit them in @var{dir}, bypassing @sc{cvs}.
+Of course, it would be wise to make sure that you have
+a backup of the sources before you remove them.
+
+The @code{checkout} command can either take a module
+name as argument (as it has done in all previous
+examples) or a path name relative to @code{$CVSROOT},
+as it did in the example above.
+
+It is a good idea to check that the permissions
+@sc{cvs} sets on the directories inside @code{$CVSROOT}
+are reasonable, and that they belong to the proper
+groups. @xref{File permissions}.
+
+If some of the files you want to import are binary, you
+may want to use the wrappers features to specify which
+files are binary and which are not. @xref{Wrappers}.
+
+@c The node name is too long, but I am having trouble
+@c thinking of something more concise.
+@node From other version control systems
+@subsection Creating Files From Other Version Control Systems
+@cindex Importing files, from other version control systems
+
+If you have a project which you are maintaining with
+another version control system, such as @sc{rcs}, you
+may wish to put the files from that project into
+@sc{cvs}, and preserve the revision history of the
+files.
+
+@table @asis
+@cindex RCS, importing files from
+@item From RCS
+If you have been using @sc{rcs}, find the @sc{rcs}
+files---usually a file named @file{foo.c} will have its
+@sc{rcs} file in @file{RCS/foo.c,v} (but it could be
+other places; consult the @sc{rcs} documentation for
+details). Then create the appropriate directories in
+@sc{cvs} if they do not already exist. Then copy the
+files into the appropriate directories in the @sc{cvs}
+repository (the name in the repository must be the name
+of the source file with @samp{,v} added; the files go
+directly in the appropriate directory of the repository,
+not in an @file{RCS} subdirectory). This is one of the
+few times when it is a good idea to access the @sc{cvs}
+repository directly, rather than using @sc{cvs}
+commands. Then you are ready to check out a new
+working directory.
+@c Someday there probably should be a "cvs import -t
+@c rcs" or some such. It could even create magic
+@c branches. It could also do something about the case
+@c where the RCS file had a (non-magic) "0" branch.
+
+The @sc{rcs} file should not be locked when you move it
+into @sc{cvs}; if it is, @sc{cvs} will have trouble
+letting you operate on it.
+@c What is the easiest way to unlock your files if you
+@c have them locked? Especially if you have a lot of them?
+@c This is a CVS bug/misfeature; importing RCS files
+@c should ignore whether they are locked and leave them in
+@c an unlocked state. Yet another reason for a separate
+@c "import RCS file" command.
+
+@c How many is "many"? Or do they just import RCS files?
+@item From another version control system
+Many version control systems have the ability to export
+@sc{rcs} files in the standard format. If yours does,
+export the @sc{rcs} files and then follow the above
+instructions.
+
+Failing that, probably your best bet is to write a
+script that will check out the files one revision at a
+time using the command line interface to the other
+system, and then check the revisions into @sc{cvs}.
+The @file{sccs2rcs} script mentioned below may be a
+useful example to follow.
+
+@cindex SCCS, importing files from
+@item From SCCS
+There is a script in the @file{contrib} directory of
+the @sc{cvs} source distribution called @file{sccs2rcs}
+which converts @sc{sccs} files to @sc{rcs} files.
+Note: you must run it on a machine which has both
+@sc{sccs} and @sc{rcs} installed, and like everything
+else in contrib it is unsupported (your mileage may
+vary).
+
+@cindex PVCS, importing files from
+@item From PVCS
+There is a script in the @file{contrib} directory of
+the @sc{cvs} source distribution called @file{pvcs_to_rcs}
+which converts @sc{pvcs} archives to @sc{rcs} files.
+You must run it on a machine which has both
+@sc{pvcs} and @sc{rcs} installed, and like everything
+else in contrib it is unsupported (your mileage may
+vary). See the comments in the script for details.
+@end table
+@c CMZ and/or PATCHY were systems that were used in the
+@c high energy physics community (especially for
+@c CERNLIB). CERN has replaced them with CVS, but the
+@c CAR format seems to live on as a way to submit
+@c changes. There is a program car2cvs which converts
+@c but I'm not sure where one gets a copy.
+@c Not sure it is worth mentioning here, since it would
+@c appear to affect only one particular community.
+@c Best page for more information is:
+@c http://wwwcn1.cern.ch/asd/cvs/index.html
+@c See also:
+@c http://ecponion.cern.ch/ecpsa/cernlib.html
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node From scratch
+@subsection Creating a directory tree from scratch
+
+@c Also/instead should be documenting
+@c $ cvs co -l .
+@c $ mkdir tc
+@c $ cvs add tc
+@c $ cd tc
+@c $ mkdir man
+@c $ cvs add man
+@c etc.
+@c Using import to create the directories only is
+@c probably a somewhat confusing concept.
+For a new project, the easiest thing to do is probably
+to create an empty directory structure, like this:
+
+@example
+$ mkdir tc
+$ mkdir tc/man
+$ mkdir tc/testing
+@end example
+
+After that, you use the @code{import} command to create
+the corresponding (empty) directory structure inside
+the repository:
+
+@example
+$ cd tc
+$ cvs import -m "Created directory structure" yoyodyne/@var{dir} yoyo start
+@end example
+
+Then, use @code{add} to add files (and new directories)
+as they appear.
+
+Check that the permissions @sc{cvs} sets on the
+directories inside @code{$CVSROOT} are reasonable.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Defining the module
+@section Defining the module
+@cindex Defining a module
+@cindex Editing the modules file
+@cindex Module, defining
+@cindex Modules file, changing
+
+The next step is to define the module in the
+@file{modules} file. This is not strictly necessary,
+but modules can be convenient in grouping together
+related files and directories.
+
+In simple cases these steps are sufficient to define a module.
+
+@enumerate
+@item
+Get a working copy of the modules file.
+
+@example
+$ cvs checkout CVSROOT/modules
+$ cd CVSROOT
+@end example
+
+@item
+Edit the file and insert a line that defines the module. @xref{Intro
+administrative files}, for an introduction. @xref{modules}, for a full
+description of the modules file. You can use the
+following line to define the module @samp{tc}:
+
+@example
+tc yoyodyne/tc
+@end example
+
+@item
+Commit your changes to the modules file.
+
+@example
+$ cvs commit -m "Added the tc module." modules
+@end example
+
+@item
+Release the modules module.
+
+@example
+$ cd ..
+$ cvs release -d CVSROOT
+@end example
+@end enumerate
+
+@c ---------------------------------------------------------------------
+@node Revisions
+@chapter Revisions
+
+For many uses of @sc{cvs}, one doesn't need to worry
+too much about revision numbers; @sc{cvs} assigns
+numbers such as @code{1.1}, @code{1.2}, and so on, and
+that is all one needs to know. However, some people
+prefer to have more knowledge and control concerning
+how @sc{cvs} assigns revision numbers.
+
+If one wants to keep track of a set of revisions
+involving more than one file, such as which revisions
+went into a particular release, one uses a @dfn{tag},
+which is a symbolic revision which can be assigned to a
+numeric revision in each file.
+
+@menu
+* Revision numbers:: The meaning of a revision number
+* Versions revisions releases:: Terminology used in this manual
+* Assigning revisions:: Assigning revisions
+* Tags:: Tags--Symbolic revisions
+* Tagging the working directory:: The cvs tag command
+* Tagging by date/tag:: The cvs rtag command
+* Modifying tags:: Adding, renaming, and deleting tags
+* Tagging add/remove:: Tags with adding and removing files
+* Sticky tags:: Certain tags are persistent
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Revision numbers
+@section Revision numbers
+@cindex Revision numbers
+@cindex Revision tree
+@cindex Linear development
+@cindex Number, revision-
+@cindex Decimal revision number
+@cindex Branch number
+@cindex Number, branch
+
+Each version of a file has a unique @dfn{revision
+number}. Revision numbers look like @samp{1.1},
+@samp{1.2}, @samp{1.3.2.2} or even @samp{1.3.2.2.4.5}.
+A revision number always has an even number of
+period-separated decimal integers. By default revision
+1.1 is the first revision of a file. Each successive
+revision is given a new number by increasing the
+rightmost number by one. The following figure displays
+a few revisions, with newer revisions to the right.
+
+@example
+ +-----+ +-----+ +-----+ +-----+ +-----+
+ ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 !
+ +-----+ +-----+ +-----+ +-----+ +-----+
+@end example
+
+It is also possible to end up with numbers containing
+more than one period, for example @samp{1.3.2.2}. Such
+revisions represent revisions on branches
+(@pxref{Branching and merging}); such revision numbers
+are explained in detail in @ref{Branches and
+revisions}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Versions revisions releases
+@section Versions, revisions and releases
+@cindex Revisions, versions and releases
+@cindex Versions, revisions and releases
+@cindex Releases, revisions and versions
+
+A file can have several versions, as described above.
+Likewise, a software product can have several versions.
+A software product is often given a version number such
+as @samp{4.1.1}.
+
+Versions in the first sense are called @dfn{revisions}
+in this document, and versions in the second sense are
+called @dfn{releases}. To avoid confusion, the word
+@dfn{version} is almost never used in this document.
+
+@node Assigning revisions
+@section Assigning revisions
+
+@c We avoid the "major revision" terminology. It seems
+@c like jargon. Hopefully "first number" is clear enough.
+By default, @sc{cvs} will assign numeric revisions by
+leaving the first number the same and incrementing the
+second number. For example, @code{1.1}, @code{1.2},
+@code{1.3}, etc.
+
+When adding a new file, the second number will always
+be one and the first number will equal the highest
+first number of any file in that directory. For
+example, the current directory contains files whose
+highest numbered revisions are @code{1.7}, @code{3.1},
+and @code{4.12}, then an added file will be given the
+numeric revision @code{4.1}.
+
+@c This is sort of redundant with something we said a
+@c while ago. Somewhere we need a better way of
+@c introducing how the first number can be anything
+@c except "1", perhaps. Also I don't think this
+@c presentation is clear on why we are discussing releases
+@c and first numbers of numeric revisions in the same
+@c breath.
+Normally there is no reason to care
+about the revision numbers---it is easier to treat them
+as internal numbers that @sc{cvs} maintains, and tags
+provide a better way to distinguish between things like
+release 1 versus release 2 of your product
+(@pxref{Tags}). However, if you want to set the
+numeric revisions, the @samp{-r} option to @code{cvs
+commit} can do that. The @samp{-r} option implies the
+@samp{-f} option, in the sense that it causes the
+files to be committed even if they are not modified.
+
+For example, to bring all your files up to
+revision 3.0 (including those that haven't changed),
+you might invoke:
+
+@example
+$ cvs commit -r 3.0
+@end example
+
+Note that the number you specify with @samp{-r} must be
+larger than any existing revision number. That is, if
+revision 3.0 exists, you cannot @samp{cvs commit
+-r 1.3}. If you want to maintain several releases in
+parallel, you need to use a branch (@pxref{Branching and merging}).
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Tags
+@section Tags--Symbolic revisions
+@cindex Tags
+
+The revision numbers live a life of their own. They
+need not have anything at all to do with the release
+numbers of your software product. Depending
+on how you use @sc{cvs} the revision numbers might change several times
+between two releases. As an example, some of the
+source files that make up @sc{rcs} 5.6 have the following
+revision numbers:
+@cindex RCS revision numbers
+
+@example
+ci.c 5.21
+co.c 5.9
+ident.c 5.3
+rcs.c 5.12
+rcsbase.h 5.11
+rcsdiff.c 5.10
+rcsedit.c 5.11
+rcsfcmp.c 5.9
+rcsgen.c 5.10
+rcslex.c 5.11
+rcsmap.c 5.2
+rcsutil.c 5.10
+@end example
+
+@cindex tag, command, introduction
+@cindex Tag, symbolic name
+@cindex Symbolic name (tag)
+@cindex Name, symbolic (tag)
+@cindex HEAD, as reserved tag name
+@cindex BASE, as reserved tag name
+You can use the @code{tag} command to give a symbolic name to a
+certain revision of a file. You can use the @samp{-v} flag to the
+@code{status} command to see all tags that a file has, and
+which revision numbers they represent. Tag names must
+start with an uppercase or lowercase letter and can
+contain uppercase and lowercase letters, digits,
+@samp{-}, and @samp{_}. The two tag names @code{BASE}
+and @code{HEAD} are reserved for use by @sc{cvs}. It
+is expected that future names which are special to
+@sc{cvs} will be specially named, for example by
+starting with @samp{.}, rather than being named analogously to
+@code{BASE} and @code{HEAD}, to avoid conflicts with
+actual tag names.
+@c Including a character such as % or = has also been
+@c suggested as the naming convention for future
+@c special tag names. Starting with . is nice because
+@c that is not a legal tag name as far as RCS is concerned.
+@c FIXME: CVS actually accepts quite a few characters
+@c in tag names, not just the ones documented above
+@c (see RCS_check_tag). RCS
+@c defines legitimate tag names by listing illegal
+@c characters rather than legal ones. CVS is said to lose its
+@c mind if you try to use "/" (try making such a tag sticky
+@c and using "cvs status" client/server--see remote
+@c protocol format for entries line for probable cause).
+@c TODO: The testsuite
+@c should test for whatever are documented above as
+@c officially-OK tag names, and CVS should at least reject
+@c characters that won't work, like "/".
+
+You'll want to choose some convention for naming tags,
+based on information such as the name of the program
+and the version number of the release. For example,
+one might take the name of the program, immediately
+followed by the version number with @samp{.} changed to
+@samp{-}, so that CVS 1.9 would be tagged with the name
+@code{cvs1-9}. If you choose a consistent convention,
+then you won't constantly be guessing whether a tag is
+@code{cvs-1-9} or @code{cvs1_9} or what. You might
+even want to consider enforcing your convention in the
+taginfo file (@pxref{user-defined logging}).
+@c Might be nice to say more about using taginfo this
+@c way, like giving an example, or pointing out any particular
+@c issues which arise.
+
+@cindex Adding a tag
+@cindex Tag, example
+The following example shows how you can add a tag to a
+file. The commands must be issued inside your working
+directory. That is, you should issue the
+command in the directory where @file{backend.c}
+resides.
+
+@example
+$ cvs tag rel-0-4 backend.c
+T backend.c
+$ cvs status -v backend.c
+===================================================================
+File: backend.c Status: Up-to-date
+
+ Version: 1.4 Tue Dec 1 14:39:01 1992
+ RCS Version: 1.4 /u/cvsroot/yoyodyne/tc/backend.c,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+ Existing Tags:
+ rel-0-4 (revision: 1.4)
+
+@end example
+
+For a complete summary of the syntax of @code{cvs tag},
+including the various options, see @ref{Invoking CVS}.
+
+There is seldom reason to tag a file in isolation. A more common use is
+to tag all the files that constitute a module with the same tag at
+strategic points in the development life-cycle, such as when a release
+is made.
+
+@example
+$ cvs tag rel-1-0 .
+cvs tag: Tagging .
+T Makefile
+T backend.c
+T driver.c
+T frontend.c
+T parser.c
+@end example
+
+(When you give @sc{cvs} a directory as argument, it generally applies the
+operation to all the files in that directory, and (recursively), to any
+subdirectories that it may contain. @xref{Recursive behavior}.)
+
+@cindex Retrieving an old revision using tags
+@cindex Tag, retrieving old revisions
+The @code{checkout} command has a flag, @samp{-r}, that lets you check out
+a certain revision of a module. This flag makes it easy to
+retrieve the sources that make up release 1.0 of the module @samp{tc} at
+any time in the future:
+
+@example
+$ cvs checkout -r rel-1-0 tc
+@end example
+
+@noindent
+This is useful, for instance, if someone claims that there is a bug in
+that release, but you cannot find the bug in the current working copy.
+
+You can also check out a module as it was at any given date.
+@xref{checkout options}. When specifying @samp{-r} to
+any of these commands, you will need beware of sticky
+tags; see @ref{Sticky tags}.
+
+When you tag more than one file with the same tag you
+can think about the tag as "a curve drawn through a
+matrix of filename vs. revision number." Say we have 5
+files with the following revisions:
+
+@example
+@group
+ file1 file2 file3 file4 file5
+
+ 1.1 1.1 1.1 1.1 /--1.1* <-*- TAG
+ 1.2*- 1.2 1.2 -1.2*-
+ 1.3 \- 1.3*- 1.3 / 1.3
+ 1.4 \ 1.4 / 1.4
+ \-1.5*- 1.5
+ 1.6
+@end group
+@end example
+
+At some time in the past, the @code{*} versions were tagged.
+You can think of the tag as a handle attached to the curve
+drawn through the tagged revisions. When you pull on
+the handle, you get all the tagged revisions. Another
+way to look at it is that you "sight" through a set of
+revisions that is "flat" along the tagged revisions,
+like this:
+
+@example
+@group
+ file1 file2 file3 file4 file5
+
+ 1.1
+ 1.2
+ 1.1 1.3 _
+ 1.1 1.2 1.4 1.1 /
+ 1.2*----1.3*----1.5*----1.2*----1.1 (--- <--- Look here
+ 1.3 1.6 1.3 \_
+ 1.4 1.4
+ 1.5
+@end group
+@end example
+
+@node Tagging the working directory
+@section Specifying what to tag from the working directory
+
+@cindex tag (subcommand)
+The example in the previous section demonstrates one of
+the most common ways to choose which revisions to tag.
+Namely, running the @code{cvs tag} command without
+arguments causes @sc{cvs} to select the revisions which
+are checked out in the current working directory. For
+example, if the copy of @file{backend.c} in working
+directory was checked out from revision 1.4, then
+@sc{cvs} will tag revision 1.4. Note that the tag is
+applied immediately to revision 1.4 in the repository;
+tagging is not like modifying a file, or other
+operations in which one first modifies the working
+directory and then runs @code{cvs commit} to transfer
+that modification to the repository.
+
+One potentially surprising aspect of the fact that
+@code{cvs tag} operates on the repository is that you
+are tagging the checked-in revisions, which may differ
+from locally modified files in your working directory.
+If you want to avoid doing this by mistake, specify the
+@samp{-c} option to @code{cvs tag}. If there are any
+locally modified files, @sc{cvs} will abort with an
+error before it tags any files:
+
+@example
+$ cvs tag -c rel-0-4
+cvs tag: backend.c is locally modified
+cvs [tag aborted]: correct the above errors first!
+@end example
+
+@node Tagging by date/tag
+@section Specifying what to tag by date or revision
+@cindex rtag (subcommand)
+
+The @code{cvs rtag} command tags the repository as of a
+certain date or time (or can be used to tag the latest
+revision). @code{rtag} works directly on the
+repository contents (it requires no prior checkout and
+does not look for a working directory).
+
+The following options specify which date or revision to
+tag. See @ref{Common options}, for a complete
+description of them.
+
+@table @code
+@item -D @var{date}
+Tag the most recent revision no later than @var{date}.
+
+@item -f
+Only useful with the @samp{-D @var{date}} or @samp{-r @var{tag}}
+flags. If no matching revision is found, use the most
+recent revision (instead of ignoring the file).
+
+@item -r @var{tag}
+Only tag those files that contain existing tag @var{tag}.
+@end table
+
+The @code{cvs tag} command also allows one to specify
+files by revision or date, using the same @samp{-r},
+@samp{-D}, and @samp{-f} options. However, this
+feature is probably not what you want. The reason is
+that @code{cvs tag} chooses which files to tag based on
+the files that exist in the working directory, rather
+than the files which existed as of the given tag/date.
+Therefore, you are generally better off using @code{cvs
+rtag}. The exceptions might be cases like:
+
+@example
+cvs tag -r 1.4 backend.c
+@end example
+
+@node Modifying tags
+@section Deleting, moving, and renaming tags
+
+@c Also see:
+@c "How do I move or rename a magic branch tag?"
+@c in the FAQ (I think the issues it talks about still
+@c apply, but this could use some sanity.sh work).
+
+Normally one does not modify tags. They exist in order
+to record the history of the repository and so deleting
+them or changing their meaning would, generally, not be
+what you want.
+
+However, there might be cases in which one uses a tag
+temporarily or accidentally puts one in the wrong
+place. Therefore, one might delete, move, or rename a
+tag. Warning: the commands in this section are
+dangerous; they permanently discard historical
+information and it can difficult or impossible to
+recover from errors. If you are a @sc{cvs}
+administrator, you may consider restricting these
+commands with taginfo (@pxref{user-defined logging}).
+
+@cindex Deleting tags
+@cindex Removing tags
+@cindex Tags, deleting
+To delete a tag, specify the @samp{-d} option to either
+@code{cvs tag} or @code{cvs rtag}. For example:
+
+@example
+cvs rtag -d rel-0-4 tc
+@end example
+
+deletes the tag @code{rel-0-4} from the module @code{tc}.
+
+@cindex Moving tags
+@cindex Tags, moving
+When we say @dfn{move} a tag, we mean to make the same
+name point to different revisions. For example, the
+@code{stable} tag may currently point to revision 1.4
+of @file{backend.c} and perhaps we want to make it
+point to revision 1.6. To move a tag, specify the
+@samp{-F} option to either @code{cvs tag} or @code{cvs
+rtag}. For example, the task just mentioned might be
+accomplished as:
+
+@example
+cvs tag -r 1.6 -F stable backend.c
+@end example
+
+@cindex Renaming tags
+@cindex Tags, renaming
+When we say @dfn{rename} a tag, we mean to make a
+different name point to the same revisions as the old
+tag. For example, one may have misspelled the tag name
+and want to correct it (hopefully before others are
+relying on the old spelling). To rename a tag, first
+create a new tag using the @samp{-r} option to
+@code{cvs rtag}, and then delete the old name. This
+leaves the new tag on exactly the same files as the old
+tag. For example:
+
+@example
+cvs rtag -r old-name-0-4 rel-0-4 tc
+cvs rtag -d old-name-0-4 tc
+@end example
+
+@node Tagging add/remove
+@section Tagging and adding and removing files
+
+The subject of exactly how tagging interacts with
+adding and removing files is somewhat obscure; for the
+most part @sc{cvs} will keep track of whether files
+exist or not without too much fussing. By default,
+tags are applied to only files which have a revision
+corresponding to what is being tagged. Files which did
+not exist yet, or which were already removed, simply
+omit the tag, and @sc{cvs} knows to treat the absence
+of a tag as meaning that the file didn't exist as of
+that tag.
+
+However, this can lose a small amount of information.
+For example, suppose a file was added and then removed.
+Then, if the tag is missing for that file, there is no
+way to know whether the tag refers to the time before
+the file was added, or the time after it was removed.
+If you specify the @samp{-r} option to @code{cvs rtag},
+then @sc{cvs} tags the files which have been removed,
+and thereby avoids this problem. For example, one
+might specify @code{-r HEAD} to tag the head.
+
+On the subject of adding and removing files, the
+@code{cvs rtag} command has a @samp{-a} option which
+means to clear the tag from removed files that would
+not otherwise be tagged. For example, one might
+specify this option in conjunction with @samp{-F} when
+moving a tag. If one moved a tag without @samp{-a},
+then the tag in the removed files might still refer to
+the old revision, rather than reflecting the fact that
+the file had been removed. I don't think this is
+necessary if @samp{-r} is specified, as noted above.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Sticky tags
+@section Sticky tags
+@cindex Sticky tags
+@cindex Tags, sticky
+
+@c A somewhat related issue is per-directory sticky
+@c tags (see comment at CVS/Tag in node Working
+@c directory storage); we probably want to say
+@c something like "you can set a sticky tag for only
+@c some files, but you don't want to" or some such.
+
+Sometimes a working copy's revision has extra data
+associated with it, for example it might be on a branch
+(@pxref{Branching and merging}), or restricted to
+versions prior to a certain date by @samp{checkout -D}
+or @samp{update -D}. Because this data persists --
+that is, it applies to subsequent commands in the
+working copy -- we refer to it as @dfn{sticky}.
+
+Most of the time, stickiness is an obscure aspect of
+@sc{cvs} that you don't need to think about. However,
+even if you don't want to use the feature, you may need
+to know @emph{something} about sticky tags (for
+example, how to avoid them!).
+
+You can use the @code{status} command to see if any
+sticky tags or dates are set:
+
+@example
+$ cvs status driver.c
+===================================================================
+File: driver.c Status: Up-to-date
+
+ Version: 1.7.2.1 Sat Dec 5 19:35:03 1992
+ RCS Version: 1.7.2.1 /u/cvsroot/yoyodyne/tc/driver.c,v
+ Sticky Tag: rel-1-0-patches (branch: 1.7.2)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+@end example
+
+@cindex Resetting sticky tags
+@cindex Sticky tags, resetting
+@cindex Deleting sticky tags
+The sticky tags will remain on your working files until
+you delete them with @samp{cvs update -A}. The
+@samp{-A} option retrieves the version of the file from
+the head of the trunk, and forgets any sticky tags,
+dates, or options.
+
+@cindex Sticky date
+The most common use of sticky tags is to identify which
+branch one is working on, as described in
+@ref{Accessing branches}. However, non-branch
+sticky tags have uses as well. For example,
+suppose that you want to avoid updating your working
+directory, to isolate yourself from possibly
+destabilizing changes other people are making. You
+can, of course, just refrain from running @code{cvs
+update}. But if you want to avoid updating only a
+portion of a larger tree, then sticky tags can help.
+If you check out a certain revision (such as 1.4) it
+will become sticky. Subsequent @code{cvs update}
+commands will
+not retrieve the latest revision until you reset the
+tag with @code{cvs update -A}. Likewise, use of the
+@samp{-D} option to @code{update} or @code{checkout}
+sets a @dfn{sticky date}, which, similarly, causes that
+date to be used for future retrievals.
+
+People often want to retrieve an old version of
+a file without setting a sticky tag. This can
+be done with the @samp{-p} option to @code{checkout} or
+@code{update}, which sends the contents of the file to
+standard output. For example:
+@example
+$ cvs update -p -r 1.1 file1 >file1
+===================================================================
+Checking out file1
+RCS: /tmp/cvs-sanity/cvsroot/first-dir/Attic/file1,v
+VERS: 1.1
+***************
+$
+@end example
+
+However, this isn't the easiest way, if you are asking
+how to undo a previous checkin (in this example, put
+@file{file1} back to the way it was as of revision
+1.1). In that case you are better off using the
+@samp{-j} option to @code{update}; for further
+discussion see @ref{Merging two revisions}.
+
+@c ---------------------------------------------------------------------
+@node Branching and merging
+@chapter Branching and merging
+@cindex Branching
+@cindex Merging
+@cindex Copying changes
+@cindex Main trunk and branches
+@cindex Revision tree, making branches
+@cindex Branches, copying changes between
+@cindex Changes, copying between branches
+@cindex Modifications, copying between branches
+
+CVS allows you to isolate changes onto a separate
+line of development, known as a @dfn{branch}. When you
+change files on a branch, those changes do not appear
+on the main trunk or other branches.
+
+Later you can move changes from one branch to another
+branch (or the main trunk) by @dfn{merging}. Merging
+involves first running @code{cvs update -j}, to merge
+the changes into the working directory.
+You can then commit that revision, and thus effectively
+copy the changes onto another branch.
+
+@menu
+* Branches motivation:: What branches are good for
+* Creating a branch:: Creating a branch
+* Accessing branches:: Checking out and updating branches
+* Branches and revisions:: Branches are reflected in revision numbers
+* Magic branch numbers:: Magic branch numbers
+* Merging a branch:: Merging an entire branch
+* Merging more than once:: Merging from a branch several times
+* Merging two revisions:: Merging differences between two revisions
+* Merging adds and removals:: What if files are added or removed?
+* Merging and keywords:: Avoiding conflicts due to keyword substitution
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Branches motivation
+@section What branches are good for
+@cindex Branches motivation
+@cindex What branches are good for
+@cindex Motivation for branches
+
+@c FIXME: this node mentions one way to use branches,
+@c but it is by no means the only way. For example,
+@c the technique of committing a new feature on a branch,
+@c until it is ready for the main trunk. The whole
+@c thing is generally speaking more akin to the
+@c "Revision management" node although it isn't clear to
+@c me whether policy matters should be centralized or
+@c distributed throughout the relevant sections.
+Suppose that release 1.0 of tc has been made. You are continuing to
+develop tc, planning to create release 1.1 in a couple of months. After a
+while your customers start to complain about a fatal bug. You check
+out release 1.0 (@pxref{Tags}) and find the bug
+(which turns out to have a trivial fix). However, the current revision
+of the sources are in a state of flux and are not expected to be stable
+for at least another month. There is no way to make a
+bugfix release based on the newest sources.
+
+The thing to do in a situation like this is to create a @dfn{branch} on
+the revision trees for all the files that make up
+release 1.0 of tc. You can then make
+modifications to the branch without disturbing the main trunk. When the
+modifications are finished you can elect to either incorporate them on
+the main trunk, or leave them on the branch.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Creating a branch
+@section Creating a branch
+@cindex Creating a branch
+@cindex Branch, creating a
+@cindex tag, creating a branch using
+@cindex rtag, creating a branch using
+
+You can create a branch with @code{tag -b}; for
+example, assuming you're in a working copy:
+
+@example
+$ cvs tag -b rel-1-0-patches
+@end example
+
+@c FIXME: we should be more explicit about the value of
+@c having a tag on the branchpoint. For example
+@c "cvs tag rel-1-0-patches-branchpoint" before
+@c the "cvs tag -b". This points out that
+@c rel-1-0-patches is a pretty awkward name for
+@c this example (more so than for the rtag example
+@c below).
+
+This splits off a branch based on the current revisions
+in the working copy, assigning that branch the name
+@samp{rel-1-0-patches}.
+
+It is important to understand that branches get created
+in the repository, not in the working copy. Creating a
+branch based on current revisions, as the above example
+does, will @emph{not} automatically switch the working
+copy to be on the new branch. For information on how
+to do that, see @ref{Accessing branches}.
+
+You can also create a branch without reference to any
+working copy, by using @code{rtag}:
+
+@example
+$ cvs rtag -b -r rel-1-0 rel-1-0-patches tc
+@end example
+
+@samp{-r rel-1-0} says that this branch should be
+rooted at the revision that
+corresponds to the tag @samp{rel-1-0}. It need not
+be the most recent revision -- it's often useful to
+split a branch off an old revision (for example, when
+fixing a bug in a past release otherwise known to be
+stable).
+
+As with @samp{tag}, the @samp{-b} flag tells
+@code{rtag} to create a branch (rather than just a
+symbolic revision name). Note that the numeric
+revision number that matches @samp{rel-1-0} will
+probably be different from file to file.
+
+So, the full effect of the command is to create a new
+branch -- named @samp{rel-1-0-patches} -- in module
+@samp{tc}, rooted in the revision tree at the point tagged
+by @samp{rel-1-0}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Accessing branches
+@section Accessing branches
+@cindex Check out a branch
+@cindex Retrieve a branch
+@cindex Access a branch
+@cindex Identifying a branch
+@cindex Branch, check out
+@cindex Branch, retrieving
+@cindex Branch, accessing
+@cindex Branch, identifying
+
+You can retrieve a branch in one of two ways: by
+checking it out fresh from the repository, or by
+switching an existing working copy over to the branch.
+
+To check out a branch from the repository, invoke
+@samp{checkout} with the @samp{-r} flag, followed by
+the tag name of the branch (@pxref{Creating a branch}):
+
+@example
+$ cvs checkout -r rel-1-0-patches tc
+@end example
+
+Or, if you already have a working copy, you can switch
+it to a given branch with @samp{update -r}:
+
+@example
+$ cvs update -r rel-1-0-patches tc
+@end example
+
+or equivalently:
+
+@example
+$ cd tc
+$ cvs update -r rel-1-0-patches
+@end example
+
+It does not matter if the working copy was originally
+on the main trunk or on some other branch -- the above
+command will switch it to the named branch. And
+similarly to a regular @samp{update} command,
+@samp{update -r} merges any changes you have made,
+notifying you of conflicts where they occur.
+
+Once you have a working copy tied to a particular
+branch, it remains there until you tell it otherwise.
+This means that changes checked in from the working
+copy will add new revisions on that branch, while
+leaving the main trunk and other branches unaffected.
+
+@cindex Branches, sticky
+To find out what branch a working copy is on, you can
+use the @samp{status} command. In its output, look for
+the field named @samp{Sticky tag} (@pxref{Sticky tags})
+-- that's @sc{cvs}'s way of telling you the branch, if
+any, of the current working files:
+
+@example
+$ cvs status -v driver.c backend.c
+===================================================================
+File: driver.c Status: Up-to-date
+
+ Version: 1.7 Sat Dec 5 18:25:54 1992
+ RCS Version: 1.7 /u/cvsroot/yoyodyne/tc/driver.c,v
+ Sticky Tag: rel-1-0-patches (branch: 1.7.2)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+ Existing Tags:
+ rel-1-0-patches (branch: 1.7.2)
+ rel-1-0 (revision: 1.7)
+
+===================================================================
+File: backend.c Status: Up-to-date
+
+ Version: 1.4 Tue Dec 1 14:39:01 1992
+ RCS Version: 1.4 /u/cvsroot/yoyodyne/tc/backend.c,v
+ Sticky Tag: rel-1-0-patches (branch: 1.4.2)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+ Existing Tags:
+ rel-1-0-patches (branch: 1.4.2)
+ rel-1-0 (revision: 1.4)
+ rel-0-4 (revision: 1.4)
+
+@end example
+
+Don't be confused by the fact that the branch numbers
+for each file are different (@samp{1.7.2} and
+@samp{1.4.2} respectively). The branch tag is the
+same, @samp{rel-1-0-patches}, and the files are
+indeed on the same branch. The numbers simply reflect
+the point in each file's revision history at which the
+branch was made. In the above example, one can deduce
+that @samp{driver.c} had been through more changes than
+@samp{backend.c} before this branch was created.
+
+See @ref{Branches and revisions} for details about how
+branch numbers are constructed.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Branches and revisions
+@section Branches and revisions
+@cindex Branch number
+@cindex Number, branch
+@cindex Revision numbers (branches)
+
+Ordinarily, a file's revision history is a linear
+series of increments (@pxref{Revision numbers}):
+
+@example
+ +-----+ +-----+ +-----+ +-----+ +-----+
+ ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 !
+ +-----+ +-----+ +-----+ +-----+ +-----+
+@end example
+
+However, @sc{cvs} is not limited to linear development. The
+@dfn{revision tree} can be split into @dfn{branches},
+where each branch is a self-maintained line of
+development. Changes made on one branch can easily be
+moved back to the main trunk.
+
+Each branch has a @dfn{branch number}, consisting of an
+odd number of period-separated decimal integers. The
+branch number is created by appending an integer to the
+revision number where the corresponding branch forked
+off. Having branch numbers allows more than one branch
+to be forked off from a certain revision.
+
+@need 3500
+All revisions on a branch have revision numbers formed
+by appending an ordinal number to the branch number.
+The following figure illustrates branching with an
+example.
+
+@example
+@c This example used to have a 1.2.2.4 revision, which
+@c might help clarify that development can continue on
+@c 1.2.2. Might be worth reinstating if it can be done
+@c without overfull hboxes.
+@group
+ +-------------+
+ Branch 1.2.2.3.2 -> ! 1.2.2.3.2.1 !
+ / +-------------+
+ /
+ /
+ +---------+ +---------+ +---------+
+Branch 1.2.2 -> _! 1.2.2.1 !----! 1.2.2.2 !----! 1.2.2.3 !
+ / +---------+ +---------+ +---------+
+ /
+ /
++-----+ +-----+ +-----+ +-----+ +-----+
+! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- The main trunk
++-----+ +-----+ +-----+ +-----+ +-----+
+ !
+ !
+ ! +---------+ +---------+ +---------+
+Branch 1.2.4 -> +---! 1.2.4.1 !----! 1.2.4.2 !----! 1.2.4.3 !
+ +---------+ +---------+ +---------+
+
+@end group
+@end example
+
+@c -- However, at least for me the figure is not enough. I suggest more
+@c -- text to accompany it. "A picture is worth a thousand words", so you
+@c -- have to make sure the reader notices the couple of hundred words
+@c -- *you* had in mind more than the others!
+
+@c -- Why an even number of segments? This section implies that this is
+@c -- how the main trunk is distinguished from branch roots, but you never
+@c -- explicitly say that this is the purpose of the [by itself rather
+@c -- surprising] restriction to an even number of segments.
+
+The exact details of how the branch number is
+constructed is not something you normally need to be
+concerned about, but here is how it works: When
+@sc{cvs} creates a branch number it picks the first
+unused even integer, starting with 2. So when you want
+to create a branch from revision 6.4 it will be
+numbered 6.4.2. All branch numbers ending in a zero
+(such as 6.4.0) are used internally by @sc{cvs}
+(@pxref{Magic branch numbers}). The branch 1.1.1 has a
+special meaning. @xref{Tracking sources}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Magic branch numbers
+@section Magic branch numbers
+
+@c Want xref to here from "log"?
+
+This section describes a @sc{cvs} feature called
+@dfn{magic branches}. For most purposes, you need not
+worry about magic branches; @sc{cvs} handles them for
+you. However, they are visible to you in certain
+circumstances, so it may be useful to have some idea of
+how it works.
+
+Externally, branch numbers consist of an odd number of
+dot-separated decimal integers. @xref{Revision
+numbers}. That is not the whole truth, however. For
+efficiency reasons @sc{cvs} sometimes inserts an extra 0
+in the second rightmost position (1.2.4 becomes
+1.2.0.4, 8.9.10.11.12 becomes 8.9.10.11.0.12 and so
+on).
+
+@sc{cvs} does a pretty good job at hiding these so
+called magic branches, but in a few places the hiding
+is incomplete:
+
+@itemize @bullet
+@ignore
+@c This is in ignore as I'm taking their word for it,
+@c that this was fixed
+@c a long time ago. But before deleting this
+@c entirely, I'd rather verify it (and add a test
+@c case to the testsuite).
+@item
+The magic branch can appear in the output from
+@code{cvs status} in vanilla @sc{cvs} 1.3. This is
+fixed in @sc{cvs} 1.3-s2.
+
+@end ignore
+@item
+The magic branch number appears in the output from
+@code{cvs log}.
+@c What output should appear instead?
+
+@item
+You cannot specify a symbolic branch name to @code{cvs
+admin}.
+
+@end itemize
+
+@c Can CVS do this automatically the first time
+@c you check something in to that branch? Should
+@c it?
+You can use the @code{admin} command to reassign a
+symbolic name to a branch the way @sc{rcs} expects it
+to be. If @code{R4patches} is assigned to the branch
+1.4.2 (magic branch number 1.4.0.2) in file
+@file{numbers.c} you can do this:
+
+@example
+$ cvs admin -NR4patches:1.4.2 numbers.c
+@end example
+
+It only works if at least one revision is already
+committed on the branch. Be very careful so that you
+do not assign the tag to the wrong number. (There is
+no way to see how the tag was assigned yesterday).
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Merging a branch
+@section Merging an entire branch
+@cindex Merging a branch
+@cindex -j (merging branches)
+
+You can merge changes made on a branch into your working copy by giving
+the @samp{-j @var{branch}} flag to the @code{update} command. With one
+@samp{-j @var{branch}} option it merges the changes made between the
+point where the branch forked and newest revision on that branch (into
+your working copy).
+
+@cindex Join
+The @samp{-j} stands for ``join''.
+
+@cindex Branch merge example
+@cindex Example, branch merge
+@cindex Merge, branch example
+Consider this revision tree:
+
+@example
++-----+ +-----+ +-----+ +-----+
+! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 ! <- The main trunk
++-----+ +-----+ +-----+ +-----+
+ !
+ !
+ ! +---------+ +---------+
+Branch R1fix -> +---! 1.2.2.1 !----! 1.2.2.2 !
+ +---------+ +---------+
+@end example
+
+@noindent
+The branch 1.2.2 has been given the tag (symbolic name) @samp{R1fix}. The
+following example assumes that the module @samp{mod} contains only one
+file, @file{m.c}.
+
+@example
+$ cvs checkout mod # @r{Retrieve the latest revision, 1.4}
+
+$ cvs update -j R1fix m.c # @r{Merge all changes made on the branch,}
+ # @r{i.e. the changes between revision 1.2}
+ # @r{and 1.2.2.2, into your working copy}
+ # @r{of the file.}
+
+$ cvs commit -m "Included R1fix" # @r{Create revision 1.5.}
+@end example
+
+A conflict can result from a merge operation. If that
+happens, you should resolve it before committing the
+new revision. @xref{Conflicts example}.
+
+If your source files contain keywords (@pxref{Keyword substitution}),
+you might be getting more conflicts than strictly necessary. See
+@ref{Merging and keywords}, for information on how to avoid this.
+
+The @code{checkout} command also supports the @samp{-j @var{branch}} flag. The
+same effect as above could be achieved with this:
+
+@example
+$ cvs checkout -j R1fix mod
+$ cvs commit -m "Included R1fix"
+@end example
+
+@node Merging more than once
+@section Merging from a branch several times
+
+Continuing our example, the revision tree now looks
+like this:
+
+@example
++-----+ +-----+ +-----+ +-----+ +-----+
+! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- The main trunk
++-----+ +-----+ +-----+ +-----+ +-----+
+ ! *
+ ! *
+ ! +---------+ +---------+
+Branch R1fix -> +---! 1.2.2.1 !----! 1.2.2.2 !
+ +---------+ +---------+
+@end example
+
+where the starred line represents the merge from the
+@samp{R1fix} branch to the main trunk, as just
+discussed.
+
+Now suppose that development continues on the
+@samp{R1fix} branch:
+
+@example
++-----+ +-----+ +-----+ +-----+ +-----+
+! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- The main trunk
++-----+ +-----+ +-----+ +-----+ +-----+
+ ! *
+ ! *
+ ! +---------+ +---------+ +---------+
+Branch R1fix -> +---! 1.2.2.1 !----! 1.2.2.2 !----! 1.2.2.3 !
+ +---------+ +---------+ +---------+
+@end example
+
+and then you want to merge those new changes onto the
+main trunk. If you just use the @code{cvs update -j
+R1fix m.c} command again, @sc{cvs} will attempt to
+merge again the changes which you have already merged,
+which can have undesirable side effects.
+
+So instead you need to specify that you only want to
+merge the changes on the branch which have not yet been
+merged into the trunk. To do that you specify two
+@samp{-j} options, and @sc{cvs} merges the changes from
+the first revision to the second revision. For
+example, in this case the simplest way would be
+
+@example
+cvs update -j 1.2.2.2 -j R1fix m.c # @r{Merge changes from 1.2.2.2 to the}
+ # @r{head of the R1fix branch}
+@end example
+
+The problem with this is that you need to specify the
+1.2.2.2 revision manually. A slightly better approach
+might be to use the date the last merge was done:
+
+@example
+cvs update -j R1fix:yesterday -j R1fix m.c
+@end example
+
+Better yet, tag the R1fix branch after every merge into
+the trunk, and then use that tag for subsequent merges:
+
+@example
+cvs update -j merged_from_R1fix_to_trunk -j R1fix m.c
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Merging two revisions
+@section Merging differences between any two revisions
+@cindex Merging two revisions
+@cindex Revisions, merging differences between
+@cindex Differences, merging
+
+With two @samp{-j @var{revision}} flags, the @code{update}
+(and @code{checkout}) command can merge the differences
+between any two revisions into your working file.
+
+@cindex Undoing a change
+@cindex Removing a change
+@example
+$ cvs update -j 1.5 -j 1.3 backend.c
+@end example
+
+@noindent
+will undo all changes made between revision
+1.3 and 1.5. Note the order of the revisions!
+
+If you try to use this option when operating on
+multiple files, remember that the numeric revisions will
+probably be very different between the various files.
+You almost always use symbolic
+tags rather than revision numbers when operating on
+multiple files.
+
+@cindex Restoring old version of removed file
+@cindex Resurrecting old version of dead file
+Specifying two @samp{-j} options can also undo file
+removals or additions. For example, suppose you have
+a file
+named @file{file1} which existed as revision 1.1, and
+you then removed it (thus adding a dead revision 1.2).
+Now suppose you want to add it again, with the same
+contents it had previously. Here is how to do it:
+
+@example
+$ cvs update -j 1.2 -j 1.1 file1
+U file1
+$ cvs commit -m test
+Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+new revision: 1.3; previous revision: 1.2
+done
+$
+@end example
+
+@node Merging adds and removals
+@section Merging can add or remove files
+
+If the changes which you are merging involve removing
+or adding some files, @code{update -j} will reflect
+such additions or removals.
+
+@c FIXME: This example needs a lot more explanation.
+@c We also need other examples for some of the other
+@c cases (not all--there are too many--as long as we present a
+@c coherent general principle).
+For example:
+@example
+cvs update -A
+touch a b c
+cvs add a b c ; cvs ci -m "added" a b c
+cvs tag -b branchtag
+cvs update -r branchtag
+touch d ; cvs add d
+rm a ; cvs rm a
+cvs ci -m "added d, removed a"
+cvs update -A
+cvs update -jbranchtag
+@end example
+
+After these commands are executed and a @samp{cvs commit} is done,
+file @file{a} will be removed and file @file{d} added in the main branch.
+@c (which was determined by trying it)
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Merging and keywords
+@section Merging and keywords
+@cindex Merging, and keyword substitution
+@cindex Keyword substitution, and merging
+@cindex -j (merging branches), and keyword substitution
+@cindex -kk, to avoid conflicts during a merge
+
+If you merge files containing keywords (@pxref{Keyword
+substitution}), you will normally get numerous
+conflicts during the merge, because the keywords are
+expanded differently in the revisions which you are
+merging.
+
+Therefore, you will often want to specify the
+@samp{-kk} (@pxref{Substitution modes}) switch to the
+merge command line. By substituting just the name of
+the keyword, not the expanded value of that keyword,
+this option ensures that the revisions which you are
+merging will be the same as each other, and avoid
+spurious conflicts.
+
+For example, suppose you have a file like this:
+
+@example
+ +---------+
+ _! 1.1.2.1 ! <- br1
+ / +---------+
+ /
+ /
++-----+ +-----+
+! 1.1 !----! 1.2 !
++-----+ +-----+
+@end example
+
+and your working directory is currently on the trunk
+(revision 1.2). Then you might get the following
+results from a merge:
+
+@example
+$ cat file1
+key $@asis{}Revision: 1.2 $
+. . .
+$ cvs update -j br1
+U file1
+RCS file: /cvsroot/first-dir/file1,v
+retrieving revision 1.1
+retrieving revision 1.1.2.1
+Merging differences between 1.1 and 1.1.2.1 into file1
+rcsmerge: warning: conflicts during merge
+$ cat file1
+@asis{}<<<<<<< file1
+key $@asis{}Revision: 1.2 $
+@asis{}=======
+key $@asis{}Revision: 1.1.2.1 $
+@asis{}>>>>>>> 1.1.2.1
+. . .
+@end example
+
+What happened was that the merge tried to merge the
+differences between 1.1 and 1.1.2.1 into your working
+directory. So, since the keyword changed from
+@code{Revision: 1.1} to @code{Revision: 1.1.2.1},
+@sc{cvs} tried to merge that change into your working
+directory, which conflicted with the fact that your
+working directory had contained @code{Revision: 1.2}.
+
+Here is what happens if you had used @samp{-kk}:
+
+@example
+$ cat file1
+key $@asis{}Revision: 1.2 $
+. . .
+$ cvs update -kk -j br1
+U file1
+RCS file: /cvsroot/first-dir/file1,v
+retrieving revision 1.1
+retrieving revision 1.1.2.1
+Merging differences between 1.1 and 1.1.2.1 into file1
+$ cat file1
+key $@asis{}Revision$
+. . .
+@end example
+
+What is going on here is that revision 1.1 and 1.1.2.1
+both expand as plain @code{Revision}, and therefore
+merging the changes between them into the working
+directory need not change anything. Therefore, there
+is no conflict.
+
+There is, however, one major caveat with using
+@samp{-kk} on merges. Namely, it overrides whatever
+keyword expansion mode @sc{cvs} would normally have
+used. In particular, this is a problem if the mode had
+been @samp{-kb} for a binary file. Therefore, if your
+repository contains binary files, you will need to deal
+with the conflicts rather than using @samp{-kk}.
+
+@ignore
+The following seems rather confusing, possibly buggy,
+and in general, in need of much more thought before it
+is a recommended technique. For one thing, does it
+apply on Windows as well as on Unix?
+
+Unchanged binary files will undergo the same keyword substitution
+but will not be checked in on a subsequent
+@code{cvs commit}. Be aware that binary files containing keyword
+strings which are present in or below the working directory
+will most likely remain corrupt until repaired, however. A simple
+@code{cvs update -A} is sufficient to fix these otherwise unaltered binary files
+if the merge is being done to the main branch but
+this must be done after the merge has been committed or the merge itself
+will be lost.
+
+For Example:
+@example
+cvs update -Akk -jbranchtag
+cvs commit
+cvs update -A
+@end example
+
+@noindent
+will update the current directory from the main trunk of the
+repository, substituting the base keyword strings for keywords,
+and merge changes made on the branch @samp{branchtag} into the new
+work files, performing the same keyword substitution on that file set before
+comparing the two sets. The final @code{cvs update -A} will restore any
+corrupted binary files as well as resetting the sticky @samp{-kk} tags which
+were present on the files in and below the working directory.
+Unfortunately, this doesn't work as well with an arbitrary branch tag, as the
+@samp{-r @var{branchtag}} switch does not reset the sticky @samp{-kk}
+switches attached to the working files as @samp{-A} does. The workaround
+for this is to release the working directory after the merge has been
+committed and check it out again.
+@end ignore
+
+@c ---------------------------------------------------------------------
+@node Recursive behavior
+@chapter Recursive behavior
+@cindex Recursive (directory descending)
+@cindex Directory, descending
+@cindex Descending directories
+@cindex Subdirectories
+
+Almost all of the subcommands of @sc{cvs} work
+recursively when you specify a directory as an
+argument. For instance, consider this directory
+structure:
+
+@example
+ @code{$HOME}
+ |
+ +--@t{tc}
+ | |
+ +--@t{CVS}
+ | (internal @sc{cvs} files)
+ +--@t{Makefile}
+ +--@t{backend.c}
+ +--@t{driver.c}
+ +--@t{frontend.c}
+ +--@t{parser.c}
+ +--@t{man}
+ | |
+ | +--@t{CVS}
+ | | (internal @sc{cvs} files)
+ | +--@t{tc.1}
+ |
+ +--@t{testing}
+ |
+ +--@t{CVS}
+ | (internal @sc{cvs} files)
+ +--@t{testpgm.t}
+ +--@t{test2.t}
+@end example
+
+@noindent
+If @file{tc} is the current working directory, the
+following is true:
+
+@itemize @bullet
+@item
+@samp{cvs update testing} is equivalent to
+
+@example
+cvs update testing/testpgm.t testing/test2.t
+@end example
+
+@item
+@samp{cvs update testing man} updates all files in the
+subdirectories
+
+@item
+@samp{cvs update .} or just @samp{cvs update} updates
+all files in the @code{tc} directory
+@end itemize
+
+If no arguments are given to @code{update} it will
+update all files in the current working directory and
+all its subdirectories. In other words, @file{.} is a
+default argument to @code{update}. This is also true
+for most of the @sc{cvs} subcommands, not only the
+@code{update} command.
+
+The recursive behavior of the @sc{cvs} subcommands can be
+turned off with the @samp{-l} option.
+Conversely, the @samp{-R} option can be used to force recursion if
+@samp{-l} is specified in @file{~/.cvsrc} (@pxref{~/.cvsrc}).
+
+@example
+$ cvs update -l # @r{Don't update files in subdirectories}
+@end example
+
+@c ---------------------------------------------------------------------
+@node Adding and removing
+@chapter Adding, removing, and renaming files and directories
+
+In the course of a project, one will often add new
+files. Likewise with removing or renaming, or with
+directories. The general concept to keep in mind in
+all these cases is that instead of making an
+irreversible change you want @sc{cvs} to record the
+fact that a change has taken place, just as with
+modifying an existing file. The exact mechanisms to do
+this in @sc{cvs} vary depending on the situation.
+
+@menu
+* Adding files:: Adding files
+* Removing files:: Removing files
+* Removing directories:: Removing directories
+* Moving files:: Moving and renaming files
+* Moving directories:: Moving and renaming directories
+@end menu
+
+@node Adding files
+@section Adding files to a directory
+@cindex Adding files
+
+To add a new file to a directory, follow these steps.
+
+@itemize @bullet
+@item
+You must have a working copy of the directory.
+@xref{Getting the source}.
+
+@item
+Create the new file inside your working copy of the directory.
+
+@item
+Use @samp{cvs add @var{filename}} to tell @sc{cvs} that you
+want to version control the file. If the file contains
+binary data, specify @samp{-kb} (@pxref{Binary files}).
+
+@item
+Use @samp{cvs commit @var{filename}} to actually check
+in the file into the repository. Other developers
+cannot see the file until you perform this step.
+@end itemize
+
+You can also use the @code{add} command to add a new
+directory.
+@c FIXCVS and/or FIXME: Adding a directory doesn't
+@c require the commit step. This probably can be
+@c considered a CVS bug, but it is possible we should
+@c warn people since this behavior probably won't be
+@c changing right away.
+
+Unlike most other commands, the @code{add} command is
+not recursive. You cannot even type @samp{cvs add
+foo/bar}! Instead, you have to
+@c FIXCVS: This is, of course, not a feature. It is
+@c just that no one has gotten around to fixing "cvs add
+@c foo/bar".
+
+@example
+$ cd foo
+$ cvs add bar
+@end example
+
+@cindex add (subcommand)
+@deffn Command {cvs add} [@code{-k} kflag] [@code{-m} message] files @dots{}
+
+Schedule @var{files} to be added to the repository.
+The files or directories specified with @code{add} must
+already exist in the current directory. To add a whole
+new directory hierarchy to the source repository (for
+example, files received from a third-party vendor), use
+the @code{import} command instead. @xref{import}.
+
+The added files are not placed in the source repository
+until you use @code{commit} to make the change
+permanent. Doing an @code{add} on a file that was
+removed with the @code{remove} command will undo the
+effect of the @code{remove}, unless a @code{commit}
+command intervened. @xref{Removing files}, for an
+example.
+
+The @samp{-k} option specifies the default way that
+this file will be checked out; for more information see
+@ref{Substitution modes}.
+
+@c As noted in BUGS, -m is broken client/server (Nov
+@c 96). Also see testsuite log2-* tests.
+The @samp{-m} option specifies a description for the
+file. This description appears in the history log (if
+it is enabled, @pxref{history file}). It will also be
+saved in the version history inside the repository when
+the file is committed. The @code{log} command displays
+this description. The description can be changed using
+@samp{admin -t}. @xref{admin}. If you omit the
+@samp{-m @var{description}} flag, an empty string will
+be used. You will not be prompted for a description.
+@end deffn
+
+For example, the following commands add the file
+@file{backend.c} to the repository:
+
+@c This example used to specify
+@c -m "Optimizer and code generation passes."
+@c to the cvs add command, but that doesn't work
+@c client/server (see log2 in sanity.sh). Should fix CVS,
+@c but also seems strange to document things which
+@c don't work...
+@example
+$ cvs add backend.c
+$ cvs commit -m "Early version. Not yet compilable." backend.c
+@end example
+
+When you add a file it is added only on the branch
+which you are working on (@pxref{Branching and merging}). You can
+later merge the additions to another branch if you want
+(@pxref{Merging adds and removals}).
+@c Should we mention that earlier versions of CVS
+@c lacked this feature (1.3) or implemented it in a buggy
+@c way (well, 1.8 had many bugs in cvs update -j)?
+@c Should we mention the bug/limitation regarding a
+@c file being a regular file on one branch and a directory
+@c on another?
+@c FIXME: This needs an example, or several, here or
+@c elsewhere, for it to make much sense.
+@c Somewhere we need to discuss the aspects of death
+@c support which don't involve branching, I guess.
+@c Like the ability to re-create a release from a tag.
+
+@c ---------------------------------------------------------------------
+@node Removing files
+@section Removing files
+@cindex Removing files
+@cindex Deleting files
+
+@c FIXME: this node wants to be split into several
+@c smaller nodes. Could make these children of
+@c "Adding and removing", probably (death support could
+@c be its own section, for example, as could the
+@c various bits about undoing mistakes in adding and
+@c removing).
+Directories change. New files are added, and old files
+disappear. Still, you want to be able to retrieve an
+exact copy of old releases.
+
+Here is what you can do to remove a file,
+but remain able to retrieve old revisions:
+
+@itemize @bullet
+@c FIXME: should probably be saying something about
+@c having a working directory in the first place.
+@item
+Make sure that you have not made any uncommitted
+modifications to the file. @xref{Viewing differences},
+for one way to do that. You can also use the
+@code{status} or @code{update} command. If you remove
+the file without committing your changes, you will of
+course not be able to retrieve the file as it was
+immediately before you deleted it.
+
+@item
+Remove the file from your working copy of the directory.
+You can for instance use @code{rm}.
+
+@item
+Use @samp{cvs remove @var{filename}} to tell @sc{cvs} that
+you really want to delete the file.
+
+@item
+Use @samp{cvs commit @var{filename}} to actually
+perform the removal of the file from the repository.
+@end itemize
+
+@c FIXME: Somehow this should be linked in with a more
+@c general discussion of death support. I don't know
+@c whether we want to use the term "death support" or
+@c not (we can perhaps get by without it), but we do
+@c need to discuss the "dead" state in "cvs log" and
+@c related subjects. The current discussion is
+@c scattered around, and not xref'd to each other.
+@c FIXME: I think this paragraph wants to be moved
+@c later down, at least after the first example.
+When you commit the removal of the file, @sc{cvs}
+records the fact that the file no longer exists. It is
+possible for a file to exist on only some branches and
+not on others, or to re-add another file with the same
+name later. CVS will correctly create or not create
+the file, based on the @samp{-r} and @samp{-D} options
+specified to @code{checkout} or @code{update}.
+
+@c FIXME: This style seems to clash with how we
+@c document things in general.
+@cindex Remove (subcommand)
+@deffn Command {cvs remove} [options] files @dots{}
+
+Schedule file(s) to be removed from the repository
+(files which have not already been removed from the
+working directory are not processed). This command
+does not actually remove the file from the repository
+until you commit the removal. For a full list of
+options, see @ref{Invoking CVS}.
+@end deffn
+
+Here is an example of removing several files:
+
+@example
+$ cd test
+$ rm *.c
+$ cvs remove
+cvs remove: Removing .
+cvs remove: scheduling a.c for removal
+cvs remove: scheduling b.c for removal
+cvs remove: use 'cvs commit' to remove these files permanently
+$ cvs ci -m "Removed unneeded files"
+cvs commit: Examining .
+cvs commit: Committing .
+@end example
+
+As a convenience you can remove the file and @code{cvs
+remove} it in one step, by specifying the @samp{-f}
+option. For example, the above example could also be
+done like this:
+
+@example
+$ cd test
+$ cvs remove -f *.c
+cvs remove: scheduling a.c for removal
+cvs remove: scheduling b.c for removal
+cvs remove: use 'cvs commit' to remove these files permanently
+$ cvs ci -m "Removed unneeded files"
+cvs commit: Examining .
+cvs commit: Committing .
+@end example
+
+If you execute @code{remove} for a file, and then
+change your mind before you commit, you can undo the
+@code{remove} with an @code{add} command.
+@ignore
+@c is this worth saying or not? Somehow it seems
+@c confusing to me.
+Of course,
+since you have removed your copy of file in the working
+directory, @sc{cvs} does not necessarily bring back the
+contents of the file from right before you executed
+@code{remove}; instead it gets the file from the
+repository again.
+@end ignore
+
+@c FIXME: what if you change your mind after you commit
+@c it? (answer is also "cvs add" but we don't say that...).
+@c We need some index entries for thinks like "undoing
+@c removal" too.
+
+@example
+$ ls
+CVS ja.h oj.c
+$ rm oj.c
+$ cvs remove oj.c
+cvs remove: scheduling oj.c for removal
+cvs remove: use 'cvs commit' to remove this file permanently
+$ cvs add oj.c
+U oj.c
+cvs add: oj.c, version 1.1.1.1, resurrected
+@end example
+
+If you realize your mistake before you run the
+@code{remove} command you can use @code{update} to
+resurrect the file:
+
+@example
+$ rm oj.c
+$ cvs update oj.c
+cvs update: warning: oj.c was lost
+U oj.c
+@end example
+
+When you remove a file it is removed only on the branch
+which you are working on (@pxref{Branching and merging}). You can
+later merge the removals to another branch if you want
+(@pxref{Merging adds and removals}).
+
+@node Removing directories
+@section Removing directories
+@cindex Removing directories
+@cindex Directories, removing
+
+In concept removing directories is somewhat similar to
+removing files---you want the directory to not exist in
+your current working directories, but you also want to
+be able to retrieve old releases in which the directory
+existed.
+
+The way that you remove a directory is to remove all
+the files in it. You don't remove the directory
+itself; there is no way to do that.
+Instead you specify the @samp{-P} option to
+@code{cvs update}, @code{cvs checkout}, or @code{cvs
+export}, which will cause @sc{cvs} to remove empty
+directories from working directories. Probably the
+best way to do this is to always specify @samp{-P}; if
+you want an empty directory then put a dummy file (for
+example @file{.keepme}) in it to prevent @samp{-P} from
+removing it.
+
+@c I'd try to give a rationale for this, but I'm not
+@c sure there is a particularly convincing one. What
+@c we would _like_ is for CVS to do a better job of version
+@c controlling whether directories exist, to eliminate the
+@c need for -P and so that a file can be a directory in
+@c one revision and a regular file in another.
+Note that @samp{-P} is implied by the @samp{-r} or @samp{-D}
+options of @code{checkout} and @code{export}. This way
+@sc{cvs} will be able to correctly create the directory
+or not depending on whether the particular version you
+are checking out contains any files in that directory.
+
+@c ---------------------------------------------------------------------
+@node Moving files
+@section Moving and renaming files
+@cindex Moving files
+@cindex Renaming files
+@cindex Files, moving
+
+Moving files to a different directory or renaming them
+is not difficult, but some of the ways in which this
+works may be non-obvious. (Moving or renaming a
+directory is even harder. @xref{Moving directories}.).
+
+The examples below assume that the file @var{old} is renamed to
+@var{new}.
+
+@menu
+* Outside:: The normal way to Rename
+* Inside:: A tricky, alternative way
+* Rename by copying:: Another tricky, alternative way
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Outside
+@subsection The Normal way to Rename
+
+@c More rename issues. Not sure whether these are
+@c worth documenting; I'm putting them here because
+@c it seems to be as good a place as any to try to
+@c set down the issues.
+@c * "cvs annotate" will annotate either the new
+@c file or the old file; it cannot annotate _each
+@c line_ based on whether it was last changed in the
+@c new or old file. Unlike "cvs log", where the
+@c consequences of having to select either the new
+@c or old name seem fairly benign, this may be a
+@c real advantage to having CVS know about renames
+@c other than as a deletion and an addition.
+
+The normal way to move a file is to copy @var{old} to
+@var{new}, and then issue the normal @sc{cvs} commands
+to remove @var{old} from the repository, and add
+@var{new} to it.
+@c The following sentence is not true: one must cd into
+@c the directory to run "cvs add".
+@c (Both @var{old} and @var{new} could
+@c contain relative paths, for example @file{foo/bar.c}).
+
+@example
+$ mv @var{old} @var{new}
+$ cvs remove @var{old}
+$ cvs add @var{new}
+$ cvs commit -m "Renamed @var{old} to @var{new}" @var{old} @var{new}
+@end example
+
+This is the simplest way to move a file, it is not
+error-prone, and it preserves the history of what was
+done. Note that to access the history of the file you
+must specify the old or the new name, depending on what
+portion of the history you are accessing. For example,
+@code{cvs log @var{old}} will give the log up until the
+time of the rename.
+
+When @var{new} is committed its revision numbers will
+start again, usually at 1.1, so if that bothers you,
+use the @samp{-r rev} option to commit. For more
+information see @ref{Assigning revisions}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Inside
+@subsection Moving the history file
+
+This method is more dangerous, since it involves moving
+files inside the repository. Read this entire section
+before trying it out!
+
+@example
+$ cd $CVSROOT/@var{dir}
+$ mv @var{old},v @var{new},v
+@end example
+
+@noindent
+Advantages:
+
+@itemize @bullet
+@item
+The log of changes is maintained intact.
+
+@item
+The revision numbers are not affected.
+@end itemize
+
+@noindent
+Disadvantages:
+
+@itemize @bullet
+@item
+Old releases cannot easily be fetched from the
+repository. (The file will show up as @var{new} even
+in revisions from the time before it was renamed).
+
+@item
+There is no log information of when the file was renamed.
+
+@item
+Nasty things might happen if someone accesses the history file
+while you are moving it. Make sure no one else runs any of the @sc{cvs}
+commands while you move it.
+@end itemize
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Rename by copying
+@subsection Copying the history file
+
+This way also involves direct modifications to the
+repository. It is safe, but not without drawbacks.
+
+@example
+# @r{Copy the @sc{rcs} file inside the repository}
+$ cd $CVSROOT/@var{dir}
+$ cp @var{old},v @var{new},v
+# @r{Remove the old file}
+$ cd ~/@var{dir}
+$ rm @var{old}
+$ cvs remove @var{old}
+$ cvs commit @var{old}
+# @r{Remove all tags from @var{new}}
+$ cvs update @var{new}
+$ cvs log @var{new} # @r{Remember the non-branch tag names}
+$ cvs tag -d @var{tag1} @var{new}
+$ cvs tag -d @var{tag2} @var{new}
+@dots{}
+@end example
+
+By removing the tags you will be able to check out old
+revisions.
+
+@noindent
+Advantages:
+
+@itemize @bullet
+@item
+@c FIXME: Is this true about -D now that we have death
+@c support? See 5B.3 in the FAQ.
+Checking out old revisions works correctly, as long as
+you use @samp{-r@var{tag}} and not @samp{-D@var{date}}
+to retrieve the revisions.
+
+@item
+The log of changes is maintained intact.
+
+@item
+The revision numbers are not affected.
+@end itemize
+
+@noindent
+Disadvantages:
+
+@itemize @bullet
+@item
+You cannot easily see the history of the file across the rename.
+
+@ignore
+@c Is this true? I don't see how the revision numbers
+@c _could_ start over, when new,v is just old,v with
+@c the tags deleted.
+@c If there is some need to reinstate this text,
+@c it is "usually 1.1", not "1.0" and it needs an
+@c xref to Assigning revisions
+@item
+Unless you use the @samp{-r rev} (@pxref{commit
+options}) flag when @var{new} is committed its revision
+numbers will start at 1.0 again.
+@end ignore
+@end itemize
+
+@c ---------------------------------------------------------------------
+@node Moving directories
+@section Moving and renaming directories
+@cindex Moving directories
+@cindex Renaming directories
+@cindex Directories, moving
+
+The normal way to rename or move a directory is to
+rename or move each file within it as described in
+@ref{Outside}. Then check out with the @samp{-P}
+option, as described in @ref{Removing directories}.
+
+If you really want to hack the repository to rename or
+delete a directory in the repository, you can do it
+like this:
+
+@enumerate
+@item
+Inform everyone who has a checked out copy of the directory that the
+directory will be renamed. They should commit all
+their changes, and remove their working copies,
+before you take the steps below.
+
+@item
+Rename the directory inside the repository.
+
+@example
+$ cd $CVSROOT/@var{parent-dir}
+$ mv @var{old-dir} @var{new-dir}
+@end example
+
+@item
+Fix the @sc{cvs} administrative files, if necessary (for
+instance if you renamed an entire module).
+
+@item
+Tell everyone that they can check out again and continue
+working.
+
+@end enumerate
+
+If someone had a working copy the @sc{cvs} commands will
+cease to work for him, until he removes the directory
+that disappeared inside the repository.
+
+It is almost always better to move the files in the
+directory instead of moving the directory. If you move the
+directory you are unlikely to be able to retrieve old
+releases correctly, since they probably depend on the
+name of the directories.
+
+@c ---------------------------------------------------------------------
+@node History browsing
+@chapter History browsing
+@cindex History browsing
+@cindex Traceability
+@cindex Isolation
+
+@ignore
+@c This is too long for an introduction (goal is
+@c one 20x80 character screen), and also mixes up a
+@c variety of issues (parallel development, history,
+@c maybe even touches on process control).
+
+@c -- @quote{To lose ones history is to lose ones soul.}
+@c -- ///
+@c -- ///Those who cannot remember the past are condemned to repeat it.
+@c -- /// -- George Santayana
+@c -- ///
+
+@sc{cvs} tries to make it easy for a group of people to work
+together. This is done in two ways:
+
+@itemize @bullet
+@item
+Isolation---You have your own working copy of the
+source. You are not affected by modifications made by
+others until you decide to incorporate those changes
+(via the @code{update} command---@pxref{update}).
+
+@item
+Traceability---When something has changed, you can
+always see @emph{exactly} what changed.
+@end itemize
+
+There are several features of @sc{cvs} that together lead
+to traceability:
+
+@itemize @bullet
+@item
+Each revision of a file has an accompanying log
+message.
+
+@item
+All commits are optionally logged to a central history
+database.
+
+@item
+Logging information can be sent to a user-defined
+program (@pxref{loginfo}).
+@end itemize
+
+@c -- More text here.
+
+This chapter should talk about the history file, the
+@code{log} command, the usefulness of ChangeLogs
+even when you run @sc{cvs}, and things like that.
+
+@end ignore
+
+@c kind of lame, in a lot of ways the above text inside
+@c the @ignore motivates this chapter better
+Once you have used @sc{cvs} to store a version control
+history---what files have changed when, how, and by
+whom, there are a variety of mechanisms for looking
+through the history.
+
+@c FIXME: should also be talking about how you look at
+@c old revisions (e.g. "cvs update -p -r 1.2 foo.c").
+@menu
+* log messages:: Log messages
+* history database:: The history database
+* user-defined logging:: User-defined logging
+* annotate:: What revision modified each line of a file?
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node log messages
+@section Log messages
+
+@c FIXME: @xref to place where we talk about how to
+@c specify message to commit.
+Whenever you commit a file you specify a log message.
+
+@c FIXME: bring the information here, and get rid of or
+@c greatly shrink the "log" node.
+To look through the log messages which have been
+specified for every revision which has been committed,
+use the @code{cvs log} command (@pxref{log}).
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node history database
+@section The history database
+
+@c FIXME: bring the information from the history file
+@c and history nodes here. Rewrite it to be motivated
+@c better (start out by clearly explaining what gets
+@c logged in history, for example).
+You can use the history file (@pxref{history file}) to
+log various @sc{cvs} actions. To retrieve the
+information from the history file, use the @code{cvs
+history} command (@pxref{history}).
+@c
+@c The history database has many problems:
+@c * It is very unclear what field means what. This
+@c could be improved greatly by better documentation,
+@c but there are still non-orthogonalities (for
+@c example, tag does not record the "repository"
+@c field but most records do).
+@c * Confusion about files, directories, and modules.
+@c Some commands record one, some record others.
+@c * File removal is not logged. There is an 'R'
+@c record type documented, but CVS never uses it.
+@c * Tags are only logged for the "cvs rtag" command,
+@c not "cvs tag". The fix for this is not completely
+@c clear (see above about modules vs. files).
+@c * Are there other cases of operations that are not
+@c logged? One would hope for all changes to the
+@c repository to be logged somehow (particularly
+@c operations like tagging, "cvs admin -k", and other
+@c operations which do not record a history that one
+@c can get with "cvs log"). Operations on the working
+@c directory, like export, get, and release, are a
+@c second category also covered by the current "cvs
+@c history".
+@c * The history file does not record the options given
+@c to a command. The most serious manifestation of
+@c this is perhaps that it doesn't record whether a command
+@c was recursive. It is not clear to me whether one
+@c wants to log at a level very close to the command
+@c line, as a sort of way of logging each command
+@c (more or less), or whether one wants
+@c to log more at the level of what was changed (or
+@c something in between), but either way the current
+@c information has pretty big gaps.
+@c * Further details about a tag--like whether it is a
+@c branch tag or, if a non-branch tag, which branch it
+@c is on. One can find out this information about the
+@c tag as it exists _now_, but if the tag has been
+@c moved, one doesn't know what it was like at the time
+@c the history record was written.
+@c * Whether operating on a particular tag, date, or
+@c options was implicit (sticky) or explicit.
+@c
+@c Another item, only somewhat related to the above, is a
+@c way to control what is logged in the history file.
+@c This is probably the only good way to handle
+@c different people having different ideas about
+@c information/space tradeoffs.
+@c
+@c It isn't really clear that it makes sense to try to
+@c patch up the history file format as it exists now to
+@c include all that stuff. It might be better to
+@c design a whole new CVSROOT/nhistory file and "cvs
+@c nhistory" command, or some such, or in some other
+@c way trying to come up with a clean break from the
+@c past, which can address the above concerns. Another
+@c open question is how/whether this relates to
+@c taginfo/loginfo/etc.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node user-defined logging
+@section User-defined logging
+
+@c FIXME: should probably also mention the fact the -l
+@c global option can disable most of the mechanisms
+@c discussed here (why? What is the -l global option for?).
+@c
+@c FIXME: probably should centralize this information
+@c here, at least to some extent. Maybe by moving the
+@c loginfo, etc., nodes here and replacing
+@c the "user-defined logging" node with one node for
+@c each method.
+You can customize @sc{cvs} to log various kinds of
+actions, in whatever manner you choose. These
+mechanisms operate by executing a script at various
+times. The script might append a message to a file
+listing the information and the programmer who created
+it, or send mail to a group of developers, or, perhaps,
+post a message to a particular newsgroup. To log
+commits, use the @file{loginfo} file (@pxref{loginfo}).
+@c FIXME: What is difference between doing it in the
+@c modules file and using loginfo/taginfo? Why should
+@c user use one or the other?
+To log commits, checkouts, exports, and tags,
+respectively, you can also use the @samp{-i},
+@samp{-o}, @samp{-e}, and @samp{-t} options in the
+modules file. For a more flexible way of giving
+notifications to various users, which requires less in
+the way of keeping centralized scripts up to date, use
+the @code{cvs watch add} command (@pxref{Getting
+Notified}); this command is useful even if you are not
+using @code{cvs watch on}.
+
+@cindex taginfo
+@cindex Exit status, of taginfo
+The @file{taginfo} file defines programs to execute
+when someone executes a @code{tag} or @code{rtag}
+command. The @file{taginfo} file has the standard form
+for administrative files (@pxref{Administrative
+files}), where each line is a regular expression
+followed by a command to execute. The arguments passed
+to the command are, in order, the @var{tagname},
+@var{operation} (@code{add} for @code{tag},
+@code{mov} for @code{tag -F}, and @code{del} for
+@code{tag -d}), @var{repository}, and any remaining are
+pairs of @var{filename} @var{revision}. A non-zero
+exit of the filter program will cause the tag to be
+aborted.
+
+Here is an example of using taginfo to log tag and rtag
+commands. In the taginfo file put:
+
+@example
+ALL /usr/local/cvsroot/CVSROOT/loggit
+@end example
+
+Where @file{/usr/local/cvsroot/CVSROOT/loggit} contains the
+following script:
+
+@example
+#!/bin/sh
+echo "$@@" >>/home/kingdon/cvsroot/CVSROOT/taglog
+@end example
+
+@node annotate
+@section Annotate command
+@cindex annotate (subcommand)
+
+@deffn Command {cvs annotate} [@code{-flR}] [@code{-r rev}|@code{-D date}] files @dots{}
+
+For each file in @var{files}, print the head revision
+of the trunk, together with information on the last
+modification for each line. For example:
+
+@example
+$ cvs annotate ssfile
+Annotations for ssfile
+***************
+1.1 (mary 27-Mar-96): ssfile line 1
+1.2 (joe 28-Mar-96): ssfile line 2
+@end example
+
+The file @file{ssfile} currently contains two lines.
+The @code{ssfile line 1} line was checked in by
+@code{mary} on March 27. Then, on March 28, @code{joe}
+added a line @code{ssfile line 2}, without modifying
+the @code{ssfile line 1} line. This report doesn't
+tell you anything about lines which have been deleted
+or replaced; you need to use @code{cvs diff} for that
+(@pxref{diff}).
+
+@end deffn
+
+The options to @code{cvs annotate} are listed in
+@ref{Invoking CVS}, and can be used to select the files
+and revisions to annotate. The options are described
+in more detail in @ref{Common options}.
+
+@c FIXME: maybe an example using the options? Just
+@c what it means to select a revision might be worth a
+@c few words of explanation ("you want to see who
+@c changed this line *before* 1.4"...).
+
+@c ---------------------------------------------------------------------
+@node Binary files
+@chapter Handling binary files
+@cindex Binary files
+
+The most common use for @sc{cvs} is to store text
+files. With text files, @sc{cvs} can merge revisions,
+display the differences between revisions in a
+human-visible fashion, and other such operations.
+However, if you are willing to give up a few of these
+abilities, @sc{cvs} can store binary files. For
+example, one might store a web site in @sc{cvs}
+including both text files and binary images.
+
+@menu
+* Binary why:: More details on issues with binary files
+* Binary howto:: How to store them
+@end menu
+
+@node Binary why
+@section The issues with binary files
+
+While the need to manage binary files may seem obvious
+if the files that you customarily work with are binary,
+putting them into version control does present some
+additional issues.
+
+One basic function of version control is to show the
+differences between two revisions. For example, if
+someone else checked in a new version of a file, you
+may wish to look at what they changed and determine
+whether their changes are good. For text files,
+@sc{cvs} provides this functionality via the @code{cvs
+diff} command. For binary files, it may be possible to
+extract the two revisions and then compare them with a
+tool external to @sc{cvs} (for example, word processing
+software often has such a feature). If there is no
+such tool, one must track changes via other mechanisms,
+such as urging people to write good log messages, and
+hoping that the changes they actually made were the
+changes that they intended to make.
+
+Another ability of a version control system is the
+ability to merge two revisions. For @sc{cvs} this
+happens in two contexts. The first is when users make
+changes in separate working directories
+(@pxref{Multiple developers}). The second is when one
+merges explicitly with the @samp{update -j} command
+(@pxref{Branching and merging}).
+
+In the case of text
+files, @sc{cvs} can merge changes made independently,
+and signal a conflict if the changes conflict. With
+binary files, the best that @sc{cvs} can do is present
+the two different copies of the file, and leave it to
+the user to resolve the conflict. The user may choose
+one copy or the other, or may run an external merge
+tool which knows about that particular file format, if
+one exists.
+Note that having the user merge relies primarily on the
+user to not accidentally omit some changes, and thus is
+potentially error prone.
+
+If this process is thought to be undesirable, the best
+choice may be to avoid merging. To avoid the merges
+that result from separate working directories, see the
+discussion of reserved checkouts (file locking) in
+@ref{Multiple developers}. To avoid the merges
+resulting from branches, restrict use of branches.
+
+@node Binary howto
+@section How to store binary files
+
+There are two issues with using @sc{cvs} to store
+binary files. The first is that @sc{cvs} by default
+converts line endings between the canonical form in
+which they are stored in the repository (linefeed
+only), and the form appropriate to the operating system
+in use on the client (for example, carriage return
+followed by line feed for Windows NT).
+
+The second is that a binary file might happen to
+contain data which looks like a keyword (@pxref{Keyword
+substitution}), so keyword expansion must be turned
+off.
+
+@c FIXME: the third is that one can't do merges with
+@c binary files. xref to Multiple Developers and the
+@c reserved checkout issues.
+
+The @samp{-kb} option available with some @sc{cvs}
+commands insures that neither line ending conversion
+nor keyword expansion will be done.
+
+Here is an example of how you can create a new file
+using the @samp{-kb} flag:
+
+@example
+$ echo '$@asis{}Id$' > kotest
+$ cvs add -kb -m"A test file" kotest
+$ cvs ci -m"First checkin; contains a keyword" kotest
+@end example
+
+If a file accidentally gets added without @samp{-kb},
+one can use the @code{cvs admin} command to recover.
+For example:
+
+@example
+$ echo '$@asis{}Id$' > kotest
+$ cvs add -m"A test file" kotest
+$ cvs ci -m"First checkin; contains a keyword" kotest
+$ cvs admin -kb kotest
+$ cvs update -A kotest
+# @r{For non-unix systems:}
+# @r{Copy in a good copy of the file from outside CVS}
+$ cvs commit -m "make it binary" kotest
+@end example
+
+@c Trying to describe this for both unix and non-unix
+@c in the same description is very confusing. Might
+@c want to split the two, or just ditch the unix "shortcut"
+@c (unixheads don't do much with binary files, anyway).
+@c This used to say "(Try the above example, and do a
+@c @code{cat kotest} after every command)". But that
+@c only really makes sense for the unix case.
+When you check in the file @file{kotest} the file is
+not preserved as a binary file, because you did not
+check it in as a binary file. The @code{cvs
+admin -kb} command sets the default keyword
+substitution method for this file, but it does not
+alter the working copy of the file that you have. If you need to
+cope with line endings (that is, you are using
+@sc{cvs} on a non-unix system), then you need to
+check in a new copy of the file, as shown by the
+@code{cvs commit} command above.
+On unix, the @code{cvs update -A} command suffices.
+@c FIXME: should also describe what the *other users*
+@c need to do, if they have checked out copies which
+@c have been corrupted by lack of -kb. I think maybe
+@c "cvs update -kb" or "cvs
+@c update -A" would suffice, although the user who
+@c reported this suggested removing the file, manually
+@c removing it from CVS/Entries, and then "cvs update"
+
+However, in using @code{cvs admin -k} to change the
+keyword expansion, be aware that the keyword expansion
+mode is not version controlled. This means that, for
+example, that if you have a text file in old releases,
+and a binary file with the same name in new releases,
+@sc{cvs} provides no way to check out the file in text
+or binary mode depending on what version you are
+checking out. There is no good workaround for this
+problem.
+
+You can also set a default for whether @code{cvs add}
+and @code{cvs import} treat a file as binary based on
+its name; for example you could say that files who
+names end in @samp{.exe} are binary. @xref{Wrappers}.
+There is currently no way to have @sc{cvs} detect
+whether a file is binary based on its contents. The
+main difficulty with designing such a feature is that
+it is not clear how to distinguish between binary and
+non-binary files, and the rules to apply would vary
+considerably with the operating system.
+@c For example, it would be good on MS-DOS-family OSes
+@c for anything containing ^Z to be binary. Having
+@c characters with the 8th bit set imply binary is almost
+@c surely a bad idea in the context of ISO-8859-* and
+@c other such character sets. On VMS or the Mac, we
+@c could use the OS's file typing. This is a
+@c commonly-desired feature, and something of this sort
+@c may make sense. But there are a lot of pitfalls here.
+@c
+@c Another, probably better, way to tell is to read the
+@c file in text mode, write it to a temp file in text
+@c mode, and then do a binary mode compare of the two
+@c files. If they differ, it is a binary file. This
+@c might have problems on VMS (or some other system
+@c with several different text modes), but in general
+@c should be relatively portable. The only other
+@c downside I can think of is that it would be fairly
+@c slow, but that is perhaps a small price to pay for
+@c not having your files corrupted. Another issue is
+@c what happens if you import a text file with bare
+@c linefeeds on Windows. Such files will show up on
+@c Windows sometimes (I think some native windows
+@c programs even write them, on occasion). Perhaps it
+@c is reasonable to treat such files as binary; after
+@c all it is something of a presumption to assume that
+@c the user would want the linefeeds converted to CRLF.
+
+@c ---------------------------------------------------------------------
+@node Multiple developers
+@chapter Multiple developers
+@cindex Multiple developers
+@cindex Team of developers
+@cindex File locking
+@cindex Locking files
+@cindex Working copy
+@cindex Reserved checkouts
+@cindex Unreserved checkouts
+@cindex RCS-style locking
+
+When more than one person works on a software project
+things often get complicated. Often, two people try to
+edit the same file simultaneously. One solution, known
+as @dfn{file locking} or @dfn{reserved checkouts}, is
+to allow only one person to edit each file at a time.
+This is the only solution with some version control
+systems, including @sc{rcs} and @sc{sccs}. Currently
+the usual way to get reserved checkouts with @sc{cvs}
+is the @code{cvs admin -l} command (@pxref{admin
+options}). This is not as nicely integrated into
+@sc{cvs} as the watch features, described below, but it
+seems that most people with a need for reserved
+checkouts find it adequate.
+@c Or "find it better than worrying about implementing
+@c nicely integrated reserved checkouts" or ...?
+It also may be possible to use the watches
+features described below, together with suitable
+procedures (not enforced by software), to avoid having
+two people edit at the same time.
+
+@c Our unreserved checkout model might not
+@c be quite the same as others. For example, I
+@c think that some systems will tend to create a branch
+@c in the case where CVS prints "up-to-date check failed".
+@c It isn't clear to me whether we should try to
+@c explore these subtleties; it could easily just
+@c confuse people.
+The default model with @sc{cvs} is known as
+@dfn{unreserved checkouts}. In this model, developers
+can edit their own @dfn{working copy} of a file
+simultaneously. The first person that commits his
+changes has no automatic way of knowing that another
+has started to edit it. Others will get an error
+message when they try to commit the file. They must
+then use @sc{cvs} commands to bring their working copy
+up to date with the repository revision. This process
+is almost automatic.
+
+@c FIXME? should probably use the word "watch" here, to
+@c tie this into the text below and above.
+@sc{cvs} also supports mechanisms which facilitate
+various kinds of communication, without actually
+enforcing rules like reserved checkouts do.
+
+The rest of this chapter describes how these various
+models work, and some of the issues involved in
+choosing between them.
+
+@ignore
+Here is a draft reserved checkout design or discussion
+of the issues. This seems like as good a place as any
+for this.
+
+Might want a cvs lock/cvs unlock--in which the names
+differ from edit/unedit because the network must be up
+for these to work. unedit gives an error if there is a
+reserved checkout in place (so that people don't
+accidentally leave locks around); unlock gives an error
+if one is not in place (this is more arguable; perhaps
+it should act like unedit in that case).
+
+On the other hand, might want it so that emacs,
+scripts, etc., can get ready to edit a file without
+having to know which model is in use. In that case we
+would have a "cvs watch lock" (or .cvsrc?) (that is,
+three settings, "on", "off", and "lock"). Having cvs
+watch lock set would cause a get to record in the CVS
+directory which model is in use, and cause "cvs edit"
+to change behaviors. We'd want a way to query which
+setting is in effect (this would be handy even if it is
+only "on" or "off" as presently). If lock is in
+effect, then commit would require a lock before
+allowing a checkin; chmod wouldn't suffice (might be
+debatable--see chmod comment below, in watches--but it
+is the way people expect RCS to work and I can't think
+of any significant downside. On the other hand, maybe
+it isn't worth bothering, because people who are used
+to RCS wouldn't think to use chmod anyway).
+
+Implementation: use file attributes or use RCS
+locking. The former avoids more dependence on RCS
+behaviors we will need to reimplement as we librarify
+RCS, and makes it easier to import/export RCS files (in
+that context, want to ignore the locker field). But
+note that RCS locks are per-branch, which is the
+correct behavior (this is also an issue for the "watch
+on" features; they should be per-branch too).
+
+Here are a few more random notes about implementation
+details, assuming "cvs watch lock" and
+
+CVS/Watched file? Or try to fit this into CVS/Entries somehow?
+Cases: (1) file is checked out (unreserved or with watch on) by old
+version of CVS, now we do something with new one, (2) file is checked
+out by new version, now we do something with old one.
+
+Remote protocol would have a "Watched" analogous to "Mode". Of course
+it would apply to all Updated-like requests. How do we keep this
+setting up to date? I guess that there wants to be a Watched request,
+and the server would send a new one if it isn't up to date? (Ugh--hard
+to implement and slows down "cvs -q update"--is there an easier way?)
+
+"cvs edit"--checks CVS/Watched, and if watch lock, then sends
+"edit-lock" request. Which comes back with a Checked-in with
+appropriate Watched (off, on, lock, locked, or some such?), or error
+message if already locked.
+
+"cvs commit"--only will commit if off/on/locked. lock is not OK.
+
+Doc:
+note that "cvs edit" must be connected to network if watch lock is in
+effect.
+
+Talk about what to do if someone has locked a file and you want to
+edit that file. (breaking locks, or lack thereof).
+
+
+One other idea (which could work along with the
+existing "cvs admin -l" reserved checkouts, as well as
+the above):
+
+"cvs editors" could show who has the file locked, if
+someone does.
+
+@end ignore
+
+@menu
+* File status:: A file can be in several states
+* Updating a file:: Bringing a file up-to-date
+* Conflicts example:: An informative example
+* Informing others:: To cooperate you must inform
+* Concurrency:: Simultaneous repository access
+* Watches:: Mechanisms to track who is editing files
+* Choosing a model:: Reserved or unreserved checkouts?
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node File status
+@section File status
+@cindex File status
+@cindex Status of a file
+
+@c Shouldn't this start with an example or something,
+@c introducing the unreserved checkout model? Before we
+@c dive into listing states?
+Based on what operations you have performed on a
+checked out file, and what operations others have
+performed to that file in the repository, one can
+classify a file in a number of states. The states, as
+reported by the @code{status} command, are:
+
+@c The order of items is chosen to group logically
+@c similar outputs together.
+@c People who want alphabetical can use the index...
+@table @asis
+@cindex Up-to-date
+@item Up-to-date
+The file is identical with the latest revision in the
+repository for the branch in use.
+@c FIXME: should we clarify "in use"? The answer is
+@c sticky tags, and trying to distinguish branch sticky
+@c tags from non-branch sticky tags seems rather awkward
+@c here.
+@c FIXME: What happens with non-branch sticky tags? Is
+@c a stuck file "Up-to-date" or "Needs checkout" or what?
+
+@item Locally Modified
+@cindex Locally Modified
+You have edited the file, and not yet committed your changes.
+
+@item Locally Added
+@cindex Locally Added
+You have added the file with @code{add}, and not yet
+committed your changes.
+@c There are many cases involving the file being
+@c added/removed/modified in the working directory, and
+@c added/removed/modified in the repository, which we
+@c don't try to describe here. I'm not sure that "cvs
+@c status" produces a non-confusing output in most of
+@c those cases.
+
+@item Locally Removed
+@cindex Locally Removed
+You have removed the file with @code{remove}, and not yet
+committed your changes.
+
+@item Needs Checkout
+@cindex Needs Checkout
+Someone else has committed a newer revision to the
+repository. The name is slightly misleading; you will
+ordinarily use @code{update} rather than
+@code{checkout} to get that newer revision.
+
+@item Needs Patch
+@cindex Needs Patch
+@c See also newb-123j0 in sanity.sh (although that case
+@c should probably be changed rather than documented).
+Like Needs Checkout, but the @sc{cvs} server will send
+a patch rather than the entire file. Sending a patch or
+sending an entire file accomplishes the same thing.
+
+@item Needs Merge
+@cindex Needs Merge
+Someone else has committed a newer revision to the repository, and you
+have also made modifications to the file.
+
+@item File had conflicts on merge
+@cindex File had conflicts on merge
+@c is it worth saying that this message was "Unresolved
+@c Conflict" in CVS 1.9 and earlier? I'm inclined to
+@c think that is unnecessarily confusing to new users.
+This is like Locally Modified, except that a previous
+@code{update} command gave a conflict. If you have not
+already done so, you need to
+resolve the conflict as described in @ref{Conflicts example}.
+
+@item Unknown
+@cindex Unknown
+@sc{cvs} doesn't know anything about this file. For
+example, you have created a new file and have not run
+@code{add}.
+@c
+@c "Entry Invalid" and "Classify Error" are also in the
+@c status.c. The latter definitely indicates a CVS bug
+@c (should it be worded more like "internal error" so
+@c people submit bug reports if they see it?). The former
+@c I'm not as sure; I haven't tracked down whether/when it
+@c appears in "cvs status" output.
+
+@end table
+
+To help clarify the file status, @code{status} also
+reports the @code{Working revision} which is the
+revision that the file in the working directory derives
+from, and the @code{Repository revision} which is the
+latest revision in the repository for the branch in
+use.
+@c FIXME: should we clarify "in use"? The answer is
+@c sticky tags, and trying to distinguish branch sticky
+@c tags from non-branch sticky tags seems rather awkward
+@c here.
+@c FIXME: What happens with non-branch sticky tags?
+@c What is the Repository Revision there? See the
+@c comment at vn_rcs in cvs.h, which is kind of
+@c confused--we really need to document better what this
+@c field contains.
+@c Q: Should we document "New file!" and other such
+@c outputs or are they self-explanatory?
+@c FIXME: what about the date to the right of "Working
+@c revision"? It doesn't appear with client/server and
+@c seems unnecessary (redundant with "ls -l") so
+@c perhaps it should be removed for non-client/server too?
+@c FIXME: Need some examples.
+@c FIXME: Working revision can also be something like
+@c "-1.3" for a locally removed file. Not at all
+@c self-explanatory (and it is possible that CVS should
+@c be changed rather than documenting this).
+
+@c Would be nice to have an @example showing output
+@c from cvs status, with comments showing the xref
+@c where each part of the output is described. This
+@c might fit in nicely if it is desirable to split this
+@c node in two; one to introduce "cvs status" and one
+@c to list each of the states.
+The options to @code{status} are listed in
+@ref{Invoking CVS}. For information on its @code{Sticky tag}
+and @code{Sticky date} output, see @ref{Sticky tags}.
+For information on its @code{Sticky options} output,
+see the @samp{-k} option in @ref{update options}.
+
+You can think of the @code{status} and @code{update}
+commands as somewhat complementary. You use
+@code{update} to bring your files up to date, and you
+can use @code{status} to give you some idea of what an
+@code{update} would do (of course, the state of the
+repository might change before you actually run
+@code{update}). In fact, if you want a command to
+display file status in a more brief format than is
+displayed by the @code{status} command, you can invoke
+
+@cindex update, to display file status
+@example
+$ cvs -n -q update
+@end example
+
+The @samp{-n} option means to not actually do the
+update, but merely to display statuses; the @samp{-q}
+option avoids printing the name of each directory. For
+more information on the @code{update} command, and
+these options, see @ref{Invoking CVS}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Updating a file
+@section Bringing a file up to date
+@cindex Bringing a file up to date
+@cindex Updating a file
+@cindex Merging a file
+@cindex Update, introduction
+
+When you want to update or merge a file, use the @code{update}
+command. For files that are not up to date this is roughly equivalent
+to a @code{checkout} command: the newest revision of the file is
+extracted from the repository and put in your working directory.
+
+Your modifications to a file are never lost when you
+use @code{update}. If no newer revision exists,
+running @code{update} has no effect. If you have
+edited the file, and a newer revision is available,
+@sc{cvs} will merge all changes into your working copy.
+
+For instance, imagine that you checked out revision 1.4 and started
+editing it. In the meantime someone else committed revision 1.5, and
+shortly after that revision 1.6. If you run @code{update} on the file
+now, @sc{cvs} will incorporate all changes between revision 1.4 and 1.6 into
+your file.
+
+@cindex Overlap
+If any of the changes between 1.4 and 1.6 were made too
+close to any of the changes you have made, an
+@dfn{overlap} occurs. In such cases a warning is
+printed, and the resulting file includes both
+versions of the lines that overlap, delimited by
+special markers.
+@xref{update}, for a complete description of the
+@code{update} command.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Conflicts example
+@section Conflicts example
+@cindex Merge, an example
+@cindex Example of merge
+@cindex driver.c (merge example)
+
+Suppose revision 1.4 of @file{driver.c} contains this:
+
+@example
+#include <stdio.h>
+
+void main()
+@{
+ parse();
+ if (nerr == 0)
+ gencode();
+ else
+ fprintf(stderr, "No code generated.\n");
+ exit(nerr == 0 ? 0 : 1);
+@}
+@end example
+
+@noindent
+Revision 1.6 of @file{driver.c} contains this:
+
+@example
+#include <stdio.h>
+
+int main(int argc,
+ char **argv)
+@{
+ parse();
+ if (argc != 1)
+ @{
+ fprintf(stderr, "tc: No args expected.\n");
+ exit(1);
+ @}
+ if (nerr == 0)
+ gencode();
+ else
+ fprintf(stderr, "No code generated.\n");
+ exit(!!nerr);
+@}
+@end example
+
+@noindent
+Your working copy of @file{driver.c}, based on revision
+1.4, contains this before you run @samp{cvs update}:
+@c -- Really include "cvs"?
+
+@example
+#include <stdlib.h>
+#include <stdio.h>
+
+void main()
+@{
+ init_scanner();
+ parse();
+ if (nerr == 0)
+ gencode();
+ else
+ fprintf(stderr, "No code generated.\n");
+ exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+@}
+@end example
+
+@noindent
+You run @samp{cvs update}:
+@c -- Really include "cvs"?
+
+@example
+$ cvs update driver.c
+RCS file: /usr/local/cvsroot/yoyodyne/tc/driver.c,v
+retrieving revision 1.4
+retrieving revision 1.6
+Merging differences between 1.4 and 1.6 into driver.c
+rcsmerge warning: overlaps during merge
+cvs update: conflicts found in driver.c
+C driver.c
+@end example
+
+@noindent
+@cindex Conflicts (merge example)
+@sc{cvs} tells you that there were some conflicts.
+Your original working file is saved unmodified in
+@file{.#driver.c.1.4}. The new version of
+@file{driver.c} contains this:
+
+@example
+#include <stdlib.h>
+#include <stdio.h>
+
+int main(int argc,
+ char **argv)
+@{
+ init_scanner();
+ parse();
+ if (argc != 1)
+ @{
+ fprintf(stderr, "tc: No args expected.\n");
+ exit(1);
+ @}
+ if (nerr == 0)
+ gencode();
+ else
+ fprintf(stderr, "No code generated.\n");
+@asis{}<<<<<<< driver.c
+ exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+@asis{}=======
+ exit(!!nerr);
+@asis{}>>>>>>> 1.6
+@}
+@end example
+
+@noindent
+@cindex Markers, conflict
+@cindex Conflict markers
+@cindex <<<<<<<
+@cindex >>>>>>>
+@cindex =======
+
+Note how all non-overlapping modifications are incorporated in your working
+copy, and that the overlapping section is clearly marked with
+@samp{<<<<<<<}, @samp{=======} and @samp{>>>>>>>}.
+
+@cindex Resolving a conflict
+@cindex Conflict resolution
+You resolve the conflict by editing the file, removing the markers and
+the erroneous line. Suppose you end up with this file:
+@c -- Add xref to the pcl-cvs manual when it talks
+@c -- about this.
+@example
+#include <stdlib.h>
+#include <stdio.h>
+
+int main(int argc,
+ char **argv)
+@{
+ init_scanner();
+ parse();
+ if (argc != 1)
+ @{
+ fprintf(stderr, "tc: No args expected.\n");
+ exit(1);
+ @}
+ if (nerr == 0)
+ gencode();
+ else
+ fprintf(stderr, "No code generated.\n");
+ exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+@}
+@end example
+
+@noindent
+You can now go ahead and commit this as revision 1.7.
+
+@example
+$ cvs commit -m "Initialize scanner. Use symbolic exit values." driver.c
+Checking in driver.c;
+/usr/local/cvsroot/yoyodyne/tc/driver.c,v <-- driver.c
+new revision: 1.7; previous revision: 1.6
+done
+@end example
+
+For your protection, @sc{cvs} will refuse to check in a
+file if a conflict occurred and you have not resolved
+the conflict. Currently to resolve a conflict, you
+must change the timestamp on the file. In previous
+versions of @sc{cvs}, you also needed to
+insure that the file contains no conflict markers.
+Because
+your file may legitimately contain conflict markers (that
+is, occurrences of @samp{>>>>>>> } at the start of a
+line that don't mark a conflict), the current
+version of @sc{cvs} will print a warning and proceed to
+check in the file.
+@c The old behavior was really icky; the only way out
+@c was to start hacking on
+@c the @code{CVS/Entries} file or other such workarounds.
+@c
+@c If the timestamp thing isn't considered nice enough,
+@c maybe there should be a "cvs resolved" command
+@c which clears the conflict indication. For a nice user
+@c interface, this should be invoked by an interactive
+@c merge tool like emerge rather than by the user
+@c directly--such a tool can verify that the user has
+@c really dealt with each conflict.
+
+@cindex emerge
+If you use release 1.04 or later of pcl-cvs (a @sc{gnu}
+Emacs front-end for @sc{cvs}) you can use an Emacs
+package called emerge to help you resolve conflicts.
+See the documentation for pcl-cvs.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Informing others
+@section Informing others about commits
+@cindex Informing others
+@cindex Spreading information
+@cindex Mail, automatic mail on commit
+
+It is often useful to inform others when you commit a
+new revision of a file. The @samp{-i} option of the
+@file{modules} file, or the @file{loginfo} file, can be
+used to automate this process. @xref{modules}.
+@xref{loginfo}. You can use these features of @sc{cvs}
+to, for instance, instruct @sc{cvs} to mail a
+message to all developers, or post a message to a local
+newsgroup.
+@c -- More text would be nice here.
+
+@node Concurrency
+@section Several developers simultaneously attempting to run CVS
+
+@cindex Locks, cvs, introduction
+@c For a discussion of *why* CVS creates locks, see
+@c the comment at the start of src/lock.c
+If several developers try to run @sc{cvs} at the same
+time, one may get the following message:
+
+@example
+[11:43:23] waiting for bach's lock in /usr/local/cvsroot/foo
+@end example
+
+@cindex #cvs.rfl, removing
+@cindex #cvs.wfl, removing
+@cindex #cvs.lock, removing
+@sc{cvs} will try again every 30 seconds, and either
+continue with the operation or print the message again,
+if it still needs to wait. If a lock seems to stick
+around for an undue amount of time, find the person
+holding the lock and ask them about the cvs command
+they are running. If they aren't running a cvs
+command, look in the repository directory mentioned in
+the message and remove files which they own whose names
+start with @file{#cvs.rfl},
+@file{#cvs.wfl}, or @file{#cvs.lock}.
+
+Note that these locks are to protect @sc{cvs}'s
+internal data structures and have no relationship to
+the word @dfn{lock} in the sense used by
+@sc{rcs}---which refers to reserved checkouts
+(@pxref{Multiple developers}).
+
+Any number of people can be reading from a given
+repository at a time; only when someone is writing do
+the locks prevent other people from reading or writing.
+
+@cindex Atomic transactions, lack of
+@cindex Transactions, atomic, lack of
+@c the following talks about what one might call commit/update
+@c atomicity.
+@c Probably also should say something about
+@c commit/commit atomicity, that is, "An update will
+@c not get partial versions of more than one commit".
+@c CVS currently has this property and I guess we can
+@c make it a documented feature.
+@c For example one person commits
+@c a/one.c and b/four.c and another commits a/two.c and
+@c b/three.c. Then an update cannot get the new a/one.c
+@c and a/two.c and the old b/four.c and b/three.c.
+One might hope for the following property
+
+@example
+If someone commits some changes in one cvs command,
+then an update by someone else will either get all the
+changes, or none of them.
+@end example
+
+but @sc{cvs} does @emph{not} have this property. For
+example, given the files
+
+@example
+a/one.c
+a/two.c
+b/three.c
+b/four.c
+@end example
+
+if someone runs
+
+@example
+cvs ci a/two.c b/three.c
+@end example
+
+and someone else runs @code{cvs update} at the same
+time, the person running @code{update} might get only
+the change to @file{b/three.c} and not the change to
+@file{a/two.c}.
+
+@node Watches
+@section Mechanisms to track who is editing files
+@cindex Watches
+
+For many groups, use of @sc{cvs} in its default mode is
+perfectly satisfactory. Users may sometimes go to
+check in a modification only to find that another
+modification has intervened, but they deal with it and
+proceed with their check in. Other groups prefer to be
+able to know who is editing what files, so that if two
+people try to edit the same file they can choose to
+talk about who is doing what when rather than be
+surprised at check in time. The features in this
+section allow such coordination, while retaining the
+ability of two developers to edit the same file at the
+same time.
+
+@c Some people might ask why CVS does not enforce the
+@c rule on chmod, by requiring a cvs edit before a cvs
+@c commit. The main reason is that it could always be
+@c circumvented--one could edit the file, and
+@c then when ready to check it in, do the cvs edit and put
+@c in the new contents and do the cvs commit. One
+@c implementation note: if we _do_ want to have cvs commit
+@c require a cvs edit, we should store the state on
+@c whether the cvs edit has occurred in the working
+@c directory, rather than having the server try to keep
+@c track of what working directories exist.
+@c FIXME: should the above discussion be part of the
+@c manual proper, somewhere, not just in a comment?
+For maximum benefit developers should use @code{cvs
+edit} (not @code{chmod}) to make files read-write to
+edit them, and @code{cvs release} (not @code{rm}) to
+discard a working directory which is no longer in use,
+but @sc{cvs} is not able to enforce this behavior.
+
+@c I'm a little dissatisfied with this presentation,
+@c because "watch on"/"edit"/"editors" are one set of
+@c functionality, and "watch add"/"watchers" is another
+@c which is somewhat orthogonal even though they interact in
+@c various ways. But I think it might be
+@c confusing to describe them separately (e.g. "watch
+@c add" with loginfo). I don't know.
+
+@menu
+* Setting a watch:: Telling CVS to watch certain files
+* Getting Notified:: Telling CVS to notify you
+* Editing files:: How to edit a file which is being watched
+* Watch information:: Information about who is watching and editing
+* Watches Compatibility:: Watches interact poorly with CVS 1.6 or earlier
+@end menu
+
+@node Setting a watch
+@subsection Telling CVS to watch certain files
+
+To enable the watch features, you first specify that
+certain files are to be watched.
+
+@cindex watch on (subcommand)
+@deffn Command {cvs watch on} [@code{-lR}] files @dots{}
+
+@cindex Read-only files, and watches
+Specify that developers should run @code{cvs edit}
+before editing @var{files}. CVS will create working
+copies of @var{files} read-only, to remind developers
+to run the @code{cvs edit} command before working on
+them.
+
+If @var{files} includes the name of a directory, CVS
+arranges to watch all files added to the corresponding
+repository directory, and sets a default for files
+added in the future; this allows the user to set
+notification policies on a per-directory basis. The
+contents of the directory are processed recursively,
+unless the @code{-l} option is given.
+The @code{-R} option can be used to force recursion if the @code{-l}
+option is set in @file{~/.cvsrc} (@pxref{~/.cvsrc}).
+
+If @var{files} is omitted, it defaults to the current directory.
+
+@cindex watch off (subcommand)
+@end deffn
+
+@deffn Command {cvs watch off} [@code{-lR}] files @dots{}
+
+Do not create @var{files} read-only on checkout; thus,
+developers will not be reminded to use @code{cvs edit}
+and @code{cvs unedit}. CVS will check out @var{files}
+read-write as usual, unless other permissions override
+due to the @code{PreservePermissions} option being
+enabled in the @file{config} administrative file
+(@pxref{Special Files}, @pxref{config})
+
+The @var{files} and options are processed as for @code{cvs
+watch on}.
+
+@end deffn
+
+@node Getting Notified
+@subsection Telling CVS to notify you
+
+You can tell @sc{cvs} that you want to receive
+notifications about various actions taken on a file.
+You can do this without using @code{cvs watch on} for
+the file, but generally you will want to use @code{cvs
+watch on}, so that developers use the @code{cvs edit}
+command.
+
+@cindex watch add (subcommand)
+@deffn Command {cvs watch add} [@code{-a} action] [@code{-lR}] files @dots{}
+
+Add the current user to the list of people to receive notification of
+work done on @var{files}.
+
+The @code{-a} option specifies what kinds of events CVS should notify
+the user about. @var{action} is one of the following:
+
+@table @code
+
+@item edit
+Another user has applied the @code{cvs edit} command (described
+below) to a file.
+
+@item unedit
+Another user has applied the @code{cvs unedit} command (described
+below) or the @code{cvs release} command to a file, or has deleted
+the file and allowed @code{cvs update} to recreate it.
+
+@item commit
+Another user has committed changes to a file.
+
+@item all
+All of the above.
+
+@item none
+None of the above. (This is useful with @code{cvs edit},
+described below.)
+
+@end table
+
+The @code{-a} option may appear more than once, or not at all. If
+omitted, the action defaults to @code{all}.
+
+The @var{files} and options are processed as for the
+@code{cvs watch} commands.
+
+@end deffn
+
+
+@cindex watch remove (subcommand)
+@deffn Command {cvs watch remove} [@code{-a} action] [@code{-lR}] files @dots{}
+
+Remove a notification request established using @code{cvs watch add};
+the arguments are the same. If the @code{-a} option is present, only
+watches for the specified actions are removed.
+
+@end deffn
+
+@cindex notify (admin file)
+When the conditions exist for notification, @sc{cvs}
+calls the @file{notify} administrative file. Edit
+@file{notify} as one edits the other administrative
+files (@pxref{Intro administrative files}). This
+file follows the usual conventions for administrative
+files (@pxref{syntax}), where each line is a regular
+expression followed by a command to execute. The
+command should contain a single occurrence of @samp{%s}
+which will be replaced by the user to notify; the rest
+of the information regarding the notification will be
+supplied to the command on standard input. The
+standard thing to put in the @code{notify} file is the
+single line:
+
+@example
+ALL mail %s -s \"CVS notification\"
+@end example
+
+This causes users to be notified by electronic mail.
+@c FIXME: should it be this hard to set up this
+@c behavior (and the result when one fails to do so,
+@c silent failure to notify, so non-obvious)? Should
+@c CVS give a warning if no line in notify matches (and
+@c document the use of "DEFAULT :" for the case where
+@c skipping the notification is indeed desired)?
+
+@cindex users (admin file)
+Note that if you set this up in the straightforward
+way, users receive notifications on the server machine.
+One could of course write a @file{notify} script which
+directed notifications elsewhere, but to make this
+easy, @sc{cvs} allows you to associate a notification
+address for each user. To do so create a file
+@file{users} in @file{CVSROOT} with a line for each
+user in the format @var{user}:@var{value}. Then
+instead of passing the name of the user to be notified
+to @file{notify}, @sc{cvs} will pass the @var{value}
+(normally an email address on some other machine).
+
+@sc{cvs} does not notify you for your own changes.
+Currently this check is done based on whether the user
+name of the person taking the action which triggers
+notification matches the user name of the person
+getting notification. In fact, in general, the watches
+features only track one edit by each user. It probably
+would be more useful if watches tracked each working
+directory separately, so this behavior might be worth
+changing.
+@c "behavior might be worth changing" is an effort to
+@c point to future directions while also not promising
+@c that "they" (as in "why don't they fix CVS to....")
+@c will do this.
+@c one implementation issue is identifying whether a
+@c working directory is same or different. Comparing
+@c pathnames/hostnames is hopeless, but having the server
+@c supply a serial number which the client stores in the
+@c CVS directory as a magic cookie should work.
+
+@node Editing files
+@subsection How to edit a file which is being watched
+
+@cindex Checkout, as term for getting ready to edit
+Since a file which is being watched is checked out
+read-only, you cannot simply edit it. To make it
+read-write, and inform others that you are planning to
+edit it, use the @code{cvs edit} command. Some systems
+call this a @dfn{checkout}, but @sc{cvs} uses that term
+for obtaining a copy of the sources (@pxref{Getting the
+source}), an operation which those systems call a
+@dfn{get} or a @dfn{fetch}.
+@c Issue to think about: should we transition CVS
+@c towards the "get" terminology? "cvs get" is already a
+@c synonym for "cvs checkout" and that section of the
+@c manual refers to "Getting the source". If this is
+@c done, needs to be done gingerly (for example, we should
+@c still accept "checkout" in .cvsrc files indefinitely
+@c even if the CVS's messages are changed from "cvs checkout: "
+@c to "cvs get: ").
+@c There is a concern about whether "get" is not as
+@c good for novices because it is a more general term
+@c than "checkout" (and thus arguably harder to assign
+@c a technical meaning for).
+
+@cindex edit (subcommand)
+@deffn Command {cvs edit} [options] files @dots{}
+
+Prepare to edit the working files @var{files}. CVS makes the
+@var{files} read-write, and notifies users who have requested
+@code{edit} notification for any of @var{files}.
+
+The @code{cvs edit} command accepts the same @var{options} as the
+@code{cvs watch add} command, and establishes a temporary watch for the
+user on @var{files}; CVS will remove the watch when @var{files} are
+@code{unedit}ed or @code{commit}ted. If the user does not wish to
+receive notifications, she should specify @code{-a none}.
+
+The @var{files} and options are processed as for the @code{cvs
+watch} commands.
+
+@strong{Caution:} If the @code{PreservePermissions}
+option is enabled in the repository (@pxref{config}),
+CVS will not change the permissions on any of the
+@var{files}. The reason for this change is to ensure
+that using @samp{cvs edit} does not interfere with the
+ability to store file permissions in the CVS
+repository.
+
+@end deffn
+
+Normally when you are done with a set of changes, you
+use the @code{cvs commit} command, which checks in your
+changes and returns the watched files to their usual
+read-only state. But if you instead decide to abandon
+your changes, or not to make any changes, you can use
+the @code{cvs unedit} command.
+
+@cindex unedit (subcommand)
+@cindex Abandoning work
+@cindex Reverting to repository version
+@deffn Command {cvs unedit} [@code{-lR}] files @dots{}
+
+Abandon work on the working files @var{files}, and revert them to the
+repository versions on which they are based. CVS makes those
+@var{files} read-only for which users have requested notification using
+@code{cvs watch on}. CVS notifies users who have requested @code{unedit}
+notification for any of @var{files}.
+
+The @var{files} and options are processed as for the
+@code{cvs watch} commands.
+
+If watches are not in use, the @code{unedit} command
+probably does not work, and the way to revert to the
+repository version is to remove the file and then use
+@code{cvs update} to get a new copy. The meaning is
+not precisely the same; removing and updating may also
+bring in some changes which have been made in the
+repository since the last time you updated.
+@c It would be a useful enhancement to CVS to make
+@c unedit work in the non-watch case as well.
+@end deffn
+
+When using client/server @sc{cvs}, you can use the
+@code{cvs edit} and @code{cvs unedit} commands even if
+@sc{cvs} is unable to successfully communicate with the
+server; the notifications will be sent upon the next
+successful @sc{cvs} command.
+
+@node Watch information
+@subsection Information about who is watching and editing
+
+@cindex watchers (subcommand)
+@deffn Command {cvs watchers} [@code{-lR}] files @dots{}
+
+List the users currently watching changes to @var{files}. The report
+includes the files being watched, and the mail address of each watcher.
+
+The @var{files} and options are processed as for the
+@code{cvs watch} commands.
+
+@end deffn
+
+
+@cindex editors (subcommand)
+@deffn Command {cvs editors} [@code{-lR}] files @dots{}
+
+List the users currently working on @var{files}. The report
+includes the mail address of each user, the time when the user began
+working with the file, and the host and path of the working directory
+containing the file.
+
+The @var{files} and options are processed as for the
+@code{cvs watch} commands.
+
+@end deffn
+
+@node Watches Compatibility
+@subsection Using watches with old versions of CVS
+
+@cindex CVS 1.6, and watches
+If you use the watch features on a repository, it
+creates @file{CVS} directories in the repository and
+stores the information about watches in that directory.
+If you attempt to use @sc{cvs} 1.6 or earlier with the
+repository, you get an error message such as the
+following (all on one line):
+
+@example
+cvs update: cannot open CVS/Entries for reading:
+No such file or directory
+@end example
+
+and your operation will likely be aborted. To use the
+watch features, you must upgrade all copies of @sc{cvs}
+which use that repository in local or server mode. If
+you cannot upgrade, use the @code{watch off} and
+@code{watch remove} commands to remove all watches, and
+that will restore the repository to a state which
+@sc{cvs} 1.6 can cope with.
+
+@node Choosing a model
+@section Choosing between reserved or unreserved checkouts
+@cindex Choosing, reserved or unreserved checkouts
+
+Reserved and unreserved checkouts each have pros and
+cons. Let it be said that a lot of this is a matter of
+opinion or what works given different groups' working
+styles, but here is a brief description of some of the
+issues. There are many ways to organize a team of
+developers. @sc{cvs} does not try to enforce a certain
+organization. It is a tool that can be used in several
+ways.
+
+Reserved checkouts can be very counter-productive. If
+two persons want to edit different parts of a file,
+there may be no reason to prevent either of them from
+doing so. Also, it is common for someone to take out a
+lock on a file, because they are planning to edit it,
+but then forget to release the lock.
+
+@c "many groups"? specifics? cites to papers on this?
+@c some way to weasel-word it a bit more so we don't
+@c need facts :-)?
+People, especially people who are familiar with
+reserved checkouts, often wonder how often conflicts
+occur if unreserved checkouts are used, and how
+difficult they are to resolve. The experience with
+many groups is that they occur rarely and usually are
+relatively straightforward to resolve.
+
+The rarity of serious conflicts may be surprising, until one realizes
+that they occur only when two developers disagree on the proper design
+for a given section of code; such a disagreement suggests that the
+team has not been communicating properly in the first place. In order
+to collaborate under @emph{any} source management regimen, developers
+must agree on the general design of the system; given this agreement,
+overlapping changes are usually straightforward to merge.
+
+In some cases unreserved checkouts are clearly
+inappropriate. If no merge tool exists for the kind of
+file you are managing (for example word processor files
+or files edited by Computer Aided Design programs), and
+it is not desirable to change to a program which uses a
+mergeable data format, then resolving conflicts is
+going to be unpleasant enough that you generally will
+be better off to simply avoid the conflicts instead, by
+using reserved checkouts.
+
+The watches features described above in @ref{Watches}
+can be considered to be an intermediate model between
+reserved checkouts and unreserved checkouts. When you
+go to edit a file, it is possible to find out who else
+is editing it. And rather than having the system
+simply forbid both people editing the file, it can tell
+you what the situation is and let you figure out
+whether it is a problem in that particular case or not.
+Therefore, for some groups it can be considered the
+best of both the reserved checkout and unreserved
+checkout worlds.
+
+@c ---------------------------------------------------------------------
+@node Revision management
+@chapter Revision management
+@cindex Revision management
+
+@c -- This chapter could be expanded a lot.
+@c -- Experiences are very welcome!
+
+If you have read this far, you probably have a pretty
+good grasp on what @sc{cvs} can do for you. This
+chapter talks a little about things that you still have
+to decide.
+
+If you are doing development on your own using @sc{cvs}
+you could probably skip this chapter. The questions
+this chapter takes up become more important when more
+than one person is working in a repository.
+
+@menu
+* When to commit:: Some discussion on the subject
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node When to commit
+@section When to commit?
+@cindex When to commit
+@cindex Commit, when to
+@cindex Policy
+
+Your group should decide which policy to use regarding
+commits. Several policies are possible, and as your
+experience with @sc{cvs} grows you will probably find
+out what works for you.
+
+If you commit files too quickly you might commit files
+that do not even compile. If your partner updates his
+working sources to include your buggy file, he will be
+unable to compile the code. On the other hand, other
+persons will not be able to benefit from the
+improvements you make to the code if you commit very
+seldom, and conflicts will probably be more common.
+
+It is common to only commit files after making sure
+that they can be compiled. Some sites require that the
+files pass a test suite. Policies like this can be
+enforced using the commitinfo file
+(@pxref{commitinfo}), but you should think twice before
+you enforce such a convention. By making the
+development environment too controlled it might become
+too regimented and thus counter-productive to the real
+goal, which is to get software written.
+
+@c ---------------------------------------------------------------------
+@node Keyword substitution
+@chapter Keyword substitution
+@cindex Keyword substitution
+@cindex Keyword expansion
+@cindex Identifying files
+
+@comment Be careful when editing this chapter.
+@comment Remember that this file is kept under
+@comment version control, so we must not accidentally
+@comment include a valid keyword in the running text.
+
+As long as you edit source files inside a working
+directory you can always find out the state of
+your files via @samp{cvs status} and @samp{cvs log}.
+But as soon as you export the files from your
+development environment it becomes harder to identify
+which revisions they are.
+
+CVS can use a mechanism known as @dfn{keyword
+substitution} (or @dfn{keyword expansion}) to help
+identifying the files. Embedded strings of the form
+@code{$@var{keyword}$} and
+@code{$@var{keyword}:@dots{}$} in a file are replaced
+with strings of the form
+@code{$@var{keyword}:@var{value}$} whenever you obtain
+a new revision of the file.
+
+@menu
+* Keyword list:: Keywords
+* Using keywords:: Using keywords
+* Avoiding substitution:: Avoiding substitution
+* Substitution modes:: Substitution modes
+* Log keyword:: Problems with the $@asis{}Log$ keyword.
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Keyword list
+@section Keyword List
+@cindex Keyword List
+
+@c FIXME: need some kind of example here I think,
+@c perhaps in a
+@c "Keyword intro" node. The intro in the "Keyword
+@c substitution" node itself seems OK, but to launch
+@c into a list of the keywords somehow seems too abrupt.
+
+This is a list of the keywords:
+
+@table @code
+@cindex Author keyword
+@item $@asis{Author}$
+The login name of the user who checked in the revision.
+
+@cindex Date keyword
+@item $@asis{Date}$
+The date and time (UTC) the revision was checked in.
+
+@cindex Header keyword
+@item $@asis{Header}$
+A standard header containing the full pathname of the
+@sc{rcs} file, the revision number, the date (UTC), the
+author, the state, and the locker (if locked). Files
+will normally never be locked when you use @sc{cvs}.
+
+@cindex Id keyword
+@item $@asis{Id}$
+Same as @code{$@asis{Header}$}, except that the @sc{rcs}
+filename is without a path.
+
+@cindex Name keyword
+@item $@asis{Name}$
+Tag name used to check out this file. The keyword is
+expanded only if one checks out with an explicit tag
+name. For example, when running the command @code{cvs
+co -r first}, the keyword expands to @samp{Name: first}.
+
+@cindex Locker keyword
+@item $@asis{Locker}$
+The login name of the user who locked the revision
+(empty if not locked, which is the normal case unless
+@code{cvs admin -l} is in use).
+
+@cindex Log keyword
+@item $@asis{Log}$
+The log message supplied during commit, preceded by a
+header containing the @sc{rcs} filename, the revision
+number, the author, and the date (UTC). Existing log
+messages are @emph{not} replaced. Instead, the new log
+message is inserted after @code{$@asis{Log:@dots{}}$}.
+Each new line is prefixed with the same string which
+precedes the @code{$Log} keyword. For example, if the
+file contains
+
+@example
+ /* Here is what people have been up to:
+ *
+ * $@asis{}Log: frob.c,v $
+ * Revision 1.1 1997/01/03 14:23:51 joe
+ * Add the superfrobnicate option
+ *
+ */
+@end example
+
+@noindent
+then additional lines which are added when expanding
+the @code{$Log} keyword will be preceded by @samp{ * }.
+Unlike previous versions of @sc{cvs} and @sc{rcs}, the
+@dfn{comment leader} from the @sc{rcs} file is not used.
+The @code{$Log} keyword is useful for
+accumulating a complete change log in a source file,
+but for several reasons it can be problematic.
+@xref{Log keyword}.
+
+@cindex RCSfile keyword
+@item $@asis{RCSfile}$
+The name of the RCS file without a path.
+
+@cindex Revision keyword
+@item $@asis{Revision}$
+The revision number assigned to the revision.
+
+@cindex Source keyword
+@item $@asis{Source}$
+The full pathname of the RCS file.
+
+@cindex State keyword
+@item $@asis{State}$
+The state assigned to the revision. States can be
+assigned with @code{cvs admin -s}---see @ref{admin options}.
+
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Using keywords
+@section Using keywords
+
+To include a keyword string you simply include the
+relevant text string, such as @code{$@asis{Id}$}, inside the
+file, and commit the file. @sc{cvs} will automatically
+expand the string as part of the commit operation.
+
+It is common to embed the @code{$@asis{}Id$} string in
+the source files so that it gets passed through to
+generated files. For example, if you are managing
+computer program source code, you might include a
+variable which is initialized to contain that string.
+Or some C compilers may provide a @code{#pragma ident}
+directive. Or a document management system might
+provide a way to pass a string through to generated
+files.
+
+@c Would be nice to give an example, but doing this in
+@c portable C is not possible and the problem with
+@c picking any one language (VMS HELP files, Ada,
+@c troff, whatever) is that people use CVS for all
+@c kinds of files.
+
+@cindex Ident (shell command)
+The @code{ident} command (which is part of the @sc{rcs}
+package) can be used to extract keywords and their
+values from a file. This can be handy for text files,
+but it is even more useful for extracting keywords from
+binary files.
+
+@example
+$ ident samp.c
+samp.c:
+ $@asis{}Id: samp.c,v 1.5 1993/10/19 14:57:32 ceder Exp $
+$ gcc samp.c
+$ ident a.out
+a.out:
+ $@asis{}Id: samp.c,v 1.5 1993/10/19 14:57:32 ceder Exp $
+@end example
+
+@cindex What (shell command)
+S@sc{ccs} is another popular revision control system.
+It has a command, @code{what}, which is very similar to
+@code{ident} and used for the same purpose. Many sites
+without @sc{rcs} have @sc{sccs}. Since @code{what}
+looks for the character sequence @code{@@(#)} it is
+easy to include keywords that are detected by either
+command. Simply prefix the keyword with the
+magic @sc{sccs} phrase, like this:
+
+@example
+static char *id="@@(#) $@asis{}Id: ab.c,v 1.5 1993/10/19 14:57:32 ceder Exp $";
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Avoiding substitution
+@section Avoiding substitution
+
+Keyword substitution has its disadvantages. Sometimes
+you might want the literal text string
+@samp{$@asis{}Author$} to appear inside a file without
+@sc{cvs} interpreting it as a keyword and expanding it
+into something like @samp{$@asis{}Author: ceder $}.
+
+There is unfortunately no way to selectively turn off
+keyword substitution. You can use @samp{-ko}
+(@pxref{Substitution modes}) to turn off keyword
+substitution entirely.
+
+In many cases you can avoid using keywords in
+the source, even though they appear in the final
+product. For example, the source for this manual
+contains @samp{$@@asis@{@}Author$} whenever the text
+@samp{$@asis{}Author$} should appear. In @code{nroff}
+and @code{troff} you can embed the null-character
+@code{\&} inside the keyword for a similar effect.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Substitution modes
+@section Substitution modes
+@cindex Keyword substitution, changing modes
+@cindex -k (keyword substitution)
+@cindex Kflag
+
+@c FIXME: This could be made more coherent, by expanding it
+@c with more examples or something.
+Each file has a stored default substitution mode, and
+each working directory copy of a file also has a
+substitution mode. The former is set by the @samp{-k}
+option to @code{cvs add} and @code{cvs admin}; the
+latter is set by the @samp{-k} or @samp{-A} options to @code{cvs
+checkout} or @code{cvs update}. @code{cvs diff} also
+has a @samp{-k} option. For some examples,
+see @ref{Binary files}, and @ref{Merging and keywords}.
+@c The fact that -A is overloaded to mean both reset
+@c sticky options and reset sticky tags/dates is
+@c somewhat questionable. Perhaps there should be
+@c separate options to reset sticky options (e.g. -k
+@c A") and tags/dates (someone suggested -r HEAD could
+@c do this instead of setting a sticky tag of "HEAD"
+@c as in the status quo but I haven't thought much
+@c about that idea. Of course -r .reset or something
+@c could be coined if this needs to be a new option).
+@c On the other hand, having -A mean "get things back
+@c into the state after a fresh checkout" has a certain
+@c appeal, and maybe there is no sufficient reason for
+@c creeping featurism in this area.
+
+The modes available are:
+
+@table @samp
+@item -kkv
+Generate keyword strings using the default form, e.g.
+@code{$@asis{}Revision: 5.7 $} for the @code{Revision}
+keyword.
+
+@item -kkvl
+Like @samp{-kkv}, except that a locker's name is always
+inserted if the given revision is currently locked.
+The locker's name is only relevant if @code{cvs admin
+-l} is in use.
+
+@item -kk
+Generate only keyword names in keyword strings; omit
+their values. For example, for the @code{Revision}
+keyword, generate the string @code{$@asis{}Revision$}
+instead of @code{$@asis{}Revision: 5.7 $}. This option
+is useful to ignore differences due to keyword
+substitution when comparing different revisions of a
+file (@pxref{Merging and keywords}).
+
+@item -ko
+Generate the old keyword string, present in the working
+file just before it was checked in. For example, for
+the @code{Revision} keyword, generate the string
+@code{$@asis{}Revision: 1.1 $} instead of
+@code{$@asis{}Revision: 5.7 $} if that is how the
+string appeared when the file was checked in.
+
+@item -kb
+Like @samp{-ko}, but also inhibit conversion of line
+endings between the canonical form in which they are
+stored in the repository (linefeed only), and the form
+appropriate to the operating system in use on the
+client. For systems, like unix, which use linefeed
+only to terminate lines, this is the same as
+@samp{-ko}. For more information on binary files, see
+@ref{Binary files}.
+
+@item -kv
+Generate only keyword values for keyword strings. For
+example, for the @code{Revision} keyword, generate the string
+@code{5.7} instead of @code{$@asis{}Revision: 5.7 $}.
+This can help generate files in programming languages
+where it is hard to strip keyword delimiters like
+@code{$@asis{}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.
+
+One often would like to use @samp{-kv} with @code{cvs
+export}---@pxref{export}. But be aware that doesn't
+handle an export containing binary files correctly.
+
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Log keyword
+@section Problems with the $@asis{}Log$ keyword.
+
+The @code{$@asis{}Log$} keyword is somewhat
+controversial. As long as you are working on your
+development system the information is easily accessible
+even if you do not use the @code{$@asis{}Log$}
+keyword---just do a @code{cvs log}. Once you export
+the file the history information might be useless
+anyhow.
+
+A more serious concern is that @sc{cvs} is not good at
+handling @code{$@asis{}Log$} entries when a branch is
+merged onto the main trunk. Conflicts often result
+from the merging operation.
+@c Might want to check whether the CVS implementation
+@c of RCS_merge has this problem the same way rcsmerge
+@c does. I would assume so....
+
+People also tend to "fix" the log entries in the file
+(correcting spelling mistakes and maybe even factual
+errors). If that is done the information from
+@code{cvs log} will not be consistent with the
+information inside the file. This may or may not be a
+problem in real life.
+
+It has been suggested that the @code{$@asis{}Log$}
+keyword should be inserted @emph{last} in the file, and
+not in the files header, if it is to be used at all.
+That way the long list of change messages will not
+interfere with everyday source file browsing.
+
+@c ---------------------------------------------------------------------
+@node Tracking sources
+@chapter Tracking third-party sources
+@cindex Third-party sources
+@cindex Tracking sources
+
+@c FIXME: Need discussion of added and removed files.
+@c FIXME: This doesn't really adequately introduce the
+@c concepts of "vendor" and "you". They don't *have*
+@c to be separate organizations or separate people.
+@c We want a description which is somewhat more based on
+@c the technical issues of which sources go where, but
+@c also with enough examples of how this relates to
+@c relationships like customer-supplier, developer-QA,
+@c maintainer-contributor, or whatever, to make it
+@c seem concrete.
+If you modify a program to better fit your site, you
+probably want to include your modifications when the next
+release of the program arrives. @sc{cvs} can help you with
+this task.
+
+@cindex Vendor
+@cindex Vendor branch
+@cindex Branch, vendor-
+In the terminology used in @sc{cvs}, the supplier of the
+program is called a @dfn{vendor}. The unmodified
+distribution from the vendor is checked in on its own
+branch, the @dfn{vendor branch}. @sc{cvs} reserves branch
+1.1.1 for this use.
+
+When you modify the source and commit it, your revision
+will end up on the main trunk. When a new release is
+made by the vendor, you commit it on the vendor branch
+and copy the modifications onto the main trunk.
+
+Use the @code{import} command to create and update
+the vendor branch. When you import a new file,
+the vendor branch is made the `head' revision, so
+anyone that checks out a copy of the file gets that
+revision. When a local modification is committed it is
+placed on the main trunk, and made the `head'
+revision.
+
+@menu
+* First import:: Importing for the first time
+* Update imports:: Updating with the import command
+* Reverting local changes:: Reverting to the latest vendor release
+* Binary files in imports:: Binary files require special handling
+* Keywords in imports:: Keyword substitution might be undesirable
+* Multiple vendor branches:: What if you get sources from several places?
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node First import
+@section Importing for the first time
+@cindex Importing modules
+
+@c Should mention naming conventions for vendor tags,
+@c release tags, and perhaps directory names.
+Use the @code{import} command to check in the sources
+for the first time. When you use the @code{import}
+command to track third-party sources, the @dfn{vendor
+tag} and @dfn{release tags} are useful. The
+@dfn{vendor tag} is a symbolic name for the branch
+(which is always 1.1.1, unless you use the @samp{-b
+@var{branch}} flag---@xref{Multiple vendor branches}.). The
+@dfn{release tags} are symbolic names for a particular
+release, such as @samp{FSF_0_04}.
+
+@c I'm not completely sure this belongs here. But
+@c we need to say it _somewhere_ reasonably obvious; it
+@c is a common misconception among people first learning CVS
+Note that @code{import} does @emph{not} change the
+directory in which you invoke it. In particular, it
+does not set up that directory as a @sc{cvs} working
+directory; if you want to work with the sources import
+them first and then check them out into a different
+directory (@pxref{Getting the source}).
+
+@cindex wdiff (import example)
+Suppose you have the sources to a program called
+@code{wdiff} in a directory @file{wdiff-0.04},
+and are going to make private modifications that you
+want to be able to use even when new releases are made
+in the future. You start by importing the source to
+your repository:
+
+@example
+$ cd wdiff-0.04
+$ cvs import -m "Import of FSF v. 0.04" fsf/wdiff FSF_DIST WDIFF_0_04
+@end example
+
+The vendor tag is named @samp{FSF_DIST} in the above
+example, and the only release tag assigned is
+@samp{WDIFF_0_04}.
+@c FIXME: Need to say where fsf/wdiff comes from.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Update imports
+@section Updating with the import command
+
+When a new release of the source arrives, you import it into the
+repository with the same @code{import} command that you used to set up
+the repository in the first place. The only difference is that you
+specify a different release tag this time.
+
+@example
+$ tar xfz wdiff-0.05.tar.gz
+$ cd wdiff-0.05
+$ cvs import -m "Import of FSF v. 0.05" fsf/wdiff FSF_DIST WDIFF_0_05
+@end example
+
+For files that have not been modified locally, the newly created
+revision becomes the head revision. If you have made local
+changes, @code{import} will warn you that you must merge the changes
+into the main trunk, and tell you to use @samp{checkout -j} to do so.
+
+@c FIXME: why "wdiff" here and "fsf/wdiff" in the
+@c "import"? I think the assumption is that one has
+@c "wdiff fsf/wdiff" or some such in modules, but it
+@c would be better to not use modules in this example.
+@example
+$ cvs checkout -jFSF_DIST:yesterday -jFSF_DIST wdiff
+@end example
+
+@noindent
+The above command will check out the latest revision of
+@samp{wdiff}, merging the changes made on the vendor branch @samp{FSF_DIST}
+since yesterday into the working copy. If any conflicts arise during
+the merge they should be resolved in the normal way (@pxref{Conflicts
+example}). Then, the modified files may be committed.
+
+Using a date, as suggested above, assumes that you do
+not import more than one release of a product per
+day. If you do, you can always use something like this
+instead:
+
+@example
+$ cvs checkout -jWDIFF_0_04 -jWDIFF_0_05 wdiff
+@end example
+
+@noindent
+In this case, the two above commands are equivalent.
+
+@node Reverting local changes
+@section Reverting to the latest vendor release
+
+You can also revert local changes completely and return
+to the latest vendor release by changing the `head'
+revision back to the vendor branch on all files. For
+example, if you have a checked-out copy of the sources
+in @file{~/work.d/wdiff}, and you want to revert to the
+vendor's version for all the files in that directory,
+you would type:
+
+@example
+$ cd ~/work.d/wdiff
+$ cvs admin -bWDIFF .
+@end example
+
+@noindent
+You must specify the @samp{-bWDIFF} without any space
+after the @samp{-b}. @xref{admin options}.
+
+@node Binary files in imports
+@section How to handle binary files with cvs import
+
+Use the @samp{-k} wrapper option to tell import which
+files are binary. @xref{Wrappers}.
+
+@node Keywords in imports
+@section How to handle keyword substitution with cvs import
+
+The sources which you are importing may contain
+keywords (@pxref{Keyword substitution}). For example,
+the vendor may use @sc{cvs} or some other system
+which uses similar keyword expansion syntax. If you
+just import the files in the default fashion, then
+the keyword expansions supplied by the vendor will
+be replaced by keyword expansions supplied by your
+own copy of @sc{cvs}. It may be more convenient to
+maintain the expansions supplied by the vendor, so
+that this information can supply information about
+the sources that you imported from the vendor.
+
+To maintain the keyword expansions supplied by the
+vendor, supply the @samp{-ko} option to @code{cvs
+import} the first time you import the file.
+This will turn off keyword expansion
+for that file entirely, so if you want to be more
+selective you'll have to think about what you want
+and use the @samp{-k} option to @code{cvs update} or
+@code{cvs admin} as appropriate.
+@c Supplying -ko to import if the file already existed
+@c has no effect. Not clear to me whether it should
+@c or not.
+
+@node Multiple vendor branches
+@section Multiple vendor branches
+
+All the examples so far assume that there is only one
+vendor from which you are getting sources. In some
+situations you might get sources from a variety of
+places. For example, suppose that you are dealing with
+a project where many different people and teams are
+modifying the software. There are a variety of ways to
+handle this, but in some cases you have a bunch of
+source trees lying around and what you want to do more
+than anything else is just to all put them in CVS so
+that you at least have them in one place.
+
+For handling situations in which there may be more than
+one vendor, you may specify the @samp{-b} option to
+@code{cvs import}. It takes as an argument the vendor
+branch to import to. The default is @samp{-b 1.1.1}.
+
+For example, suppose that there are two teams, the red
+team and the blue team, that are sending you sources.
+You want to import the red team's efforts to branch
+1.1.1 and use the vendor tag RED. You want to import
+the blue team's efforts to branch 1.1.3 and use the
+vendor tag BLUE. So the commands you might use are:
+
+@example
+$ cvs import dir RED RED_1-0
+$ cvs import -b 1.1.3 dir BLUE BLUE_1-5
+@end example
+
+Note that if your vendor tag does not match your
+@samp{-b} option, CVS will not detect this case! For
+example,
+
+@example
+$ cvs import -b 1.1.3 dir RED RED_1-0
+@end example
+
+@noindent
+Be careful; this kind of mismatch is sure to sow
+confusion or worse. I can't think of a useful purpose
+for the ability to specify a mismatch here, but if you
+discover such a use, don't. CVS is likely to make this
+an error in some future release.
+
+@c Probably should say more about the semantics of
+@c multiple branches. What about the default branch?
+@c What about joining (perhaps not as useful with
+@c multiple branches, or perhaps it is. Either way
+@c should be mentioned).
+
+@c I'm not sure about the best location for this. In
+@c one sense, it might belong right after we've introduced
+@c CVS's basic version control model, because people need
+@c to figure out builds right away. The current location
+@c is based on the theory that it kind of akin to the
+@c "Revision management" section.
+@node Builds
+@chapter How your build system interacts with CVS
+@cindex Builds
+@cindex make
+
+As mentioned in the introduction, @sc{cvs} does not
+contain software for building your software from source
+code. This section describes how various aspects of
+your build system might interact with @sc{cvs}.
+
+@c Is there a way to discuss this without reference to
+@c tools other than CVS? I'm not sure there is; I
+@c wouldn't think that people who learn CVS first would
+@c even have this concern.
+One common question, especially from people who are
+accustomed to @sc{rcs}, is how to make their build get
+an up to date copy of the sources. The answer to this
+with @sc{cvs} is two-fold. First of all, since
+@sc{cvs} itself can recurse through directories, there
+is no need to modify your @file{Makefile} (or whatever
+configuration file your build tool uses) to make sure
+each file is up to date. Instead, just use two
+commands, first @code{cvs -q update} and then
+@code{make} or whatever the command is to invoke your
+build tool. Secondly, you do not necessarily
+@emph{want} to get a copy of a change someone else made
+until you have finished your own work. One suggested
+approach is to first update your sources, then
+implement, build and
+test the change you were thinking of, and then commit
+your sources (updating first if necessary). By
+periodically (in between changes, using the approach
+just described) updating your entire tree, you ensure
+that your sources are sufficiently up to date.
+
+@cindex Bill of materials
+One common need is to record which versions of which
+source files went into a particular build. This kind
+of functionality is sometimes called @dfn{bill of
+materials} or something similar. The best way to do
+this with @sc{cvs} is to use the @code{tag} command to
+record which versions went into a given build
+(@pxref{Tags}).
+
+Using @sc{cvs} in the most straightforward manner
+possible, each developer will have a copy of the entire
+source tree which is used in a particular build. If
+the source tree is small, or if developers are
+geographically dispersed, this is the preferred
+solution. In fact one approach for larger projects is
+to break a project down into smaller
+@c I say subsystem instead of module because they may or
+@c may not use the modules file.
+separately-compiled subsystems, and arrange a way of
+releasing them internally so that each developer need
+check out only those subsystems which are they are
+actively working on.
+
+Another approach is to set up a structure which allows
+developers to have their own copies of some files, and
+for other files to access source files from a central
+location. Many people have come up with some such a
+@c two such people are paul@sander.cupertino.ca.us (for
+@c a previous employer)
+@c and gtornblo@senet.abb.se (spicm and related tools),
+@c but as far as I know
+@c no one has nicely packaged or released such a system (or
+@c instructions for constructing one).
+system using features such as the symbolic link feature
+found in many operating systems, or the @code{VPATH}
+feature found in many versions of @code{make}. One build
+tool which is designed to help with this kind of thing
+is Odin (see
+@code{ftp://ftp.cs.colorado.edu/pub/distribs/odin}).
+@c Should we be saying more about Odin? Or how you use
+@c it with CVS? Also, the Prime Time Freeware for Unix
+@c disk (see http://www.ptf.com/) has Odin (with a nice
+@c paragraph summarizing it on the web), so that might be a
+@c semi-"official" place to point people.
+@c
+@c Of course, many non-CVS systems have this kind of
+@c functionality, for example OSF's ODE
+@c (http://www.osf.org/ode/) or mk
+@c (http://www.grin.net/~pzi/mk-3.18.4.docs/mk_toc.html
+@c He has changed providers in the past; a search engine search
+@c for "Peter Ziobrzynski" probably won't get too many
+@c spurious hits :-). A more stable URL might be
+@c ftp://ftp.uu.net/pub/cmvc/mk). But I'm not sure
+@c there is any point in mentioning them here unless they
+@c can work with CVS.
+
+@c ---------------------------------------------------------------------
+@node Special Files
+@chapter Special Files
+
+@cindex Special files
+@cindex Device nodes
+@cindex Ownership, saving in CVS
+@cindex Permissions, saving in CVS
+@cindex Hard links
+@cindex Symbolic links
+
+In normal circumstances, CVS works only with regular
+files. Every file in a project is assumed to be
+persistent; it must be possible to open, read and close
+them; and so on. CVS also ignores file permissions and
+ownerships, leaving such issues to be resolved by the
+developer at installation time. In other words, it is
+not possible to "check in" a device into a repository;
+if the device file cannot be opened, CVS will refuse to
+handle it. Files also lose their ownerships and
+permissions during repository transactions.
+
+If the configuration variable @code{PreservePermissions}
+(@pxref{config}) is set in the repository, CVS will
+save the following file characteristics in the
+repository:
+
+@itemize @bullet
+@item user and group ownership
+@item permissions
+@item major and minor device numbers
+@item symbolic links
+@item hard link structure
+@end itemize
+
+Using the @code{PreservePermissions} option affects the
+behavior of CVS in several ways. First, some of the
+new operations supported by CVS are not accessible to
+all users. In particular, file ownership and special
+file characteristics may only be changed by the
+superuser. When the @code{PreservePermissions}
+configuration variable is set, therefore, users will
+have to be `root' in order to perform CVS operations.
+
+When @code{PreservePermissions} is in use, some CVS
+operations (such as @samp{cvs status}) will not
+recognize a file's hard link structure, and so will
+emit spurious warnings about mismatching hard links.
+The reason is that CVS's internal structure does not
+make it easy for these operations to collect all the
+necessary data about hard links, so they check for file
+conflicts with inaccurate data.
+
+A more subtle difference is that CVS considers a file
+to have changed only if its contents have changed
+(specifically, if the modification time of the working
+file does not match that of the repository's file).
+Therefore, if only the permissions, ownership or hard
+linkage have changed, or if a device's major or minor
+numbers have changed, CVS will not notice. In order to
+commit such a change to the repository, you must force
+the commit with @samp{cvs commit -f}. This also means
+that if a file's permissions have changed and the
+repository file is newer than the working copy,
+performing @samp{cvs update} will silently change the
+permissions on the working copy.
+
+Changing hard links in a CVS repository is particularly
+delicate. Suppose that file @file{foo} is linked to
+file @file{old}, but is later relinked to file
+@file{new}. You can wind up in the unusual situation
+where, although @file{foo}, @file{old} and @file{new}
+have all had their underlying link patterns changed,
+only @file{foo} and @file{new} have been modified, so
+@file{old} is not considered a candidate for checking
+in. It can be very easy to produce inconsistent
+results this way. Therefore, we recommend that when it
+is important to save hard links in a repository, the
+prudent course of action is to @code{touch} any file
+whose linkage or status has changed since the last
+checkin. Indeed, it may be wise to @code{touch *}
+before each commit in a directory with complex hard
+link structures.
+
+It is worth noting that only regular files may
+be merged, for reasons that hopefully are obvious. If
+@samp{cvs update} or @samp{cvs checkout -j} attempts to
+merge a symbolic link with a regular file, or two
+device files for different kinds of devices, CVS will
+report a conflict and refuse to perform the merge. At
+the same time, @samp{cvs diff} will not report any
+differences between these files, since no meaningful
+textual comparisons can be made on files which contain
+no text.
+
+The @code{PreservePermissions} features do not work
+with client/server @sc{cvs}. Another limitation is
+that hard links must be to other files within the same
+directory; hard links across directories are not
+supported.
+
+@c ---------------------------------------------------------------------
+@node CVS commands
+@appendix Guide to CVS commands
+
+This appendix describes the overall structure of
+@sc{cvs} commands, and describes some commands in
+detail (others are described elsewhere; for a quick
+reference to @sc{cvs} commands, @pxref{Invoking CVS}).
+@c The idea is that we want to move the commands which
+@c are described here into the main body of the manual,
+@c in the process reorganizing the manual to be
+@c organized around what the user wants to do, not
+@c organized around CVS commands.
+@c
+@c Note that many users do expect a manual which is
+@c organized by command. At least some users do.
+@c One good addition to the "organized by command"
+@c section (if any) would be "see also" links.
+@c The awk manual might be a good example; it has a
+@c reference manual which is more verbose than Invoking
+@c CVS but probably somewhat less verbose than CVS
+@c Commands.
+
+@menu
+* Structure:: Overall structure of CVS commands
+* Exit status:: Indicating CVS's success or failure
+* ~/.cvsrc:: Default options with the ~/.csvrc file
+* Global options:: Options you give to the left of cvs_command
+* Common options:: Options you give to the right of cvs_command
+* admin:: Administration
+* checkout:: Checkout sources for editing
+* commit:: Check files into the repository
+* diff:: Show differences between revisions
+* export:: Export sources from CVS, similar to checkout
+* history:: Show status of files and users
+* import:: Import sources into CVS, using vendor branches
+* log:: Show log messages for files
+* rdiff:: 'patch' format diffs between releases
+* release:: Indicate that a directory is no longer in use
+* update:: Bring work tree in sync with repository
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Structure
+@appendixsec Overall structure of CVS commands
+@cindex Structure
+@cindex CVS command structure
+@cindex Command structure
+@cindex Format of CVS commands
+
+The overall format of all @sc{cvs} commands is:
+
+@example
+cvs [ cvs_options ] cvs_command [ command_options ] [ command_args ]
+@end example
+
+@table @code
+@item cvs
+The name of the @sc{cvs} program.
+
+@item cvs_options
+Some options that affect all sub-commands of @sc{cvs}. These are
+described below.
+
+@item cvs_command
+One of several different sub-commands. Some of the commands have
+aliases that can be used instead; those aliases are noted in the
+reference manual for that command. There are only two situations
+where you may omit @samp{cvs_command}: @samp{cvs -H} elicits a
+list of available commands, and @samp{cvs -v} displays version
+information on @sc{cvs} itself.
+
+@item command_options
+Options that are specific for the command.
+
+@item command_args
+Arguments to the commands.
+@end table
+
+There is unfortunately some confusion between
+@code{cvs_options} and @code{command_options}.
+@samp{-l}, when given as a @code{cvs_option}, only
+affects some of the commands. When it is given as a
+@code{command_option} is has a different meaning, and
+is accepted by more commands. In other words, do not
+take the above categorization too seriously. Look at
+the documentation instead.
+
+@node Exit status
+@appendixsec CVS's exit status
+@cindex Exit status, of CVS
+
+CVS can indicate to the calling environment whether it
+succeeded or failed by setting its @dfn{exit status}.
+The exact way of testing the exit status will vary from
+one operating system to another. For example in a unix
+shell script the @samp{$?} variable will be 0 if the
+last command returned a successful exit status, or
+greater than 0 if the exit status indicated failure.
+
+If CVS is successful, it returns a successful status;
+if there is an error, it prints an error message and
+returns a failure status. The one exception to this is
+the @code{cvs diff} command. It will return a
+successful status if it found no differences, or a
+failure status if there were differences or if there
+was an error. Because this behavior provides no good
+way to detect errors, in the future it is possible that
+@code{cvs diff} will be changed to behave like the
+other @sc{cvs} commands.
+@c It might seem like checking whether cvs -q diff
+@c produces empty or non-empty output can tell whether
+@c there were differences or not. But it seems like
+@c there are cases with output but no differences
+@c (testsuite basica-8b). It is not clear to me how
+@c useful it is for a script to be able to check
+@c whether there were differences.
+@c FIXCVS? In previous versions of CVS, cvs diff
+@c returned 0 for no differences, 1 for differences, or
+@c 2 for errors. Is this behavior worth trying to
+@c bring back (but what does it mean for VMS?)?
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node ~/.cvsrc
+@appendixsec Default options and the ~/.cvsrc file
+@cindex .cvsrc file
+@cindex Option defaults
+
+There are some @code{command_options} that are used so
+often that you might have set up an alias or some other
+means to make sure you always specify that option. One
+example (the one that drove the implementation of the
+@file{.cvsrc} support, actually) is that many people find the
+default output of the @samp{diff} command to be very
+hard to read, and that either context diffs or unidiffs
+are much easier to understand.
+
+The @file{~/.cvsrc} file is a way that you can add
+default options to @code{cvs_commands} within cvs,
+instead of relying on aliases or other shell scripts.
+
+The format of the @file{~/.cvsrc} file is simple. The
+file is searched for a line that begins with the same
+name as the @code{cvs_command} being executed. If a
+match is found, then the remainder of the line is split
+up (at whitespace characters) into separate options and
+added to the command arguments @emph{before} any
+options from the command line.
+
+If a command has two names (e.g., @code{checkout} and
+@code{co}), the official name, not necessarily the one
+used on the command line, will be used to match against
+the file. So if this is the contents of the user's
+@file{~/.cvsrc} file:
+
+@example
+log -N
+diff -u
+update -P
+checkout -P
+@end example
+
+@noindent
+the command @samp{cvs checkout foo} would have the
+@samp{-P} option added to the arguments, as well as
+@samp{cvs co foo}.
+
+With the example file above, the output from @samp{cvs
+diff foobar} will be in unidiff format. @samp{cvs diff
+-c foobar} will provide context diffs, as usual.
+Getting "old" format diffs would be slightly more
+complicated, because @code{diff} doesn't have an option
+to specify use of the "old" format, so you would need
+@samp{cvs -f diff foobar}.
+
+In place of the command name you can use @code{cvs} to
+specify global options (@pxref{Global options}). For
+example the following line in @file{.cvsrc}
+
+@example
+cvs -z6
+@end example
+
+causes @sc{cvs} to use compression level 6.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Global options
+@appendixsec Global options
+@cindex Options, global
+@cindex Global options
+@cindex Left-hand options
+
+The available @samp{cvs_options} (that are given to the
+left of @samp{cvs_command}) are:
+
+@table @code
+@item --allow-root=@var{rootdir}
+Specify legal @sc{cvsroot} directory. See
+@ref{Password authentication server}.
+
+@cindex Authentication, stream
+@cindex Stream authentication
+@item -a
+Authenticate all communication between the client and
+the server. Only has an effect on the @sc{cvs} client.
+As of this writing, this is only implemented when using
+a GSSAPI connection (@pxref{GSSAPI authenticated}).
+Authentication prevents certain sorts of attacks
+involving hijacking the active @sc{tcp} connection.
+Enabling authentication does not enable encryption.
+
+@cindex RCSBIN, overriding
+@cindex Overriding RCSBIN
+@item -b @var{bindir}
+In @sc{cvs} 1.9.18 and older, this specified that
+@sc{rcs} programs are in the @var{bindir} directory.
+Current versions of @sc{cvs} do not run @sc{rcs}
+programs; for compatibility this option is accepted,
+but it does nothing.
+
+@cindex TMPDIR, overriding
+@cindex Overriding TMPDIR
+@item -T @var{tempdir}
+Use @var{tempdir} as the directory where temporary files are
+located. Overrides the setting of the @code{$TMPDIR} environment
+variable and any precompiled directory. This parameter should be
+specified as an absolute pathname.
+
+@cindex CVSROOT, overriding
+@cindex Overriding CVSROOT
+@item -d @var{cvs_root_directory}
+Use @var{cvs_root_directory} as the root directory
+pathname of the repository. Overrides the setting of
+the @code{$CVSROOT} environment variable. @xref{Repository}.
+
+@cindex EDITOR, overriding
+@cindex Overriding EDITOR
+@item -e @var{editor}
+Use @var{editor} to enter revision log information. Overrides the
+setting of the @code{$CVSEDITOR} and @code{$EDITOR}
+environment variables. For more information, see
+@ref{Committing your changes}.
+
+@item -f
+Do not read the @file{~/.cvsrc} file. This
+option is most often used because of the
+non-orthogonality of the @sc{cvs} option set. For
+example, the @samp{cvs log} option @samp{-N} (turn off
+display of tag names) does not have a corresponding
+option to turn the display on. So if you have
+@samp{-N} in the @file{~/.cvsrc} entry for @samp{log},
+you may need to use @samp{-f} to show the tag names.
+
+@item -H
+@itemx --help
+Display usage information about the specified @samp{cvs_command}
+(but do not actually execute the command). If you don't specify
+a command name, @samp{cvs -H} displays overall help for
+@sc{cvs}, including a list of other help options.
+@c It seems to me it is better to document it this way
+@c rather than trying to update this documentation
+@c every time that we add a --help-foo option. But
+@c perhaps that is confusing...
+
+@item -l
+Do not log the @samp{cvs_command} in the command history (but execute it
+anyway). @xref{history}, for information on command history.
+
+@cindex Read-only mode
+@item -n
+Do not change any files. Attempt to execute the
+@samp{cvs_command}, but only to issue reports; do not remove,
+update, or merge any existing files, or create any new files.
+
+Note that @sc{cvs} will not necessarily produce exactly
+the same output as without @samp{-n}. In some cases
+the output will be the same, but in other cases
+@sc{cvs} will skip some of the processing that would
+have been required to produce the exact same output.
+
+@item -Q
+Cause the command to be really quiet; the command will only
+generate output for serious problems.
+
+@item -q
+Cause the command to be somewhat quiet; informational messages,
+such as reports of recursion through subdirectories, are
+suppressed.
+
+@cindex Read-only files, and -r
+@item -r
+Make new working files read-only. Same effect
+as if the @code{$CVSREAD} environment variable is set
+(@pxref{Environment variables}). The default is to
+make working files writable, unless watches are on
+(@pxref{Watches}).
+
+@item -s @var{variable}=@var{value}
+Set a user variable (@pxref{Variables}).
+
+@cindex Trace
+@item -t
+Trace program execution; display messages showing the steps of
+@sc{cvs} activity. Particularly useful with @samp{-n} to explore the
+potential impact of an unfamiliar command.
+
+@item -v
+@item --version
+Display version and copyright information for @sc{cvs}.
+
+@cindex CVSREAD, overriding
+@cindex Overriding CVSREAD
+@item -w
+Make new working files read-write. Overrides the
+setting of the @code{$CVSREAD} environment variable.
+Files are created read-write by default, unless @code{$CVSREAD} is
+set or @samp{-r} is given.
+@c Note that -w only overrides -r and CVSREAD; it has
+@c no effect on files which are readonly because of
+@c "cvs watch on". My guess is that is the way it
+@c should be (or should "cvs -w get" on a watched file
+@c be the same as a get and a cvs edit?), but I'm not
+@c completely sure whether to document it this way.
+
+@item -x
+@cindex Encryption
+Encrypt all communication between the client and the
+server. Only has an effect on the @sc{cvs} client. As
+of this writing, this is only implemented when using a
+GSSAPI connection (@pxref{GSSAPI authenticated}) or a
+Kerberos connection (@pxref{Kerberos authenticated}).
+Enabling encryption implies that message traffic is
+also authenticated. Encryption support is not
+available by default; it must be enabled using a
+special configure option, @file{--enable-encryption},
+when you build @sc{cvs}.
+
+@item -z @var{gzip-level}
+@cindex Compression
+@cindex Gzip
+Set the compression level. Only has an effect on the
+@sc{cvs} client.
+
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Common options
+@appendixsec Common command options
+@cindex Common options
+@cindex Right-hand options
+
+This section describes the @samp{command_options} that
+are available across several @sc{cvs} commands. These
+options are always given to the right of
+@samp{cvs_command}. Not all
+commands support all of these options; each option is
+only supported for commands where it makes sense.
+However, when a command has one of these options you
+can almost always count on the same behavior of the
+option as in other commands. (Other command options,
+which are listed with the individual commands, may have
+different behavior from one @sc{cvs} command to the other).
+
+@strong{Warning:} the @samp{history} command is an exception; it supports
+many options that conflict even with these standard options.
+
+@table @code
+@cindex Dates
+@cindex Time
+@cindex Specifying dates
+@item -D @var{date_spec}
+Use the most recent revision no later than @var{date_spec}.
+@var{date_spec} is a single argument, a date description
+specifying a date in the past.
+
+The specification is @dfn{sticky} when you use it to make a
+private copy of a source file; that is, when you get a working
+file using @samp{-D}, @sc{cvs} records the date you specified, so that
+further updates in the same directory will use the same date
+(for more information on sticky tags/dates, @pxref{Sticky tags}).
+
+@samp{-D} is available with the @code{checkout},
+@code{diff}, @code{export}, @code{history},
+@code{rdiff}, @code{rtag}, and @code{update} commands.
+(The @code{history} command uses this option in a
+slightly different way; @pxref{history options}).
+
+@c What other formats should we accept? I don't want
+@c to start accepting a whole mess of non-standard
+@c new formats (there are a lot which are in wide use in
+@c one context or another), but practicality does
+@c dictate some level of flexibility.
+@c * POSIX.2 (e.g. touch, ls output, date) and other
+@c POSIX and/or de facto unix standards (e.g. at). The
+@c practice here is too inconsistent to be of any use.
+@c * VMS dates. This is not a formal standard, but
+@c there is a published specification (see SYS$ASCTIM
+@c and SYS$BINTIM in the _VMS System Services Reference
+@c Manual_), it is implemented consistently in VMS
+@c utilities, and VMS users will expect CVS running on
+@c VMS to support this format (and if we're going to do
+@c that, better to make CVS support it on all
+@c platforms. Maybe).
+@c
+@c NOTE: The tar manual has some documentation for
+@c getdate.y (just for our info; we don't want to
+@c attempt to document all the formats accepted by
+@c getdate.y).
+@c
+@c One more note: In output, CVS should consistently
+@c use one date format, and that format should be one that
+@c it accepts in input as well. The former isn't
+@c really true (see survey below), and I'm not
+@c sure that either of those formats is accepted in
+@c input.
+@c
+@c cvs log
+@c current 1996/01/02 13:45:31
+@c Internet 02 Jan 1996 13:45:31 UT
+@c ISO 1996-01-02 13:45:31
+@c cvs ann
+@c current 02-Jan-96
+@c Internet-like 02 Jan 96
+@c ISO 96-01-02
+@c cvs status
+@c current Tue Jun 11 02:54:53 1996
+@c Internet [Tue,] 11 Jun 1996 02:54:53
+@c ISO 1996-06-11 02:54:53
+@c note: date possibly should be omitted entirely for
+@c other reasons.
+@c cvs editors
+@c current Tue Jun 11 02:54:53 1996 GMT
+@c cvs history
+@c current 06/11 02:54 +0000
+@c any others?
+@c There is a good chance the proper solution has to
+@c involve at least some level of letting the user
+@c decide which format (with the default being the
+@c formats CVS has always used; changing these might be
+@c _very_ disruptive since scripts may very well be
+@c parsing them).
+@c
+@c Another random bit of prior art concerning dates is
+@c the strptime function which takes templates such as
+@c "%m/%d/%y", and apparent a variant of getdate()
+@c which also honors them. See
+@c X/Open CAE Specification, System Interfaces and
+@c Headers Issue 4, Version 2 (September 1994), in the
+@c entry for getdate() on page 231
+
+@cindex Timezone, in input
+@cindex Zone, time, in input
+A wide variety of date formats are supported by
+@sc{cvs}. The most standard ones are ISO8601 (from the
+International Standards Organization) and the Internet
+e-mail standard (specified in RFC822 as amended by
+RFC1123).
+
+@c Probably should be doing more to spell out just what
+@c the rules are, rather than just giving examples.
+@c But I want to keep this simple too.
+@c So I don't know....
+@c A few specific issues: (1) Maybe should reassure
+@c people that years after 2000
+@c work (they are in the testsuite, so they do indeed
+@c work). (2) What do two digit years
+@c mean? Where do we accept them? (3) Local times can
+@c be ambiguous or nonexistent if they fall during the
+@c hour when daylight savings time goes into or out of
+@c effect. Pretty obscure, so I'm not at all sure we
+@c should be documenting the behavior in that case.
+ISO8601 dates have many variants but a few examples
+are:
+
+@example
+1972-09-24
+1972-09-24 20:05
+@end example
+@c I doubt we really accept all ISO8601 format dates
+@c (for example, decimal hours like 1972-09-24 20,2)
+@c I'm not sure we should, many of them are pretty
+@c bizarre and it has lots of gratuitous multiple ways
+@c to specify the same thing.
+
+There are a lot more ISO8601 date formats, and CVS
+accepts many of them, but you probably don't want to
+hear the @emph{whole} long story :-).
+
+@c Citing a URL here is kind of problematic given how
+@c much they change and people who have old versions of
+@c this manual, but in case we want to reinstate an
+@c ISO8601 URL, a few are:
+@c http://www.saqqara.demon.co.uk/datefmt.htm
+@c http://www.cl.cam.ac.uk/~mgk25/iso-time.html
+@c Citing some other ISO8601 source is probably even
+@c worse :-).
+
+In addition to the dates allowed in Internet e-mail
+itself, @sc{cvs} also allows some of the fields to be
+omitted. For example:
+@c FIXME: Need to figure out better, and document,
+@c what we want to allow the user to omit.
+@c NOTE: "omit" does not imply "reorder".
+@c FIXME: Need to cite a web page describing how to get
+@c RFC's.
+
+@example
+24 Sep 1972 20:05
+24 Sep
+@end example
+
+The date is interpreted as being in the
+local timezone, unless a specific timezone is
+specified.
+
+These two date formats are preferred. However,
+@sc{cvs} currently accepts a wide variety of other date
+formats. They are intentionally not documented here in
+any detail, and future versions of @sc{cvs} might not
+accept all of them.
+@c We should document and testsuite "now" and
+@c "yesterday". "now" is mentioned in the FAQ and
+@c "yesterday" is mentioned in this document (and the
+@c message from "cvs import" suggesting a merge
+@c command). What else? Probably some/all of the "3
+@c weeks ago" family.
+@c
+@c Maybe at
+@c some point have CVS start give warnings on "unofficial"
+@c formats (many of which might be typos or user
+@c misunderstandings, and/or formats people never/rarely
+@c use to specify dates)?
+
+One such format is
+@code{@var{month}/@var{day}/@var{year}}. This may
+confuse people who are accustomed to having the month
+and day in the other order; @samp{1/4/96} is January 4,
+not April 1.
+
+Remember to quote the argument to the @samp{-D}
+flag so that your shell doesn't interpret spaces as
+argument separators. A command using the @samp{-D}
+flag can look like this:
+
+@example
+$ cvs diff -D "1 hour ago" cvs.texinfo
+@end example
+
+@cindex Forcing a tag match
+@item -f
+When you specify a particular date or tag to @sc{cvs} commands, they
+normally ignore files that do not contain the tag (or did not
+exist prior to the date) that you specified. Use the @samp{-f} option
+if you want files retrieved even when there is no match for the
+tag or date. (The most recent revision of the file
+will be used).
+
+Note that even with @samp{-f}, a tag that you specify
+must exist (that is, in some file, not necessary in
+every file). This is so that @sc{cvs} will continue to
+give an error if you mistype a tag name.
+
+@need 800
+@samp{-f} is available with these commands:
+@code{annotate}, @code{checkout}, @code{export},
+@code{rdiff}, @code{rtag}, and @code{update}.
+
+@strong{Warning:} The @code{commit} and @code{remove}
+commands also have a
+@samp{-f} option, but it has a different behavior for
+those commands. See @ref{commit options}, and
+@ref{Removing files}.
+
+@item -k @var{kflag}
+Alter the default processing of keywords.
+@xref{Keyword substitution}, for the meaning of
+@var{kflag}. Your @var{kflag} specification is
+@dfn{sticky} when you use it to create a private copy
+of a source file; that is, when you use this option
+with the @code{checkout} or @code{update} commands,
+@sc{cvs} associates your selected @var{kflag} with the
+file, and continues to use it with future update
+commands on the same file until you specify otherwise.
+
+The @samp{-k} option is available with the @code{add},
+@code{checkout}, @code{diff}, @code{import} and
+@code{update} commands.
+
+@item -l
+Local; run only in current working directory, rather than
+recursing through subdirectories.
+
+@strong{Warning:} this is not the same
+as the overall @samp{cvs -l} option, which you can specify to the
+left of a cvs command!
+
+Available with the following commands: @code{annotate}, @code{checkout},
+@code{commit}, @code{diff}, @code{edit}, @code{editors}, @code{export},
+@code{log}, @code{rdiff}, @code{remove}, @code{rtag},
+@code{status}, @code{tag}, @code{unedit}, @code{update}, @code{watch},
+and @code{watchers}.
+
+@cindex Editor, avoiding invocation of
+@cindex Avoiding editor invocation
+@item -m @var{message}
+Use @var{message} as log information, instead of
+invoking an editor.
+
+Available with the following commands: @code{add},
+@code{commit} and @code{import}.
+
+@item -n
+Do not run any checkout/commit/tag program. (A program can be
+specified to run on each of these activities, in the modules
+database (@pxref{modules}); this option bypasses it).
+
+@strong{Warning:} this is not the same as the overall @samp{cvs -n}
+option, which you can specify to the left of a cvs command!
+
+Available with the @code{checkout}, @code{commit}, @code{export},
+and @code{rtag} commands.
+
+@item -P
+Prune empty directories. See @ref{Removing directories}.
+
+@item -p
+Pipe the files retrieved from the repository to standard output,
+rather than writing them in the current directory. Available
+with the @code{checkout} and @code{update} commands.
+
+@item -R
+Process directories recursively. This is on by default.
+
+Available with the following commands: @code{annotate}, @code{checkout},
+@code{commit}, @code{diff}, @code{edit}, @code{editors}, @code{export},
+@code{rdiff}, @code{remove}, @code{rtag},
+@code{status}, @code{tag}, @code{unedit}, @code{update}, @code{watch},
+and @code{watchers}.
+
+@item -r @var{tag}
+@cindex HEAD, special tag
+@cindex BASE, special tag
+Use the revision specified by the @var{tag} argument instead of the
+default @dfn{head} revision. As well as arbitrary tags defined
+with the @code{tag} or @code{rtag} command, two special tags are
+always available: @samp{HEAD} refers to the most recent version
+available in the repository, and @samp{BASE} refers to the
+revision you last checked out into the current working directory.
+
+@c FIXME: What does HEAD really mean? I believe that
+@c the current answer is the head of the default branch
+@c for all cvs commands except diff. For diff, it
+@c seems to be (a) the head of the trunk (or the default
+@c branch?) if there is no sticky tag, (b) the head of the
+@c branch for the sticky tag, if there is a sticky tag.
+@c (b) is ugly as it differs
+@c from what HEAD means for other commands, but people
+@c and/or scripts are quite possibly used to it.
+@c See "head" tests in sanity.sh.
+@c Probably the best fix is to introduce two new
+@c special tags, ".thead" for the head of the trunk,
+@c and ".bhead" for the head of the current branch.
+@c Then deprecate HEAD. This has the advantage of
+@c not surprising people with a change to HEAD, and a
+@c side benefit of also phasing out the poorly-named
+@c HEAD (see discussion of reserved tag names in node
+@c "Tags"). Of course, .thead and .bhead should be
+@c carefully implemented (with the implementation the
+@c same for "diff" as for everyone else), test cases
+@c written (similar to the ones in "head"), new tests
+@c cases written for things like default branches, &c.
+
+The tag specification is sticky when you use this
+@c option
+with @code{checkout} or @code{update} to make your own
+copy of a file: @sc{cvs} remembers the tag and continues to use it on
+future update commands, until you specify otherwise (for more information
+on sticky tags/dates, @pxref{Sticky tags}).
+
+The tag can be either a symbolic or numeric tag, as
+described in @ref{Tags}, or the name of a branch, as
+described in @ref{Branching and merging}.
+
+Specifying the @samp{-q} global option along with the
+@samp{-r} command option is often useful, to suppress
+the warning messages when the @sc{rcs} file
+does not contain the specified tag.
+
+@strong{Warning:} this is not the same as the overall @samp{cvs -r} option,
+which you can specify to the left of a @sc{cvs} command!
+
+@samp{-r} is available with the @code{checkout}, @code{commit},
+@code{diff}, @code{history}, @code{export}, @code{rdiff},
+@code{rtag}, and @code{update} commands.
+
+@item -W
+Specify file names that should be filtered. You can
+use this option repeatedly. The spec can be a file
+name pattern of the same type that you can specify in
+the @file{.cvswrappers} file.
+Available with the following commands: @code{import},
+and @code{update}.
+
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node admin
+@appendixsec admin---Administration
+@cindex Admin (subcommand)
+
+@itemize @bullet
+@item
+Requires: repository, working directory.
+@item
+Changes: repository.
+@item
+Synonym: rcs
+@end itemize
+
+This is the @sc{cvs} interface to assorted
+administrative facilities. Some of them have
+questionable usefulness for @sc{cvs} but exist for
+historical purposes. Some of the questionable options
+are likely to disappear in the future. This command
+@emph{does} work recursively, so extreme care should be
+used.
+
+@cindex cvsadmin
+On unix, if there is a group named @code{cvsadmin},
+only members of that group can run @code{cvs admin}
+(except for the @code{cvs admin -k} command, which can
+be run by anybody). This group should exist on the
+server, or any system running the non-client/server
+@sc{cvs}. To disallow @code{cvs admin} for all users,
+create a group with no users in it. On NT, the
+@code{cvsadmin} feature does not exist and all users
+can run @code{cvs admin}.
+
+@menu
+* admin options:: admin options
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node admin options
+@appendixsubsec admin options
+
+Some of these options have questionable usefulness for
+@sc{cvs} but exist for historical purposes. Some even
+make it impossible to use @sc{cvs} until you undo the
+effect!
+
+@table @code
+@item -A@var{oldfile}
+Might not work together with @sc{cvs}. Append the
+access list of @var{oldfile} to the access list of the
+@sc{rcs} file.
+
+@item -a@var{logins}
+Might not work together with @sc{cvs}. Append the
+login names appearing in the comma-separated list
+@var{logins} to the access list of the @sc{rcs} file.
+
+@item -b[@var{rev}]
+Set the default branch to @var{rev}. In @sc{cvs}, you
+normally do not manipulate default branches; sticky
+tags (@pxref{Sticky tags}) are a better way to decide
+which branch you want to work on. There is one reason
+to run @code{cvs admin -b}: to revert to the vendor's
+version when using vendor branches (@pxref{Reverting
+local changes}).
+There can be no space between @samp{-b} and its argument.
+@c Hmm, we don't document the usage where rev is
+@c omitted. Maybe that usage can/should be deprecated
+@c (and replaced with -bHEAD or something?) (so we can toss
+@c the optional argument). Note that -bHEAD does not
+@c work, as of 17 Sep 1997, but probably will once "cvs
+@c admin" is internal to CVS.
+
+@cindex Comment leader
+@item -c@var{string}
+Sets the comment leader to @var{string}. The comment
+leader is not used by current versions of @sc{cvs} or
+@sc{rcs} 5.7. Therefore, you can almost surely not
+worry about it. @xref{Keyword substitution}.
+
+@item -e[@var{logins}]
+Might not work together with @sc{cvs}. Erase the login
+names appearing in the comma-separated list
+@var{logins} from the access list of the RCS file. If
+@var{logins} is omitted, erase the entire access list.
+There can be no space between @samp{-e} and its argument.
+
+@item -I
+Run interactively, even if the standard input is not a
+terminal. This option does not work with the
+client/server @sc{cvs} and is likely to disappear in
+a future release of @sc{cvs}.
+
+@item -i
+Useless with @sc{cvs}. This creates and initializes a
+new @sc{rcs} file, without depositing a revision. With
+@sc{cvs}, add files with the @code{cvs add} command
+(@pxref{Adding files}).
+
+@item -k@var{subst}
+Set the default keyword
+substitution to @var{subst}. @xref{Keyword
+substitution}. Giving an explicit @samp{-k} option to
+@code{cvs update}, @code{cvs export}, or @code{cvs
+checkout} overrides this default.
+
+@item -l[@var{rev}]
+Lock the revision with number @var{rev}. If a branch
+is given, lock the latest revision on that branch. If
+@var{rev} is omitted, lock the latest revision on the
+default branch. There can be no space between
+@samp{-l} and its argument.
+
+This can be used in conjunction with the
+@file{rcslock.pl} script in the @file{contrib}
+directory of the @sc{cvs} source distribution to
+provide reserved checkouts (where only one user can be
+editing a given file at a time). See the comments in
+that file for details (and see the @file{README} file
+in that directory for disclaimers about the unsupported
+nature of contrib). According to comments in that
+file, locking must set to strict (which is the default).
+
+@item -L
+Set locking to strict. Strict locking means that the
+owner of an RCS file is not exempt from locking for
+checkin. For use with @sc{cvs}, strict locking must be
+set; see the discussion under the @samp{-l} option above.
+
+@cindex Changing a log message
+@cindex Replacing a log message
+@cindex Correcting a log message
+@cindex Fixing a log message
+@cindex Log message, correcting
+@item -m@var{rev}:@var{msg}
+Replace the log message of revision @var{rev} with
+@var{msg}.
+
+@c The rcs -M option, to suppress sending mail, has never been
+@c documented as a cvs admin option.
+
+@item -N@var{name}[:[@var{rev}]]
+Act like @samp{-n}, except override any previous
+assignment of @var{name}. For use with magic branches,
+see @ref{Magic branch numbers}.
+
+@item -n@var{name}[:[@var{rev}]]
+Associate the symbolic name @var{name} with the branch
+or revision @var{rev}. It is normally better to use
+@samp{cvs tag} or @samp{cvs rtag} instead. Delete the
+symbolic name if both @samp{:} and @var{rev} are
+omitted; otherwise, print an error message if
+@var{name} is already associated with another number.
+If @var{rev} is symbolic, it is expanded before
+association. A @var{rev} consisting of a branch number
+followed by a @samp{.} stands for the current latest
+revision in the branch. A @samp{:} with an empty
+@var{rev} stands for the current latest revision on the
+default branch, normally the trunk. For example,
+@samp{cvs admin -n@var{name}:} associates @var{name} with the
+current latest revision of all the RCS files;
+this contrasts with @samp{cvs admin -n@var{name}:$} which
+associates @var{name} with the revision numbers
+extracted from keyword strings in the corresponding
+working files.
+
+@cindex Deleting revisions
+@cindex Outdating revisions
+@cindex Saving space
+@item -o@var{range}
+Deletes (@dfn{outdates}) the revisions given by
+@var{range}.
+
+Note that this command can be quite dangerous unless
+you know @emph{exactly} what you are doing (for example
+see the warnings below about how the
+@var{rev1}:@var{rev2} syntax is confusing).
+
+If you are short on disc this option might help you.
+But think twice before using it---there is no way short
+of restoring the latest backup to undo this command!
+If you delete different revisions than you planned,
+either due to carelessness or (heaven forbid) a CVS
+bug, there is no opportunity to correct the error
+before the revisions are deleted. It probably would be
+a good idea to experiment on a copy of the repository
+first.
+
+Specify @var{range} in one of the following ways:
+
+@table @code
+@item @var{rev1}::@var{rev2}
+Collapse all revisions between rev1 and rev2, so that
+CVS only stores the differences associated with going
+from rev1 to rev2, not intermediate steps. For
+example, after @samp{-o 1.3::1.5} one can retrieve
+revision 1.3, revision 1.5, or the differences to get
+from 1.3 to 1.5, but not the revision 1.4, or the
+differences between 1.3 and 1.4. Other examples:
+@samp{-o 1.3::1.4} and @samp{-o 1.3::1.3} have no
+effect, because there are no intermediate revisions to
+remove.
+
+@item ::@var{rev}
+Collapse revisions between the beginning of the branch
+containing @var{rev} and @var{rev} itself. The
+branchpoint and @var{rev} are left intact. For
+example, @samp{-o ::1.3.2.6} deletes revision 1.3.2.1,
+revision 1.3.2.5, and everything in between, but leaves
+1.3 and 1.3.2.6 intact.
+
+@item @var{rev}::
+Collapse revisions between @var{rev} and the end of the
+branch containing @var{rev}. Revision @var{rev} is
+left intact but the head revision is deleted.
+
+@item @var{rev}
+Delete the revision @var{rev}. For example, @samp{-o
+1.3} is equivalent to @samp{-o 1.2::1.4}.
+
+@item @var{rev1}:@var{rev2}
+Delete the revisions from @var{rev1} to @var{rev2},
+inclusive, on the same branch. One will not be able to
+retrieve @var{rev1} or @var{rev2} or any of the
+revisions in between. For example, the command
+@samp{cvs admin -oR_1_01:R_1_02 .} is rarely useful.
+It means to delete revisions up to, and including, the
+tag R_1_02. But beware! If there are files that have not
+changed between R_1_02 and R_1_03 the file will have
+@emph{the same} numerical revision number assigned to
+the tags R_1_02 and R_1_03. So not only will it be
+impossible to retrieve R_1_02; R_1_03 will also have to
+be restored from the tapes! In most cases you want to
+specify @var{rev1}::@var{rev2} instead.
+
+@item :@var{rev}
+Delete revisions from the beginning of the
+branch containing @var{rev} up to and including
+@var{rev}.
+
+@item @var{rev}:
+Delete revisions from revision @var{rev}, including
+@var{rev} itself, to the end of the branch containing
+@var{rev}.
+@end table
+
+None of the revisions to be deleted may have
+branches or locks.
+
+If any of the revisions to be deleted have symbolic
+names, and one specifies one of the @samp{::} syntaxes,
+then @sc{cvs} will give an error and not delete any
+revisions. If you really want to delete both the
+symbolic names and the revisions, first delete the
+symbolic names with @code{cvs tag -d}, then run
+@code{cvs admin -o}. If one specifies the
+non-@samp{::} syntaxes, then @sc{cvs} will delete the
+revisions but leave the symbolic names pointing to
+nonexistent revisions. This behavior is preserved for
+compatibility with previous versions of @sc{cvs}, but
+because it isn't very useful, in the future it may
+change to be like the @samp{::} case.
+
+Due to the way @sc{cvs} handles branches @var{rev}
+cannot be specified symbolically if it is a branch.
+@xref{Magic branch numbers}, for an explanation.
+@c FIXME: is this still true? I suspect not.
+
+Make sure that no-one has checked out a copy of the
+revision you outdate. Strange things will happen if he
+starts to edit it and tries to check it back in. For
+this reason, this option is not a good way to take back
+a bogus commit; commit a new revision undoing the bogus
+change instead (@pxref{Merging two revisions}).
+
+@item -q
+Run quietly; do not print diagnostics.
+
+@item -s@var{state}[:@var{rev}]
+Useful with @sc{cvs}. Set the state attribute of the
+revision @var{rev} to @var{state}. If @var{rev} is a
+branch number, assume the latest revision on that
+branch. If @var{rev} is omitted, assume the latest
+revision on the default branch. Any identifier is
+acceptable for @var{state}. A useful set of states is
+@samp{Exp} (for experimental), @samp{Stab} (for
+stable), and @samp{Rel} (for released). By default,
+the state of a new revision is set to @samp{Exp} when
+it is created. The state is visible in the output from
+@var{cvs log} (@pxref{log}), and in the
+@samp{$@asis{}Log$} and @samp{$@asis{}State$} keywords
+(@pxref{Keyword substitution}). Note that @sc{cvs}
+uses the @code{dead} state for its own purposes; to
+take a file to or from the @code{dead} state use
+commands like @code{cvs remove} and @code{cvs add}, not
+@code{cvs admin -s}.
+
+@item -t[@var{file}]
+Useful with @sc{cvs}. Write descriptive text from the
+contents of the named @var{file} into the RCS file,
+deleting the existing text. The @var{file} pathname
+may not begin with @samp{-}. The descriptive text can be seen in the
+output from @samp{cvs log} (@pxref{log}).
+There can be no space between @samp{-t} and its argument.
+
+If @var{file} is omitted,
+obtain the text from standard input, terminated by
+end-of-file or by a line containing @samp{.} by itself.
+Prompt for the text if interaction is possible; see
+@samp{-I}. Reading from standard input does not work
+for client/server @sc{cvs} and may change in a future
+release of @sc{cvs}.
+@c Changing it to doeditor() is the most obvious thing
+@c (but with a different syntax, as we would like to
+@c phase out optional arguments). I don't know. I'm
+@c tempted to say the whole concept is unnecessary.
+
+@item -t-@var{string}
+Similar to @samp{-t@var{file}}. Write descriptive text
+from the @var{string} into the @sc{rcs} file, deleting
+the existing text.
+There can be no space between @samp{-t} and its argument.
+
+@c The rcs -T option, do not update last-mod time for
+@c minor changes, has never been documented as a
+@c cvs admin option.
+
+@item -U
+Set locking to non-strict. Non-strict locking means
+that the owner of a file need not lock a revision for
+checkin. For use with @sc{cvs}, strict locking must be
+set; see the discussion under the @samp{-l} option
+above.
+
+@item -u[@var{rev}]
+See the option @samp{-l} above, for a discussion of
+using this option with @sc{cvs}. Unlock the revision
+with number @var{rev}. If a branch is given, unlock
+the latest revision on that branch. If @var{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 @code{.} by itself.
+There can be no space between @samp{-u} and its argument.
+@c In the future "send mail" probably will go via the
+@c CVSROOT/notify mechanism. But for now it means
+@c whatever it means to "rcs".
+
+@item -V@var{n}
+In previous versions of @sc{cvs}, this option meant to
+write an @sc{rcs} file which would be acceptable to
+@sc{rcs} version @var{n}, but it is now obsolete and
+specifying it will produce an error.
+@c Note that -V without an argument has never been
+@c documented as a cvs admin option.
+
+@item -x@var{suffixes}
+In previous versions of @sc{cvs}, this was documented
+as a way of specifying the names of the @sc{rcs}
+files. However, @sc{cvs} has always required that the
+@sc{rcs} files used by @sc{cvs} end in @samp{,v}, so
+this option has never done anything useful.
+
+@c The rcs -z option, to specify the timezone, has
+@c never been documented as a cvs admin option.
+@end table
+
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node checkout
+@appendixsec checkout---Check out sources for editing
+@cindex checkout (subcommand)
+@cindex co (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: checkout [options] modules@dots{}
+@item
+Requires: repository.
+@item
+Changes: working directory.
+@item
+Synonyms: co, get
+@end itemize
+
+Create or update a working directory containing copies of the
+source files specified by @var{modules}. You must execute
+@code{checkout} before using most of the other @sc{cvs}
+commands, since most of them operate on your working
+directory.
+
+The @var{modules} are either
+symbolic names for some
+collection of source directories and files, or paths to
+directories or files in the repository. The symbolic
+names are defined in the @samp{modules} file.
+@xref{modules}.
+@c Needs an example, particularly of the non-"modules"
+@c case but probably of both.
+
+@c FIXME: this seems like a very odd place to introduce
+@c people to how CVS works. The bit about unreserved
+@c checkouts is also misleading as it depends on how
+@c things are set up.
+Depending on the modules you specify, @code{checkout} may
+recursively create directories and populate them with
+the appropriate source files. You can then edit these
+source files at any time (regardless of whether other
+software developers are editing their own copies of the
+sources); update them to include new changes applied by
+others to the source repository; or commit your work as
+a permanent change to the source repository.
+
+Note that @code{checkout} is used to create
+directories. The top-level directory created is always
+added to the directory where @code{checkout} is
+invoked, and usually has the same name as the specified
+module. In the case of a module alias, the created
+sub-directory may have a different name, but you can be
+sure that it will be a sub-directory, and that
+@code{checkout} will show the relative path leading to
+each file as it is extracted into your private work
+area (unless you specify the @samp{-Q} global option).
+
+The files created by @code{checkout} are created
+read-write, unless the @samp{-r} option to @sc{cvs}
+(@pxref{Global options}) is specified, the
+@code{CVSREAD} environment variable is specified
+(@pxref{Environment variables}), or a watch is in
+effect for that file (@pxref{Watches}).
+
+Note that running @code{checkout} on a directory that was already
+built by a prior @code{checkout} is also permitted.
+This is similar to specifying the @samp{-d} option
+to the @code{update} command in the sense that new
+directories that have been created in the repository
+will appear in your work area.
+However, @code{checkout} takes a module name whereas
+@code{update} takes a directory name. Also
+to use @code{checkout} this way it must be run from the
+top level directory (where you originally ran
+@code{checkout} from), so before you run
+@code{checkout} to update an existing directory, don't
+forget to change your directory to the top level
+directory.
+
+For the output produced by the @code{checkout} command
+see @ref{update output}.
+
+@menu
+* checkout options:: checkout options
+* checkout examples:: checkout examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node checkout options
+@appendixsubsec checkout options
+
+These standard options are supported by @code{checkout}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -D @var{date}
+Use the most recent revision no later than @var{date}.
+This option is sticky, and implies @samp{-P}. See
+@ref{Sticky tags}, for more information on sticky tags/dates.
+
+@item -f
+Only useful with the @samp{-D @var{date}} or @samp{-r
+@var{tag}} flags. If no matching revision is found,
+retrieve the most recent revision (instead of ignoring
+the file).
+
+@item -k @var{kflag}
+Process keywords according to @var{kflag}. See
+@ref{Keyword substitution}.
+This option is sticky; future updates of
+this file in this working directory will use the same
+@var{kflag}. The @code{status} command can be viewed
+to see the sticky options. See @ref{Invoking CVS}, for
+more information on the @code{status} command.
+
+@item -l
+Local; run only in current working directory.
+
+@item -n
+Do not run any checkout program (as specified
+with the @samp{-o} option in the modules file;
+@pxref{modules}).
+
+@item -P
+Prune empty directories. See @ref{Moving directories}.
+
+@item -p
+Pipe files to the standard output.
+
+@item -R
+Checkout directories recursively. This option is on by default.
+
+@item -r @var{tag}
+Use revision @var{tag}. This option is sticky, and implies @samp{-P}.
+See @ref{Sticky tags}, for more information on sticky tags/dates.
+@end table
+
+In addition to those, you can use these special command
+options with @code{checkout}:
+
+@table @code
+@item -A
+Reset any sticky tags, dates, or @samp{-k} options.
+See @ref{Sticky tags}, for more information on sticky tags/dates.
+
+@item -c
+Copy the module file, sorted, to the standard output,
+instead of creating or modifying any files or
+directories in your working directory.
+
+@item -d @var{dir}
+Create a directory called @var{dir} for the working
+files, instead of using the module name. In general,
+using this flag is equivalent to using @samp{mkdir
+@var{dir}; cd @var{dir}} followed by the checkout
+command without the @samp{-d} flag.
+
+There is an important exception, however. It is very
+convenient when checking out a single item to have the
+output appear in a directory that doesn't contain empty
+intermediate directories. In this case @emph{only},
+CVS tries to ``shorten'' pathnames to avoid those empty
+directories.
+
+For example, given a module @samp{foo} that contains
+the file @samp{bar.c}, the command @samp{cvs co -d dir
+foo} will create directory @samp{dir} and place
+@samp{bar.c} inside. Similarly, given a module
+@samp{bar} which has subdirectory @samp{baz} wherein
+there is a file @samp{quux.c}, the command @samp{cvs -d
+dir co bar/baz} will create directory @samp{dir} and
+place @samp{quux.c} inside.
+
+Using the @samp{-N} flag will defeat this behavior.
+Given the same module definitions above, @samp{cvs co
+-N -d dir foo} will create directories @samp{dir/foo}
+and place @samp{bar.c} inside, while @samp{cvs co -N -d
+dir bar/baz} will create directories @samp{dir/bar/baz}
+and place @samp{quux.c} inside.
+
+@item -j @var{tag}
+With two @samp{-j} options, merge changes from the
+revision specified with the first @samp{-j} option to
+the revision specified with the second @samp{j} option,
+into the working directory.
+
+With one @samp{-j} option, merge changes from the
+ancestor revision to the revision specified with the
+@samp{-j} option, into the working directory. The
+ancestor revision is the common ancestor of the
+revision which the working directory is based on, and
+the revision specified in the @samp{-j} option.
+
+In addition, each -j option can contain an optional
+date specification which, when used with branches, can
+limit the chosen revision to one within a specific
+date. An optional date is specified by adding a colon
+(:) to the tag:
+@samp{-j@var{Symbolic_Tag}:@var{Date_Specifier}}.
+
+@xref{Branching and merging}.
+
+@item -N
+Only useful together with @samp{-d @var{dir}}. With
+this option, @sc{cvs} will not ``shorten'' module paths
+in your working directory when you check out a single
+module. See the @samp{-d} flag for examples and a
+discussion.
+
+@item -s
+Like @samp{-c}, but include the status of all modules,
+and sort it by the status string. @xref{modules}, for
+info about the @samp{-s} option that is used inside the
+modules file to set the module status.
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node checkout examples
+@appendixsubsec checkout examples
+
+Get a copy of the module @samp{tc}:
+
+@example
+$ cvs checkout tc
+@end example
+
+Get a copy of the module @samp{tc} as it looked one day
+ago:
+
+@example
+$ cvs checkout -D yesterday tc
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node commit
+@appendixsec commit---Check files into the repository
+@cindex commit (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: commit [-lnRf] [-m 'log_message' |
+-F file] [-r revision] [files@dots{}]
+@item
+Requires: working directory, repository.
+@item
+Changes: repository.
+@item
+Synonym: ci
+@end itemize
+
+Use @code{commit} when you want to incorporate changes
+from your working source files into the source
+repository.
+
+If you don't specify particular files to commit, all of
+the files in your working current directory are
+examined. @code{commit} is careful to change in the
+repository only those files that you have really
+changed. By default (or if you explicitly specify the
+@samp{-R} option), files in subdirectories are also
+examined and committed if they have changed; you can
+use the @samp{-l} option to limit @code{commit} to the
+current directory only.
+
+@code{commit} verifies that the selected files are up
+to date with the current revisions in the source
+repository; it will notify you, and exit without
+committing, if any of the specified files must be made
+current first with @code{update} (@pxref{update}).
+@code{commit} does not call the @code{update} command
+for you, but rather leaves that for you to do when the
+time is right.
+
+When all is well, an editor is invoked to allow you to
+enter a log message that will be written to one or more
+logging programs (@pxref{modules}, and @pxref{loginfo})
+and placed in the @sc{rcs} file inside the
+repository. This log message can be retrieved with the
+@code{log} command; see @ref{log}. You can specify the
+log message on the command line with the @samp{-m
+@var{message}} option, and thus avoid the editor invocation,
+or use the @samp{-F @var{file}} option to specify
+that the argument file contains the log message.
+
+@menu
+* commit options:: commit options
+* commit examples:: commit examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node commit options
+@appendixsubsec commit options
+
+These standard options are supported by @code{commit}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -l
+Local; run only in current working directory.
+
+@item -n
+Do not run any module program.
+
+@item -R
+Commit directories recursively. This is on by default.
+
+@item -r @var{revision}
+Commit to @var{revision}. @var{revision} must be
+either a branch, or a revision on the main trunk that
+is higher than any existing revision number
+(@pxref{Assigning revisions}). You
+cannot commit to a specific revision on a branch.
+@c FIXME: Need xref for branch case.
+@end table
+
+@code{commit} also supports these options:
+
+@table @code
+@item -F @var{file}
+Read the log message from @var{file}, instead
+of invoking an editor.
+
+@item -f
+Note that this is not the standard behavior of
+the @samp{-f} option as defined in @ref{Common options}.
+
+Force @sc{cvs} to commit a new revision even if you haven't
+made any changes to the file. If the current revision
+of @var{file} is 1.7, then the following two commands
+are equivalent:
+
+@example
+$ cvs commit -f @var{file}
+$ cvs commit -r 1.8 @var{file}
+@end example
+
+@c This is odd, but it's how CVS has worked for some
+@c time.
+The @samp{-f} option disables recursion (i.e., it
+implies @samp{-l}). To force @sc{cvs} to commit a new
+revision for all files in all subdirectories, you must
+use @samp{-f -R}.
+
+@item -m @var{message}
+Use @var{message} as the log message, instead of
+invoking an editor.
+@end table
+
+@need 2000
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node commit examples
+@appendixsubsec commit examples
+
+@c FIXME: this material wants to be somewhere
+@c in "Branching and merging".
+
+@appendixsubsubsec Committing to a branch
+
+You can commit to a branch revision (one that has an
+even number of dots) with the @samp{-r} option. To
+create a branch revision, use the @samp{-b} option
+of the @code{rtag} or @code{tag} commands
+(@pxref{Branching and merging}). Then, either @code{checkout} or
+@code{update} can be used to base your sources on the
+newly created branch. From that point on, all
+@code{commit} changes made within these working sources
+will be automatically added to a branch revision,
+thereby not disturbing main-line development in any
+way. For example, if you had to create a patch to the
+1.2 version of the product, even though the 2.0 version
+is already under development, you might do:
+
+@example
+$ cvs rtag -b -r FCS1_2 FCS1_2_Patch product_module
+$ cvs checkout -r FCS1_2_Patch product_module
+$ cd product_module
+[[ hack away ]]
+$ cvs commit
+@end example
+
+@noindent
+This works automatically since the @samp{-r} option is
+sticky.
+
+@appendixsubsubsec Creating the branch after editing
+
+Say you have been working on some extremely
+experimental software, based on whatever revision you
+happened to checkout last week. If others in your
+group would like to work on this software with you, but
+without disturbing main-line development, you could
+commit your change to a new branch. Others can then
+checkout your experimental stuff and utilize the full
+benefit of @sc{cvs} conflict resolution. The scenario might
+look like:
+
+@c FIXME: Should we be recommending tagging the branchpoint?
+@example
+[[ hacked sources are present ]]
+$ cvs tag -b EXPR1
+$ cvs update -r EXPR1
+$ cvs commit
+@end example
+
+The @code{update} command will make the @samp{-r
+EXPR1} option sticky on all files. Note that your
+changes to the files will never be removed by the
+@code{update} command. The @code{commit} will
+automatically commit to the correct branch, because the
+@samp{-r} is sticky. You could also do like this:
+
+@c FIXME: Should we be recommending tagging the branchpoint?
+@example
+[[ hacked sources are present ]]
+$ cvs tag -b EXPR1
+$ cvs commit -r EXPR1
+@end example
+
+@noindent
+but then, only those files that were changed by you
+will have the @samp{-r EXPR1} sticky flag. If you hack
+away, and commit without specifying the @samp{-r EXPR1}
+flag, some files may accidentally end up on the main
+trunk.
+
+To work with you on the experimental change, others
+would simply do
+
+@example
+$ cvs checkout -r EXPR1 whatever_module
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node diff
+@appendixsec diff---Show differences between revisions
+@cindex diff (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: diff [-lR] [format_options] [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files@dots{}]
+@item
+Requires: working directory, repository.
+@item
+Changes: nothing.
+@end itemize
+
+The @code{diff} command is used to compare different
+revisions of files. The default action is to compare
+your working files with the revisions they were based
+on, and report any differences that are found.
+
+If any file names are given, only those files are
+compared. If any directories are given, all files
+under them will be compared.
+
+The exit status for diff is different than for other
+@sc{cvs} commands; for details @ref{Exit status}.
+
+@menu
+* diff options:: diff options
+* diff examples:: diff examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node diff options
+@appendixsubsec diff options
+
+These standard options are supported by @code{diff}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -D @var{date}
+Use the most recent revision no later than @var{date}.
+See @samp{-r} for how this affects the comparison.
+
+@item -k @var{kflag}
+Process keywords according to @var{kflag}. See
+@ref{Keyword substitution}.
+
+@item -l
+Local; run only in current working directory.
+
+@item -R
+Examine directories recursively. This option is on by
+default.
+
+@item -r @var{tag}
+Compare with revision @var{tag}. Zero, one or two
+@samp{-r} options can be present. With no @samp{-r}
+option, the working file will be compared with the
+revision it was based on. With one @samp{-r}, that
+revision will be compared to your current working file.
+With two @samp{-r} options those two revisions will be
+compared (and your working file will not affect the
+outcome in any way).
+@c We should be a lot more explicit, with examples,
+@c about the difference between "cvs diff" and "cvs
+@c diff -r HEAD". This often confuses new users.
+
+One or both @samp{-r} options can be replaced by a
+@samp{-D @var{date}} option, described above.
+@end table
+
+@c Conceptually, this is a disaster. There are 3
+@c zillion diff formats that we support via the diff
+@c library. It is not obvious to me that we should
+@c document them all. Maybe just the most common ones
+@c like -c and -u, and think about phasing out the
+@c obscure ones.
+@c FIXCVS: also should be a way to specify an external
+@c diff program (which can be different for different
+@c file types) and pass through
+@c arbitrary options, so that the user can do
+@c "--pass=-Z --pass=foo" or something even if CVS
+@c doesn't know about the "-Z foo" option to diff.
+@c This would fit nicely with deprecating/eliminating
+@c the obscure options of the diff library, because it
+@c would let people specify an external GNU diff if
+@c they are into that sort of thing.
+The following options specify the format of the
+output. They have the same meaning as in GNU diff.
+
+@example
+-0 -1 -2 -3 -4 -5 -6 -7 -8 -9
+--binary
+--brief
+--changed-group-format=@var{arg}
+-c
+ -C @var{nlines}
+ --context[=@var{lines}]
+-e --ed
+-t --expand-tabs
+-f --forward-ed
+--horizon-lines=@var{arg}
+--ifdef=@var{arg}
+-w --ignore-all-space
+-B --ignore-blank-lines
+-i --ignore-case
+-I @var{regexp}
+ --ignore-matching-lines=@var{regexp}
+-h
+-b --ignore-space-change
+-T --initial-tab
+-L @var{label}
+ --label=@var{label}
+--left-column
+-d --minimal
+-N --new-file
+--new-line-format=@var{arg}
+--old-line-format=@var{arg}
+--paginate
+-n --rcs
+-s --report-identical-files
+-p
+--show-c-function
+-y --side-by-side
+-F @var{regexp}
+--show-function-line=@var{regexp}
+-H --speed-large-files
+--suppress-common-lines
+-a --text
+--unchanged-group-format=@var{arg}
+-u
+ -U @var{nlines}
+ --unified[=@var{lines}]
+@c FIXCVS: This option is accepted by src/diff.c but
+@c not diff/diff.c; it would appear that any attempt to
+@c use it would get an error.
+-V @var{arg}
+-W @var{columns}
+ --width=@var{columns}
+@end example
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node diff examples
+@appendixsubsec diff examples
+
+The following line produces a Unidiff (@samp{-u} flag)
+between revision 1.14 and 1.19 of
+@file{backend.c}. Due to the @samp{-kk} flag no
+keywords are substituted, so differences that only depend
+on keyword substitution are ignored.
+
+@example
+$ cvs diff -kk -u -r 1.14 -r 1.19 backend.c
+@end example
+
+Suppose the experimental branch EXPR1 was based on a
+set of files tagged RELEASE_1_0. To see what has
+happened on that branch, the following can be used:
+
+@example
+$ cvs diff -r RELEASE_1_0 -r EXPR1
+@end example
+
+A command like this can be used to produce a context
+diff between two releases:
+
+@example
+$ cvs diff -c -r RELEASE_1_0 -r RELEASE_1_1 > diffs
+@end example
+
+If you are maintaining ChangeLogs, a command like the following
+just before you commit your changes may help you write
+the ChangeLog entry. All local modifications that have
+not yet been committed will be printed.
+
+@example
+$ cvs diff -u | less
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node export
+@appendixsec export---Export sources from CVS, similar to checkout
+@cindex export (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: export [-flNnR] [-r rev|-D date] [-k subst] [-d dir] module@dots{}
+@item
+Requires: repository.
+@item
+Changes: current directory.
+@end itemize
+
+This command is a variant of @code{checkout}; use it
+when you want a copy of the source for module without
+the @sc{cvs} administrative directories. For example, you
+might use @code{export} to prepare source for shipment
+off-site. This command requires that you specify a
+date or tag (with @samp{-D} or @samp{-r}), so that you
+can count on reproducing the source you ship to others.
+
+One often would like to use @samp{-kv} with @code{cvs
+export}. This causes any keywords to be
+expanded such that an import done at some other site
+will not lose the keyword revision information. But be
+aware that doesn't handle an export containing binary
+files correctly. Also be aware that after having used
+@samp{-kv}, one can no longer use the @code{ident}
+command (which is part of the @sc{rcs} suite---see
+ident(1)) which looks for keyword strings. If
+you want to be able to use @code{ident} you must not
+use @samp{-kv}.
+
+@menu
+* export options:: export options
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node export options
+@appendixsubsec export options
+
+These standard options are supported by @code{export}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -D @var{date}
+Use the most recent revision no later than @var{date}.
+
+@item -f
+If no matching revision is found, retrieve the most
+recent revision (instead of ignoring the file).
+
+@item -l
+Local; run only in current working directory.
+
+@item -n
+Do not run any checkout program.
+
+@item -R
+Export directories recursively. This is on by default.
+
+@item -r @var{tag}
+Use revision @var{tag}.
+@end table
+
+In addition, these options (that are common to
+@code{checkout} and @code{export}) are also supported:
+
+@table @code
+@item -d @var{dir}
+Create a directory called @var{dir} for the working
+files, instead of using the module name.
+@xref{checkout options}, for complete details on how
+@sc{cvs} handles this flag.
+
+@item -k @var{subst}
+Set keyword expansion mode (@pxref{Substitution modes}).
+
+@item -N
+Only useful together with @samp{-d @var{dir}}.
+@xref{checkout options}, for complete details on how
+@sc{cvs} handles this flag.
+@end table
+
+@ignore
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@c @node export examples
+@appendixsubsec export examples
+
+Contributed examples are gratefully accepted.
+@c -- Examples here!!
+@end ignore
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node history
+@appendixsec history---Show status of files and users
+@cindex history (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: history [-report] [-flags] [-options args] [files@dots{}]
+@item
+Requires: the file @file{$CVSROOT/CVSROOT/history}
+@item
+Changes: nothing.
+@end itemize
+
+@sc{cvs} can keep a history file that tracks each use of the
+@code{checkout}, @code{commit}, @code{rtag},
+@code{update}, and @code{release} commands. You can
+use @code{history} to display this information in
+various formats.
+
+Logging must be enabled by creating the file
+@file{$CVSROOT/CVSROOT/history}.
+
+@strong{Warning:} @code{history} uses @samp{-f}, @samp{-l},
+@samp{-n}, and @samp{-p} in ways that conflict with the
+normal use inside @sc{cvs} (@pxref{Common options}).
+
+@menu
+* history options:: history options
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node history options
+@appendixsubsec history options
+
+Several options (shown above as @samp{-report}) control what
+kind of report is generated:
+
+@table @code
+@item -c
+Report on each time commit was used (i.e., each time
+the repository was modified).
+
+@item -e
+Everything (all record types). Equivalent to
+specifying @samp{-x} with all record types. Of course,
+@samp{-e} will also include record types which are
+added in a future version of @sc{cvs}; if you are
+writing a script which can only handle certain record
+types, you'll want to specify @samp{-x}.
+
+@item -m @var{module}
+Report on a particular module. (You can meaningfully
+use @samp{-m} more than once on the command line.)
+
+@item -o
+Report on checked-out modules.
+
+@item -T
+Report on all tags.
+
+@item -x @var{type}
+Extract a particular set of record types @var{type} from the @sc{cvs}
+history. The types are indicated by single letters,
+which you may specify in combination.
+
+Certain commands have a single record type:
+
+@table @code
+@item F
+release
+@item O
+checkout
+@item E
+export
+@item T
+rtag
+@end table
+
+@noindent
+One of four record types may result from an update:
+
+@table @code
+@item C
+A merge was necessary but collisions were
+detected (requiring manual merging).
+@item G
+A merge was necessary and it succeeded.
+@item U
+A working file was copied from the repository.
+@item W
+The working copy of a file was deleted during
+update (because it was gone from the repository).
+@end table
+
+@noindent
+One of three record types results from commit:
+
+@table @code
+@item A
+A file was added for the first time.
+@item M
+A file was modified.
+@item R
+A file was removed.
+@end table
+@end table
+
+The options shown as @samp{-flags} constrain or expand
+the report without requiring option arguments:
+
+@table @code
+@item -a
+Show data for all users (the default is to show data
+only for the user executing @code{history}).
+
+@item -l
+Show last modification only.
+
+@item -w
+Show only the records for modifications done from the
+same working directory where @code{history} is
+executing.
+@end table
+
+The options shown as @samp{-options @var{args}} constrain the report
+based on an argument:
+
+@table @code
+@item -b @var{str}
+Show data back to a record containing the string
+@var{str} in either the module name, the file name, or
+the repository path.
+
+@item -D @var{date}
+Show data since @var{date}. This is slightly different
+from the normal use of @samp{-D @var{date}}, which
+selects the newest revision older than @var{date}.
+
+@item -f @var{file}
+Show data for a particular file
+(you can specify several @samp{-f} options on the same command line).
+This is equivalent to specifying the file on the command line.
+
+@item -n @var{module}
+Show data for a particular module
+(you can specify several @samp{-n} options on the same command line).
+
+@item -p @var{repository}
+Show data for a particular source repository (you
+can specify several @samp{-p} options on the same command
+line).
+
+@item -r @var{rev}
+Show records referring to revisions since the revision
+or tag named @var{rev} appears in individual @sc{rcs}
+files. Each @sc{rcs} file is searched for the revision or
+tag.
+
+@item -t @var{tag}
+Show records since tag @var{tag} was last added to the
+history file. This differs from the @samp{-r} flag
+above in that it reads only the history file, not the
+@sc{rcs} files, and is much faster.
+
+@item -u @var{name}
+Show records for user @var{name}.
+
+@item -z @var{timezone}
+Show times in the selected records using the specified
+time zone instead of UTC.
+@end table
+
+@ignore
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@c @node history examples
+@appendixsubsec history examples
+
+Contributed examples will gratefully be accepted.
+@c -- Examples here!
+@end ignore
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node import
+@appendixsec import---Import sources into CVS, using vendor branches
+@cindex import (subcommand)
+
+@c FIXME: This node is way too long for one which has subnodes.
+
+@itemize @bullet
+@item
+Synopsis: import [-options] repository vendortag releasetag@dots{}
+@item
+Requires: Repository, source distribution directory.
+@item
+Changes: repository.
+@end itemize
+
+Use @code{import} to incorporate an entire source
+distribution from an outside source (e.g., a source
+vendor) into your source repository directory. You can
+use this command both for initial creation of a
+repository, and for wholesale updates to the module
+from the outside source. @xref{Tracking sources}, for
+a discussion on this subject.
+
+The @var{repository} argument gives a directory name
+(or a path to a directory) under the @sc{cvs} root directory
+for repositories; if the directory did not exist,
+import creates it.
+
+When you use import for updates to source that has been
+modified in your source repository (since a prior
+import), it will notify you of any files that conflict
+in the two branches of development; use @samp{checkout
+-j} to reconcile the differences, as import instructs
+you to do.
+
+If @sc{cvs} decides a file should be ignored
+(@pxref{cvsignore}), it does not import it and prints
+@samp{I } followed by the filename (@pxref{import output}, for a
+complete description of the output).
+
+If the file @file{$CVSROOT/CVSROOT/cvswrappers} exists,
+any file whose names match the specifications in that
+file will be treated as packages and the appropriate
+filtering will be performed on the file/directory
+before being imported. @xref{Wrappers}.
+
+The outside source is saved in a first-level
+branch, by default 1.1.1. Updates are leaves of this
+branch; for example, files from the first imported
+collection of source will be revision 1.1.1.1, then
+files from the first imported update will be revision
+1.1.1.2, and so on.
+
+At least three arguments are required.
+@var{repository} is needed to identify the collection
+of source. @var{vendortag} is a tag for the entire
+branch (e.g., for 1.1.1). You must also specify at
+least one @var{releasetag} to identify the files at
+the leaves created each time you execute @code{import}.
+
+@c I'm not completely sure this belongs here. But
+@c we need to say it _somewhere_ reasonably obvious; it
+@c is a common misconception among people first learning CVS
+Note that @code{import} does @emph{not} change the
+directory in which you invoke it. In particular, it
+does not set up that directory as a @sc{cvs} working
+directory; if you want to work with the sources import
+them first and then check them out into a different
+directory (@pxref{Getting the source}).
+
+@menu
+* import options:: import options
+* import output:: import output
+* import examples:: import examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node import options
+@appendixsubsec import options
+
+This standard option is supported by @code{import}
+(@pxref{Common options}, for a complete description):
+
+@table @code
+@item -m @var{message}
+Use @var{message} as log information, instead of
+invoking an editor.
+@end table
+
+There are the following additional special options.
+
+@table @code
+@item -b @var{branch}
+See @ref{Multiple vendor branches}.
+
+@item -k @var{subst}
+Indicate the keyword expansion mode desired. This
+setting will apply to all files created during the
+import, but not to any files that previously existed in
+the repository. See @ref{Substitution modes}, for a
+list of valid @samp{-k} settings.
+
+@item -I @var{name}
+Specify file names that should be ignored during
+import. You can use this option repeatedly. To avoid
+ignoring any files at all (even those ignored by
+default), specify `-I !'.
+
+@var{name} can be a file name pattern of the same type
+that you can specify in the @file{.cvsignore} file.
+@xref{cvsignore}.
+@c -- Is this really true?
+
+@item -W @var{spec}
+Specify file names that should be filtered during
+import. You can use this option repeatedly.
+
+@var{spec} can be a file name pattern of the same type
+that you can specify in the @file{.cvswrappers}
+file. @xref{Wrappers}.
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node import output
+@appendixsubsec import output
+
+@code{import} keeps you informed of its progress by printing a line
+for each file, preceded by one character indicating the status of the file:
+
+@table @code
+@item U @var{file}
+The file already exists in the repository and has not been locally
+modified; a new revision has been created (if necessary).
+
+@item N @var{file}
+The file is a new file which has been added to the repository.
+
+@item C @var{file}
+The file already exists in the repository but has been locally modified;
+you will have to merge the changes.
+
+@item I @var{file}
+The file is being ignored (@pxref{cvsignore}).
+
+@cindex Symbolic link, importing
+@cindex Link, symbolic, importing
+@c FIXME: also (somewhere else) probably
+@c should be documenting what happens if you "cvs add"
+@c a symbolic link. Also maybe what happens if
+@c you manually create symbolic links within the
+@c repository (? - not sure why we'd want to suggest
+@c doing that).
+@item L @var{file}
+The file is a symbolic link; @code{cvs import} ignores symbolic links.
+People periodically suggest that this behavior should
+be changed, but if there is a consensus on what it
+should be changed to, it doesn't seem to be apparent.
+(Various options in the @file{modules} file can be used
+to recreate symbolic links on checkout, update, etc.;
+@pxref{modules}.)
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node import examples
+@appendixsubsec import examples
+
+See @ref{Tracking sources}, and @ref{From files}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node log
+@appendixsec log---Print out log information for files
+@cindex log (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: log [options] [files@dots{}]
+@item
+Requires: repository, working directory.
+@item
+Changes: nothing.
+@end itemize
+
+Display log information for files. @code{log} used to
+call the @sc{rcs} utility @code{rlog}. Although this
+is no longer true in the current sources, this history
+determines the format of the output and the options,
+which are not quite in the style of the other @sc{cvs}
+commands.
+
+@cindex Timezone, in output
+@cindex Zone, time, in output
+@c Kind of a funny place to document the timezone used
+@c in output from commands other than @code{log}.
+@c There is also more we need to say about this,
+@c including what happens in a client/server environment.
+The output includes the location of the @sc{rcs} file,
+the @dfn{head} revision (the latest revision on the
+trunk), all symbolic names (tags) and some other
+things. For each revision, the revision number, the
+author, the number of lines added/deleted and the log
+message are printed. All times are displayed in
+Coordinated Universal Time (UTC). (Other parts of
+@sc{cvs} print times in the local timezone).
+@c FIXCVS: need a better way to control the timezone
+@c used in output. Previous/current versions of CVS did/do
+@c sometimes support -z in RCSINIT, and/or an
+@c undocumented (except by reference to 'rlog') -z option
+@c to cvs log, but this has not been a consistent,
+@c documented feature. Perhaps a new global option,
+@c where LT means the client's timezone, which the
+@c client then communicates to the server, is the
+@c right solution.
+
+@strong{Warning:} @code{log} uses @samp{-R} in a way that conflicts
+with the normal use inside @sc{cvs} (@pxref{Common options}).
+
+@menu
+* log options:: log options
+* log examples:: log examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node log options
+@appendixsubsec log options
+
+By default, @code{log} prints all information that is
+available. All other options restrict the output.
+
+@table @code
+@item -b
+Print information about the revisions on the default
+branch, normally the highest branch on the trunk.
+
+@item -d @var{dates}
+Print information about revisions with a checkin
+date/time in the range given by the
+semicolon-separated list of dates. The date formats
+accepted are those accepted by the @samp{-D} option to
+many other @sc{cvs} commands (@pxref{Common options}).
+Dates can be combined into ranges as follows:
+
+@c Should we be thinking about accepting ISO8601
+@c ranges? For example "1972-09-10/1972-09-12".
+@table @code
+@item @var{d1}<@var{d2}
+@itemx @var{d2}>@var{d1}
+Select the revisions that were deposited between
+@var{d1} and @var{d2}.
+
+@item <@var{d}
+@itemx @var{d}>
+Select all revisions dated @var{d} or earlier.
+
+@item @var{d}<
+@itemx >@var{d}
+Select all revisions dated @var{d} or later.
+
+@item @var{d}
+Select the single, latest revision dated @var{d} or
+earlier.
+@end table
+
+The @samp{>} or @samp{<} characters may be followed by
+@samp{=} to indicate an inclusive range rather than an
+exclusive one.
+
+Note that the separator is a semicolon (;).
+
+@item -h
+Print only the name of the @sc{rcs} file, name
+of the file in the working directory, head,
+default branch, access list, locks, symbolic names, and
+suffix.
+
+@item -l
+Local; run only in current working directory. (Default
+is to run recursively).
+
+@item -N
+Do not print the list of tags for this file. This
+option can be very useful when your site uses a lot of
+tags, so rather than "more"'ing over 3 pages of tag
+information, the log information is presented without
+tags at all.
+
+@item -R
+Print only the name of the @sc{rcs} file.
+
+@c Note that using a bare revision (in addition to not
+@c being explicitly documented here) is potentially
+@c confusing; it shows the log message to get from the
+@c previous revision to that revision. "-r1.3 -r1.6"
+@c (equivalent to "-r1.3,1.6") is even worse; it
+@c prints the messages to get from 1.2 to 1.3 and 1.5
+@c to 1.6. By analogy with "cvs diff", users might
+@c expect that it is more like specifying a range.
+@c It is not 100% clear to me how much of this should
+@c be documented (for example, multiple -r options
+@c perhaps could/should be deprecated given the false
+@c analogy with "cvs diff").
+@c In general, this section should be rewritten to talk
+@c about messages to get from revision rev1 to rev2,
+@c rather than messages for revision rev2 (that is, the
+@c messages are associated with a change not a static
+@c revision and failing to make this distinction causes
+@c much confusion).
+@item -r@var{revisions}
+Print information about revisions given in the
+comma-separated list @var{revisions} of revisions and
+ranges. The following table explains the available
+range formats:
+
+@table @code
+@item @var{rev1}:@var{rev2}
+Revisions @var{rev1} to @var{rev2} (which must be on
+the same branch).
+
+@item :@var{rev}
+Revisions from the beginning of the branch up to
+and including @var{rev}.
+
+@item @var{rev}:
+Revisions starting with @var{rev} to the end of the
+branch containing @var{rev}.
+
+@item @var{branch}
+An argument that is a branch means all revisions on
+that branch.
+
+@item @var{branch1}:@var{branch2}
+A range of branches means all revisions
+on the branches in that range.
+
+@item @var{branch}.
+The latest revision in @var{branch}.
+@end table
+
+A bare @samp{-r} with no revisions means the latest
+revision on the default branch, normally the trunk.
+There can be no space between the @samp{-r} option and
+its argument.
+
+@item -s @var{states}
+Print information about revisions whose state
+attributes match one of the states given in the
+comma-separated list @var{states}.
+
+@item -t
+Print the same as @samp{-h}, plus the descriptive text.
+
+@item -w@var{logins}
+Print information about revisions checked in by users
+with login names appearing in the comma-separated list
+@var{logins}. If @var{logins} is omitted, the user's
+login is assumed. There can be no space between the
+@samp{-w} option and its argument.
+@end table
+
+@code{log} prints the intersection of the revisions
+selected with the options @samp{-d}, @samp{-s}, and
+@samp{-w}, intersected with the union of the revisions
+selected by @samp{-b} and @samp{-r}.
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node log examples
+@appendixsubsec log examples
+
+Contributed examples are gratefully accepted.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node rdiff
+@appendixsec rdiff---'patch' format diffs between releases
+@cindex rdiff (subcommand)
+
+@itemize @bullet
+@item
+rdiff [-flags] [-V vn] [-r t|-D d [-r t2|-D d2]] modules@dots{}
+@item
+Requires: repository.
+@item
+Changes: nothing.
+@item
+Synonym: patch
+@end itemize
+
+Builds a Larry Wall format patch(1) file between two
+releases, that can be fed directly into the @code{patch}
+program to bring an old release up-to-date with the new
+release. (This is one of the few @sc{cvs} commands that
+operates directly from the repository, and doesn't
+require a prior checkout.) The diff output is sent to
+the standard output device.
+
+You can specify (using the standard @samp{-r} and
+@samp{-D} options) any combination of one or two
+revisions or dates. If only one revision or date is
+specified, the patch file reflects differences between
+that revision or date and the current head revisions in
+the @sc{rcs} file.
+
+Note that if the software release affected is contained
+in more than one directory, then it may be necessary to
+specify the @samp{-p} option to the @code{patch} command when
+patching the old sources, so that @code{patch} is able to find
+the files that are located in other directories.
+
+@menu
+* rdiff options:: rdiff options
+* rdiff examples:: rdiff examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node rdiff options
+@appendixsubsec rdiff options
+
+These standard options are supported by @code{rdiff}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -D @var{date}
+Use the most recent revision no later than @var{date}.
+
+@item -f
+If no matching revision is found, retrieve the most
+recent revision (instead of ignoring the file).
+
+@item -l
+Local; don't descend subdirectories.
+
+@item -R
+Examine directories recursively. This option is on by default.
+
+@item -r @var{tag}
+Use revision @var{tag}.
+@end table
+
+In addition to the above, these options are available:
+
+@table @code
+@item -c
+Use the context diff format. This is the default format.
+
+@item -s
+Create a summary change report instead of a patch. The
+summary includes information about files that were
+changed or added between the releases. It is sent to
+the standard output device. This is useful for finding
+out, for example, which files have changed between two
+dates or revisions.
+
+@item -t
+A diff of the top two revisions is sent to the standard
+output device. This is most useful for seeing what the
+last change to a file was.
+
+@item -u
+Use the unidiff format for the context diffs.
+Remember that old versions
+of the @code{patch} program can't handle the unidiff
+format, so if you plan to post this patch to the net
+you should probably not use @samp{-u}.
+
+@item -V @var{vn}
+Expand keywords according to the rules current in
+@sc{rcs} version @var{vn} (the expansion format changed with
+@sc{rcs} version 5). Note that this option is no
+longer accepted. CVS will always expand keywords the
+way that @sc{rcs} version 5 does.
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node rdiff examples
+@appendixsubsec rdiff examples
+
+Suppose you receive mail from @t{foo@@example.net} asking for an
+update from release 1.2 to 1.4 of the tc compiler. You
+have no such patches on hand, but with @sc{cvs} that can
+easily be fixed with a command such as this:
+
+@example
+$ cvs rdiff -c -r FOO1_2 -r FOO1_4 tc | \
+$$ Mail -s 'The patches you asked for' foo@@example.net
+@end example
+
+Suppose you have made release 1.3, and forked a branch
+called @samp{R_1_3fix} for bugfixes. @samp{R_1_3_1}
+corresponds to release 1.3.1, which was made some time
+ago. Now, you want to see how much development has been
+done on the branch. This command can be used:
+
+@example
+$ cvs patch -s -r R_1_3_1 -r R_1_3fix module-name
+cvs rdiff: Diffing module-name
+File ChangeLog,v changed from revision 1.52.2.5 to 1.52.2.6
+File foo.c,v changed from revision 1.52.2.3 to 1.52.2.4
+File bar.h,v changed from revision 1.29.2.1 to 1.2
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node release
+@appendixsec release---Indicate that a Module is no longer in use
+@cindex release (subcommand)
+
+@itemize @bullet
+@item
+release [-d] directories@dots{}
+@item
+Requires: Working directory.
+@item
+Changes: Working directory, history log.
+@end itemize
+
+This command is meant to safely cancel the effect of
+@samp{cvs checkout}. Since @sc{cvs} doesn't lock files, it
+isn't strictly necessary to use this command. You can
+always simply delete your working directory, if you
+like; but you risk losing changes you may have
+forgotten, and you leave no trace in the @sc{cvs} history
+file (@pxref{history file}) that you've abandoned your
+checkout.
+
+Use @samp{cvs release} to avoid these problems. This
+command checks that no uncommitted changes are
+present; that you are executing it from immediately
+above a @sc{cvs} working directory; and that the repository
+recorded for your files is the same as the repository
+defined in the module database.
+
+If all these conditions are true, @samp{cvs release}
+leaves a record of its execution (attesting to your
+intentionally abandoning your checkout) in the @sc{cvs}
+history log.
+
+@menu
+* release options:: release options
+* release output:: release output
+* release examples:: release examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node release options
+@appendixsubsec release options
+
+The @code{release} command supports one command option:
+
+@table @code
+@item -d
+Delete your working copy of the file if the release
+succeeds. If this flag is not given your files will
+remain in your working directory.
+
+@strong{Warning:} The @code{release} command deletes
+all directories and files recursively. This
+has the very serious side-effect that any directory
+that you have created inside your checked-out sources,
+and not added to the repository (using the @code{add}
+command; @pxref{Adding files}) will be silently deleted---even
+if it is non-empty!
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node release output
+@appendixsubsec release output
+
+Before @code{release} releases your sources it will
+print a one-line message for any file that is not
+up-to-date.
+
+@strong{Warning:} Any new directories that you have
+created, but not added to the @sc{cvs} directory hierarchy
+with the @code{add} command (@pxref{Adding files}) will be
+silently ignored (and deleted, if @samp{-d} is
+specified), even if they contain files.
+@c FIXCVS: This is a bug. But is it true? I think
+@c maybe they print "? dir" now.
+
+@table @code
+@item U @var{file}
+@itemx P @var{file}
+There exists a newer revision of this file in the
+repository, and you have not modified your local copy
+of the file (@samp{U} and @samp{P} mean the same thing).
+
+@item A @var{file}
+The file has been added to your private copy of the
+sources, but has not yet been committed to the
+repository. If you delete your copy of the sources
+this file will be lost.
+
+@item R @var{file}
+The file has been removed from your private copy of the
+sources, but has not yet been removed from the
+repository, since you have not yet committed the
+removal. @xref{commit}.
+
+@item M @var{file}
+The file is modified in your working directory. There
+might also be a newer revision inside the repository.
+
+@item ? @var{file}
+@var{file} is in your working directory, but does not
+correspond to anything in the source repository, and is
+not in the list of files for @sc{cvs} to ignore (see the
+description of the @samp{-I} option, and
+@pxref{cvsignore}). If you remove your working
+sources, this file will be lost.
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node release examples
+@appendixsubsec release examples
+
+Release the @file{tc} directory, and delete your local working copy
+of the files.
+
+@example
+$ cd .. # @r{You must stand immediately above the}
+ # @r{sources when you issue @samp{cvs release}.}
+$ cvs release -d tc
+You have [0] altered files in this repository.
+Are you sure you want to release (and delete) directory `tc': y
+$
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node update
+@appendixsec update---Bring work tree in sync with repository
+@cindex update (subcommand)
+
+@itemize @bullet
+@item
+update [-AdflPpR] [-d] [-r tag|-D date] files@dots{}
+@item
+Requires: repository, working directory.
+@item
+Changes: working directory.
+@end itemize
+
+After you've run checkout to create your private copy
+of source from the common repository, other developers
+will continue changing the central source. From time
+to time, when it is convenient in your development
+process, you can use the @code{update} command from
+within your working directory to reconcile your work
+with any revisions applied to the source repository
+since your last checkout or update.
+
+@menu
+* update options:: update options
+* update output:: update output
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node update options
+@appendixsubsec update options
+
+These standard options are available with @code{update}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -D date
+Use the most recent revision no later than @var{date}.
+This option is sticky, and implies @samp{-P}.
+See @ref{Sticky tags}, for more information on sticky tags/dates.
+
+@item -f
+Only useful with the @samp{-D @var{date}} or @samp{-r
+@var{tag}} flags. If no matching revision is found,
+retrieve the most recent revision (instead of ignoring
+the file).
+
+@item -k @var{kflag}
+Process keywords according to @var{kflag}. See
+@ref{Keyword substitution}.
+This option is sticky; future updates of
+this file in this working directory will use the same
+@var{kflag}. The @code{status} command can be viewed
+to see the sticky options. See @ref{Invoking CVS}, for
+more information on the @code{status} command.
+
+@item -l
+Local; run only in current working directory. @xref{Recursive behavior}.
+
+@item -P
+Prune empty directories. See @ref{Moving directories}.
+
+@item -p
+Pipe files to the standard output.
+
+@item -R
+Update directories recursively (default). @xref{Recursive
+behavior}.
+
+@item -r rev
+Retrieve revision/tag @var{rev}. This option is sticky,
+and implies @samp{-P}.
+See @ref{Sticky tags}, for more information on sticky tags/dates.
+@end table
+
+@need 800
+These special options are also available with
+@code{update}.
+
+@table @code
+@item -A
+Reset any sticky tags, dates, or @samp{-k} options.
+See @ref{Sticky tags}, for more information on sticky tags/dates.
+
+@item -C
+Overwrite locally modified files with clean copies from
+the repository (the modified file is saved in
+@file{.#@var{file}.@var{revision}}, however).
+
+@item -d
+Create any directories that exist in the repository if
+they're missing from the working directory. Normally,
+@code{update} acts only on directories and files that
+were already enrolled in your working directory.
+
+This is useful for updating directories that were
+created in the repository since the initial checkout;
+but it has an unfortunate side effect. If you
+deliberately avoided certain directories in the
+repository when you created your working directory
+(either through use of a module name or by listing
+explicitly the files and directories you wanted on the
+command line), then updating with @samp{-d} will create
+those directories, which may not be what you want.
+
+@item -I @var{name}
+Ignore files whose names match @var{name} (in your
+working directory) during the update. You can specify
+@samp{-I} more than once on the command line to specify
+several files to ignore. Use @samp{-I !} to avoid
+ignoring any files at all. @xref{cvsignore}, for other
+ways to make @sc{cvs} ignore some files.
+
+@item -W@var{spec}
+Specify file names that should be filtered during
+update. You can use this option repeatedly.
+
+@var{spec} can be a file name pattern of the same type
+that you can specify in the @file{.cvswrappers}
+file. @xref{Wrappers}.
+
+@item -j@var{revision}
+With two @samp{-j} options, merge changes from the
+revision specified with the first @samp{-j} option to
+the revision specified with the second @samp{j} option,
+into the working directory.
+
+With one @samp{-j} option, merge changes from the
+ancestor revision to the revision specified with the
+@samp{-j} option, into the working directory. The
+ancestor revision is the common ancestor of the
+revision which the working directory is based on, and
+the revision specified in the @samp{-j} option.
+
+In addition, each @samp{-j} option can contain an optional
+date specification which, when used with branches, can
+limit the chosen revision to one within a specific
+date. An optional date is specified by adding a colon
+(:) to the tag:
+@samp{-j@var{Symbolic_Tag}:@var{Date_Specifier}}.
+
+@xref{Branching and merging}.
+
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node update output
+@appendixsubsec update output
+
+@code{update} and @code{checkout} keep you informed of
+their progress by printing a line for each file, preceded
+by one character indicating the status of the file:
+
+@table @code
+@item U @var{file}
+The file was brought up to date with respect to the
+repository. This is done for any file that exists in
+the repository but not in your source, and for files
+that you haven't changed but are not the most recent
+versions available in the repository.
+
+@item P @var{file}
+Like @samp{U}, but the @sc{cvs} server sends a patch
+instead of an entire file. These two things accomplish
+the same thing.
+
+@item A @var{file}
+The file has been added to your private copy of the
+sources, and will be added to the source repository
+when you run @code{commit} on the file. This is a
+reminder to you that the file needs to be committed.
+
+@item R @var{file}
+The file has been removed from your private copy of the
+sources, and will be removed from the source repository
+when you run @code{commit} on the file. This is a
+reminder to you that the file needs to be committed.
+
+@item M @var{file}
+The file is modified in your working directory.
+
+@samp{M} can indicate one of two states for a file
+you're working on: either there were no modifications
+to the same file in the repository, so that your file
+remains as you last saw it; or there were modifications
+in the repository as well as in your copy, but they
+were merged successfully, without conflict, in your
+working directory.
+
+@sc{cvs} will print some messages if it merges your work,
+and a backup copy of your working file (as it looked
+before you ran @code{update}) will be made. The exact
+name of that file is printed while @code{update} runs.
+
+@item C @var{file}
+@cindex .# files
+@cindex __ files (VMS)
+A conflict was detected while trying to merge your
+changes to @var{file} with changes from the source
+repository. @var{file} (the copy in your working
+directory) is now the result of attempting to merge
+the two revisions; an unmodified copy of your file
+is also in your working directory, with the name
+@file{.#@var{file}.@var{revision}} where @var{revision}
+is the revision that your modified file started
+from. Resolve the conflict as described in
+@ref{Conflicts example}.
+@c "some systems" as in out-of-the-box OSes? Not as
+@c far as I know. We need to advise sysadmins as well
+@c as users how to set up this kind of purge, if that is
+@c what they want.
+@c We also might want to think about cleaner solutions,
+@c like having CVS remove the .# file once the conflict
+@c has been resolved or something like that.
+(Note that some systems automatically purge
+files that begin with @file{.#} if they have not been
+accessed for a few days. If you intend to keep a copy
+of your original file, it is a very good idea to rename
+it.) Under @sc{vms}, the file name starts with
+@file{__} rather than @file{.#}.
+
+@item ? @var{file}
+@var{file} is in your working directory, but does not
+correspond to anything in the source repository, and is
+not in the list of files for @sc{cvs} to ignore (see the
+description of the @samp{-I} option, and
+@pxref{cvsignore}).
+@end table
+
+@node Invoking CVS
+@appendix Quick reference to CVS commands
+@cindex Command reference
+@cindex Reference, commands
+@cindex Invoking CVS
+
+This appendix describes how to invoke @sc{cvs}, with
+references to where each command or feature is
+described in detail. For other references run the
+@code{cvs --help} command, or see @ref{Index}.
+
+A @sc{cvs} command looks like:
+
+@example
+cvs [ @var{global_options} ] @var{command} [ @var{command_options} ] [ @var{command_args} ]
+@end example
+
+Global options:
+
+@table @code
+@item --allow-root=@var{rootdir}
+Specify legal @sc{cvsroot} directory (server only) (not
+in @sc{cvs} 1.9 and older). See @ref{Password
+authentication server}.
+
+@item -a
+Authenticate all communication (client only) (not in @sc{cvs}
+1.9 and older). See @ref{Global options}.
+
+@item -b
+Specify RCS location (@sc{cvs} 1.9 and older). See
+@ref{Global options}.
+
+@item -d @var{root}
+Specify the @sc{cvsroot}. See @ref{Repository}.
+
+@item -e @var{editor}
+Edit messages with @var{editor}. See @ref{Committing
+your changes}.
+
+@item -f
+Do not read the @file{~/.cvsrc} file. See @ref{Global
+options}.
+
+@item -H
+@itemx --help
+Print a help message. See @ref{Global options}.
+
+@item -l
+Do not log in @file{$CVSROOT/CVSROOT/history} file. See @ref{Global
+options}.
+
+@item -n
+Do not change any files. See @ref{Global options}.
+
+@item -Q
+Be really quiet. See @ref{Global options}.
+
+@item -q
+Be somewhat quiet. See @ref{Global options}.
+
+@item -r
+Make new working files read-only. See @ref{Global options}.
+
+@item -s @var{variable}=@var{value}
+Set a user variable. See @ref{Variables}.
+
+@item -T @var{tempdir}
+Put temporary files in @var{tempdir}. See @ref{Global
+options}.
+
+@item -t
+Trace @sc{cvs} execution. See @ref{Global options}.
+
+@item -v
+@item --version
+Display version and copyright information for @sc{cvs}.
+
+@item -w
+Make new working files read-write. See @ref{Global
+options}.
+
+@item -x
+Encrypt all communication (client only). See
+@ref{Global options}.
+
+@item -z @var{gzip-level}
+@cindex Compression
+@cindex Gzip
+Set the compression level (client only).
+@c FIXME: what are the valid values for gzip-level.
+@c And shouldn't this be documented in at least a
+@c little bit of detail somewhere?
+
+@end table
+
+Keyword expansion modes (@pxref{Substitution modes}):
+
+@example
+-kkv $@asis{}Id: file1,v 1.1 1993/12/09 03:21:13 joe Exp $
+-kkvl $@asis{}Id: file1,v 1.1 1993/12/09 03:21:13 joe Exp harry $
+-kk $@asis{}Id$
+-kv file1,v 1.1 1993/12/09 03:21:13 joe Exp
+-ko @i{no expansion}
+-kb @i{no expansion, file is binary}
+@end example
+
+Keywords (@pxref{Keyword list}):
+
+@example
+$@asis{}Author: joe $
+$@asis{}Date: 1993/12/09 03:21:13 $
+$@asis{}Header: /home/files/file1,v 1.1 1993/12/09 03:21:13 joe Exp harry $
+$@asis{}Id: file1,v 1.1 1993/12/09 03:21:13 joe Exp harry $
+$@asis{}Locker: harry $
+$@asis{}Name: snapshot_1_14 $
+$@asis{}RCSfile: file1,v $
+$@asis{}Revision: 1.1 $
+$@asis{}Source: /home/files/file1,v $
+$@asis{}State: Exp $
+$@asis{}Log: file1,v $
+Revision 1.1 1993/12/09 03:30:17 joe
+Initial revision
+
+@end example
+
+@c The idea behind this table is that we want each item
+@c to be a sentence or two at most. Preferably a
+@c single line.
+@c
+@c In some cases refs to "foo options" are just to get
+@c this thing written quickly, not because the "foo
+@c options" node is really the best place to point.
+Commands, command options, and command arguments:
+
+@table @code
+@item add [@var{options}] [@var{files}@dots{}]
+Add a new file/directory. See @ref{Adding files}.
+
+@table @code
+@item -k @var{kflag}
+Set keyword expansion.
+
+@item -m @var{msg}
+Set file description.
+@end table
+
+@item admin [@var{options}] [@var{files}@dots{}]
+Administration of history files in the repository. See
+@ref{admin}.
+@c This list omits those options which are not
+@c documented as being useful with CVS. That might be
+@c a mistake...
+
+@table @code
+@item -b[@var{rev}]
+Set default branch. See @ref{Reverting local changes}.
+
+@item -c@var{string}
+Set comment leader.
+
+@item -k@var{subst}
+Set keyword substitution. See @ref{Keyword
+substitution}.
+
+@item -l[@var{rev}]
+Lock revision @var{rev}, or latest revision.
+
+@item -m@var{rev}:@var{msg}
+Replace the log message of revision @var{rev} with
+@var{msg}.
+
+@item -o@var{range}
+Delete revisions from the repository. See
+@ref{admin options}.
+
+@item -q
+Run quietly; do not print diagnostics.
+
+@item -s@var{state}[:@var{rev}]
+Set the state.
+
+@c Does not work for client/server CVS
+@item -t
+Set file description from standard input.
+
+@item -t@var{file}
+Set file description from @var{file}.
+
+@item -t-@var{string}
+Set file description to @var{string}.
+
+@item -u[@var{rev}]
+Unlock revision @var{rev}, or latest revision.
+@end table
+
+@item annotate [@var{options}] [@var{files}@dots{}]
+Show last revision where each line was modified. See
+@ref{annotate}.
+
+@table @code
+@item -D @var{date}
+Annotate the most recent revision no later than
+@var{date}. See @ref{Common options}.
+
+@item -f
+Use head revision if tag/date not found. See
+@ref{Common options}.
+
+@item -l
+Local; run only in current working directory. @xref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{tag}
+Annotate revision @var{tag}. See @ref{Common options}.
+@end table
+
+@item checkout [@var{options}] @var{modules}@dots{}
+Get a copy of the sources. See @ref{checkout}.
+
+@table @code
+@item -A
+Reset any sticky tags/date/options. See @ref{Sticky
+tags} and @ref{Keyword substitution}.
+
+@item -c
+Output the module database. See @ref{checkout options}.
+
+@item -D @var{date}
+Check out revisions as of @var{date} (is sticky). See
+@ref{Common options}.
+
+@item -d @var{dir}
+Check out into @var{dir}. See @ref{checkout options}.
+
+@item -f
+Use head revision if tag/date not found. See
+@ref{Common options}.
+
+@c Probably want to use rev1/rev2 style like for diff
+@c -r. Here and in on-line help.
+@item -j @var{rev}
+Merge in changes. See @ref{checkout options}.
+
+@item -k @var{kflag}
+Use @var{kflag} keyword expansion. See
+@ref{Substitution modes}.
+
+@item -l
+Local; run only in current working directory. @xref{Recursive behavior}.
+
+@item -N
+Don't ``shorten'' module paths if -d specified. See
+@ref{checkout options}.
+
+@item -n
+Do not run module program (if any). See @ref{checkout options}.
+
+@item -P
+Prune empty directories. See @ref{Moving directories}.
+
+@item -p
+Check out files to standard output (avoids
+stickiness). See @ref{checkout options}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{tag}
+Checkout revision @var{tag} (is sticky). See @ref{Common options}.
+
+@item -s
+Like -c, but include module status. See @ref{checkout options}.
+@end table
+
+@item commit [@var{options}] [@var{files}@dots{}]
+Check changes into the repository. See @ref{commit}.
+
+@table @code
+@item -F @var{file}
+Read log message from @var{file}. See @ref{commit options}.
+
+@item -f
+@c What is this "disables recursion"? It is from the
+@c on-line help; is it documented in this manual?
+Force the file to be committed; disables recursion.
+See @ref{commit options}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -m @var{msg}
+Use @var{msg} as log message. See @ref{commit options}.
+
+@item -n
+Do not run module program (if any). See @ref{commit options}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{rev}
+Commit to @var{rev}. See @ref{commit options}.
+@c FIXME: should be dragging over text from
+@c commit options, especially if it can be cleaned up
+@c and made concise enough.
+@end table
+
+@item diff [@var{options}] [@var{files}@dots{}]
+Show differences between revisions. See @ref{diff}.
+In addition to the options shown below, accepts a wide
+variety of options to control output style, for example
+@samp{-c} for context diffs.
+
+@table @code
+@item -D @var{date1}
+Diff revision for date against working file. See
+@ref{diff options}.
+
+@item -D @var{date2}
+Diff @var{rev1}/@var{date1} against @var{date2}. See
+@ref{diff options}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -N
+Include diffs for added and removed files. See
+@ref{diff options}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{rev1}
+Diff revision for @var{rev1} against working file. See
+@ref{diff options}.
+
+@item -r @var{rev2}
+Diff @var{rev1}/@var{date1} against @var{rev2}. See @ref{diff options}.
+@end table
+
+@item edit [@var{options}] [@var{files}@dots{}]
+Get ready to edit a watched file. See @ref{Editing files}.
+
+@table @code
+@item -a @var{actions}
+Specify actions for temporary watch, where
+@var{actions} is @code{edit}, @code{unedit},
+@code{commit}, @code{all}, or @code{none}. See
+@ref{Editing files}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+@end table
+
+@item editors [@var{options}] [@var{files}@dots{}]
+See who is editing a watched file. See @ref{Watch information}.
+
+@table @code
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+@end table
+
+@item export [@var{options}] @var{modules}@dots{}
+Export files from CVS. See @ref{export}.
+
+@table @code
+@item -D @var{date}
+Check out revisions as of @var{date}. See
+@ref{Common options}.
+
+@item -d @var{dir}
+Check out into @var{dir}. See @ref{export options}.
+
+@item -f
+Use head revision if tag/date not found. See
+@ref{Common options}.
+
+@item -k @var{kflag}
+Use @var{kflag} keyword expansion. See
+@ref{Substitution modes}.
+
+@item -l
+Local; run only in current working directory. @xref{Recursive behavior}.
+
+@item -N
+Don't ``shorten'' module paths if -d specified. See
+@ref{export options}.
+
+@item -n
+Do not run module program (if any). See @ref{export options}.
+
+@item -P
+Prune empty directories. See @ref{Moving directories}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{tag}
+Checkout revision @var{tag}. See @ref{Common options}.
+@end table
+
+@item history [@var{options}] [@var{files}@dots{}]
+Show repository access history. See @ref{history}.
+
+@table @code
+@item -a
+All users (default is self). See @ref{history options}.
+
+@item -b @var{str}
+Back to record with @var{str} in module/file/repos
+field. See @ref{history options}.
+
+@item -c
+Report on committed (modified) files. See @ref{history options}.
+
+@item -D @var{date}
+Since @var{date}. See @ref{history options}.
+
+@item -e
+Report on all record types. See @ref{history options}.
+
+@item -l
+Last modified (committed or modified report). See @ref{history options}.
+
+@item -m @var{module}
+Report on @var{module} (repeatable). See @ref{history
+options}.
+
+@item -n @var{module}
+In @var{module}. See @ref{history options}.
+
+@item -o
+Report on checked out modules. See @ref{history options}.
+
+@item -r @var{rev}
+Since revision @var{rev}. See @ref{history options}.
+
+@item -T
+@c What the @#$@# is a TAG? Same as a tag? This
+@c wording is also in the online-line help.
+Produce report on all TAGs. See @ref{history options}.
+
+@item -t @var{tag}
+Since tag record placed in history file (by anyone).
+See @ref{history options}.
+
+@item -u @var{user}
+For user @var{user} (repeatable). See @ref{history
+options}.
+
+@item -w
+Working directory must match. See @ref{history options}.
+
+@item -x @var{types}
+Report on @var{types}, one or more of
+@code{TOEFWUCGMAR}. See @ref{history options}.
+
+@item -z @var{zone}
+Output for time zone @var{zone}. See @ref{history
+options}.
+@end table
+
+@item import [@var{options}] @var{repository} @var{vendor-tag} @var{release-tags}@dots{}
+Import files into CVS, using vendor branches. See
+@ref{import}.
+
+@table @code
+@item -b @var{bra}
+Import to vendor branch @var{bra}. See
+@ref{Multiple vendor branches}.
+
+@item -d
+Use the file's modification time as the time of
+import. See @ref{import options}.
+
+@item -k @var{kflag}
+Set default keyword substitution mode. See
+@ref{import options}.
+
+@item -m @var{msg}
+Use @var{msg} for log message. See
+@ref{import options}.
+
+@item -I @var{ign}
+More files to ignore (! to reset). See
+@ref{import options}.
+
+@item -W @var{spec}
+More wrappers. See @ref{import options}.
+@end table
+
+@item init
+Create a CVS repository if it doesn't exist. See
+@ref{Creating a repository}.
+
+@item log [@var{options}] [@var{files}@dots{}]
+Print out history information for files. See @ref{log}.
+
+@table @code
+@item -b
+Only list revisions on the default branch. See @ref{log options}.
+
+@item -d @var{dates}
+Specify dates (@var{d1}<@var{d2} for range, @var{d} for
+latest before). See @ref{log options}.
+
+@item -h
+Only print header. See @ref{log options}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -N
+Do not list tags. See @ref{log options}.
+
+@item -R
+Only print name of RCS file. See @ref{log options}.
+
+@item -r@var{revs}
+Only list revisions @var{revs}. See @ref{log options}.
+
+@item -s @var{states}
+Only list revisions with specified states. See @ref{log options}.
+
+@item -t
+Only print header and descriptive text. See @ref{log
+options}.
+
+@item -w@var{logins}
+Only list revisions checked in by specified logins. See @ref{log options}.
+@end table
+
+@item login
+Prompt for password for authenticating server. See
+@ref{Password authentication client}.
+
+@item logout
+Remove stored password for authenticating server. See
+@ref{Password authentication client}.
+
+@item rdiff [@var{options}] @var{modules}@dots{}
+Show differences between releases. See @ref{rdiff}.
+
+@table @code
+@item -c
+Context diff output format (default). See @ref{rdiff options}.
+
+@item -D @var{date}
+Select revisions based on @var{date}. See @ref{Common options}.
+
+@item -f
+Use head revision if tag/date not found. See
+@ref{Common options}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{rev}
+Select revisions based on @var{rev}. See @ref{Common options}.
+
+@item -s
+Short patch - one liner per file. See @ref{rdiff options}.
+
+@item -t
+Top two diffs - last change made to the file. See
+@ref{diff options}.
+
+@item -u
+Unidiff output format. See @ref{rdiff options}.
+
+@item -V @var{vers}
+Use RCS Version @var{vers} for keyword expansion (obsolete). See
+@ref{rdiff options}.
+@end table
+
+@item release [@var{options}] @var{directory}
+Indicate that a directory is no longer in use. See
+@ref{release}.
+
+@table @code
+@item -d
+Delete the given directory. See @ref{release options}.
+@end table
+
+@item remove [@var{options}] [@var{files}@dots{}]
+Remove an entry from the repository. See @ref{Removing files}.
+
+@table @code
+@item -f
+Delete the file before removing it. See @ref{Removing files}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+@end table
+
+@item rtag [@var{options}] @var{tag} @var{modules}@dots{}
+Add a symbolic tag to a module.
+See @ref{Revisions} and @ref{Branching and merging}.
+
+@table @code
+@item -a
+Clear tag from removed files that would not otherwise
+be tagged. See @ref{Tagging add/remove}.
+
+@item -b
+Create a branch named @var{tag}. See @ref{Branching and merging}.
+
+@item -D @var{date}
+Tag revisions as of @var{date}. See @ref{Tagging by date/tag}.
+
+@item -d
+Delete @var{tag}. See @ref{Modifying tags}.
+
+@item -F
+Move @var{tag} if it already exists. See @ref{Modifying tags}.
+
+@item -f
+Force a head revision match if tag/date not found.
+See @ref{Tagging by date/tag}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -n
+No execution of tag program. See @ref{Common options}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{rev}
+Tag existing tag @var{rev}. See @ref{Tagging by date/tag}.
+@end table
+
+@item status [@var{options}] @var{files}@dots{}
+Display status information in a working directory. See
+@ref{File status}.
+
+@table @code
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -v
+Include tag information for file. See @ref{Tags}.
+@end table
+
+@item tag [@var{options}] @var{tag} [@var{files}@dots{}]
+Add a symbolic tag to checked out version of files.
+See @ref{Revisions} and @ref{Branching and merging}.
+
+@table @code
+@item -b
+Create a branch named @var{tag}. See @ref{Branching and merging}.
+
+@item -c
+Check that working files are unmodified. See
+@ref{Tagging the working directory}.
+
+@item -D @var{date}
+Tag revisions as of @var{date}. See @ref{Tagging by date/tag}.
+
+@item -d
+Delete @var{tag}. See @ref{Modifying tags}.
+
+@item -F
+Move @var{tag} if it already exists. See @ref{Modifying tags}.
+
+@item -f
+Force a head revision match if tag/date not found.
+See @ref{Tagging by date/tag}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{rev}
+Tag existing tag @var{rev}. See @ref{Tagging by date/tag}.
+@end table
+
+@item unedit [@var{options}] [@var{files}@dots{}]
+Undo an edit command. See @ref{Editing files}.
+
+@table @code
+@item -a @var{actions}
+Specify actions for temporary watch, where
+@var{actions} is @code{edit}, @code{unedit},
+@code{commit}, @code{all}, or @code{none}. See
+@ref{Editing files}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+@end table
+
+@item update [@var{options}] [@var{files}@dots{}]
+Bring work tree in sync with repository. See
+@ref{update}.
+
+@table @code
+@item -A
+Reset any sticky tags/date/options. See @ref{Sticky
+tags} and @ref{Keyword substitution}.
+
+@item -C
+Overwrite locally modified files with clean copies from
+the repository (the modified file is saved in
+@file{.#@var{file}.@var{revision}}, however).
+
+@item -D @var{date}
+Check out revisions as of @var{date} (is sticky). See
+@ref{Common options}.
+
+@item -d
+Create directories. See @ref{update options}.
+
+@item -f
+Use head revision if tag/date not found. See
+@ref{Common options}.
+
+@item -I @var{ign}
+More files to ignore (! to reset). See
+@ref{import options}.
+
+@c Probably want to use rev1/rev2 style like for diff
+@c -r. Here and in on-line help.
+@item -j @var{rev}
+Merge in changes. See @ref{update options}.
+
+@item -k @var{kflag}
+Use @var{kflag} keyword expansion. See
+@ref{Substitution modes}.
+
+@item -l
+Local; run only in current working directory. @xref{Recursive behavior}.
+
+@item -P
+Prune empty directories. See @ref{Moving directories}.
+
+@item -p
+Check out files to standard output (avoids
+stickiness). See @ref{update options}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+
+@item -r @var{tag}
+Checkout revision @var{tag} (is sticky). See @ref{Common options}.
+
+@item -W @var{spec}
+More wrappers. See @ref{import options}.
+@end table
+
+@item watch [on|off|add|remove] [@var{options}] [@var{files}@dots{}]
+
+on/off: turn on/off read-only checkouts of files. See
+@ref{Setting a watch}.
+
+add/remove: add or remove notification on actions. See
+@ref{Getting Notified}.
+
+@table @code
+@item -a @var{actions}
+Specify actions for temporary watch, where
+@var{actions} is @code{edit}, @code{unedit},
+@code{commit}, @code{all}, or @code{none}. See
+@ref{Editing files}.
+
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+@end table
+
+@item watchers [@var{options}] [@var{files}@dots{}]
+See who is watching a file. See @ref{Watch information}.
+
+@table @code
+@item -l
+Local; run only in current working directory. See @ref{Recursive behavior}.
+
+@item -R
+Operate recursively (default). @xref{Recursive
+behavior}.
+@end table
+
+@end table
+
+@c ---------------------------------------------------------------------
+@node Administrative files
+@appendix Reference manual for Administrative files
+@cindex Administrative files (reference)
+@cindex Files, reference manual
+@cindex Reference manual (files)
+@cindex CVSROOT (file)
+
+@c FIXME? Somewhere there needs to be a more "how-to"
+@c guide to writing these. I think the triggers
+@c (commitinfo, loginfo, taginfo, &c) are perhaps a
+@c different case than files like modules. One
+@c particular issue that people sometimes are
+@c (unnecessarily?) worried about is performance, and
+@c the impact of writing in perl or sh or ____.
+Inside the repository, in the directory
+@file{$CVSROOT/CVSROOT}, there are a number of
+supportive files for @sc{cvs}. You can use @sc{cvs} in a limited
+fashion without any of them, but if they are set up
+properly they can help make life easier. For a
+discussion of how to edit them, see @ref{Intro
+administrative files}.
+
+The most important of these files is the @file{modules}
+file, which defines the modules inside the repository.
+
+@menu
+* modules:: Defining modules
+* Wrappers:: Specify binary-ness based on file name
+* commit files:: The commit support files
+* commitinfo:: Pre-commit checking
+* verifymsg:: How are log messages evaluated?
+* editinfo:: Specifying how log messages are created
+ (obsolete)
+* loginfo:: Where should log messages be sent?
+* rcsinfo:: Templates for the log messages
+* cvsignore:: Ignoring files via cvsignore
+* checkoutlist:: Adding your own administrative files
+* history file:: History information
+* Variables:: Various variables are expanded
+* config:: Miscellaneous CVS configuration
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node modules
+@appendixsec The modules file
+@cindex Modules (admin file)
+@cindex Defining modules (reference manual)
+
+The @file{modules} file records your definitions of
+names for collections of source code. @sc{cvs} will
+use these definitions if you use @sc{cvs} to update the
+modules file (use normal commands like @code{add},
+@code{commit}, etc).
+
+The @file{modules} file may contain blank lines and
+comments (lines beginning with @samp{#}) as well as
+module definitions. Long lines can be continued on the
+next line by specifying a backslash (@samp{\}) as the
+last character on the line.
+
+There are three basic types of modules: alias modules,
+regular modules, and ampersand modules. The difference
+between them is the way that they map files in the
+repository to files in the working directory. In all
+of the following examples, the top-level repository
+contains a directory called @file{first-dir}, which
+contains two files, @file{file1} and @file{file2}, and a
+directory @file{sdir}. @file{first-dir/sdir} contains
+a file @file{sfile}.
+
+@c FIXME: should test all the examples in this section.
+
+@menu
+* Alias modules:: The simplest kind of module
+* Regular modules::
+* Ampersand modules::
+* Excluding directories:: Excluding directories from a module
+* Module options:: Regular and ampersand modules can take options
+* Module program options:: How the modules ``program options'' programs
+ are run.
+@end menu
+
+@node Alias modules
+@appendixsubsec Alias modules
+@cindex Alias modules
+@cindex -a, in modules file
+
+Alias modules are the simplest kind of module:
+
+@table @code
+@item @var{mname} -a @var{aliases}@dots{}
+This represents the simplest way of defining a module
+@var{mname}. The @samp{-a} flags the definition as a
+simple alias: @sc{cvs} will treat any use of @var{mname} (as
+a command argument) as if the list of names
+@var{aliases} had been specified instead.
+@var{aliases} may contain either other module names or
+paths. When you use paths in aliases, @code{checkout}
+creates all intermediate directories in the working
+directory, just as if the path had been specified
+explicitly in the @sc{cvs} arguments.
+@end table
+
+For example, if the modules file contains:
+
+@example
+amodule -a first-dir
+@end example
+
+@noindent
+then the following two commands are equivalent:
+
+@example
+$ cvs co amodule
+$ cvs co first-dir
+@end example
+
+@noindent
+and they each would provide output such as:
+
+@example
+cvs checkout: Updating first-dir
+U first-dir/file1
+U first-dir/file2
+cvs checkout: Updating first-dir/sdir
+U first-dir/sdir/sfile
+@end example
+
+@node Regular modules
+@appendixsubsec Regular modules
+@cindex Regular modules
+
+@table @code
+@item @var{mname} [ options ] @var{dir} [ @var{files}@dots{} ]
+In the simplest case, this form of module definition
+reduces to @samp{@var{mname} @var{dir}}. This defines
+all the files in directory @var{dir} as module mname.
+@var{dir} is a relative path (from @code{$CVSROOT}) to a
+directory of source in the source repository. In this
+case, on checkout, a single directory called
+@var{mname} is created as a working directory; no
+intermediate directory levels are used by default, even
+if @var{dir} was a path involving several directory
+levels.
+@end table
+
+For example, if a module is defined by:
+
+@example
+regmodule first-dir
+@end example
+
+@noindent
+then regmodule will contain the files from first-dir:
+
+@example
+$ cvs co regmodule
+cvs checkout: Updating regmodule
+U regmodule/file1
+U regmodule/file2
+cvs checkout: Updating regmodule/sdir
+U regmodule/sdir/sfile
+$
+@end example
+
+By explicitly specifying files in the module definition
+after @var{dir}, you can select particular files from
+directory @var{dir}. Here is
+an example:
+
+@example
+regfiles first-dir/sdir sfile
+@end example
+
+@noindent
+With this definition, getting the regfiles module
+will create a single working directory
+@file{regfiles} containing the file listed, which
+comes from a directory deeper
+in the @sc{cvs} source repository:
+
+@example
+$ cvs co regfiles
+U regfiles/sfile
+$
+@end example
+
+@node Ampersand modules
+@appendixsubsec Ampersand modules
+@cindex Ampersand modules
+@cindex &, in modules file
+
+A module definition can refer to other modules by
+including @samp{&@var{module}} in its definition.
+@example
+@var{mname} [ options ] @var{&module}@dots{}
+@end example
+
+Then getting the module creates a subdirectory for each such
+module, in the directory containing the module. For
+example, if modules contains
+
+@example
+ampermod &first-dir
+@end example
+
+then a checkout will create an @code{ampermod} directory
+which contains a directory called @code{first-dir},
+which in turns contains all the directories and files
+which live there. For example, the command
+
+@example
+$ cvs co ampermod
+@end example
+
+@noindent
+will create the following files:
+
+@example
+ampermod/first-dir/file1
+ampermod/first-dir/file2
+ampermod/first-dir/sdir/sfile
+@end example
+
+There is one quirk/bug: the messages that @sc{cvs}
+prints omit the @file{ampermod}, and thus do not
+correctly display the location to which it is checking
+out the files:
+
+@example
+$ cvs co ampermod
+cvs checkout: Updating first-dir
+U first-dir/file1
+U first-dir/file2
+cvs checkout: Updating first-dir/sdir
+U first-dir/sdir/sfile
+$
+@end example
+
+Do not rely on this buggy behavior; it may get fixed in
+a future release of @sc{cvs}.
+
+@c FIXCVS: What happens if regular and & modules are
+@c combined, as in "ampermodule first-dir &second-dir"?
+@c When I tried it, it seemed to just ignore the
+@c "first-dir". I think perhaps it should be an error
+@c (but this needs further investigation).
+@c In addition to discussing what each one does, we
+@c should put in a few words about why you would use one or
+@c the other in various situations.
+
+@node Excluding directories
+@appendixsubsec Excluding directories
+@cindex Excluding directories, in modules file
+@cindex !, in modules file
+
+An alias module may exclude particular directories from
+other modules by using an exclamation mark (@samp{!})
+before the name of each directory to be excluded.
+
+For example, if the modules file contains:
+
+@example
+exmodule -a !first-dir/sdir first-dir
+@end example
+
+then checking out the module @samp{exmodule} will check
+out everything in @samp{first-dir} except any files in
+the subdirectory @samp{first-dir/sdir}.
+@c Note that the "!first-dir/sdir" sometimes must be listed
+@c before "first-dir". That seems like a probable bug, in which
+@c case perhaps it should be fixed (to allow either
+@c order) rather than documented. See modules4 in testsuite.
+
+@node Module options
+@appendixsubsec Module options
+@cindex Options, in modules file
+
+Either regular modules or ampersand modules can contain
+options, which supply additional information concerning
+the module.
+
+@table @code
+@cindex -d, in modules file
+@item -d @var{name}
+Name the working directory something other than the
+module name.
+@c FIXME: Needs a bunch of examples, analogous to the
+@c examples for alias, regular, and ampersand modules
+@c which show where the files go without -d.
+
+@cindex Export program
+@cindex -e, in modules file
+@item -e @var{prog}
+Specify a program @var{prog} to run whenever files in a
+module are exported. @var{prog} runs with a single
+argument, the module name.
+@c FIXME: Is it run on server? client?
+
+@cindex Checkin program
+@cindex -i, in modules file
+@item -i @var{prog}
+Specify a program @var{prog} to run whenever files in a
+module are committed. @var{prog} runs with a single
+argument, the full pathname of the affected directory
+in a source repository. The @file{commitinfo},
+@file{loginfo}, and @file{verifymsg} files provide other
+ways to call a program on commit.
+@c FIXME: Is it run on server? client?
+
+@cindex Checkout program
+@cindex -o, in modules file
+@item -o @var{prog}
+Specify a program @var{prog} to run whenever files in a
+module are checked out. @var{prog} runs with a single
+argument, the module name.
+@c FIXME: Is it run on server? client?
+
+@cindex Status of a module
+@cindex Module status
+@cindex -s, in modules file
+@item -s @var{status}
+Assign a status to the module. When the module file is
+printed with @samp{cvs checkout -s} the modules are
+sorted according to primarily module status, and
+secondarily according to the module name. This option
+has no other meaning. You can use this option for
+several things besides status: for instance, list the
+person that is responsible for this module.
+
+@cindex Tag program
+@cindex -t, in modules file
+@item -t @var{prog}
+Specify a program @var{prog} to run whenever files in a
+module are tagged with @code{rtag}. @var{prog} runs
+with two arguments: the module name and the symbolic
+tag specified to @code{rtag}. It is not run
+when @code{tag} is executed. Generally you will find
+that taginfo is a better solution (@pxref{user-defined logging}).
+@c FIXME: Is it run on server? client?
+@c Problems with -t include:
+@c * It is run after the tag not before
+@c * It doesn't get passed all the information that
+@c taginfo does ("mov", &c).
+@c * It only is run for rtag, not tag.
+
+@cindex Update program
+@cindex -u, in modules file
+@item -u @var{prog}
+Specify a program @var{prog} to run whenever @samp{cvs
+update} is executed from the top-level directory of the
+checked-out module. @var{prog} runs with a single
+argument, the full path to the source repository for
+this module.
+@c FIXME: Is it run on server? client?
+@c One drawback of -u and -i are that CVS/Update.prog
+@c and CVS/Checkin.prog only get updated on initial
+@c checkout, and don't get updated if the modules file
+@c changes. Also, the user can edit them, which means
+@c they are no good for security-type stuff.
+@end table
+
+You should also see @pxref{Module program options} about how the
+``program options'' programs are run.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+@node Module program options
+@appendixsubsec How the modules file ``program options'' programs are run
+@cindex Modules file program options
+@cindex -u, in modules file
+@cindex -t, in modules file
+@cindex -o, in modules file
+@cindex -i, in modules file
+@cindex -e, in modules file
+
+@noindent
+For checkout, rtag, and export, the program is server-based, and as such the
+following applies:-
+
+If using remote access methods (pserver, ext, etc.),
+CVS will execute this program on the server from a temporary
+directory. The path is searched for this program.
+
+If using ``local access'' (on a local or remote NFS filesystem, i.e.
+repository set just to a path),
+the program will be executed from the newly checked-out tree, if
+found there, or alternatively searched for in the path if not.
+
+@noindent
+The commit and update programs are locally-based, and are run as
+follows:-
+
+The program is always run locally. One must
+re-checkout the tree one is using if these options are updated in the
+modules administrative file. The file CVS/Checkin.prog contains the
+value of the option `-i' set in the modules file, and similarly for
+the file CVS/Update.prog and `-u'. The program is always executed from
+the top level of the checked-out copy on the client. Again, the program
+is first searched for in the checked-out copy and then using the path.
+
+Note also that the commit and update programs work ONLY when using local
+repository access -- the files simply aren't created when sources are
+checked out from a pserver or other remote CVS.
+
+The programs are all run after the operation has effectively
+completed.
+
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Wrappers
+@appendixsec The cvswrappers file
+@cindex cvswrappers (admin file)
+@cindex CVSWRAPPERS, environment variable
+@cindex Wrappers
+
+@c FIXME: need some better way of separating this out
+@c by functionality. -t/-f is one feature, -m is
+@c another, and -k is a third. And this discussion
+@c should be better motivated (e.g. start with the
+@c problems, then explain how the feature solves it).
+
+Wrappers refers to a @sc{cvs} feature which lets you
+control certain settings based on the name of the file
+which is being operated on. The settings are @samp{-k}
+for binary files, and @samp{-m} for nonmergeable text
+files.
+
+@ignore
+Wrappers allow you to set a hook which transforms files on
+their way in and out of @sc{cvs}.
+
+The file @file{cvswrappers} defines the script that will be
+run on a file when its name matches a regular
+expression. There are two scripts that can be run on a
+file or directory. One script is executed on the file/directory
+before being checked into the repository (this is denoted
+with the @code{-t} flag) and the other when the file is
+checked out of the repository (this is denoted with the
+@code{-f} flag). The @samp{-t}/@samp{-f} feature does
+not work with client/server @sc{cvs}.
+@c I think maybe -t/-f works client/server if a single
+@c file converts to/from a single file, as opposed to
+@c the file<->directory case. Could use more
+@c investigation...
+@end ignore
+
+The @samp{-m} option
+specifies the merge methodology that should be used when
+a non-binary file is updated. @code{MERGE} means the usual
+@sc{cvs} behavior: try to merge the files. @code{COPY}
+means that @code{cvs update} will refuse to merge
+files, as it also does for files specified as binary
+with @samp{-kb} (but if the file is specified as
+binary, there is no need to specify @samp{-m 'COPY'}).
+CVS will provide the user with the
+two versions of the files, and require the user using
+mechanisms outside @sc{cvs}, to insert any necessary
+changes. @strong{WARNING}: do not use @code{COPY} with
+@sc{cvs} 1.9 or earlier--such versions of @sc{cvs} will
+copy one version of your file over the other, wiping
+out the previous contents.
+@c Ordinarily we don't document the behavior of old
+@c versions. But this one is so dangerous, I think we
+@c must. I almost renamed it to -m 'NOMERGE' so we
+@c could say "never use -m 'COPY'".
+The @samp{-m} wrapper option only affects behavior when
+merging is done on update; it does not affect how files
+are stored. See @ref{Binary files}, for more on
+binary files.
+
+The basic format of the file @file{cvswrappers} is:
+
+@c FIXME: @example is all wrong for this. Use @deffn or
+@c something more sensible.
+@example
+wildcard [option value][option value]...
+
+where option is one of
+@ignore
+-f from cvs filter value: path to filter
+-t to cvs filter value: path to filter
+@end ignore
+-m update methodology value: MERGE or COPY
+-k keyword expansion value: expansion mode
+
+and value is a single-quote delimited value.
+@end example
+
+@ignore
+@example
+*.nib -f 'unwrap %s' -t 'wrap %s %s' -m 'COPY'
+*.c -t 'indent %s %s'
+@end example
+@c When does the filter need to be an absolute pathname
+@c and when will something like the above work? I
+@c suspect it relates to the PATH of the server (which
+@c in turn depends on all kinds of stuff, e.g. inetd
+@c for pserver). I'm not sure whether/where to discuss
+@c this.
+@c FIXME: What do the %s's stand for?
+
+@noindent
+The above example of a @file{cvswrappers} file
+states that all files/directories that end with a @code{.nib}
+should be filtered with the @file{wrap} program before
+checking the file into the repository. The file should
+be filtered though the @file{unwrap} program when the
+file is checked out of the repository. The
+@file{cvswrappers} file also states that a @code{COPY}
+methodology should be used when updating the files in
+the repository (that is, no merging should be performed).
+
+@c What pitfalls arise when using indent this way? Is
+@c it a winning thing to do? Would be nice to at least
+@c hint at those issues; we want our examples to tell
+@c how to solve problems, not just to say that cvs can
+@c do certain things.
+The last example line says that all files that end with
+@code{.c} should be filtered with @file{indent}
+before being checked into the repository. Unlike the previous
+example, no filtering of the @code{.c} file is done when
+it is checked out of the repository.
+@noindent
+The @code{-t} filter is called with two arguments,
+the first is the name of the file/directory to filter
+and the second is the pathname to where the resulting
+filtered file should be placed.
+
+@noindent
+The @code{-f} filter is called with one argument,
+which is the name of the file to filter from. The end
+result of this filter will be a file in the users directory
+that they can work on as they normally would.
+
+Note that the @samp{-t}/@samp{-f} features do not
+conveniently handle one portion of CVS's operation:
+determining when files are modified. CVS will still
+want a file (or directory) to exist, and it will use
+its modification time to determine whether a file is
+modified. If CVS erroneously thinks a file is
+unmodified (for example, a directory is unchanged but
+one of the files within it is changed), you can force
+it to check in the file anyway by specifying the
+@samp{-f} option to @code{cvs commit} (@pxref{commit
+options}).
+@c This is, of course, a serious design flaw in -t/-f.
+@c Probably the whole functionality needs to be
+@c redesigned (starting from requirements) to fix this.
+@end ignore
+
+@c FIXME: We don't document -W or point to where it is
+@c documented. Or .cvswrappers.
+For example, the following command imports a
+directory, treating files whose name ends in
+@samp{.exe} as binary:
+
+@example
+cvs import -I ! -W "*.exe -k 'b'" first-dir vendortag reltag
+@end example
+
+@c Another good example, would be storing files
+@c (e.g. binary files) compressed in the repository.
+@c ::::::::::::::::::
+@c cvswrappers
+@c ::::::::::::::::::
+@c *.t12 -m 'COPY'
+@c *.t[0-9][0-9] -f 'gunzipcp %s' -t 'gzipcp %s %s' -m 'COPY'
+@c
+@c ::::::::::::::::::
+@c gunzipcp
+@c ::::::::::::::::::
+@c :
+@c [ -f $1 ] || exit 1
+@c zcat $1 > /tmp/.#$1.$$
+@c mv /tmp/.#$1.$$ $1
+@c
+@c ::::::::::::::::::
+@c gzipcp
+@c ::::::::::::::::::
+@c :
+@c DIRNAME=`echo $1 | sed -e "s|/.*/||g"`
+@c if [ ! -d $DIRNAME ] ; then
+@c DIRNAME=`echo $1 | sed -e "s|.*/||g"`
+@c fi
+@c gzip -c $DIRNAME > $2
+@c One catch--"cvs diff" will not invoke the wrappers
+@c (probably a CVS bug, although I haven't thought it out).
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node commit files
+@appendixsec The commit support files
+@cindex Commit files
+
+The @samp{-i} flag in the @file{modules} file can be
+used to run a certain program whenever files are
+committed (@pxref{modules}). The files described in
+this section provide other, more flexible, ways to run
+programs whenever something is committed.
+
+There are three kind of programs that can be run on
+commit. They are specified in files in the repository,
+as described below. The following table summarizes the
+file names and the purpose of the corresponding
+programs.
+
+@table @file
+@item commitinfo
+The program is responsible for checking that the commit
+is allowed. If it exits with a non-zero exit status
+the commit will be aborted.
+
+@item verifymsg
+The specified program is used to evaluate the log message,
+and possibly verify that it contains all required
+fields. This is most useful in combination with the
+@file{rcsinfo} file, which can hold a log message
+template (@pxref{rcsinfo}).
+
+@item editinfo
+The specified program is used to edit the log message,
+and possibly verify that it contains all required
+fields. This is most useful in combination with the
+@file{rcsinfo} file, which can hold a log message
+template (@pxref{rcsinfo}). (obsolete)
+
+@item loginfo
+The specified program is called when the commit is
+complete. It receives the log message and some
+additional information and can store the log message in
+a file, or mail it to appropriate persons, or maybe
+post it to a local newsgroup, or@dots{} Your
+imagination is the limit!
+@end table
+
+@menu
+* syntax:: The common syntax
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node syntax
+@appendixsubsec The common syntax
+@cindex Info files (syntax)
+@cindex Syntax of info files
+@cindex Common syntax of info files
+
+@c FIXME: having this so totally separate from the
+@c Variables node is rather bogus.
+
+The administrative files such as @file{commitinfo},
+@file{loginfo}, @file{rcsinfo}, @file{verifymsg}, etc.,
+all have a common format. The purpose of the files are
+described later on. The common syntax is described
+here.
+
+@cindex Regular expression syntax
+Each line contains the following:
+@itemize @bullet
+@item
+@c Say anything about DEFAULT and ALL? Right now we
+@c leave that to the description of each file (and in fact
+@c the practice is inconsistent which is really annoying).
+A regular expression. This is a basic regular
+expression in the syntax used by GNU emacs.
+@c FIXME: What we probably should be saying is "POSIX Basic
+@c Regular Expression with the following extensions (`\('
+@c `\|' '+' etc)"
+@c rather than define it with reference to emacs.
+@c The reference to emacs is not strictly speaking
+@c true, as we don't support \=, \s, or \S. Also it isn't
+@c clear we should document and/or promise to continue to
+@c support all the obscure emacs extensions like \<.
+@c Also need to better cite (or include) full
+@c documentation for the syntax.
+@c Also see comment in configure.in about what happens to the
+@c syntax if we pick up a system-supplied regexp matcher.
+
+@item
+A whitespace separator---one or more spaces and/or tabs.
+
+@item
+A file name or command-line template.
+@end itemize
+
+@noindent
+Blank lines are ignored. Lines that start with the
+character @samp{#} are treated as comments. Long lines
+unfortunately can @emph{not} be broken in two parts in
+any way.
+
+The first regular expression that matches the current
+directory name in the repository is used. The rest of the line
+is used as a file name or command-line as appropriate.
+
+@c FIXME: need an example. In particular, show what
+@c the regular expression is matched against (one
+@c ordinarily clueful person got confused about whether it
+@c includes the filename--"directory name" above should be
+@c unambiguous but there is nothing like an example to
+@c confirm people's understanding of this sort of thing).
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node commitinfo
+@appendixsec Commitinfo
+@cindex Commitinfo
+@cindex Checking commits
+@cindex Precommit checking
+
+The @file{commitinfo} file defines programs to execute
+whenever @samp{cvs commit} is about to execute. These
+programs are used for pre-commit checking to verify
+that the modified, added and removed files are really
+ready to be committed. This could be used, for
+instance, to verify that the changed files conform to
+to your site's standards for coding practice.
+
+As mentioned earlier, each line in the
+@file{commitinfo} file consists of a regular expression
+and a command-line template. The template can include
+a program name and any number of arguments you wish to
+supply to it. The full path to the current source
+repository is appended to the template, followed by the
+file names of any files involved in the commit (added,
+removed, and modified files).
+
+@cindex Exit status, of commitinfo
+The first line with a regular expression matching the
+directory within the repository will be used. If the
+command returns a non-zero exit status the commit will
+be aborted.
+@c FIXME: need example(s) of what "directory within the
+@c repository" means.
+
+@cindex DEFAULT in commitinfo
+If the repository name does not match any of the
+regular expressions in this file, the @samp{DEFAULT}
+line is used, if it is specified.
+
+@cindex ALL in commitinfo
+All occurrences of the name @samp{ALL} appearing as a
+regular expression are used in addition to the first
+matching regular expression or the name @samp{DEFAULT}.
+
+Note: when @sc{cvs} is accessing a remote repository,
+@file{commitinfo} will be run on the @emph{remote}
+(i.e., server) side, not the client side (@pxref{Remote
+repositories}).
+
+@c FIXME: should discuss using commitinfo to control
+@c who has checkin access to what (e.g. Joe can check into
+@c directories a, b, and c, and Mary can check into
+@c directories b, c, and d--note this case cannot be
+@c conveniently handled with unix groups). Of course,
+@c adding a new set of features to CVS might be a more
+@c natural way to fix this problem than telling people to
+@c use commitinfo.
+@c FIXME: Should make some reference, especially in
+@c the context of controlling who has access, to the fact
+@c that commitinfo can be circumvented. Perhaps
+@c mention SETXID (but has it been carefully examined
+@c for holes?). This fits in with the discussion of
+@c general CVS security in "Password authentication
+@c security" (the bit which is not pserver-specific).
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node verifymsg
+@appendixsec Verifying log messages
+@cindex verifymsg (admin file)
+@cindex Log message, verifying
+
+Once you have entered a log message, you can evaluate
+that message to check for specific content, such as
+a bug ID. Use the @file{verifymsg} file to
+specify a program that is used to verify the log message.
+This program could be a simple script that checks
+that the entered message contains the required fields.
+
+The @file{verifymsg} file is often most useful together
+with the @file{rcsinfo} file, which can be used to
+specify a log message template.
+
+Each line in the @file{verifymsg} file consists of a
+regular expression and a command-line template. The
+template must include a program name, and can include
+any number of arguments. The full path to the current
+log message template file is appended to the template.
+
+One thing that should be noted is that the @samp{ALL}
+keyword is not supported. If more than one matching
+line is found, the first one is used. This can be
+useful for specifying a default verification script in a
+directory, and then overriding it in a subdirectory.
+
+@cindex DEFAULT in verifymsg
+If the repository name does not match any of the
+regular expressions in this file, the @samp{DEFAULT}
+line is used, if it is specified.
+
+@cindex Exit status, of verifymsg
+If the verification script exits with a non-zero exit status,
+the commit is aborted.
+
+Note that the verification script cannot change the log
+message; it can merely accept it or reject it.
+@c FIXME? Is this an annoying limitation? It would be
+@c relatively easy to fix (although it would *not* be a
+@c good idea for a verifymsg script to interact with the user
+@c at least in the client/server case because of locks
+@c and all that jazz).
+
+The following is a little silly example of a
+@file{verifymsg} file, together with the corresponding
+@file{rcsinfo} file, the log message template and an
+verification script. We begin with the log message template.
+We want to always record a bug-id number on the first
+line of the log message. The rest of log message is
+free text. The following template is found in the file
+@file{/usr/cvssupport/tc.template}.
+
+@example
+BugId:
+@end example
+
+The script @file{/usr/cvssupport/bugid.verify} is used to
+evaluate the log message.
+
+@example
+#!/bin/sh
+#
+# bugid.verify filename
+#
+# Verify that the log message contains a valid bugid
+# on the first line.
+#
+if head -1 < $1 | grep '^BugId:[ ]*[0-9][0-9]*$' > /dev/null; then
+ exit 0
+else
+ echo "No BugId found."
+ exit 1
+fi
+@end example
+
+The @file{verifymsg} file contains this line:
+
+@example
+^tc /usr/cvssupport/bugid.verify
+@end example
+
+The @file{rcsinfo} file contains this line:
+
+@example
+^tc /usr/cvssupport/tc.template
+@end example
+
+
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node editinfo
+@appendixsec Editinfo
+@cindex editinfo (admin file)
+@cindex Editor, specifying per module
+@cindex Per-module editor
+@cindex Log messages, editing
+
+@emph{NOTE:} The @file{editinfo} feature has been
+rendered obsolete. To set a default editor for log
+messages use the @code{EDITOR} environment variable
+(@pxref{Environment variables}) or the @samp{-e} global
+option (@pxref{Global options}). See @ref{verifymsg},
+for information on the use of the @file{verifymsg}
+feature for evaluating log messages.
+
+If you want to make sure that all log messages look the
+same way, you can use the @file{editinfo} file to
+specify a program that is used to edit the log message.
+This program could be a custom-made editor that always
+enforces a certain style of the log message, or maybe a
+simple shell script that calls an editor, and checks
+that the entered message contains the required fields.
+
+If no matching line is found in the @file{editinfo}
+file, the editor specified in the environment variable
+@code{$CVSEDITOR} is used instead. If that variable is
+not set, then the environment variable @code{$EDITOR}
+is used instead. If that variable is not
+set a default will be used. See @ref{Committing your changes}.
+
+The @file{editinfo} file is often most useful together
+with the @file{rcsinfo} file, which can be used to
+specify a log message template.
+
+Each line in the @file{editinfo} file consists of a
+regular expression and a command-line template. The
+template must include a program name, and can include
+any number of arguments. The full path to the current
+log message template file is appended to the template.
+
+One thing that should be noted is that the @samp{ALL}
+keyword is not supported. If more than one matching
+line is found, the first one is used. This can be
+useful for specifying a default edit script in a
+module, and then overriding it in a subdirectory.
+
+@cindex DEFAULT in editinfo
+If the repository name does not match any of the
+regular expressions in this file, the @samp{DEFAULT}
+line is used, if it is specified.
+
+If the edit script exits with a non-zero exit status,
+the commit is aborted.
+
+Note: when @sc{cvs} is accessing a remote repository,
+or when the @samp{-m} or @samp{-F} options to @code{cvs
+commit} are used, @file{editinfo} will not be consulted.
+There is no good workaround for this; use
+@file{verifymsg} instead.
+
+@menu
+* editinfo example:: Editinfo example
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node editinfo example
+@appendixsubsec Editinfo example
+
+The following is a little silly example of a
+@file{editinfo} file, together with the corresponding
+@file{rcsinfo} file, the log message template and an
+editor script. We begin with the log message template.
+We want to always record a bug-id number on the first
+line of the log message. The rest of log message is
+free text. The following template is found in the file
+@file{/usr/cvssupport/tc.template}.
+
+@example
+BugId:
+@end example
+
+The script @file{/usr/cvssupport/bugid.edit} is used to
+edit the log message.
+
+@example
+#!/bin/sh
+#
+# bugid.edit filename
+#
+# Call $EDITOR on FILENAME, and verify that the
+# resulting file contains a valid bugid on the first
+# line.
+if [ "x$EDITOR" = "x" ]; then EDITOR=vi; fi
+if [ "x$CVSEDITOR" = "x" ]; then CVSEDITOR=$EDITOR; fi
+$CVSEDITOR $1
+until head -1|grep '^BugId:[ ]*[0-9][0-9]*$' < $1
+do echo -n "No BugId found. Edit again? ([y]/n)"
+ read ans
+ case $@{ans@} in
+ n*) exit 1;;
+ esac
+ $CVSEDITOR $1
+done
+@end example
+
+The @file{editinfo} file contains this line:
+
+@example
+^tc /usr/cvssupport/bugid.edit
+@end example
+
+The @file{rcsinfo} file contains this line:
+
+@example
+^tc /usr/cvssupport/tc.template
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node loginfo
+@appendixsec Loginfo
+@cindex loginfo (admin file)
+@cindex Storing log messages
+@cindex Mailing log messages
+@cindex Distributing log messages
+@cindex Log messages
+
+@c "cvs commit" is not quite right. What we
+@c mean is "when the repository gets changed" which
+@c also includes "cvs import" and "cvs add" on a directory.
+The @file{loginfo} file is used to control where
+@samp{cvs commit} log information is sent. The first
+entry on a line is a regular expression which is tested
+against the directory that the change is being made to,
+relative to the @code{$CVSROOT}. If a match is found, then
+the remainder of the line is a filter program that
+should expect log information on its standard input.
+
+If the repository name does not match any of the
+regular expressions in this file, the @samp{DEFAULT}
+line is used, if it is specified.
+
+All occurrences of the name @samp{ALL} appearing as a
+regular expression are used in addition to the first
+matching regular expression or @samp{DEFAULT}.
+
+The first matching regular expression is used.
+
+@xref{commit files}, for a description of the syntax of
+the @file{loginfo} file.
+
+The user may specify a format string as
+part of the filter. The string is composed of a
+@samp{%} followed by a space, or followed by a single
+format character, or followed by a set of format
+characters surrounded by @samp{@{} and @samp{@}} as
+separators. The format characters are:
+
+@table @t
+@item s
+file name
+@item V
+old version number (pre-checkin)
+@item v
+new version number (post-checkin)
+@end table
+
+All other characters that appear in a format string
+expand to an empty field (commas separating fields are
+still provided).
+
+For example, some valid format strings are @samp{%},
+@samp{%s}, @samp{%@{s@}}, and @samp{%@{sVv@}}.
+
+The output will be a string of tokens separated by
+spaces. For backwards compatibility, the first
+token will be the repository subdirectory. The rest of the
+tokens will be comma-delimited lists of the information
+requested in the format string. For example, if
+@samp{/u/src/master/yoyodyne/tc} is the repository, @samp{%@{sVv@}}
+is the format string, and three files (@t{ChangeLog},
+@t{Makefile}, @t{foo.c}) were modified, the output
+might be:
+
+@example
+yoyodyne/tc ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
+@end example
+
+As another example, @samp{%@{@}} means that only the
+name of the repository will be generated.
+
+Note: when @sc{cvs} is accessing a remote repository,
+@file{loginfo} will be run on the @emph{remote}
+(i.e., server) side, not the client side (@pxref{Remote
+repositories}).
+
+@menu
+* loginfo example:: Loginfo example
+* Keeping a checked out copy:: Updating a tree on every checkin
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node loginfo example
+@appendixsubsec Loginfo example
+
+The following @file{loginfo} file, together with the
+tiny shell-script below, appends all log messages
+to the file @file{$CVSROOT/CVSROOT/commitlog},
+and any commits to the administrative files (inside
+the @file{CVSROOT} directory) are also logged in
+@file{/usr/adm/cvsroot-log}.
+Commits to the @file{prog1} directory are mailed to @t{ceder}.
+
+@c FIXME: is it a CVS feature or bug that only the
+@c first matching line is used? It is documented
+@c above, but is it useful? For example, if we wanted
+@c to run both "cvs-log" and "Mail" for the CVSROOT
+@c directory, it is kind of awkward if
+@c only the first matching line is used.
+@example
+ALL /usr/local/bin/cvs-log $CVSROOT/CVSROOT/commitlog $USER
+^CVSROOT /usr/local/bin/cvs-log /usr/adm/cvsroot-log
+^prog1 Mail -s %s ceder
+@end example
+
+The shell-script @file{/usr/local/bin/cvs-log} looks
+like this:
+
+@example
+#!/bin/sh
+(echo "------------------------------------------------------";
+ echo -n $2" ";
+ date;
+ echo;
+ cat) >> $1
+@end example
+
+@node Keeping a checked out copy
+@appendixsubsec Keeping a checked out copy
+
+@c What other index entries? It seems like
+@c people might want to use a lot of different
+@c words for this functionality.
+@cindex Keeping a checked out copy
+@cindex Checked out copy, keeping
+@cindex Web pages, maintaining with CVS
+
+It is often useful to maintain a directory tree which
+contains files which correspond to the latest version
+in the repository. For example, other developers might
+want to refer to the latest sources without having to
+check them out, or you might be maintaining a web site
+with @sc{cvs} and want every checkin to cause the files
+used by the web server to be updated.
+@c Can we offer more details on the web example? Or
+@c point the user at how to figure it out? This text
+@c strikes me as sufficient for someone who already has
+@c some idea of what we mean but not enough for the naive
+@c user/sysadmin to understand it and set it up.
+
+The way to do this is by having loginfo invoke
+@code{cvs update}. Doing so in the naive way will
+cause a problem with locks, so the @code{cvs update}
+must be run in the background.
+@c Should we try to describe the problem with locks?
+@c It seems like a digression for someone who just
+@c wants to know how to make it work.
+@c Another choice which might work for a single file
+@c is to use "cvs -n update -p" which doesn't take
+@c out locks (I think) but I don't see many advantages
+@c of that and we might as well document something which
+@c works for multiple files.
+Here is an example for unix (this should all be on one line):
+
+@example
+^cyclic-pages (date; cat; (sleep 2; cd /u/www/local-docs;
+ cvs -q update -d) &) >> $CVSROOT/CVSROOT/updatelog 2>&1
+@end example
+
+This will cause checkins to repository directories
+starting with @code{cyclic-pages} to update the checked
+out tree in @file{/u/www/local-docs}.
+@c More info on some of the details? The "sleep 2" is
+@c so if we are lucky the lock will be gone by the time
+@c we start and we can wait 2 seconds instead of 30.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node rcsinfo
+@appendixsec Rcsinfo
+@cindex rcsinfo (admin file)
+@cindex Form for log message
+@cindex Log message template
+@cindex Template for log message
+
+The @file{rcsinfo} file can be used to specify a form to
+edit when filling out the commit log. The
+@file{rcsinfo} file has a syntax similar to the
+@file{verifymsg}, @file{commitinfo} and @file{loginfo}
+files. @xref{syntax}. Unlike the other files the second
+part is @emph{not} a command-line template. Instead,
+the part after the regular expression should be a full pathname to
+a file containing the log message template.
+
+If the repository name does not match any of the
+regular expressions in this file, the @samp{DEFAULT}
+line is used, if it is specified.
+
+All occurrences of the name @samp{ALL} appearing as a
+regular expression are used in addition to the first
+matching regular expression or @samp{DEFAULT}.
+
+@c FIXME: should be offering advice, somewhere around
+@c here, about where to put the template file. The
+@c verifymsg example uses /usr/cvssupport but doesn't
+@c say anything about what that directory is for or
+@c whether it is hardwired into CVS or who creates
+@c it or anything. In particular we should say
+@c how to version control the template file. A
+@c probably better answer than the /usr/cvssupport
+@c stuff is to use checkoutlist (with xref to the
+@c checkoutlist doc).
+@c Also I am starting to see a connection between
+@c this and the Keeping a checked out copy node.
+@c Probably want to say something about that.
+The log message template will be used as a default log
+message. If you specify a log message with @samp{cvs
+commit -m @var{message}} or @samp{cvs commit -f
+@var{file}} that log message will override the
+template.
+
+@xref{verifymsg}, for an example @file{rcsinfo}
+file.
+
+When @sc{cvs} is accessing a remote repository,
+the contents of @file{rcsinfo} at the time a directory
+is first checked out will specify a template which does
+not then change. If you edit @file{rcsinfo} or its
+templates, you may need to check out a new working
+directory.
+@c Would be nice to fix CVS so this isn't needed. For
+@c example, a mechanism analogous to CVS/Entries, where
+@c the client keeps track of what version of the template
+@c it has.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node cvsignore
+@appendixsec Ignoring files via cvsignore
+@cindex cvsignore (admin file), global
+@cindex Global cvsignore
+@cindex Ignoring files
+@c -- This chapter should maybe be moved to the
+@c tutorial part of the manual?
+
+There are certain file names that frequently occur
+inside your working copy, but that you don't want to
+put under @sc{cvs} control. Examples are all the object
+files that you get while you compile your sources.
+Normally, when you run @samp{cvs update}, it prints a
+line for each file it encounters that it doesn't know
+about (@pxref{update output}).
+
+@sc{cvs} has a list of files (or sh(1) file name patterns)
+that it should ignore while running @code{update},
+@code{import} and @code{release}.
+@c -- Are those the only three commands affected?
+This list is constructed in the following way.
+
+@itemize @bullet
+@item
+The list is initialized to include certain file name
+patterns: names associated with @sc{cvs}
+administration, or with other common source control
+systems; common names for patch files, object files,
+archive files, and editor backup files; and other names
+that are usually artifacts of assorted utilities.
+Currently, the default list of ignored file name
+patterns is:
+
+@cindex Ignored files
+@cindex Automatically ignored files
+@example
+ RCS SCCS CVS CVS.adm
+ RCSLOG cvslog.*
+ tags TAGS
+ .make.state .nse_depinfo
+ *~ #* .#* ,* _$* *$
+ *.old *.bak *.BAK *.orig *.rej .del-*
+ *.a *.olb *.o *.obj *.so *.exe
+ *.Z *.elc *.ln
+ core
+@end example
+
+@item
+The per-repository list in
+@file{$CVSROOT/CVSROOT/cvsignore} is appended to
+the list, if that file exists.
+
+@item
+The per-user list in @file{.cvsignore} in your home
+directory is appended to the list, if it exists.
+
+@item
+Any entries in the environment variable
+@code{$CVSIGNORE} is appended to the list.
+
+@item
+Any @samp{-I} options given to @sc{cvs} is appended.
+
+@item
+As @sc{cvs} traverses through your directories, the contents
+of any @file{.cvsignore} will be appended to the list.
+The patterns found in @file{.cvsignore} are only valid
+for the directory that contains them, not for
+any sub-directories.
+@end itemize
+
+In any of the 5 places listed above, a single
+exclamation mark (@samp{!}) clears the ignore list.
+This can be used if you want to store any file which
+normally is ignored by @sc{cvs}.
+
+Specifying @samp{-I !} to @code{cvs import} will import
+everything, which is generally what you want to do if
+you are importing files from a pristine distribution or
+any other source which is known to not contain any
+extraneous files. However, looking at the rules above
+you will see there is a fly in the ointment; if the
+distribution contains any @file{.cvsignore} files, then
+the patterns from those files will be processed even if
+@samp{-I !} is specified. The only workaround is to
+remove the @file{.cvsignore} files in order to do the
+import. Because this is awkward, in the future
+@samp{-I !} might be modified to override
+@file{.cvsignore} files in each directory.
+
+Note that the syntax of the ignore files consists of a
+series of lines, each of which contains a space
+separated list of filenames. This offers no clean way
+to specify filenames which contain spaces, but you can
+use a workaround like @file{foo?bar} to match a file
+named @file{foo bar} (it also matches @file{fooxbar}
+and the like). Also note that there is currently no
+way to specify comments.
+@c FIXCVS? I don't _like_ this syntax at all, but
+@c changing it raises all the usual compatibility
+@c issues and I'm also not sure what to change it to.
+
+@node checkoutlist
+@appendixsec The checkoutlist file
+@cindex checkoutlist
+
+It may be helpful to use @sc{cvs} to maintain your own
+files in the @file{CVSROOT} directory. For example,
+suppose that you have a script @file{logcommit.pl}
+which you run by including the following line in the
+@file{commitinfo} administrative file:
+
+@example
+ALL $CVSROOT/CVSROOT/logcommit.pl
+@end example
+
+To maintain @file{logcommit.pl} with @sc{cvs} you would
+add the following line to the @file{checkoutlist}
+administrative file:
+
+@example
+logcommit.pl
+@end example
+
+The format of @file{checkoutlist} is one line for each
+file that you want to maintain using @sc{cvs}, giving
+the name of the file.
+
+After setting up @file{checkoutlist} in this fashion,
+the files listed there will function just like
+@sc{cvs}'s built-in administrative files. For example,
+when checking in one of the files you should get a
+message such as:
+
+@example
+cvs commit: Rebuilding administrative file database
+@end example
+
+and the checked out copy in the @file{CVSROOT}
+directory should be updated.
+
+Note that listing @file{passwd} (@pxref{Password
+authentication server}) in @file{checkoutlist} is not
+recommended for security reasons.
+
+For information about keeping a checkout out copy in a
+more general context than the one provided by
+@file{checkoutlist}, see @ref{Keeping a checked out
+copy}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node history file
+@appendixsec The history file
+@cindex History file
+@cindex Log information, saving
+
+The file @file{$CVSROOT/CVSROOT/history} is used
+to log information for the @code{history} command
+(@pxref{history}). This file must be created to turn
+on logging. This is done automatically if the
+@code{cvs init} command is used to set up the
+repository (@pxref{Creating a repository}).
+
+The file format of the @file{history} file is
+documented only in comments in the @sc{cvs} source
+code, but generally programs should use the @code{cvs
+history} command to access it anyway, in case the
+format changes with future releases of @sc{cvs}.
+
+@node Variables
+@appendixsec Expansions in administrative files
+@cindex Internal variables
+@cindex Variables
+
+Sometimes in writing an administrative file, you might
+want the file to be able to know various things based
+on environment @sc{cvs} is running in. There are
+several mechanisms to do that.
+
+To find the home directory of the user running @sc{cvs}
+(from the @code{HOME} environment variable), use
+@samp{~} followed by @samp{/} or the end of the line.
+Likewise for the home directory of @var{user}, use
+@samp{~@var{user}}. These variables are expanded on
+the server machine, and don't get any reasonable
+expansion if pserver (@pxref{Password authenticated})
+is in use; therefore user variables (see below) may be
+a better choice to customize behavior based on the user
+running @sc{cvs}.
+@c Based on these limitations, should we deprecate ~?
+@c What is it good for? Are people using it?
+
+One may want to know about various pieces of
+information internal to @sc{cvs}. A @sc{cvs} internal
+variable has the syntax @code{$@{@var{variable}@}},
+where @var{variable} starts with a letter and consists
+of alphanumeric characters and @samp{_}. If the
+character following @var{variable} is a
+non-alphanumeric character other than @samp{_}, the
+@samp{@{} and @samp{@}} can be omitted. The @sc{cvs}
+internal variables are:
+
+@table @code
+@item CVSROOT
+@cindex CVSROOT, internal variable
+This is the value of the @sc{cvs} root in use.
+@xref{Repository}, for a description of the various
+ways to specify this.
+
+@item RCSBIN
+@cindex RCSBIN, internal variable
+In @sc{cvs} 1.9.18 and older, this specified the
+directory where @sc{cvs} was looking for @sc{rcs}
+programs. Because @sc{cvs} no longer runs @sc{rcs}
+programs, specifying this internal variable is now an
+error.
+
+@item CVSEDITOR
+@cindex CVSEDITOR, internal variable
+@itemx VISUAL
+@cindex VISUAL, internal variable
+@itemx EDITOR
+@cindex EDITOR, internal variable
+These all expand to the same value, which is the editor
+that @sc{cvs} is using. @xref{Global options}, for how
+to specify this.
+
+@item USER
+@cindex USER, internal variable
+Username of the user running @sc{cvs} (on the @sc{cvs}
+server machine).
+@end table
+
+If you want to pass a value to the administrative files
+which the user who is running @sc{cvs} can specify,
+use a user variable.
+@cindex User variables
+To expand a user variable, the
+administrative file contains
+@code{$@{=@var{variable}@}}. To set a user variable,
+specify the global option @samp{-s} to @sc{cvs}, with
+argument @code{@var{variable}=@var{value}}. It may be
+particularly useful to specify this option via
+@file{.cvsrc} (@pxref{~/.cvsrc}).
+
+For example, if you want the administrative file to
+refer to a test directory you might create a user
+variable @code{TESTDIR}. Then if @sc{cvs} is invoked
+as
+
+@example
+cvs -s TESTDIR=/work/local/tests
+@end example
+
+@noindent
+and the
+administrative file contains @code{sh
+$@{=TESTDIR@}/runtests}, then that string is expanded
+to @code{sh /work/local/tests/runtests}.
+
+All other strings containing @samp{$} are reserved;
+there is no way to quote a @samp{$} character so that
+@samp{$} represents itself.
+
+@node config
+@appendixsec The CVSROOT/config configuration file
+
+@cindex config, in CVSROOT
+@cindex CVSROOT/config
+
+The administrative file @file{config} contains various
+miscellaneous settings which affect the behavior of
+@sc{cvs}. The syntax is slightly different from the
+other administrative files. Variables are not
+expanded. Lines which start with @samp{#} are
+considered comments.
+@c FIXME: where do we define comments for the other
+@c administrative files.
+Other lines consist of a keyword, @samp{=}, and a
+value. Note that this syntax is very strict.
+Extraneous spaces or tabs are not permitted.
+@c See comments in parseinfo.c:parse_config for more
+@c discussion of this strictness.
+
+Currently defined keywords are:
+
+@table @code
+@cindex RCSBIN, in CVSROOT/config
+@item RCSBIN=@var{bindir}
+For @sc{cvs} 1.9.12 through 1.9.18, this setting told
+@sc{cvs} to look for @sc{rcs} programs in the
+@var{bindir} directory. Current versions of @sc{cvs}
+do not run @sc{rcs} programs; for compatibility this
+setting is accepted, but it does nothing.
+
+@cindex SystemAuth, in CVSROOT/config
+@item SystemAuth=@var{value}
+If @var{value} is @samp{yes}, then pserver should check
+for users in the system's user database if not found in
+@file{CVSROOT/passwd}. If it is @samp{no}, then all
+pserver users must exist in @file{CVSROOT/passwd}.
+The default is @samp{yes}. For more on pserver, see
+@ref{Password authenticated}.
+
+@cindex PreservePermissions, in CVSROOT/config
+@item PreservePermissions=@var{value}
+Enable support for saving special device files,
+symbolic links, file permissions and ownerships in the
+repository. The default value is @samp{no}.
+@xref{Special Files}, for the full implications of using
+this keyword.
+
+@cindex TopLevelAdmin, in CVSROOT/config
+@item TopLevelAdmin=@var{value}
+Modify the @samp{checkout} command to create a
+@samp{CVS} directory at the top level of the new
+working directory, in addition to @samp{CVS}
+directories created within checked-out directories.
+The default value is @samp{no}.
+
+This option is useful if you find yourself performing
+many commands at the top level of your working
+directory, rather than in one of the checked out
+subdirectories. The @file{CVS} directory created there
+will mean you don't have to specify @code{CVSROOT} for
+each command. It also provides a place for the
+@file{CVS/Template} file (@pxref{Working directory
+storage}).
+
+@cindex LockDir, in CVSROOT/config
+@item LockDir=@var{directory}
+Put CVS lock files in @var{directory} rather than
+directly in the repository. This is useful if you want
+to let users read from the repository while giving them
+write access only to @var{directory}, not to the
+repository. You need to create @var{directory}, but
+CVS will create subdirectories of @var{directory} as it
+needs them. For information on CVS locks, see
+@ref{Concurrency}.
+
+@c Mention this in Compatibility section?
+Before enabling the LockDir option, make sure that you
+have tracked down and removed any copies of CVS 1.9 or
+older. Such versions neither support LockDir, nor will
+give an error indicating that they don't support it.
+The result, if this is allowed to happen, is that some
+CVS users will put the locks one place, and others will
+put them another place, and therefore the repository
+could become corrupted. CVS 1.10 does not support
+LockDir but it will print a warning if run on a
+repository with LockDir enabled.
+@end table
+
+@c ---------------------------------------------------------------------
+@node Environment variables
+@appendix All environment variables which affect CVS
+@cindex Environment variables
+@cindex Reference manual for variables
+
+This is a complete list of all environment variables
+that affect @sc{cvs}.
+
+@table @code
+@cindex CVSIGNORE, environment variable
+@item $CVSIGNORE
+A whitespace-separated list of file name patterns that
+@sc{cvs} should ignore. @xref{cvsignore}.
+
+@cindex CVSWRAPPERS, environment variable
+@item $CVSWRAPPERS
+A whitespace-separated list of file name patterns that
+@sc{cvs} should treat as wrappers. @xref{Wrappers}.
+
+@cindex CVSREAD, environment variable
+@cindex Read-only files, and CVSREAD
+@item $CVSREAD
+If this is set, @code{checkout} and @code{update} will
+try hard to make the files in your working directory
+read-only. When this is not set, the default behavior
+is to permit modification of your working files.
+
+@item $CVSUMASK
+Controls permissions of files in the repository. See
+@ref{File permissions}.
+
+@item $CVSROOT
+Should contain the full pathname to the root of the @sc{cvs}
+source repository (where the @sc{rcs} files are
+kept). This information must be available to @sc{cvs} for
+most commands to execute; if @code{$CVSROOT} is not set,
+or if you wish to override it for one invocation, you
+can supply it on the command line: @samp{cvs -d cvsroot
+cvs_command@dots{}} Once you have checked out a working
+directory, @sc{cvs} stores the appropriate root (in
+the file @file{CVS/Root}), so normally you only need to
+worry about this when initially checking out a working
+directory.
+
+@item $EDITOR
+@itemx $CVSEDITOR
+@itemx $VISUAL
+Specifies the program to use for recording log messages
+during commit. @code{$CVSEDITOR} overrides
+@code{$EDITOR}. See @ref{Committing your changes}.
+
+@cindex PATH, environment variable
+@item $PATH
+If @code{$RCSBIN} is not set, and no path is compiled
+into @sc{cvs}, it will use @code{$PATH} to try to find all
+programs it uses.
+
+@cindex HOME, environment variable
+@item $HOME
+@cindex HOMEPATH, environment variable
+@item $HOMEPATH
+@cindex HOMEDRIVE, environment variable
+@item $HOMEDRIVE
+Used to locate the directory where the @file{.cvsrc}
+file, and other such files, are searched. On Unix, CVS
+just checks for @code{HOME}. On Windows NT, the system will
+set @code{HOMEDRIVE}, for example to @samp{d:} and @code{HOMEPATH},
+for example to @file{\joe}. On Windows 95, you'll
+probably need to set @code{HOMEDRIVE} and @code{HOMEPATH} yourself.
+@c We are being vague about whether HOME works on
+@c Windows; see long comment in windows-NT/filesubr.c.
+
+@cindex CVS_RSH, environment variable
+@item $CVS_RSH
+Specifies the external program which CVS connects with,
+when @code{:ext:} access method is specified.
+@pxref{Connecting via rsh}.
+
+@item $CVS_SERVER
+Used in client-server mode when accessing a remote
+repository using @sc{rsh}. It specifies the name of
+the program to start on the server side when accessing
+a remote repository using @sc{rsh}. The default value
+is @code{cvs}. @pxref{Connecting via rsh}
+
+@item $CVS_PASSFILE
+Used in client-server mode when accessing the @code{cvs
+login server}. Default value is @file{$HOME/.cvspass}.
+@pxref{Password authentication client}
+
+@item $CVS_CLIENT_PORT
+Used in client-server mode when accessing the server
+via Kerberos.
+@pxref{Kerberos authenticated}
+
+@cindex CVS_RCMD_PORT, environment variable
+@item $CVS_RCMD_PORT
+Used in client-server mode. If set, specifies the port
+number to be used when accessing the @sc{rcmd} demon on
+the server side. (Currently not used for Unix clients).
+
+@cindex CVS_CLIENT_LOG, environment variable
+@item $CVS_CLIENT_LOG
+Used for debugging only in client-server
+mode. If set, everything sent to the server is logged
+into @file{@code{$CVS_CLIENT_LOG}.in} and everything
+sent from the server is logged into
+@file{@code{$CVS_CLIENT_LOG}.out}.
+
+@cindex CVS_SERVER_SLEEP, environment variable
+@item $CVS_SERVER_SLEEP
+Used only for debugging the server side in
+client-server mode. If set, delays the start of the
+server child process the specified amount of
+seconds so that you can attach to it with a debugger.
+
+@cindex CVS_IGNORE_REMOTE_ROOT, environment variable
+@item $CVS_IGNORE_REMOTE_ROOT
+For @sc{cvs} 1.10 and older, setting this variable
+prevents @sc{cvs} from overwriting the @file{CVS/Root}
+file when the @samp{-d} global option is specified.
+Later versions of @sc{cvs} do not rewrite
+@file{CVS/Root}, so @code{CVS_IGNORE_REMOTE_ROOT} has no
+effect.
+
+@cindex COMSPEC, environment variable
+@item $COMSPEC
+Used under OS/2 only. It specifies the name of the
+command interpreter and defaults to @sc{cmd.exe}.
+
+@cindex TMPDIR, environment variable
+@item $TMPDIR
+@cindex TMP, environment variable
+@itemx $TMP
+@cindex TEMP, environment variable
+@itemx $TEMP
+@cindex Temporary files, location of
+@c This is quite nuts. We don't talk about tempnam
+@c or mkstemp which we sometimes use. The discussion
+@c of "Global options" is semi-incoherent.
+@c I'm not even sure those are the only inaccuracies.
+@c Furthermore, the conventions are
+@c pretty crazy and they should be simplified.
+Directory in which temporary files are located.
+The @sc{cvs} server uses
+@code{TMPDIR}. @xref{Global options}, for a
+description of how to specify this.
+Some parts of @sc{cvs} will always use @file{/tmp} (via
+the @code{tmpnam} function provided by the system).
+
+On Windows NT, @code{TMP} is used (via the @code{_tempnam}
+function provided by the system).
+
+The @code{patch} program which is used by the @sc{cvs}
+client uses @code{TMPDIR}, and if it is not set, uses
+@file{/tmp} (at least with GNU patch 2.1). Note that
+if your server and client are both running @sc{cvs}
+1.9.10 or later, @sc{cvs} will not invoke an external
+@code{patch} program.
+@end table
+
+@node Compatibility
+@appendix Compatibility between CVS Versions
+
+@cindex CVS, versions of
+@cindex Versions, of CVS
+@cindex Compatibility, between CVS versions
+@c We don't mention versions older than CVS 1.3
+@c on the theory that it would clutter it up for the vast
+@c majority of people, who don't have anything that old.
+@c
+The repository format is compatible going back to
+@sc{cvs} 1.3. But see @ref{Watches Compatibility}, if
+you have copies of @sc{cvs} 1.6 or older and you want
+to use the optional developer communication features.
+@c If you "cvs rm" and commit using 1.3, then you'll
+@c want to run "rcs -sdead <file,v>" on each of the
+@c files in the Attic if you then want 1.5 and
+@c later to recognize those files as dead (I think the
+@c symptom if this is not done is that files reappear
+@c in joins). (Wait: the above will work but really to
+@c be strictly correct we should suggest checking
+@c in a new revision rather than just changing the
+@c state of the head revision, shouldn't we?).
+@c The old convert.sh script was for this, but it never
+@c did get updated to reflect use of the RCS "dead"
+@c state.
+@c Note: this is tricky to document without confusing
+@c people--need to carefully say what CVS version we
+@c are talking about and keep in mind the distinction
+@c between a
+@c repository created with 1.3 and on which one now
+@c uses 1.5+, and a repository on which one wants to
+@c use both versions side by side (e.g. during a
+@c transition period).
+@c Wait, can't CVS just detect the case in which a file
+@c is in the Attic but the head revision is not dead?
+@c Not sure whether this should produce a warning or
+@c something, and probably needs further thought, but
+@c it would appear that the situation can be detected.
+@c
+@c We might want to separate out the 1.3 compatibility
+@c section (for repository & working directory) from the
+@c rest--that might help avoid confusing people who
+@c are upgrading (for example) from 1.6 to 1.8.
+@c
+@c A minor incompatibility is if a current version of CVS
+@c puts "Nfoo" into CVS/Tag, then CVS 1.9 or older will
+@c see this as if there is no tag. Seems to me this is
+@c too obscure to mention.
+
+The working directory format is compatible going back
+to @sc{cvs} 1.5. It did change between @sc{cvs} 1.3
+and @sc{cvs} 1.5. If you run @sc{cvs} 1.5 or newer on
+a working directory checked out with @sc{cvs} 1.3,
+@sc{cvs} will convert it, but to go back to @sc{cvs}
+1.3 you need to check out a new working directory with
+@sc{cvs} 1.3.
+
+The remote protocol is interoperable going back to @sc{cvs} 1.5, but no
+further (1.5 was the first official release with the remote protocol,
+but some older versions might still be floating around). In many
+cases you need to upgrade both the client and the server to take
+advantage of new features and bugfixes, however.
+
+@c Perhaps should be saying something here about the
+@c "D" lines in Entries (written by CVS 1.9; 1.8 and
+@c older don't use them). These are supposed to be
+@c compatible in both directions, but I'm not sure
+@c they quite are 100%. One common gripe is if you
+@c "rm -r" a directory and 1.9 gets confused, as it
+@c still sees it in Entries. That one is fixed in
+@c (say) 1.9.6. Someone else reported problems with
+@c starting with a directory which was checked out with
+@c an old version, and then using a new version, and
+@c some "D" lines appeared, but not for every
+@c directory, causing some directories to be skipped.
+@c They weren't sure how to reproduce this, though.
+
+@c ---------------------------------------------------------------------
+@node Troubleshooting
+@appendix Troubleshooting
+
+If you are having trouble with @sc{cvs}, this appendix
+may help. If there is a particular error message which
+you are seeing, then you can look up the message
+alphabetically. If not, you can look through the
+section on other problems to see if your problem is
+mentioned there.
+
+@menu
+* Error messages:: Partial list of CVS errors
+* Connection:: Trouble making a connection to a CVS server
+* Other problems:: Problems not readily listed by error message
+@end menu
+
+@ignore
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@c @node Bad administrative files
+@appendixsec Bad administrative files
+
+@c -- Give hints on how to fix them
+@end ignore
+
+@node Error messages
+@appendixsec Partial list of error messages
+
+Here is a partial list of error messages that you may
+see from @sc{cvs}. It is not a complete list---@sc{cvs}
+is capable of printing many, many error messages, often
+with parts of them supplied by the operating system,
+but the intention is to list the common and/or
+potentially confusing error messages.
+
+The messages are alphabetical, but introductory text
+such as @samp{cvs update: } is not considered in
+ordering them.
+
+In some cases the list includes messages printed by old
+versions of @sc{cvs} (partly because users may not be
+sure which version of @sc{cvs} they are using at any
+particular moment).
+@c If we want to start retiring messages, perhaps we
+@c should pick a cutoff version (for example, no more
+@c messages which are specific to versions before 1.9)
+@c and then move the old messages to an "old messages"
+@c node rather than deleting them completely.
+
+@table @code
+@c FIXME: What is the correct way to format a multiline
+@c error message here? Maybe @table is the wrong
+@c choice? Texinfo gurus?
+@item cvs @var{command}: authorization failed: server @var{host} rejected access
+This is a generic response when trying to connect to a
+pserver server which chooses not to provide a
+specific reason for denying authorization. Check that
+the username and password specified are correct and
+that the @code{CVSROOT} specified is allowed by @samp{--allow-root}
+in @file{inetd.conf}. See @ref{Password authenticated}.
+
+@item @var{file}:@var{line}: Assertion '@var{text}' failed
+The exact format of this message may vary depending on
+your system. It indicates a bug in @sc{cvs}, which can
+be handled as described in @ref{BUGS}.
+
+@item cvs @var{command}: conflict: removed @var{file} was modified by second party
+This message indicates that you removed a file, and
+someone else modified it. To resolve the conflict,
+first run @samp{cvs add @var{file}}. If desired, look
+at the other party's modification to decide whether you
+still want to remove it. If you don't want to remove
+it, stop here. If you do want to remove it, proceed
+with @samp{cvs remove @var{file}} and commit your
+removal.
+@c Tests conflicts2-142b* in sanity.sh test for this.
+
+@item cannot change permissions on temporary directory
+@example
+Operation not permitted
+@end example
+This message has been happening in a non-reproducible,
+occasional way when we run the client/server testsuite,
+both on Red Hat Linux 3.0.3 and 4.1. We haven't been
+able to figure out what causes it, nor is it known
+whether it is specific to linux (or even to this
+particular machine!). If the problem does occur on
+other unices, @samp{Operation not permitted} would be
+likely to read @samp{Not owner} or whatever the system
+in question uses for the unix @code{EPERM} error. If
+you have any information to add, please let us know as
+described in @ref{BUGS}. If you experience this error
+while using @sc{cvs}, retrying the operation which
+produced it should work fine.
+@c This has been seen in a variety of tests, including
+@c multibranch-2, multibranch-5, and basic1-24-rm-rm,
+@c so it doesn't seem particularly specific to any one
+@c test.
+
+@item cvs [server aborted]: Cannot check out files into the repository itself
+The obvious cause for this message (especially for
+non-client/server @sc{cvs}) is that the @sc{cvs} root
+is, for example, @file{/usr/local/cvsroot} and you try
+to check out files when you are in a subdirectory, such
+as @file{/usr/local/cvsroot/test}. However, there is a
+more subtle cause, which is that the temporary
+directory on the server is set to a subdirectory of the
+root (which is also not allowed). If this is the
+problem, set the temporary directory to somewhere else,
+for example @file{/var/tmp}; see @code{TMPDIR} in
+@ref{Environment variables}, for how to set the
+temporary directory.
+
+@c For one example see basica-1a10 in the testsuite
+@c For another example, "cvs co ." on NT; see comment
+@c at windows-NT/filesubr.c (expand_wild).
+@c For another example, "cvs co foo/bar" where foo exists.
+@item cannot open CVS/Entries for reading: No such file or directory
+This generally indicates a @sc{cvs} internal error, and
+can be handled as with other @sc{cvs} bugs
+(@pxref{BUGS}). Usually there is a workaround---the
+exact nature of which would depend on the situation but
+which hopefully could be figured out.
+
+@c This is more obscure than it might sound; it only
+@c happens if you run "cvs init" from a directory which
+@c contains a CVS/Root file at the start.
+@item cvs [init aborted]: cannot open CVS/Root: No such file or directory
+This message is harmless. Provided it is not
+accompanied by other errors, the operation has
+completed successfully. This message should not occur
+with current versions of @sc{cvs}, but it is documented
+here for the benefit of @sc{cvs} 1.9 and older.
+
+@item cvs [checkout aborted]: cannot rename file @var{file} to CVS/,,@var{file}: Invalid argument
+This message has been reported as intermittently
+happening with CVS 1.9 on Solaris 2.5. The cause is
+unknown; if you know more about what causes it, let us
+know as described in @ref{BUGS}.
+
+@item cvs [@var{command} aborted]: cannot start server via rcmd
+This, unfortunately, is a rather nonspecific error
+message which @sc{cvs} 1.9 will print if you are
+running the @sc{cvs} client and it is having trouble
+connecting to the server. Current versions of @sc{cvs}
+should print a much more specific error message. If
+you get this message when you didn't mean to run the
+client at all, you probably forgot to specify
+@code{:local:}, as described in @ref{Repository}.
+
+@item ci: @var{file},v: bad diff output line: Binary files - and /tmp/T2a22651 differ
+CVS 1.9 and older will print this message
+when trying to check in a binary file if
+@sc{rcs} is not correctly installed. Re-read the
+instructions that came with your @sc{rcs} distribution
+and the @sc{install} file in the @sc{cvs}
+distribution. Alternately, upgrade to a current
+version of @sc{cvs}, which checks in files itself
+rather than via @sc{rcs}.
+
+@item cvs checkout: could not check out @var{file}
+With CVS 1.9, this can mean that the @code{co} program
+(part of @sc{rcs}) returned a failure. It should be
+preceded by another error message, however it has been
+observed without another error message and the cause is
+not well-understood. With the current version of CVS,
+which does not run @code{co}, if this message occurs
+without another error message, it is definitely a CVS
+bug (@pxref{BUGS}).
+@c My current suspicion is that the RCS in the rcs (not
+@c cvs/winnt/rcs57nt.zip) directory on the _Practical_
+@c CD is bad (remains to be confirmed).
+@c There is also a report of something which looks
+@c very similar on SGI, Irix 5.2, so I dunno.
+
+@item cvs [login aborted]: could not find out home directory
+This means that you need to set the environment
+variables that CVS uses to locate your home directory.
+See the discussion of @code{HOME}, @code{HOMEDRIVE}, and @code{HOMEPATH} in
+@ref{Environment variables}.
+
+@item cvs update: could not merge revision @var{rev} of @var{file}: No such file or directory
+CVS 1.9 and older will print this message if there was
+a problem finding the @code{rcsmerge} program. Make
+sure that it is in your @code{PATH}, or upgrade to a
+current version of CVS, which does not require
+an external @code{rcsmerge} program.
+
+@item cvs [update aborted]: could not patch @var{file}: No such file or directory
+This means that there was a problem finding the
+@code{patch} program. Make sure that it is in your
+@code{PATH}. Note that despite appearances the message
+is @emph{not} referring to whether it can find @var{file}.
+If both the client and the server are running a current
+version of @sc{cvs}, then there is no need for an
+external patch program and you should not see this
+message. But if either client or server is running
+@sc{cvs} 1.9, then you need @code{patch}.
+
+@item cvs update: could not patch @var{file}; will refetch
+This means that for whatever reason the client was
+unable to apply a patch that the server sent. The
+message is nothing to be concerned about, because
+inability to apply the patch only slows things down and
+has no effect on what @sc{cvs} does.
+@c xref to update output. Or File status?
+@c Or some place else that
+@c explains this whole "patch"/P/Needs Patch thing?
+
+@item dying gasps from @var{server} unexpected
+There is a known bug in the server for @sc{cvs} 1.9.18
+and older which can cause this. For me, this was
+reproducible if I used the @samp{-t} global option. It
+was fixed by Andy Piper's 14 Nov 1997 change to
+src/filesubr.c, if anyone is curious.
+If you see the message,
+you probably can just retry the operation which failed,
+or if you have discovered information concerning its
+cause, please let us know as described in @ref{BUGS}.
+
+@item end of file from server (consult above messages if any)
+The most common cause for this message is if you are
+using an external @code{rsh} program and it exited with
+an error. In this case the @code{rsh} program should
+have printed a message, which will appear before the
+above message. For more information on setting up a
+@sc{cvs} client and server, see @ref{Remote repositories}.
+
+@item cvs [update aborted]: EOF in key in RCS file @var{file},v
+@itemx cvs [checkout aborted]: EOF while looking for end of string in RCS file @var{file},v
+This means that there is a syntax error in the given
+@sc{rcs} file. Note that this might be true even if @sc{rcs} can
+read the file OK; @sc{cvs} does more error checking of
+errors in the RCS file. That is why you may see this
+message when upgrading from @sc{cvs} 1.9 to @sc{cvs}
+1.10. The likely cause for the original corruption is
+hardware, the operating system, or the like. Of
+course, if you find a case in which @sc{cvs} seems to
+corrupting the file, by all means report it,
+(@pxref{BUGS}).
+There are quite a few variations of this error message,
+depending on exactly where in the @sc{rcs} file @sc{cvs}
+finds the syntax error.
+
+@cindex mkmodules
+@item cvs commit: Executing 'mkmodules'
+This means that your repository is set up for a version
+of @sc{cvs} prior to @sc{cvs} 1.8. When using @sc{cvs}
+1.8 or later, the above message will be preceded by
+
+@example
+cvs commit: Rebuilding administrative file database
+@end example
+
+If you see both messages, the database is being rebuilt
+twice, which is unnecessary but harmless. If you wish
+to avoid the duplication, and you have no versions of
+@sc{cvs} 1.7 or earlier in use, remove @code{-i mkmodules}
+every place it appears in your @code{modules}
+file. For more information on the @code{modules} file,
+see @ref{modules}.
+
+@c This message comes from "co", and I believe is
+@c possible only with older versions of CVS which call
+@c co. The problem with being able to create the bogus
+@c RCS file still exists, though (and I think maybe
+@c there is a different symptom(s) now).
+@c FIXME: Would be nice to have a more exact wording
+@c for this message.
+@item missing author
+Typically this can happen if you created an RCS file
+with your username set to empty. CVS will, bogusly,
+create an illegal RCS file with no value for the author
+field. The solution is to make sure your username is
+set to a non-empty value and re-create the RCS file.
+@c "make sure your username is set" is complicated in
+@c and of itself, as there are the environment
+@c variables the system login name, &c, and it depends
+@c on the version of CVS.
+
+@item cvs [checkout aborted]: no such tag @var{tag}
+This message means that @sc{cvs} isn't familiar with
+the tag @var{tag}. Usually this means that you have
+mistyped a tag name; however there are (relatively
+obscure) cases in which @sc{cvs} will require you to
+@c Search sanity.sh for "no such tag" to see some of
+@c the relatively obscure cases.
+try a few other @sc{cvs} commands involving that tag,
+before you find one which will cause @sc{cvs} to update
+the @file{val-tags} file; see discussion of val-tags in
+@ref{File permissions}. You only need to worry about
+this once for a given tag; when a tag is listed in
+@file{val-tags}, it stays there. Note that using
+@samp{-f} to not require tag matches does not override
+this check; see @ref{Common options}.
+
+@item *PANIC* administration files missing
+This typically means that there is a directory named
+CVS but it does not contain the administrative files
+which CVS puts in a CVS directory. If the problem is
+that you created a CVS directory via some mechanism
+other than CVS, then the answer is simple, use a name
+other than CVS. If not, it indicates a CVS bug
+(@pxref{BUGS}).
+
+@item rcs error: Unknown option: -x,v/
+This message will be followed by a usage message for
+@sc{rcs}. It means that you have an old version of
+@sc{rcs} (probably supplied with your operating
+system). CVS only works with @sc{rcs} version 5 and
+later.
+@c For more information on installing @sc{cvs}, see
+@c (FIXME: where? it depends on whether you are
+@c getting binaries or sources or what).
+@c The message can also say "ci error" or something
+@c instead of "rcs error", I suspect.
+
+@item cvs [server aborted]: received broken pipe signal
+This message seems to be caused by a hard-to-track-down
+bug in @sc{cvs} or the systems it runs on (we don't
+know---we haven't tracked it down yet!). It seems to
+happen only after a @sc{cvs} command has completed, and
+you should be able to just ignore the message.
+However, if you have discovered information concerning its
+cause, please let us know as described in @ref{BUGS}.
+
+@item Too many arguments!
+This message is typically printed by the @file{log.pl}
+script which is in the @file{contrib} directory in the
+@sc{cvs} source distribution. In some versions of
+@sc{cvs}, @file{log.pl} has been part of the default
+@sc{cvs} installation. The @file{log.pl} script gets
+called from the @file{loginfo} administrative file.
+Check that the arguments passed in @file{loginfo} match
+what your version of @file{log.pl} expects. In
+particular, the @file{log.pl} from @sc{cvs} 1.3 and
+older expects the logfile as an argument whereas the
+@file{log.pl} from @sc{cvs} 1.5 and newer expects the
+logfile to be specified with a @samp{-f} option. Of
+course, if you don't need @file{log.pl} you can just
+comment it out of @file{loginfo}.
+
+@item cvs [update aborted]: unexpected EOF reading @var{file},v
+See @samp{EOF in key in RCS file}.
+
+@item cvs [login aborted]: unrecognized auth response from @var{server}
+This message typically means that the server is not set
+up properly. For example, if @file{inetd.conf} points
+to a nonexistent cvs executable. To debug it further,
+find the log file which inetd writes
+(@file{/var/log/messages} or whatever inetd uses on
+your system). For details, see @ref{Connection}, and
+@ref{Password authentication server}.
+
+@item cvs commit: Up-to-date check failed for `@var{file}'
+This means that someone else has committed a change to
+that file since the last time that you did a @code{cvs
+update}. So before proceeding with your @code{cvs
+commit} you need to @code{cvs update}. CVS will merge
+the changes that you made and the changes that the
+other person made. If it does not detect any conflicts
+it will report @samp{M cacErrCodes.h} and you are ready
+to @code{cvs commit}. If it detects conflicts it will
+print a message saying so, will report @samp{C cacErrCodes.h},
+and you need to manually resolve the
+conflict. For more details on this process see
+@ref{Conflicts example}.
+
+@item Usage: diff3 [-exEX3 [-i | -m] [-L label1 -L label3]] file1 file2 file3
+@example
+Only one of [exEX3] allowed
+@end example
+This indicates a problem with the installation of
+@code{diff3} and @code{rcsmerge}. Specifically
+@code{rcsmerge} was compiled to look for GNU diff3, but
+it is finding unix diff3 instead. The exact text of
+the message will vary depending on the system. The
+simplest solution is to upgrade to a current version of
+@sc{cvs}, which does not rely on external
+@code{rcsmerge} or @code{diff3} programs.
+
+@item warning: unrecognized response `@var{text}' from cvs server
+If @var{text} contains a valid response (such as
+@samp{ok}) followed by an extra carriage return
+character (on many systems this will cause the second
+part of the message to overwrite the first part), then
+it probably means that you are using the @samp{:ext:}
+access method with a version of rsh, such as most
+non-unix rsh versions, which does not by default
+provide a transparent data stream. In such cases you
+probably want to try @samp{:server:} instead of
+@samp{:ext:}. If @var{text} is something else, this
+may signify a problem with your CVS server.
+Double-check your installation against the instructions
+for setting up the CVS server.
+@c FIXCVS: should be printing CR as \r or \015 or some
+@c such, probably.
+
+@item cvs commit: [@var{time}] waiting for @var{user}'s lock in @var{directory}
+This is a normal message, not an error. See
+@ref{Concurrency}, for more details.
+
+@item cvs commit: warning: editor session failed
+@cindex Exit status, of editor
+This means that the editor which @sc{cvs} is using exits with a nonzero
+exit status. Some versions of vi will do this even when there was not
+a problem editing the file. If so, point the
+@code{CVSEDITOR} environment variable to a small script
+such as:
+
+@example
+#!/bin/sh
+vi $*
+exit 0
+@end example
+
+@c "warning: foo was lost" and "no longer pertinent" (both normal).
+@c Would be nice to write these up--they are
+@c potentially confusing for the new user.
+@end table
+
+@node Connection
+@appendixsec Trouble making a connection to a CVS server
+
+This section concerns what to do if you are having
+trouble making a connection to a @sc{cvs} server. If
+you are running the @sc{cvs} command line client
+running on Windows, first upgrade the client to
+@sc{cvs} 1.9.12 or later. The error reporting in
+earlier versions provided much less information about
+what the problem was. If the client is non-Windows,
+@sc{cvs} 1.9 should be fine.
+
+If the error messages are not sufficient to track down
+the problem, the next steps depend largely on which
+access method you are using.
+
+@table @code
+@cindex :ext:, troubleshooting
+@item :ext:
+Try running the rsh program from the command line. For
+example: "rsh servername cvs -v" should print @sc{cvs}
+version information. If this doesn't work, you need to
+fix it before you can worry about @sc{cvs} problems.
+
+@cindex :server:, troubleshooting
+@item :server:
+You don't need a command line rsh program to use this
+access method, but if you have an rsh program around,
+it may be useful as a debugging tool. Follow the
+directions given for :ext:.
+
+@cindex :pserver:, troubleshooting
+@item :pserver:
+One good debugging tool is to "telnet servername
+2401". After connecting, send any text (for example
+"foo" followed by return). If @sc{cvs} is working
+correctly, it will respond with
+
+@example
+cvs [pserver aborted]: bad auth protocol start: foo
+@end example
+
+If this fails to work, then make sure inetd is working
+right. Change the invocation in @file{inetd.conf} to run the
+echo program instead of cvs. For example:
+
+@example
+2401 stream tcp nowait root /bin/echo echo hello
+@end example
+
+After making that change and instructing inetd to
+re-read its configuration file, "telnet servername
+2401" should show you the text hello and then the
+server should close the connection. If this doesn't
+work, you need to fix it before you can worry about
+@sc{cvs} problems.
+
+On AIX systems, the system will often have its own
+program trying to use port 2401. This is AIX's problem
+in the sense that port 2401 is registered for use with
+@sc{cvs}. I hear that there is an AIX patch available
+to address this problem.
+
+Another good debugging tool is the @samp{-d}
+(debugging) option to inetd. Consult your system
+documentation for more information.
+
+If you seem to be connecting but get errors like:
+
+@example
+cvs server: cannot open /root/.cvsignore: Permission denied
+cvs [server aborted]: can't chdir(/root): Permission denied
+@end example
+
+then either you haven't specified @samp{-f} in @file{inetd.conf}
+or your system is setting the @code{HOME} environment variable
+for programs being run by inetd. In the latter case, you can either
+have inetd run a shell script that unsets @code{HOME} and then runs
+@sc{cvs}, or you can use @code{env} to run @sc{cvs} with a pristine
+environment.
+@end table
+
+@node Other problems
+@appendixsec Other common problems
+
+Here is a list of problems which do not fit into the
+above categories. They are in no particular order.
+
+@itemize @bullet
+@item
+On Windows, if there is a 30 second or so delay when
+you run a @sc{cvs} command, it may mean that you have
+your home directory set to @file{C:/}, for example (see
+@code{HOMEDRIVE} and @code{HOMEPATH} in
+@ref{Environment variables}). CVS expects the home
+directory to not end in a slash, for example @file{C:}
+or @file{C:\cvs}.
+@c FIXCVS: CVS should at least detect this and print an
+@c error, presumably.
+
+@item
+If you are running @sc{cvs} 1.9.18 or older, and
+@code{cvs update} finds a conflict and tries to
+merge, as described in @ref{Conflicts example}, but
+doesn't tell you there were conflicts, then you may
+have an old version of @sc{rcs}. The easiest solution
+probably is to upgrade to a current version of
+@sc{cvs}, which does not rely on external @sc{rcs}
+programs.
+@end itemize
+
+@c ---------------------------------------------------------------------
+@node Credits
+@appendix Credits
+
+@cindex Contributors (manual)
+@cindex Credits (manual)
+Roland Pesch, then of Cygnus Support <@t{roland@@wrs.com}>
+wrote the manual pages which were distributed with
+@sc{cvs} 1.3. Much of their text was copied into this
+manual. He also read an early draft
+of this manual and contributed many ideas and
+corrections.
+
+The mailing-list @code{info-cvs} is sometimes
+informative. I have included information from postings
+made by the following persons:
+David G. Grubbs <@t{dgg@@think.com}>.
+
+Some text has been extracted from the man pages for
+@sc{rcs}.
+
+The @sc{cvs} @sc{faq} by David G. Grubbs has provided
+useful material. The @sc{faq} is no longer maintained,
+however, and this manual is about the closest thing there
+is to a successor (with respect to documenting how to
+use @sc{cvs}, at least).
+
+In addition, the following persons have helped by
+telling me about mistakes I've made:
+
+@display
+Roxanne Brunskill <@t{rbrunski@@datap.ca}>,
+Kathy Dyer <@t{dyer@@phoenix.ocf.llnl.gov}>,
+Karl Pingle <@t{pingle@@acuson.com}>,
+Thomas A Peterson <@t{tap@@src.honeywell.com}>,
+Inge Wallin <@t{ingwa@@signum.se}>,
+Dirk Koschuetzki <@t{koschuet@@fmi.uni-passau.de}>
+and Michael Brown <@t{brown@@wi.extrel.com}>.
+@end display
+
+The list of contributors here is not comprehensive; for a more
+complete list of who has contributed to this manual see
+the file @file{doc/ChangeLog} in the @sc{cvs} source
+distribution.
+
+@c ---------------------------------------------------------------------
+@node BUGS
+@appendix Dealing with bugs in CVS or this manual
+
+@cindex Bugs in this manual or CVS
+Neither @sc{cvs} nor this manual is perfect, and they
+probably never will be. If you are having trouble
+using @sc{cvs}, or think you have found a bug, there
+are a number of things you can do about it. Note that
+if the manual is unclear, that can be considered a bug
+in the manual, so these problems are often worth doing
+something about as well as problems with @sc{cvs} itself.
+
+@cindex Reporting bugs
+@cindex Bugs, reporting
+@cindex Errors, reporting
+@itemize @bullet
+@item
+If you want someone to help you and fix bugs that you
+report, there are companies which will do that for a
+fee. Two such companies are:
+
+@cindex Signum Support
+@cindex Cyclic Software
+@cindex Support, getting CVS support
+@example
+Signum Support AB
+Box 2044
+S-580 02 Linkoping
+Sweden
+Email: info@@signum.se
+Phone: +46 (0)13 - 21 46 00
+Fax: +46 (0)13 - 21 47 00
+http://www.signum.se/
+
+Cyclic Software
+United States of America
+http://www.cyclic.com/
+info@@cyclic.com
+@end example
+
+@item
+If you got @sc{cvs} through a distributor, such as an
+operating system vendor or a vendor of freeware
+@sc{cd-rom}s, you may wish to see whether the
+distributor provides support. Often, they will provide
+no support or minimal support, but this may vary from
+distributor to distributor.
+
+@item
+If you have the skills and time to do so, you may wish
+to fix the bug yourself. If you wish to submit your
+fix for inclusion in future releases of @sc{cvs}, see
+the file @sc{hacking} in the @sc{cvs} source
+distribution. It contains much more information on the
+process of submitting fixes.
+
+@item
+There may be resources on the net which can help. Two
+good places to start are:
+
+@example
+http://www.cyclic.com
+http://www.loria.fr/~molli/cvs-index.html
+@end example
+
+If you are so inspired, increasing the information
+available on the net is likely to be appreciated. For
+example, before the standard @sc{cvs} distribution
+worked on Windows 95, there was a web page with some
+explanation and patches for running @sc{cvs} on Windows
+95, and various people helped out by mentioning this
+page on mailing lists or newsgroups when the subject
+came up.
+
+@item
+It is also possible to report bugs to @code{bug-cvs}.
+Note that someone may or may not want to do anything
+with your bug report---if you need a solution consider
+one of the options mentioned above. People probably do
+want to hear about bugs which are particularly severe
+in consequences and/or easy to fix, however. You can
+also increase your odds by being as clear as possible
+about the exact nature of the bug and any other
+relevant information. The way to report bugs is to
+send email to @code{bug-cvs@@gnu.org}. Note
+that submissions to @code{bug-cvs} may be distributed
+under the terms of the @sc{gnu} Public License, so if
+you don't like this, don't submit them. There is
+usually no justification for sending mail directly to
+one of the @sc{cvs} maintainers rather than to
+@code{bug-cvs}; those maintainers who want to hear
+about such bug reports read @code{bug-cvs}. Also note
+that sending a bug report to other mailing lists or
+newsgroups is @emph{not} a substitute for sending it to
+@code{bug-cvs}. It is fine to discuss @sc{cvs} bugs on
+whatever forum you prefer, but there are not
+necessarily any maintainers reading bug reports sent
+anywhere except @code{bug-cvs}.
+@end itemize
+
+@cindex Known bugs in this manual or CVS
+People often ask if there is a list of known bugs or
+whether a particular bug is a known one. The file
+@sc{bugs} in the @sc{cvs} source distribution is one
+list of known bugs, but it doesn't necessarily try to
+be comprehensive. Perhaps there will never be a
+comprehensive, detailed list of known bugs.
+
+@c ---------------------------------------------------------------------
+@node Index
+@unnumbered Index
+@cindex Index
+
+@printindex cp
+
+@summarycontents
+
+@contents
+
+@bye
+
+Local Variables:
+fill-column: 55
+End:
diff --git a/gnu/dist/cvs/doc/cvsclient.texi b/gnu/dist/cvs/doc/cvsclient.texi
new file mode 100644
index 00000000000..de3595dc872
--- /dev/null
+++ b/gnu/dist/cvs/doc/cvsclient.texi
@@ -0,0 +1,2073 @@
+\input texinfo @c -*- texinfo -*-
+
+@setfilename cvsclient.info
+@include CVSvn.texi
+
+@dircategory Programming
+@direntry
+* cvsclient: (cvsclient). The CVS client/server protocol.
+@end direntry
+
+@node Top
+@top CVS Client/Server
+
+This document describes the client/server protocol used by CVS. It does
+not describe how to use or administer client/server CVS; see the regular
+CVS manual for that. This is version @value{CVSVN} of the protocol
+specification---@xref{Introduction}, for more on what this version number
+means.
+
+@menu
+* Introduction:: What is CVS and what is the client/server protocol for?
+* Goals:: Basic design decisions, requirements, scope, etc.
+* Connection and Authentication:: Various ways to connect to the server
+* Password scrambling:: Scrambling used by pserver
+* Protocol:: Complete description of the protocol
+* Protocol Notes:: Possible enhancements, limitations, etc. of the protocol
+@end menu
+
+@node Introduction
+@chapter Introduction
+
+CVS is a version control system (with some additional configuration
+management functionality). It maintains a central @dfn{repository}
+which stores files (often source code), including past versions,
+information about who modified them and when, and so on. People who
+wish to look at or modify those files, known as @dfn{developers}, use
+CVS to @dfn{check out} a @dfn{working directory} from the repository, to
+@dfn{check in} new versions of files to the repository, and other
+operations such as viewing the modification history of a file. If
+developers are connected to the repository by a network, particularly a
+slow or flaky one, the most efficient way to use the network is with the
+CVS-specific protocol described in this document.
+
+Developers, using the machine on which they store their working
+directory, run the CVS @dfn{client} program. To perform operations
+which cannot be done locally, it connects to the CVS @dfn{server}
+program, which maintains the repository. For more information on how
+to connect see @ref{Connection and Authentication}.
+
+This document describes the CVS protocol. Unfortunately, it does not
+yet completely document one aspect of the protocol---the detailed
+operation of each CVS command and option---and one must look at the CVS
+user documentation, @file{cvs.texinfo}, for that information. The
+protocol is non-proprietary (anyone who wants to is encouraged to
+implement it) and an implementation, known as CVS, is available under
+the GNU Public License. The CVS distribution, containing this
+implementation, @file{cvs.texinfo}, and a copy (possibly more or less up
+to date than what you are reading now) of this document,
+@file{cvsclient.texi}, can be found at the usual GNU FTP sites, with a
+filename such as @file{cvs-@var{version}.tar.gz}.
+
+This is version @value{CVSVN} of the protocol specification. This
+version number is intended only to aid in distinguishing different
+versions of this specification. Although the specification is currently
+maintained in conjunction with the CVS implementation, and carries the
+same version number, it also intends to document what is involved with
+interoperating with other implementations (such as other versions of
+CVS); see @ref{Requirements}. This version number should not be used
+by clients or servers to determine what variant of the protocol to
+speak; they should instead use the @code{valid-requests} and
+@code{Valid-responses} mechanism (@pxref{Protocol}), which is more
+flexible.
+
+@node Goals
+@chapter Goals
+
+@itemize @bullet
+@item
+Do not assume any access to the repository other than via this protocol.
+It does not depend on NFS, rdist, etc.
+
+@item
+Providing a reliable transport is outside this protocol. The protocol
+expects a reliable transport that is transparent (that is, there is no
+translation of characters, including characters such as such as
+linefeeds or carriage returns), and can transmit all 256 octets (for
+example for proper handling of binary files, compression, and
+encryption). The encoding of characters specified by the protocol (the
+names of requests and so on) is the invariant ISO 646 character set (a
+subset of most popular character sets including ASCII and others). For
+more details on running the protocol over the TCP reliable transport,
+see @ref{Connection and Authentication}.
+
+@item
+Security and authentication are handled outside this protocol (but see
+below about @samp{cvs kserver} and @samp{cvs pserver}).
+
+@item
+The protocol makes it possible for updates to be atomic with respect to
+checkins; that is if someone commits changes to several files in one cvs
+command, then an update by someone else would either get all the
+changes, or none of them. The current @sc{cvs} server can't do this,
+but that isn't the protocol's fault.
+
+@item
+The protocol is, with a few exceptions, transaction-based. That is, the
+client sends all its requests (without waiting for server responses),
+and then waits for the server to send back all responses (without
+waiting for further client requests). This has the advantage of
+minimizing network turnarounds and the disadvantage of sometimes
+transferring more data than would be necessary if there were a richer
+interaction. Another, more subtle, advantage is that there is no need
+for the protocol to provide locking for features such as making checkins
+atomic with respect to updates. Any such locking can be handled
+entirely by the server. A good server implementation (such as the
+current @sc{cvs} server) will make sure that it does not have any such
+locks in place whenever it is waiting for communication with the client;
+this prevents one client on a slow or flaky network from interfering
+with the work of others.
+
+@item
+It is a general design goal to provide only one way to do a given
+operation (where possible). For example, implementations have no choice
+about whether to terminate lines with linefeeds or some other
+character(s), and request and response names are case-sensitive. This
+is to enhance interoperability. If a protocol allows more than one way
+to do something, it is all too easy for some implementations to support
+only some of them (perhaps accidentally).
+@c I vaguely remember reading, probably in an RFC, about the problems
+@c that were caused when some people decided that SMTP should accept
+@c other line termination (in the message ("DATA")?) than CRLF. However, I
+@c can't seem to track down the reference.
+@end itemize
+
+@node Connection and Authentication
+@chapter How to Connect to and Authenticate Oneself to the CVS server
+
+Connection and authentication occurs before the CVS protocol itself is
+started. There are several ways to connect.
+
+@table @asis
+@item server
+If the client has a way to execute commands on the server, and provide
+input to the commands and output from them, then it can connect that
+way. This could be the usual rsh (port 514) protocol, Kerberos rsh,
+SSH, or any similar mechanism. The client may allow the user to specify
+the name of the server program; the default is @code{cvs}. It is
+invoked with one argument, @code{server}. Once it invokes the server,
+the client proceeds to start the cvs protocol.
+
+@item kserver
+The kerberized server listens on a port (in the current implementation,
+by having inetd call "cvs kserver") which defaults to 1999. The client
+connects, sends the usual kerberos authentication information, and then
+starts the cvs protocol. Note: port 1999 is officially registered for
+another use, and in any event one cannot register more than one port for
+CVS, so GSS-API (see below) is recommended instead of kserver as a way
+to support kerberos.
+
+@item pserver
+The name @dfn{pserver} is somewhat confusing. It refers to both a
+generic framework which allows the CVS protocol to support several
+authentication mechanisms, and a name for a specific mechanism which
+transfers a username and a cleartext password. Servers need not support
+all mechanisms, and in fact servers will typically want to support only
+those mechanisms which meet the relevant security needs.
+
+The pserver server listens on a port (in the current
+implementation, by having inetd call "cvs pserver") which defaults to
+2401 (this port is officially registered). The client
+connects, and sends the following:
+
+@itemize @bullet
+@item
+the string @samp{BEGIN AUTH REQUEST}, a linefeed,
+@item
+the cvs root, a linefeed,
+@item
+the username, a linefeed,
+@item
+the password trivially encoded (see @ref{Password scrambling}), a
+linefeed,
+@item
+the string @samp{END AUTH REQUEST}, and a linefeed.
+@end itemize
+
+The client must send the
+identical string for cvs root both here and later in the
+@code{Root} request of the cvs
+protocol itself. Servers are encouraged to enforce this restriction.
+The possible server responses (each of which is followed by a linefeed)
+are the following. Note that although there is a small similarity
+between this authentication protocol and the cvs protocol, they are
+separate.
+
+@table @code
+@item I LOVE YOU
+The authentication is successful. The client proceeds with the cvs
+protocol itself.
+
+@item I HATE YOU
+The authentication fails. After sending this response, the server may
+close the connection. It is up to the server to decide whether to give
+this response, which is generic, or a more specific response using
+@samp{E} and/or @samp{error}.
+
+@item E @var{text}
+Provide a message for the user. After this reponse, the authentication
+protocol continues with another response. Typically the server will
+provide a series of @samp{E} responses followed by @samp{error}.
+Compatibility note: @sc{cvs} 1.9.10 and older clients will print
+@code{unrecognized auth response} and @var{text}, and then exit, upon
+receiving this response.
+
+@item error @var{code} @var{text}
+The authentication fails. After sending this response, the server may
+close the connection. The @var{code} is a code describing why it
+failed, intended for computer consumption. The only code currently
+defined is @samp{0} which is nonspecific, but clients must silently
+treat any unrecognized codes as nonspecific.
+The @var{text} should be supplied to the
+user. Compatibility note: @sc{cvs} 1.9.10 and older clients will print
+@code{unrecognized auth response} and @var{text}, and then exit, upon
+receiving this response.
+Note that @var{text} for this response, or the @var{text} in an @code{E}
+response, is not designed for machine parsing. More vigorous use of
+@var{code}, or future extensions, will be needed to prove a cleaner
+machine-parseable indication of what the error was.
+@end table
+
+@c If you are thinking of putting samp or code around BEGIN AUTH REQUEST
+@c and friends, watch for overfull hboxes.
+If the client wishes to merely authenticate without starting the cvs
+protocol, the procedure is the same, except BEGIN AUTH REQUEST is
+replaced with BEGIN VERIFICATION REQUEST, END AUTH REQUEST
+is replaced with END VERIFICATION REQUEST, and upon receipt of
+I LOVE YOU the connection is closed rather than continuing.
+
+Another mechanism is GSSAPI authentication. GSSAPI is a
+generic interface to security services such as kerberos. GSSAPI is
+specified in RFC2078 (GSSAPI version 2) and RFC1508 (GSSAPI version 1);
+we are not aware of differences between the two which affect the
+protocol in incompatible ways, so we make no attempt to specify one
+version or the other.
+The procedure here is to start with @samp{BEGIN
+GSSAPI REQUEST}. GSSAPI authentication information is then exchanged
+between the client and the server. Each packet of information consists
+of a two byte big endian length, followed by that many bytes of data.
+After the GSSAPI authentication is complete, the server continues with
+the responses described above (@samp{I LOVE YOU}, etc.).
+
+@item future possibilities
+There are a nearly unlimited number of ways to connect and authenticate.
+One might want to allow access based on IP address (similar to the usual
+rsh protocol but with different/no restrictions on ports < 1024), to
+adopt mechanisms such as Pluggable Authentication Modules (PAM), to
+allow users to run their own servers under their own usernames without
+root access, or any number of other possibilities. The way to add
+future mechanisms, for the most part, should be to continue to use port
+2401, but to use different strings in place of @samp{BEGIN AUTH
+REQUEST}.
+@end table
+
+@node Password scrambling
+@chapter Password scrambling algorithm
+
+The pserver authentication protocol, as described in @ref{Connection and
+Authentication}, trivially encodes the passwords. This is only to
+prevent inadvertent compromise; it provides no protection against even a
+relatively unsophisticated attacker. For comparison, HTTP Basic
+Authentication (as described in RFC2068) uses BASE64 for a similar
+purpose. CVS uses its own algorithm, described here.
+
+The scrambled password starts with @samp{A}, which serves to identify
+the scrambling algorithm in use. After that follows a single octet for
+each character in the password, according to a fixed encoding. The
+values are shown here, with the encoded values in decimal. Control
+characters, space, and characters outside the invariant ISO 646
+character set are not shown; such characters are not recommended for use
+in passwords. There is a long discussion of character set issues in
+@ref{Protocol Notes}.
+
+@example
+ 0 111 P 125 p 58
+! 120 1 52 A 57 Q 55 a 121 q 113
+" 53 2 75 B 83 R 54 b 117 r 32
+ 3 119 C 43 S 66 c 104 s 90
+ 4 49 D 46 T 124 d 101 t 44
+% 109 5 34 E 102 U 126 e 100 u 98
+& 72 6 82 F 40 V 59 f 69 v 60
+' 108 7 81 G 89 W 47 g 73 w 51
+( 70 8 95 H 38 X 92 h 99 x 33
+) 64 9 65 I 103 Y 71 i 63 y 97
+* 76 : 112 J 45 Z 115 j 94 z 62
++ 67 ; 86 K 50 k 93
+, 116 < 118 L 42 l 39
+- 74 = 110 M 123 m 37
+. 68 > 122 N 91 n 61
+/ 87 ? 105 O 35 _ 56 o 48
+@end example
+
+@node Protocol
+@chapter The CVS client/server protocol
+
+In the following, @samp{\n} refers to a linefeed and @samp{\t} refers to
+a horizontal tab; @dfn{requests} are what the client sends and
+@dfn{responses} are what the server sends. In general, the connection is
+governed by the client---the server does not send responses without
+first receiving requests to do so; see @ref{Response intro} for more
+details of this convention.
+
+It is typical, early in the connection, for the client to transmit a
+@code{Valid-responses} request, containing all the responses it
+supports, followed by a @code{valid-requests} request, which elicits
+from the server a @code{Valid-requests} response containing all the
+requests it understands. In this way, the client and server each find
+out what the other supports before exchanging large amounts of data
+(such as file contents).
+
+@c Hmm, having 3 sections in this menu makes a certain amount of sense
+@c but that structure gets lost in the printed manual (not sure about
+@c HTML). Perhaps there is a better way.
+@menu
+
+General protocol conventions:
+
+* Entries Lines:: Transmitting RCS data
+* File Modes:: Read, write, execute, and possibly more...
+* Filenames:: Conventions regarding filenames
+* File transmissions:: How file contents are transmitted
+* Strings:: Strings in various requests and responses
+* Dates:: Times and dates
+
+The protocol itself:
+
+* Request intro:: General conventions relating to requests
+* Requests:: List of requests
+* Response intro:: General conventions relating to responses
+* Response pathnames:: The "pathname" in responses
+* Responses:: List of responses
+* Text tags:: More details about the MT response
+
+An example session, and some further observations:
+
+* Example:: A conversation between client and server
+* Requirements:: Things not to omit from an implementation
+* Obsolete:: Former protocol features
+@end menu
+
+@node Entries Lines
+@section Entries Lines
+
+Entries lines are transmitted as:
+
+@example
+/ @var{name} / @var{version} / @var{conflict} / @var{options} / @var{tag_or_date}
+@end example
+
+@var{tag_or_date} is either @samp{T} @var{tag} or @samp{D} @var{date}
+or empty. If it is followed by a slash, anything after the slash
+shall be silently ignored.
+
+@var{version} can be empty, or start with @samp{0} or @samp{-}, for no
+user file, new user file, or user file to be removed, respectively.
+
+@c FIXME: should distinguish sender and receiver behavior here; the
+@c "anything else" and "does not start with" are intended for future
+@c expansion, and we should specify a sender behavior.
+@var{conflict}, if it starts with @samp{+}, indicates that the file had
+conflicts in it. The rest of @var{conflict} is @samp{=} if the
+timestamp matches the file, or anything else if it doesn't. If
+@var{conflict} does not start with a @samp{+}, it is silently ignored.
+
+@var{options} signifies the keyword expansion options (for example
+@samp{-ko}). In an @code{Entry} request, this indicates the options
+that were specified with the file from the previous file updating
+response (@pxref{Response intro}, for a list of file updating
+responses); if the client is specifying the @samp{-k} or @samp{-A}
+option to @code{update}, then it is the server which figures out what
+overrides what.
+
+@node File Modes
+@section File Modes
+
+A mode is any number of repetitions of
+
+@example
+@var{mode-type} = @var{data}
+@end example
+
+separated by @samp{,}.
+
+@var{mode-type} is an identifier composed of alphanumeric characters.
+Currently specified: @samp{u} for user, @samp{g} for group, @samp{o}
+for other (see below for discussion of whether these have their POSIX
+meaning or are more loose). Unrecognized values of @var{mode-type}
+are silently ignored.
+
+@var{data} consists of any data not containing @samp{,}, @samp{\0} or
+@samp{\n}. For @samp{u}, @samp{g}, and @samp{o} mode types, data
+consists of alphanumeric characters, where @samp{r} means read, @samp{w}
+means write, @samp{x} means execute, and unrecognized letters are
+silently ignored.
+
+The two most obvious ways in which the mode matters are: (1) is it
+writeable? This is used by the developer communication features, and
+is implemented even on OS/2 (and could be implemented on DOS), whose
+notion of mode is limited to a readonly bit. (2) is it executable?
+Unix CVS users need CVS to store this setting (for shell scripts and
+the like). The current CVS implementation on unix does a little bit
+more than just maintain these two settings, but it doesn't really have
+a nice general facility to store or version control the mode, even on
+unix, much less across operating systems with diverse protection
+features. So all the ins and outs of what the mode means across
+operating systems haven't really been worked out (e.g. should the VMS
+port use ACLs to get POSIX semantics for groups?).
+
+@node Filenames
+@section Conventions regarding transmission of file names
+
+In most contexts, @samp{/} is used to separate directory and file
+names in filenames, and any use of other conventions (for example,
+that the user might type on the command line) is converted to that
+form. The only exceptions might be a few cases in which the server
+provides a magic cookie which the client then repeats verbatim, but as
+the server has not yet been ported beyond unix, the two rules provide
+the same answer (and what to do if future server ports are operating
+on a repository like e:/foo or CVS_ROOT:[FOO.BAR] has not been
+carefully thought out).
+
+Characters outside the invariant ISO 646 character set should be avoided
+in filenames. This restriction may need to be relaxed to allow for
+characters such as @samp{[} and @samp{]} (see above about non-unix
+servers); this has not been carefully considered (and currently
+implementations probably use whatever character sets that the operating
+systems they are running on allow, and/or that users specify). Of
+course the most portable practice is to restrict oneself further, to the
+POSIX portable filename character set as specified in POSIX.1.
+
+@node File transmissions
+@section File transmissions
+
+File contents (noted below as @var{file transmission}) can be sent in
+one of two forms. The simpler form is a number of bytes, followed by a
+linefeed, followed by the specified number of bytes of file contents.
+These are the entire contents of the specified file. Second, if both
+client and server support @samp{gzip-file-contents}, a @samp{z} may
+precede the length, and the `file contents' sent are actually compressed
+with @samp{gzip} (RFC1952/1951) compression. The length specified is
+that of the compressed version of the file.
+
+In neither case are the file content followed by any additional data.
+The transmission of a file will end with a linefeed iff that file (or its
+compressed form) ends with a linefeed.
+
+The encoding of file contents depends on the value for the @samp{-k}
+option. If the file is binary (as specified by the @samp{-kb} option in
+the appropriate place), then it is just a certain number of octets, and
+the protocol contributes nothing towards determining the encoding (using
+the file name is one widespread, if not universally popular, mechanism).
+If the file is text (not binary), then the file is sent as a series of
+lines, separated by linefeeds. If the keyword expansion is set to
+something other than @samp{-ko}, then it is expected that the file
+conform to the RCS expectations regarding keyword expansion---in
+particular, that it is in a character set such as ASCII in which 0x24 is
+a dollar sign (@samp{$}).
+
+@node Strings
+@section Strings
+
+In various contexts, for example the @code{Argument} request and the
+@code{M} response, one transmits what is essentially an arbitrary
+string. Often this will have been supplied by the user (for example,
+the @samp{-m} option to the @code{ci} request). The protocol has no
+mechanism to specify the character set of such strings; it would be
+fairly safe to stick to the invariant ISO 646 character set but the
+existing practice is probably to just transmit whatever the user
+specifies, and hope that everyone involved agrees which character set is
+in use, or sticks to a common subset.
+
+@node Dates
+@section Dates
+
+The protocol contains times and dates in various places.
+
+For the @samp{-D} option to the @code{annotate}, @code{co}, @code{diff},
+@code{export}, @code{history}, @code{rdiff}, @code{rtag}, @code{tag},
+and @code{update} requests, the server should support two formats:
+
+@example
+26 May 1997 13:01:40 -0000 ; @r{RFC 822 as modified by RFC 1123}
+5/26/1997 13:01:40 GMT ; @r{traditional}
+@end example
+
+The former format is preferred; the latter however is sent by the CVS
+command line client (versions 1.5 through at least 1.9).
+
+For the @samp{-d} option to the @code{log} request, servers should at
+least support RFC 822/1123 format. Clients are encouraged to use this
+format too (the command line CVS client, version 1.10 and older, just passed
+along the date format specified by the user, however).
+
+The @code{Mod-time} response and @code{Checkin-time} request use RFC
+822/1123 format (see the descriptions of that response and request for
+details).
+
+For @code{Notify}, see the description of that request.
+
+@node Request intro
+@section Request intro
+
+By convention, requests which begin with a capital letter do not elicit
+a response from the server, while all others do -- save one. The
+exception is @samp{gzip-file-contents}. Unrecognized requests will
+always elicit a response from the server, even if that request begins
+with a capital letter.
+
+The term @dfn{command} means a request which expects a response (except
+@code{valid-requests}). The general model is that the client transmits
+a great number of requests, but nothing happens until the very end when
+the client transmits a command. Although the intention is that
+transmitting several commands in one connection should be legal,
+existing servers probably have some bugs with some combinations of more
+than one command, and so clients may find it necessary to make several
+connections in some cases. This should be thought of as a workaround
+rather than a desired attribute of the protocol.
+
+@node Requests
+@section Requests
+
+Here are the requests:
+
+@table @code
+@item Root @var{pathname} \n
+Response expected: no. Tell the server which @code{CVSROOT} to use.
+Note that @var{pathname} is a local directory and @emph{not} a fully
+qualified @code{CVSROOT} variable. @var{pathname} must
+already exist; if creating a new root, use the @code{init} request, not
+@code{Root}. @var{pathname} does not include the hostname of the
+server, how to access the server, etc.; by the time the CVS protocol is
+in use, connection, authentication, etc., are already taken care of.
+
+The @code{Root} request must be sent only once, and it must be sent
+before any requests other than @code{Valid-responses},
+@code{valid-requests}, @code{UseUnchanged}, @code{Set} or @code{init}.
+
+@item Valid-responses @var{request-list} \n
+Response expected: no.
+Tell the server what responses the client will accept.
+request-list is a space separated list of tokens.
+
+@item valid-requests \n
+Response expected: yes.
+Ask the server to send back a @code{Valid-requests} response.
+
+@item Directory @var{local-directory} \n
+Additional data: @var{repository} \n. Response expected: no.
+Tell the server what directory to use. The @var{repository} should be a
+directory name from a previous server response. Note that
+this both gives a default for @code{Entry} and @code{Modified} and
+also for @code{ci} and the other commands; normal usage is to send
+@code{Directory} for each directory in which there will be an
+@code{Entry} or @code{Modified}, and then a final @code{Directory}
+for the original directory, then the command.
+The @var{local-directory} is relative to
+the top level at which the command is occurring (i.e. the last
+@code{Directory} which is sent before the command);
+to indicate that top level, @samp{.} should be sent for
+@var{local-directory}.
+
+Here is an example of where a client gets @var{repository} and
+@var{local-directory}. Suppose that there is a module defined by
+
+@example
+moddir 1dir
+@end example
+
+That is, one can check out @code{moddir} and it will take @code{1dir} in
+the repository and check it out to @code{moddir} in the working
+directory. Then an initial check out could proceed like this:
+
+@example
+C: Root /home/kingdon/zwork/cvsroot
+. . .
+C: Argument moddir
+C: Directory .
+C: /home/kingdon/zwork/cvsroot
+C: co
+S: Clear-sticky moddir/
+S: /home/kingdon/zwork/cvsroot/1dir/
+. . .
+S: ok
+@end example
+
+In this example the response shown is @code{Clear-sticky}, but it could
+be another response instead. Note that it returns two pathnames.
+The first one, @file{moddir/}, indicates the working
+directory to check out into. The second one, ending in @file{1dir/},
+indicates the directory to pass back to the server in a subsequent
+@code{Directory} request. For example, a subsequent @code{update}
+request might look like:
+
+@example
+C: Directory moddir
+C: /home/kingdon/zwork/cvsroot/1dir
+. . .
+C: update
+@end example
+
+For a given @var{local-directory}, the repository will be the same for
+each of the responses, so one can use the repository from whichever
+response is most convenient. Typically a client will store the
+repository along with the sources for each @var{local-directory}, use
+that same setting whenever operating on that @var{local-directory}, and
+not update the setting as long as the @var{local-directory} exists.
+
+A client is free to rename a @var{local-directory} at any time (for
+example, in response to an explicit user request). While it is true
+that the server supplies a @var{local-directory} to the client, as noted
+above, this is only the default place to put the directory. Of course,
+the various @code{Directory} requests for a single command (for example,
+@code{update} or @code{ci} request) should name a particular directory
+with the same @var{local-directory}.
+
+Each @code{Directory} request specifies a brand-new
+@var{local-directory} and @var{repository}; that is,
+@var{local-directory} and @var{repository} are never relative to paths
+specified in any previous @code{Directory} request.
+
+Here's a more complex example, in which we request an update of a
+working directory which has been checked out from multiple places in the
+repository.
+
+@example
+C: Argument dir1
+C: Directory dir1
+C: /home/foo/repos/mod1
+. . .
+C: Argument dir2
+C: Directory dir2
+C: /home/foo/repos/mod2
+. . .
+C: Argument dir3
+C: Directory dir3/subdir3
+C: /home/foo/repos/mod3
+. . .
+C: update
+@end example
+
+While directories @code{dir1} and @code{dir2} will be handled in similar
+fashion to the other examples given above, @code{dir3} is slightly
+different from the server's standpoint. Notice that module @code{mod3}
+is actually checked out into @code{dir3/subdir3}, meaning that directory
+@code{dir3} is either empty or does not contain data checked out from
+this repository.
+
+The above example will work correctly in @sc{cvs} 1.10.1 and later. The
+server will descend the tree starting from all directories mentioned in
+@code{Argument} requests and update those directories specifically
+mentioned in @code{Directory} requests.
+
+Previous versions of @sc{cvs} (1.10 and earlier) do not behave the same
+way. While the descent of the tree begins at all directories mentioned
+in @code{Argument} requests, descent into subdirectories only occurs if
+a directory has been mentioned in a @code{Directory} request.
+Therefore, the above example would succeed in updating @code{dir1} and
+@code{dir2}, but would skip @code{dir3} because that directory was not
+specifically mentioned in a @code{Directory} request. A functional
+version of the above that would run on a 1.10 or earlier server is as
+follows:
+
+@example
+C: Argument dir1
+C: Directory dir1
+C: /home/foo/repos/mod1
+. . .
+C: Argument dir2
+C: Directory dir2
+C: /home/foo/repos/mod2
+. . .
+C: Argument dir3
+C: Directory dir3
+C: /home/foo/repos/.
+. . .
+C: Directory dir3/subdir3
+C: /home/foo/repos/mod3
+. . .
+C: update
+@end example
+
+Note the extra @code{Directory dir3} request. It might be better to use
+@code{Emptydir} as the repository for the @code{dir3} directory, but the
+above will certainly work.
+
+One more peculiarity of the 1.10 and earlier protocol is the ordering of
+@code{Directory} arguments. In order for a subdirectory to be
+registered correctly for descent by the recursion processor, its parent
+must be sent first. For example, the following would not work to update
+@code{dir3/subdir3}:
+
+@example
+. . .
+C: Argument dir3
+C: Directory dir3/subdir3
+C: /home/foo/repos/mod3
+. . .
+C: Directory dir3
+C: /home/foo/repos/.
+. . .
+C: update
+@end example
+
+The implementation of the server in 1.10 and earlier writes the
+administration files for a given directory at the time of the
+@code{Directory} request. It also tries to register the directory with
+its parent to mark it for recursion. In the above example, at the time
+@code{dir3/subdir3} is created, the physical directory for @code{dir3}
+will be created on disk, but the administration files will not have been
+created. Therefore, when the server tries to register
+@code{dir3/subdir3} for recursion, the operation will silently fail
+because the administration files do not yet exist for @code{dir3}.
+
+@item Max-dotdot @var{level} \n
+Response expected: no.
+Tell the server that @var{level} levels of directories above the
+directory which @code{Directory} requests are relative to will be
+needed. For example, if the client is planning to use a
+@code{Directory} request for @file{../../foo}, it must send a
+@code{Max-dotdot} request with a @var{level} of at least 2.
+@code{Max-dotdot} must be sent before the first @code{Directory}
+request.
+
+@item Static-directory \n
+Response expected: no. Tell the server that the directory most recently
+specified with @code{Directory} should not have
+additional files checked out unless explicitly requested. The client
+sends this if the @code{Entries.Static} flag is set, which is controlled
+by the @code{Set-static-directory} and @code{Clear-static-directory}
+responses.
+
+@item Sticky @var{tagspec} \n
+Response expected: no. Tell the server that the directory most recently
+specified with @code{Directory} has a sticky tag or date @var{tagspec}.
+The first character of @var{tagspec} is @samp{T} for a tag, @samp{D}
+for a date, or some other character supplied by a Set-sticky response
+from a previous request to the server. The remainder of @var{tagspec}
+contains the actual tag or date, again as supplied by Set-sticky.
+
+The server should remember @code{Static-directory} and @code{Sticky}
+requests for a particular directory; the client need not resend them
+each time it sends a @code{Directory} request for a given directory.
+However, the server is not obliged to remember them beyond the context
+of a single command.
+
+@item Checkin-prog @var{program} \n
+Response expected: no. Tell the server that the directory most recently
+specified with @code{Directory} has a checkin program @var{program}.
+Such a program would have been previously set with the
+@code{Set-checkin-prog} response.
+
+@item Update-prog @var{program} \n
+Response expected: no. Tell the server that the directory most recently
+specified with @code{Directory} has an update program @var{program}.
+Such a program would have been previously set with the
+@code{Set-update-prog} response.
+
+@item Entry @var{entry-line} \n
+Response expected: no. Tell the server what version of a file is on the
+local machine. The name in @var{entry-line} is a name relative to the
+directory most recently specified with @code{Directory}. If the user
+is operating on only some files in a directory, @code{Entry} requests
+for only those files need be included. If an @code{Entry} request is
+sent without @code{Modified}, @code{Is-modified}, or @code{Unchanged},
+it means the file is
+lost (does not exist in the working directory). If both @code{Entry}
+and one of @code{Modified}, @code{Is-modified}, or @code{Unchanged} are
+sent for the same file, @code{Entry} must be sent first. For a
+given file, one can send @code{Modified}, @code{Is-modified}, or
+@code{Unchanged}, but not more than one of these three.
+
+@item Kopt @var{option} \n
+This indicates to the server which keyword expansion options to use for
+the file specified by the next @code{Modified} or @code{Is-modified}
+request (for example @samp{-kb} for a binary file). This is similar to
+@code{Entry}, but is used for a file for which there is no entries line.
+Typically this will be a file being added via an @code{add} or
+@code{import} request. The client may not send both @code{Kopt} and
+@code{Entry} for the same file.
+
+@item Checkin-time @var{time} \n
+For the file specified by the next @code{Modified} request, use
+@var{time} as the time of the checkin. The @var{time} is in the format
+specified by RFC822 as modified by RFC1123. The client may specify any
+timezone it chooses; servers will want to convert that to their own
+timezone as appropriate. An example of this format is:
+
+@example
+26 May 1997 13:01:40 -0400
+@end example
+
+There is no requirement that the client and server clocks be
+synchronized. The client just sends its recommendation for a timestamp
+(based on file timestamps or whatever), and the server should just believe
+it (this means that the time might be in the future, for example).
+
+Note that this is not a general-purpose way to tell the server about the
+timestamp of a file; that would be a separate request (if there are
+servers which can maintain timestamp and time of checkin separately).
+
+This request should affect the @code{import} request, and may optionally
+affect the @code{ci} request or other relevant requests if any.
+
+@item Modified @var{filename} \n
+Response expected: no. Additional data: mode, \n, file transmission.
+Send the server a copy of one locally modified file. @var{filename} is
+a file within the most recent directory sent with @code{Directory}; it
+must not contain @samp{/}. If
+the user is operating on only some files in a directory, only those
+files need to be included. This can also be sent without @code{Entry},
+if there is no entry for the file.
+
+@item Is-modified @var{filename} \n
+Response expected: no. Additional data: none. Like @code{Modified},
+but used if the server only needs
+to know whether the file is modified, not the contents.
+
+The commands which can take @code{Is-modified} instead of
+@code{Modified} with no known change in behavior are: @code{admin},
+@code{diff} (if and only if two @samp{-r} or @samp{-D} options are
+specified), @code{watch-on}, @code{watch-off}, @code{watch-add},
+@code{watch-remove}, @code{watchers}, @code{editors},
+@code{log}, and @code{annotate}.
+
+For the @code{status} command, one can send @code{Is-modified} but if
+the client is using imperfect mechanisms such as timestamps to determine
+whether to consider a file modified, then the behavior will be
+different. That is, if one sends @code{Modified}, then the server will
+actually compare the contents of the file sent and the one it derives
+from to determine whether the file is genuinely modified. But if one
+sends @code{Is-modified}, then the server takes the client's word for
+it. A similar situation exists for @code{tag}, if the @samp{-c} option
+is specified.
+
+Commands for which @code{Modified} is necessary are @code{co},
+@code{ci}, @code{update}, and @code{import}.
+
+Commands which do not need to inform the server about a working
+directory, and thus should not be sending either @code{Modified} or
+@code{Is-modified}: @code{rdiff}, @code{rtag}, @code{history},
+@code{init}, and @code{release}.
+
+Commands for which further investigation is warranted are:
+@code{remove}, @code{add}, and @code{export}. Pending such
+investigation, the more conservative course of action is to stick to
+@code{Modified}.
+
+@item Unchanged @var{filename} \n
+Response expected: no. Tell the server that @var{filename} has not been
+modified in the checked out directory. The @var{filename} is
+a file within the most recent directory sent with @code{Directory}; it
+must not contain @samp{/}.
+
+@item UseUnchanged \n
+Response expected: no. To specify the version of the protocol described
+in this document, servers must support this request (although it need
+not do anything) and clients must issue it.
+
+@item Notify @var{filename} \n
+Response expected: no.
+Tell the server that an @code{edit} or @code{unedit} command has taken
+place. The server needs to send a @code{Notified} response, but such
+response is deferred until the next time that the server is sending
+responses.
+The @var{filename} is a file within the most recent directory sent with
+@code{Directory}; it must not contain @samp{/}.
+Additional data:
+@example
+@var{notification-type} \t @var{time} \t @var{clienthost} \t
+@var{working-dir} \t @var{watches} \n
+@end example
+where @var{notification-type} is @samp{E} for edit, @samp{U} for
+unedit, undefined behavior if @samp{C}, and all other letters should be
+silently ignored for future expansion.
+@var{time} is the time at which the edit or unedit took place, in a
+user-readable format of the client's choice (the server should treat the
+time as an opaque string rather than interpreting it).
+@c Might be useful to specify a format, but I don't know if we want to
+@c specify the status quo (ISO C asctime() format plus timezone) without
+@c offering the option of ISO8601 and/or RFC822/1123 (see cvs.texinfo
+@c for much much more on date formats).
+@var{clienthost} is the name of the host on which the edit or unedit
+took place, and @var{working-dir} is the pathname of the working
+directory where the edit or unedit took place. @var{watches} are the
+temporary watches, zero or more of the following characters in the
+following order: @samp{E} for edit, @samp{U} for unedit, @samp{C} for
+commit, and all other letters should be silently ignored for future
+expansion. If @var{notification-type} is @samp{E} the temporary watches
+are set; if it is @samp{U} they are cleared.
+If @var{watches} is followed by \t then the
+\t and the rest of the line should be ignored, for future expansion.
+
+The @var{time}, @var{clienthost}, and @var{working-dir} fields may not
+contain the characters @samp{+}, @samp{,}, @samp{>}, @samp{;}, or @samp{=}.
+
+Note that a client may be capable of performing an @code{edit} or
+@code{unedit} operation without connecting to the server at that time,
+and instead connecting to the server when it is convenient (for example,
+when a laptop is on the net again) to send the @code{Notify} requests.
+Even if a client is capable of deferring notifications, it should
+attempt to send them immediately (one can send @code{Notify} requests
+together with a @code{noop} request, for example), unless perhaps if
+it can know that a connection would be impossible.
+
+@item Questionable @var{filename} \n
+Response expected: no. Additional data: no. Tell the server to check
+whether @var{filename} should be ignored, and if not, next time the
+server sends responses, send (in a @code{M} response) @samp{?} followed
+by the directory and filename. @var{filename} must not contain
+@samp{/}; it needs to be a file in the directory named by the most
+recent @code{Directory} request.
+@c FIXME: the bit about not containing / is true of most of the
+@c requests, but isn't documented and should be.
+
+@item Case \n
+Response expected: no. Tell the server that filenames should be matched
+in a case-insensitive fashion. Note that this is not the primary
+mechanism for achieving case-insensitivity; for the most part the client
+keeps track of the case which the server wants to use and takes care to
+always use that case regardless of what the user specifies. For example
+the filenames given in @code{Entry} and @code{Modified} requests for the
+same file must match in case regardless of whether the @code{Case}
+request is sent. The latter mechanism is more general (it could also be
+used for 8.3 filenames, VMS filenames with more than one @samp{.}, and
+any other situation in which there is a predictable mapping between
+filenames in the working directory and filenames in the protocol), but
+there are some situations it cannot handle (ignore patterns, or
+situations where the user specifies a filename and the client does not
+know about that file).
+
+@item Argument @var{text} \n
+Response expected: no.
+Save argument for use in a subsequent command. Arguments
+accumulate until an argument-using command is given, at which point
+they are forgotten.
+
+@item Argumentx @var{text} \n
+Response expected: no. Append \n followed by text to the current
+argument being saved.
+
+@item Global_option @var{option} \n
+Response expected: no.
+Transmit one of the global options @samp{-q}, @samp{-Q}, @samp{-l},
+@samp{-t}, @samp{-r}, or @samp{-n}. @var{option} must be one of those
+strings, no variations (such as combining of options) are allowed. For
+graceful handling of @code{valid-requests}, it is probably better to
+make new global options separate requests, rather than trying to add
+them to this request.
+
+@item Gzip-stream @var{level} \n
+Response expected: no.
+Use zlib (RFC 1950/1951) compression to compress all further communication
+between the client and the server. After this request is sent, all
+further communication must be compressed. All further data received
+from the server will also be compressed. The @var{level} argument
+suggests to the server the level of compression that it should apply; it
+should be an integer between 1 and 9, inclusive, where a higher number
+indicates more compression.
+
+@item Kerberos-encrypt \n
+Response expected: no.
+Use Kerberos encryption to encrypt all further communication between the
+client and the server. This will only work if the connection was made
+over Kerberos in the first place. If both the @code{Gzip-stream} and
+the @code{Kerberos-encrypt} requests are used, the
+@code{Kerberos-encrypt} request should be used first. This will make
+the client and server encrypt the compressed data, as opposed to
+compressing the encrypted data. Encrypted data is generally
+incompressible.
+
+Note that this request does not fully prevent an attacker from hijacking
+the connection, in the sense that it does not prevent hijacking the
+connection between the initial authentication and the
+@code{Kerberos-encrypt} request.
+
+@item Gssapi-encrypt \n
+Response expected: no.
+Use GSSAPI encryption to encrypt all further communication between the
+client and the server. This will only work if the connection was made
+over GSSAPI in the first place. See @code{Kerberos-encrypt}, above, for
+the relation between @code{Gssapi-encrypt} and @code{Gzip-stream}.
+
+Note that this request does not fully prevent an attacker from hijacking
+the connection, in the sense that it does not prevent hijacking the
+connection between the initial authentication and the
+@code{Gssapi-encrypt} request.
+
+@item Gssapi-authenticate \n
+Response expected: no.
+Use GSSAPI authentication to authenticate all further communication
+between the client and the server. This will only work if the
+connection was made over GSSAPI in the first place. Encrypted data is
+automatically authenticated, so using both @code{Gssapi-authenticate}
+and @code{Gssapi-encrypt} has no effect beyond that of
+@code{Gssapi-encrypt}. Unlike encrypted data, it is reasonable to
+compress authenticated data.
+
+Note that this request does not fully prevent an attacker from hijacking
+the connection, in the sense that it does not prevent hijacking the
+connection between the initial authentication and the
+@code{Gssapi-authenticate} request.
+
+@item Set @var{variable}=@var{value} \n
+Response expected: no.
+Set a user variable @var{variable} to @var{value}.
+
+@item expand-modules \n
+Response expected: yes. Expand the modules which are specified in the
+arguments. Returns the data in @code{Module-expansion} responses. Note
+that the server can assume that this is checkout or export, not rtag or
+rdiff; the latter do not access the working directory and thus have no
+need to expand modules on the client side.
+
+Expand may not be the best word for what this request does. It does not
+necessarily tell you all the files contained in a module, for example.
+Basically it is a way of telling you which working directories the
+server needs to know about in order to handle a checkout of the
+specified modules.
+
+For example, suppose that the server has a module defined by
+
+@example
+aliasmodule -a 1dir
+@end example
+
+That is, one can check out @code{aliasmodule} and it will take
+@code{1dir} in the repository and check it out to @code{1dir} in the
+working directory. Now suppose the client already has this module
+checked out and is planning on using the @code{co} request to update it.
+Without using @code{expand-modules}, the client would have two bad
+choices: it could either send information about @emph{all} working
+directories under the current directory, which could be unnecessarily
+slow, or it could be ignorant of the fact that @code{aliasmodule} stands
+for @code{1dir}, and neglect to send information for @code{1dir}, which
+would lead to incorrect operation.
+@c Those don't really seem like the only two options. I mean, what
+@c about keeping track of the correspondence from when we first checked
+@c out a fresh directory? Not that the CVS client does this, or that
+@c I've really thought about whether it would be a good idea...
+
+With @code{expand-modules}, the client would first ask for the module to
+be expanded:
+
+@example
+C: Root /home/kingdon/zwork/cvsroot
+. . .
+C: Argument aliasmodule
+C: Directory .
+C: /home/kingdon/zwork/cvsroot
+C: expand-modules
+S: Module-expansion 1dir
+S: ok
+@end example
+
+and then it knows to check the @file{1dir} directory and send
+requests such as @code{Entry} and @code{Modified} for the files in that
+directory.
+
+@item ci \n
+@itemx diff \n
+@itemx tag \n
+@itemx status \n
+@itemx admin \n
+@itemx history \n
+@itemx watchers \n
+@itemx editors \n
+@itemx annotate \n
+Response expected: yes. Actually do a cvs command. This uses any
+previous @code{Argument}, @code{Directory}, @code{Entry}, or
+@code{Modified} requests, if they have been sent. The
+last @code{Directory} sent specifies the working directory at the time
+of the operation. No provision is made for any input from the user.
+This means that @code{ci} must use a @code{-m} argument if it wants to
+specify a log message.
+
+@item log \n
+Response expected: yes. Show information for past revisions. This uses
+any previous @code{Directory}, @code{Entry}, or @code{Modified}
+requests, if they have been sent. The last @code{Directory} sent
+specifies the working directory at the time of the operation. Also uses
+previous @code{Argument}'s of which the canonical forms are the
+following (@sc{cvs} 1.10 and older clients sent what the user specified,
+but clients are encouraged to use the canonical forms and other forms
+are deprecated):
+
+@table @code
+@item -b, -h, -l, -N, -R, -t
+These options go by themselves, one option per @code{Argument} request.
+
+@item -d @var{date1}<@var{date2}
+Select revisions between @var{date1} and @var{date2}. Either date
+may be omitted in which case there is no date limit at that end of the
+range (clients may specify dates such as 1 Jan 1970 or 1 Jan 2038 for
+similar purposes but this is problematic as it makes assumptions about
+what dates the server supports). Dates are in RFC822/1123 format. The
+@samp{-d} is one @code{Argument} request and the date range is a second
+one.
+
+@item -d @var{date1}<=@var{date2}
+Likewise but compare dates for equality.
+
+@item -d @var{singledate}
+Select the single, latest revision dated @var{singledate} or earlier.
+
+To include several date ranges and/or singledates, repeat the @samp{-d}
+option as many times as necessary.
+
+@item -r@var{rev1}:@var{rev2}
+@itemx -r@var{branch}
+@itemx -r@var{branch}.
+@itemx -r
+Specify revisions (note that @var{rev1} or @var{rev2} can be omitted, or
+can refer to branches). Send both the @samp{-r} and the revision
+information in a single @code{Argument} request. To include several
+revision selections, repeat the @samp{-r} option.
+
+@item -s @var{state}
+@itemx -w
+@itemx -w@var{login}
+Select on states or users. To include more than one state or user,
+repeat the option. Send the @samp{-s} option as a separate argument
+from the state being selected. Send the @samp{-w} option as part of the
+same argument as the user being selected.
+@end table
+
+@item co \n
+Response expected: yes. Get files from the repository. This uses any
+previous @code{Argument}, @code{Directory}, @code{Entry}, or
+@code{Modified} requests, if they have been sent. Arguments to this
+command are module names; the client cannot know what directories they
+correspond to except by (1) just sending the @code{co} request, and then
+seeing what directory names the server sends back in its responses, and
+(2) the @code{expand-modules} request.
+
+@item export \n
+Response expected: yes. Get files from the repository. This uses any
+previous @code{Argument}, @code{Directory}, @code{Entry}, or
+@code{Modified} requests, if they have been sent. Arguments to this
+command are module names, as described for the @code{co} request. The
+intention behind this command is that a client can get sources from a
+server without storing CVS information about those sources. That is, a
+client probably should not count on being able to take the entries line
+returned in the @code{Created} response from an @code{export} request
+and send it in a future @code{Entry} request. Note that the entries
+line in the @code{Created} response must indicate whether the file is
+binary or text, so the client can create it correctly.
+
+@item rdiff \n
+@itemx rtag \n
+Response expected: yes. Actually do a cvs command. This uses any
+previous @code{Argument} requests, if they have been sent. The client
+should not send @code{Directory}, @code{Entry}, or @code{Modified}
+requests for this command; they are not used. Arguments to these
+commands are module names, as described for @code{co}.
+
+@item init @var{root-name} \n
+Response expected: yes. If it doesn't already exist, create a @sc{cvs}
+repository @var{root-name}. Note that @var{root-name} is a local
+directory and @emph{not} a fully qualified @code{CVSROOT} variable. The
+@code{Root} request need not have been previously sent.
+
+@item update \n
+Response expected: yes. Actually do a @code{cvs update} command. This
+uses any previous @code{Argument}, @code{Directory}, @code{Entry},
+or @code{Modified} requests, if they have been sent. The
+last @code{Directory} sent specifies the working directory at the time
+of the operation. The @code{-I} option is not used--files which the
+client can decide whether to ignore are not mentioned and the client
+sends the @code{Questionable} request for others.
+
+@item import \n
+Response expected: yes. Actually do a @code{cvs import} command. This
+uses any previous @code{Argument}, @code{Directory}, @code{Entry}, or
+@code{Modified} requests, if they have been sent. The
+last @code{Directory} sent specifies the working directory at the time
+of the operation - unlike most commands, the repository field of each
+@code{Directory} request is ignored (it merely must point somewhere
+within the root). The files to be imported are sent in @code{Modified}
+requests (files which the client knows should be ignored are not sent;
+the server must still process the CVSROOT/cvsignore file unless -I ! is
+sent). A log message must have been specified with a @code{-m}
+argument.
+
+@item add \n
+Response expected: yes. Add a file or directory. This uses any
+previous @code{Argument}, @code{Directory}, @code{Entry}, or
+@code{Modified} requests, if they have been sent. The
+last @code{Directory} sent specifies the working directory at the time
+of the operation.
+
+To add a directory, send the directory to be added using
+@code{Directory} and @code{Argument} requests. For example:
+
+@example
+C: Root /u/cvsroot
+. . .
+C: Argument nsdir
+C: Directory nsdir
+C: /u/cvsroot/1dir/nsdir
+C: Directory .
+C: /u/cvsroot/1dir
+C: add
+S: M Directory /u/cvsroot/1dir/nsdir added to the repository
+S: ok
+@end example
+
+You will notice that the server does not signal to the client in any
+particular way that the directory has been successfully added. The
+client is supposed to just assume that the directory has been added and
+update its records accordingly. Note also that adding a directory is
+immediate; it does not wait until a @code{ci} request as files do.
+
+To add a file, send the file to be added using a @code{Modified}
+request. For example:
+
+@example
+C: Argument nfile
+C: Directory .
+C: /u/cvsroot/1dir
+C: Modified nfile
+C: u=rw,g=r,o=r
+C: 6
+C: hello
+C: add
+S: E cvs server: scheduling file `nfile' for addition
+S: Mode u=rw,g=r,o=r
+S: Checked-in ./
+S: /u/cvsroot/1dir/nfile
+S: /nfile/0///
+S: E cvs server: use 'cvs commit' to add this file permanently
+S: ok
+@end example
+
+Note that the file has not been added to the repository; the only effect
+of a successful @code{add} request, for a file, is to supply the client
+with a new entries line containing @samp{0} to indicate an added file.
+In fact, the client probably could perform this operation without
+contacting the server, although using @code{add} does cause the server
+to perform a few more checks.
+
+The client sends a subsequent @code{ci} to actually add the file to the
+repository.
+
+Another quirk of the @code{add} request is that with CVS 1.9 and older,
+a pathname specified in
+an @code{Argument} request cannot contain @samp{/}. There is no good
+reason for this restriction, and in fact more recent CVS servers don't
+have it.
+But the way to interoperate with the older servers is to ensure that
+all @code{Directory} requests for @code{add} (except those used to add
+directories, as described above), use @samp{.} for
+@var{local-directory}. Specifying another string for
+@var{local-directory} may not get an error, but it will get you strange
+@code{Checked-in} responses from the buggy servers.
+
+@item remove \n
+Response expected: yes. Remove a file. This uses any
+previous @code{Argument}, @code{Directory}, @code{Entry}, or
+@code{Modified} requests, if they have been sent. The
+last @code{Directory} sent specifies the working directory at the time
+of the operation.
+
+Note that this request does not actually do anything to the repository;
+the only effect of a successful @code{remove} request is to supply the
+client with a new entries line containing @samp{-} to indicate a removed
+file. In fact, the client probably could perform this operation without
+contacting the server, although using @code{remove} may cause the server
+to perform a few more checks.
+
+The client sends a subsequent @code{ci} request to actually record the
+removal in the repository.
+
+@item watch-on \n
+@itemx watch-off \n
+@itemx watch-add \n
+@itemx watch-remove \n
+Response expected: yes. Actually do the @code{cvs watch on}, @code{cvs
+watch off}, @code{cvs watch add}, and @code{cvs watch remove} commands,
+respectively. This uses any previous @code{Argument},
+@code{Directory}, @code{Entry}, or @code{Modified}
+requests, if they have been sent. The last @code{Directory} sent
+specifies the working directory at the time of the operation.
+
+@item release \n
+Response expected: yes. Note that a @code{cvs release} command has
+taken place and update the history file accordingly.
+
+@item noop \n
+Response expected: yes. This request is a null command in the sense
+that it doesn't do anything, but merely (as with any other requests
+expecting a response) sends back any responses pertaining to pending
+errors, pending @code{Notified} responses, etc.
+
+@item update-patches \n
+Response expected: yes.
+This request does not actually do anything. It is used as a signal that
+the server is able to generate patches when given an @code{update}
+request. The client must issue the @code{-u} argument to @code{update}
+in order to receive patches.
+
+@item gzip-file-contents @var{level} \n
+Response expected: no. Note that this request does not follow the
+response convention stated above. @code{Gzip-stream} is suggested
+instead of @code{gzip-file-contents} as it gives better compression; the
+only reason to implement the latter is to provide compression with
+@sc{cvs} 1.8 and earlier. The @code{gzip-file-contents} request asks
+the server to compress files it sends to the client using @code{gzip}
+(RFC1952/1951) compression, using the specified level of compression.
+If this request is not made, the server must not compress files.
+
+This is only a hint to the server. It may still decide (for example, in
+the case of very small files, or files that already appear to be
+compressed) not to do the compression. Compression is indicated by a
+@samp{z} preceding the file length.
+
+Availability of this request in the server indicates to the client that
+it may compress files sent to the server, regardless of whether the
+client actually uses this request.
+
+@item wrapper-sendme-rcsOptions \n
+Response expected: yes.
+Request that the server transmit mappings from filenames to keyword
+expansion modes in @code{Wrapper-rcsOption} responses.
+
+@item @var{other-request} @var{text} \n
+Response expected: yes.
+Any unrecognized request expects a response, and does not
+contain any additional data. The response will normally be something like
+@samp{error unrecognized request}, but it could be a different error if
+a previous request which doesn't expect a response produced an error.
+@end table
+
+When the client is done, it drops the connection.
+
+@node Response intro
+@section Introduction to Responses
+
+After a command which expects a response, the server sends however many
+of the following responses are appropriate. The server should not send
+data at other times (the current implementation may violate this
+principle in a few minor places, where the server is printing an error
+message and exiting---this should be investigated further).
+
+Any set of responses always ends with @samp{error} or @samp{ok}. This
+indicates that the response is over.
+
+@c "file updating response" and "file update modifying response" are
+@c lame terms (mostly because they are so awkward). Any better ideas?
+The responses @code{Checked-in}, @code{New-entry}, @code{Updated},
+@code{Created}, @code{Update-existing}, @code{Merged}, and
+@code{Patched} are refered to as @dfn{file updating} responses, because
+they change the status of a file in the working directory in some way.
+The responses @code{Mode}, @code{Mod-time}, and @code{Checksum} are
+referred to as @dfn{file update modifying} responses because they modify
+the next file updating response. In no case shall a file update
+modifying response apply to a file updating response other than the next
+one. Nor can the same file update modifying response occur twice for
+a given file updating response (if servers diagnose this problem, it may
+aid in detecting the case where clients send an update modifying
+response without following it by a file updating response).
+
+@node Response pathnames
+@section The "pathname" in responses
+
+Many of the responses contain something called @var{pathname}.
+@c FIXME: should better document when the specified repository needs to
+@c end in "/.".
+The name is somewhat misleading; it actually indicates a pair of
+pathnames. First, a local directory name
+relative to the directory in which the command was given (i.e. the last
+@code{Directory} before the command). Then a linefeed and a repository
+name. Then
+a slash and the filename (without a @samp{,v} ending).
+For example, for a file @file{i386.mh}
+which is in the local directory @file{gas.clean/config} and for which
+the repository is @file{/rel/cvsfiles/devo/gas/config}:
+
+@example
+gas.clean/config/
+/rel/cvsfiles/devo/gas/config/i386.mh
+@end example
+
+If the server wants to tell the client to create a directory, then it
+merely uses the directory in any response, as described above, and the
+client should create the directory if it does not exist. Note that this
+should only be done one directory at a time, in order to permit the
+client to correctly store the repository for each directory. Servers
+can use requests such as @code{Clear-sticky},
+@code{Clear-static-directory}, or any other requests, to create
+directories.
+@c FIXME: Need example here of how "repository" needs to be sent for
+@c each directory, and cannot be correctly deduced from, say, the most
+@c deeply nested directory.
+
+Some server
+implementations may poorly distinguish between a directory which should
+not exist and a directory which contains no files; in order to refrain
+from creating empty directories a client should both send the @samp{-P}
+option to @code{update} or @code{co}, and should also detect the case in
+which the server asks to create a directory but not any files within it
+(in that case the client should remove the directory or refrain from
+creating it in the first place). Note that servers could clean this up
+greatly by only telling the client to create directories if the
+directory in question should exist, but until servers do this, clients
+will need to offer the @samp{-P} behavior described above.
+
+@node Responses
+@section Responses
+
+Here are the responses:
+
+@table @code
+@item Valid-requests @var{request-list} \n
+Indicate what requests the server will accept. @var{request-list}
+is a space separated list of tokens. If the server supports sending
+patches, it will include @samp{update-patches} in this list. The
+@samp{update-patches} request does not actually do anything.
+
+@item Checked-in @var{pathname} \n
+Additional data: New Entries line, \n. This means a file @var{pathname}
+has been successfully operated on (checked in, added, etc.). name in
+the Entries line is the same as the last component of @var{pathname}.
+
+@item New-entry @var{pathname} \n
+Additional data: New Entries line, \n. Like @code{Checked-in}, but the
+file is not up to date.
+
+@item Updated @var{pathname} \n
+Additional data: New Entries line, \n, mode, \n, file transmission. A
+new copy of the file is enclosed. This is used for a new revision of an
+existing file, or for a new file, or for any other case in which the
+local (client-side) copy of the file needs to be updated, and after
+being updated it will be up to date. If any directory in pathname does
+not exist, create it. This response is not used if @code{Created} and
+@code{Update-existing} are supported.
+
+@item Created @var{pathname} \n
+This is just like @code{Updated} and takes the same additional data, but
+is used only if no @code{Entry}, @code{Modified}, or
+@code{Unchanged} request has been sent for the file in question. The
+distinction between @code{Created} and @code{Update-existing} is so
+that the client can give an error message in several cases: (1) there is
+a file in the working directory, but not one for which @code{Entry},
+@code{Modified}, or @code{Unchanged} was sent (for example, a file which
+was ignored, or a file for which @code{Questionable} was sent), (2)
+there is a file in the working directory whose name differs from the one
+mentioned in @code{Created} in ways that the client is unable to use to
+distinguish files. For example, the client is case-insensitive and the
+names differ only in case.
+
+@item Update-existing @var{pathname} \n
+This is just like @code{Updated} and takes the same additional data, but
+is used only if a @code{Entry}, @code{Modified}, or @code{Unchanged}
+request has been sent for the file in question.
+
+This response, or @code{Merged}, indicates that the server has
+determined that it is OK to overwrite the previous contents of the file
+specified by @var{pathname}. Provided that the client has correctly
+sent @code{Modified} or @code{Is-modified} requests for a modified file,
+and the file was not modified while CVS was running, the server can
+ensure that a user's modifications are not lost.
+
+@item Merged @var{pathname} \n
+This is just like @code{Updated} and takes the same additional data,
+with the one difference that after the new copy of the file is enclosed,
+it will still not be up to date. Used for the results of a merge, with
+or without conflicts.
+
+It is useful to preserve an copy of what the file looked like before the
+merge. This is basically handled by the server; before sending
+@code{Merged} it will send a @code{Copy-file} response. For example, if
+the file is @file{aa} and it derives from revision 1.3, the
+@code{Copy-file} response will tell the client to copy @file{aa} to
+@file{.#aa.1.3}. It is up to the client to decide how long to keep this
+file around; traditionally clients have left it around forever, thus
+letting the user clean it up as desired. But another answer, such as
+until the next commit, might be preferable.
+
+@item Rcs-diff @var{pathname} \n
+This is just like @code{Updated} and takes the same additional data,
+with the one difference that instead of sending a new copy of the file,
+the server sends an RCS change text. This change text is produced by
+@samp{diff -n} (the GNU diff @samp{-a} option may also be used). The
+client must apply this change text to the existing file. This will only
+be used when the client has an exact copy of an earlier revision of a
+file. This response is only used if the @code{update} command is given
+the @samp{-u} argument.
+
+@item Patched @var{pathname} \n
+This is just like @code{Rcs-diff} and takes the same additional data,
+except that it sends a standard patch rather than an RCS change text.
+The patch is produced by @samp{diff -c} for @sc{cvs} 1.6 and later (see
+POSIX.2 for a description of this format), or @samp{diff -u} for
+previous versions of @sc{cvs}; clients are encouraged to accept either
+format. Like @code{Rcs-diff}, this response is only used if the
+@code{update} command is given the @samp{-u} argument.
+
+The @code{Patched} response is deprecated in favor of the
+@code{Rcs-diff} response. However, older clients (CVS 1.9 and earlier)
+only support @code{Patched}.
+
+@item Mode @var{mode} \n
+This @var{mode} applies to the next file mentioned in
+@code{Checked-in}. @code{Mode} is a file update modifying response
+as described in @ref{Response intro}.
+
+@item Mod-time @var{time} \n
+Set the modification time of the next file sent to @var{time}.
+@code{Mod-time} is a file update modifying response
+as described in @ref{Response intro}.
+The
+@var{time} is in the format specified by RFC822 as modified by RFC1123.
+The server may specify any timezone it chooses; clients will want to
+convert that to their own timezone as appropriate. An example of this
+format is:
+
+@example
+26 May 1997 13:01:40 -0400
+@end example
+
+There is no requirement that the client and server clocks be
+synchronized. The server just sends its recommendation for a timestamp
+(based on its own clock, presumably), and the client should just believe
+it (this means that the time might be in the future, for example).
+
+If the server does not send @code{Mod-time} for a given file, the client
+should pick a modification time in the usual way (usually, just let the
+operating system set the modification time to the time that the CVS
+command is running).
+
+@item Checksum @var{checksum}\n
+The @var{checksum} applies to the next file sent (that is,
+@code{Checksum} is a file update modifying response
+as described in @ref{Response intro}).
+In the case of
+@code{Patched}, the checksum applies to the file after being patched,
+not to the patch itself. The client should compute the checksum itself,
+after receiving the file or patch, and signal an error if the checksums
+do not match. The checksum is the 128 bit MD5 checksum represented as
+32 hex digits (MD5 is described in RFC1321).
+This response is optional, and is only used if the
+client supports it (as judged by the @code{Valid-responses} request).
+
+@item Copy-file @var{pathname} \n
+Additional data: @var{newname} \n. Copy file @var{pathname} to
+@var{newname} in the same directory where it already is. This does not
+affect @code{CVS/Entries}.
+
+This can optionally be implemented as a rename instead of a copy. The
+only use for it which currently has been identified is prior to a
+@code{Merged} response as described under @code{Merged}. Clients can
+probably assume that is how it is being used, if they want to worry
+about things like how long to keep the @var{newname} file around.
+
+@item Removed @var{pathname} \n
+The file has been removed from the repository (this is the case where
+cvs prints @samp{file foobar.c is no longer pertinent}).
+
+@item Remove-entry @var{pathname} \n
+The file needs its entry removed from @code{CVS/Entries}, but the file
+itself is already gone (this happens in response to a @code{ci} request
+which involves committing the removal of a file).
+
+@item Set-static-directory @var{pathname} \n
+This instructs the client to set the @code{Entries.Static} flag, which
+it should then send back to the server in a @code{Static-directory}
+request whenever the directory is operated on. @var{pathname} ends in a
+slash; its purpose is to specify a directory, not a file within a
+directory.
+
+@item Clear-static-directory @var{pathname} \n
+Like @code{Set-static-directory}, but clear, not set, the flag.
+
+@item Set-sticky @var{pathname} \n
+Additional data: @var{tagspec} \n. Tell the client to set a sticky tag
+or date, which should be supplied with the @code{Sticky} request for
+future operations. @var{pathname} ends in a slash; its purpose is to
+specify a directory, not a file within a directory. The client should
+store @var{tagspec} and pass it back to the server as-is, to allow for
+future expansion. The first character of @var{tagspec} is @samp{T} for
+a tag, @samp{D} for a date, or something else for future expansion. The
+remainder of @var{tagspec} contains the actual tag or date.
+
+@item Clear-sticky @var{pathname} \n
+Clear any sticky tag or date set by @code{Set-sticky}.
+
+@item Template @var{pathname} \n
+Additional data: file transmission (note: compressed file transmissions
+are not supported). @var{pathname} ends in a slash; its purpose is to
+specify a directory, not a file within a directory. Tell the client to
+store the file transmission as the template log message, and then use
+that template in the future when prompting the user for a log message.
+
+@item Set-checkin-prog @var{dir} \n
+Additional data: @var{prog} \n. Tell the client to set a checkin
+program, which should be supplied with the @code{Checkin-prog} request
+for future operations.
+
+@item Set-update-prog @var{dir} \n
+Additional data: @var{prog} \n. Tell the client to set an update
+program, which should be supplied with the @code{Update-prog} request
+for future operations.
+
+@item Notified @var{pathname} \n
+Indicate to the client that the notification for @var{pathname} has been
+done. There should be one such response for every @code{Notify}
+request; if there are several @code{Notify} requests for a single file,
+the requests should be processed in order; the first @code{Notified}
+response pertains to the first @code{Notify} request, etc.
+
+@item Module-expansion @var{pathname} \n
+Return a file or directory
+which is included in a particular module. @var{pathname} is relative
+to cvsroot, unlike most pathnames in responses. @var{pathname} should
+be used to look and see whether some or all of the module exists on
+the client side; it is not necessarily suitable for passing as an
+argument to a @code{co} request (for example, if the modules file
+contains the @samp{-d} option, it will be the directory specified with
+@samp{-d}, not the name of the module).
+
+@item Wrapper-rcsOption @var{pattern} -k '@var{option}' \n
+Transmit to the client a filename pattern which implies a certain
+keyword expansion mode. The @var{pattern} is a wildcard pattern (for
+example, @samp{*.exe}. The @var{option} is @samp{b} for binary, and so
+on. Note that although the syntax happens to resemble the syntax in
+certain CVS configuration files, it is more constrained; there must be
+exactly one space between @var{pattern} and @samp{-k} and exactly one
+space between @samp{-k} and @samp{'}, and no string is permitted in
+place of @samp{-k} (extensions should be done with new responses, not by
+extending this one, for graceful handling of @code{Valid-responses}).
+
+@item M @var{text} \n
+A one-line message for the user.
+Note that the format of @var{text} is not designed for machine parsing.
+Although sometimes scripts and clients will have little choice, the
+exact text which is output is subject to vary at the discretion of the
+server and the example output given in this document is just that,
+example output. Servers are encouraged to use the @samp{MT} response,
+and future versions of this document will hopefully standardize more of
+the @samp{MT} tags; see @ref{Text tags}.
+
+@item Mbinary \n
+Additional data: file transmission (note: compressed file transmissions
+are not supported). This is like @samp{M}, except the contents of the
+file transmission are binary and should be copied to standard output
+without translation to local text file conventions. To transmit a text
+file to standard output, servers should use a series of @samp{M} requests.
+
+@item E @var{text} \n
+Same as @code{M} but send to stderr not stdout.
+
+@item F \n
+@c FIXME: The second sentence, defining "flush", is somewhat off the top
+@c of my head. Is there some text we can steal from ANSI C or someplace
+@c which is more carefully thought out?
+Flush stderr. That is, make it possible for the user to see what has
+been written to stderr (it is up to the implementation to decide exactly
+how far it should go to ensure this).
+
+@item MT @var{tagname} @var{data} \n
+
+This response provides for tagged text. It is similar to
+SGML/HTML/XML in that the data is structured and a naive application
+can also make some sense of it without understanding the structure.
+The syntax is not SGML-like, however, in order to fit into the CVS
+protocol better and (more importantly) to make it easier to parse,
+especially in a language like perl or awk.
+
+The @var{tagname} can have several forms. If it starts with @samp{a}
+to @samp{z} or @samp{A} to @samp{Z}, then it represents tagged text.
+If the implementation recognizes @var{tagname}, then it may interpret
+@var{data} in some particular fashion. If the implementation does not
+recognize @var{tagname}, then it should simply treat @var{data} as
+text to be sent to the user (similar to an @samp{M} response). There
+are two tags which are general purpose. The @samp{text} tag is
+similar to an unrecognized tag in that it provides text which will
+ordinarily be sent to the user. The @samp{newline} tag is used
+without @var{data} and indicates that a newline will ordinarily be
+sent to the user (there is no provision for embedding newlines in the
+@var{data} of other tagged text responses).
+
+If @var{tagname} starts with @samp{+} it indicates a start tag and if
+it starts with @samp{-} it indicates an end tag. The remainder of
+@var{tagname} should be the same for matching start and end tags, and
+tags should be nested (for example one could have tags in the
+following order @code{+bold} @code{+italic} @code{text} @code{-italic}
+@code{-bold} but not @code{+bold} @code{+italic} @code{text}
+@code{-bold} @code{-italic}). A particular start and end tag may be
+documented to constrain the tagged text responses which are valid
+between them.
+
+Note that if @var{data} is present there will always be exactly one
+space between @var{tagname} and @var{data}; if there is more than one
+space, then the spaces beyond the first are part of @var{data}.
+
+Here is an example of some tagged text responses. Note that there is
+a trailing space after @samp{Checking in} and @samp{initial revision:}
+and there are two trailing spaces after @samp{<--}. Such trailing
+spaces are, of course, part of @var{data}.
+
+@example
+MT +checking-in
+MT text Checking in
+MT fname gz.tst
+MT text ;
+MT newline
+MT rcsfile /home/kingdon/zwork/cvsroot/foo/gz.tst,v
+MT text <--
+MT fname gz.tst
+MT newline
+MT text initial revision:
+MT init-rev 1.1
+MT newline
+MT text done
+MT newline
+MT -checking-in
+@end example
+
+If the client does not support the @samp{MT} response, the same
+responses might be sent as:
+
+@example
+M Checking in gz.tst;
+M /home/kingdon/zwork/cvsroot/foo/gz.tst,v <-- gz.tst
+M initial revision: 1.1
+M done
+@end example
+
+For a list of specific tags, see @ref{Text tags}.
+
+@item error @var{errno-code} @samp{ } @var{text} \n
+The command completed with an error. @var{errno-code} is a symbolic
+error code (e.g. @code{ENOENT}); if the server doesn't support this
+feature, or if it's not appropriate for this particular message, it just
+omits the errno-code (in that case there are two spaces after
+@samp{error}). Text is an error message such as that provided by
+strerror(), or any other message the server wants to use.
+The @var{text} is like the @code{M} response, in the sense that it is
+not particularly intended to be machine-parsed; servers may wish to
+print an error message with @code{MT} responses, and then issue a
+@code{error} response without @var{text} (although it should be noted
+that @code{MT} currently has no way of flagging the output as intended
+for standard error, the way that the @code{E} response does).
+
+@item ok \n
+The command completed successfully.
+@end table
+
+@node Text tags
+@section Tags for the MT tagged text response
+
+The @code{MT} response, as described in @ref{Responses}, offers a
+way for the server to send tagged text to the client. This section
+describes specific tags. The intention is to update this section as
+servers add new tags.
+
+In the following descriptions, @code{text} and @code{newline} tags are
+omitted. Such tags contain information which is intended for users (or
+to be discarded), and are subject to change at the whim of the server.
+To avoid being vulnerable to such whim, clients should look for the tags
+listed here, not @code{text}, @code{newline}, or other tags.
+
+The following tag means to indicate to the user that a file has been
+updated. It is more or less redundant with the @code{Created} and
+@code{Update-existing} responses, but we don't try to specify here
+whether it occurs in exactly the same circumstances as @code{Created}
+and @code{Update-existing}. The @var{name} is the pathname of the file
+being updated relative to the directory in which the command is
+occurring (that is, the last @code{Directory} request which is sent
+before the command).
+
+@example
+MT +updated
+MT fname @var{name}
+MT -updated
+@end example
+
+The @code{importmergecmd} tag is used when doing an import which has
+conflicts. The client can use it to report how to merge in the newly
+imported changes. The @var{count} is the number of conflicts. The
+newly imported changes can be merged by running the following command:
+@smallexample
+cvs checkout -j @var{tag1} -j @var{tag2} @var{repository}
+@end smallexample
+
+@example
+MT +importmergecmd
+MT conflicts @var{count}
+MT mergetag1 @var{tag1}
+MT mergetag2 @var{tag2}
+MT repository @var{repository}
+MT -importmergecmd
+@end example
+
+@node Example
+@section Example
+
+@c The C:/S: convention is in imitation of RFC1869 (and presumably
+@c other RFC's). In other formatting concerns, we might want to think
+@c about whether there is an easy way to provide RFC1543 formatting
+@c (without negating the advantages of texinfo), and whether we should
+@c use RFC2234 BNF (I fear that would be less clear than
+@c what we do now, however). Plus what about RFC2119 terminology (MUST,
+@c SHOULD, &c) or ISO terminology (shall, should, or whatever they are)?
+Here is an example; lines are prefixed by @samp{C: } to indicate the
+client sends them or @samp{S: } to indicate the server sends them.
+
+The client starts by connecting, sending the root, and completing the
+protocol negotiation. In actual practice the lists of valid responses
+and requests would be longer.
+@c The reason that we artificially shorten the lists is to avoid phony
+@c line breaks. Any better solutions?
+@c Other than that, this exchange is taken verbatim from the data
+@c exchanged by CVS (as of Nov 1996). That is why some of the requests and
+@c reponses are not quite what you would pick for pedagogical purposes.
+
+@example
+C: Root /u/cvsroot
+C: Valid-responses ok error Checked-in M E
+C: valid-requests
+S: Valid-requests Root Directory Entry Modified Argument Argumentx ci co
+S: ok
+C: UseUnchanged
+@end example
+
+The client wants to check out the @code{supermunger} module into a fresh
+working directory. Therefore it first expands the @code{supermunger}
+module; this step would be omitted if the client was operating on a
+directory rather than a module.
+@c Why does it send Directory here? The description of expand-modules
+@c doesn't really say much of anything about what use, if any, it makes of
+@c Directory and similar requests sent previously.
+
+@example
+C: Argument supermunger
+C: Directory .
+C: /u/cvsroot
+C: expand-modules
+@end example
+
+The server replies that the @code{supermunger} module expands to the
+directory @code{supermunger} (the simplest case):
+
+@example
+S: Module-expansion supermunger
+S: ok
+@end example
+
+The client then proceeds to check out the directory. The fact that it
+sends only a single @code{Directory} request which specifies @samp{.}
+for the working directory means that there is not already a
+@code{supermunger} directory on the client.
+@c What is -N doing here?
+
+@example
+C: Argument -N
+C: Argument supermunger
+C: Directory .
+C: /u/cvsroot
+C: co
+@end example
+
+The server replies with the requested files. In this example, there is
+only one file, @file{mungeall.c}. The @code{Clear-sticky} and
+@code{Clear-static-directory} requests are sent by the current
+implementation but they have no effect because the default is for those
+settings to be clear when a directory is newly created.
+
+@example
+S: Clear-sticky supermunger/
+S: /u/cvsroot/supermunger/
+S: Clear-static-directory supermunger/
+S: /u/cvsroot/supermunger/
+S: E cvs server: Updating supermunger
+S: M U supermunger/mungeall.c
+S: Created supermunger/
+S: /u/cvsroot/supermunger/mungeall.c
+S: /mungeall.c/1.1///
+S: u=rw,g=r,o=r
+S: 26
+S: int mein () @{ abort (); @}
+S: ok
+@end example
+
+The current client implementation would break the connection here and make a
+new connection for the next command. However, the protocol allows it
+to keep the connection open and continue, which is what we show here.
+
+After the user modifies the file and instructs the client to check it
+back in. The client sends arguments to specify the log message and file
+to check in:
+
+@example
+C: Argument -m
+C: Argument Well, you see, it took me hours and hours to find
+C: Argumentx this typo and I searched and searched and eventually
+C: Argumentx had to ask John for help.
+C: Argument mungeall.c
+@end example
+
+It also sends information about the contents of the working directory,
+including the new contents of the modified file. Note that the user has
+changed into the @file{supermunger} directory before executing this
+command; the top level directory is a user-visible concept because the
+server should print filenames in @code{M} and @code{E} responses
+relative to that directory.
+@c We are waving our hands about the order of the requests. "Directory"
+@c and "Argument" can be in any order, but this probably isn't specified
+@c very well.
+
+@example
+C: Directory .
+C: /u/cvsroot/supermunger
+C: Entry /mungeall.c/1.1///
+C: Modified mungeall.c
+C: u=rw,g=r,o=r
+C: 26
+C: int main () @{ abort (); @}
+@end example
+
+And finally, the client issues the checkin command (which makes use of
+the data just sent):
+
+@example
+C: ci
+@end example
+
+And the server tells the client that the checkin succeeded:
+
+@example
+S: M Checking in mungeall.c;
+S: E /u/cvsroot/supermunger/mungeall.c,v <-- mungeall.c
+S: E new revision: 1.2; previous revision: 1.1
+S: E done
+S: Mode u=rw,g=r,o=r
+S: Checked-in ./
+S: /u/cvsroot/supermunger/mungeall.c
+S: /mungeall.c/1.2///
+S: ok
+@end example
+
+@node Requirements
+@section Required versus optional parts of the protocol
+
+The following are part of every known implementation of the CVS protocol
+(except obsolete, pre-1.5, versions of CVS) and it is considered
+reasonable behavior to completely fail to work if you are connected with
+an implementation which attempts to not support them. Requests:
+@code{Root}, @code{Valid-responses}, @code{valid-requests},
+@code{Directory}, @code{Entry}, @code{Modified}, @code{Unchanged},
+@code{Argument}, @code{Argumentx}, @code{ci}, @code{co}, @code{update}.
+Responses: @code{ok}, @code{error}, @code{Valid-requests},
+@code{Checked-in}, @code{Updated}, @code{Merged}, @code{Removed},
+@code{M}, @code{E}.
+
+A server need not implement @code{Repository}, but in order to interoperate
+with CVS 1.5 through 1.9 it must claim to implement it (in
+@code{Valid-requests}). The client will not actually send the request.
+
+@node Obsolete
+@section Obsolete protocol elements
+
+This section briefly describes protocol elements which are obsolete.
+There is no attempt to document them in full detail.
+
+There was a @code{Repository} request which was like @code{Directory}
+except it only provided @var{repository}, and the local directory was
+assumed to be similarly named.
+
+If the @code{UseUnchanged} request was not sent, there was a @code{Lost}
+request which was sent to indicate that a file did not exist in the
+working directory, and the meaning of sending @code{Entries} without
+@code{Lost} or @code{Modified} was different. All current clients (CVS
+1.5 and later) will send @code{UseUnchanged} if it is supported.
+
+@node Protocol Notes
+@chapter Notes on the Protocol
+
+A number of enhancements are possible. Also see the file @sc{todo} in
+the @sc{cvs} source distribution, which has further ideas concerning
+various aspects of @sc{cvs}, some of which impact the protocol.
+Similarly, the @code{http://www.cyclic.com} site, in particular the
+@cite{Development of CVS} page.
+
+@itemize @bullet
+@item
+The @code{Modified} request could be speeded up by sending diffs rather
+than entire files. The client would need some way to keep the version
+of the file which was originally checked out; probably requiring the use
+of "cvs edit" in this case is the most sensible course (the "cvs edit"
+could be handled by a package like VC for emacs). This would also allow
+local operation of @code{cvs diff} without arguments.
+
+@item
+The fact that @code{pserver} requires an extra network turnaround in
+order to perform authentication would be nice to avoid. This relates to
+the issue of reporting errors; probably the clean solution is to defer
+the error until the client has issued a request which expects a
+response. To some extent this might relate to the next item (in terms
+of how easy it is to skip a whole bunch of requests until we get to one
+that expects a response). I know that the kerberos code doesn't wait in
+this fashion, but that probably can cause network deadlocks and perhaps
+future problems running over a transport which is more transaction
+oriented than TCP. On the other hand I'm not sure it is wise to make
+the client conduct a lengthy upload only to find there is an
+authentication failure.
+
+@item
+The protocol uses an extra network turnaround for protocol negotiation
+(@code{valid-requests}). It might be nice to avoid this by having the
+client be able to send requests and tell the server to ignore them if
+they are unrecognized (different requests could produce a fatal error if
+unrecognized). To do this there should be a standard syntax for
+requests. For example, perhaps all future requests should be a single
+line, with mechanisms analogous to @code{Argumentx}, or several requests
+working together, to provide greater amounts of information. Or there
+might be a standard mechanism for counted data (analogous to that used
+by @code{Modified}) or continuation lines (like a generalized
+@code{Argumentx}). It would be useful to compare what HTTP is planning
+in this area; last I looked they were contemplating something called
+Protocol Extension Protocol but I haven't looked at the relevant IETF
+documents in any detail. Obviously, we want something as simple as
+possible (but no simpler).
+
+@item
+The scrambling algorithm in the CVS client and server actually support
+more characters than those documented in @ref{Password scrambling}.
+Someday we are going to either have to document them all (but this is
+not as easy as it may look, see below), or (gradually and with adequate
+process) phase out the support for other characters in the CVS
+implementation. This business of having the feature partly undocumented
+isn't a desirable state long-term.
+
+The problem with documenting other characters is that unless we know
+what character set is in use, there is no way to make a password
+portable from one system to another. For example, a with a circle on
+top might have different encodings in different character sets.
+
+It @emph{almost} works to say that the client picks an arbitrary,
+unknown character set (indeed, having the CVS client know what character
+set the user has in mind is a hard problem otherwise), and scrambles
+according to a certain octet<->octet mapping. There are two problems
+with this. One is that the protocol has no way to transmit character 10
+decimal (linefeed), and the current server and clients have no way to
+handle 0 decimal (NUL). This may cause problems with certain multibyte
+character sets, in which octets 10 and 0 will appear in the middle of
+other characters. The other problem, which is more minor and possibly
+not worth worrying about, is that someone can type a password on one
+system and then go to another system which uses a different encoding for
+the same characters, and have their password not work.
+
+The restriction to the ISO646 invariant subset is the best approach for
+strings which are not particularly significant to users. Passwords are
+visible enough that this is somewhat doubtful as applied here. ISO646
+does, however, have the virtue (!?) of offending everyone. It is easy
+to say "But the $ is right on people's keyboards! Surely we can't
+forbid that". From a human factors point of view, that makes quite a
+bit of sense. The contrary argument, of course, is that a with a circle
+on top, or some of the characters poorly handled by Unicode, are on
+@emph{someone}'s keyboard.
+
+@end itemize
+
+@bye
diff --git a/gnu/dist/cvs/man/.cvsignore b/gnu/dist/cvs/man/.cvsignore
new file mode 100644
index 00000000000..f3c7a7c5da6
--- /dev/null
+++ b/gnu/dist/cvs/man/.cvsignore
@@ -0,0 +1 @@
+Makefile
diff --git a/gnu/dist/cvs/man/ChangeLog b/gnu/dist/cvs/man/ChangeLog
new file mode 100644
index 00000000000..202ffedb2be
--- /dev/null
+++ b/gnu/dist/cvs/man/ChangeLog
@@ -0,0 +1,237 @@
+2000-01-02 John P Cavanaugh <cavanaug@sr.hp.com>
+
+ * cvs.texinfo: document new -C option to update, now that it works
+ both remotely and locally.
+ (Re-applied by Karl Fogel <kfogel@red-bean.com>.)
+
+1999-12-11 Karl Fogel <kfogel@red-bean.com>
+
+ * Revert previous change -- it doesn't work remotely yet.
+
+1999-12-10 John P Cavanaugh <cavanaug@sr.hp.com>
+
+ * cvs.1: document new -C option to update.
+ (Applied by Karl Fogel <kfogel@red-bean.com>.)
+
+1999-11-29 Larry Jones <larry.jones@sdrc.com>
+
+ * cvsbug.8: Change .TH from section 1 to section 8.
+
+1999-11-10 Jim Kingdon <http://developer.redhat.com/>
+
+ * cvs.1: Don't document -H as a command option; it has been a
+ while since that worked.
+
+1999-01-19 Vitaly V Fedrushkov <willy@snowyowl.csu.ac.ru>
+
+ * Makefile.in (INSTALL_DATA): Wrong manpage permissions fixed.
+
+1998-06-28 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.1: Update various items which were out of date. Mostly
+ these related to CVS no longer calling external RCS programs.
+
+Mon Jan 12 11:10:21 1998 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.1: Refer to Cederqvist as Cederqvist not as cvs.texinfo.
+ Describe it in a way which hopefully makes more sense for people
+ who aren't getting the CVS source distribution itself. In "SEE
+ ALSO" section, also mention web pages. Use a period not a comma
+ at the end of the references to other manpages.
+
+ * cvs.1: Remove $Id; we decided to get rid of these some time ago.
+
+Sun Jan 11 13:46:54 1998 Peter Wemm <peter@netplex.com.au>
+
+ * cvs.1: Fix "the the" typo. From Wolfram Schneider
+ <wosch@freebsd.org> via FreeBSD sources.
+
+Wed Feb 12 14:20:43 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (install): Depend on installdirs.
+
+Thu Jan 2 13:30:56 1997 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in, cvsbug.8: Remove "675" paragraph;
+ see ../ChangeLog for rationale.
+
+Sat Nov 30 14:49:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.1: Add note at beginning about how cvs.texinfo is more
+ complete than this document. Without some such indication, users
+ have no way of knowing which document to consult.
+
+Tue Oct 1 14:01:28 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.1: Revert all changes by Greg Woods since CVS 1.8.86. They
+ are for new features which are not appropriate at this stage of
+ the release process.
+
+Mon Sep 30 18:21:05 1996 Greg A. Woods <woods@most.weird.com>
+
+ * cvs.1: document -D, -g, DIFFBIN, and GREPBIN.
+
+Mon Sep 16 23:22:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsbug.8: Remove references to cvsbug.el (or other aspects of
+ emacs interface) and cvsbug.info (or other aspects of texinfo
+ documentation). Neither one has ever existed.
+
+Wed Jul 24 19:01:35 1996 Ian Lance Taylor <ian@cygnus.com>
+
+ * cvs.1: Document -x.
+
+Fri Sep 13 11:01:59 1996 Greg A. Woods <woods@clapton.seachange.com>
+
+ * cvs.1: add description of -k and -K for rdiff. Mention that
+ import allows '-b 1' to import to the trunk.
+
+Tue May 14 10:23:59 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.1: Add cvs.texinfo to the SEE ALSO section. Replace an out
+ of date list of default ignore patterns with a reference to
+ cvs.texinfo (if someone wants to undo this change that is OK with
+ me but I did it this way because it isn't clear people would
+ actually keep cvs.1 up to date).
+
+Wed Mar 13 17:06:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsinit.8: Removed.
+ * Makefile.in, cvs.1, cvs.5: Remove references to cvsinit.
+
+Tue Feb 13 22:30:54 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * Makefile.in: Remove reference to mkmodules.1
+
+Mon Feb 12 16:30:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.1, cvsinit.8: Remove references to mkmodules, rm, sort
+ * cvs.5: Remove references to mkmodules.
+ * mkmodules.1: Removed.
+
+Tue Jan 30 18:32:27 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in: Revise comment regarding install and installdirs.
+
+Tue Nov 14 15:47:44 1995 Greg A. Woods <woods@most.weird.com>
+
+ * cvs.5:
+ - list filenames in alpha sort order
+ - describe the cvswrappers file
+ - describe the taginfo file
+ - fix cvsinit chapter number
+ (This is by no means complete -- it's just stuff I noticed.)
+
+ * cvs.1: put the filenames in alpha sort order
+
+ * cvsinit.8: remove the .pl extension from filenames
+
+Wed Oct 18 11:07:07 1995 Vince Demarco <vdemarco@bou.shl.com>
+
+ * cvs.1 (Flag): Updated the CVSROOT/wrappers stuff. Everyone seems
+ to think this is a NEXT specific thing it isn't.
+
+Tue Oct 17 17:38:27 1995 Warren Jones <wjones@tc.fluke.com>
+
+ * cvs.1: Change \. to \&. at start of line.
+
+Tue Oct 3 13:43:33 1995 Del <del@matra.com.au>
+
+ * cvs.1: Updated man page for all the new features of 1.6
+ (including some that were missed in 1.5 and 1.4.xx). This includes:
+ - -f and -z global options.
+ - tag -F, and -r options.
+ - rtag -F options
+ - CVSROOT/taginfo and CVSROOT/wrappers files (the latter could use a touch
+ up because I don't really understand how wrappers work or why anyone would
+ use them -- I haven't ever played with a NEXT.
+ - export -k option
+ - New environment variables CVS_IGNORE_REMOTE_ROOT, CVS_RSH, CVS_SERVER, and
+ CVSWRAPPERS. I left CVS_CLIENT_LOG, CVS_CLIENT_PORT, and CVS_SERVER_SLEEP
+ undocumented because these appear to be for testing / debugging only.
+ Note that TMPDIR, HOME and PATH are used as well and strictly speaking
+ should be documented.
+ - New files ~/.cvsrc and ~/.cvswrappers
+
+Tue Aug 15 08:13:14 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * Makefile.in (MANFILES): include $MAN8FILES too, so they get
+ tarred up in the distribution just like anything else.
+
+Mon Jul 24 19:11:15 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.1: Remove references to -q and -Q command options.
+
+Fri Jul 14 23:30:33 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (prefix): Don't forget to give this a value.
+
+Sun Jul 9 21:22:56 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ Greg Woods' change:
+
+ * cvsbug.8, cvsinit.8: new files.
+
+Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * cvs.1: document 'cvs status [-qQ]'
+ - note reference to cvsinit(8) and cvsbug(8)
+ (from previous local changes)
+
+ * Makefile.in: add support for installing in man8, and new cvsbug
+ and cvsinit pages (from previous local changes)
+
+Sat May 27 08:46:00 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (Makefile): Regenerate only Makefile in current
+ directory when Makefile.in is out of date. Depend on ../config.status.
+
+Fri Apr 28 22:51:31 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (DISTFILES): Updated.
+ (dist-dir): Renamed from dist; changed to work with DISTDIR
+ variable passed from parent.
+
+Fri Jul 15 12:58:14 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * Makefile.in (install): Do not depend upon installdirs.
+
+Sat Dec 18 01:23:13 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead
+
+Mon Jun 14 12:20:33 1993 david d `zoo' zuhn (zoo at rtl.cygnus.com)
+
+ * Makefile.in (install): remove parentdir support
+
+Mon Aug 31 01:42:43 1992 david d [zoo] zuhn (zoo at cirdan.cygnus.com)
+
+ * Makefile.in (install): create $(man1dir) and $(man5dir) before
+ installing the man pages
+
+Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * Makefile.in, configure.in: removed traces of namesubdir,
+ -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced
+ copyrights to '92, changed some from Cygnus to FSF.
+
+Tue Dec 10 04:07:08 1991 K. Richard Pixley (rich at rtl.cygnus.com)
+
+ * Makefile.in: infodir belongs in datadir.
+
+Tue Dec 10 03:59:10 1991 K. Richard Pixley (rich at cygnus.com)
+
+ * cvs.man: small correction to an explanation of an example.
+
+Thu Dec 5 22:45:59 1991 K. Richard Pixley (rich at rtl.cygnus.com)
+
+ * Makefile.in: idestdir and ddestdir go away. Added copyrights
+ and shift gpl to v2. Added ChangeLog if it didn't exist. docdir
+ and mandir now keyed off datadir by default.
+
+Wed Nov 27 02:46:20 1991 K. Richard Pixley (rich at sendai)
+
+ * brought Makefile.in's up to standards.text.
+
+ * fresh changelog.
+
diff --git a/gnu/dist/cvs/man/Makefile.in b/gnu/dist/cvs/man/Makefile.in
new file mode 100644
index 00000000000..d92d494e77e
--- /dev/null
+++ b/gnu/dist/cvs/man/Makefile.in
@@ -0,0 +1,91 @@
+# Makefile for GNU CVS documentation.
+# Do not use this makefile directly, but only from `../Makefile'.
+# Copyright (C) 1986-1992 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+MAN1FILES = cvs.1
+MAN5FILES = cvs.5
+MAN8FILES = cvsbug.8
+MANFILES = $(MAN1FILES) $(MAN5FILES) $(MAN8FILES)
+
+DISTFILES = .cvsignore ChangeLog Makefile.in $(MANFILES)
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+prefix = @prefix@
+mandir = $(prefix)/man
+man1dir = $(mandir)/man1
+man5dir = $(mandir)/man5
+man8dir = $(mandir)/man8
+
+all:
+.PHONY: all
+
+# This needs to depend on installdirs so that (for example) the
+# install will not fail if $(prefix)/man/man5 does not yet exist.
+install: all installdirs
+ for f in $(MAN1FILES); do \
+ $(INSTALL_DATA) $(srcdir)/$$f $(man1dir)/$$f; \
+ done
+ for f in $(MAN5FILES); do \
+ $(INSTALL_DATA) $(srcdir)/$$f $(man5dir)/$$f; \
+ done
+ for f in $(MAN8FILES); do \
+ $(INSTALL_DATA) $(srcdir)/$$f $(man8dir)/$$f; \
+ done
+
+installdirs:
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(man1dir) $(man5dir) $(man8dir)
+
+.PHONY: install installdirs
+
+tags:
+.PHONY: tags
+
+TAGS:
+.PHONY: TAGS
+
+ls:
+ @true
+.PHONY: ls
+
+clean:
+.PHONY: clean
+
+distclean: clean
+ rm -f Makefile
+.PHONY: distclean
+
+realclean: distclean
+.PHONY: realclean
+
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+.PHONY: dist-dir
+
+subdir = man
+Makefile: ../config.status Makefile.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
+
+#../config.status: ../configure
+# cd .. ; $(SHELL) config.status --recheck
+
+#../configure: ../configure.in
+# cd $(top_srcdir) ; autoconf
diff --git a/gnu/dist/cvs/man/cvs.1 b/gnu/dist/cvs/man/cvs.1
new file mode 100644
index 00000000000..050703274e2
--- /dev/null
+++ b/gnu/dist/cvs/man/cvs.1
@@ -0,0 +1,2129 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.TH CVS 1 "\*(Dt"
+.\" Full space in nroff; half space in troff
+.de SP
+.if n .sp
+.if t .sp .5
+..
+.\" quoted command
+.de `
+.RB ` "\|\\$1\|" '\\$2
+..
+.SH "NAME"
+cvs \- Concurrent Versions System
+.SH "SYNOPSIS"
+.TP
+\fBcvs\fP [ \fIcvs_options\fP ]
+.I cvs_command
+[
+.I command_options
+] [
+.I command_args
+]
+.SH "NOTE"
+This manpage is a summary of some of the features of
+.B cvs
+but for more in-depth documentation, consult the Cederqvist manual (as
+described in the SEE ALSO section of this manpage).
+.SH "DESCRIPTION"
+.IX "revision control system" "\fLcvs\fR"
+.IX cvs "" "\fLcvs\fP \- concurrent versions system"
+.IX "concurrent versions system \- \fLcvs\fP"
+.IX "release control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system"
+.IX "source control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system"
+.IX revisions "cvs command" "" "\fLcvs\fP \- source control"
+CVS is a version control system, which allows you to keep old versions
+of files (usually source code), keep a log of who, when, and why
+changes occurred, etc., like RCS or SCCS. Unlike the simpler systems,
+CVS does not just operate on one file at a time or one directory at a
+time, but operates on hierarchical collections of directories
+consisting of version controlled files. CVS helps to manage releases
+and to control the concurrent editing of source files among multiple
+authors. CVS allows triggers to enable/log/control various
+operations and works well over a wide area network.
+.SP
+.B cvs
+keeps a single copy of the master sources.
+This copy is called the source ``repository''; it contains all the
+information to permit extracting previous software releases at any
+time based on either a symbolic revision tag, or a date in the past.
+.SH "ESSENTIAL COMMANDS"
+.B cvs
+provides a rich variety of commands (\fIcvs_command\fP in the
+Synopsis), each of which often has a wealth of options, to satisfy the
+many needs of source management in distributed environments. However,
+you don't have to master every detail to do useful work with
+.BR cvs ;
+in fact, five commands are sufficient to use (and contribute to)
+the source repository.
+.TP
+\fBcvs checkout\fP \fImodules\fP\|.\|.\|.
+A necessary preliminary for most \fBcvs\fP work: creates your private
+copy of the source for \fImodules\fP (named collections of source; you
+can also use a path relative to the source repository here). You can
+work with this copy without interfering with others' work. At least
+one subdirectory level is always created.
+.TP
+.B cvs update
+Execute this command from \fIwithin\fP your private source
+directory when you wish to update your copies of source files from
+changes that other developers have made to the source in the
+repository.
+.TP
+\fBcvs add\fP \fIfile\fP\|.\|.\|.
+Use this command to enroll new files in \fBcvs\fP records of your
+working directory. The files will be added to the repository the next
+time you run
+.` "cvs commit".
+Note:
+You should use the
+.` "cvs import"
+command to bootstrap new sources into the source repository.
+.` "cvs add"
+is only used for new files to an already checked-out module.
+.TP
+\fBcvs remove\fP \fIfile\fP\|.\|.\|.
+Use this command (after erasing any files listed) to declare that you
+wish to eliminate files from the repository. The removal does not
+affect others until you run
+.` "cvs commit".
+.TP
+\fBcvs commit\fP \fIfile\fP\|.\|.\|.
+Use this command when you wish to ``publish'' your changes to other
+developers, by incorporating them in the source repository.
+.SH "OPTIONS"
+The
+.B cvs
+command line can include
+.IR cvs_options ,
+which apply to the overall
+.B cvs
+program; a
+.IR cvs_command ,
+which specifies a particular action on the source repository; and
+.I command_options
+and
+.I command_arguments
+to fully specify what the
+.I cvs_command
+will do.
+.SP
+.I Warning:
+you must be careful of precisely where you place options relative to the
+.IR cvs_command .
+The same option can mean different things depending on whether it
+is in the
+.I cvs_options
+position (to the left of a
+.B cvs
+command) or in the
+.I command_options
+position (to the right of a
+.B cvs
+command).
+.SP
+There are only two situations where you may omit
+.IR cvs_command :
+.` "cvs \-H"
+or
+.` "cvs --help"
+elicits a list of available commands, and
+.` "cvs \-v"
+or
+.` "cvs --version"
+displays version information on \fBcvs\fP itself.
+.SP
+.SH "CVS OPTIONS"
+As of release 1.6,
+.B cvs
+supports
+.SM GNU
+style long options as well as short options. Only
+a few long options are currently supported, these are listed in
+brackets after the short options whose functions they duplicate.
+.SP
+Use these options to control the overall
+.B cvs
+program:
+.TP
+.B \-H [ --help ]
+Display usage information about the specified
+.I cvs_command
+(but do not actually execute the command). If you don't specify a
+command name,
+.` "cvs \-H"
+displays a summary of all the commands available.
+.TP
+.B \-Q
+Causes the command to be
+.I really
+quiet; the command will generate output only for serious problems.
+.TP
+.B \-q
+Causes the command to be somewhat quiet; informational messages, such
+as reports of recursion through subdirectories, are suppressed.
+.TP
+\fB\-b\fP \fIbindir\fP
+Use
+.I bindir
+as the directory where
+.SM RCS
+programs are located (CVS 1.9 and older).
+Overrides the setting of the
+.SM RCSBIN
+environment variable.
+This value should be specified as an absolute pathname.
+.TP
+\fB\-d\fP \fICVS_root_directory\fP
+Use
+.I CVS_root_directory
+as the root directory pathname of the master
+source repository.
+Overrides the setting of the
+.SM CVSROOT
+environment variable.
+This value should be specified as an absolute pathname.
+.TP
+\fB\-e\fP \fIeditor\fP
+Use
+.I editor
+to enter revision log information.
+Overrides the setting of the
+.SM CVSEDITOR
+and the
+.SM EDITOR
+environment variables.
+.TP
+.B \-f
+Do not read the
+.B cvs
+startup file (\fI~/.cvsrc\fP).
+.TP
+.B \-l
+Do not log the
+.I cvs_command
+in the command history (but execute it anyway). See the description
+of the
+.B history
+command for information on command history.
+.TP
+.B \-n
+Do not change any files. Attempt to execute the
+.IR cvs_command ,
+but only to issue reports; do not remove, update, or merge any
+existing files, or create any new files.
+.TP
+.B \-t
+Trace program execution; display messages showing the steps of
+.B cvs
+activity. Particularly useful with
+.B \-n
+to explore the potential impact of an unfamiliar command.
+.TP
+.B \-r
+Makes new working files read-only.
+Same effect as if the
+.SM CVSREAD
+environment variable is set.
+.TP
+.B \-v [ --version ]
+Displays version and copyright information for
+.BR cvs .
+.TP
+.B \-w
+Makes new working files read-write (default).
+Overrides the setting of the
+.SM CVSREAD
+environment variable.
+.TP
+.B \-x
+Encrypt all communication between the client and the server. As of
+this writing, this is only implemented when using a Kerberos
+connection.
+.TP
+\fB\-z\fP \fIcompression\-level\fP
+When transferring files across the network use
+.B gzip
+with compression level \fIcompression\-level\fP to compress and
+de-compress data as it is transferred. Requires the presence of
+the
+.SM GNU
+.B gzip
+program in the current search path at both ends of the link.
+.SH "USAGE"
+Except when requesting general help with
+.` "cvs \-H",
+you must specify a
+.I cvs_command
+to
+.B cvs
+to select a specific release control function to perform.
+Each
+.B cvs
+command accepts its own collection of options and arguments.
+However, many options are available across several commands.
+You can display a usage summary for each command by specifying the
+.B \-H
+option with the command.
+.SH "CVS STARTUP FILE"
+Normally, when CVS starts up, it reads the
+.I .cvsrc
+file from the home directory of the user reading it. This startup
+procedure can be turned off with the
+.B \-f
+flag.
+.SP
+The
+.I .cvsrc
+file lists CVS commands with a list of arguments, one command per
+line. For example, the following line in \fI.cvsrc\fP:
+.SP
+diff \-c
+.SP
+will mean that the
+.` "cvs diff"
+command will always be passed the \-c option in addition to any
+other options that are specified in the command line (in this case
+it will have the effect of producing context sensitive diffs for
+all executions of
+.` "cvs diff"
+).
+.SH "CVS COMMAND SUMMARY"
+Here are brief descriptions of all the
+.B cvs
+commands:
+.TP
+.B add
+Add a new file or directory to the repository, pending a
+.` "cvs commit"
+on the same file.
+Can only be done from within sources created by a previous
+.` "cvs checkout"
+invocation.
+Use
+.` "cvs import"
+to place whole new hierarchies of sources under
+.B cvs
+control.
+(Does not directly affect repository; changes
+working directory.)
+.TP
+.B admin
+Execute
+control functions on the source repository. (Changes
+repository directly; uses working directory without changing it.)
+.TP
+.B checkout
+Make a working directory of source files for editing. (Creates or changes
+working directory.)
+.TP
+.B commit
+Apply to the source repository changes, additions, and deletions from your
+working directory. (Changes repository.)
+.TP
+.B diff
+Show differences between files in working directory and source
+repository, or between two revisions in source repository.
+(Does not change either repository or working directory.)
+.TP
+.B export
+Prepare copies of a set of source files for shipment off site.
+Differs from
+.` "cvs checkout"
+in that no
+.B cvs
+administrative directories are created (and therefore
+.` "cvs commit"
+cannot be executed from a directory prepared with
+.` "cvs export"),
+and a symbolic tag must be specified.
+(Does not change repository; creates directory similar to working
+directories).
+.TP
+.B history
+Show reports on
+.B cvs
+commands that you or others have executed on a particular file or
+directory in the source repository. (Does not change repository or
+working directory.) History logs are kept only if enabled by creation
+of the
+.` "$CVSROOT/CVSROOT/history"
+file; see
+.BR cvs ( 5 ).
+.TP
+.B import
+Incorporate a set of updates from off-site into the source repository,
+as a ``vendor branch''. (Changes repository.)
+.TP
++.B init
++Initialize a repository by adding the CVSROOT subdirectory and some default
++control files. You must use this command or initialize the repository in
++some other way before you can use it.
++.TP
+.B log
+Display
+log information.
+(Does not change repository or working directory.)
+.TP
+.B rdiff
+Prepare a collection of diffs as a patch file between two releases in
+the repository. (Does not change repository or working directory.)
+.TP
+.B release
+Cancel a
+.` "cvs checkout",
+abandoning any changes.
+(Can delete working directory; no effect on repository.)
+.TP
+.B remove
+Remove files from the source repository, pending a
+.` "cvs commit"
+on the same files. (Does not directly affect repository;
+changes working directory.)
+.TP
+.B rtag
+Explicitly specify a symbolic tag for particular revisions of files in the
+source repository. See also
+.` "cvs tag".
+(Changes repository directly; does not require or affect
+working directory.)
+.TP
+.B status
+Show current status of files: latest version, version in working
+directory, whether working version has been edited and, optionally,
+symbolic tags in the
+.SM RCS
+file. (Does not change
+repository or working directory.)
+.TP
+.B tag
+Specify a symbolic tag for files in the repository. By default, tags
+the revisions
+that were last synchronized with your working directory. (Changes
+repository directly; uses working directory without changing it.)
+.TP
+.B update
+Bring your working directory up to date with changes from the
+repository. Merges are performed automatically when possible; a
+warning is issued if manual resolution is required for conflicting
+changes. (Changes working directory; does not change repository.)
+.SH "COMMON COMMAND OPTIONS"
+This section describes the
+.I command_options
+that are available across several
+.B cvs
+commands. Not all commands support all of these options; each option
+is only supported for commands where it makes sense. However, when
+a command has one of these options you can count on the same meaning
+for the option as in other commands. (Other command
+options, which are listed with the individual commands, may have
+different meanings from one
+.B cvs
+command to another.)
+.I "Warning:"
+the
+.B history
+command is an exception;
+it supports many options that conflict
+even with these standard options.
+.TP
+\fB\-D\fP \fIdate_spec\fP
+Use the most recent revision no later than \fIdate_spec\fP (a single
+argument, date description specifying a date in the
+past). A wide variety of date formats are supported, in particular
+ISO ("1972-09-24 20:05") or Internet ("24 Sep 1972 20:05").
+The \fIdate_spec\fP is interpreted as being in the local timezone, unless a
+specific timezone is specified.
+The specification is ``sticky'' when you use it to make a
+private copy of a source file; that is, when you get a working file
+using \fB\-D\fP, \fBcvs\fP records the date you
+specified, so that further updates in the same directory will use the
+same date (unless you explicitly override it; see the description of
+the \fBupdate\fP command).
+.B \-D
+is available with the
+.BR checkout ", " diff ", " history ", " export ", "
+.BR rdiff ", " rtag ", and "
+.B update
+commands.
+Examples of valid date specifications include:
+.in +1i
+.ft B
+.nf
+1 month ago
+2 hours ago
+400000 seconds ago
+last year
+last Monday
+yesterday
+a fortnight ago
+3/31/92 10:00:07 PST
+January 23, 1987 10:05pm
+22:00 GMT
+.fi
+.ft P
+.in -1i
+.TP
+.B \-f
+When you specify a particular date or tag to \fBcvs\fP commands, they
+normally ignore files that do not contain the tag (or did not exist on
+the date) that you specified. Use the \fB\-f\fP option if you want
+files retrieved even when there is no match for the tag or date. (The
+most recent version is used in this situation.)
+.B \-f
+is available with these commands:
+.BR checkout ", " export ", "
+.BR rdiff ", " rtag ", and " update .
+.TP
+\fB\-k\fP \fIkflag\fP
+Alter the default
+processing of keywords.
+The \fB\-k\fP option is available with the
+.BR add ", " checkout ", " diff ", " export ", "
+.BR rdiff ", and " update
+commands. Your \fIkflag\fP specification is ``sticky'' when you use
+it to create a private copy of a source file; that is, when you use
+this option with the \fBcheckout\fP or \fBupdate\fP commands,
+\fBcvs\fP associates your selected \fIkflag\fP with the file, and
+continues to use it with future \fBupdate\fP commands on the same file
+until you specify otherwise.
+.SP
+Some of the more useful \fIkflag\fPs are \-ko and \-kb (for binary files),
+and \-kv which is useful for an
+.B export
+where you wish to retain keyword information after an
+.B import
+at some other site.
+.TP
+.B \-l
+Local; run only in current working directory, rather than recurring through
+subdirectories. Available with the following commands:
+.BR checkout ", " commit ", " diff ", "
+.BR export ", " remove ", " rdiff ", " rtag ", "
+.BR status ", " tag ", and " update .
+.I Warning:
+this is not the same
+as the overall
+.` "cvs \-l"
+option, which you can specify to the
+.I left
+of a
+.B cvs
+command!
+.TP
+.B \-n
+Do
+.I not
+run any
+.BR checkout / commit / tag / update
+program. (A program can be specified to run on each of these
+activities, in the modules database; this option bypasses it.)
+Available with the
+.BR checkout ", " commit ", " export ", and "
+.B rtag
+commands.
+.I Warning:
+this is not the same
+as the overall
+.` "cvs \-n"
+option, which you can specify to the
+.I left
+of a
+.B cvs
+command!
+.TP
+.B \-P
+Prune (remove) directories that are empty after being updated, on
+.BR checkout ", or " update .
+Normally, an empty directory (one that is void of revision-controlled
+files) is left alone.
+Specifying
+.B \-P
+will cause these directories to be silently removed from your checked-out
+sources.
+This does not remove the directory from the repository, only from your
+checked out copy.
+Note that this option is implied by the
+.B \-r
+or
+.B \-D
+options of
+.BR checkout " and " export .
+.TP
+.B \-p
+Pipe the files retrieved from the repository to standard output,
+rather than writing them in the current directory. Available with the
+.BR checkout " and " update
+commands.
+.TP
+\fB\-r\fP \fItag\fP
+Use the revision specified by the
+.I tag
+argument instead of the default ``head'' revision. As well as
+arbitrary tags defined with the \fBtag\fP or \fBrtag\fP command, two
+special tags are always available:
+.` "HEAD"
+refers to the most
+recent version available in the repository, and
+.` "BASE"
+refers to the revision you last checked out into the current working
+directory.
+.SP
+The \fItag\fP specification is ``sticky'' when you use
+this option with
+.` "cvs checkout"
+or
+.` "cvs update"
+to
+make your own copy of a file: \fBcvs\fP remembers the \fItag\fP and
+continues to use it on future \fBupdate\fP commands, until you specify
+otherwise.
+.I tag
+can be either a symbolic or numeric tag.
+Specifying the
+.B \-q
+global option along with the
+.B \-r
+command option is often useful, to suppress the warning messages when the
+.SM RCS
+file does not contain the specified tag.
+.B \-r
+is available with the
+.BR checkout ", " commit ", " diff ", "
+.BR history ", " export ", "
+.BR rdiff ", " rtag ", and " update
+commands.
+.I Warning:
+this is not the same
+as the overall
+.` "cvs \-r"
+option, which you can specify to the
+.I left
+of a
+.B cvs
+command!
+.SH "CVS COMMANDS"
+Here (finally) are details on all the
+.B cvs
+commands and the options each accepts. The summary lines at the top
+of each command's description highlight three kinds of things:
+.TP 1i
+\ \ \ \ Command Options and Arguments
+Special options are described in detail below; common command options
+may appear only in the summary line.
+.TP 1i
+\ \ \ \ Working Directory, or Repository?
+Some \fBcvs\fP commands require a working directory to operate; some
+require a repository. Also, some commands \fIchange\fP the
+repository, some change the working directory, and some change
+nothing.
+.TP 1i
+\ \ \ \ Synonyms
+Many commands have synonyms, which you may find easier to
+remember (or type) than the principal name.
+.PP
+.TP
+\fBadd\fP [\fB\-k\fP \fIkflag\fP] [\fB\-m '\fP\fImessage\fP\fB'\fP] \fIfiles.\|.\|.\fP
+.I Requires:
+repository, working directory.
+.br
+.I Changes:
+working directory.
+.br
+.I Synonym:
+.B new
+.br
+Use the
+.B add
+command to create a new file or directory in the
+source repository.
+The files or directories specified with
+.B add
+must already exist in the current directory (which must have been created
+with the
+.B checkout
+command).
+To add a whole new directory hierarchy to the source repository
+(for example, files received from a third-party vendor), use the
+.` "cvs import"
+command instead.
+.SP
+If the argument to
+.` "cvs add"
+refers to an immediate sub-directory, the directory is
+created at the correct place in the
+source repository, and the necessary
+.B cvs
+administration files are created in your working directory.
+If the directory already exists in the source repository,
+.` "cvs add"
+still creates the administration files in your version of the directory.
+This allows you to use
+.` "cvs add"
+to add a particular directory to your private sources even if
+someone else created that directory after your
+.B checkout
+of the sources. You can do the following:
+.SP
+.in +1i
+.ft B
+.nf
+example% mkdir new_directory
+example% cvs add new_directory
+example% cvs update new_directory
+.fi
+.ft P
+.in -1i
+.SP
+An alternate approach using
+.` "cvs update"
+might be:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs update -d new_directory
+.fi
+.ft P
+.in -1i
+.SP
+(To add \fIany available\fP new directories to your working directory, it's
+probably simpler to use
+.` "cvs checkout"
+or
+.` "cvs update -d".)
+.SP
+The added files are not placed in the
+source repository until you use
+.` "cvs commit"
+to make the change permanent.
+Doing a
+.` "cvs add"
+on a file that was removed with the
+.` "cvs remove"
+command will resurrect the file, if no
+.` "cvs commit"
+command intervened.
+.SP
+You will have the opportunity to specify a logging message, as usual,
+when you use
+.` "cvs commit"
+to make the new file permanent. If you'd like to have another
+logging message associated with just
+.I creation
+of the file (for example, to describe the file's purpose), you can
+specify it with the
+.` "\-m \fImessage\fP"
+option to the
+.B add
+command.
+.SP
+The
+.` "-k kflag"
+option specifies the default way that this
+file will be checked out.
+The
+.` "kflag"
+argument is stored in the
+.SM RCS
+file and can be changed with
+.` "cvs admin".
+Specifying
+.` "-ko"
+is useful for checking in binaries that
+shouldn't have
+keywords expanded.
+.TP
+\fBadmin\fP [\fIrcs-options\fP] \fIfiles.\|.\|.\fP
+.I Requires:
+repository, working directory.
+.br
+.I Changes:
+repository.
+.br
+.I Synonym:
+.B rcs
+.br
+This is the
+.B cvs
+interface to assorted administrative
+facilities, similar to
+.BR rcs ( 1 ).
+This command works recursively, so extreme care should be
+used.
+.TP
+\fBcheckout\fP [\fBoptions\fP] \fImodules\fP.\|.\|.
+.I Requires:
+repository.
+.br
+.I Changes:
+working directory.
+.br
+.I Synonyms:
+.BR co ", " get
+.br
+Make a working directory containing copies of the source files specified by
+.IR modules .
+You must execute
+.` "cvs checkout"
+before using most of the other
+.B cvs
+commands, since most of them operate on your working directory.
+.SP
+\fImodules\fP are either symbolic names (themselves defined as the
+module
+.` "modules"
+in the source repository; see
+.BR cvs ( 5 ))
+for some collection of source directories and files, or paths to
+directories or files in the repository.
+.SP
+Depending on the
+.I modules
+you specify,
+.B checkout
+may recursively create directories and populate them with the appropriate
+source files.
+You can then edit these source files at any time (regardless of whether
+other software developers are editing their own copies of the sources);
+update them to include new changes applied by others to the source
+repository; or commit your work as a permanent change to the
+repository.
+.SP
+Note that
+.B checkout
+is used to create directories.
+The top-level directory created is always added to the directory
+where
+.B checkout
+is invoked, and usually has the same name as the specified
+.IR module .
+In the case of a
+.I module
+alias, the created sub-directory may have a different name, but you can be
+sure that it will be a sub-directory, and that
+.B checkout
+will show the relative path leading to each file as it is extracted into
+your private work area (unless you specify the
+.B \-Q
+global option).
+.SP
+Running
+.` "cvs checkout"
+on a directory that was already built by a prior
+.B checkout
+is also permitted, and
+has the same effect as specifying the
+.B \-d
+option to the
+.B update
+command described below.
+.SP
+The
+.I options
+permitted with
+.` "cvs checkout"
+include the standard command options
+.BR \-P ", " \-f ", "
+.BI \-k " kflag"
+\&,
+.BR \-l ", " \-n ", " \-p ", "
+.BR \-r
+.IR tag ", and"
+.BI \-D " date"\c
+\&.
+.SP
+In addition to those, you can use these special command options
+with
+.BR checkout :
+.SP
+Use the
+.B \-A
+option to reset any sticky tags, dates, or
+.B \-k
+options. (If you get a working file using one of the
+\fB\-r\fP, \fB\-D\fP, or \fB\-k\fP options, \fBcvs\fP remembers the
+corresponding tag, date, or \fIkflag\fP and continues using it on
+future updates; use the \fB\-A\fP option to make \fBcvs\fP forget these
+specifications, and retrieve the ``head'' version of the file).
+.SP
+The
+.BI \-j " branch"
+option merges the changes made between the
+resulting revision and the revision that it is based on (e.g., if
+the tag refers to a branch,
+.B cvs
+will merge all changes made in that branch into your working file).
+.SP
+With two \fB-j\fP options,
+.B cvs
+will merge in the changes between the two respective revisions.
+This can be used to ``remove'' a certain delta from your working file.
+.SP
+In addition, each \fB-j\fP option can contain on optional date
+specification which, when used with branches, can limit the chosen
+revision to one within a specific date.
+An optional date is specified by adding a colon (:) to the tag.
+An example might be what
+.` "cvs import"
+tells you to do when you have
+just imported sources that have conflicts with local changes:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs checkout -jTAG:yesterday -jTAG module
+.fi
+.ft P
+.in -1i
+.SP
+Use the
+.B \-N
+option with
+.` "\-d \fIdir\fP"
+to avoid shortening module paths in your working directory. (Normally, \fBcvs\fP shortens paths as much as possible when you specify an explicit target directory.)
+.SP
+Use the
+.B \-c
+option to copy the module file, sorted, to the standard output,
+instead of creating or modifying any files or directories in your
+working directory.
+.SP
+Use the
+.BI \-d " dir"
+option to create a directory called
+.I dir
+for the working files, instead of using the module name. Unless you
+also use \fB\-N\fP, the paths created under \fIdir\fP will be as short
+as possible.
+.SP
+Use the
+.B \-s
+option to display per-module status information stored with
+the
+.B \-s
+option within the modules file.
+.TP
+\fBcommit\fP [\fB\-lnR\fP] [\fB\-m\fP '\fIlog_message\fP' | \fB\-f\fP \fIfile\fP] [\fB\-r\fP \fIrevision\fP] [\fIfiles.\|.\|.\fP]
+.I Requires:
+working directory, repository.
+.br
+.I Changes:
+repository.
+.br
+.I Synonym:
+.B ci
+.br
+Use
+.` "cvs commit"
+when you want to incorporate changes from your working source
+files into the general source repository.
+.SP
+If you don't specify particular \fIfiles\fP to commit, all
+of the files in your working current directory are examined.
+.B commit
+is careful to change in the repository only those files that you have
+really changed. By default (or if you explicitly specify the
+.B \-R
+option), files
+in subdirectories are also examined and committed if they have
+changed; you can use the
+.B \-l
+option to limit
+.B commit
+to the current directory only.
+Sometimes you may want to force a file to be committed even though it
+is unchanged; this is achieved with the
+.B \-f
+flag, which also has the effect of disabling recursion (you can turn
+it back on with
+.B \-R
+of course).
+.SP
+.B commit
+verifies that the selected files are up to date with the current revisions
+in the source repository; it will notify you, and exit without
+committing, if any of the specified files must be made current first
+with
+.` "cvs update".
+.B commit
+does not call the
+.B update
+command for you, but rather leaves that for you to do when
+the time is right.
+.SP
+When all is well, an editor is invoked to allow you to enter a log
+message that will be written to one or more logging programs and placed in the
+source repository file.
+You can instead specify the log message on the command line with the
+.B \-m
+option, thus suppressing the editor invocation, or use the
+.B \-F
+option to specify that the argument \fIfile\fP contains the log message.
+.SP
+The
+.B \-r
+option can be used to commit to a particular symbolic or numeric revision.
+For example, to bring all your files up to the
+revision ``3.0'' (including those that haven't changed), you might do:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs commit -r3.0
+.fi
+.ft P
+.in -1i
+.SP
+.B cvs
+will only allow you to commit to a revision that is on the main trunk (a
+revision with a single dot).
+However, you can also commit to a branch revision (one that has an even
+number of dots) with the
+.B \-r
+option.
+To create a branch revision, one typically use the
+.B \-b
+option of the
+.BR rtag " or " tag
+commands.
+Then, either
+.BR checkout " or " update
+can be used to base your sources on the newly created branch.
+From that point on, all
+.B commit
+changes made within these working sources will be automatically added
+to a branch revision, thereby not perturbing main-line development in any
+way.
+For example, if you had to create a patch to the 1.2 version of the
+product, even though the 2.0 version is already under development, you
+might do:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs rtag -b -rFCS1_2 FCS1_2_Patch product_module
+example% cvs checkout -rFCS1_2_Patch product_module
+example% cd product_module
+[[ hack away ]]
+example% cvs commit
+.fi
+.ft P
+.in -1i
+.SP
+Say you have been working on some extremely experimental software, based on
+whatever revision you happened to checkout last week.
+If others in your group would like to work on this software with you, but
+without disturbing main-line development, you could commit your change to a
+new branch.
+Others can then checkout your experimental stuff and utilize the full
+benefit of
+.B cvs
+conflict resolution.
+The scenario might look like:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs tag -b EXPR1
+example% cvs update -rEXPR1
+[[ hack away ]]
+example% cvs commit
+.fi
+.ft P
+.in -1i
+.SP
+Others would simply do
+.` "cvs checkout -rEXPR1 whatever_module"
+to work with you on the experimental change.
+.TP
+\fBdiff\fP [\fB\-kl\fP] [\fIrcsdiff_options\fP] [[\fB\-r\fP \fIrev1\fP | \fB\-D\fP \fIdate1\fP] [\fB\-r\fP \fIrev2\fP | \fB\-D\fP \fIdate2\fP]] [\fIfiles.\|.\|.\fP]
+.I Requires:
+working directory, repository.
+.br
+.I Changes:
+nothing.
+.br
+You can compare your working files with revisions in the source
+repository, with the
+.` "cvs diff"
+command. If you don't specify a particular revision, your files
+are compared with the revisions they were based on. You can also use
+the standard
+.B cvs
+command option
+.B \-r
+to specify a particular revision to compare your files with. Finally,
+if you use
+.B \-r
+twice, you can see differences between two revisions in the
+repository.
+You can also specify
+.B \-D
+options to diff against a revision in the past.
+The
+.B \-r
+and
+.B \-D
+options can be mixed together with at most two options ever specified.
+.SP
+See
+.BR rcsdiff ( 1 )
+for a list of other accepted options.
+.SP
+If you don't specify any files,
+.B diff
+will display differences for all those files in the current directory
+(and its subdirectories, unless you use the standard option
+.BR \-l )
+that
+differ from the corresponding revision in the source repository
+(i.e. files that
+.I you
+have changed), or that differ from the revision specified.
+.TP
+\fBexport\fP [\-\fBf\|lNnQq\fP] \fB\-r\fP \fIrev\fP\||\|\fB\-D\fP \fIdate\fP [\fB\-d\fP \fIdir\fP] [\fB\-k\fP \fIkflag\fP] \fImodule\fP.\|.\|.
+.I Requires:
+repository.
+.br
+.I Changes:
+current directory.
+.br
+This command is a variant of
+.` "cvs checkout";
+use it when you want a copy of the source for \fImodule\fP
+without the \fBcvs\fP administrative directories. For example, you
+might use
+.` "cvs export"
+to prepare source for shipment
+off-site. This command \fIrequires\fP that you specify a date or tag
+(with \fB\-D\fP or \fB\-r\fP), so that you can count on reproducing
+the source you ship to others.
+.SP
+The only non-standard options are
+.` "\-d \fIdir\fP"
+(write the
+source into directory \fIdir\fP) and
+.` "\-N"
+(don't shorten
+module paths).
+These have the same meanings as the same options in
+.` "cvs checkout".
+.SP
+The
+.B \-kv
+option is useful when
+.B export
+is used.
+This causes any
+keywords to be expanded such that an
+.B import
+done at some other site will not lose the keyword revision information.
+Other \fIkflag\fPs may be used with
+.` "cvs export"
+and are described in
+.BR co ( 1 ).
+.TP
+\fBhistory\fP [\fB\-\fP\fIreport\fP] [\fB\-\fP\fIflags\fP] [\fB\-\fP\fIoptions args\fP] [\fIfiles\fP.\|.\|.]
+.I Requires:
+the file
+.` "$CVSROOT/CVSROOT/history"
+.br
+.I Changes:
+nothing.
+.br
+\fBcvs\fP keeps a history file that tracks each use of the
+\fBcheckout\fP, \fBcommit\fP, \fBrtag\fP, \fBupdate\fP, and \fBrelease\fP
+commands. You can use
+.` "cvs history"
+to display this
+information in various formats.
+.SP
+.I Warning:
+.` "cvs history"
+uses
+.` "\-f",
+.` "\-l",
+.` "\-n",
+and
+.` "\-p"
+in ways that conflict with the
+descriptions in
+.SM
+COMMON COMMAND OPTIONS\c
+\&.
+.SP
+Several options (shown above as \fB\-\fP\fIreport\fP) control what
+kind of report is generated:
+.TP 1i
+.B \ \ \ \ \ \ \-c
+Report on each time \fBcommit\fP was used (i.e., each time the
+repository was modified).
+.TP 1i
+\fB\ \ \ \ \ \ \-m\fP \fImodule\fP
+Report on a particular \fImodule\fP. (You can meaningfully use
+\fB\-m\fP more than once on the command line.)
+.TP 1i
+.B \ \ \ \ \ \ \-o
+Report on checked-out modules.
+.TP 1i
+.B \ \ \ \ \ \ \-T
+Report on all tags.
+.TP 1i
+\fB\ \ \ \ \ \ \-x\fP \fItype\fP
+Extract a particular set of record types \fIX\fP from the \fBcvs\fP
+history. The types are indicated by single letters, which you may
+specify in combination.
+Certain commands have a single record type: \fBcheckout\fP (type `O'),
+\fBrelease\fP (type `F'), and \fBrtag\fP (type `T'). One of four
+record types may result from an \fBupdate\fP: `W', when the working copy
+of a file is deleted during update (because it was gone from the
+repository); `U', when a working file was copied from the
+repository; `G', when a merge was necessary and it succeeded; and 'C',
+when a merge was necessary but collisions were detected (requiring
+manual merging). Finally, one of three record types results from
+\fBcommit\fP: `M', when a file was modified; `A', when a file is first
+added; and `R', when a file is removed.
+.TP 1i
+.B \ \ \ \ \ \ \-e
+Everything (all record types); equivalent to specifying
+.` "\-xMACFROGWUT".
+.TP 1i
+\fB\ \ \ \ \ \ \-z\fP \fIzone\fP
+Use time zone
+.I zone
+when outputting history records.
+The zone name
+.B LT
+stands for local time;
+numeric offsets stand for hours and minutes ahead of UTC.
+For example,
+.B +0530
+stands for 5 hours and 30 minutes ahead of (i.e. east of) UTC.
+.PP
+.RS .5i
+The options shown as \fB\-\fP\fIflags\fP constrain the report without
+requiring option arguments:
+.RE
+.TP 1i
+.B \ \ \ \ \ \ \-a
+Show data for all users (the default is to show data only for the user
+executing
+.` "cvs history").
+.TP 1i
+.B \ \ \ \ \ \ \-l
+Show last modification only.
+.TP 1i
+.B \ \ \ \ \ \ \-w
+Show only the records for modifications done from the same working
+directory where
+.` "cvs history"
+is executing.
+.PP
+.RS .5i
+The options shown as \fB\-\fP\fIoptions args\fP constrain the report
+based on an argument:
+.RE
+.TP 1i
+\fB\ \ \ \ \ \ \-b\fP \fIstr\fP
+Show data back to a record containing the string \fIstr\fP in either
+the module name, the file name, or the repository path.
+.TP 1i
+\fB\ \ \ \ \ \ \-D\fP \fIdate\fP
+Show data since \fIdate\fP.
+.TP 1i
+\fB\ \ \ \ \ \ \-p\fP \fIrepository\fP
+Show data for a particular source repository (you can specify several
+\fB\-p\fP options on the same command line).
+.TP 1i
+\fB\ \ \ \ \ \ \-r\fP \fIrev\fP
+Show records referring to revisions since the revision or tag
+named \fIrev\fP appears in individual RCS files.
+Each
+.SM RCS
+file is searched for the revision or tag.
+.TP 1i
+\fB\ \ \ \ \ \ \-t\fP \fItag\fP
+Show records since tag \fItag\fP was last added to the history file.
+This differs from the \fB-r\fP flag above in that it reads
+only the history file, not the
+.SM RCS
+files, and is much faster.
+.TP 1i
+\fB\ \ \ \ \ \ \-u\fP \fIname\fP
+Show records for user \fIname\fP.
+.PP
+.TP
+\fBimport\fP [\fB\-\fP\fIoptions\fP] \fIrepository vendortag releasetag\fP.\|.\|.
+.I Requires:
+Repository, source distribution directory.
+.br
+.I Changes:
+repository.
+.br
+Use
+.` "cvs import"
+to incorporate an entire source
+distribution from an outside source (e.g., a source vendor) into your
+source repository directory. You can use this command both for
+initial creation of a repository, and for wholesale updates to the
+module form the outside source.
+.SP
+The \fIrepository\fP argument gives a directory name (or a path to a
+directory) under the CVS root directory for repositories; if the
+directory did not exist, \fBimport\fP creates it.
+.SP
+When you use \fBimport\fP for updates to source that has been modified in your
+source repository (since a prior \fBimport\fP), it
+will notify you of any files that conflict in the two branches of
+development; use
+.` "cvs checkout -j"
+to reconcile the differences, as \fBimport\fP instructs you to do.
+.SP
+By default, certain file names are ignored during
+.` "cvs import":
+names associated with
+.SM CVS
+administration, or with other common source control systems; common
+names for patch files, object files, archive files, and editor backup
+files; and other names that are usually artifacts of assorted utilities.
+For an up to date list of ignored file names, see the Cederqvist manual (as
+described in the SEE ALSO section of this manpage).
+.SP
+The outside source is saved in a first-level
+branch, by default
+.` "1.1.1".
+Updates are leaves of this
+branch; for example, files from the first imported collection of
+source will be revision
+.` "1.1.1.1",
+then files from the first
+imported update will be revision
+.` "1.1.1.2",
+and so on.
+.SP
+At least three arguments are required. \fIrepository\fP is needed to
+identify the collection of source. \fIvendortag\fP is a tag for the
+entire branch (e.g., for
+.` "1.1.1").
+You must also specify at
+least one \fIreleasetag\fP to identify the files at the leaves created
+each time you execute
+.` "cvs import".
+.SP
+One of the standard
+.B cvs
+command options is available: \fB\-m\fP
+\fImessage\fP. If you do not specify a logging message with
+\fB\-m\fP, your editor is invoked (as with \fBcommit\fP) to allow you
+to enter one.
+.SP
+There are three additional special options.
+.SP
+Use
+.` "\-d"
+to specify that each file's time of last modification should be used
+for the checkin date and time.
+.SP
+Use
+.` "\-b \fIbranch\fP"
+to specify a first-level branch other
+than
+.` "1.1.1".
+.SP
+Use
+.` "\-I \fIname\fP"
+to specify file names that should be
+ignored during \fBimport\fP. You can use this option repeatedly.
+To avoid ignoring any files at all (even those ignored by default),
+specify
+.` "\-I !".
+.TP
+\fBlog\fP [\fB\-l\fP] \fIrlog-options [files\fP\|.\|.\|.]
+.I Requires:
+repository, working directory.
+.br
+.I Changes:
+nothing.
+.br
+.I Synonym:
+.B rlog
+.br
+Display log information for \fIfiles\fP.
+Among the more useful options are \fB\-h\fP
+to display only the header (including tag definitions, but omitting
+most of the full log); \fB\-r\fP to select logs on particular
+revisions or ranges of revisions; and \fB\-d\fP to select particular
+dates or date ranges. See
+.BR rlog ( 1 )
+for full explanations.
+This command is recursive by default, unless the
+.B \-l
+option is specified.
+.TP
+\fBrdiff\fP [\fB\-\fP\fIflags\fP] [\fB\-V\fP \fIvn\fP] [\fB\-r\fP \fIt\fP|\fB\-D\fP \fId\fP [\fB\-r\fP \fIt2\fP|\fB\-D\fP \fId2\fP]] \fImodules\|.\|.\|.\fP
+.I Requires:
+repository.
+.br
+.I Changes:
+nothing.
+.br
+.I Synonym:
+.B patch
+.br
+Builds a Larry Wall format
+.BR patch ( 1 )
+file between two releases, that can be fed directly into the
+.B patch
+program to bring an old release up-to-date with the new release.
+(This is one of the few \fBcvs\fP commands that operates directly from
+the repository, and doesn't require a prior
+.BR checkout .)
+The diff output is sent to the standard output device.
+You can specify (using the standard \fB\-r\fP and \fB\-D\fP options)
+any combination of one or two revisions or dates.
+If only one revision or date is specified, the
+patch file reflects differences between that revision or date and the
+current ``head'' revisions in the
+.SM RCS
+file.
+.SP
+Note that if the software release affected
+is contained in more than one directory, then it may be necessary to
+specify the
+.B \-p
+option to the
+.B patch
+command when patching the old sources, so that
+.B patch
+is able to find the files that are located in other directories.
+.SP
+The standard option \fIflags\fP \fB\-f\fP, and \fB\-l\fP
+are available with this command. There are also several
+special options flags:
+.SP
+If you use the
+.B \-s
+option, no patch output is produced.
+Instead, a summary of the changed or added files between the two
+releases is sent to the standard output device.
+This is useful for finding out, for example, which files have changed
+between two dates or revisions.
+.SP
+If you use the
+.B \-t
+option, a diff of the top two revisions is sent to the standard output device.
+This is most useful for seeing what the last change to a file was.
+.SP
+If you use the
+.B \-u
+option, the patch output uses the newer ``unidiff'' format for context
+diffs.
+.SP
+You can use
+.B \-c
+to explicitly specify the
+.` "diff \-c"
+form of context diffs
+(which is the default), if you like.
+.TP
+\fBrelease\fP [\fB\-dQq\fP] \fImodules\fP\|.\|.\|.
+.I Requires:
+Working directory.
+.br
+.I Changes:
+Working directory, history log.
+.br
+This command is meant to safely cancel the effect of
+.` "cvs checkout'.
+Since
+.B cvs
+doesn't lock files, it isn't strictly necessary to use this command.
+You can always simply delete your working directory, if you
+like; but you risk losing changes you may have forgotten, and you
+leave no trace in the
+.B cvs
+history file that you've abandoned your checkout.
+.SP
+Use
+.` "cvs release"
+to avoid these problems. This command
+checks that no un-committed changes are present; that you are
+executing it from immediately above, or inside, a \fBcvs\fP working
+directory; and that the repository recorded for your files is the same
+as the repository defined in the module database.
+.SP
+If all these conditions are true,
+.` "cvs release"
+leaves a
+record of its execution (attesting to your intentionally abandoning
+your checkout) in the
+.B cvs
+history log.
+.SP
+You can use the \fB\-d\fP flag to request that your working copies of
+the source files be deleted if the \fBrelease\fP succeeds.
+.TP
+\fBremove\fP [\fB\-lR\fP] [\fIfiles\|.\|.\|.\fP]
+.I Requires:
+Working directory.
+.br
+.I Changes:
+Working directory.
+.br
+.I Synonyms:
+.BR rm ", " delete
+.br
+Use this command to declare that you wish to remove \fIfiles\fP from
+the source repository. Like most
+.B cvs
+commands,
+.` "cvs remove"
+works on files in your working
+directory, not directly on the repository. As a safeguard, it also
+requires that you first erase the specified files from your working
+directory.
+.SP
+The files are not actually removed until you apply your changes to the
+repository with
+.BR commit ;
+at that point, the corresponding
+.SM RCS
+files in the source repository are
+.I moved
+into the
+.` "Attic"
+directory (also within the source repository).
+.SP
+This command is recursive by default, scheduling all physically removed
+files that it finds for removal by the next
+.BR commit .
+Use the
+.B \-l
+option to avoid this recursion, or just specify that actual files that you
+wish remove to consider.
+.TP
+\fBrtag\fP [\fB\-f\|alnRQq\fP] [\fB\-b\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP | \fB\-D\fP \fIdate\fP] \fIsymbolic_tag\fP \fImodules\|.\|.\|.\fP
+.I Requires:
+repository.
+.br
+.I Changes:
+repository.
+.br
+.I Synonym:
+.B rfreeze
+.br
+You can use this command to assign symbolic tags to particular,
+explicitly specified source versions in the repository.
+.` "cvs rtag"
+works directly on the repository contents (and requires no
+prior
+.BR checkout ).
+Use
+.` "cvs tag"
+instead, to base the selection of
+versions to tag on the contents of your working directory.
+.SP
+In general, tags (often the symbolic names of software distributions)
+should not be removed, but the
+.B \-d
+option is available as a means to remove completely obsolete symbolic names
+if necessary (as might be the case for an Alpha release, say).
+.SP
+.` "cvs rtag"
+will not move a tag that already exists. With the \fB\-F\fP option,
+however,
+.` "cvs rtag"
+will re-locate any instance of \fIsymbolic_tag\fP that already exists
+on that file to the new repository versions. Without the \fB\-F\fP
+option, attempting to use
+.` "cvs rtag"
+to apply a tag that already exists on that file will produce an error
+message.
+.SP
+The \fB-b\fP option makes the tag a ``branch'' tag, allowing
+concurrent, isolated development.
+This is most useful for creating a patch to a previously released software
+distribution.
+.SP
+You can use the standard \fB\-r\fP and \fB\-D\fP options to tag only those
+files that already contain a certain tag. This method would be used
+to rename a tag: tag only the files identified by the old tag, then delete the
+old tag, leaving the new tag on exactly the same files as the old tag.
+.SP
+.B rtag
+executes recursively by default, tagging all subdirectories of
+\fImodules\fP you specify in the argument. You can restrict its
+operation to top-level directories with the standard \fB\-l\fP option;
+or you can explicitly request recursion with \fB\-R\fP.
+.SP
+The modules database can specify a program to execute whenever a tag
+is specified; a typical use is to send electronic mail to a group of
+interested parties. If you want to bypass that program, use the
+standard \fB\-n\fP option.
+.SP
+Use the
+.B \-a
+option to have
+.B rtag
+look in the
+.` "Attic"
+for removed files that contain the specified tag.
+The tag is removed from these files, which makes it convenient to re-use a
+symbolic tag as development continues (and files get removed from the
+up-coming distribution).
+.TP
+\fBstatus\fP [\fB\-lRqQ\fP] [\fB\-v\fP] [\fIfiles\fP\|.\|.\|.]
+.I Requires:
+working directory, repository.
+.br
+.I Changes:
+nothing.
+.br
+Display a brief report on the current status of \fIfiles\fP with
+respect to the source repository, including any ``sticky'' tags,
+dates, or \fB\-k\fP options. (``Sticky'' options will restrict how
+.` "cvs update"
+operates until you reset them; see the
+description of
+.` "cvs update \-A\|.\|.\|.".)
+.SP
+You can also use this command to anticipate the potential impact of a
+.` "cvs update"
+on your working source directory. If you do
+not specify any \fIfiles\fP explicitly, reports are shown for all
+files that \fBcvs\fP has placed in your working directory. You can
+limit the scope of this search to the current directory itself (not
+its subdirectories) with the standard \fB\-l\fP option flag; or you
+can explicitly request recursive status reports with the \fB\-R\fP
+option.
+.SP
+The
+.B \-v
+option causes the symbolic tags for the
+.SM RCS
+file to be displayed as well.
+.TP
+\fBtag\fP [\fB\-lQqR\fP] [\fB\-F\fP] [\fB\-b\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP | \fB\-D\fP \fIdate\fP] [\fB\-f\fP] \fIsymbolic_tag\fP [\fIfiles\fP\|.\|.\|.\|]
+.I Requires:
+working directory, repository.
+.br
+.I Changes:
+repository.
+.br
+.I Synonym:
+.B freeze
+.br
+Use this command to assign symbolic tags to the nearest repository
+versions to your working sources. The tags are applied immediately to
+the repository, as with \fBrtag\fP.
+.SP
+One use for tags is to record a ``snapshot'' of the current sources
+when the software freeze date of a project arrives. As bugs are fixed
+after the freeze date, only those changed sources that are to be part
+of the release need be re-tagged.
+.SP
+The symbolic tags are meant to permanently record which revisions of which
+files were used in creating a software distribution.
+The
+.BR checkout ,
+.B export
+and
+.B update
+commands allow you to extract an exact copy of a tagged release at any time in
+the future, regardless of whether files have been changed, added, or removed
+since the release was tagged.
+.SP
+You can use the standard \fB\-r\fP and \fB\-D\fP options to tag only those
+files that already contain a certain tag. This method would be used
+to rename a tag: tag only the files identified by the old tag, then delete the
+old tag, leaving the new tag on exactly the same files as the old tag.
+.SP
+Specifying the \fB\-f\fP flag in addition to the \fB\-r\fP or \fB\-D\fP
+flags will tag those files named on the command line even if they do not
+contain the old tag or did not exist on the specified date.
+.SP
+By default (without a \fB\-r\fP or \fB\-D\fP flag)
+the versions to be tagged are supplied
+implicitly by the \fBcvs\fP records of your working files' history
+rather than applied explicitly.
+.SP
+If you use
+.` "cvs tag \-d \fIsymbolic_tag\fP\|.\|.\|.",
+the
+symbolic tag you specify is
+.I deleted
+instead of being added. \fIWarning\fP: Be very certain of your ground
+before you delete a tag; doing this effectively discards some
+historical information, which may later turn out to have been valuable.
+.SP
+.` "cvs tag"
+will not move a tag that already exists. With the \fB\-F\fP option,
+however,
+.` "cvs tag"
+will re-locate any instance of \fIsymbolic_tag\fP that already exists
+on that file to the new repository versions. Without the \fB\-F\fP
+option, attempting to use
+.` "cvs tag"
+to apply a tag that already exists on that file will produce an error
+message.
+.SP
+The \fB-b\fP option makes the tag a ``branch'' tag, allowing
+concurrent, isolated development.
+This is most useful for creating a patch to a previously released software
+distribution.
+.SP
+Normally,
+.B tag
+executes recursively through subdirectories; you can prevent this by
+using the standard \fB\-l\fP option, or specify the recursion
+explicitly by using \fB\-R\fP.
+.TP
+\fBupdate\fP [\fB\-ACdf\|lPpQqR\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP|\fB\-D\fP \fIdate\fP] \fIfiles\|.\|.\|.\fP
+.I Requires:
+repository, working directory.
+.br
+.I Changes:
+working directory.
+.br
+After you've run
+.B checkout
+to create your private copy of source from the common repository,
+other developers will continue changing the central source. From time
+to time, when it is convenient in your development process, you can
+use the
+.B update
+command
+from within your working directory to reconcile your work with any
+revisions applied to the source repository since your last
+.B checkout
+or
+.BR update .
+.SP
+.B update
+keeps you informed of its progress by printing a line for each file,
+prefaced with one of the characters
+.` "U A R M C ?"
+to indicate the status of the file:
+.TP 1i
+\fBU\fP \fIfile\fP
+The file was brought \fIup to date\fP with respect to the repository.
+This is done for any file that exists in the repository but not in
+your source, and for files that you haven't changed but are not the most
+recent versions available in the repository.
+.TP 1i
+\fBA\fP \fIfile\fP
+The file has been \fIadded\fP to your private copy of the sources, and
+will be added to the
+source repository when you run
+.` "cvs commit"
+on the file.
+This is a reminder to you that the file needs to be committed.
+.TP 1i
+\fBR\fP \fIfile\fP
+The file has been \fIremoved\fP from your private copy of the sources, and
+will be removed from the
+source repository when you run
+.` "cvs commit"
+on the file.
+This is a reminder to you that the file needs to be committed.
+.TP 1i
+\fBM\fP \fIfile\fP
+The file is \fImodified\fP in your working directory.
+.` "M"
+can indicate one of two states for a file you're working on: either
+there were no modifications to the same file in the repository, so
+that your file remains as you last saw it; or there were modifications
+in the repository as well as in your copy, but they were
+\fImerged\fP successfully, without conflict, in your working
+directory.
+.TP 1i
+\fBC\fP \fIfile\fP
+A \fIconflict\fP was detected while trying to merge your changes to
+\fIfile\fP with changes from the source repository. \fIfile\fP (the
+copy in your working directory) is now the result of merging
+the two versions; an unmodified copy of your file is also
+in your working directory, with the name `\fB.#\fP\fIfile\fP\fB.\fP\fIversion\fP',
+where
+.I version
+is the
+revision that your modified file started from.
+(Note that some systems automatically purge files that begin with
+\&
+.` ".#"
+if they have not been accessed for a few days.
+If you intend to keep a copy of your original file, it is a very good
+idea to rename it.)
+.TP 1i
+\fB?\fP \fIfile\fP
+\fIfile\fP is in your working directory, but does not correspond to
+anything in the source repository, and is not in the list of files
+for \fBcvs\fP to ignore (see the description of the \fB\-I\fP option).
+.PP
+.RS .5i
+.SP
+Use the
+.B \-A
+option to reset any sticky tags, dates, or
+.B \-k
+options. (If you get a working copy of a file by using one of the
+\fB\-r\fP, \fB\-D\fP, or \fB\-k\fP options, \fBcvs\fP remembers the
+corresponding tag, date, or \fIkflag\fP and continues using it on
+future updates; use the \fB\-A\fP option to make \fBcvs\fP forget these
+specifications, and retrieve the ``head'' version of the file).
+.SP
+The \fB\-j\fP\fIbranch\fP option
+merges the changes made between the
+resulting revision and the revision that it is based on (e.g., if
+the tag refers to a branch,
+.B cvs
+will merge all changes made in
+that branch into your working file).
+.SP
+With two \fB-j\fP options,
+.B cvs
+will merge in the changes between the two respective revisions.
+This can be used to ``remove'' a certain delta from your working file.
+E.g., If the file foo.c is based on
+revision 1.6 and I want to remove the changes made between 1.3 and
+1.5, I might do:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs update -j1.5 -j1.3 foo.c # note the order...
+.fi
+.ft P
+.in -1i
+.SP
+In addition, each \fB-j\fP option can contain on optional date
+specification which, when used with branches, can limit the chosen
+revision to one within a specific date.
+An optional date is specified by adding a colon (:) to the tag.
+.SP
+.in +1i
+.ft B
+.nf
+-jSymbolic_Tag:Date_Specifier
+.fi
+.ft P
+.in -1i
+.SP
+Use the
+.B \-d
+option to create any directories that exist in the repository if they're
+missing from the working directory. (Normally, update acts only on
+directories and files that were already enrolled in your
+working directory.) This is useful for updating directories
+that were created in the repository since the initial
+\fBcheckout\fP; but it has an unfortunate side effect. If you
+deliberately avoided certain directories in the repository when you
+created your working directory (either through use of a module name or by
+listing explicitly the files and directories you wanted on the
+command line), then updating with
+.B \-d
+will create those directories, which may not be what you want.
+.SP
+Use \fB\-I\fP \fIname\fP to ignore files whose names match \fIname\fP
+(in your working directory) during the update. You can specify
+\fB\-I\fP more than once on the command line to specify several files
+to ignore. By default,
+\fBupdate\fP ignores files whose names match certain patterns; for
+an up to date list of ignored file names, see the Cederqvist manual (as
+described in the SEE ALSO section of this manpage).
+.SP
+Use
+.` "\-I !"
+to avoid ignoring any files at all.
+.SP
+Use the
+.` "\-C"
+option to overwrite locally modified files with clean copies from
+the repository (the modified file is saved in
+`\fB.#\fP\fIfile\fP\fB.\fP\fIrevision\fP', however).
+.SP
+The standard \fBcvs\fP command options \fB\-f\fP, \fB\-k\fP,
+\fB\-l\fP, \fB\-P\fP, \fB\-p\fP, and \fB\-r\fP
+are also available with \fBupdate\fP.
+.RE
+.SH "FILES"
+For more detailed information on
+.B cvs
+supporting files, see
+.BR cvs ( 5 ).
+.LP
+.I
+Files in home directories:
+.TP
+\&.cvsrc
+The
+.B cvs
+initialisation file. Lines in this file can be used to specify default
+options for each
+.B cvs
+command. For example the line
+.` "diff \-c"
+will ensure that
+.` "cvs diff"
+is always passed the
+.B \-c
+option in addition to any other options passed on the command line.
+.TP
+\&.cvswrappers
+Specifies wrappers to be used in addition to those specified in the
+CVSROOT/cvswrappers file in the repository.
+.LP
+.I
+Files in working directories:
+.TP
+CVS
+A directory of \fBcvs\fP administrative files.
+.I
+Do not delete.
+.TP
+CVS/Entries
+List and status of files in your working directory.
+.TP
+CVS/Entries.Backup
+A backup of
+.` "CVS/Entries".
+.TP
+CVS/Entries.Static
+Flag: do not add more entries on
+.` "cvs update".
+.TP
+CVS/Root
+Pathname to the repository (
+.SM CVSROOT
+) location at the time of checkout. This file is used instead
+of the
+.SM CVSROOT
+environment variable if the environment variable is not
+set. A warning message will be issued when the contents of this
+file and the
+.SM CVSROOT
+environment variable differ. The file may be over-ridden by the
+presence of the
+.SM CVS_IGNORE_REMOTE_ROOT
+environment variable.
+.TP
+CVS/Repository
+Pathname to the corresponding directory in the source repository.
+.TP
+CVS/Tag
+Contains the per-directory ``sticky'' tag or date information.
+This file is created/updated when you specify
+.B \-r
+or
+.B \-D
+to the
+.B checkout
+or
+.B update
+commands, and no files are specified.
+.TP
+CVS/Checkin.prog
+Name of program to run on
+.` "cvs commit".
+.TP
+CVS/Update.prog
+Name of program to run on
+.` "cvs update".
+.LP
+.I
+Files in source repositories:
+.TP
+$CVSROOT/CVSROOT
+Directory of global administrative files for repository.
+.TP
+CVSROOT/commitinfo,v
+Records programs for filtering
+.` "cvs commit"
+requests.
+.TP
+CVSROOT/cvswrappers,v
+Records
+.B cvs
+wrapper commands to be used when checking files into and out of the
+repository. Wrappers allow the file or directory to be processed
+on the way in and out of CVS. The intended uses are many, one
+possible use would be to reformat a C file before the file is checked
+in, so all of the code in the repository looks the same.
+.TP
+CVSROOT/editinfo,v
+Records programs for editing/validating
+.` "cvs commit"
+log entries.
+.TP
+CVSROOT/history
+Log file of \fBcvs\fP transactions.
+.TP
+CVSROOT/loginfo,v
+Records programs for piping
+.` "cvs commit"
+log entries.
+.TP
+CVSROOT/modules,v
+Definitions for modules in this repository.
+.TP
+CVSROOT/rcsinfo,v
+Records pathnames to templates used during a
+.` "cvs commit"
+operation.
+.TP
+CVSROOT/taginfo,v
+Records programs for validating/logging
+.` "cvs tag"
+and
+.` "cvs rtag"
+operations.
+.TP
+MODULE/Attic
+Directory for removed source files.
+.TP
+#cvs.lock
+A lock directory created by
+.B cvs
+when doing sensitive changes to the
+source repository.
+.TP
+#cvs.tfl.\fIpid\fP
+Temporary lock file for repository.
+.TP
+#cvs.rfl.\fIpid\fP
+A read lock.
+.TP
+#cvs.wfl.\fIpid\fP
+A write lock.
+.SH "ENVIRONMENT VARIABLES"
+.TP
+.SM CVSROOT
+Should contain the full pathname to the root of the
+.B cvs
+source repository (where the
+.SM RCS
+files are kept). This information must be available to \fBcvs\fP for
+most commands to execute; if
+.SM CVSROOT
+is not set, or if you wish to override it for one invocation, you can
+supply it on the command line:
+.` "cvs \-d \fIcvsroot cvs_command\fP\|.\|.\|."
+You may not need to set
+.SM CVSROOT
+if your \fBcvs\fP binary has the right path compiled in; use
+.` "cvs \-v"
+to display all compiled-in paths.
+.TP
+.SM CVSREAD
+If this is set,
+.B checkout
+and
+.B update
+will try hard to make the files in your working directory read-only.
+When this is not set, the default behavior is to permit modification
+of your working files.
+.TP
+.SM RCSBIN
+Specifies the full pathname where to find
+.SM RCS
+programs, such as
+.BR co ( 1 )
+and
+.BR ci ( 1 )
+(CVS 1.9 and older).
+.TP
+.SM CVSEDITOR
+Specifies the program to use for recording log messages during
+.BR commit .
+If not set, the
+.SM EDITOR
+environment variable is used instead.
+If
+.SM EDITOR
+is not set either, the default is
+.BR /usr/ucb/vi .
+.TP
+.SM CVS_IGNORE_REMOTE_ROOT
+If this variable is set then
+.B cvs
+will ignore all references to remote repositories in the CVS/Root file.
+.TP
+.SM CVS_RSH
+.B cvs
+uses the contents of this variable to determine the name of the
+remote shell command to use when starting a
+.B cvs
+server. If this variable is not set then
+.` "rsh"
+is used.
+.TP
+.SM CVS_SERVER
+.B cvs
+uses the contents of this variable to determine the name of the
+.B cvs
+server command. If this variable is not set then
+.` "cvs"
+is used.
+.TP
+.SM CVSWRAPPERS
+This variable is used by the
+.` "cvswrappers"
+script to determine the name of the wrapper file, in addition to the
+wrappers defaults contained in the repository
+.SM (CVSROOT/cvswrappers)
+and the user's home directory (~/.cvswrappers).
+.SH "AUTHORS"
+.TP
+Dick Grune
+Original author of the
+.B cvs
+shell script version posted to
+.B comp.sources.unix
+in the volume6 release of December, 1986.
+Credited with much of the
+.B cvs
+conflict resolution algorithms.
+.TP
+Brian Berliner
+Coder and designer of the
+.B cvs
+program itself in April, 1989, based on the original work done by Dick.
+.TP
+Jeff Polk
+Helped Brian with the design of the
+.B cvs
+module and vendor branch support and author of the
+.BR checkin ( 1 )
+shell script (the ancestor of
+.` "cvs import").
+.TP
+And many others too numerous to mention here.
+.SH "SEE ALSO"
+The most comprehensive manual for CVS is
+Version Management with CVS by Per Cederqvist et al. Depending on
+your system, you may be able to get it with the
+.B info cvs
+command or it may be available as cvs.ps (postscript), cvs.texinfo
+(texinfo source), or cvs.html.
+.SP
+For CVS updates, more information on documentation, software related
+to CVS, development of CVS, and more, see:
+.in +1i
+.B http://www.cyclic.com
+.B http://www.loria.fr/~molli/cvs-index.html
+.in -1i
+.SP
+.BR ci ( 1 ),
+.BR co ( 1 ),
+.BR cvs ( 5 ),
+.BR cvsbug ( 8 ),
+.BR diff ( 1 ),
+.BR grep ( 1 ),
+.BR patch ( 1 ),
+.BR rcs ( 1 ),
+.BR rcsdiff ( 1 ),
+.BR rcsmerge ( 1 ),
+.BR rlog ( 1 ).
diff --git a/gnu/dist/cvs/man/cvs.5 b/gnu/dist/cvs/man/cvs.5
new file mode 100644
index 00000000000..fb2bcec0a09
--- /dev/null
+++ b/gnu/dist/cvs/man/cvs.5
@@ -0,0 +1,325 @@
+.TH cvs 5 "12 February 1992"
+.\" Full space in nroff; half space in troff
+.de SP
+.if n .sp
+.if t .sp .5
+..
+.SH NAME
+cvs \- Concurrent Versions System support files
+.SH SYNOPSIS
+.hy 0
+.na
+.TP
+.B $CVSROOT/CVSROOT/commitinfo,v
+.TP
+.B $CVSROOT/CVSROOT/cvsignore,v
+.TP
+.B $CVSROOT/CVSROOT/cvswrappers,v
+.TP
+.B $CVSROOT/CVSROOT/editinfo,v
+.TP
+.B $CVSROOT/CVSROOT/history
+.TP
+.B $CVSROOT/CVSROOT/loginfo,v
+.TP
+.B $CVSROOT/CVSROOT/modules,v
+.TP
+.B $CVSROOT/CVSROOT/rcsinfo,v
+.TP
+.B $CVSROOT/CVSROOT/taginfo,v
+.ad b
+.hy 1
+.SH DESCRIPTION
+.B cvs
+is a system for providing source control to hierarchical collections
+of source directories. Commands and procedures for using \fBcvs\fP
+are described in
+.BR cvs ( 1 ).
+.SP
+.B cvs
+manages \fIsource repositories\fP, the directories containing master
+copies of the revision-controlled files, by copying particular
+revisions of the files to (and modifications back from) developers'
+private \fIworking directories\fP. In terms of file structure, each
+individual source repository is an immediate subdirectory of
+\fB$CVSROOT\fP.
+.SP
+The files described here are supporting files; they do not have to
+exist for \fBcvs\fP to operate, but they allow you to make \fBcvs\fP
+operation more flexible.
+.SP
+You can use the `\|modules\|' file to define symbolic names for
+collections of source maintained with \fBcvs\fP. If there is no
+`\|modules\|' file, developers must specify complete path names
+(absolute, or relative to \fB$CVSROOT\fP) for the files they wish to
+manage with \fBcvs\fP commands.
+.SP
+You can use the `\|commitinfo\|' file to define programs to execute
+whenever `\|\fBcvs commit\fP\|' is about to execute.
+These programs are used for ``pre-commit'' checking to verify that the
+modified, added, and removed files are really ready to be committed.
+Some uses for this check might be to turn off a portion (or all) of the
+source repository from a particular person or group.
+Or, perhaps, to verify that the changed files conform to the site's
+standards for coding practice.
+.SP
+You can use the `\|cvswrappers\|' file to record
+.B cvs
+wrapper commands to be used when checking files into and out of the
+repository. Wrappers allow the file or directory to be processed
+on the way in and out of CVS. The intended uses are many, one
+possible use would be to reformat a C file before the file is checked
+in, so all of the code in the repository looks the same.
+.SP
+You can use the `\|loginfo\|' file to define programs to execute after
+any
+.BR commit ,
+which writes a log entry for changes in the repository.
+These logging programs might be used to append the log message to a file.
+Or send the log message through electronic mail to a group of developers.
+Or, perhaps, post the log message to a particular newsgroup.
+.SP
+You can use the `\|taginfo\|' file to define programs to execute after
+any
+.BR tag or rtag
+operation. These programs might be used to append a message to a file
+listing the new tag name and the programmer who created it, or send mail
+to a group of developers, or, perhaps, post a message to a particular
+newsgroup.
+.SP
+You can use the `\|rcsinfo\|' file to define forms for log messages.
+.SP
+You can use the `\|editinfo\|' file to define a program to execute for
+editing/validating `\|\fBcvs commit\fP\|' log entries.
+This is most useful when used with a `\|rcsinfo\|' forms specification, as
+it can verify that the proper fields of the form have been filled in by the
+user committing the change.
+.SP
+You can use the `\|cvsignore\|' file to specify the default list of
+files to ignore during \fBupdate\fP.
+.SP
+You can use the `\|history\|' file to record the \fBcvs\fP commands
+that affect the repository.
+The creation of this file enables history logging.
+.SH FILES
+.TP
+.B modules
+The `\|modules\|' file records your definitions of names for
+collections of source code. \fBcvs\fP will use these definitions if
+you use \fBcvs\fP to check in a file with the right format to
+`\|\fB$CVSROOT/CVSROOT/modules,v\fP\|'.
+.SP
+The `\|modules\|' file may contain blank lines and comments (lines
+beginning with `\|\fB#\fP\|') as well as module definitions.
+Long lines can be continued on the next line by specifying a backslash
+(``\e'') as the last character on the line.
+.SP
+A \fImodule definition\fP is a single line of the `\|modules\|' file,
+in either of two formats. In both cases, \fImname\fP represents the
+symbolic module name, and the remainder of the line is its definition.
+.SP
+\fImname\fP \fB\-a\fP \fIaliases\fP\|.\|.\|.
+.br
+This represents the simplest way of defining a module \fImname\fP.
+The `\|\fB\-a\fP\|' flags the definition as a simple alias: \fBcvs\fP
+will treat any use of \fImname\fP (as a command argument) as if the list
+of names \fIaliases\fP had been specified instead. \fIaliases\fP may
+contain either other module names or paths. When you use paths in
+\fIaliases\fP, `\|\fBcvs checkout\fP\|' creates all intermediate
+directories in the working directory, just as if the path had been
+specified explicitly in the \fBcvs\fP arguments.
+.SP
+.nf
+\fImname\fP [ \fIoptions\fP ] \fIdir\fP [ \fIfiles\fP\|.\|.\|. ] [ \fB&\fP\fImodule\fP\|.\|.\|. ]
+.fi
+.SP
+In the simplest case, this form of module definition reduces to
+`\|\fImname dir\fP\|'. This defines all the files in directory
+\fIdir\fP as module \fImname\fP. \fIdir\fP is a relative path (from
+\fB$CVSROOT\fP) to a directory of source in one of the source
+repositories. In this case, on \fBcheckout\fP, a single directory
+called \fImname\fP is created as a working directory; no intermediate
+directory levels are used by default, even if \fIdir\fP was a path
+involving several directory levels.
+.SP
+By explicitly specifying \fIfiles\fP in the module definition after
+\fIdir\fP, you can select particular files from directory
+\fIdir\fP. The sample definition for \fBmodules\fP is an example of
+a module defined with a single file from a particular directory. Here
+is another example:
+.SP
+.nf
+.ft B
+m4test unsupported/gnu/m4 foreach.m4 forloop.m4
+.ft P
+.fi
+.SP
+With this definition, executing `\|\fBcvs checkout m4test\fP\|'
+will create a single working directory `\|m4test\|' containing the two
+files listed, which both come from a common directory several levels
+deep in the \fBcvs\fP source repository.
+.SP
+A module definition can refer to other modules by including
+`\|\fB&\fP\fImodule\fP\|' in its definition. \fBcheckout\fP creates
+a subdirectory for each such \fImodule\fP, in your working directory.
+.br
+.I
+New in \fBcvs\fP 1.3;
+avoid this feature if sharing module definitions with older versions
+of \fBcvs\fP.
+.SP
+Finally, you can use one or more of the following \fIoptions\fP in
+module definitions:
+.SP
+\&`\|\fB\-d\fP \fIname\fP\|', to name the working directory something
+other than the module name.
+.br
+.I
+New in \fBcvs\fP 1.3;
+avoid this feature if sharing module definitions with older versions
+of \fBcvs\fP.
+.SP
+\&`\|\fB\-i\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP
+to run whenever files in a module are committed. \fIprog\fP runs with a
+single argument, the full pathname of the affected directory in a
+source repository. The `\|commitinfo\|', `\|loginfo\|', and
+`\|editinfo\|' files provide other ways to call a program on \fBcommit\fP.
+.SP
+`\|\fB\-o\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP
+to run whenever files in a module are checked out. \fIprog\fP runs
+with a single argument, the module name.
+.SP
+`\|\fB\-e\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP
+to run whenever files in a module are exported. \fIprog\fP runs
+with a single argument, the module name.
+.SP
+`\|\fB\-t\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP
+to run whenever files in a module are tagged. \fIprog\fP runs with two
+arguments: the module name and the symbolic tag specified to \fBrtag\fP.
+.SP
+`\|\fB\-u\fP \fIprog\fP\|' allows you to specify a program \fIprog\fP
+to run whenever `\|\fBcvs update\fP\|' is executed from the top-level
+directory of the checked-out module. \fIprog\fP runs with a
+single argument, the full path to the source repository for this module.
+.TP
+\&\fBcommitinfo\fP, \fBloginfo\fP, \fBrcsinfo\fP, \fBeditinfo\fP
+These files all specify programs to call at different points in the
+`\|\fBcvs commit\fP\|' process. They have a common structure.
+Each line is a pair of fields: a regular expression, separated by
+whitespace from a filename or command-line template.
+Whenever one of the regular expression matches a directory name in the
+repository, the rest of the line is used.
+If the line begins with a \fB#\fP character, the entire line is considered
+a comment and is ignored.
+Whitespace between the fields is also ignored.
+.SP
+For `\|loginfo\|', the rest of the
+line is a command-line template to execute.
+The templates can include not only
+a program name, but whatever list of arguments you wish. If you write
+`\|\fB%s\fP\|' somewhere on the argument list, \fBcvs\fP supplies, at
+that point, the list of files affected by the \fBcommit\fP.
+The first entry in the list is the relative path within the source
+repository where the change is being made.
+The remaining arguments list the files that are being modified, added, or
+removed by this \fBcommit\fP invocation.
+.SP
+For `\|taginfo\|', the rest of the
+line is a command-line template to execute.
+The arguments passed to the command are, in order, the
+.I tagname ,
+.I operation
+(i.e.
+.B add
+for `tag',
+.B mov
+for `tag -F', and
+.B del
+for `tag -d`),
+.I repository ,
+and any remaining are pairs of
+.B "filename revision" .
+A non-zero exit of the filter program will cause the tag to be aborted.
+.SP
+For `\|commitinfo\|', the rest of the line is a command-line template to
+execute.
+The template can include not only a program name, but whatever
+list of arguments you wish.
+The full path to the current source repository is appended to the template,
+followed by the file names of any files involved in the commit (added,
+removed, and modified files).
+.SP
+For `\|rcsinfo\|', the rest of the line is the full path to a file that
+should be loaded into the log message template.
+.SP
+For `\|editinfo\|', the rest of the line is a command-line template to
+execute.
+The template can include not only a program name, but whatever
+list of arguments you wish.
+The full path to the current log message template file is appended to the
+template.
+.SP
+You can use one of two special strings instead of a regular
+expression: `\|\fBALL\fP\|' specifies a command line template that
+must always be executed, and `\|\fBDEFAULT\fP\|' specifies a command
+line template to use if no regular expression is a match.
+.SP
+The `\|commitinfo\|' file contains commands to execute \fIbefore\fP any
+other \fBcommit\fP activity, to allow you to check any conditions that
+must be satisfied before \fBcommit\fP can proceed. The rest of the
+\fBcommit\fP will execute only if all selected commands from this file
+exit with exit status \fB0\fP.
+.SP
+The `\|rcsinfo\|' file allows you to specify \fIlog templates\fP for
+the \fBcommit\fP logging session; you can use this to provide a form
+to edit when filling out the \fBcommit\fP log. The field after the
+regular expression, in this file, contains filenames (of files
+containing the logging forms) rather than command templates.
+.SP
+The `\|editinfo\|' file allows you to execute a script \fIbefore the
+commit starts\fP, but after the log information is recorded. These
+"edit" scripts can verify information recorded in the log file. If
+the edit script exits wth a non-zero exit status, the commit is aborted.
+.SP
+The `\|loginfo\|' file contains commands to execute \fIat the end\fP
+of a commit. The text specified as a commit log message is piped
+through the command; typical uses include sending mail, filing an
+article in a newsgroup, or appending to a central file.
+.TP
+\&\fBcvsignore\fP, \fB.cvsignore\fP
+The default list of files (or
+.BR sh ( 1 )
+file name patterns) to ignore during `\|\fBcvs update\fP\|'.
+At startup time, \fBcvs\fP loads the compiled in default list of file name
+patterns (see
+.BR cvs ( 1 )).
+Then the per-repository list included in \fB$CVSROOT/CVSROOT/cvsignore\fP
+is loaded, if it exists.
+Then the per-user list is loaded from `\|$HOME/.cvsignore\|'.
+Finally, as \fBcvs\fP traverses through your directories, it will load any
+per-directory `\|.cvsignore\|' files whenever it finds one.
+These per-directory files are only valid for exactly the directory that
+contains them, not for any sub-directories.
+.TP
+.B history
+Create this file in \fB$CVSROOT/CVSROOT\fP to enable history logging
+(see the description of `\|\fBcvs history\fP\|').
+.SH "SEE ALSO"
+.BR cvs ( 1 ),
+.SH COPYING
+Copyright \(co 1992 Cygnus Support, Brian Berliner, and Jeff Polk
+.PP
+Permission is granted to make and distribute verbatim copies of
+this manual provided the copyright notice and this permission notice
+are preserved on all copies.
+.PP
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+.PP
+Permission is granted to copy and distribute translations of this
+manual into another language, under the above conditions for modified
+versions, except that this permission notice may be included in
+translations approved by the Free Software Foundation instead of in
+the original English.
diff --git a/gnu/dist/cvs/man/cvsbug.8 b/gnu/dist/cvs/man/cvsbug.8
new file mode 100644
index 00000000000..ecf2eb8a7ee
--- /dev/null
+++ b/gnu/dist/cvs/man/cvsbug.8
@@ -0,0 +1,243 @@
+.\" -*- nroff -*-
+.\" ---------------------------------------------------------------------------
+.\" man page for send-pr (by Heinz G. Seidl, hgs@cygnus.com)
+.\" updated Feb 1993 for GNATS 3.00 by Jeffrey Osier, jeffrey@cygnus.com
+.\"
+.\" This file is part of the Problem Report Management System (GNATS)
+.\" Copyright 1992 Cygnus Support
+.\"
+.\" This program is free software; you can redistribute it and/or
+.\" modify it under the terms of the GNU General Public
+.\" License as published by the Free Software Foundation; either
+.\" version 2 of the License, or (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+.\" General Public License for more details.
+.\"
+.\" ---------------------------------------------------------------------------
+.nh
+.TH CVSBUG 8 xVERSIONx "February 1993"
+.SH NAME
+cvsbug \- send problem report (PR) about CVS to a central support site
+.SH SYNOPSIS
+.B cvsbug
+[
+.I site
+]
+[
+.B \-f
+.I problem-report
+]
+[
+.B \-t
+.I mail-address
+]
+.br
+.in +0.8i
+[
+.B \-P
+]
+[
+.B \-L
+]
+[
+.B \-\-request-id
+]
+[
+.B \-v
+]
+.SH DESCRIPTION
+.B cvsbug
+is a tool used to submit
+.I problem reports
+.\" SITE ADMINISTRATORS - change this if you use a local default
+(PRs) to a central support site. In most cases the correct
+.I site
+will be the default. This argument indicates the support site which
+is responsible for the category of problem involved. Some sites may
+use a local address as a default.
+.I site
+values are defined by using the
+.BR aliases (5).
+.LP
+.B cvsbug
+invokes an editor on a problem report template (after trying to fill
+in some fields with reasonable default values). When you exit the
+editor,
+.B cvsbug
+sends the completed form to the
+.I Problem Report Management System
+(\fBGNATS\fR) at a central support site. At the support site, the PR
+is assigned a unique number and is stored in the \fBGNATS\fR database
+according to its category and submitter-id. \fBGNATS\fR automatically
+replies with an acknowledgement, citing the category and the PR
+number.
+.LP
+To ensure that a PR is handled promptly, it should contain your (unique)
+\fIsubmitter-id\fR and one of the available \fIcategories\fR to identify the
+problem area. (Use
+.B `cvsbug -L'
+to see a list of categories.)
+.LP
+The
+.B cvsbug
+template at your site should already be customized with your
+submitter-id (running `\|\fBinstall-sid\fP \fIsubmitter-id\fP\|' to
+accomplish this is part of the installation procedures for
+.BR cvsbug ).
+If this hasn't been done, see your system administrator for your
+submitter-id, or request one from your support site by invoking
+.B `cvsbug \-\-request\-id'.
+If your site does not distinguish between different user sites, or if
+you are not affiliated with the support site, use
+.B `net'
+for this field.
+.LP
+The more precise your problem description and the more complete your
+information, the faster your support team can solve your problems.
+.SH OPTIONS
+.TP
+.BI \-f " problem-report"
+specify a file (\fIproblem-report\fR) which already contains a
+complete problem report.
+.B cvsbug
+sends the contents of the file without invoking the editor. If
+the value for
+.I problem-report
+is
+.BR `\|\-\|' ,
+then
+.B cvsbug
+reads from standard input.
+.TP
+.BI \-t " mail-address"
+Change mail address at the support site for problem reports. The
+default
+.I mail-address
+is the address used for the default
+.IR site .
+Use the
+.I site
+argument rather than this option in nearly all cases.
+.TP
+.B \-P
+print the form specified by the environment variable
+.B PR_FORM
+on standard output. If
+.B PR_FORM
+is not set, print the standard blank PR template. No mail is sent.
+.TP
+.B -L
+print the list of available categories. No mail is sent.
+.TP
+.B \-\-request\-id
+sends mail to the default support site, or
+.I site
+if specified, with a request for your
+.IR submitter-id .
+If you are
+not affiliated with
+.IR site ,
+use a
+.I submitter-id
+of
+.BR net \|'.
+.TP
+.B \-v
+Display the
+.B cvsbug
+version number.
+.LP
+Note: use
+.B cvsbug
+to submit problem reports rather than mailing them directly. Using
+both the template and
+.B cvsbug
+itself will help ensure all necessary information will reach the
+support site.
+.SH ENVIRONMENT
+The environment variable
+.B EDITOR
+specifies the editor to invoke on the template.
+.br
+default:
+.B vi
+.sp
+If the environment variable
+.B PR_FORM
+is set, then its value is used as the file name of the template for
+your problem-report editing session. You can use this to start with a
+partially completed form (for example, a form with the identification
+fields already completed).
+.SH "HOW TO FILL OUT A PROBLEM REPORT"
+Problem reports have to be in a particular form so that a program can
+easily manage them. Please remember the following guidelines:
+.IP \(bu 3m
+describe only
+.B one problem
+with each problem report.
+.IP \(bu 3m
+For follow-up mail, use the same subject line as the one in the automatic
+acknowledgent. It consists of category, PR number and the original synopsis
+line. This allows the support site to relate several mail messages to a
+particular PR and to record them automatically.
+.IP \(bu 3m
+Please try to be as accurate as possible in the subject and/or synopsis line.
+.IP \(bu 3m
+The subject and the synopsis line are not confidential. This is
+because open-bugs lists are compiled from them. Avoid confidential
+information there.
+.LP
+See the GNU
+.B Info
+file
+.B cvsbug.info
+or the document \fIReporting Problems With cvsbug\fR\ for detailed
+information on reporting problems
+.SH "HOW TO SUBMIT TEST CASES, CODE, ETC."
+Submit small code samples with the PR. Contact the support site for
+instructions on submitting larger test cases and problematic source
+code.
+.SH FILES
+.ta \w'/tmp/pbad$$ 'u
+/tmp/p$$ copy of PR used in editing session
+.br
+/tmp/pf$$ copy of empty PR form, for testing purposes
+.br
+/tmp/pbad$$ file for rejected PRs
+.SH INSTALLATION AND CONFIGURATION
+See
+.B INSTALL
+for installation instructions.
+.SH SEE ALSO
+.BR gnats (l),
+.BR query-pr (1),
+.BR edit-pr (1),
+.BR gnats (8),
+.BR queue-pr (8),
+.BR at-pr (8),
+.BR mkcat (8),
+.BR mkdist (8).
+.SH AUTHORS
+Jeffrey Osier, Brendan Kehoe, Jason Merrill, Heinz G. Seidl (Cygnus
+Support)
+.SH COPYING
+Copyright (c) 1992, 1993 Free Software Foundation, Inc.
+.PP
+Permission is granted to make and distribute verbatim copies of
+this manual provided the copyright notice and this permission notice
+are preserved on all copies.
+.PP
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+.PP
+Permission is granted to copy and distribute translations of this
+manual into another language, under the above conditions for modified
+versions, except that this permission notice may be included in
+translations approved by the Free Software Foundation instead of in
+the original English.
+
diff --git a/gnu/dist/cvs/src/build_src.com b/gnu/dist/cvs/src/build_src.com
new file mode 100755
index 00000000000..3d3baa65891
--- /dev/null
+++ b/gnu/dist/cvs/src/build_src.com
@@ -0,0 +1,68 @@
+$ CC :== CC/NOOPT/DEB/STANDARD=VAXC/DEFINE=HAVE_CONFIG_H-
+/INCLUDE_DIR=([-],[-.VMS],[-.LIB])/PREFIX=ALL
+$ CC add.c
+$ CC admin.c
+$ CC buffer.c
+$ CC checkin.c
+$ CC checkout.c
+$ CC classify.c
+$ CC client.c
+$ CC commit.c
+$ CC create_adm.c
+$ CC cvsrc.c
+$ CC diff.c
+$ CC edit.c
+$ CC entries.c
+$ CC error.c
+$ CC expand_path.c
+$ CC fileattr.c
+$ CC find_names.c
+$ CC hash.c
+$ CC history.c
+$ CC ignore.c
+$ CC import.c
+$ CC lock.c
+$ CC log.c
+$ CC login.c
+$ CC logmsg.c
+$ CC main.c
+$ CC mkmodules.c
+$ CC modules.c
+$ CC myndbm.c
+$ CC no_diff.c
+$ CC parseinfo.c
+$ CC patch.c
+$ CC rcs.c
+$ CC/INCLUDE_DIR=([-],[-.VMS],[-.LIB],[-.diff]) rcscmds.c
+$ CC recurse.c
+$ CC release.c
+$ CC remove.c
+$ CC repos.c
+$ CC root.c
+$ CC rtag.c
+$ CC run.c
+$ CC scramble.c
+$ CC server.c
+$ CC status.c
+$ CC subr.c
+$ CC tag.c
+$ CC update.c
+$ CC version.c
+$ CC vers_ts.c
+$ CC watch.c
+$ CC wrapper.c
+$ CC/INCLUDE_DIR=([-],[-.VMS],[-.LIB],[-.zlib]) zlib.c
+$ LIBRARY/CREATE cvslib.olb add.obj,admin.obj,buffer.obj,checkin.obj,-
+checkout.obj,-
+classify.obj,client.obj,commit.obj,create_adm.obj,cvsrc.obj,-
+diff.obj,edit.obj,entries.obj,error.obj,expand_path.obj,fileattr.obj,-
+find_names.obj,hash.obj,history.obj,ignore.obj,import.obj,-
+lock.obj,log.obj,login.obj,logmsg.obj,mkmodules.obj,modules.obj,myndbm.obj,-
+no_diff.obj,-
+parseinfo.obj,patch.obj,rcs.obj,rcscmds.obj,recurse.obj,release.obj,-
+remove.obj,repos.obj,root.obj,rtag.obj,run.obj,scramble.obj,server.obj,-
+status.obj,-
+subr.obj,tag.obj,update.obj,version.obj,vers_ts.obj,watch.obj,wrapper.obj,-
+zlib.obj
+$ link/nodeb/exe=cvs.exe main.obj,cvslib.olb/lib,[-.lib]gnulib.olb/lib,-
+[-.vms]openvmslib.olb/lib,[-.zlib]zlib.olb/lib,[-.diff]diff.olb/lib
diff --git a/gnu/dist/cvs/src/diff.c b/gnu/dist/cvs/src/diff.c
new file mode 100644
index 00000000000..e98938d9b55
--- /dev/null
+++ b/gnu/dist/cvs/src/diff.c
@@ -0,0 +1,996 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Difference
+ *
+ * Run diff against versions in the repository. Options that are specified are
+ * passed on directly to "rcsdiff".
+ *
+ * Without any file arguments, runs diff against all the currently modified
+ * files.
+ */
+
+#include "cvs.h"
+
+enum diff_file
+{
+ DIFF_ERROR,
+ DIFF_ADDED,
+ DIFF_REMOVED,
+ DIFF_DIFFERENT,
+ DIFF_SAME
+};
+
+static Dtype diff_dirproc PROTO ((void *callerdat, char *dir,
+ char *pos_repos, char *update_dir,
+ List *entries));
+static int diff_filesdoneproc PROTO ((void *callerdat, int err,
+ char *repos, char *update_dir,
+ List *entries));
+static int diff_dirleaveproc PROTO ((void *callerdat, char *dir,
+ int err, char *update_dir,
+ List *entries));
+static enum diff_file diff_file_nodiff PROTO ((struct file_info *finfo,
+ Vers_TS *vers,
+ enum diff_file));
+static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static void diff_mark_errors PROTO((int err));
+
+
+/* Global variables. Would be cleaner if we just put this stuff in a
+ struct like log.c does. */
+
+/* Command line tags, from -r option. Points into argv. */
+static char *diff_rev1, *diff_rev2;
+/* Command line dates, from -D option. Malloc'd. */
+static char *diff_date1, *diff_date2;
+static char *use_rev1, *use_rev2;
+static int have_rev1_label, have_rev2_label;
+
+/* Revision of the user file, if it is unchanged from something in the
+ repository and we want to use that fact. */
+static char *user_file_rev;
+
+static char *options;
+static char *opts;
+static size_t opts_allocated = 1;
+static int diff_errors;
+static int empty_files = 0;
+
+/* FIXME: should be documenting all the options here. They don't
+ perfectly match rcsdiff options (for example, we always support
+ --ifdef and --context, but rcsdiff only does if diff does). */
+static const char *const diff_usage[] =
+{
+ "Usage: %s %s [-lNR] [rcsdiff-options]\n",
+ " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-D d1\tDiff revision for date against working file.\n",
+ "\t-D d2\tDiff rev1/date1 against date2.\n",
+ "\t-N\tinclude diffs for added and removed files.\n",
+ "\t-r rev1\tDiff revision for rev1 against working file.\n",
+ "\t-r rev2\tDiff rev1/date1 against rev2.\n",
+ "\t--ifdef=arg\tOutput diffs in ifdef format.\n",
+ "(consult the documentation for your diff program for rcsdiff-options.\n",
+ "The most popular is -c for context diffs but there are many more).\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+/* I copied this array directly out of diff.c in diffutils 2.7, after
+ removing the following entries, none of which seem relevant to use
+ with CVS:
+ --help
+ --version
+ --recursive
+ --unidirectional-new-file
+ --starting-file
+ --exclude
+ --exclude-from
+ --sdiff-merge-assist
+
+ I changed the options which take optional arguments (--context and
+ --unified) to return a number rather than a letter, so that the
+ optional argument could be handled more easily. I changed the
+ --paginate and --brief options to return a number, since -l and -q
+ mean something else to cvs diff.
+
+ The numbers 129- that appear in the fourth element of some entries
+ tell the big switch in `diff' how to process those options. -- Ian
+
+ The following options, which diff lists as "An alias, no longer
+ recommended" have been removed: --file-label --entire-new-file
+ --ascii --print. */
+
+static struct option const longopts[] =
+{
+ {"ignore-blank-lines", 0, 0, 'B'},
+ {"context", 2, 0, 143},
+ {"ifdef", 1, 0, 131},
+ {"show-function-line", 1, 0, 'F'},
+ {"speed-large-files", 0, 0, 'H'},
+ {"ignore-matching-lines", 1, 0, 'I'},
+ {"label", 1, 0, 'L'},
+ {"new-file", 0, 0, 'N'},
+ {"initial-tab", 0, 0, 148},
+ {"width", 1, 0, 'W'},
+ {"text", 0, 0, 'a'},
+ {"ignore-space-change", 0, 0, 'b'},
+ {"minimal", 0, 0, 'd'},
+ {"ed", 0, 0, 'e'},
+ {"forward-ed", 0, 0, 'f'},
+ {"ignore-case", 0, 0, 'i'},
+ {"paginate", 0, 0, 144},
+ {"rcs", 0, 0, 'n'},
+ {"show-c-function", 0, 0, 'p'},
+
+ /* This is a potentially very useful option, except the output is so
+ silly. It would be much better for it to look like "cvs rdiff -s"
+ which displays all the same info, minus quite a few lines of
+ extraneous garbage. */
+ {"brief", 0, 0, 145},
+
+ {"report-identical-files", 0, 0, 's'},
+ {"expand-tabs", 0, 0, 't'},
+ {"ignore-all-space", 0, 0, 'w'},
+ {"side-by-side", 0, 0, 147},
+ {"unified", 2, 0, 146},
+ {"left-column", 0, 0, 129},
+ {"suppress-common-lines", 0, 0, 130},
+ {"old-line-format", 1, 0, 132},
+ {"new-line-format", 1, 0, 133},
+ {"unchanged-line-format", 1, 0, 134},
+ {"line-format", 1, 0, 135},
+ {"old-group-format", 1, 0, 136},
+ {"new-group-format", 1, 0, 137},
+ {"unchanged-group-format", 1, 0, 138},
+ {"changed-group-format", 1, 0, 139},
+ {"horizon-lines", 1, 0, 140},
+ {"binary", 0, 0, 142},
+ {0, 0, 0, 0}
+};
+
+/* CVS 1.9 and similar versions seemed to have pretty weird handling
+ of -y and -T. In the cases where it called rcsdiff,
+ they would have the meanings mentioned below. In the cases where it
+ called diff, they would have the meanings mentioned in "longopts".
+ Noone seems to have missed them, so I think the right thing to do is
+ just to remove the options altogether (which I have done).
+
+ In the case of -z and -q, "cvs diff" did not accept them even back
+ when we called rcsdiff (at least, it hasn't accepted them
+ recently).
+
+ In comparing rcsdiff to the new CVS implementation, I noticed that
+ the following rcsdiff flags are not handled by CVS diff:
+
+ -y: perform diff even when the requested revisions are the
+ same revision number
+ -q: run quietly
+ -T: preserve modification time on the RCS file
+ -z: specify timezone for use in file labels
+
+ I think these are not really relevant. -y is undocumented even in
+ RCS 5.7, and seems like a minor change at best. According to RCS
+ documentation, -T only applies when a RCS file has been modified
+ because of lock changes; doesn't CVS sidestep RCS's entire lock
+ structure? -z seems to be unsupported by CVS diff, and has a
+ different meaning as a global option anyway. (Adding it could be
+ a feature, but if it is left out for now, it should not break
+ anything.) For the purposes of producing output, CVS diff appears
+ mostly to ignore -q. Maybe this should be fixed, but I think it's
+ a larger issue than the changes included here. */
+
+static void strcat_and_allocate PROTO ((char **, size_t *, const char *));
+
+/* *STR is a pointer to a malloc'd string. *LENP is its allocated
+ length. Add SRC to the end of it, reallocating if necessary. */
+static void
+strcat_and_allocate (str, lenp, src)
+ char **str;
+ size_t *lenp;
+ const char *src;
+{
+ size_t new_size;
+
+ new_size = strlen (*str) + strlen (src) + 1;
+ if (*str == NULL || new_size >= *lenp)
+ {
+ while (new_size >= *lenp)
+ *lenp *= 2;
+ *str = xrealloc (*str, *lenp);
+ }
+ strcat (*str, src);
+}
+
+int
+diff (argc, argv)
+ int argc;
+ char **argv;
+{
+ char tmp[50];
+ int c, err = 0;
+ int local = 0;
+ int which;
+ int option_index;
+
+ if (argc == -1)
+ usage (diff_usage);
+
+ have_rev1_label = have_rev2_label = 0;
+
+ /*
+ * Note that we catch all the valid arguments here, so that we can
+ * intercept the -r arguments for doing revision diffs; and -l/-R for a
+ * non-recursive/recursive diff.
+ */
+
+ /* Clean out our global variables (multiroot can call us multiple
+ times and the server can too, if the client sends several
+ diff commands). */
+ if (opts == NULL)
+ {
+ opts_allocated = 1;
+ opts = xmalloc (opts_allocated);
+ }
+ opts[0] = '\0';
+ diff_rev1 = NULL;
+ diff_rev2 = NULL;
+ diff_date1 = NULL;
+ diff_date2 = NULL;
+
+ optind = 0;
+ while ((c = getopt_long (argc, argv,
+ "+abcdefhilnpstuw0123456789BHNRC:D:F:I:L:U:V:W:k:r:",
+ longopts, &option_index)) != -1)
+ {
+ switch (c)
+ {
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'h': case 'i': case 'n': case 'p': case 's': case 't':
+ case 'u': case 'w': case '0': case '1': case '2':
+ case '3': case '4': case '5': case '6': case '7': case '8':
+ case '9': case 'B': case 'H':
+ (void) sprintf (tmp, " -%c", (char) c);
+ strcat_and_allocate (&opts, &opts_allocated, tmp);
+ break;
+ case 'L':
+ if (have_rev1_label++)
+ if (have_rev2_label++)
+ {
+ error (0, 0, "extra -L arguments ignored");
+ break;
+ }
+
+ strcat_and_allocate (&opts, &opts_allocated, " -L");
+ strcat_and_allocate (&opts, &opts_allocated, optarg);
+ break;
+ case 'C': case 'F': case 'I': case 'U': case 'V': case 'W':
+ (void) sprintf (tmp, " -%c", (char) c);
+ strcat_and_allocate (&opts, &opts_allocated, tmp);
+ strcat_and_allocate (&opts, &opts_allocated, optarg);
+ break;
+ case 131:
+ /* --ifdef. */
+ strcat_and_allocate (&opts, &opts_allocated, " --ifdef=");
+ strcat_and_allocate (&opts, &opts_allocated, optarg);
+ break;
+ case 129: case 130: case 132: case 133: case 134:
+ case 135: case 136: case 137: case 138: case 139: case 140:
+ case 141: case 142: case 143: case 144: case 145: case 146:
+ case 147: case 148:
+ strcat_and_allocate (&opts, &opts_allocated, " --");
+ strcat_and_allocate (&opts, &opts_allocated,
+ longopts[option_index].name);
+ if (longopts[option_index].has_arg == 1
+ || (longopts[option_index].has_arg == 2
+ && optarg != NULL))
+ {
+ strcat_and_allocate (&opts, &opts_allocated, "=");
+ strcat_and_allocate (&opts, &opts_allocated, optarg);
+ }
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'r':
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (diff_rev1 != NULL || diff_date1 != NULL)
+ diff_rev2 = optarg;
+ else
+ diff_rev1 = optarg;
+ break;
+ case 'D':
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (diff_rev1 != NULL || diff_date1 != NULL)
+ diff_date2 = Make_Date (optarg);
+ else
+ diff_date1 = Make_Date (optarg);
+ break;
+ case 'N':
+ empty_files = 1;
+ break;
+ case '?':
+ default:
+ usage (diff_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* make sure options is non-null */
+ if (!options)
+ options = xstrdup ("");
+
+#ifdef CLIENT_SUPPORT
+ if (client_active) {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg("-l");
+ if (empty_files)
+ send_arg("-N");
+ send_option_string (opts);
+ if (options[0] != '\0')
+ send_arg (options);
+ if (diff_rev1)
+ option_with_arg ("-r", diff_rev1);
+ if (diff_date1)
+ client_senddate (diff_date1);
+ if (diff_rev2)
+ option_with_arg ("-r", diff_rev2);
+ if (diff_date2)
+ client_senddate (diff_date2);
+
+ /* Send the current files unless diffing two revs from the archive */
+ if (diff_rev2 == NULL && diff_date2 == NULL)
+ send_files (argc, argv, local, 0, 0);
+ else
+ send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+
+ send_to_server ("diff\012", 0);
+ err = get_responses_and_close ();
+ free (options);
+ options = NULL;
+ return (err);
+ }
+#endif
+
+ if (diff_rev1 != NULL)
+ tag_check_valid (diff_rev1, argc, argv, local, 0, "");
+ if (diff_rev2 != NULL)
+ tag_check_valid (diff_rev2, argc, argv, local, 0, "");
+
+ which = W_LOCAL;
+ if (diff_rev1 != NULL || diff_date1 != NULL)
+ which |= W_REPOS | W_ATTIC;
+
+ wrap_setup ();
+
+ /* start the recursion processor */
+ err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
+ diff_dirleaveproc, NULL, argc, argv, local,
+ which, 0, 1, (char *) NULL, 1);
+
+ /* clean up */
+ free (options);
+ options = NULL;
+
+ if (diff_date1 != NULL)
+ free (diff_date1);
+ if (diff_date2 != NULL)
+ free (diff_date2);
+
+ return (err);
+}
+
+/*
+ * Do a file diff
+ */
+/* ARGSUSED */
+static int
+diff_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ int status, err = 2; /* 2 == trouble, like rcsdiff */
+ Vers_TS *vers;
+ enum diff_file empty_file = DIFF_DIFFERENT;
+ char *tmp;
+ char *tocvsPath;
+ char *fname;
+
+ /* Initialize these solely to avoid warnings from gcc -Wall about
+ variables that might be used uninitialized. */
+ tmp = NULL;
+ fname = NULL;
+
+ user_file_rev = 0;
+ vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
+
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ {
+ /* Skip all the following checks regarding the user file; we're
+ not using it. */
+ }
+ else if (vers->vn_user == NULL)
+ {
+ /* The file does not exist in the working directory. */
+ if ((diff_rev1 != NULL || diff_date1 != NULL)
+ && vers->srcfile != NULL)
+ {
+ /* The file does exist in the repository. */
+ if (empty_files)
+ empty_file = DIFF_REMOVED;
+ else
+ {
+ int exists;
+
+ exists = 0;
+ /* special handling for TAG_HEAD */
+ if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
+ {
+ char *head =
+ (vers->vn_rcs == NULL
+ ? NULL
+ : RCS_branch_head (vers->srcfile, vers->vn_rcs));
+ exists = head != NULL;
+ if (head != NULL)
+ free (head);
+ }
+ else
+ {
+ Vers_TS *xvers;
+
+ xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
+ 1, 0);
+ exists = xvers->vn_rcs != NULL;
+ freevers_ts (&xvers);
+ }
+ if (exists)
+ error (0, 0,
+ "%s no longer exists, no comparison available",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ else
+ {
+ error (0, 0, "I know nothing about %s", finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ {
+ if (empty_files)
+ empty_file = DIFF_ADDED;
+ else
+ {
+ error (0, 0, "%s is a new entry, no comparison available",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ if (empty_files)
+ empty_file = DIFF_REMOVED;
+ else
+ {
+ error (0, 0, "%s was removed, no comparison available",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ else
+ {
+ if (vers->vn_rcs == NULL && vers->srcfile == NULL)
+ {
+ error (0, 0, "cannot find revision control file for %s",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ else
+ {
+ if (vers->ts_user == NULL)
+ {
+ error (0, 0, "cannot find %s", finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ else if (!strcmp (vers->ts_user, vers->ts_rcs))
+ {
+ /* The user file matches some revision in the repository
+ Diff against the repository (for remote CVS, we might not
+ have a copy of the user file around). */
+ user_file_rev = vers->vn_user;
+ }
+ }
+ }
+
+ empty_file = diff_file_nodiff (finfo, vers, empty_file);
+ if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR)
+ {
+ freevers_ts (&vers);
+ if (empty_file == DIFF_SAME)
+ {
+ /* In the server case, would be nice to send a "Checked-in"
+ response, so that the client can rewrite its timestamp.
+ server_checked_in by itself isn't the right thing (it
+ needs a server_register), but I'm not sure what is.
+ It isn't clear to me how "cvs status" handles this (that
+ is, for a client which sends Modified not Is-modified to
+ "cvs status"), but it does. */
+ return (0);
+ }
+ else
+ {
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+
+ if (empty_file == DIFF_DIFFERENT)
+ {
+ int dead1, dead2;
+
+ if (use_rev1 == NULL)
+ dead1 = 0;
+ else
+ dead1 = RCS_isdead (vers->srcfile, use_rev1);
+ if (use_rev2 == NULL)
+ dead2 = 0;
+ else
+ dead2 = RCS_isdead (vers->srcfile, use_rev2);
+
+ if (dead1 && dead2)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ else if (dead1)
+ {
+ if (empty_files)
+ empty_file = DIFF_ADDED;
+ else
+ {
+ error (0, 0, "%s is a new entry, no comparison available",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ else if (dead2)
+ {
+ if (empty_files)
+ empty_file = DIFF_REMOVED;
+ else
+ {
+ error (0, 0, "%s was removed, no comparison available",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ }
+
+ /* Output an "Index:" line for patch to use */
+ cvs_output ("Index: ", 0);
+ cvs_output (finfo->fullname, 0);
+ cvs_output ("\n", 1);
+
+ tocvsPath = wrap_tocvs_process_file(finfo->file);
+ if (tocvsPath)
+ {
+ /* Backup the current version of the file to CVS/,,filename */
+ fname = xmalloc (strlen (finfo->file)
+ + sizeof CVSADM
+ + sizeof CVSPREFIX
+ + 10);
+ sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
+ if (unlink_file_dir (fname) < 0)
+ if (! existence_error (errno))
+ error (1, errno, "cannot remove %s", fname);
+ rename_file (finfo->file, fname);
+ /* Copy the wrapped file to the current directory then go to work */
+ copy_file (tocvsPath, finfo->file);
+ }
+
+ if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
+ {
+ /* This is file, not fullname, because it is the "Index:" line which
+ is supposed to contain the directory. */
+ cvs_output ("\
+===================================================================\n\
+RCS file: ", 0);
+ cvs_output (finfo->file, 0);
+ cvs_output ("\n", 1);
+
+ cvs_output ("diff -N ", 0);
+ cvs_output (finfo->file, 0);
+ cvs_output ("\n", 1);
+
+ if (empty_file == DIFF_ADDED)
+ {
+ if (use_rev2 == NULL)
+ status = diff_exec (DEVNULL, finfo->file, opts, RUN_TTY);
+ else
+ {
+ int retcode;
+
+ tmp = cvs_temp_name ();
+ retcode = RCS_checkout (vers->srcfile, (char *) NULL,
+ use_rev2, (char *) NULL,
+ (*options
+ ? options
+ : vers->options),
+ tmp, (RCSCHECKOUTPROC) NULL,
+ (void *) NULL);
+ if (retcode != 0)
+ {
+ diff_mark_errors (err);
+ return err;
+ }
+
+ status = diff_exec (DEVNULL, tmp, opts, RUN_TTY);
+ }
+ }
+ else
+ {
+ int retcode;
+
+ tmp = cvs_temp_name ();
+ retcode = RCS_checkout (vers->srcfile, (char *) NULL,
+ use_rev1, (char *) NULL,
+ *options ? options : vers->options,
+ tmp, (RCSCHECKOUTPROC) NULL,
+ (void *) NULL);
+ if (retcode != 0)
+ {
+ diff_mark_errors (err);
+ return err;
+ }
+
+ status = diff_exec (tmp, DEVNULL, opts, RUN_TTY);
+ }
+ }
+ else
+ {
+ char *label1 = NULL;
+ char *label2 = NULL;
+
+ if (!have_rev1_label)
+ label1 =
+ make_file_label (finfo->fullname, use_rev1, vers->srcfile);
+
+ if (!have_rev2_label)
+ label2 =
+ make_file_label (finfo->fullname, use_rev2, vers->srcfile);
+
+ status = RCS_exec_rcsdiff (vers->srcfile, opts,
+ *options ? options : vers->options,
+ use_rev1, use_rev2,
+ label1, label2,
+ finfo->file);
+
+ if (label1) free (label1);
+ if (label2) free (label2);
+ }
+
+ switch (status)
+ {
+ case -1: /* fork failed */
+ error (1, errno, "fork failed while diffing %s",
+ vers->srcfile->path);
+ case 0: /* everything ok */
+ err = 0;
+ break;
+ default: /* other error */
+ err = status;
+ break;
+ }
+
+ if (tocvsPath)
+ {
+ if (unlink_file_dir (finfo->file) < 0)
+ if (! existence_error (errno))
+ error (1, errno, "cannot remove %s", finfo->file);
+
+ rename_file (fname, finfo->file);
+ if (unlink_file (tocvsPath) < 0)
+ error (1, errno, "cannot remove %s", tocvsPath);
+ free (fname);
+ }
+
+ if (empty_file == DIFF_REMOVED
+ || (empty_file == DIFF_ADDED && use_rev2 != NULL))
+ {
+ if (CVS_UNLINK (tmp) < 0)
+ error (0, errno, "cannot remove %s", tmp);
+ free (tmp);
+ }
+
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+}
+
+/*
+ * Remember the exit status for each file.
+ */
+static void
+diff_mark_errors (err)
+ int err;
+{
+ if (err > diff_errors)
+ diff_errors = err;
+}
+
+/*
+ * Print a warm fuzzy message when we enter a dir
+ *
+ * Don't try to diff directories that don't exist! -- DW
+ */
+/* ARGSUSED */
+static Dtype
+diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *pos_repos;
+ char *update_dir;
+ List *entries;
+{
+ /* XXX - check for dirs we don't want to process??? */
+
+ /* YES ... for instance dirs that don't exist!!! -- DW */
+ if (!isdir (dir))
+ return (R_SKIP_ALL);
+
+ if (!quiet)
+ error (0, 0, "Diffing %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Concoct the proper exit status - done with files
+ */
+/* ARGSUSED */
+static int
+diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ return (diff_errors);
+}
+
+/*
+ * Concoct the proper exit status - leaving directories
+ */
+/* ARGSUSED */
+static int
+diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ int err;
+ char *update_dir;
+ List *entries;
+{
+ return (diff_errors);
+}
+
+/*
+ * verify that a file is different
+ */
+static enum diff_file
+diff_file_nodiff (finfo, vers, empty_file)
+ struct file_info *finfo;
+ Vers_TS *vers;
+ enum diff_file empty_file;
+{
+ Vers_TS *xvers;
+ int retcode;
+
+ /* free up any old use_rev* variables and reset 'em */
+ if (use_rev1)
+ free (use_rev1);
+ if (use_rev2)
+ free (use_rev2);
+ use_rev1 = use_rev2 = (char *) NULL;
+
+ if (diff_rev1 || diff_date1)
+ {
+ /* special handling for TAG_HEAD */
+ if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
+ use_rev1 = ((vers->vn_rcs == NULL || vers->srcfile == NULL)
+ ? NULL
+ : RCS_branch_head (vers->srcfile, vers->vn_rcs));
+ else
+ {
+ xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
+ if (xvers->vn_rcs != NULL)
+ use_rev1 = xstrdup (xvers->vn_rcs);
+ freevers_ts (&xvers);
+ }
+ }
+ if (diff_rev2 || diff_date2)
+ {
+ /* special handling for TAG_HEAD */
+ if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
+ use_rev2 = ((vers->vn_rcs == NULL || vers->srcfile == NULL)
+ ? NULL
+ : RCS_branch_head (vers->srcfile, vers->vn_rcs));
+ else
+ {
+ xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
+ if (xvers->vn_rcs != NULL)
+ use_rev2 = xstrdup (xvers->vn_rcs);
+ freevers_ts (&xvers);
+ }
+
+ if (use_rev1 == NULL)
+ {
+ /* The first revision does not exist. If EMPTY_FILES is
+ true, treat this as an added file. Otherwise, warn
+ about the missing tag. */
+ if (use_rev2 == NULL)
+ /* At least in the case where DIFF_REV1 and DIFF_REV2
+ are both numeric, we should be returning some kind
+ of error (see basicb-8a0 in testsuite). The symbolic
+ case may be more complicated. */
+ return DIFF_SAME;
+ else if (empty_files)
+ return DIFF_ADDED;
+ else if (diff_rev1)
+ error (0, 0, "tag %s is not in file %s", diff_rev1,
+ finfo->fullname);
+ else
+ error (0, 0, "no revision for date %s in file %s",
+ diff_date1, finfo->fullname);
+ return DIFF_ERROR;
+ }
+
+ if (use_rev2 == NULL)
+ {
+ /* The second revision does not exist. If EMPTY_FILES is
+ true, treat this as a removed file. Otherwise warn
+ about the missing tag. */
+ if (empty_files)
+ return DIFF_REMOVED;
+ else if (diff_rev2)
+ error (0, 0, "tag %s is not in file %s", diff_rev2,
+ finfo->fullname);
+ else
+ error (0, 0, "no revision for date %s in file %s",
+ diff_date2, finfo->fullname);
+ return DIFF_ERROR;
+ }
+
+ /* now, see if we really need to do the diff */
+ if (strcmp (use_rev1, use_rev2) == 0)
+ return DIFF_SAME;
+ else
+ return DIFF_DIFFERENT;
+ }
+
+ if ((diff_rev1 || diff_date1) && use_rev1 == NULL)
+ {
+ /* The first revision does not exist, and no second revision
+ was given. */
+ if (empty_files)
+ {
+ if (empty_file == DIFF_REMOVED)
+ return DIFF_SAME;
+ else
+ {
+ if (user_file_rev && use_rev2 == NULL)
+ use_rev2 = xstrdup (user_file_rev);
+ return DIFF_ADDED;
+ }
+ }
+ else
+ {
+ if (diff_rev1)
+ error (0, 0, "tag %s is not in file %s", diff_rev1,
+ finfo->fullname);
+ else
+ error (0, 0, "no revision for date %s in file %s",
+ diff_date1, finfo->fullname);
+ return DIFF_ERROR;
+ }
+ }
+
+ if (user_file_rev)
+ {
+ /* drop user_file_rev into first unused use_rev */
+ if (!use_rev1)
+ use_rev1 = xstrdup (user_file_rev);
+ else if (!use_rev2)
+ use_rev2 = xstrdup (user_file_rev);
+ /* and if not, it wasn't needed anyhow */
+ user_file_rev = 0;
+ }
+
+ /* now, see if we really need to do the diff */
+ if (use_rev1 && use_rev2)
+ {
+ if (strcmp (use_rev1, use_rev2) == 0)
+ return DIFF_SAME;
+ else
+ return DIFF_DIFFERENT;
+ }
+
+ if (use_rev1 == NULL
+ || (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0))
+ {
+ if (empty_file == DIFF_DIFFERENT
+ && vers->ts_user != NULL
+ && strcmp (vers->ts_rcs, vers->ts_user) == 0
+ && (!(*options) || strcmp (options, vers->options) == 0))
+ {
+ return DIFF_SAME;
+ }
+ if (use_rev1 == NULL
+ && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
+ {
+ if (vers->vn_user[0] == '-')
+ use_rev1 = xstrdup (vers->vn_user + 1);
+ else
+ use_rev1 = xstrdup (vers->vn_user);
+ }
+ }
+
+ /* If we already know that the file is being added or removed,
+ then we don't want to do an actual file comparison here. */
+ if (empty_file != DIFF_DIFFERENT)
+ return empty_file;
+
+ /*
+ * with 0 or 1 -r option specified, run a quick diff to see if we
+ * should bother with it at all.
+ */
+
+ retcode = RCS_cmp_file (vers->srcfile, use_rev1,
+ *options ? options : vers->options,
+ finfo->file);
+
+ return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;
+}
diff --git a/gnu/dist/cvs/src/edit.c b/gnu/dist/cvs/src/edit.c
new file mode 100644
index 00000000000..8bc8468aa17
--- /dev/null
+++ b/gnu/dist/cvs/src/edit.c
@@ -0,0 +1,1125 @@
+/* Implementation for "cvs edit", "cvs watch on", and related commands
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+#include "cvs.h"
+#include "getline.h"
+#include "watch.h"
+#include "edit.h"
+#include "fileattr.h"
+
+static int watch_onoff PROTO ((int, char **));
+
+static int setting_default;
+static int turning_on;
+
+static int setting_tedit;
+static int setting_tunedit;
+static int setting_tcommit;
+
+static int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int
+onoff_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
+ return 0;
+}
+
+static int onoff_filesdoneproc PROTO ((void *, int, char *, char *, List *));
+
+static int
+onoff_filesdoneproc (callerdat, err, repository, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repository;
+ char *update_dir;
+ List *entries;
+{
+ if (setting_default)
+ fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
+ return err;
+}
+
+static int
+watch_onoff (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int local = 0;
+ int err;
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+lR")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (watch_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ setting_default = (argc <= 0);
+
+ lock_tree_for_write (argc, argv, local, 0);
+
+ err = start_recursion (onoff_fileproc, onoff_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0);
+
+ Lock_Cleanup ();
+ return err;
+}
+
+int
+watch_on (argc, argv)
+ int argc;
+ char **argv;
+{
+ turning_on = 1;
+ return watch_onoff (argc, argv);
+}
+
+int
+watch_off (argc, argv)
+ int argc;
+ char **argv;
+{
+ turning_on = 0;
+ return watch_onoff (argc, argv);
+}
+
+static int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int
+dummy_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ /* This is a pretty hideous hack, but the gist of it is that recurse.c
+ won't call notify_check unless there is a fileproc, so we can't just
+ pass NULL for fileproc. */
+ return 0;
+}
+
+static int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+/* Check for and process notifications. Local only. I think that doing
+ this as a fileproc is the only way to catch all the
+ cases (e.g. foo/bar.c), even though that means checking over and over
+ for the same CVSADM_NOTIFY file which we removed the first time we
+ processed the directory. */
+
+static int
+ncheck_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ int notif_type;
+ char *filename;
+ char *val;
+ char *cp;
+ char *watches;
+
+ FILE *fp;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ /* We send notifications even if noexec. I'm not sure which behavior
+ is most sensible. */
+
+ fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", CVSADM_NOTIFY);
+ return 0;
+ }
+
+ while (getline (&line, &line_len, fp) > 0)
+ {
+ notif_type = line[0];
+ if (notif_type == '\0')
+ continue;
+ filename = line + 1;
+ cp = strchr (filename, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '\0';
+ val = cp;
+ cp = strchr (val, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '\0';
+ watches = cp;
+ cp = strchr (cp, '\n');
+ if (cp == NULL)
+ continue;
+ *cp = '\0';
+
+ notify_do (notif_type, filename, getcaller (), val, watches,
+ finfo->repository);
+ }
+ free (line);
+
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+
+ if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
+ error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
+
+ return 0;
+}
+
+static int send_notifications PROTO ((int, char **, int));
+
+/* Look through the CVSADM_NOTIFY file and process each item there
+ accordingly. */
+static int
+send_notifications (argc, argv, local)
+ int argc;
+ char **argv;
+ int local;
+{
+ int err = 0;
+
+#ifdef CLIENT_SUPPORT
+ /* OK, we've done everything which needs to happen on the client side.
+ Now we can try to contact the server; if we fail, then the
+ notifications stay in CVSADM_NOTIFY to be sent next time. */
+ if (client_active)
+ {
+ if (strcmp (command_name, "release") != 0)
+ {
+ start_server ();
+ ign_setup ();
+ }
+
+ err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0);
+
+ send_to_server ("noop\012", 0);
+ if (strcmp (command_name, "release") == 0)
+ err += get_server_responses ();
+ else
+ err += get_responses_and_close ();
+ }
+ else
+#endif
+ {
+ /* Local. */
+
+ lock_tree_for_write (argc, argv, local, 0);
+ err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0);
+ Lock_Cleanup ();
+ }
+ return err;
+}
+
+static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int
+edit_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ FILE *fp;
+ time_t now;
+ char *ascnow;
+ char *basefilename;
+
+ if (noexec)
+ return 0;
+
+ /* This is a somewhat screwy way to check for this, because it
+ doesn't help errors other than the nonexistence of the file
+ (e.g. permissions problems). It might be better to rearrange
+ the code so that CVSADM_NOTIFY gets written only after the
+ various actions succeed (but what if only some of them
+ succeed). */
+ if (!isfile (finfo->file))
+ {
+ error (0, 0, "no such file %s; ignored", finfo->fullname);
+ return 0;
+ }
+
+ fp = open_file (CVSADM_NOTIFY, "a");
+
+ (void) time (&now);
+ ascnow = asctime (gmtime (&now));
+ ascnow[24] = '\0';
+ fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
+ ascnow, hostname, CurDir);
+ if (setting_tedit)
+ fprintf (fp, "E");
+ if (setting_tunedit)
+ fprintf (fp, "U");
+ if (setting_tcommit)
+ fprintf (fp, "C");
+ fprintf (fp, "\n");
+
+ if (fclose (fp) < 0)
+ {
+ if (finfo->update_dir[0] == '\0')
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ else
+ error (0, errno, "cannot close %s/%s", finfo->update_dir,
+ CVSADM_NOTIFY);
+ }
+
+ xchmod (finfo->file, 1);
+
+ /* Now stash the file away in CVSADM so that unedit can revert even if
+ it can't communicate with the server. We stash away a writable
+ copy so that if the user removes the working file, then restores it
+ with "cvs update" (which clears _editors but does not update
+ CVSADM_BASE), then a future "cvs edit" can still win. */
+ /* Could save a system call by only calling mkdir_if_needed if
+ trying to create the output file fails. But copy_file isn't
+ set up to facilitate that. */
+ mkdir_if_needed (CVSADM_BASE);
+ basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
+ strcpy (basefilename, CVSADM_BASE);
+ strcat (basefilename, "/");
+ strcat (basefilename, finfo->file);
+ copy_file (finfo->file, basefilename);
+ free (basefilename);
+
+ {
+ Node *node;
+
+ node = findnode_fn (finfo->entries, finfo->file);
+ if (node != NULL)
+ base_register (finfo, ((Entnode *) node->data)->version);
+ }
+
+ return 0;
+}
+
+static const char *const edit_usage[] =
+{
+ "Usage: %s %s [-lR] [files...]\n",
+ "-l: Local directory only, not recursive\n",
+ "-R: Process directories recursively\n",
+ "-a: Specify what actions for temporary watch, one of\n",
+ " edit,unedit,commit,all,none\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+edit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+ int err;
+ int a_omitted;
+
+ if (argc == -1)
+ usage (edit_usage);
+
+ a_omitted = 1;
+ setting_tedit = 0;
+ setting_tunedit = 0;
+ setting_tcommit = 0;
+ optind = 0;
+ while ((c = getopt (argc, argv, "+lRa:")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'a':
+ a_omitted = 0;
+ if (strcmp (optarg, "edit") == 0)
+ setting_tedit = 1;
+ else if (strcmp (optarg, "unedit") == 0)
+ setting_tunedit = 1;
+ else if (strcmp (optarg, "commit") == 0)
+ setting_tcommit = 1;
+ else if (strcmp (optarg, "all") == 0)
+ {
+ setting_tedit = 1;
+ setting_tunedit = 1;
+ setting_tcommit = 1;
+ }
+ else if (strcmp (optarg, "none") == 0)
+ {
+ setting_tedit = 0;
+ setting_tunedit = 0;
+ setting_tcommit = 0;
+ }
+ else
+ usage (edit_usage);
+ break;
+ case '?':
+ default:
+ usage (edit_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (a_omitted)
+ {
+ setting_tedit = 1;
+ setting_tunedit = 1;
+ setting_tcommit = 1;
+ }
+
+ /* No need to readlock since we aren't doing anything to the
+ repository. */
+ err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0);
+
+ err += send_notifications (argc, argv, local);
+
+ return err;
+}
+
+static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int
+unedit_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ FILE *fp;
+ time_t now;
+ char *ascnow;
+ char *basefilename;
+
+ if (noexec)
+ return 0;
+
+ basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
+ strcpy (basefilename, CVSADM_BASE);
+ strcat (basefilename, "/");
+ strcat (basefilename, finfo->file);
+ if (!isfile (basefilename))
+ {
+ /* This file apparently was never cvs edit'd (e.g. we are uneditting
+ a directory where only some of the files were cvs edit'd. */
+ free (basefilename);
+ return 0;
+ }
+
+ if (xcmp (finfo->file, basefilename) != 0)
+ {
+ printf ("%s has been modified; revert changes? ", finfo->fullname);
+ if (!yesno ())
+ {
+ /* "no". */
+ free (basefilename);
+ return 0;
+ }
+ }
+ rename_file (basefilename, finfo->file);
+ free (basefilename);
+
+ fp = open_file (CVSADM_NOTIFY, "a");
+
+ (void) time (&now);
+ ascnow = asctime (gmtime (&now));
+ ascnow[24] = '\0';
+ fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
+ ascnow, hostname, CurDir);
+
+ if (fclose (fp) < 0)
+ {
+ if (finfo->update_dir[0] == '\0')
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ else
+ error (0, errno, "cannot close %s/%s", finfo->update_dir,
+ CVSADM_NOTIFY);
+ }
+
+ /* Now update the revision number in CVS/Entries from CVS/Baserev.
+ The basic idea here is that we are reverting to the revision
+ that the user edited. If we wanted "cvs update" to update
+ CVS/Base as we go along (so that an unedit could revert to the
+ current repository revision), we would need:
+
+ update (or all send_files?) (client) needs to send revision in
+ new Entry-base request. update (server/local) needs to check
+ revision against repository and send new Update-base response
+ (like Update-existing in that the file already exists. While
+ we are at it, might try to clean up the syntax by having the
+ mode only in a "Mode" response, not in the Update-base itself). */
+ {
+ char *baserev;
+ Node *node;
+ Entnode *entdata;
+
+ baserev = base_get (finfo);
+ node = findnode_fn (finfo->entries, finfo->file);
+ /* The case where node is NULL probably should be an error or
+ something, but I don't want to think about it too hard right
+ now. */
+ if (node != NULL)
+ {
+ entdata = (Entnode *) node->data;
+ if (baserev == NULL)
+ {
+ /* This can only happen if the CVS/Baserev file got
+ corrupted. We suspect it might be possible if the
+ user interrupts CVS, although I haven't verified
+ that. */
+ error (0, 0, "%s not mentioned in %s", finfo->fullname,
+ CVSADM_BASEREV);
+
+ /* Since we don't know what revision the file derives from,
+ keeping it around would be asking for trouble. */
+ if (unlink_file (finfo->file) < 0)
+ error (0, errno, "cannot remove %s", finfo->fullname);
+
+ /* This is cheesy, in a sense; why shouldn't we do the
+ update for the user? However, doing that would require
+ contacting the server, so maybe this is OK. */
+ error (0, 0, "run update to complete the unedit");
+ return 0;
+ }
+ Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
+ entdata->options, entdata->tag, entdata->date,
+ entdata->conflict);
+ }
+ free (baserev);
+ base_deregister (finfo);
+ }
+
+ xchmod (finfo->file, 0);
+ return 0;
+}
+
+static const char *const unedit_usage[] =
+{
+ "Usage: %s %s [-lR] [files...]\n",
+ "-l: Local directory only, not recursive\n",
+ "-R: Process directories recursively\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+unedit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+ int err;
+
+ if (argc == -1)
+ usage (unedit_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+lR")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (unedit_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* No need to readlock since we aren't doing anything to the
+ repository. */
+ err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0);
+
+ err += send_notifications (argc, argv, local);
+
+ return err;
+}
+
+void
+mark_up_to_date (file)
+ char *file;
+{
+ char *base;
+
+ /* The file is up to date, so we better get rid of an out of
+ date file in CVSADM_BASE. */
+ base = xmalloc (strlen (file) + 80);
+ strcpy (base, CVSADM_BASE);
+ strcat (base, "/");
+ strcat (base, file);
+ if (unlink_file (base) < 0 && ! existence_error (errno))
+ error (0, errno, "cannot remove %s", file);
+ free (base);
+}
+
+
+void
+editor_set (filename, editor, val)
+ char *filename;
+ char *editor;
+ char *val;
+{
+ char *edlist;
+ char *newlist;
+
+ edlist = fileattr_get0 (filename, "_editors");
+ newlist = fileattr_modify (edlist, editor, val, '>', ',');
+ /* If the attributes is unchanged, don't rewrite the attribute file. */
+ if (!((edlist == NULL && newlist == NULL)
+ || (edlist != NULL
+ && newlist != NULL
+ && strcmp (edlist, newlist) == 0)))
+ fileattr_set (filename, "_editors", newlist);
+ if (edlist != NULL)
+ free (edlist);
+ if (newlist != NULL)
+ free (newlist);
+}
+
+struct notify_proc_args {
+ /* What kind of notification, "edit", "tedit", etc. */
+ char *type;
+ /* User who is running the command which causes notification. */
+ char *who;
+ /* User to be notified. */
+ char *notifyee;
+ /* File. */
+ char *file;
+};
+
+/* Pass as a static until we get around to fixing Parse_Info to pass along
+ a void * where we can stash it. */
+static struct notify_proc_args *notify_args;
+
+static int notify_proc PROTO ((char *repository, char *filter));
+
+static int
+notify_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ FILE *pipefp;
+ char *prog;
+ char *expanded_prog;
+ char *p;
+ char *q;
+ char *srepos;
+ struct notify_proc_args *args = notify_args;
+
+ srepos = Short_Repository (repository);
+ prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
+ /* Copy FILTER to PROG, replacing the first occurrence of %s with
+ the notifyee. We only allocated enough memory for one %s, and I doubt
+ there is a need for more. */
+ for (p = filter, q = prog; *p != '\0'; ++p)
+ {
+ if (p[0] == '%')
+ {
+ if (p[1] == 's')
+ {
+ strcpy (q, args->notifyee);
+ q += strlen (q);
+ strcpy (q, p + 2);
+ q += strlen (q);
+ break;
+ }
+ else
+ continue;
+ }
+ *q++ = *p;
+ }
+ *q = '\0';
+
+ /* FIXME: why are we calling expand_proc? Didn't we already
+ expand it in Parse_Info, before passing it to notify_proc? */
+ expanded_prog = expand_path (prog, "notify", 0);
+ if (!expanded_prog)
+ {
+ free (prog);
+ return 1;
+ }
+
+ pipefp = run_popen (expanded_prog, "w");
+ if (pipefp == NULL)
+ {
+ error (0, errno, "cannot write entry to notify filter: %s", prog);
+ free (prog);
+ free (expanded_prog);
+ return 1;
+ }
+
+ fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
+ fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
+ fprintf (pipefp, "By %s\n", args->who);
+
+ /* Lots more potentially useful information we could add here; see
+ logfile_write for inspiration. */
+
+ free (prog);
+ free (expanded_prog);
+ return (pclose (pipefp));
+}
+
+/* FIXME: this function should have a way to report whether there was
+ an error so that server.c can know whether to report Notified back
+ to the client. */
+void
+notify_do (type, filename, who, val, watches, repository)
+ int type;
+ char *filename;
+ char *who;
+ char *val;
+ char *watches;
+ char *repository;
+{
+ static struct addremove_args blank;
+ struct addremove_args args;
+ char *watchers;
+ char *p;
+ char *endp;
+ char *nextp;
+
+ /* Initialize fields to 0, NULL, or 0.0. */
+ args = blank;
+ switch (type)
+ {
+ case 'E':
+ if (strpbrk (val, ",>;=\n") != NULL)
+ {
+ error (0, 0, "invalid character in editor value");
+ return;
+ }
+ editor_set (filename, who, val);
+ break;
+ case 'U':
+ case 'C':
+ editor_set (filename, who, NULL);
+ break;
+ default:
+ return;
+ }
+
+ watchers = fileattr_get0 (filename, "_watchers");
+ p = watchers;
+ while (p != NULL)
+ {
+ char *q;
+ char *endq;
+ char *nextq;
+ char *notif;
+
+ endp = strchr (p, '>');
+ if (endp == NULL)
+ break;
+ nextp = strchr (p, ',');
+
+ if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
+ {
+ /* Don't notify user of their own changes. Would perhaps
+ be better to check whether it is the same working
+ directory, not the same user, but that is hairy. */
+ p = nextp == NULL ? nextp : nextp + 1;
+ continue;
+ }
+
+ /* Now we point q at a string which looks like
+ "edit+unedit+commit,"... and walk down it. */
+ q = endp + 1;
+ notif = NULL;
+ while (q != NULL)
+ {
+ endq = strchr (q, '+');
+ if (endq == NULL || (nextp != NULL && endq > nextp))
+ {
+ if (nextp == NULL)
+ endq = q + strlen (q);
+ else
+ endq = nextp;
+ nextq = NULL;
+ }
+ else
+ nextq = endq + 1;
+
+ /* If there is a temporary and a regular watch, send a single
+ notification, for the regular watch. */
+ if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
+ {
+ notif = "edit";
+ }
+ else if (type == 'U'
+ && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
+ {
+ notif = "unedit";
+ }
+ else if (type == 'C'
+ && endq - q == 6 && strncmp ("commit", q, 6) == 0)
+ {
+ notif = "commit";
+ }
+ else if (type == 'E'
+ && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
+ {
+ if (notif == NULL)
+ notif = "temporary edit";
+ }
+ else if (type == 'U'
+ && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
+ {
+ if (notif == NULL)
+ notif = "temporary unedit";
+ }
+ else if (type == 'C'
+ && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
+ {
+ if (notif == NULL)
+ notif = "temporary commit";
+ }
+ q = nextq;
+ }
+ if (nextp != NULL)
+ ++nextp;
+
+ if (notif != NULL)
+ {
+ struct notify_proc_args args;
+ size_t len = endp - p;
+ FILE *fp;
+ char *usersname;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ args.notifyee = NULL;
+ usersname = xmalloc (strlen (CVSroot_directory)
+ + sizeof CVSROOTADM
+ + sizeof CVSROOTADM_USERS
+ + 20);
+ strcpy (usersname, CVSroot_directory);
+ strcat (usersname, "/");
+ strcat (usersname, CVSROOTADM);
+ strcat (usersname, "/");
+ strcat (usersname, CVSROOTADM_USERS);
+ fp = CVS_FOPEN (usersname, "r");
+ if (fp == NULL && !existence_error (errno))
+ error (0, errno, "cannot read %s", usersname);
+ if (fp != NULL)
+ {
+ while (getline (&line, &line_len, fp) >= 0)
+ {
+ if (strncmp (line, p, len) == 0
+ && line[len] == ':')
+ {
+ char *cp;
+ args.notifyee = xstrdup (line + len + 1);
+
+ /* There may or may not be more
+ colon-separated fields added to this in the
+ future; in any case, we ignore them right
+ now, and if there are none we make sure to
+ chop off the final newline, if any. */
+ cp = strpbrk (args.notifyee, ":\n");
+
+ if (cp != NULL)
+ *cp = '\0';
+ break;
+ }
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", usersname);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", usersname);
+ }
+ free (usersname);
+ if (line != NULL)
+ free (line);
+
+ if (args.notifyee == NULL)
+ {
+ args.notifyee = xmalloc (endp - p + 1);
+ strncpy (args.notifyee, p, endp - p);
+ args.notifyee[endp - p] = '\0';
+ }
+
+ notify_args = &args;
+ args.type = notif;
+ args.who = who;
+ args.file = filename;
+
+ (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
+ free (args.notifyee);
+ }
+
+ p = nextp;
+ }
+ if (watchers != NULL)
+ free (watchers);
+
+ switch (type)
+ {
+ case 'E':
+ if (*watches == 'E')
+ {
+ args.add_tedit = 1;
+ ++watches;
+ }
+ if (*watches == 'U')
+ {
+ args.add_tunedit = 1;
+ ++watches;
+ }
+ if (*watches == 'C')
+ {
+ args.add_tcommit = 1;
+ }
+ watch_modify_watchers (filename, &args);
+ break;
+ case 'U':
+ case 'C':
+ args.remove_temp = 1;
+ watch_modify_watchers (filename, &args);
+ break;
+ }
+}
+
+#ifdef CLIENT_SUPPORT
+/* Check and send notifications. This is only for the client. */
+void
+notify_check (repository, update_dir)
+ char *repository;
+ char *update_dir;
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ if (! server_started)
+ /* We are in the midst of a command which is not to talk to
+ the server (e.g. the first phase of a cvs edit). Just chill
+ out, we'll catch the notifications on the flip side. */
+ return;
+
+ /* We send notifications even if noexec. I'm not sure which behavior
+ is most sensible. */
+
+ fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", CVSADM_NOTIFY);
+ return;
+ }
+ while (getline (&line, &line_len, fp) > 0)
+ {
+ int notif_type;
+ char *filename;
+ char *val;
+ char *cp;
+
+ notif_type = line[0];
+ if (notif_type == '\0')
+ continue;
+ filename = line + 1;
+ cp = strchr (filename, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '\0';
+ val = cp;
+
+ client_notify (repository, update_dir, filename, notif_type, val);
+ }
+ if (line)
+ free (line);
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+
+ /* Leave the CVSADM_NOTIFY file there, until the server tells us it
+ has dealt with it. */
+}
+#endif /* CLIENT_SUPPORT */
+
+
+static const char *const editors_usage[] =
+{
+ "Usage: %s %s [-lR] [files...]\n",
+ "\t-l\tProcess this directory only (not recursive).\n",
+ "\t-R\tProcess directories recursively.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int
+editors_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *them;
+ char *p;
+
+ them = fileattr_get0 (finfo->file, "_editors");
+ if (them == NULL)
+ return 0;
+
+ cvs_output (finfo->fullname, 0);
+
+ p = them;
+ while (1)
+ {
+ cvs_output ("\t", 1);
+ while (*p != '>' && *p != '\0')
+ cvs_output (p++, 1);
+ if (*p == '\0')
+ {
+ /* Only happens if attribute is misformed. */
+ cvs_output ("\n", 1);
+ break;
+ }
+ ++p;
+ cvs_output ("\t", 1);
+ while (1)
+ {
+ while (*p != '+' && *p != ',' && *p != '\0')
+ cvs_output (p++, 1);
+ if (*p == '\0')
+ {
+ cvs_output ("\n", 1);
+ goto out;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ break;
+ }
+ ++p;
+ cvs_output ("\t", 1);
+ }
+ cvs_output ("\n", 1);
+ }
+ out:;
+ return 0;
+}
+
+int
+editors (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (editors_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+lR")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (editors_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_to_server ("editors\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ return start_recursion (editors_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 0);
+}
diff --git a/gnu/dist/cvs/src/entries.c b/gnu/dist/cvs/src/entries.c
new file mode 100644
index 00000000000..14163bb8ae6
--- /dev/null
+++ b/gnu/dist/cvs/src/entries.c
@@ -0,0 +1,1185 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Entries file to Files file
+ *
+ * Creates the file Files containing the names that comprise the project, from
+ * the Entries file.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static Node *AddEntryNode PROTO((List * list, Entnode *entnode));
+
+static Entnode *fgetentent PROTO((FILE *, char *, int *));
+static int fputentent PROTO((FILE *, Entnode *));
+
+static Entnode *subdir_record PROTO((int, const char *, const char *));
+
+static FILE *entfile;
+static char *entfilename; /* for error messages */
+
+/*
+ * Construct an Entnode
+ */
+static Entnode *Entnode_Create PROTO ((enum ent_type, const char *,
+ const char *, const char *,
+ const char *, const char *,
+ const char *, const char *));
+
+static Entnode *
+Entnode_Create(type, user, vn, ts, options, tag, date, ts_conflict)
+ enum ent_type type;
+ const char *user;
+ const char *vn;
+ const char *ts;
+ const char *options;
+ const char *tag;
+ const char *date;
+ const char *ts_conflict;
+{
+ Entnode *ent;
+
+ /* Note that timestamp and options must be non-NULL */
+ ent = (Entnode *) xmalloc (sizeof (Entnode));
+ ent->type = type;
+ ent->user = xstrdup (user);
+ ent->version = xstrdup (vn);
+ ent->timestamp = xstrdup (ts ? ts : "");
+ ent->options = xstrdup (options ? options : "");
+ ent->tag = xstrdup (tag);
+ ent->date = xstrdup (date);
+ ent->conflict = xstrdup (ts_conflict);
+
+ return ent;
+}
+
+/*
+ * Destruct an Entnode
+ */
+static void Entnode_Destroy PROTO ((Entnode *));
+
+static void
+Entnode_Destroy (ent)
+ Entnode *ent;
+{
+ free (ent->user);
+ free (ent->version);
+ free (ent->timestamp);
+ free (ent->options);
+ if (ent->tag)
+ free (ent->tag);
+ if (ent->date)
+ free (ent->date);
+ if (ent->conflict)
+ free (ent->conflict);
+ free (ent);
+}
+
+/*
+ * Write out the line associated with a node of an entries file
+ */
+static int write_ent_proc PROTO ((Node *, void *));
+static int
+write_ent_proc (node, closure)
+ Node *node;
+ void *closure;
+{
+ Entnode *entnode;
+
+ entnode = (Entnode *) node->data;
+
+ if (closure != NULL && entnode->type != ENT_FILE)
+ *(int *) closure = 1;
+
+ if (fputentent(entfile, entnode))
+ error (1, errno, "cannot write %s", entfilename);
+
+ return (0);
+}
+
+/*
+ * write out the current entries file given a list, making a backup copy
+ * first of course
+ */
+static void
+write_entries (list)
+ List *list;
+{
+ int sawdir;
+
+ sawdir = 0;
+
+ /* open the new one and walk the list writing entries */
+ entfilename = CVSADM_ENTBAK;
+ entfile = CVS_FOPEN (entfilename, "w+");
+ if (entfile == NULL)
+ {
+ /* Make this a warning, not an error. For example, one user might
+ have checked out a working directory which, for whatever reason,
+ contains an Entries.Log file. A second user, without write access
+ to that working directory, might want to do a "cvs log". The
+ problem rewriting Entries shouldn't affect the ability of "cvs log"
+ to work, although the warning is probably a good idea so that
+ whether Entries gets rewritten is not an inexplicable process. */
+ /* FIXME: should be including update_dir in message. */
+ error (0, errno, "cannot rewrite %s", entfilename);
+
+ /* Now just return. We leave the Entries.Log file around. As far
+ as I know, there is never any data lying around in 'list' that
+ is not in Entries.Log at this time (if there is an error writing
+ Entries.Log that is a separate problem). */
+ return;
+ }
+
+ (void) walklist (list, write_ent_proc, (void *) &sawdir);
+ if (! sawdir)
+ {
+ struct stickydirtag *sdtp;
+
+ /* We didn't write out any directories. Check the list
+ private data to see whether subdirectory information is
+ known. If it is, we need to write out an empty D line. */
+ sdtp = (struct stickydirtag *) list->list->data;
+ if (sdtp == NULL || sdtp->subdirs)
+ if (fprintf (entfile, "D\n") < 0)
+ error (1, errno, "cannot write %s", entfilename);
+ }
+ if (fclose (entfile) == EOF)
+ error (1, errno, "error closing %s", entfilename);
+
+ /* now, atomically (on systems that support it) rename it */
+ rename_file (entfilename, CVSADM_ENT);
+
+ /* now, remove the log file */
+ if (unlink_file (CVSADM_ENTLOG) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", CVSADM_ENTLOG);
+}
+
+/*
+ * Removes the argument file from the Entries file if necessary.
+ */
+void
+Scratch_Entry (list, fname)
+ List *list;
+ char *fname;
+{
+ Node *node;
+
+ if (trace)
+ (void) fprintf (stderr, "%s-> Scratch_Entry(%s)\n",
+ CLIENT_SERVER_STR, fname);
+
+ /* hashlookup to see if it is there */
+ if ((node = findnode_fn (list, fname)) != NULL)
+ {
+ if (!noexec)
+ {
+ entfilename = CVSADM_ENTLOG;
+ entfile = open_file (entfilename, "a");
+
+ if (fprintf (entfile, "R ") < 0)
+ error (1, errno, "cannot write %s", entfilename);
+
+ write_ent_proc (node, NULL);
+
+ if (fclose (entfile) == EOF)
+ error (1, errno, "error closing %s", entfilename);
+ }
+
+ delnode (node); /* delete the node */
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_scratch (fname);
+#endif
+ }
+}
+
+/*
+ * Enters the given file name/version/time-stamp into the Entries file,
+ * removing the old entry first, if necessary.
+ */
+void
+Register (list, fname, vn, ts, options, tag, date, ts_conflict)
+ List *list;
+ char *fname;
+ char *vn;
+ char *ts;
+ char *options;
+ char *tag;
+ char *date;
+ char *ts_conflict;
+{
+ Entnode *entnode;
+ Node *node;
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_register (fname, vn, ts, options, tag, date, ts_conflict);
+ }
+#endif
+
+ if (trace)
+ {
+ (void) fprintf (stderr, "%s-> Register(%s, %s, %s%s%s, %s, %s %s)\n",
+ CLIENT_SERVER_STR,
+ fname, vn, ts ? ts : "",
+ ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "",
+ options, tag ? tag : "", date ? date : "");
+ }
+
+ entnode = Entnode_Create (ENT_FILE, fname, vn, ts, options, tag, date,
+ ts_conflict);
+ node = AddEntryNode (list, entnode);
+
+ if (!noexec)
+ {
+ entfilename = CVSADM_ENTLOG;
+ entfile = CVS_FOPEN (entfilename, "a");
+
+ if (entfile == NULL)
+ {
+ /* Warning, not error, as in write_entries. */
+ /* FIXME-update-dir: should be including update_dir in message. */
+ error (0, errno, "cannot open %s", entfilename);
+ return;
+ }
+
+ if (fprintf (entfile, "A ") < 0)
+ error (1, errno, "cannot write %s", entfilename);
+
+ write_ent_proc (node, NULL);
+
+ if (fclose (entfile) == EOF)
+ error (1, errno, "error closing %s", entfilename);
+ }
+}
+
+/*
+ * Node delete procedure for list-private sticky dir tag/date info
+ */
+static void
+freesdt (p)
+ Node *p;
+{
+ struct stickydirtag *sdtp;
+
+ sdtp = (struct stickydirtag *) p->data;
+ if (sdtp->tag)
+ free (sdtp->tag);
+ if (sdtp->date)
+ free (sdtp->date);
+ free ((char *) sdtp);
+}
+
+/* Return the next real Entries line. On end of file, returns NULL.
+ On error, prints an error message and returns NULL. */
+
+static Entnode *
+fgetentent(fpin, cmd, sawdir)
+ FILE *fpin;
+ char *cmd;
+ int *sawdir;
+{
+ Entnode *ent;
+ char *line;
+ size_t line_chars_allocated;
+ register char *cp;
+ enum ent_type type;
+ char *l, *user, *vn, *ts, *options;
+ char *tag_or_date, *tag, *date, *ts_conflict;
+ int line_length;
+
+ line = NULL;
+ line_chars_allocated = 0;
+
+ ent = NULL;
+ while ((line_length = getline (&line, &line_chars_allocated, fpin)) > 0)
+ {
+ l = line;
+
+ /* If CMD is not NULL, we are reading an Entries.Log file.
+ Each line in the Entries.Log file starts with a single
+ character command followed by a space. For backward
+ compatibility, the absence of a space indicates an add
+ command. */
+ if (cmd != NULL)
+ {
+ if (l[1] != ' ')
+ *cmd = 'A';
+ else
+ {
+ *cmd = l[0];
+ l += 2;
+ }
+ }
+
+ type = ENT_FILE;
+
+ if (l[0] == 'D')
+ {
+ type = ENT_SUBDIR;
+ *sawdir = 1;
+ ++l;
+ /* An empty D line is permitted; it is a signal that this
+ Entries file lists all known subdirectories. */
+ }
+
+ if (l[0] != '/')
+ continue;
+
+ user = l + 1;
+ if ((cp = strchr (user, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ vn = cp;
+ if ((cp = strchr (vn, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ ts = cp;
+ if ((cp = strchr (ts, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ options = cp;
+ if ((cp = strchr (options, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ tag_or_date = cp;
+ if ((cp = strchr (tag_or_date, '\n')) == NULL)
+ continue;
+ *cp = '\0';
+ tag = (char *) NULL;
+ date = (char *) NULL;
+ if (*tag_or_date == 'T')
+ tag = tag_or_date + 1;
+ else if (*tag_or_date == 'D')
+ date = tag_or_date + 1;
+
+ if ((ts_conflict = strchr (ts, '+')))
+ *ts_conflict++ = '\0';
+
+ /*
+ * XXX - Convert timestamp from old format to new format.
+ *
+ * If the timestamp doesn't match the file's current
+ * mtime, we'd have to generate a string that doesn't
+ * match anyways, so cheat and base it on the existing
+ * string; it doesn't have to match the same mod time.
+ *
+ * For an unmodified file, write the correct timestamp.
+ */
+ {
+ struct stat sb;
+ if (strlen (ts) > 30 && CVS_STAT (user, &sb) == 0)
+ {
+ char *c = ctime (&sb.st_mtime);
+
+ if (!strncmp (ts + 25, c, 24))
+ ts = time_stamp (user);
+ else
+ {
+ ts += 24;
+ ts[0] = '*';
+ }
+ }
+ }
+
+ ent = Entnode_Create (type, user, vn, ts, options, tag, date,
+ ts_conflict);
+ break;
+ }
+
+ if (line_length < 0 && !feof (fpin))
+ error (0, errno, "cannot read entries file");
+
+ free (line);
+ return ent;
+}
+
+static int
+fputentent(fp, p)
+ FILE *fp;
+ Entnode *p;
+{
+ switch (p->type)
+ {
+ case ENT_FILE:
+ break;
+ case ENT_SUBDIR:
+ if (fprintf (fp, "D") < 0)
+ return 1;
+ break;
+ }
+
+ if (fprintf (fp, "/%s/%s/%s", p->user, p->version, p->timestamp) < 0)
+ return 1;
+ if (p->conflict)
+ {
+ if (fprintf (fp, "+%s", p->conflict) < 0)
+ return 1;
+ }
+ if (fprintf (fp, "/%s/", p->options) < 0)
+ return 1;
+
+ if (p->tag)
+ {
+ if (fprintf (fp, "T%s\n", p->tag) < 0)
+ return 1;
+ }
+ else if (p->date)
+ {
+ if (fprintf (fp, "D%s\n", p->date) < 0)
+ return 1;
+ }
+ else
+ {
+ if (fprintf (fp, "\n") < 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* Read the entries file into a list, hashing on the file name.
+
+ UPDATE_DIR is the name of the current directory, for use in error
+ messages, or NULL if not known (that is, noone has gotten around
+ to updating the caller to pass in the information). */
+List *
+Entries_Open (aflag, update_dir)
+ int aflag;
+ char *update_dir;
+{
+ List *entries;
+ struct stickydirtag *sdtp = NULL;
+ Entnode *ent;
+ char *dirtag, *dirdate;
+ int dirnonbranch;
+ int do_rewrite = 0;
+ FILE *fpin;
+ int sawdir;
+
+ /* get a fresh list... */
+ entries = getlist ();
+
+ /*
+ * Parse the CVS/Tag file, to get any default tag/date settings. Use
+ * list-private storage to tuck them away for Version_TS().
+ */
+ ParseTag (&dirtag, &dirdate, &dirnonbranch);
+ if (aflag || dirtag || dirdate)
+ {
+ sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
+ memset ((char *) sdtp, 0, sizeof (*sdtp));
+ sdtp->aflag = aflag;
+ sdtp->tag = xstrdup (dirtag);
+ sdtp->date = xstrdup (dirdate);
+ sdtp->nonbranch = dirnonbranch;
+
+ /* feed it into the list-private area */
+ entries->list->data = (char *) sdtp;
+ entries->list->delproc = freesdt;
+ }
+
+ sawdir = 0;
+
+ fpin = CVS_FOPEN (CVSADM_ENT, "r");
+ if (fpin == NULL)
+ {
+ if (update_dir != NULL)
+ error (0, 0, "in directory %s:", update_dir);
+ error (0, errno, "cannot open %s for reading", CVSADM_ENT);
+ }
+ else
+ {
+ while ((ent = fgetentent (fpin, (char *) NULL, &sawdir)) != NULL)
+ {
+ (void) AddEntryNode (entries, ent);
+ }
+
+ if (fclose (fpin) < 0)
+ /* FIXME-update-dir: should include update_dir in message. */
+ error (0, errno, "cannot close %s", CVSADM_ENT);
+ }
+
+ fpin = CVS_FOPEN (CVSADM_ENTLOG, "r");
+ if (fpin != NULL)
+ {
+ char cmd;
+ Node *node;
+
+ while ((ent = fgetentent (fpin, &cmd, &sawdir)) != NULL)
+ {
+ switch (cmd)
+ {
+ case 'A':
+ (void) AddEntryNode (entries, ent);
+ break;
+ case 'R':
+ node = findnode_fn (entries, ent->user);
+ if (node != NULL)
+ delnode (node);
+ Entnode_Destroy (ent);
+ break;
+ default:
+ /* Ignore unrecognized commands. */
+ break;
+ }
+ }
+ do_rewrite = 1;
+ if (fclose (fpin) < 0)
+ /* FIXME-update-dir: should include update_dir in message. */
+ error (0, errno, "cannot close %s", CVSADM_ENTLOG);
+ }
+
+ /* Update the list private data to indicate whether subdirectory
+ information is known. Nonexistent list private data is taken
+ to mean that it is known. */
+ if (sdtp != NULL)
+ sdtp->subdirs = sawdir;
+ else if (! sawdir)
+ {
+ sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
+ memset ((char *) sdtp, 0, sizeof (*sdtp));
+ sdtp->subdirs = 0;
+ entries->list->data = (char *) sdtp;
+ entries->list->delproc = freesdt;
+ }
+
+ if (do_rewrite && !noexec)
+ write_entries (entries);
+
+ /* clean up and return */
+ if (dirtag)
+ free (dirtag);
+ if (dirdate)
+ free (dirdate);
+ return (entries);
+}
+
+void
+Entries_Close(list)
+ List *list;
+{
+ if (list)
+ {
+ if (!noexec)
+ {
+ if (isfile (CVSADM_ENTLOG))
+ write_entries (list);
+ }
+ dellist(&list);
+ }
+}
+
+
+/*
+ * Free up the memory associated with the data section of an ENTRIES type
+ * node
+ */
+static void
+Entries_delproc (node)
+ Node *node;
+{
+ Entnode *p;
+
+ p = (Entnode *) node->data;
+ Entnode_Destroy(p);
+}
+
+/*
+ * Get an Entries file list node, initialize it, and add it to the specified
+ * list
+ */
+static Node *
+AddEntryNode (list, entdata)
+ List *list;
+ Entnode *entdata;
+{
+ Node *p;
+
+ /* was it already there? */
+ if ((p = findnode_fn (list, entdata->user)) != NULL)
+ {
+ /* take it out */
+ delnode (p);
+ }
+
+ /* get a node and fill in the regular stuff */
+ p = getnode ();
+ p->type = ENTRIES;
+ p->delproc = Entries_delproc;
+
+ /* this one gets a key of the name for hashing */
+ /* FIXME This results in duplicated data --- the hash package shouldn't
+ assume that the key is dynamically allocated. The user's free proc
+ should be responsible for freeing the key. */
+ p->key = xstrdup (entdata->user);
+ p->data = (char *) entdata;
+
+ /* put the node into the list */
+ addnode (list, p);
+ return (p);
+}
+
+/*
+ * Write out/Clear the CVS/Tag file.
+ */
+void
+WriteTag (dir, tag, date, nonbranch, update_dir, repository)
+ char *dir;
+ char *tag;
+ char *date;
+ int nonbranch;
+ char *update_dir;
+ char *repository;
+{
+ FILE *fout;
+ char *tmp;
+
+ if (noexec)
+ return;
+
+ tmp = xmalloc ((dir ? strlen (dir) : 0)
+ + sizeof (CVSADM_TAG)
+ + 10);
+ if (dir == NULL)
+ (void) strcpy (tmp, CVSADM_TAG);
+ else
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG);
+
+ if (tag || date)
+ {
+ fout = open_file (tmp, "w+");
+ if (tag)
+ {
+ if (nonbranch)
+ {
+ if (fprintf (fout, "N%s\n", tag) < 0)
+ error (1, errno, "write to %s failed", tmp);
+ }
+ else
+ {
+ if (fprintf (fout, "T%s\n", tag) < 0)
+ error (1, errno, "write to %s failed", tmp);
+ }
+ }
+ else
+ {
+ if (fprintf (fout, "D%s\n", date) < 0)
+ error (1, errno, "write to %s failed", tmp);
+ }
+ if (fclose (fout) == EOF)
+ error (1, errno, "cannot close %s", tmp);
+ }
+ else
+ if (unlink_file (tmp) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove %s", tmp);
+ free (tmp);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (update_dir, repository, tag, date, nonbranch);
+#endif
+}
+
+/* Parse the CVS/Tag file for the current directory.
+
+ If it contains a date, sets *DATEP to the date in a newly malloc'd
+ string, *TAGP to NULL, and *NONBRANCHP to an unspecified value.
+
+ If it contains a branch tag, sets *TAGP to the tag in a newly
+ malloc'd string, *NONBRANCHP to 0, and *DATEP to NULL.
+
+ If it contains a nonbranch tag, sets *TAGP to the tag in a newly
+ malloc'd string, *NONBRANCHP to 1, and *DATEP to NULL.
+
+ If it does not exist, or contains something unrecognized by this
+ version of CVS, set *DATEP and *TAGP to NULL and *NONBRANCHP to
+ an unspecified value.
+
+ If there is an error, print an error message, set *DATEP and *TAGP
+ to NULL, and return. */
+void
+ParseTag (tagp, datep, nonbranchp)
+ char **tagp;
+ char **datep;
+ int *nonbranchp;
+{
+ FILE *fp;
+
+ if (tagp)
+ *tagp = (char *) NULL;
+ if (datep)
+ *datep = (char *) NULL;
+ /* Always store a value here, even in the 'D' case where the value
+ is unspecified. Shuts up tools which check for references to
+ uninitialized memory. */
+ if (nonbranchp != NULL)
+ *nonbranchp = 0;
+ fp = CVS_FOPEN (CVSADM_TAG, "r");
+ if (fp)
+ {
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+
+ line = NULL;
+ line_chars_allocated = 0;
+
+ if ((line_length = getline (&line, &line_chars_allocated, fp)) > 0)
+ {
+ /* Remove any trailing newline. */
+ if (line[line_length - 1] == '\n')
+ line[--line_length] = '\0';
+ switch (*line)
+ {
+ case 'T':
+ if (tagp != NULL)
+ *tagp = xstrdup (line + 1);
+ break;
+ case 'D':
+ if (datep != NULL)
+ *datep = xstrdup (line + 1);
+ break;
+ case 'N':
+ if (tagp != NULL)
+ *tagp = xstrdup (line + 1);
+ if (nonbranchp != NULL)
+ *nonbranchp = 1;
+ break;
+ default:
+ /* Silently ignore it; it may have been
+ written by a future version of CVS which extends the
+ syntax. */
+ break;
+ }
+ }
+
+ if (line_length < 0)
+ {
+ /* FIXME-update-dir: should include update_dir in messages. */
+ if (feof (fp))
+ error (0, 0, "cannot read %s: end of file", CVSADM_TAG);
+ else
+ error (0, errno, "cannot read %s", CVSADM_TAG);
+ }
+
+ if (fclose (fp) < 0)
+ /* FIXME-update-dir: should include update_dir in message. */
+ error (0, errno, "cannot close %s", CVSADM_TAG);
+
+ free (line);
+ }
+ else if (!existence_error (errno))
+ /* FIXME-update-dir: should include update_dir in message. */
+ error (0, errno, "cannot open %s", CVSADM_TAG);
+}
+
+/*
+ * This is called if all subdirectory information is known, but there
+ * aren't any subdirectories. It records that fact in the list
+ * private data.
+ */
+
+void
+Subdirs_Known (entries)
+ List *entries;
+{
+ struct stickydirtag *sdtp;
+
+ /* If there is no list private data, that means that the
+ subdirectory information is known. */
+ sdtp = (struct stickydirtag *) entries->list->data;
+ if (sdtp != NULL && ! sdtp->subdirs)
+ {
+ FILE *fp;
+
+ sdtp->subdirs = 1;
+ if (!noexec)
+ {
+ /* Create Entries.Log so that Entries_Close will do something. */
+ entfilename = CVSADM_ENTLOG;
+ fp = CVS_FOPEN (entfilename, "a");
+ if (fp == NULL)
+ {
+ int save_errno = errno;
+
+ /* As in subdir_record, just silently skip the whole thing
+ if there is no CVSADM directory. */
+ if (! isdir (CVSADM))
+ return;
+ error (1, save_errno, "cannot open %s", entfilename);
+ }
+ else
+ {
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", entfilename);
+ }
+ }
+ }
+}
+
+/* Record subdirectory information. */
+
+static Entnode *
+subdir_record (cmd, parent, dir)
+ int cmd;
+ const char *parent;
+ const char *dir;
+{
+ Entnode *entnode;
+
+ /* None of the information associated with a directory is
+ currently meaningful. */
+ entnode = Entnode_Create (ENT_SUBDIR, dir, "", "", "",
+ (char *) NULL, (char *) NULL,
+ (char *) NULL);
+
+ if (!noexec)
+ {
+ if (parent == NULL)
+ entfilename = CVSADM_ENTLOG;
+ else
+ {
+ entfilename = xmalloc (strlen (parent)
+ + sizeof CVSADM_ENTLOG
+ + 10);
+ sprintf (entfilename, "%s/%s", parent, CVSADM_ENTLOG);
+ }
+
+ entfile = CVS_FOPEN (entfilename, "a");
+ if (entfile == NULL)
+ {
+ int save_errno = errno;
+
+ /* It is not an error if there is no CVS administration
+ directory. Permitting this case simplifies some
+ calling code. */
+
+ if (parent == NULL)
+ {
+ if (! isdir (CVSADM))
+ return entnode;
+ }
+ else
+ {
+ sprintf (entfilename, "%s/%s", parent, CVSADM);
+ if (! isdir (entfilename))
+ {
+ free (entfilename);
+ entfilename = NULL;
+ return entnode;
+ }
+ }
+
+ error (1, save_errno, "cannot open %s", entfilename);
+ }
+
+ if (fprintf (entfile, "%c ", cmd) < 0)
+ error (1, errno, "cannot write %s", entfilename);
+
+ if (fputentent (entfile, entnode) != 0)
+ error (1, errno, "cannot write %s", entfilename);
+
+ if (fclose (entfile) == EOF)
+ error (1, errno, "error closing %s", entfilename);
+
+ if (parent != NULL)
+ {
+ free (entfilename);
+ entfilename = NULL;
+ }
+ }
+
+ return entnode;
+}
+
+/*
+ * Record the addition of a new subdirectory DIR in PARENT. PARENT
+ * may be NULL, which means the current directory. ENTRIES is the
+ * current entries list; it may be NULL, which means that it need not
+ * be updated.
+ */
+
+void
+Subdir_Register (entries, parent, dir)
+ List *entries;
+ const char *parent;
+ const char *dir;
+{
+ Entnode *entnode;
+
+ /* Ignore attempts to register ".". These can happen in the
+ server code. */
+ if (dir[0] == '.' && dir[1] == '\0')
+ return;
+
+ entnode = subdir_record ('A', parent, dir);
+
+ if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0))
+ (void) AddEntryNode (entries, entnode);
+ else
+ Entnode_Destroy (entnode);
+}
+
+/*
+ * Record the removal of a subdirectory. The arguments are the same
+ * as for Subdir_Register.
+ */
+
+void
+Subdir_Deregister (entries, parent, dir)
+ List *entries;
+ const char *parent;
+ const char *dir;
+{
+ Entnode *entnode;
+
+ entnode = subdir_record ('R', parent, dir);
+ Entnode_Destroy (entnode);
+
+ if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0))
+ {
+ Node *p;
+
+ p = findnode_fn (entries, dir);
+ if (p != NULL)
+ delnode (p);
+ }
+}
+
+
+
+/* OK, the following base_* code tracks the revisions of the files in
+ CVS/Base. We do this in a file CVS/Baserev. Separate from
+ CVS/Entries because it needs to go in separate data structures
+ anyway (the name in Entries must be unique), so this seemed
+ cleaner. The business of rewriting the whole file in
+ base_deregister and base_register is the kind of thing we used to
+ do for Entries and which turned out to be slow, which is why there
+ is now the Entries.Log machinery. So maybe from that point of
+ view it is a mistake to do this separately from Entries, I dunno.
+
+ We also need something analogous for:
+
+ 1. CVS/Template (so we can update the Template file automagically
+ without the user needing to check out a new working directory).
+ Updating would probably print a message (that part might be
+ optional, although probably it should be visible because not all
+ cvs commands would make the update happen and so it is a
+ user-visible behavior). Constructing version number for template
+ is a bit hairy (base it on the timestamp on the server? Or see if
+ the template is in checkoutlist and if yes use its versioning and
+ if no don't version it?)....
+
+ 2. cvsignore (need to keep a copy in the working directory to do
+ "cvs release" on the client side; see comment at src/release.c
+ (release). Would also allow us to stop needing Questionable. */
+
+enum base_walk {
+ /* Set the revision for FILE to *REV. */
+ BASE_REGISTER,
+ /* Get the revision for FILE and put it in a newly malloc'd string
+ in *REV, or put NULL if not mentioned. */
+ BASE_GET,
+ /* Remove FILE. */
+ BASE_DEREGISTER
+};
+
+static void base_walk PROTO ((enum base_walk, struct file_info *, char **));
+
+/* Read through the lines in CVS/Baserev, taking the actions as documented
+ for CODE. */
+
+static void
+base_walk (code, finfo, rev)
+ enum base_walk code;
+ struct file_info *finfo;
+ char **rev;
+{
+ FILE *fp;
+ char *line;
+ size_t line_allocated;
+ FILE *newf;
+ char *baserev_fullname;
+ char *baserevtmp_fullname;
+
+ line = NULL;
+ line_allocated = 0;
+ newf = NULL;
+
+ /* First compute the fullnames for the error messages. This
+ computation probably should be broken out into a separate function,
+ as recurse.c does it too and places like Entries_Open should be
+ doing it. */
+ baserev_fullname = xmalloc (sizeof (CVSADM_BASEREV)
+ + strlen (finfo->update_dir)
+ + 2);
+ baserev_fullname[0] = '\0';
+ baserevtmp_fullname = xmalloc (sizeof (CVSADM_BASEREVTMP)
+ + strlen (finfo->update_dir)
+ + 2);
+ baserevtmp_fullname[0] = '\0';
+ if (finfo->update_dir[0] != '\0')
+ {
+ strcat (baserev_fullname, finfo->update_dir);
+ strcat (baserev_fullname, "/");
+ strcat (baserevtmp_fullname, finfo->update_dir);
+ strcat (baserevtmp_fullname, "/");
+ }
+ strcat (baserev_fullname, CVSADM_BASEREV);
+ strcat (baserevtmp_fullname, CVSADM_BASEREVTMP);
+
+ fp = CVS_FOPEN (CVSADM_BASEREV, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ {
+ error (0, errno, "cannot open %s for reading", baserev_fullname);
+ goto out;
+ }
+ }
+
+ switch (code)
+ {
+ case BASE_REGISTER:
+ case BASE_DEREGISTER:
+ newf = CVS_FOPEN (CVSADM_BASEREVTMP, "w");
+ if (newf == NULL)
+ {
+ error (0, errno, "cannot open %s for writing",
+ baserevtmp_fullname);
+ goto out;
+ }
+ break;
+ case BASE_GET:
+ *rev = NULL;
+ break;
+ }
+
+ if (fp != NULL)
+ {
+ while (getline (&line, &line_allocated, fp) >= 0)
+ {
+ char *linefile;
+ char *p;
+ char *linerev;
+
+ if (line[0] != 'B')
+ /* Ignore, for future expansion. */
+ continue;
+
+ linefile = line + 1;
+ p = strchr (linefile, '/');
+ if (p == NULL)
+ /* Syntax error, ignore. */
+ continue;
+ linerev = p + 1;
+ p = strchr (linerev, '/');
+ if (p == NULL)
+ continue;
+
+ linerev[-1] = '\0';
+ if (fncmp (linefile, finfo->file) == 0)
+ {
+ switch (code)
+ {
+ case BASE_REGISTER:
+ case BASE_DEREGISTER:
+ /* Don't copy over the old entry, we don't want it. */
+ break;
+ case BASE_GET:
+ *p = '\0';
+ *rev = xstrdup (linerev);
+ *p = '/';
+ goto got_it;
+ }
+ }
+ else
+ {
+ linerev[-1] = '/';
+ switch (code)
+ {
+ case BASE_REGISTER:
+ case BASE_DEREGISTER:
+ if (fprintf (newf, "%s\n", line) < 0)
+ error (0, errno, "error writing %s",
+ baserevtmp_fullname);
+ break;
+ case BASE_GET:
+ break;
+ }
+ }
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", baserev_fullname);
+ }
+ got_it:
+
+ if (code == BASE_REGISTER)
+ {
+ if (fprintf (newf, "B%s/%s/\n", finfo->file, *rev) < 0)
+ error (0, errno, "error writing %s",
+ baserevtmp_fullname);
+ }
+
+ out:
+
+ if (line != NULL)
+ free (line);
+
+ if (fp != NULL)
+ {
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", baserev_fullname);
+ }
+ if (newf != NULL)
+ {
+ if (fclose (newf) < 0)
+ error (0, errno, "cannot close %s", baserevtmp_fullname);
+ rename_file (CVSADM_BASEREVTMP, CVSADM_BASEREV);
+ }
+
+ free (baserev_fullname);
+ free (baserevtmp_fullname);
+}
+
+/* Return, in a newly malloc'd string, the revision for FILE in CVS/Baserev,
+ or NULL if not listed. */
+
+char *
+base_get (finfo)
+ struct file_info *finfo;
+{
+ char *rev;
+ base_walk (BASE_GET, finfo, &rev);
+ return rev;
+}
+
+/* Set the revision for FILE to REV. */
+
+void
+base_register (finfo, rev)
+ struct file_info *finfo;
+ char *rev;
+{
+ base_walk (BASE_REGISTER, finfo, &rev);
+}
+
+/* Remove FILE. */
+
+void
+base_deregister (finfo)
+ struct file_info *finfo;
+{
+ base_walk (BASE_DEREGISTER, finfo, NULL);
+}
diff --git a/gnu/dist/cvs/src/error.c b/gnu/dist/cvs/src/error.c
new file mode 100644
index 00000000000..bdb519d5a09
--- /dev/null
+++ b/gnu/dist/cvs/src/error.c
@@ -0,0 +1,236 @@
+/* error.c -- error handler for noninteractive utilities
+ Copyright (C) 1990-1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+/* David MacKenzie */
+/* Brian Berliner added support for CVS */
+
+#include "cvs.h"
+
+#include <stdio.h>
+
+/* If non-zero, error will use the CVS protocol to stdout to report error
+ messages. This will only be set in the CVS server parent process;
+ most other code is run via do_cvs_command, which forks off a child
+ process and packages up its stderr in the protocol. */
+int error_use_protocol;
+
+#ifdef HAVE_VPRINTF
+
+#ifdef __STDC__
+#include <stdarg.h>
+#define VA_START(args, lastarg) va_start(args, lastarg)
+#else /* ! __STDC__ */
+#include <varargs.h>
+#define VA_START(args, lastarg) va_start(args)
+#endif /* __STDC__ */
+
+#else /* ! HAVE_VPRINTF */
+
+#ifdef HAVE_DOPRNT
+#define va_alist args
+#define va_dcl int args;
+#else /* ! HAVE_DOPRNT */
+#define va_alist a1, a2, a3, a4, a5, a6, a7, a8
+#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
+#endif /* HAVE_DOPRNT */
+
+#endif /* HAVE_VPRINTF */
+
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <string.h>
+#else /* ! STDC_HEADERS */
+#ifdef __STDC__
+void exit(int status);
+#else /* ! __STDC__ */
+void exit ();
+#endif /* __STDC__ */
+#endif /* STDC_HEADERS */
+
+#ifndef strerror
+extern char *strerror ();
+#endif
+
+void
+error_exit PROTO ((void))
+{
+ Lock_Cleanup();
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_cleanup (0);
+#endif
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (EXIT_FAILURE);
+}
+
+/* Print the program name and error message MESSAGE, which is a printf-style
+ format string with optional args. This is a very limited printf subset:
+ %s, %d, %c, %x and %% only (without anything between the % and the s,
+ d, &c). Callers who want something fancier can use sprintf.
+
+ If ERRNUM is nonzero, print its corresponding system error message.
+ Exit with status EXIT_FAILURE if STATUS is nonzero. If MESSAGE is "",
+ no need to print a message.
+
+ I think this is largely cleaned up to the point where it does the right
+ thing for the server, whether the normal server_active (child process)
+ case or the error_use_protocol (parent process) case. The one exception
+ is that STATUS nonzero for error_use_protocol probably doesn't work yet;
+ in that case still need to use the pending_error machinery in server.c.
+
+ error() does not molest errno; some code (e.g. Entries_Open) depends
+ on being able to say something like:
+ error (0, 0, "foo");
+ error (0, errno, "bar");
+
+ */
+
+/* VARARGS */
+void
+#if defined (__STDC__)
+error (int status, int errnum, const char *message, ...)
+#else
+error (status, errnum, message, va_alist)
+ int status;
+ int errnum;
+ const char *message;
+ va_dcl
+#endif
+{
+ int save_errno = errno;
+
+ if (message[0] != '\0')
+ {
+ va_list args;
+ const char *p;
+ char *q;
+ char *str;
+ int num;
+ unsigned int unum;
+ int ch;
+ unsigned char buf[100];
+
+ cvs_outerr (program_name, 0);
+ if (command_name && *command_name)
+ {
+ cvs_outerr (" ", 1);
+ if (status != 0)
+ cvs_outerr ("[", 1);
+ cvs_outerr (command_name, 0);
+ if (status != 0)
+ cvs_outerr (" aborted]", 0);
+ }
+ cvs_outerr (": ", 2);
+
+ VA_START (args, message);
+ p = message;
+ while ((q = strchr (p, '%')) != NULL)
+ {
+ static const char msg[] =
+ "\ninternal error: bad % in error()\n";
+ if (q - p > 0)
+ cvs_outerr (p, q - p);
+
+ switch (q[1])
+ {
+ case 's':
+ str = va_arg (args, char *);
+ cvs_outerr (str, strlen (str));
+ break;
+ case 'd':
+ num = va_arg (args, int);
+ sprintf (buf, "%d", num);
+ cvs_outerr (buf, strlen (buf));
+ break;
+ case 'x':
+ unum = va_arg (args, unsigned int);
+ sprintf (buf, "%x", unum);
+ cvs_outerr (buf, strlen (buf));
+ break;
+ case 'c':
+ ch = va_arg (args, int);
+ buf[0] = ch;
+ cvs_outerr (buf, 1);
+ break;
+ case '%':
+ cvs_outerr ("%", 1);
+ break;
+ default:
+ cvs_outerr (msg, sizeof (msg) - 1);
+ /* Don't just keep going, because q + 1 might point to the
+ terminating '\0'. */
+ goto out;
+ }
+ p = q + 2;
+ }
+ cvs_outerr (p, strlen (p));
+ out:
+ va_end (args);
+
+ if (errnum != 0)
+ {
+ cvs_outerr (": ", 2);
+ cvs_outerr (strerror (errnum), 0);
+ }
+ cvs_outerr ("\n", 1);
+ }
+
+ if (status)
+ error_exit ();
+ errno = save_errno;
+}
+
+/* Print the program name and error message MESSAGE, which is a printf-style
+ format string with optional args to the file specified by FP.
+ If ERRNUM is nonzero, print its corresponding system error message.
+ Exit with status EXIT_FAILURE if STATUS is nonzero. */
+/* VARARGS */
+void
+#if defined (HAVE_VPRINTF) && defined (__STDC__)
+fperrmsg (FILE *fp, int status, int errnum, char *message, ...)
+#else
+fperrmsg (fp, status, errnum, message, va_alist)
+ FILE *fp;
+ int status;
+ int errnum;
+ char *message;
+ va_dcl
+#endif
+{
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+
+ fprintf (fp, "%s: ", program_name);
+#ifdef HAVE_VPRINTF
+ VA_START (args, message);
+ vfprintf (fp, message, args);
+ va_end (args);
+#else
+#ifdef HAVE_DOPRNT
+ _doprnt (message, &args, fp);
+#else
+ fprintf (fp, message, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+#endif
+ if (errnum)
+ fprintf (fp, ": %s", strerror (errnum));
+ putc ('\n', fp);
+ fflush (fp);
+ if (status)
+ error_exit ();
+}
diff --git a/gnu/dist/cvs/src/expand_path.c b/gnu/dist/cvs/src/expand_path.c
new file mode 100644
index 00000000000..25e561cc0e6
--- /dev/null
+++ b/gnu/dist/cvs/src/expand_path.c
@@ -0,0 +1,335 @@
+/* expand_path.c -- expand environmental variables in passed in string
+ *
+ * The main routine is expand_path(), it is the routine that handles
+ * the '~' character in four forms:
+ * ~name
+ * ~name/
+ * ~/
+ * ~
+ * and handles environment variables contained within the pathname
+ * which are defined by:
+ * ${var_name} (var_name is the name of the environ variable)
+ * $var_name (var_name ends w/ non-alphanumeric char other than '_')
+ */
+
+#include "cvs.h"
+#include <sys/types.h>
+
+static char *expand_variable PROTO((char *env, char *file, int line));
+
+
+/* User variables. */
+
+List *variable_list = NULL;
+
+static void variable_delproc PROTO ((Node *));
+
+static void
+variable_delproc (node)
+ Node *node;
+{
+ free (node->data);
+}
+
+/* Currently used by -s option; we might want a way to set user
+ variables in a file in the $CVSROOT/CVSROOT directory too. */
+
+void
+variable_set (nameval)
+ char *nameval;
+{
+ char *p;
+ char *name;
+ Node *node;
+
+ p = nameval;
+ while (isalnum ((unsigned char) *p) || *p == '_')
+ ++p;
+ if (*p != '=')
+ error (1, 0, "illegal character in user variable name in %s", nameval);
+ if (p == nameval)
+ error (1, 0, "empty user variable name in %s", nameval);
+ name = xmalloc (p - nameval + 1);
+ strncpy (name, nameval, p - nameval);
+ name[p - nameval] = '\0';
+ /* Make p point to the value. */
+ ++p;
+ if (strchr (p, '\012') != NULL)
+ error (1, 0, "linefeed in user variable value in %s", nameval);
+
+ if (variable_list == NULL)
+ variable_list = getlist ();
+
+ node = findnode (variable_list, name);
+ if (node == NULL)
+ {
+ node = getnode ();
+ node->type = VARIABLE;
+ node->delproc = variable_delproc;
+ node->key = name;
+ node->data = xstrdup (p);
+ (void) addnode (variable_list, node);
+ }
+ else
+ {
+ /* Replace the old value. For example, this means that -s
+ options on the command line override ones from .cvsrc. */
+ free (node->data);
+ node->data = xstrdup (p);
+ free (name);
+ }
+}
+
+/* This routine will expand the pathname to account for ~ and $
+ characters as described above. Returns a pointer to a newly
+ malloc'd string. If an error occurs, an error message is printed
+ via error() and NULL is returned. FILE and LINE are the filename
+ and linenumber to include in the error message. FILE must point
+ to something; LINE can be zero to indicate the line number is not
+ known. */
+char *
+expand_path (name, file, line)
+ char *name;
+ char *file;
+ int line;
+{
+ char *s;
+ char *d;
+
+ char *mybuf = NULL;
+ size_t mybuf_size = 0;
+ char *buf = NULL;
+ size_t buf_size = 0;
+
+ size_t doff;
+
+ char *result;
+
+ /* Sorry this routine is so ugly; it is a head-on collision
+ between the `traditional' unix *d++ style and the need to
+ dynamically allocate. It would be much cleaner (and probably
+ faster, not that this is a bottleneck for CVS) with more use of
+ strcpy & friends, but I haven't taken the effort to rewrite it
+ thusly. */
+
+ /* First copy from NAME to MYBUF, expanding $<foo> as we go. */
+ s = name;
+ d = mybuf;
+ doff = d - mybuf;
+ expand_string (&mybuf, &mybuf_size, doff + 1);
+ d = mybuf + doff;
+ while ((*d++ = *s))
+ {
+ if (*s++ == '$')
+ {
+ char *p = d;
+ char *e;
+ int flag = (*s == '{');
+
+ doff = d - mybuf;
+ expand_string (&mybuf, &mybuf_size, doff + 1);
+ d = mybuf + doff;
+ for (; (*d++ = *s); s++)
+ {
+ if (flag
+ ? *s =='}'
+ : isalnum ((unsigned char) *s) == 0 && *s != '_')
+ break;
+ doff = d - mybuf;
+ expand_string (&mybuf, &mybuf_size, doff + 1);
+ d = mybuf + doff;
+ }
+ *--d = '\0';
+ e = expand_variable (&p[flag], file, line);
+
+ if (e)
+ {
+ doff = d - mybuf;
+ expand_string (&mybuf, &mybuf_size, doff + 1);
+ d = mybuf + doff;
+ for (d = &p[-1]; (*d++ = *e++);)
+ {
+ doff = d - mybuf;
+ expand_string (&mybuf, &mybuf_size, doff + 1);
+ d = mybuf + doff;
+ }
+ --d;
+ if (flag && *s)
+ s++;
+ }
+ else
+ /* expand_variable has already printed an error message. */
+ goto error_exit;
+ }
+ doff = d - mybuf;
+ expand_string (&mybuf, &mybuf_size, doff + 1);
+ d = mybuf + doff;
+ }
+ doff = d - mybuf;
+ expand_string (&mybuf, &mybuf_size, doff + 1);
+ d = mybuf + doff;
+ *d = '\0';
+
+ /* Then copy from MYBUF to BUF, expanding ~. */
+ s = mybuf;
+ d = buf;
+ /* If you don't want ~username ~/ to be expanded simply remove
+ * This entire if statement including the else portion
+ */
+ if (*s++ == '~')
+ {
+ char *t;
+ char *p=s;
+ if (*s=='/' || *s==0)
+ t = get_homedir ();
+ else
+ {
+#ifdef GETPWNAM_MISSING
+ for (; *p!='/' && *p; p++)
+ ;
+ *p = 0;
+ if (line != 0)
+ error (0, 0,
+ "%s:%d:tilde expansion not supported on this system",
+ file, line);
+ else
+ error (0, 0, "%s:tilde expansion not supported on this system",
+ file);
+ return NULL;
+#else
+ struct passwd *ps;
+ for (; *p!='/' && *p; p++)
+ ;
+ *p = 0;
+ ps = getpwnam (s);
+ if (ps == 0)
+ {
+ if (line != 0)
+ error (0, 0, "%s:%d: no such user %s",
+ file, line, s);
+ else
+ error (0, 0, "%s: no such user %s", file, s);
+ return NULL;
+ }
+ t = ps->pw_dir;
+#endif
+ }
+ if (t == NULL)
+ error (1, 0, "cannot find home directory");
+
+ doff = d - buf;
+ expand_string (&buf, &buf_size, doff + 1);
+ d = buf + doff;
+ while ((*d++ = *t++))
+ {
+ doff = d - buf;
+ expand_string (&buf, &buf_size, doff + 1);
+ d = buf + doff;
+ }
+ --d;
+ if (*p == 0)
+ *p = '/'; /* always add / */
+ s=p;
+ }
+ else
+ --s;
+ /* Kill up to here */
+ doff = d - buf;
+ expand_string (&buf, &buf_size, doff + 1);
+ d = buf + doff;
+ while ((*d++ = *s++))
+ {
+ doff = d - buf;
+ expand_string (&buf, &buf_size, doff + 1);
+ d = buf + doff;
+ }
+ doff = d - buf;
+ expand_string (&buf, &buf_size, doff + 1);
+ d = buf + doff;
+ *d = '\0';
+
+ /* OK, buf contains the value we want to return. Clean up and return
+ it. */
+ free (mybuf);
+ /* Save a little memory with xstrdup; buf will tend to allocate
+ more than it needs to. */
+ result = xstrdup (buf);
+ free (buf);
+ return result;
+
+ error_exit:
+ if (mybuf != NULL)
+ free (mybuf);
+ if (buf != NULL)
+ free (buf);
+ return NULL;
+}
+
+static char *
+expand_variable (name, file, line)
+ char *name;
+ char *file;
+ int line;
+{
+ if (strcmp (name, CVSROOT_ENV) == 0)
+ return CVSroot_original;
+ else if (strcmp (name, "RCSBIN") == 0)
+ {
+ error (0, 0, "RCSBIN internal variable is no longer supported");
+ return NULL;
+ }
+ else if (strcmp (name, EDITOR1_ENV) == 0)
+ return Editor;
+ else if (strcmp (name, EDITOR2_ENV) == 0)
+ return Editor;
+ else if (strcmp (name, EDITOR3_ENV) == 0)
+ return Editor;
+ else if (strcmp (name, "USER") == 0)
+ return getcaller ();
+ else if (isalpha ((unsigned char) name[0]))
+ {
+ /* These names are reserved for future versions of CVS,
+ so that is why it is an error. */
+ if (line != 0)
+ error (0, 0, "%s:%d: no such internal variable $%s",
+ file, line, name);
+ else
+ error (0, 0, "%s: no such internal variable $%s",
+ file, name);
+ return NULL;
+ }
+ else if (name[0] == '=')
+ {
+ Node *node;
+ /* Crazy syntax for a user variable. But we want
+ *something* that lets the user name a user variable
+ anything he wants, without interference from
+ (existing or future) internal variables. */
+ node = findnode (variable_list, name + 1);
+ if (node == NULL)
+ {
+ if (line != 0)
+ error (0, 0, "%s:%d: no such user variable ${%s}",
+ file, line, name);
+ else
+ error (0, 0, "%s: no such user variable ${%s}",
+ file, name);
+ return NULL;
+ }
+ return node->data;
+ }
+ else
+ {
+ /* It is an unrecognized character. We return an error to
+ reserve these for future versions of CVS; it is plausible
+ that various crazy syntaxes might be invented for inserting
+ information about revisions, branches, etc. */
+ if (line != 0)
+ error (0, 0, "%s:%d: unrecognized variable syntax %s",
+ file, line, name);
+ else
+ error (0, 0, "%s: unrecognized variable syntax %s",
+ file, name);
+ return NULL;
+ }
+}
diff --git a/gnu/dist/cvs/src/fileattr.c b/gnu/dist/cvs/src/fileattr.c
new file mode 100644
index 00000000000..4042d2a528e
--- /dev/null
+++ b/gnu/dist/cvs/src/fileattr.c
@@ -0,0 +1,652 @@
+/* Implementation for file attribute munging features.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+#include "cvs.h"
+#include "getline.h"
+#include "fileattr.h"
+#include <assert.h>
+
+static void fileattr_read PROTO ((void));
+static int writeattr_proc PROTO ((Node *, void *));
+
+/* Where to look for CVSREP_FILEATTR. */
+static char *fileattr_stored_repos;
+
+/* The in-memory attributes. */
+static List *attrlist;
+static char *fileattr_default_attrs;
+/* We have already tried to read attributes and failed in this directory
+ (for example, there is no CVSREP_FILEATTR file). */
+static int attr_read_attempted;
+
+/* Have the in-memory attributes been modified since we read them? */
+static int attrs_modified;
+
+/* More in-memory attributes: linked list of unrecognized
+ fileattr lines. We pass these on unchanged. */
+struct unrecog {
+ char *line;
+ struct unrecog *next;
+};
+static struct unrecog *unrecog_head;
+
+/* Note that if noone calls fileattr_get, this is very cheap. No stat(),
+ no open(), no nothing. */
+void
+fileattr_startdir (repos)
+ char *repos;
+{
+ assert (fileattr_stored_repos == NULL);
+ fileattr_stored_repos = xstrdup (repos);
+ assert (attrlist == NULL);
+ attr_read_attempted = 0;
+ assert (unrecog_head == NULL);
+}
+
+static void
+fileattr_delproc (node)
+ Node *node;
+{
+ assert (node->data != NULL);
+ free (node->data);
+ node->data = NULL;
+}
+
+/* Read all the attributes for the current directory into memory. */
+static void
+fileattr_read ()
+{
+ char *fname;
+ FILE *fp;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ /* If there are no attributes, don't waste time repeatedly looking
+ for the CVSREP_FILEATTR file. */
+ if (attr_read_attempted)
+ return;
+
+ /* If NULL was passed to fileattr_startdir, then it isn't kosher to look
+ at attributes. */
+ assert (fileattr_stored_repos != NULL);
+
+ fname = xmalloc (strlen (fileattr_stored_repos)
+ + 1
+ + sizeof (CVSREP_FILEATTR)
+ + 1);
+
+ strcpy (fname, fileattr_stored_repos);
+ strcat (fname, "/");
+ strcat (fname, CVSREP_FILEATTR);
+
+ attr_read_attempted = 1;
+ fp = CVS_FOPEN (fname, FOPEN_BINARY_READ);
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot read %s", fname);
+ free (fname);
+ return;
+ }
+ attrlist = getlist ();
+ while (1) {
+ int nread;
+ nread = getline (&line, &line_len, fp);
+ if (nread < 0)
+ break;
+ /* Remove trailing newline. */
+ line[nread - 1] = '\0';
+ if (line[0] == 'F')
+ {
+ char *p;
+ Node *newnode;
+
+ p = strchr (line, '\t');
+ if (p == NULL)
+ error (1, 0,
+ "file attribute database corruption: tab missing in %s",
+ fname);
+ *p++ = '\0';
+ newnode = getnode ();
+ newnode->type = FILEATTR;
+ newnode->delproc = fileattr_delproc;
+ newnode->key = xstrdup (line + 1);
+ newnode->data = xstrdup (p);
+ if (addnode (attrlist, newnode) != 0)
+ /* If the same filename appears twice in the file, discard
+ any line other than the first for that filename. This
+ is the way that CVS has behaved since file attributes
+ were first introduced. */
+ free (newnode);
+ }
+ else if (line[0] == 'D')
+ {
+ char *p;
+ /* Currently nothing to skip here, but for future expansion,
+ ignore anything located here. */
+ p = strchr (line, '\t');
+ if (p == NULL)
+ error (1, 0,
+ "file attribute database corruption: tab missing in %s",
+ fname);
+ ++p;
+ fileattr_default_attrs = xstrdup (p);
+ }
+ else
+ {
+ /* Unrecognized type, we want to just preserve the line without
+ changing it, for future expansion. */
+ struct unrecog *new;
+
+ new = (struct unrecog *) xmalloc (sizeof (struct unrecog));
+ new->line = xstrdup (line);
+ new->next = unrecog_head;
+ unrecog_head = new;
+ }
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", fname);
+ if (line != NULL)
+ free (line);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ attrs_modified = 0;
+ free (fname);
+}
+
+char *
+fileattr_get (filename, attrname)
+ const char *filename;
+ const char *attrname;
+{
+ Node *node;
+ size_t attrname_len = strlen (attrname);
+ char *p;
+
+ if (attrlist == NULL)
+ fileattr_read ();
+ if (attrlist == NULL)
+ /* Either nothing has any attributes, or fileattr_read already printed
+ an error message. */
+ return NULL;
+
+ if (filename == NULL)
+ p = fileattr_default_attrs;
+ else
+ {
+ node = findnode (attrlist, filename);
+ if (node == NULL)
+ /* A file not mentioned has no attributes. */
+ return NULL;
+ p = node->data;
+ }
+ while (p)
+ {
+ if (strncmp (attrname, p, attrname_len) == 0
+ && p[attrname_len] == '=')
+ {
+ /* Found it. */
+ return p + attrname_len + 1;
+ }
+ p = strchr (p, ';');
+ if (p == NULL)
+ break;
+ ++p;
+ }
+ /* The file doesn't have this attribute. */
+ return NULL;
+}
+
+char *
+fileattr_get0 (filename, attrname)
+ const char *filename;
+ const char *attrname;
+{
+ char *cp;
+ char *cpend;
+ char *retval;
+
+ cp = fileattr_get (filename, attrname);
+ if (cp == NULL)
+ return NULL;
+ cpend = strchr (cp, ';');
+ if (cpend == NULL)
+ cpend = cp + strlen (cp);
+ retval = xmalloc (cpend - cp + 1);
+ strncpy (retval, cp, cpend - cp);
+ retval[cpend - cp] = '\0';
+ return retval;
+}
+
+char *
+fileattr_modify (list, attrname, attrval, namevalsep, entsep)
+ char *list;
+ const char *attrname;
+ const char *attrval;
+ int namevalsep;
+ int entsep;
+{
+ char *retval;
+ char *rp;
+ size_t attrname_len = strlen (attrname);
+
+ /* Portion of list before the attribute to be replaced. */
+ char *pre;
+ char *preend;
+ /* Portion of list after the attribute to be replaced. */
+ char *post;
+
+ char *p;
+ char *p2;
+
+ p = list;
+ pre = list;
+ preend = NULL;
+ /* post is NULL unless set otherwise. */
+ post = NULL;
+ p2 = NULL;
+ if (list != NULL)
+ {
+ while (1) {
+ p2 = strchr (p, entsep);
+ if (p2 == NULL)
+ {
+ p2 = p + strlen (p);
+ if (preend == NULL)
+ preend = p2;
+ }
+ else
+ ++p2;
+ if (strncmp (attrname, p, attrname_len) == 0
+ && p[attrname_len] == namevalsep)
+ {
+ /* Found it. */
+ preend = p;
+ if (preend > list)
+ /* Don't include the preceding entsep. */
+ --preend;
+
+ post = p2;
+ }
+ if (p2[0] == '\0')
+ break;
+ p = p2;
+ }
+ }
+ if (post == NULL)
+ post = p2;
+
+ if (preend == pre && attrval == NULL && post == p2)
+ return NULL;
+
+ retval = xmalloc ((preend - pre)
+ + 1
+ + (attrval == NULL ? 0 : (attrname_len + 1
+ + strlen (attrval)))
+ + 1
+ + (p2 - post)
+ + 1);
+ if (preend != pre)
+ {
+ strncpy (retval, pre, preend - pre);
+ rp = retval + (preend - pre);
+ if (attrval != NULL)
+ *rp++ = entsep;
+ *rp = '\0';
+ }
+ else
+ retval[0] = '\0';
+ if (attrval != NULL)
+ {
+ strcat (retval, attrname);
+ rp = retval + strlen (retval);
+ *rp++ = namevalsep;
+ strcpy (rp, attrval);
+ }
+ if (post != p2)
+ {
+ rp = retval + strlen (retval);
+ if (preend != pre || attrval != NULL)
+ *rp++ = entsep;
+ strncpy (rp, post, p2 - post);
+ rp += p2 - post;
+ *rp = '\0';
+ }
+ return retval;
+}
+
+void
+fileattr_set (filename, attrname, attrval)
+ const char *filename;
+ const char *attrname;
+ const char *attrval;
+{
+ Node *node;
+ char *p;
+
+ if (filename == NULL)
+ {
+ p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
+ '=', ';');
+ if (fileattr_default_attrs != NULL)
+ free (fileattr_default_attrs);
+ fileattr_default_attrs = p;
+ attrs_modified = 1;
+ return;
+ }
+ if (attrlist == NULL)
+ fileattr_read ();
+ if (attrlist == NULL)
+ {
+ /* Not sure this is a graceful way to handle things
+ in the case where fileattr_read was unable to read the file. */
+ /* No attributes existed previously. */
+ attrlist = getlist ();
+ }
+
+ node = findnode (attrlist, filename);
+ if (node == NULL)
+ {
+ if (attrval == NULL)
+ /* Attempt to remove an attribute which wasn't there. */
+ return;
+
+ /* First attribute for this file. */
+ node = getnode ();
+ node->type = FILEATTR;
+ node->delproc = fileattr_delproc;
+ node->key = xstrdup (filename);
+ node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1);
+ strcpy (node->data, attrname);
+ strcat (node->data, "=");
+ strcat (node->data, attrval);
+ addnode (attrlist, node);
+ }
+
+ p = fileattr_modify (node->data, attrname, attrval, '=', ';');
+ if (p == NULL)
+ delnode (node);
+ else
+ {
+ free (node->data);
+ node->data = p;
+ }
+
+ attrs_modified = 1;
+}
+
+char *
+fileattr_getall (filename)
+ const char *filename;
+{
+ Node *node;
+ char *p;
+
+ if (attrlist == NULL)
+ fileattr_read ();
+ if (attrlist == NULL)
+ /* Either nothing has any attributes, or fileattr_read already printed
+ an error message. */
+ return NULL;
+
+ if (filename == NULL)
+ p = fileattr_default_attrs;
+ else
+ {
+ node = findnode (attrlist, filename);
+ if (node == NULL)
+ /* A file not mentioned has no attributes. */
+ return NULL;
+ p = node->data;
+ }
+ return xstrdup (p);
+}
+
+void
+fileattr_setall (filename, attrs)
+ const char *filename;
+ const char *attrs;
+{
+ Node *node;
+
+ if (filename == NULL)
+ {
+ if (fileattr_default_attrs != NULL)
+ free (fileattr_default_attrs);
+ fileattr_default_attrs = xstrdup (attrs);
+ attrs_modified = 1;
+ return;
+ }
+ if (attrlist == NULL)
+ fileattr_read ();
+ if (attrlist == NULL)
+ {
+ /* Not sure this is a graceful way to handle things
+ in the case where fileattr_read was unable to read the file. */
+ /* No attributes existed previously. */
+ attrlist = getlist ();
+ }
+
+ node = findnode (attrlist, filename);
+ if (node == NULL)
+ {
+ /* The file had no attributes. Add them if we have any to add. */
+ if (attrs != NULL)
+ {
+ node = getnode ();
+ node->type = FILEATTR;
+ node->delproc = fileattr_delproc;
+ node->key = xstrdup (filename);
+ node->data = xstrdup (attrs);
+ addnode (attrlist, node);
+ }
+ }
+ else
+ {
+ if (attrs == NULL)
+ delnode (node);
+ else
+ {
+ free (node->data);
+ node->data = xstrdup (attrs);
+ }
+ }
+
+ attrs_modified = 1;
+}
+
+void
+fileattr_newfile (filename)
+ const char *filename;
+{
+ Node *node;
+
+ if (attrlist == NULL)
+ fileattr_read ();
+
+ if (fileattr_default_attrs == NULL)
+ return;
+
+ if (attrlist == NULL)
+ {
+ /* Not sure this is a graceful way to handle things
+ in the case where fileattr_read was unable to read the file. */
+ /* No attributes existed previously. */
+ attrlist = getlist ();
+ }
+
+ node = getnode ();
+ node->type = FILEATTR;
+ node->delproc = fileattr_delproc;
+ node->key = xstrdup (filename);
+ node->data = xstrdup (fileattr_default_attrs);
+ addnode (attrlist, node);
+ attrs_modified = 1;
+}
+
+static int
+writeattr_proc (node, data)
+ Node *node;
+ void *data;
+{
+ FILE *fp = (FILE *)data;
+ fputs ("F", fp);
+ fputs (node->key, fp);
+ fputs ("\t", fp);
+ fputs (node->data, fp);
+ fputs ("\012", fp);
+ return 0;
+}
+
+void
+fileattr_write ()
+{
+ FILE *fp;
+ char *fname;
+ mode_t omask;
+
+ if (!attrs_modified)
+ return;
+
+ if (noexec)
+ return;
+
+ /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
+ attributes. */
+ assert (fileattr_stored_repos != NULL);
+
+ fname = xmalloc (strlen (fileattr_stored_repos)
+ + 1
+ + sizeof (CVSREP_FILEATTR)
+ + 1);
+
+ strcpy (fname, fileattr_stored_repos);
+ strcat (fname, "/");
+ strcat (fname, CVSREP_FILEATTR);
+
+ if (list_isempty (attrlist)
+ && fileattr_default_attrs == NULL
+ && unrecog_head == NULL)
+ {
+ /* There are no attributes. */
+ if (unlink_file (fname) < 0)
+ {
+ if (!existence_error (errno))
+ {
+ error (0, errno, "cannot remove %s", fname);
+ }
+ }
+
+ /* Now remove CVSREP directory, if empty. The main reason we bother
+ is that CVS 1.6 and earlier will choke if a CVSREP directory
+ exists, so provide the user a graceful way to remove it. */
+ strcpy (fname, fileattr_stored_repos);
+ strcat (fname, "/");
+ strcat (fname, CVSREP);
+ if (CVS_RMDIR (fname) < 0)
+ {
+ if (errno != ENOTEMPTY
+
+ /* Don't know why we would be here if there is no CVSREP
+ directory, but it seemed to be happening anyway, so
+ check for it. */
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", fname);
+ }
+
+ free (fname);
+ return;
+ }
+
+ omask = umask (cvsumask);
+ fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
+ if (fp == NULL)
+ {
+ if (existence_error (errno))
+ {
+ /* Maybe the CVSREP directory doesn't exist. Try creating it. */
+ char *repname;
+
+ repname = xmalloc (strlen (fileattr_stored_repos)
+ + 1
+ + sizeof (CVSREP)
+ + 1);
+ strcpy (repname, fileattr_stored_repos);
+ strcat (repname, "/");
+ strcat (repname, CVSREP);
+
+ if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
+ {
+ error (0, errno, "cannot make directory %s", repname);
+ (void) umask (omask);
+ free (repname);
+ return;
+ }
+ free (repname);
+
+ fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE);
+ }
+ if (fp == NULL)
+ {
+ error (0, errno, "cannot write %s", fname);
+ (void) umask (omask);
+ return;
+ }
+ }
+ (void) umask (omask);
+
+ /* First write the "F" attributes. */
+ walklist (attrlist, writeattr_proc, fp);
+
+ /* Then the "D" attribute. */
+ if (fileattr_default_attrs != NULL)
+ {
+ fputs ("D\t", fp);
+ fputs (fileattr_default_attrs, fp);
+ fputs ("\012", fp);
+ }
+
+ /* Then any other attributes. */
+ while (unrecog_head != NULL)
+ {
+ struct unrecog *p;
+
+ p = unrecog_head;
+ fputs (p->line, fp);
+ fputs ("\012", fp);
+
+ unrecog_head = p->next;
+ free (p->line);
+ free (p);
+ }
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ attrs_modified = 0;
+ free (fname);
+}
+
+void
+fileattr_free ()
+{
+ /* Note that attrs_modified will ordinarily be zero, but there are
+ a few cases in which fileattr_write will fail to zero it (if
+ noexec is set, or error conditions). This probably is the way
+ it should be. */
+ dellist (&attrlist);
+ if (fileattr_stored_repos != NULL)
+ free (fileattr_stored_repos);
+ fileattr_stored_repos = NULL;
+ if (fileattr_default_attrs != NULL)
+ free (fileattr_default_attrs);
+ fileattr_default_attrs = NULL;
+}
diff --git a/gnu/dist/cvs/src/filesubr.c b/gnu/dist/cvs/src/filesubr.c
new file mode 100644
index 00000000000..f3da62a5efd
--- /dev/null
+++ b/gnu/dist/cvs/src/filesubr.c
@@ -0,0 +1,973 @@
+/* filesubr.c --- subroutines for dealing with files
+ Jim Blandy <jimb@cyclic.com>
+
+ This file is part of GNU CVS.
+
+ GNU CVS is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+/* These functions were moved out of subr.c because they need different
+ definitions under operating systems (like, say, Windows NT) with different
+ file system semantics. */
+
+#include "cvs.h"
+
+static int deep_remove_dir PROTO((const char *path));
+
+/*
+ * Copies "from" to "to".
+ */
+void
+copy_file (from, to)
+ const char *from;
+ const char *to;
+{
+ struct stat sb;
+ struct utimbuf t;
+ int fdin, fdout;
+
+ if (trace)
+ (void) fprintf (stderr, "%s-> copy(%s,%s)\n",
+ CLIENT_SERVER_STR, from, to);
+ if (noexec)
+ return;
+
+ /* If the file to be copied is a link or a device, then just create
+ the new link or device appropriately. */
+ if (islink (from))
+ {
+ char *source = xreadlink (from);
+ symlink (source, to);
+ free (source);
+ return;
+ }
+
+ if (isdevice (from))
+ {
+ if (stat (from, &sb) < 0)
+ error (1, errno, "cannot stat %s", from);
+ mknod (to, sb.st_mode, sb.st_rdev);
+ }
+ else
+ {
+ /* Not a link or a device... probably a regular file. */
+ if ((fdin = open (from, O_RDONLY)) < 0)
+ error (1, errno, "cannot open %s for copying", from);
+ if (fstat (fdin, &sb) < 0)
+ error (1, errno, "cannot fstat %s", from);
+ if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0)
+ error (1, errno, "cannot create %s for copying", to);
+ if (sb.st_size > 0)
+ {
+ char buf[BUFSIZ];
+ int n;
+
+ for (;;)
+ {
+ n = read (fdin, buf, sizeof(buf));
+ if (n == -1)
+ {
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ error (1, errno, "cannot read file %s for copying", from);
+ }
+ else if (n == 0)
+ break;
+
+ if (write(fdout, buf, n) != n) {
+ error (1, errno, "cannot write file %s for copying", to);
+ }
+ }
+
+#ifdef HAVE_FSYNC
+ if (fsync (fdout))
+ error (1, errno, "cannot fsync file %s after copying", to);
+#endif
+ }
+
+ if (close (fdin) < 0)
+ error (0, errno, "cannot close %s", from);
+ if (close (fdout) < 0)
+ error (1, errno, "cannot close %s", to);
+ }
+
+ /* now, set the times for the copied file to match those of the original */
+ memset ((char *) &t, 0, sizeof (t));
+ t.actime = sb.st_atime;
+ t.modtime = sb.st_mtime;
+ (void) utime (to, &t);
+}
+
+/* FIXME-krp: these functions would benefit from caching the char * &
+ stat buf. */
+
+/*
+ * Returns non-zero if the argument file is a directory, or is a symbolic
+ * link which points to a directory.
+ */
+int
+isdir (file)
+ const char *file;
+{
+ struct stat sb;
+
+ if (stat (file, &sb) < 0)
+ return (0);
+ return (S_ISDIR (sb.st_mode));
+}
+
+/*
+ * Returns non-zero if the argument file is a symbolic link.
+ */
+int
+islink (file)
+ const char *file;
+{
+#ifdef S_ISLNK
+ struct stat sb;
+
+ if (CVS_LSTAT (file, &sb) < 0)
+ return (0);
+ return (S_ISLNK (sb.st_mode));
+#else
+ return (0);
+#endif
+}
+
+/*
+ * Returns non-zero if the argument file is a block or
+ * character special device.
+ */
+int
+isdevice (file)
+ const char *file;
+{
+ struct stat sb;
+
+ if (CVS_LSTAT (file, &sb) < 0)
+ return (0);
+#ifdef S_ISBLK
+ if (S_ISBLK (sb.st_mode))
+ return 1;
+#endif
+#ifdef S_ISCHR
+ if (S_ISCHR (sb.st_mode))
+ return 1;
+#endif
+ return 0;
+}
+
+/*
+ * Returns non-zero if the argument file exists.
+ */
+int
+isfile (file)
+ const char *file;
+{
+ return isaccessible(file, F_OK);
+}
+
+/*
+ * Returns non-zero if the argument file is readable.
+ */
+int
+isreadable (file)
+ const char *file;
+{
+ return isaccessible(file, R_OK);
+}
+
+/*
+ * Returns non-zero if the argument file is writable.
+ */
+int
+iswritable (file)
+ const char *file;
+{
+ return isaccessible(file, W_OK);
+}
+
+/*
+ * Returns non-zero if the argument file is accessable according to
+ * mode. If compiled with SETXID_SUPPORT also works if cvs has setxid
+ * bits set.
+ */
+int
+isaccessible (file, mode)
+ const char *file;
+ const int mode;
+{
+#ifdef SETXID_SUPPORT
+ struct stat sb;
+ int umask = 0;
+ int gmask = 0;
+ int omask = 0;
+ int uid;
+
+ if (stat(file, &sb) == -1)
+ return 0;
+ if (mode == F_OK)
+ return 1;
+
+ uid = geteuid();
+ if (uid == 0) /* superuser */
+ {
+ if (mode & X_OK)
+ return sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH);
+ else
+ return 1;
+ }
+
+ if (mode & R_OK)
+ {
+ umask |= S_IRUSR;
+ gmask |= S_IRGRP;
+ omask |= S_IROTH;
+ }
+ if (mode & W_OK)
+ {
+ umask |= S_IWUSR;
+ gmask |= S_IWGRP;
+ omask |= S_IWOTH;
+ }
+ if (mode & X_OK)
+ {
+ umask |= S_IXUSR;
+ gmask |= S_IXGRP;
+ omask |= S_IXOTH;
+ }
+
+ if (sb.st_uid == uid)
+ return (sb.st_mode & umask) == umask;
+ else if (sb.st_gid == getegid())
+ return (sb.st_mode & gmask) == gmask;
+ else
+ return (sb.st_mode & omask) == omask;
+#else
+ return access(file, mode) == 0;
+#endif
+}
+
+/*
+ * Open a file and die if it fails
+ */
+FILE *
+open_file (name, mode)
+ const char *name;
+ const char *mode;
+{
+ FILE *fp;
+
+ if ((fp = fopen (name, mode)) == NULL)
+ error (1, errno, "cannot open %s", name);
+ return (fp);
+}
+
+/*
+ * Make a directory and die if it fails
+ */
+void
+make_directory (name)
+ const char *name;
+{
+ struct stat sb;
+
+ if (stat (name, &sb) == 0 && (!S_ISDIR (sb.st_mode)))
+ error (0, 0, "%s already exists but is not a directory", name);
+ if (!noexec && mkdir (name, 0777) < 0)
+ error (1, errno, "cannot make directory %s", name);
+}
+
+/*
+ * Make a path to the argument directory, printing a message if something
+ * goes wrong.
+ */
+void
+make_directories (name)
+ const char *name;
+{
+ char *cp;
+
+ if (noexec)
+ return;
+
+ if (mkdir (name, 0777) == 0 || errno == EEXIST)
+ return;
+ if (! existence_error (errno))
+ {
+ error (0, errno, "cannot make path to %s", name);
+ return;
+ }
+ if ((cp = strrchr (name, '/')) == NULL)
+ return;
+ *cp = '\0';
+ make_directories (name);
+ *cp++ = '/';
+ if (*cp == '\0')
+ return;
+ (void) mkdir (name, 0777);
+}
+
+/* Create directory NAME if it does not already exist; fatal error for
+ other errors. Returns 0 if directory was created; 1 if it already
+ existed. */
+int
+mkdir_if_needed (name)
+ char *name;
+{
+ if (mkdir (name, 0777) < 0)
+ {
+ if (!(errno == EEXIST
+ || (errno == EACCES && isdir (name))))
+ error (1, errno, "cannot make directory %s", name);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Change the mode of a file, either adding write permissions, or removing
+ * all write permissions. Either change honors the current umask setting.
+ *
+ * Don't do anything if PreservePermissions is set to `yes'. This may
+ * have unexpected consequences for some uses of xchmod.
+ */
+void
+xchmod (fname, writable)
+ char *fname;
+ int writable;
+{
+ struct stat sb;
+ mode_t mode, oumask;
+
+ if (preserve_perms)
+ return;
+
+ if (stat (fname, &sb) < 0)
+ {
+ if (!noexec)
+ error (0, errno, "cannot stat %s", fname);
+ return;
+ }
+ oumask = umask (0);
+ (void) umask (oumask);
+ if (writable)
+ {
+ mode = sb.st_mode | (~oumask
+ & (((sb.st_mode & S_IRUSR) ? S_IWUSR : 0)
+ | ((sb.st_mode & S_IRGRP) ? S_IWGRP : 0)
+ | ((sb.st_mode & S_IROTH) ? S_IWOTH : 0)));
+ }
+ else
+ {
+ mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask;
+ }
+
+ if (trace)
+ (void) fprintf (stderr, "%s-> chmod(%s,%o)\n",
+ CLIENT_SERVER_STR, fname,
+ (unsigned int) mode);
+ if (noexec)
+ return;
+
+ if (chmod (fname, mode) < 0)
+ error (0, errno, "cannot change mode of file %s", fname);
+}
+
+/*
+ * Rename a file and die if it fails
+ */
+void
+rename_file (from, to)
+ const char *from;
+ const char *to;
+{
+ if (trace)
+ (void) fprintf (stderr, "%s-> rename(%s,%s)\n",
+ CLIENT_SERVER_STR, from, to);
+ if (noexec)
+ return;
+
+ if (rename (from, to) < 0)
+ error (1, errno, "cannot rename file %s to %s", from, to);
+}
+
+/*
+ * unlink a file, if possible.
+ */
+int
+unlink_file (f)
+ const char *f;
+{
+ if (trace)
+ (void) fprintf (stderr, "%s-> unlink(%s)\n",
+ CLIENT_SERVER_STR, f);
+ if (noexec)
+ return (0);
+
+ return (unlink (f));
+}
+
+/*
+ * Unlink a file or dir, if possible. If it is a directory do a deep
+ * removal of all of the files in the directory. Return -1 on error
+ * (in which case errno is set).
+ */
+int
+unlink_file_dir (f)
+ const char *f;
+{
+ struct stat sb;
+
+ if (trace
+#ifdef SERVER_SUPPORT
+ /* This is called by the server parent process in contexts where
+ it is not OK to send output (e.g. after we sent "ok" to the
+ client). */
+ && !server_active
+#endif
+ )
+ (void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
+
+ if (noexec)
+ return (0);
+
+ /* For at least some unices, if root tries to unlink() a directory,
+ instead of doing something rational like returning EISDIR,
+ the system will gleefully go ahead and corrupt the filesystem.
+ So we first call stat() to see if it is OK to call unlink(). This
+ doesn't quite work--if someone creates a directory between the
+ call to stat() and the call to unlink(), we'll still corrupt
+ the filesystem. Where is the Unix Haters Handbook when you need
+ it? */
+ if (stat (f, &sb) < 0)
+ {
+ if (existence_error (errno))
+ {
+ /* The file or directory doesn't exist anyhow. */
+ return -1;
+ }
+ }
+ else if (S_ISDIR (sb.st_mode))
+ return deep_remove_dir (f);
+
+ return unlink (f);
+}
+
+/* Remove a directory and everything it contains. Returns 0 for
+ * success, -1 for failure (in which case errno is set).
+ */
+
+static int
+deep_remove_dir (path)
+ const char *path;
+{
+ DIR *dirp;
+ struct dirent *dp;
+
+ if (rmdir (path) != 0)
+ {
+ if (errno == ENOTEMPTY
+ || errno == EEXIST
+ /* Ugly workaround for ugly AIX 4.1 (and 3.2) header bug
+ (it defines ENOTEMPTY and EEXIST to 17 but actually
+ returns 87). */
+ || (ENOTEMPTY == 17 && EEXIST == 17 && errno == 87))
+ {
+ if ((dirp = opendir (path)) == NULL)
+ /* If unable to open the directory return
+ * an error
+ */
+ return -1;
+
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ char *buf;
+
+ if (strcmp (dp->d_name, ".") == 0 ||
+ strcmp (dp->d_name, "..") == 0)
+ continue;
+
+ buf = xmalloc (strlen (path) + strlen (dp->d_name) + 5);
+ sprintf (buf, "%s/%s", path, dp->d_name);
+
+ /* See comment in unlink_file_dir explanation of why we use
+ isdir instead of just calling unlink and checking the
+ status. */
+ if (isdir(buf))
+ {
+ if (deep_remove_dir(buf))
+ {
+ closedir(dirp);
+ free (buf);
+ return -1;
+ }
+ }
+ else
+ {
+ if (unlink (buf) != 0)
+ {
+ closedir(dirp);
+ free (buf);
+ return -1;
+ }
+ }
+ free (buf);
+
+ errno = 0;
+ }
+ if (errno != 0)
+ {
+ int save_errno = errno;
+ closedir (dirp);
+ errno = save_errno;
+ return -1;
+ }
+ closedir (dirp);
+ return rmdir (path);
+ }
+ else
+ return -1;
+ }
+
+ /* Was able to remove the directory return 0 */
+ return 0;
+}
+
+/* Read NCHARS bytes from descriptor FD into BUF.
+ Return the number of characters successfully read.
+ The number returned is always NCHARS unless end-of-file or error. */
+static size_t
+block_read (fd, buf, nchars)
+ int fd;
+ char *buf;
+ size_t nchars;
+{
+ char *bp = buf;
+ size_t nread;
+
+ do
+ {
+ nread = read (fd, bp, nchars);
+ if (nread == (size_t)-1)
+ {
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ return (size_t)-1;
+ }
+
+ if (nread == 0)
+ break;
+
+ bp += nread;
+ nchars -= nread;
+ } while (nchars != 0);
+
+ return bp - buf;
+}
+
+
+/*
+ * Compare "file1" to "file2". Return non-zero if they don't compare exactly.
+ * If FILE1 and FILE2 are special files, compare their salient characteristics
+ * (i.e. major/minor device numbers, links, etc.
+ */
+int
+xcmp (file1, file2)
+ const char *file1;
+ const char *file2;
+{
+ char *buf1, *buf2;
+ struct stat sb1, sb2;
+ int fd1, fd2;
+ int ret;
+
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot lstat %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot lstat %s", file2);
+
+ /* If FILE1 and FILE2 are not the same file type, they are unequal. */
+ if ((sb1.st_mode & S_IFMT) != (sb2.st_mode & S_IFMT))
+ return 1;
+
+ /* If FILE1 and FILE2 are symlinks, they are equal if they point to
+ the same thing. */
+ if (S_ISLNK (sb1.st_mode) && S_ISLNK (sb2.st_mode))
+ {
+ int result;
+ buf1 = xreadlink (file1);
+ buf2 = xreadlink (file2);
+ result = (strcmp (buf1, buf2) == 0);
+ free (buf1);
+ free (buf2);
+ return result;
+ }
+
+ /* If FILE1 and FILE2 are devices, they are equal if their device
+ numbers match. */
+ if (S_ISBLK (sb1.st_mode) || S_ISCHR (sb1.st_mode))
+ {
+ if (sb1.st_rdev == sb2.st_rdev)
+ return 0;
+ else
+ return 1;
+ }
+
+ if ((fd1 = open (file1, O_RDONLY)) < 0)
+ error (1, errno, "cannot open file %s for comparing", file1);
+ if ((fd2 = open (file2, O_RDONLY)) < 0)
+ error (1, errno, "cannot open file %s for comparing", file2);
+
+ /* A generic file compare routine might compare st_dev & st_ino here
+ to see if the two files being compared are actually the same file.
+ But that won't happen in CVS, so we won't bother. */
+
+ if (sb1.st_size != sb2.st_size)
+ ret = 1;
+ else if (sb1.st_size == 0)
+ ret = 0;
+ else
+ {
+ /* FIXME: compute the optimal buffer size by computing the least
+ common multiple of the files st_blocks field */
+ size_t buf_size = 8 * 1024;
+ size_t read1;
+ size_t read2;
+
+ buf1 = xmalloc (buf_size);
+ buf2 = xmalloc (buf_size);
+
+ do
+ {
+ read1 = block_read (fd1, buf1, buf_size);
+ if (read1 == (size_t)-1)
+ error (1, errno, "cannot read file %s for comparing", file1);
+
+ read2 = block_read (fd2, buf2, buf_size);
+ if (read2 == (size_t)-1)
+ error (1, errno, "cannot read file %s for comparing", file2);
+
+ /* assert (read1 == read2); */
+
+ ret = memcmp(buf1, buf2, read1);
+ } while (ret == 0 && read1 == buf_size);
+
+ free (buf1);
+ free (buf2);
+ }
+
+ (void) close (fd1);
+ (void) close (fd2);
+ return (ret);
+}
+
+/* Generate a unique temporary filename. Returns a pointer to a newly
+ malloc'd string containing the name. Returns successfully or not at
+ all. */
+/* There are at least three functions for generating temporary
+ filenames. We use tempnam (SVID 3) if possible, else mktemp (BSD
+ 4.3), and as last resort tmpnam (POSIX). Reason is that tempnam and
+ mktemp both allow to specify the directory in which the temporary
+ file will be created. */
+#ifdef HAVE_TEMPNAM
+char *
+cvs_temp_name ()
+{
+ char *retval;
+
+ retval = tempnam (Tmpdir, "cvs");
+ if (retval == NULL)
+ error (1, errno, "cannot generate temporary filename");
+ /* tempnam returns a pointer to a newly malloc'd string, so there's
+ no need for a xstrdup */
+ return retval;
+}
+#else
+char *
+cvs_temp_name ()
+{
+# ifdef HAVE_MKTEMP
+ char *value;
+ char *retval;
+
+ value = xmalloc (strlen (Tmpdir) + 40);
+ sprintf (value, "%s/%s", Tmpdir, "cvsXXXXXX" );
+ retval = mktemp (value);
+
+ if (retval == NULL)
+ error (1, errno, "cannot generate temporary filename");
+ return value;
+# else
+ char value[L_tmpnam + 1];
+ char *retval;
+
+ retval = tmpnam (value);
+ if (retval == NULL)
+ error (1, errno, "cannot generate temporary filename");
+ return xstrdup (value);
+# endif
+}
+#endif
+
+/* Return non-zero iff FILENAME is absolute.
+ Trivial under Unix, but more complicated under other systems. */
+int
+isabsolute (filename)
+ const char *filename;
+{
+ return filename[0] == '/';
+}
+
+/*
+ * Return a string (dynamically allocated) with the name of the file to which
+ * LINK is symlinked.
+ */
+char *
+xreadlink (link)
+ const char *link;
+{
+ char *file = NULL;
+ char *tfile;
+ int buflen = 128;
+ int link_name_len;
+
+ if (!islink (link))
+ return NULL;
+
+ /* Get the name of the file to which `from' is linked.
+ FIXME: what portability issues arise here? Are readlink &
+ ENAMETOOLONG defined on all systems? -twp */
+ do
+ {
+ file = xrealloc (file, buflen);
+ link_name_len = readlink (link, file, buflen - 1);
+ buflen *= 2;
+ }
+ while (link_name_len < 0 && errno == ENAMETOOLONG);
+
+ if (link_name_len < 0)
+ error (1, errno, "cannot readlink %s", link);
+
+ file[link_name_len] = '\0';
+
+ tfile = xstrdup (file);
+ free (file);
+
+ return tfile;
+}
+
+
+/* Return a pointer into PATH's last component. */
+char *
+last_component (path)
+ char *path;
+{
+ char *last = strrchr (path, '/');
+
+ if (last && (last != path))
+ return last + 1;
+ else
+ return path;
+}
+
+/* Return the home directory. Returns a pointer to storage
+ managed by this function or its callees (currently getenv).
+ This function will return the same thing every time it is
+ called. Returns NULL if there is no home directory.
+
+ Note that for a pserver server, this may return root's home
+ directory. What typically happens is that upon being started from
+ inetd, before switching users, the code in cvsrc.c calls
+ get_homedir which remembers root's home directory in the static
+ variable. Then the switch happens and get_homedir might return a
+ directory that we don't even have read or execute permissions for
+ (which is bad, when various parts of CVS try to read there). One
+ fix would be to make the value returned by get_homedir only good
+ until the next call (which would free the old value). Another fix
+ would be to just always malloc our answer, and let the caller free
+ it (that is best, because some day we may need to be reentrant).
+
+ The workaround is to put -f in inetd.conf which means that
+ get_homedir won't get called until after the switch in user ID.
+
+ The whole concept of a "home directory" on the server is pretty
+ iffy, although I suppose some people probably are relying on it for
+ .cvsrc and such, in the cases where it works. */
+char *
+get_homedir ()
+{
+ static char *home = NULL;
+ char *env = getenv ("HOME");
+ struct passwd *pw;
+
+ if (home != NULL)
+ return home;
+
+ if (env)
+ home = env;
+ else if ((pw = (struct passwd *) getpwuid (getuid ()))
+ && pw->pw_dir)
+ home = xstrdup (pw->pw_dir);
+ else
+ return 0;
+
+ return home;
+}
+
+/* See cvs.h for description. On unix this does nothing, because the
+ shell expands the wildcards. */
+void
+expand_wild (argc, argv, pargc, pargv)
+ int argc;
+ char **argv;
+ int *pargc;
+ char ***pargv;
+{
+ int i;
+ *pargc = argc;
+ *pargv = (char **) xmalloc (argc * sizeof (char *));
+ for (i = 0; i < argc; ++i)
+ (*pargv)[i] = xstrdup (argv[i]);
+}
+
+#ifdef SERVER_SUPPORT
+/* Case-insensitive string compare. I know that some systems
+ have such a routine, but I'm not sure I see any reasons for
+ dealing with the hair of figuring out whether they do (I haven't
+ looked into whether this is a performance bottleneck; I would guess
+ not). */
+int
+cvs_casecmp (str1, str2)
+ char *str1;
+ char *str2;
+{
+ char *p;
+ char *q;
+ int pqdiff;
+
+ p = str1;
+ q = str2;
+ while ((pqdiff = tolower (*p) - tolower (*q)) == 0)
+ {
+ if (*p == '\0')
+ return 0;
+ ++p;
+ ++q;
+ }
+ return pqdiff;
+}
+
+/* Case-insensitive file open. As you can see, this is an expensive
+ call. We don't regard it as our main strategy for dealing with
+ case-insensitivity. Returns errno code or 0 for success. Puts the
+ new file in *FP. NAME and MODE are as for fopen. If PATHP is not
+ NULL, then put a malloc'd string containing the pathname as found
+ into *PATHP. *PATHP is only set if the return value is 0.
+
+ Might be cleaner to separate the file finding (which just gives
+ *PATHP) from the file opening (which the caller can do). For one
+ thing, might make it easier to know whether to put NAME or *PATHP
+ into error messages. */
+int
+fopen_case (name, mode, fp, pathp)
+ char *name;
+ char *mode;
+ FILE **fp;
+ char **pathp;
+{
+ struct dirent *dp;
+ DIR *dirp;
+ char *dir;
+ char *fname;
+ char *found_name;
+ int retval;
+
+ /* Separate NAME into directory DIR and filename within the directory
+ FNAME. */
+ dir = xstrdup (name);
+ fname = strrchr (dir, '/');
+ if (fname == NULL)
+ error (1, 0, "internal error: relative pathname in fopen_case");
+ *fname++ = '\0';
+
+ found_name = NULL;
+ dirp = CVS_OPENDIR (dir);
+ if (dirp == NULL)
+ {
+ if (existence_error (errno))
+ {
+ /* This can happen if we are looking in the Attic and the Attic
+ directory does not exist. Return the error to the caller;
+ they know what to do with it. */
+ retval = errno;
+ goto out;
+ }
+ else
+ {
+ /* Give a fatal error; that way the error message can be
+ more specific than if we returned the error to the caller. */
+ error (1, errno, "cannot read directory %s", dir);
+ }
+ }
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (cvs_casecmp (dp->d_name, fname) == 0)
+ {
+ if (found_name != NULL)
+ error (1, 0, "%s is ambiguous; could mean %s or %s",
+ fname, dp->d_name, found_name);
+ found_name = xstrdup (dp->d_name);
+ }
+ }
+ if (errno != 0)
+ error (1, errno, "cannot read directory %s", dir);
+ closedir (dirp);
+
+ if (found_name == NULL)
+ {
+ *fp = NULL;
+ retval = ENOENT;
+ }
+ else
+ {
+ char *p;
+
+ /* Copy the found name back into DIR. We are assuming that
+ found_name is the same length as fname, which is true as
+ long as the above code is just ignoring case and not other
+ aspects of filename syntax. */
+ p = dir + strlen (dir);
+ *p++ = '/';
+ strcpy (p, found_name);
+ *fp = fopen (dir, mode);
+ if (*fp == NULL)
+ retval = errno;
+ else
+ retval = 0;
+ }
+
+ if (pathp == NULL)
+ free (dir);
+ else if (retval != 0)
+ free (dir);
+ else
+ *pathp = dir;
+ free (found_name);
+ out:
+ return retval;
+}
+#endif /* SERVER_SUPPORT */
diff --git a/gnu/dist/cvs/src/find_names.c b/gnu/dist/cvs/src/find_names.c
new file mode 100644
index 00000000000..6fb927bf6a9
--- /dev/null
+++ b/gnu/dist/cvs/src/find_names.c
@@ -0,0 +1,425 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Find Names
+ *
+ * Finds all the pertinent file names, both from the administration and from the
+ * repository
+ *
+ * Find Dirs
+ *
+ * Finds all pertinent sub-directories of the checked out instantiation and the
+ * repository (and optionally the attic)
+ */
+
+#include "cvs.h"
+
+static int find_dirs PROTO((char *dir, List * list, int checkadm,
+ List *entries));
+static int find_rcs PROTO((char *dir, List * list));
+static int add_subdir_proc PROTO((Node *, void *));
+static int register_subdir_proc PROTO((Node *, void *));
+
+static List *filelist;
+
+/*
+ * add the key from entry on entries list to the files list
+ */
+static int add_entries_proc PROTO((Node *, void *));
+static int
+add_entries_proc (node, closure)
+ Node *node;
+ void *closure;
+{
+ Entnode *entnode;
+ Node *fnode;
+
+ entnode = (Entnode *) node->data;
+ if (entnode->type != ENT_FILE)
+ return (0);
+
+ fnode = getnode ();
+ fnode->type = FILES;
+ fnode->key = xstrdup (node->key);
+ if (addnode (filelist, fnode) != 0)
+ freenode (fnode);
+ return (0);
+}
+
+/* Find files in the repository and/or working directory. On error,
+ may either print a nonfatal error and return NULL, or just give
+ a fatal error. On success, return non-NULL (even if it is an empty
+ list). */
+
+List *
+Find_Names (repository, which, aflag, optentries)
+ char *repository;
+ int which;
+ int aflag;
+ List **optentries;
+{
+ List *entries;
+ List *files;
+
+ /* make a list for the files */
+ files = filelist = getlist ();
+
+ /* look at entries (if necessary) */
+ if (which & W_LOCAL)
+ {
+ /* parse the entries file (if it exists) */
+ entries = Entries_Open (aflag, NULL);
+ if (entries != NULL)
+ {
+ /* walk the entries file adding elements to the files list */
+ (void) walklist (entries, add_entries_proc, NULL);
+
+ /* if our caller wanted the entries list, return it; else free it */
+ if (optentries != NULL)
+ *optentries = entries;
+ else
+ Entries_Close (entries);
+ }
+ }
+
+ if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT))
+ {
+ /* search the repository */
+ if (find_rcs (repository, files) != 0)
+ {
+ error (0, errno, "cannot open directory %s", repository);
+ goto error_exit;
+ }
+
+ /* search the attic too */
+ if (which & W_ATTIC)
+ {
+ char *dir;
+ dir = xmalloc (strlen (repository) + sizeof (CVSATTIC) + 10);
+ (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
+ if (find_rcs (dir, files) != 0
+ && !existence_error (errno))
+ /* For now keep this a fatal error, seems less useful
+ for access control than the case above. */
+ error (1, errno, "cannot open directory %s", dir);
+ free (dir);
+ }
+ }
+
+ /* sort the list into alphabetical order and return it */
+ sortlist (files, fsortcmp);
+ return (files);
+ error_exit:
+ dellist (&files);
+ return NULL;
+}
+
+/*
+ * Add an entry from the subdirs list to the directories list. This
+ * is called via walklist.
+ */
+
+static int
+add_subdir_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ List *dirlist = (List *) closure;
+ Entnode *entnode;
+ Node *dnode;
+
+ entnode = (Entnode *) p->data;
+ if (entnode->type != ENT_SUBDIR)
+ return 0;
+
+ dnode = getnode ();
+ dnode->type = DIRS;
+ dnode->key = xstrdup (entnode->user);
+ if (addnode (dirlist, dnode) != 0)
+ freenode (dnode);
+ return 0;
+}
+
+/*
+ * Register a subdirectory. This is called via walklist.
+ */
+
+/*ARGSUSED*/
+static int
+register_subdir_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ List *entries = (List *) closure;
+
+ Subdir_Register (entries, (char *) NULL, p->key);
+ return 0;
+}
+
+/*
+ * create a list of directories to traverse from the current directory
+ */
+List *
+Find_Directories (repository, which, entries)
+ char *repository;
+ int which;
+ List *entries;
+{
+ List *dirlist;
+
+ /* make a list for the directories */
+ dirlist = getlist ();
+
+ /* find the local ones */
+ if (which & W_LOCAL)
+ {
+ List *tmpentries;
+ struct stickydirtag *sdtp;
+
+ /* Look through the Entries file. */
+
+ if (entries != NULL)
+ tmpentries = entries;
+ else if (isfile (CVSADM_ENT))
+ tmpentries = Entries_Open (0, NULL);
+ else
+ tmpentries = NULL;
+
+ if (tmpentries != NULL)
+ sdtp = (struct stickydirtag *) tmpentries->list->data;
+
+ /* If we do have an entries list, then if sdtp is NULL, or if
+ sdtp->subdirs is nonzero, all subdirectory information is
+ recorded in the entries list. */
+ if (tmpentries != NULL && (sdtp == NULL || sdtp->subdirs))
+ walklist (tmpentries, add_subdir_proc, (void *) dirlist);
+ else
+ {
+ /* This is an old working directory, in which subdirectory
+ information is not recorded in the Entries file. Find
+ the subdirectories the hard way, and, if possible, add
+ it to the Entries file for next time. */
+
+ /* FIXME-maybe: find_dirs is bogus for this usage because
+ it skips CVSATTIC and CVSLCK directories--those names
+ should be special only in the repository. However, in
+ the interests of not perturbing this code, we probably
+ should leave well enough alone unless we want to write
+ a sanity.sh test case (which would operate by manually
+ hacking on the CVS/Entries file). */
+
+ if (find_dirs (".", dirlist, 1, tmpentries) != 0)
+ error (1, errno, "cannot open current directory");
+ if (tmpentries != NULL)
+ {
+ if (! list_isempty (dirlist))
+ walklist (dirlist, register_subdir_proc,
+ (void *) tmpentries);
+ else
+ Subdirs_Known (tmpentries);
+ }
+ }
+
+ if (entries == NULL && tmpentries != NULL)
+ Entries_Close (tmpentries);
+ }
+
+ /* look for sub-dirs in the repository */
+ if ((which & W_REPOS) && repository)
+ {
+ /* search the repository */
+ if (find_dirs (repository, dirlist, 0, entries) != 0)
+ error (1, errno, "cannot open directory %s", repository);
+
+ /* We don't need to look in the attic because directories
+ never go in the attic. In the future, there hopefully will
+ be a better mechanism for detecting whether a directory in
+ the repository is alive or dead; it may or may not involve
+ moving directories to the attic. */
+ }
+
+ /* sort the list into alphabetical order and return it */
+ sortlist (dirlist, fsortcmp);
+ return (dirlist);
+}
+
+/*
+ * Finds all the ,v files in the argument directory, and adds them to the
+ * files list. Returns 0 for success and non-zero if the argument directory
+ * cannot be opened, in which case errno is set to indicate the error.
+ * In the error case LIST is left in some reasonable state (unchanged, or
+ * containing the files which were found before the error occurred).
+ */
+static int
+find_rcs (dir, list)
+ char *dir;
+ List *list;
+{
+ Node *p;
+ struct dirent *dp;
+ DIR *dirp;
+
+ /* set up to read the dir */
+ if ((dirp = CVS_OPENDIR (dir)) == NULL)
+ return (1);
+
+ /* read the dir, grabbing the ,v files */
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (CVS_FNMATCH (RCSPAT, dp->d_name, 0) == 0)
+ {
+ char *comma;
+
+ comma = strrchr (dp->d_name, ','); /* strip the ,v */
+ *comma = '\0';
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (dp->d_name);
+ if (addnode (list, p) != 0)
+ freenode (p);
+ }
+ errno = 0;
+ }
+ if (errno != 0)
+ {
+ int save_errno = errno;
+ (void) closedir (dirp);
+ errno = save_errno;
+ return 1;
+ }
+ (void) closedir (dirp);
+ return (0);
+}
+
+/*
+ * Finds all the subdirectories of the argument dir and adds them to
+ * the specified list. Sub-directories without a CVS administration
+ * directory are optionally ignored. If ENTRIES is not NULL, all
+ * files on the list are ignored. Returns 0 for success or 1 on
+ * error, in which case errno is set to indicate the error.
+ */
+static int
+find_dirs (dir, list, checkadm, entries)
+ char *dir;
+ List *list;
+ int checkadm;
+ List *entries;
+{
+ Node *p;
+ char *tmp = NULL;
+ size_t tmp_size = 0;
+ struct dirent *dp;
+ DIR *dirp;
+ int skip_emptydir = 0;
+
+ /* First figure out whether we need to skip directories named
+ Emptydir. Except in the CVSNULLREPOS case, Emptydir is just
+ a normal directory name. */
+ if (isabsolute (dir)
+ && strncmp (dir, CVSroot_directory, strlen (CVSroot_directory)) == 0
+ && ISDIRSEP (dir[strlen (CVSroot_directory)])
+ && strcmp (dir + strlen (CVSroot_directory) + 1, CVSROOTADM) == 0)
+ skip_emptydir = 1;
+
+ /* set up to read the dir */
+ if ((dirp = CVS_OPENDIR (dir)) == NULL)
+ return (1);
+
+ /* read the dir, grabbing sub-dirs */
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 ||
+ strcmp (dp->d_name, "..") == 0 ||
+ strcmp (dp->d_name, CVSATTIC) == 0 ||
+ strcmp (dp->d_name, CVSLCK) == 0 ||
+ strcmp (dp->d_name, CVSREP) == 0)
+ goto do_it_again;
+
+ /* findnode() is going to be significantly faster than stat()
+ because it involves no system calls. That is why we bother
+ with the entries argument, and why we check this first. */
+ if (entries != NULL && findnode (entries, dp->d_name) != NULL)
+ goto do_it_again;
+
+ if (skip_emptydir
+ && strcmp (dp->d_name, CVSNULLREPOS) == 0)
+ goto do_it_again;
+
+#ifdef DT_DIR
+ if (dp->d_type != DT_DIR)
+ {
+ if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK)
+ goto do_it_again;
+#endif
+ /* don't bother stating ,v files */
+ if (CVS_FNMATCH (RCSPAT, dp->d_name, 0) == 0)
+ goto do_it_again;
+
+ expand_string (&tmp,
+ &tmp_size,
+ strlen (dir) + strlen (dp->d_name) + 10);
+ sprintf (tmp, "%s/%s", dir, dp->d_name);
+ if (!isdir (tmp))
+ goto do_it_again;
+
+#ifdef DT_DIR
+ }
+#endif
+
+ /* check for administration directories (if needed) */
+ if (checkadm)
+ {
+ /* blow off symbolic links to dirs in local dir */
+#ifdef DT_DIR
+ if (dp->d_type != DT_DIR)
+ {
+ /* we're either unknown or a symlink at this point */
+ if (dp->d_type == DT_LNK)
+ goto do_it_again;
+#endif
+ /* Note that we only get here if we already set tmp
+ above. */
+ if (islink (tmp))
+ goto do_it_again;
+#ifdef DT_DIR
+ }
+#endif
+
+ /* check for new style */
+ expand_string (&tmp,
+ &tmp_size,
+ (strlen (dir) + strlen (dp->d_name)
+ + sizeof (CVSADM) + 10));
+ (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM);
+ if (!isdir (tmp))
+ goto do_it_again;
+ }
+
+ /* put it in the list */
+ p = getnode ();
+ p->type = DIRS;
+ p->key = xstrdup (dp->d_name);
+ if (addnode (list, p) != 0)
+ freenode (p);
+
+ do_it_again:
+ errno = 0;
+ }
+ if (errno != 0)
+ {
+ int save_errno = errno;
+ (void) closedir (dirp);
+ errno = save_errno;
+ return 1;
+ }
+ (void) closedir (dirp);
+ if (tmp != NULL)
+ free (tmp);
+ return (0);
+}
diff --git a/gnu/dist/cvs/src/hardlink.c b/gnu/dist/cvs/src/hardlink.c
new file mode 100644
index 00000000000..309d27f078d
--- /dev/null
+++ b/gnu/dist/cvs/src/hardlink.c
@@ -0,0 +1,305 @@
+/* This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+/* Collect and manage hardlink info associated with a particular file. */
+
+#include "cvs.h"
+#include "hardlink.h"
+
+/* The structure currently used to manage hardlink info is a list.
+ Therefore, most of the functions which manipulate hardlink data
+ are walklist procedures. This is not a very efficient implementation;
+ if someone decides to use a real hash table (for instance), then
+ much of this code can be rewritten to be a little less arcane.
+
+ Each element of `hardlist' represents an inode. It is keyed on the
+ inode number, and points to a list of files. This is to make it
+ easy to find out what files are linked to a given file FOO: find
+ FOO's inode, look it up in hardlist, and retrieve the list of files
+ associated with that inode.
+
+ Each file node, in turn, is represented by a `hardlink_info' struct,
+ which includes `status' and `links' fields. The `status' field should
+ be used by a procedure like commit_fileproc or update_fileproc to
+ record each file's status; that way, after all file links have been
+ recorded, CVS can check the linkage of files which are in doubt
+ (i.e. T_NEEDS_MERGE files).
+
+ TODO: a diagram of an example hardlist would help here. */
+
+/* TODO: change this to something with a marginal degree of
+ efficiency, like maybe a hash table. Yeah. */
+
+List *hardlist; /* Record hardlink information for working files */
+char *working_dir; /* The top-level working directory, used for
+ constructing full pathnames. */
+
+/* Return a pointer to FILEPATH's node in the hardlist. This means
+ looking up its inode, retrieving the list of files linked to that
+ inode, and then looking up FILE in that list. If the file doesn't
+ seem to exist, return NULL. */
+Node *
+lookup_file_by_inode (filepath)
+ const char *filepath;
+{
+ char *inodestr, *file;
+ struct stat sb;
+ Node *hp, *p;
+
+ /* Get file's basename, so that we can stat it. */
+ file = strrchr (filepath, '/');
+ if (file)
+ ++file;
+ else
+ file = (char *) filepath;
+
+ /* inodestr contains the hexadecimal representation of an
+ inode, so it requires two bytes of text to represent
+ each byte of the inode number. */
+ inodestr = (char *) xmalloc (2*sizeof(ino_t) + 1);
+ if (stat (file, &sb) < 0)
+ {
+ if (existence_error (errno))
+ {
+ /* The file doesn't exist; we may be doing an update on a
+ file that's been removed. A nonexistent file has no
+ link information, so return without changing hardlist. */
+ free (inodestr);
+ return NULL;
+ }
+ error (1, errno, "cannot stat %s", file);
+ }
+
+ sprintf (inodestr, "%lx", (unsigned long) sb.st_ino);
+
+ /* Find out if this inode is already in the hardlist, adding
+ a new entry to the list if not. */
+ hp = findnode (hardlist, inodestr);
+ if (hp == NULL)
+ {
+ hp = getnode ();
+ hp->type = NT_UNKNOWN;
+ hp->key = inodestr;
+ hp->data = (char *) getlist();
+ hp->delproc = dellist;
+ (void) addnode (hardlist, hp);
+ }
+ else
+ {
+ free (inodestr);
+ }
+
+ p = findnode ((List *) hp->data, filepath);
+ if (p == NULL)
+ {
+ p = getnode();
+ p->type = NT_UNKNOWN;
+ p->key = xstrdup (filepath);
+ p->data = NULL;
+ (void) addnode ((List *) hp->data, p);
+ }
+
+ return p;
+}
+
+/* After a file has been checked out, add a node for it to the hardlist
+ (if necessary) and mark it as checked out. */
+void
+update_hardlink_info (file)
+ const char *file;
+{
+ char *path;
+ Node *n;
+ struct hardlink_info *hlinfo;
+
+ if (file[0] == '/')
+ {
+ path = xstrdup (file);
+ }
+ else
+ {
+ /* file is a relative pathname; assume it's from the current
+ working directory. */
+ char *dir = xgetwd();
+ path = xmalloc (strlen(dir) + strlen(file) + 2);
+ sprintf (path, "%s/%s", dir, file);
+ free (dir);
+ }
+
+ n = lookup_file_by_inode (path);
+ if (n == NULL)
+ {
+ /* Something is *really* wrong if the file doesn't exist here;
+ update_hardlink_info should be called only when a file has
+ just been checked out to a working directory. */
+ error (1, 0, "lost hardlink info for %s", file);
+ }
+
+ if (n->data == NULL)
+ n->data = (char *) xmalloc (sizeof (struct hardlink_info));
+ hlinfo = (struct hardlink_info *) n->data;
+ hlinfo->status = T_UPTODATE;
+ hlinfo->checked_out = 1;
+}
+
+/* Return a List with all the files known to be linked to FILE in
+ the working directory. Used by special_file_mismatch, to determine
+ whether it is safe to merge two files.
+
+ FIXME: What is the memory allocation for the return value? We seem
+ to sometimes allocate a new list (getlist() call below) and sometimes
+ return an existing list (where we return n->data). */
+List *
+list_linked_files_on_disk (file)
+ char *file;
+{
+ char *inodestr, *path;
+ struct stat sb;
+ Node *n;
+
+ /* If hardlist is NULL, we have not been doing an operation that
+ would permit us to know anything about the file's hardlinks
+ (cvs update, cvs commit, etc). Return an empty list. */
+ if (hardlist == NULL)
+ return getlist();
+
+ /* Get the full pathname of file (assuming the working directory) */
+ if (file[0] == '/')
+ path = xstrdup (file);
+ else
+ {
+ char *dir = xgetwd();
+ path = (char *) xmalloc (strlen(dir) + strlen(file) + 2);
+ sprintf (path, "%s/%s", dir, file);
+ free (dir);
+ }
+
+ /* We do an extra lookup_file here just to make sure that there
+ is a node for `path' in the hardlist. If that were not so,
+ comparing the working directory linkage against the repository
+ linkage for a file would always fail. */
+ (void) lookup_file_by_inode (path);
+
+ if (stat (path, &sb) < 0)
+ error (1, errno, "cannot stat %s", file);
+ /* inodestr contains the hexadecimal representation of an
+ inode, so it requires two bytes of text to represent
+ each byte of the inode number. */
+ inodestr = (char *) xmalloc (2*sizeof(ino_t) + 1);
+ sprintf (inodestr, "%lx", (unsigned long) sb.st_ino);
+
+ /* Make sure the files linked to this inode are sorted. */
+ n = findnode (hardlist, inodestr);
+ sortlist ((List *) n->data, fsortcmp);
+
+ free (inodestr);
+ return (List *) n->data;
+}
+
+/* Compare the files in the `key' fields of two lists, returning 1 if
+ the lists are equivalent and 0 otherwise.
+
+ Only the basenames of each file are compared. This is an awful hack
+ that exists because list_linked_files_on_disk returns full paths
+ and the `hardlinks' structure of a RCSVers node contains only
+ basenames. That in turn is a result of the awful hack that only
+ basenames are stored in the RCS file. If anyone ever solves the
+ problem of correctly managing cross-directory hardlinks, this
+ function (along with most functions in this file) must be fixed. */
+
+int
+compare_linkage_lists (links1, links2)
+ List *links1;
+ List *links2;
+{
+ Node *n1, *n2;
+ char *p1, *p2;
+
+ sortlist (links1, fsortcmp);
+ sortlist (links2, fsortcmp);
+
+ n1 = links1->list->next;
+ n2 = links2->list->next;
+
+ while (n1 != links1->list && n2 != links2->list)
+ {
+ /* Get the basenames of both files. */
+ p1 = strrchr (n1->key, '/');
+ if (p1 == NULL)
+ p1 = n1->key;
+ else
+ ++p1;
+
+ p2 = strrchr (n2->key, '/');
+ if (p2 == NULL)
+ p2 = n2->key;
+ else
+ ++p2;
+
+ /* Compare the files' basenames. */
+ if (strcmp (p1, p2) != 0)
+ return 0;
+
+ n1 = n1->next;
+ n2 = n2->next;
+ }
+
+ /* At this point we should be at the end of both lists; if not,
+ one file has more links than the other, and return 1. */
+ return (n1 == links1->list && n2 == links2->list);
+}
+
+/* Find a checked-out file in a list of filenames. Used by RCS_checkout
+ when checking out a new hardlinked file, to decide whether this file
+ can be linked to any others that already exist. The return value
+ is not currently used. */
+
+int
+find_checkedout_proc (node, data)
+ Node *node;
+ void *data;
+{
+ Node **uptodate = (Node **) data;
+ Node *link;
+ char *dir = xgetwd();
+ char *path;
+ struct hardlink_info *hlinfo;
+
+ /* If we have already found a file, don't do anything. */
+ if (*uptodate != NULL)
+ return 0;
+
+ /* Look at this file in the hardlist and see whether the checked_out
+ field is 1, meaning that it has been checked out during this CVS run. */
+ path = (char *)
+ xmalloc (strlen (dir) + strlen (node->key) + 2);
+ sprintf (path, "%s/%s", dir, node->key);
+ link = lookup_file_by_inode (path);
+ free (path);
+ free (dir);
+
+ if (link == NULL)
+ {
+ /* We haven't seen this file -- maybe it hasn't been checked
+ out yet at all. */
+ return 0;
+ }
+
+ hlinfo = (struct hardlink_info *) link->data;
+ if (hlinfo->checked_out)
+ {
+ /* This file has been checked out recently, so it's safe to
+ link to it. */
+ *uptodate = link;
+ }
+
+ return 0;
+}
+
diff --git a/gnu/dist/cvs/src/hash.c b/gnu/dist/cvs/src/hash.c
new file mode 100644
index 00000000000..3f06c266cde
--- /dev/null
+++ b/gnu/dist/cvs/src/hash.c
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Polk's hash list manager. So cool.
+ */
+
+#include "cvs.h"
+#include <assert.h>
+
+/* Global caches. The idea is that we maintain a linked list of "free"d
+ nodes or lists, and get new items from there. It has been suggested
+ to use an obstack instead, but off the top of my head, I'm not sure
+ that would gain enough to be worth worrying about. */
+static List *listcache = NULL;
+static Node *nodecache = NULL;
+
+static void freenode_mem PROTO((Node * p));
+
+/* hash function */
+static int
+hashp (key)
+ const char *key;
+{
+ unsigned int h = 0;
+ unsigned int g;
+
+ assert(key != NULL);
+
+ while (*key != 0)
+ {
+ unsigned int c = *key++;
+ /* The FOLD_FN_CHAR is so that findnode_fn works. */
+ h = (h << 4) + FOLD_FN_CHAR (c);
+ if ((g = h & 0xf0000000) != 0)
+ h = (h ^ (g >> 24)) ^ g;
+ }
+
+ return (h % HASHSIZE);
+}
+
+/*
+ * create a new list (or get an old one from the cache)
+ */
+List *
+getlist ()
+{
+ int i;
+ List *list;
+ Node *node;
+
+ if (listcache != NULL)
+ {
+ /* get a list from the cache and clear it */
+ list = listcache;
+ listcache = listcache->next;
+ list->next = (List *) NULL;
+ for (i = 0; i < HASHSIZE; i++)
+ list->hasharray[i] = (Node *) NULL;
+ }
+ else
+ {
+ /* make a new list from scratch */
+ list = (List *) xmalloc (sizeof (List));
+ memset ((char *) list, 0, sizeof (List));
+ node = getnode ();
+ list->list = node;
+ node->type = HEADER;
+ node->next = node->prev = node;
+ }
+ return (list);
+}
+
+/*
+ * free up a list
+ */
+void
+dellist (listp)
+ List **listp;
+{
+ int i;
+ Node *p;
+
+ if (*listp == (List *) NULL)
+ return;
+
+ p = (*listp)->list;
+
+ /* free each node in the list (except header) */
+ while (p->next != p)
+ delnode (p->next);
+
+ /* free any list-private data, without freeing the actual header */
+ freenode_mem (p);
+
+ /* free up the header nodes for hash lists (if any) */
+ for (i = 0; i < HASHSIZE; i++)
+ {
+ if ((p = (*listp)->hasharray[i]) != (Node *) NULL)
+ {
+ /* put the nodes into the cache */
+#ifndef NOCACHE
+ p->type = NT_UNKNOWN;
+ p->next = nodecache;
+ nodecache = p;
+#else
+ /* If NOCACHE is defined we turn off the cache. This can make
+ it easier to tools to determine where items were allocated
+ and freed, for tracking down memory leaks and the like. */
+ free (p);
+#endif
+ }
+ }
+
+ /* put it on the cache */
+#ifndef NOCACHE
+ (*listp)->next = listcache;
+ listcache = *listp;
+#else
+ free ((*listp)->list);
+ free (*listp);
+#endif
+ *listp = (List *) NULL;
+}
+
+/*
+ * get a new list node
+ */
+Node *
+getnode ()
+{
+ Node *p;
+
+ if (nodecache != (Node *) NULL)
+ {
+ /* get one from the cache */
+ p = nodecache;
+ nodecache = p->next;
+ }
+ else
+ {
+ /* make a new one */
+ p = (Node *) xmalloc (sizeof (Node));
+ }
+
+ /* always make it clean */
+ memset ((char *) p, 0, sizeof (Node));
+ p->type = NT_UNKNOWN;
+
+ return (p);
+}
+
+/*
+ * remove a node from it's list (maybe hash list too) and free it
+ */
+void
+delnode (p)
+ Node *p;
+{
+ if (p == (Node *) NULL)
+ return;
+
+ /* take it out of the list */
+ p->next->prev = p->prev;
+ p->prev->next = p->next;
+
+ /* if it was hashed, remove it from there too */
+ if (p->hashnext != (Node *) NULL)
+ {
+ p->hashnext->hashprev = p->hashprev;
+ p->hashprev->hashnext = p->hashnext;
+ }
+
+ /* free up the storage */
+ freenode (p);
+}
+
+/*
+ * free up the storage associated with a node
+ */
+static void
+freenode_mem (p)
+ Node *p;
+{
+ if (p->delproc != (void (*) ()) NULL)
+ p->delproc (p); /* call the specified delproc */
+ else
+ {
+ if (p->data != NULL) /* otherwise free() it if necessary */
+ free (p->data);
+ }
+ if (p->key != NULL) /* free the key if necessary */
+ free (p->key);
+
+ /* to be safe, re-initialize these */
+ p->key = p->data = (char *) NULL;
+ p->delproc = (void (*) ()) NULL;
+}
+
+/*
+ * free up the storage associated with a node and recycle it
+ */
+void
+freenode (p)
+ Node *p;
+{
+ /* first free the memory */
+ freenode_mem (p);
+
+ /* then put it in the cache */
+#ifndef NOCACHE
+ p->type = NT_UNKNOWN;
+ p->next = nodecache;
+ nodecache = p;
+#else
+ free (p);
+#endif
+}
+
+/*
+ * Link item P into list LIST before item MARKER. If P->KEY is non-NULL and
+ * that key is already in the hash table, return -1 without modifying any
+ * parameter.
+ *
+ * return 0 on success
+ */
+int
+insert_before (list, marker, p)
+ List *list;
+ Node *marker;
+ Node *p;
+{
+ if (p->key != NULL) /* hash it too? */
+ {
+ int hashval;
+ Node *q;
+
+ hashval = hashp (p->key);
+ if (list->hasharray[hashval] == NULL) /* make a header for list? */
+ {
+ q = getnode ();
+ q->type = HEADER;
+ list->hasharray[hashval] = q->hashnext = q->hashprev = q;
+ }
+
+ /* put it into the hash list if it's not already there */
+ for (q = list->hasharray[hashval]->hashnext;
+ q != list->hasharray[hashval]; q = q->hashnext)
+ {
+ if (strcmp (p->key, q->key) == 0)
+ return (-1);
+ }
+ q = list->hasharray[hashval];
+ p->hashprev = q->hashprev;
+ p->hashnext = q;
+ p->hashprev->hashnext = p;
+ q->hashprev = p;
+ }
+
+ p->next = marker;
+ p->prev = marker->prev;
+ marker->prev->next = p;
+ marker->prev = p;
+
+ return (0);
+}
+
+/*
+ * insert item p at end of list "list" (maybe hash it too) if hashing and it
+ * already exists, return -1 and don't actually put it in the list
+ *
+ * return 0 on success
+ */
+int
+addnode (list, p)
+ List *list;
+ Node *p;
+{
+ return insert_before (list, list->list, p);
+}
+
+/*
+ * Like addnode, but insert p at the front of `list'. This bogosity is
+ * necessary to preserve last-to-first output order for some RCS functions.
+ */
+int
+addnode_at_front (list, p)
+ List *list;
+ Node *p;
+{
+ return insert_before (list, list->list->next, p);
+}
+
+/* Look up an entry in hash list table and return a pointer to the
+ node. Return NULL if not found. Abort with a fatal error for
+ errors. */
+Node *
+findnode (list, key)
+ List *list;
+ const char *key;
+{
+ Node *head, *p;
+
+ /* This probably should be "assert (list != NULL)" (or if not we
+ should document the current behavior), but only if we check all
+ the callers to see if any are relying on this behavior. */
+ if ((list == (List *) NULL))
+ return ((Node *) NULL);
+
+ assert (key != NULL);
+
+ head = list->hasharray[hashp (key)];
+ if (head == (Node *) NULL)
+ /* Not found. */
+ return ((Node *) NULL);
+
+ for (p = head->hashnext; p != head; p = p->hashnext)
+ if (strcmp (p->key, key) == 0)
+ return (p);
+ return ((Node *) NULL);
+}
+
+/*
+ * Like findnode, but for a filename.
+ */
+Node *
+findnode_fn (list, key)
+ List *list;
+ const char *key;
+{
+ Node *head, *p;
+
+ /* This probably should be "assert (list != NULL)" (or if not we
+ should document the current behavior), but only if we check all
+ the callers to see if any are relying on this behavior. */
+ if (list == (List *) NULL)
+ return ((Node *) NULL);
+
+ assert (key != NULL);
+
+ head = list->hasharray[hashp (key)];
+ if (head == (Node *) NULL)
+ return ((Node *) NULL);
+
+ for (p = head->hashnext; p != head; p = p->hashnext)
+ if (fncmp (p->key, key) == 0)
+ return (p);
+ return ((Node *) NULL);
+}
+
+/*
+ * walk a list with a specific proc
+ */
+int
+walklist (list, proc, closure)
+ List *list;
+ int (*proc) PROTO ((Node *, void *));
+ void *closure;
+{
+ Node *head, *p;
+ int err = 0;
+
+ if (list == NULL)
+ return (0);
+
+ head = list->list;
+ for (p = head->next; p != head; p = p->next)
+ err += proc (p, closure);
+ return (err);
+}
+
+int
+list_isempty (list)
+ List *list;
+{
+ return list == NULL || list->list->next == list->list;
+}
+
+static int (*client_comp) PROTO ((const Node *, const Node *));
+static int qsort_comp PROTO ((const void *, const void *));
+
+static int
+qsort_comp (elem1, elem2)
+ const void *elem1;
+ const void *elem2;
+{
+ Node **node1 = (Node **) elem1;
+ Node **node2 = (Node **) elem2;
+ return client_comp (*node1, *node2);
+}
+
+/*
+ * sort the elements of a list (in place)
+ */
+void
+sortlist (list, comp)
+ List *list;
+ int (*comp) PROTO ((const Node *, const Node *));
+{
+ Node *head, *remain, *p, **array;
+ int i, n;
+
+ /* save the old first element of the list */
+ head = list->list;
+ remain = head->next;
+
+ /* count the number of nodes in the list */
+ n = 0;
+ for (p = remain; p != head; p = p->next)
+ n++;
+
+ /* allocate an array of nodes and populate it */
+ array = (Node **) xmalloc (sizeof(Node *) * n);
+ i = 0;
+ for (p = remain; p != head; p = p->next)
+ array[i++] = p;
+
+ /* sort the array of nodes */
+ client_comp = comp;
+ qsort (array, n, sizeof(Node *), qsort_comp);
+
+ /* rebuild the list from beginning to end */
+ head->next = head->prev = head;
+ for (i = 0; i < n; i++)
+ {
+ p = array[i];
+ p->next = head;
+ p->prev = head->prev;
+ p->prev->next = p;
+ head->prev = p;
+ }
+
+ /* release the array of nodes */
+ free (array);
+}
+
+/*
+ * compare two files list node (for sort)
+ */
+int
+fsortcmp (p, q)
+ const Node *p;
+ const Node *q;
+{
+ return (strcmp (p->key, q->key));
+}
+
+/* Debugging functions. Quite useful to call from within gdb. */
+
+static char *nodetypestring PROTO ((Ntype));
+
+static char *
+nodetypestring (type)
+ Ntype type;
+{
+ switch (type) {
+ case NT_UNKNOWN: return("UNKNOWN");
+ case HEADER: return("HEADER");
+ case ENTRIES: return("ENTRIES");
+ case FILES: return("FILES");
+ case LIST: return("LIST");
+ case RCSNODE: return("RCSNODE");
+ case RCSVERS: return("RCSVERS");
+ case DIRS: return("DIRS");
+ case UPDATE: return("UPDATE");
+ case LOCK: return("LOCK");
+ case NDBMNODE: return("NDBMNODE");
+ case FILEATTR: return("FILEATTR");
+ case VARIABLE: return("VARIABLE");
+ case RCSFIELD: return("RCSFIELD");
+ }
+
+ return("<trash>");
+}
+
+static int printnode PROTO ((Node *, void *));
+static int
+printnode (node, closure)
+ Node *node;
+ void *closure;
+{
+ if (node == NULL)
+ {
+ (void) printf("NULL node.\n");
+ return(0);
+ }
+
+ (void) printf("Node at 0x%p: type = %s, key = 0x%p = \"%s\", data = 0x%p, next = 0x%p, prev = 0x%p\n",
+ node, nodetypestring(node->type), node->key, node->key, node->data, node->next, node->prev);
+
+ return(0);
+}
+
+/* This is global, not static, so that its name is unique and to avoid
+ compiler warnings about it not being used. But it is not used by CVS;
+ it exists so one can call it from a debugger. */
+void printlist PROTO ((List *));
+
+void
+printlist (list)
+ List *list;
+{
+ if (list == NULL)
+ {
+ (void) printf("NULL list.\n");
+ return;
+ }
+
+ (void) printf("List at 0x%p: list = 0x%p, HASHSIZE = %d, next = 0x%p\n",
+ list, list->list, HASHSIZE, list->next);
+
+ (void) walklist(list, printnode, NULL);
+
+ return;
+}
diff --git a/gnu/dist/cvs/src/history.c b/gnu/dist/cvs/src/history.c
new file mode 100644
index 00000000000..8cd31d5bdec
--- /dev/null
+++ b/gnu/dist/cvs/src/history.c
@@ -0,0 +1,1587 @@
+/*
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS 1.0 kit.
+ *
+ * **************** History of Users and Module ****************
+ *
+ * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
+ *
+ * On For each Tag, Add, Checkout, Commit, Update or Release command,
+ * one line of text is written to a History log.
+ *
+ * X date | user | CurDir | special | rev(s) | argument '\n'
+ *
+ * where: [The spaces in the example line above are not in the history file.]
+ *
+ * X is a single character showing the type of event:
+ * T "Tag" cmd.
+ * O "Checkout" cmd.
+ * E "Export" cmd.
+ * F "Release" cmd.
+ * W "Update" cmd - No User file, Remove from Entries file.
+ * U "Update" cmd - File was checked out over User file.
+ * G "Update" cmd - File was merged successfully.
+ * C "Update" cmd - File was merged and shows overlaps.
+ * M "Commit" cmd - "Modified" file.
+ * A "Commit" cmd - "Added" file.
+ * R "Commit" cmd - "Removed" file.
+ *
+ * date is a fixed length 8-char hex representation of a Unix time_t.
+ * [Starting here, variable fields are delimited by '|' chars.]
+ *
+ * user is the username of the person who typed the command.
+ *
+ * CurDir The directory where the action occurred. This should be the
+ * absolute path of the directory which is at the same level as
+ * the "Repository" field (for W,U,G,C & M,A,R).
+ *
+ * Repository For record types [W,U,G,C,M,A,R] this field holds the
+ * repository read from the administrative data where the
+ * command was typed.
+ * T "A" --> New Tag, "D" --> Delete Tag
+ * Otherwise it is the Tag or Date to modify.
+ * O,F,E A "" (null field)
+ *
+ * rev(s) Revision number or tag.
+ * T The Tag to apply.
+ * O,E The Tag or Date, if specified, else "" (null field).
+ * F "" (null field)
+ * W The Tag or Date, if specified, else "" (null field).
+ * U The Revision checked out over the User file.
+ * G,C The Revision(s) involved in merge.
+ * M,A,R RCS Revision affected.
+ *
+ * argument The module (for [TOEUF]) or file (for [WUGCMAR]) affected.
+ *
+ *
+ *** Report categories: "User" and "Since" modifiers apply to all reports.
+ * [For "sort" ordering see the "sort_order" routine.]
+ *
+ * Extract list of record types
+ *
+ * -e, -x [TOEFWUGCMAR]
+ *
+ * Extracted records are simply printed, No analysis is performed.
+ * All "field" modifiers apply. -e chooses all types.
+ *
+ * Checked 'O'ut modules
+ *
+ * -o, -w
+ * Checked out modules. 'F' and 'O' records are examined and if
+ * the last record for a repository/file is an 'O', a line is
+ * printed. "-w" forces the "working dir" to be used in the
+ * comparison instead of the repository.
+ *
+ * Committed (Modified) files
+ *
+ * -c, -l, -w
+ * All 'M'odified, 'A'dded and 'R'emoved records are examined.
+ * "Field" modifiers apply. -l forces a sort by file within user
+ * and shows only the last modifier. -w works as in Checkout.
+ *
+ * Warning: Be careful with what you infer from the output of
+ * "cvs hi -c -l". It means the last time *you*
+ * changed the file, not the list of files for which
+ * you were the last changer!!!
+ *
+ * Module history for named modules.
+ * -m module, -l
+ *
+ * This is special. If one or more modules are specified, the
+ * module names are remembered and the files making up the
+ * modules are remembered. Only records matching exactly those
+ * files and repositories are shown. Sorting by "module", then
+ * filename, is implied. If -l ("last modified") is specified,
+ * then "update" records (types WUCG), tag and release records
+ * are ignored and the last (by date) "modified" record.
+ *
+ * TAG history
+ *
+ * -T All Tag records are displayed.
+ *
+ *** Modifiers.
+ *
+ * Since ... [All records contain a timestamp, so any report
+ * category can be limited by date.]
+ *
+ * -D date - The "date" is parsed into a Unix "time_t" and
+ * records with an earlier time stamp are ignored.
+ * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
+ * you use this option, every file is searched for the
+ * indicated rev/tag.
+ * -t tag - The "tag" is searched for in the history file and no
+ * record is displayed before the tag is found. An
+ * error is printed if the tag is never found.
+ * -b string - Records are printed only back to the last reference
+ * to the string in the "module", "file" or
+ * "repository" fields.
+ *
+ * Field Selections [Simple comparisons on existing fields. All field
+ * selections are repeatable.]
+ *
+ * -a - All users.
+ * -u user - If no user is given and '-a' is not given, only
+ * records for the user typing the command are shown.
+ * ==> If -a or -u is not specified, just use "self".
+ *
+ * -f filematch - Only records in which the "file" field contains the
+ * string "filematch" are considered.
+ *
+ * -p repository - Only records in which the "repository" string is a
+ * prefix of the "repos" field are considered.
+ *
+ * -m modulename - Only records which contain "modulename" in the
+ * "module" field are considered.
+ *
+ *
+ * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
+ *
+ *** Checked out files for username. (default self, e.g. "dgg")
+ * cvs hi [equivalent to: "cvs hi -o -u dgg"]
+ * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
+ * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
+ *
+ *** Committed (modified) files from the beginning of the file.
+ * cvs hi -c [-u user]
+ *
+ *** Committed (modified) files since Midnight, January 1, 1990:
+ * cvs hi -c -D 'Jan 1 1990' [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was stored in the history file:
+ * cvs hi -c -t TAG [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was placed on the files:
+ * cvs hi -c -r TAG [-u user]
+ *
+ *** Who last committed file/repository X?
+ * cvs hi -c -l -[fp] X
+ *
+ *** Modified files since tag/date/file/repos?
+ * cvs hi -c {-r TAG | -D Date | -b string}
+ *
+ *** Tag history
+ * cvs hi -T
+ *
+ *** History of file/repository/module X.
+ * cvs hi -[fpn] X
+ *
+ *** History of user "user".
+ * cvs hi -e -u user
+ *
+ *** Dump (eXtract) specified record types
+ * cvs hi -x [TOFWUGCMAR]
+ *
+ *
+ * FUTURE: J[Join], I[Import] (Not currently implemented.)
+ *
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+static struct hrec
+{
+ char *type; /* Type of record (In history record) */
+ char *user; /* Username (In history record) */
+ char *dir; /* "Compressed" Working dir (In history record) */
+ char *repos; /* (Tag is special.) Repository (In history record) */
+ char *rev; /* Revision affected (In history record) */
+ char *file; /* Filename (In history record) */
+ char *end; /* Ptr into repository to copy at end of workdir */
+ char *mod; /* The module within which the file is contained */
+ time_t date; /* Calculated from date stored in record */
+ int idx; /* Index of record, for "stable" sort. */
+} *hrec_head;
+
+
+static void fill_hrec PROTO((char *line, struct hrec * hr));
+static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
+static int select_hrec PROTO((struct hrec * hr));
+static int sort_order PROTO((const PTR l, const PTR r));
+static int within PROTO((char *find, char *string));
+static void expand_modules PROTO((void));
+static void read_hrecs PROTO((char *fname));
+static void report_hrecs PROTO((void));
+static void save_file PROTO((char *dir, char *name, char *module));
+static void save_module PROTO((char *module));
+static void save_user PROTO((char *name));
+
+#define ALL_REC_TYPES "TOEFWUCGMAR"
+#define USER_INCREMENT 2
+#define FILE_INCREMENT 128
+#define MODULE_INCREMENT 5
+#define HREC_INCREMENT 128
+
+static short report_count;
+
+static short extract;
+static short v_checkout;
+static short modified;
+static short tag_report;
+static short module_report;
+static short working;
+static short last_entry;
+static short all_users;
+
+static short user_sort;
+static short repos_sort;
+static short file_sort;
+static short module_sort;
+
+static short tz_local;
+static time_t tz_seconds_east_of_GMT;
+static char *tz_name = "+0000";
+
+/* -r, -t, or -b options, malloc'd. These are "" if the option in
+ question is not specified or is overridden by another option. The
+ main reason for using "" rather than NULL is historical. Together
+ with since_date, these are a mutually exclusive set; one overrides the
+ others. */
+static char *since_rev;
+static char *since_tag;
+static char *backto;
+/* -D option, or 0 if not specified. RCS format. */
+static char * since_date;
+
+static struct hrec *last_since_tag;
+static struct hrec *last_backto;
+
+/* Record types to look for, malloc'd. Probably could be statically
+ allocated, but only if we wanted to check for duplicates more than
+ we do. */
+static char *rec_types;
+
+static int hrec_count;
+static int hrec_max;
+
+static char **user_list; /* Ptr to array of ptrs to user names */
+static int user_max; /* Number of elements allocated */
+static int user_count; /* Number of elements used */
+
+static struct file_list_str
+{
+ char *l_file;
+ char *l_module;
+} *file_list; /* Ptr to array file name structs */
+static int file_max; /* Number of elements allocated */
+static int file_count; /* Number of elements used */
+
+static char **mod_list; /* Ptr to array of ptrs to module names */
+static int mod_max; /* Number of elements allocated */
+static int mod_count; /* Number of elements used */
+
+static char *histfile; /* Ptr to the history file name */
+
+/* This is pretty unclear. First of all, separating "flags" vs.
+ "options" (I think the distinction is that "options" take arguments)
+ is nonstandard, and not something we do elsewhere in CVS. Second of
+ all, what does "reports" mean? I think it means that you can only
+ supply one of those options, but "reports" hardly has that meaning in
+ a self-explanatory way. */
+static const char *const history_usg[] =
+{
+ "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
+ " Reports:\n",
+ " -T Produce report on all TAGs\n",
+ " -c Committed (Modified) files\n",
+ " -o Checked out modules\n",
+ " -m <module> Look for specified module (repeatable)\n",
+ " -x [TOEFWUCGMAR] Extract by record type\n",
+ " -e Everything (same as -x, but all record types)\n",
+ " Flags:\n",
+ " -a All users (Default is self)\n",
+ " -l Last modified (committed or modified report)\n",
+ " -w Working directory must match\n",
+ " Options:\n",
+ " -D <date> Since date (Many formats)\n",
+ " -b <str> Back to record with str in module/file/repos field\n",
+ " -f <file> Specified file (same as command line) (repeatable)\n",
+ " -n <modulename> In module (repeatable)\n",
+ " -p <repos> In repository (repeatable)\n",
+ " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
+ " -t <tag> Since tag record placed in history file (by anyone).\n",
+ " -u <user> For user name (repeatable)\n",
+ " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
+ NULL};
+
+/* Sort routine for qsort:
+ - If a user is selected at all, sort it first. User-within-file is useless.
+ - If a module was selected explicitly, sort next on module.
+ - Then sort by file. "File" is "repository/file" unless "working" is set,
+ then it is "workdir/file". (Revision order should always track date.)
+ - Always sort timestamp last.
+*/
+static int
+sort_order (l, r)
+ const PTR l;
+ const PTR r;
+{
+ int i;
+ const struct hrec *left = (const struct hrec *) l;
+ const struct hrec *right = (const struct hrec *) r;
+
+ if (user_sort) /* If Sort by username, compare users */
+ {
+ if ((i = strcmp (left->user, right->user)) != 0)
+ return (i);
+ }
+ if (module_sort) /* If sort by modules, compare module names */
+ {
+ if (left->mod && right->mod)
+ if ((i = strcmp (left->mod, right->mod)) != 0)
+ return (i);
+ }
+ if (repos_sort) /* If sort by repository, compare them. */
+ {
+ if ((i = strcmp (left->repos, right->repos)) != 0)
+ return (i);
+ }
+ if (file_sort) /* If sort by filename, compare files, NOT dirs. */
+ {
+ if ((i = strcmp (left->file, right->file)) != 0)
+ return (i);
+
+ if (working)
+ {
+ if ((i = strcmp (left->dir, right->dir)) != 0)
+ return (i);
+
+ if ((i = strcmp (left->end, right->end)) != 0)
+ return (i);
+ }
+ }
+
+ /*
+ * By default, sort by date, time
+ * XXX: This fails after 2030 when date slides into sign bit
+ */
+ if ((i = ((long) (left->date) - (long) (right->date))) != 0)
+ return (i);
+
+ /* For matching dates, keep the sort stable by using record index */
+ return (left->idx - right->idx);
+}
+
+int
+history (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i, c;
+ char *fname;
+
+ if (argc == -1)
+ usage (history_usg);
+
+ since_rev = xstrdup ("");
+ since_tag = xstrdup ("");
+ backto = xstrdup ("");
+ rec_types = xstrdup ("");
+ optind = 0;
+ while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
+ {
+ switch (c)
+ {
+ case 'T': /* Tag list */
+ report_count++;
+ tag_report++;
+ break;
+ case 'a': /* For all usernames */
+ all_users++;
+ break;
+ case 'c':
+ report_count++;
+ modified = 1;
+ break;
+ case 'e':
+ report_count++;
+ extract++;
+ free (rec_types);
+ rec_types = xstrdup (ALL_REC_TYPES);
+ break;
+ case 'l': /* Find Last file record */
+ last_entry = 1;
+ break;
+ case 'o':
+ report_count++;
+ v_checkout = 1;
+ break;
+ case 'w': /* Match Working Dir (CurDir) fields */
+ working = 1;
+ break;
+ case 'X': /* Undocumented debugging flag */
+ histfile = optarg;
+ break;
+ case 'D': /* Since specified date */
+ if (*since_rev || *since_tag || *backto)
+ {
+ error (0, 0, "date overriding rev/tag/backto");
+ *since_rev = *since_tag = *backto = '\0';
+ }
+ since_date = Make_Date (optarg);
+ break;
+ case 'b': /* Since specified file/Repos */
+ if (since_date || *since_rev || *since_tag)
+ {
+ error (0, 0, "backto overriding date/rev/tag");
+ *since_rev = *since_tag = '\0';
+ if (since_date != NULL)
+ free (since_date);
+ since_date = NULL;
+ }
+ free (backto);
+ backto = xstrdup (optarg);
+ break;
+ case 'f': /* For specified file */
+ save_file ("", optarg, (char *) NULL);
+ break;
+ case 'm': /* Full module report */
+ report_count++;
+ module_report++;
+ case 'n': /* Look for specified module */
+ save_module (optarg);
+ break;
+ case 'p': /* For specified directory */
+ save_file (optarg, "", (char *) NULL);
+ break;
+ case 'r': /* Since specified Tag/Rev */
+ if (since_date || *since_tag || *backto)
+ {
+ error (0, 0, "rev overriding date/tag/backto");
+ *since_tag = *backto = '\0';
+ if (since_date != NULL)
+ free (since_date);
+ since_date = NULL;
+ }
+ free (since_rev);
+ since_rev = xstrdup (optarg);
+ break;
+ case 't': /* Since specified Tag/Rev */
+ if (since_date || *since_rev || *backto)
+ {
+ error (0, 0, "tag overriding date/marker/file/repos");
+ *since_rev = *backto = '\0';
+ if (since_date != NULL)
+ free (since_date);
+ since_date = NULL;
+ }
+ free (since_tag);
+ since_tag = xstrdup (optarg);
+ break;
+ case 'u': /* For specified username */
+ save_user (optarg);
+ break;
+ case 'x':
+ report_count++;
+ extract++;
+ {
+ char *cp;
+
+ for (cp = optarg; *cp; cp++)
+ if (!strchr (ALL_REC_TYPES, *cp))
+ error (1, 0, "%c is not a valid report type", *cp);
+ }
+ free (rec_types);
+ rec_types = xstrdup (optarg);
+ break;
+ case 'z':
+ tz_local =
+ (optarg[0] == 'l' || optarg[0] == 'L')
+ && (optarg[1] == 't' || optarg[1] == 'T')
+ && !optarg[2];
+ if (tz_local)
+ tz_name = optarg;
+ else
+ {
+ /*
+ * Convert a known time with the given timezone to time_t.
+ * Use the epoch + 23 hours, so timezones east of GMT work.
+ */
+ static char f[] = "1/1/1970 23:00 %s";
+ char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
+ time_t t;
+ sprintf (buf, f, optarg);
+ t = get_date (buf, (struct timeb *) NULL);
+ free (buf);
+ if (t == (time_t) -1)
+ error (0, 0, "%s is not a known time zone", optarg);
+ else
+ {
+ /*
+ * Convert to seconds east of GMT, removing the
+ * 23-hour offset mentioned above.
+ */
+ tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t;
+ tz_name = optarg;
+ }
+ }
+ break;
+ case '?':
+ default:
+ usage (history_usg);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ for (i = 0; i < argc; i++)
+ save_file ("", argv[i], (char *) NULL);
+
+
+ /* ================ Now analyze the arguments a bit */
+ if (!report_count)
+ v_checkout++;
+ else if (report_count > 1)
+ error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ struct file_list_str *f1;
+ char **mod;
+
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (tag_report)
+ send_arg("-T");
+ if (all_users)
+ send_arg("-a");
+ if (modified)
+ send_arg("-c");
+ if (last_entry)
+ send_arg("-l");
+ if (v_checkout)
+ send_arg("-o");
+ if (working)
+ send_arg("-w");
+ if (histfile)
+ send_arg("-X");
+ if (since_date)
+ client_senddate (since_date);
+ if (backto[0] != '\0')
+ option_with_arg ("-b", backto);
+ for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
+ {
+ if (f1->l_file[0] == '*')
+ option_with_arg ("-p", f1->l_file + 1);
+ else
+ option_with_arg ("-f", f1->l_file);
+ }
+ if (module_report)
+ send_arg("-m");
+ for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
+ option_with_arg ("-n", *mod);
+ if (*since_rev)
+ option_with_arg ("-r", since_rev);
+ if (*since_tag)
+ option_with_arg ("-t", since_tag);
+ for (mod = user_list; mod < &user_list[user_count]; ++mod)
+ option_with_arg ("-u", *mod);
+ if (extract)
+ option_with_arg ("-x", rec_types);
+ option_with_arg ("-z", tz_name);
+
+ send_to_server ("history\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ if (all_users)
+ save_user ("");
+
+ if (mod_list)
+ expand_modules ();
+
+ if (tag_report)
+ {
+ if (!strchr (rec_types, 'T'))
+ {
+ rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
+ (void) strcat (rec_types, "T");
+ }
+ }
+ else if (extract)
+ {
+ if (user_list)
+ user_sort++;
+ }
+ else if (modified)
+ {
+ free (rec_types);
+ rec_types = xstrdup ("MAR");
+ /*
+ * If the user has not specified a date oriented flag ("Since"), sort
+ * by Repository/file before date. Default is "just" date.
+ */
+ if (last_entry
+ || (!since_date && !*since_rev && !*since_tag && !*backto))
+ {
+ repos_sort++;
+ file_sort++;
+ /*
+ * If we are not looking for last_modified and the user specified
+ * one or more users to look at, sort by user before filename.
+ */
+ if (!last_entry && user_list)
+ user_sort++;
+ }
+ }
+ else if (module_report)
+ {
+ free (rec_types);
+ rec_types = xstrdup (last_entry ? "OMAR" : ALL_REC_TYPES);
+ module_sort++;
+ repos_sort++;
+ file_sort++;
+ working = 0; /* User's workdir doesn't count here */
+ }
+ else
+ /* Must be "checkout" or default */
+ {
+ free (rec_types);
+ rec_types = xstrdup ("OF");
+ /* See comments in "modified" above */
+ if (!last_entry && user_list)
+ user_sort++;
+ if (last_entry
+ || (!since_date && !*since_rev && !*since_tag && !*backto))
+ file_sort++;
+ }
+
+ /* If no users were specified, use self (-a saves a universal ("") user) */
+ if (!user_list)
+ save_user (getcaller ());
+
+ /* If we're looking back to a Tag value, must consider "Tag" records */
+ if (*since_tag && !strchr (rec_types, 'T'))
+ {
+ rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
+ (void) strcat (rec_types, "T");
+ }
+
+ if (histfile)
+ fname = xstrdup (histfile);
+ else
+ {
+ fname = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM)
+ + sizeof (CVSROOTADM_HISTORY) + 10);
+ (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_HISTORY);
+ }
+
+ read_hrecs (fname);
+ if(hrec_count>0)
+ {
+ qsort ((PTR) hrec_head, hrec_count,
+ sizeof (struct hrec), sort_order);
+ }
+ report_hrecs ();
+ free (fname);
+ if (since_date != NULL)
+ free (since_date);
+ free (since_rev);
+ free (since_tag);
+ free (backto);
+ free (rec_types);
+
+ return (0);
+}
+
+void
+history_write (type, update_dir, revs, name, repository)
+ int type;
+ char *update_dir;
+ char *revs;
+ char *name;
+ char *repository;
+{
+ char *fname;
+ char *workdir;
+ char *username = getcaller ();
+ int fd;
+ char *line;
+ char *slash = "", *cp, *cp2, *repos;
+ int i;
+ static char *tilde = "";
+ static char *PrCurDir = NULL;
+
+ if (logoff) /* History is turned off by cmd line switch */
+ return;
+ fname = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM)
+ + sizeof (CVSROOTADM_HISTORY) + 10);
+ (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_HISTORY);
+
+ /* turn off history logging if the history file does not exist */
+ if (!isfile (fname))
+ {
+ logoff = 1;
+ goto out;
+ }
+
+ if (trace)
+ fprintf (stderr, "%s-> fopen(%s,a)\n",
+ CLIENT_SERVER_STR, fname);
+ if (noexec)
+ goto out;
+ fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666);
+ if (fd < 0)
+ error (1, errno, "cannot open history file: %s", fname);
+
+ repos = Short_Repository (repository);
+
+ if (!PrCurDir)
+ {
+ char *pwdir;
+
+ pwdir = get_homedir ();
+ PrCurDir = CurDir;
+ if (pwdir != NULL)
+ {
+ /* Assumes neither CurDir nor pwdir ends in '/' */
+ i = strlen (pwdir);
+ if (!strncmp (CurDir, pwdir, i))
+ {
+ PrCurDir += i; /* Point to '/' separator */
+ tilde = "~";
+ }
+ else
+ {
+ /* Try harder to find a "homedir" */
+ struct saved_cwd cwd;
+ char *homedir;
+
+ if (save_cwd (&cwd))
+ error_exit ();
+
+ if ( CVS_CHDIR (pwdir) < 0)
+ error (1, errno, "can't chdir(%s)", pwdir);
+ homedir = xgetwd ();
+ if (homedir == NULL)
+ error (1, errno, "can't getwd in %s", pwdir);
+
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+
+ i = strlen (homedir);
+ if (!strncmp (CurDir, homedir, i))
+ {
+ PrCurDir += i; /* Point to '/' separator */
+ tilde = "~";
+ }
+ free (homedir);
+ }
+ }
+ }
+
+ if (type == 'T')
+ {
+ repos = update_dir;
+ update_dir = "";
+ }
+ else if (update_dir && *update_dir)
+ slash = "/";
+ else
+ update_dir = "";
+
+ workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash)
+ + strlen (update_dir) + 10);
+ (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
+
+ /*
+ * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
+ * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
+ *
+ * "$workdir/$name" is the working file name.
+ * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
+ *
+ * First, note that the history format was intended to save space, not
+ * to be human readable.
+ *
+ * The working file directory ("workdir") and the Repository ("repos")
+ * usually end with the same one or more directory elements. To avoid
+ * duplication (and save space), the "workdir" field ends with
+ * an integer offset into the "repos" field. This offset indicates the
+ * beginning of the "tail" of "repos", after which all characters are
+ * duplicates.
+ *
+ * In other words, if the "workdir" field has a '*' (a very stupid thing
+ * to put in a filename) in it, then every thing following the last '*'
+ * is a hex offset into "repos" of the first character from "repos" to
+ * append to "workdir" to finish the pathname.
+ *
+ * It might be easier to look at an example:
+ *
+ * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+ *
+ * Indicates that the workdir is really "~/work/cvs/examples", saving
+ * 10 characters, where "~/work*d" would save 6 characters and mean that
+ * the workdir is really "~/work/examples". It will mean more on
+ * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
+ *
+ * "workdir" is always an absolute pathname (~/xxx is an absolute path)
+ * "repos" is always a relative pathname. So we can assume that we will
+ * never run into the top of "workdir" -- there will always be a '/' or
+ * a '~' at the head of "workdir" that is not matched by anything in
+ * "repos". On the other hand, we *can* run off the top of "repos".
+ *
+ * Only "compress" if we save characters.
+ */
+
+ if (!repos)
+ repos = "";
+
+ cp = workdir + strlen (workdir) - 1;
+ cp2 = repos + strlen (repos) - 1;
+ for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
+ i++;
+
+ if (i > 2)
+ {
+ i = strlen (repos) - i;
+ (void) sprintf ((cp + 1), "*%x", i);
+ }
+
+ if (!revs)
+ revs = "";
+ line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
+ + strlen (revs) + strlen (name) + 100);
+ sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n",
+ type, (long) time ((time_t *) NULL),
+ username, workdir, repos, revs, name);
+
+ /* Lessen some race conditions on non-Posix-compliant hosts. */
+ if (lseek (fd, (off_t) 0, SEEK_END) == -1)
+ error (1, errno, "cannot seek to end of history file: %s", fname);
+
+ if (write (fd, line, strlen (line)) < 0)
+ error (1, errno, "cannot write to history file: %s", fname);
+ free (line);
+ if (close (fd) != 0)
+ error (1, errno, "cannot close history file: %s", fname);
+ free (workdir);
+ out:
+ free (fname);
+}
+
+/*
+ * save_user() adds a user name to the user list to select. Zero-length
+ * username ("") matches any user.
+ */
+static void
+save_user (name)
+ char *name;
+{
+ if (user_count == user_max)
+ {
+ user_max += USER_INCREMENT;
+ user_list = (char **) xrealloc ((char *) user_list,
+ (int) user_max * sizeof (char *));
+ }
+ user_list[user_count++] = xstrdup (name);
+}
+
+/*
+ * save_file() adds file name and associated module to the file list to select.
+ *
+ * If "dir" is null, store a file name as is.
+ * If "name" is null, store a directory name with a '*' on the front.
+ * Else, store concatenated "dir/name".
+ *
+ * Later, in the "select" stage:
+ * - if it starts with '*', it is prefix-matched against the repository.
+ * - if it has a '/' in it, it is matched against the repository/file.
+ * - else it is matched against the file name.
+ */
+static void
+save_file (dir, name, module)
+ char *dir;
+ char *name;
+ char *module;
+{
+ char *cp;
+ struct file_list_str *fl;
+
+ if (file_count == file_max)
+ {
+ file_max += FILE_INCREMENT;
+ file_list = (struct file_list_str *) xrealloc ((char *) file_list,
+ file_max * sizeof (*fl));
+ }
+ fl = &file_list[file_count++];
+ fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
+ fl->l_module = module;
+
+ if (dir && *dir)
+ {
+ if (name && *name)
+ {
+ (void) strcpy (cp, dir);
+ (void) strcat (cp, "/");
+ (void) strcat (cp, name);
+ }
+ else
+ {
+ *cp++ = '*';
+ (void) strcpy (cp, dir);
+ }
+ }
+ else
+ {
+ if (name && *name)
+ {
+ (void) strcpy (cp, name);
+ }
+ else
+ {
+ error (0, 0, "save_file: null dir and file name");
+ }
+ }
+}
+
+static void
+save_module (module)
+ char *module;
+{
+ if (mod_count == mod_max)
+ {
+ mod_max += MODULE_INCREMENT;
+ mod_list = (char **) xrealloc ((char *) mod_list,
+ mod_max * sizeof (char *));
+ }
+ mod_list[mod_count++] = xstrdup (module);
+}
+
+static void
+expand_modules ()
+{
+}
+
+/* fill_hrec
+ *
+ * Take a ptr to 7-part history line, ending with a newline, for example:
+ *
+ * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+ *
+ * Split it into 7 parts and drop the parts into a "struct hrec".
+ * Return a pointer to the character following the newline.
+ *
+ */
+
+#define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return; *(line - 1) = '\0'; } while (0)
+
+static void
+fill_hrec (line, hr)
+ char *line;
+ struct hrec *hr;
+{
+ char *cp;
+ int c;
+ int off;
+ static int idx = 0;
+ unsigned long date;
+
+ memset ((char *) hr, 0, sizeof (*hr));
+
+ while (isspace ((unsigned char) *line))
+ line++;
+
+ hr->type = line++;
+ (void) sscanf (line, "%lx", &date);
+ hr->date = date;
+ while (*line && strchr ("0123456789abcdefABCDEF", *line))
+ line++;
+ if (*line == '\0')
+ return;
+
+ line++;
+ NEXT_BAR (user);
+ NEXT_BAR (dir);
+ if ((cp = strrchr (hr->dir, '*')) != NULL)
+ {
+ *cp++ = '\0';
+ (void) sscanf (cp, "%x", &off);
+ hr->end = line + off;
+ }
+ else
+ hr->end = line - 1; /* A handy pointer to '\0' */
+ NEXT_BAR (repos);
+ NEXT_BAR (rev);
+ hr->idx = idx++;
+ if (strchr ("FOET", *(hr->type)))
+ hr->mod = line;
+
+ NEXT_BAR (file);
+}
+
+
+#ifndef STAT_BLOCKSIZE
+#if HAVE_ST_BLKSIZE
+#define STAT_BLOCKSIZE(s) (s).st_blksize
+#else
+#define STAT_BLOCKSIZE(s) (4 * 1024)
+#endif
+#endif
+
+
+/* read_hrecs's job is to read the history file and fill in all the "hrec"
+ * (history record) array elements with the ones we need to print.
+ *
+ * Logic:
+ * - Read a block from the file.
+ * - Walk through the block parsing line into hr records.
+ * - if the hr isn't used, free its strings, if it is, bump the hrec counter
+ * - at the end of a block, copy the end of the current block to the start
+ * of space for the next block, then read in the next block. If we get less
+ * than the whole block, we're done.
+ */
+static void
+read_hrecs (fname)
+ char *fname;
+{
+ unsigned char *cpstart, *cp, *nl;
+ char *hrline;
+ int i;
+ int fd;
+ struct stat st_buf;
+
+ if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
+ error (1, errno, "cannot open history file: %s", fname);
+
+ if (fstat (fd, &st_buf) < 0)
+ error (1, errno, "can't stat history file");
+
+ if (!(st_buf.st_size))
+ error (1, 0, "history file is empty");
+
+ cpstart = xmalloc (2 * STAT_BLOCKSIZE(st_buf));
+ cpstart[0] = '\0';
+ cp = cpstart;
+
+ hrec_max = HREC_INCREMENT;
+ hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
+
+ for (;;)
+ {
+ for (nl = cp; *nl && *nl != '\n'; nl++)
+ if (!isprint(*nl)) *nl = ' ';
+
+ if (!*nl)
+ {
+ if (nl - cp >= STAT_BLOCKSIZE(st_buf))
+ {
+ error(1, 0, "history line too long (> %lu)",
+ (unsigned long) STAT_BLOCKSIZE(st_buf));
+ }
+ if (nl > cp)
+ memmove (cpstart, cp, nl - cp);
+ nl = cpstart + (nl - cp);
+ cp = cpstart;
+ i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
+ if (i > 0)
+ {
+ nl[i] = '\0';
+ continue;
+ }
+ if (i < 0)
+ error (1, errno, "error reading history file");
+ if (nl == cp) break;
+ nl[1] = '\0';
+ }
+ *nl = '\0';
+
+ if (hrec_count == hrec_max)
+ {
+ struct hrec *old_head = hrec_head;
+
+ hrec_max += HREC_INCREMENT;
+ hrec_head = xrealloc ((char *) hrec_head,
+ hrec_max * sizeof (struct hrec));
+ if (last_since_tag)
+ last_since_tag = hrec_head + (last_since_tag - old_head);
+ if (last_backto)
+ last_backto = hrec_head + (last_backto - old_head);
+ }
+
+ /* fill_hrec dates from when history read the entire
+ history file in one chunk, and then records were pulled out
+ by pointing to the various parts of this big chunk. This is
+ why there are ugly hacks here: I don't want to completely
+ re-write the whole history stuff right now. */
+
+ hrline = xstrdup ((char *)cp);
+ fill_hrec (hrline, &hrec_head[hrec_count]);
+ if (select_hrec (&hrec_head[hrec_count]))
+ hrec_count++;
+ else
+ free(hrline);
+
+ cp = nl + 1;
+ }
+ free (cpstart);
+ close (fd);
+
+ /* Special selection problem: If "since_tag" is set, we have saved every
+ * record from the 1st occurrence of "since_tag", when we want to save
+ * records since the *last* occurrence of "since_tag". So what we have
+ * to do is bump hrec_head forward and reduce hrec_count accordingly.
+ */
+ if (last_since_tag)
+ {
+ hrec_count -= (last_since_tag - hrec_head);
+ hrec_head = last_since_tag;
+ }
+
+ /* Much the same thing is necessary for the "backto" option. */
+ if (last_backto)
+ {
+ hrec_count -= (last_backto - hrec_head);
+ hrec_head = last_backto;
+ }
+}
+
+/* Utility program for determining whether "find" is inside "string" */
+static int
+within (find, string)
+ char *find, *string;
+{
+ int c, len;
+
+ if (!find || !string)
+ return (0);
+
+ c = *find++;
+ len = strlen (find);
+
+ while (*string)
+ {
+ if (!(string = strchr (string, c)))
+ return (0);
+ string++;
+ if (!strncmp (find, string, len))
+ return (1);
+ }
+ return (0);
+}
+
+/* The purpose of "select_hrec" is to apply the selection criteria based on
+ * the command arguments and defaults and return a flag indicating whether
+ * this record should be remembered for printing.
+ */
+static int
+select_hrec (hr)
+ struct hrec *hr;
+{
+ char **cpp, *cp, *cp2;
+ struct file_list_str *fl;
+ int count;
+
+ /* "Since" checking: The argument parser guarantees that only one of the
+ * following four choices is set:
+ *
+ * 1. If "since_date" is set, it contains the date specified on the
+ * command line. hr->date fields earlier than "since_date" are ignored.
+ * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
+ * number (which is of limited use) or a symbolic TAG. Each RCS file
+ * is examined and the date on the specified revision (or the revision
+ * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
+ * compared against hr->date as in 1. above.
+ * 3. If "since_tag" is set, matching tag records are saved. The field
+ * "last_since_tag" is set to the last one of these. Since we don't
+ * know where the last one will be, all records are saved from the
+ * first occurrence of the TAG. Later, at the end of "select_hrec"
+ * records before the last occurrence of "since_tag" are skipped.
+ * 4. If "backto" is set, all records with a module name or file name
+ * matching "backto" are saved. In addition, all records with a
+ * repository field with a *prefix* matching "backto" are saved.
+ * The field "last_backto" is set to the last one of these. As in
+ * 3. above, "select_hrec" adjusts to include the last one later on.
+ */
+ if (since_date)
+ {
+ char *ourdate = date_from_time_t (hr->date);
+ count = RCS_datecmp (ourdate, since_date);
+ free (ourdate);
+ if (count < 0)
+ return (0);
+ }
+ else if (*since_rev)
+ {
+ Vers_TS *vers;
+ time_t t;
+ struct file_info finfo;
+
+ memset (&finfo, 0, sizeof finfo);
+ finfo.file = hr->file;
+ /* Not used, so don't worry about it. */
+ finfo.update_dir = NULL;
+ finfo.fullname = finfo.file;
+ finfo.repository = hr->repos;
+ finfo.entries = NULL;
+ finfo.rcs = NULL;
+
+ vers = Version_TS (&finfo, (char *) NULL, since_rev, (char *) NULL,
+ 1, 0);
+ if (vers->vn_rcs)
+ {
+ if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
+ != (time_t) 0)
+ {
+ if (hr->date < t)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ }
+ }
+ freevers_ts (&vers);
+ }
+ else if (*since_tag)
+ {
+ if (*(hr->type) == 'T')
+ {
+ /*
+ * A 'T'ag record, the "rev" field holds the tag to be set,
+ * while the "repos" field holds "D"elete, "A"dd or a rev.
+ */
+ if (within (since_tag, hr->rev))
+ {
+ last_since_tag = hr;
+ return (1);
+ }
+ else
+ return (0);
+ }
+ if (!last_since_tag)
+ return (0);
+ }
+ else if (*backto)
+ {
+ if (within (backto, hr->file) || within (backto, hr->mod) ||
+ within (backto, hr->repos))
+ last_backto = hr;
+ else
+ return (0);
+ }
+
+ /* User checking:
+ *
+ * Run down "user_list", match username ("" matches anything)
+ * If "" is not there and actual username is not there, return failure.
+ */
+ if (user_list && hr->user)
+ {
+ for (cpp = user_list, count = user_count; count; cpp++, count--)
+ {
+ if (!**cpp)
+ break; /* null user == accept */
+ if (!strcmp (hr->user, *cpp)) /* found listed user */
+ break;
+ }
+ if (!count)
+ return (0); /* Not this user */
+ }
+
+ /* Record type checking:
+ *
+ * 1. If Record type is not in rec_types field, skip it.
+ * 2. If mod_list is null, keep everything. Otherwise keep only modules
+ * on mod_list.
+ * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
+ * file_list is null, keep everything. Otherwise, keep only files on
+ * file_list, matched appropriately.
+ */
+ if (!strchr (rec_types, *(hr->type)))
+ return (0);
+ if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
+ {
+ if (file_list) /* If file_list is null, accept all */
+ {
+ for (fl = file_list, count = file_count; count; fl++, count--)
+ {
+ /* 1. If file_list entry starts with '*', skip the '*' and
+ * compare it against the repository in the hrec.
+ * 2. If file_list entry has a '/' in it, compare it against
+ * the concatenation of the repository and file from hrec.
+ * 3. Else compare the file_list entry against the hrec file.
+ */
+ char *cmpfile = NULL;
+
+ if (*(cp = fl->l_file) == '*')
+ {
+ cp++;
+ /* if argument to -p is a prefix of repository */
+ if (!strncmp (cp, hr->repos, strlen (cp)))
+ {
+ hr->mod = fl->l_module;
+ break;
+ }
+ }
+ else
+ {
+ if (strchr (cp, '/'))
+ {
+ cmpfile = xmalloc (strlen (hr->repos)
+ + strlen (hr->file)
+ + 10);
+ (void) sprintf (cmpfile, "%s/%s",
+ hr->repos, hr->file);
+ cp2 = cmpfile;
+ }
+ else
+ {
+ cp2 = hr->file;
+ }
+
+ /* if requested file is found within {repos}/file fields */
+ if (within (cp, cp2))
+ {
+ hr->mod = fl->l_module;
+ break;
+ }
+ if (cmpfile != NULL)
+ free (cmpfile);
+ }
+ }
+ if (!count)
+ return (0); /* String specified and no match */
+ }
+ }
+ if (mod_list)
+ {
+ for (cpp = mod_list, count = mod_count; count; cpp++, count--)
+ {
+ if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
+ break;
+ }
+ if (!count)
+ return (0); /* Module specified & this record is not one of them. */
+ }
+
+ return (1); /* Select this record unless rejected above. */
+}
+
+/* The "sort_order" routine (when handed to qsort) has arranged for the
+ * hrecs files to be in the right order for the report.
+ *
+ * Most of the "selections" are done in the select_hrec routine, but some
+ * selections are more easily done after the qsort by "accept_hrec".
+ */
+static void
+report_hrecs ()
+{
+ struct hrec *hr, *lr;
+ struct tm *tm;
+ int i, count, ty;
+ char *cp;
+ int user_len, file_len, rev_len, mod_len, repos_len;
+
+ if (*since_tag && !last_since_tag)
+ {
+ (void) printf ("No tag found: %s\n", since_tag);
+ return;
+ }
+ else if (*backto && !last_backto)
+ {
+ (void) printf ("No module, file or repository with: %s\n", backto);
+ return;
+ }
+ else if (hrec_count < 1)
+ {
+ (void) printf ("No records selected.\n");
+ return;
+ }
+
+ user_len = file_len = rev_len = mod_len = repos_len = 0;
+
+ /* Run through lists and find maximum field widths */
+ hr = lr = hrec_head;
+ hr++;
+ for (count = hrec_count; count--; lr = hr, hr++)
+ {
+ char *repos;
+
+ if (!count)
+ hr = NULL;
+ if (!accept_hrec (lr, hr))
+ continue;
+
+ ty = *(lr->type);
+ repos = xstrdup (lr->repos);
+ if ((cp = strrchr (repos, '/')) != NULL)
+ {
+ if (lr->mod && !strcmp (++cp, lr->mod))
+ {
+ (void) strcpy (cp, "*");
+ }
+ }
+ if ((i = strlen (lr->user)) > user_len)
+ user_len = i;
+ if ((i = strlen (lr->file)) > file_len)
+ file_len = i;
+ if (ty != 'T' && (i = strlen (repos)) > repos_len)
+ repos_len = i;
+ if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
+ rev_len = i;
+ if (lr->mod && (i = strlen (lr->mod)) > mod_len)
+ mod_len = i;
+ free (repos);
+ }
+
+ /* Walk through hrec array setting "lr" (Last Record) to each element.
+ * "hr" points to the record following "lr" -- It is NULL in the last
+ * pass.
+ *
+ * There are two sections in the loop below:
+ * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
+ * decide whether the record should be printed.
+ * 2. Based on the record type, format and print the data.
+ */
+ for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
+ {
+ char *workdir;
+ char *repos;
+
+ if (!hrec_count)
+ hr = NULL;
+ if (!accept_hrec (lr, hr))
+ continue;
+
+ ty = *(lr->type);
+ if (!tz_local)
+ {
+ time_t t = lr->date + tz_seconds_east_of_GMT;
+ tm = gmtime (&t);
+ }
+ else
+ tm = localtime (&(lr->date));
+
+ (void) printf ("%c %02d/%02d %02d:%02d %s %-*s", ty, tm->tm_mon + 1,
+ tm->tm_mday, tm->tm_hour, tm->tm_min, tz_name,
+ user_len, lr->user);
+
+ workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
+ (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
+ if ((cp = strrchr (workdir, '/')) != NULL)
+ {
+ if (lr->mod && !strcmp (++cp, lr->mod))
+ {
+ (void) strcpy (cp, "*");
+ }
+ }
+ repos = xmalloc (strlen (lr->repos) + 10);
+ (void) strcpy (repos, lr->repos);
+ if ((cp = strrchr (repos, '/')) != NULL)
+ {
+ if (lr->mod && !strcmp (++cp, lr->mod))
+ {
+ (void) strcpy (cp, "*");
+ }
+ }
+
+ switch (ty)
+ {
+ case 'T':
+ /* 'T'ag records: repository is a "tag type", rev is the tag */
+ (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
+ repos);
+ if (working)
+ (void) printf (" {%s}", workdir);
+ break;
+ case 'F':
+ case 'E':
+ case 'O':
+ if (lr->rev && *(lr->rev))
+ (void) printf (" [%s]", lr->rev);
+ (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
+ mod_len + 1 - (int) strlen (lr->mod),
+ "=", workdir);
+ break;
+ case 'W':
+ case 'U':
+ case 'C':
+ case 'G':
+ case 'M':
+ case 'A':
+ case 'R':
+ (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
+ file_len, lr->file, repos_len, repos,
+ lr->mod ? lr->mod : "", workdir);
+ break;
+ default:
+ (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
+ break;
+ }
+ (void) putchar ('\n');
+ free (workdir);
+ free (repos);
+ }
+}
+
+static int
+accept_hrec (lr, hr)
+ struct hrec *hr, *lr;
+{
+ int ty;
+
+ ty = *(lr->type);
+
+ if (last_since_tag && ty == 'T')
+ return (1);
+
+ if (v_checkout)
+ {
+ if (ty != 'O')
+ return (0); /* Only interested in 'O' records */
+
+ /* We want to identify all the states that cause the next record
+ * ("hr") to be different from the current one ("lr") and only
+ * print a line at the allowed boundaries.
+ */
+
+ if (!hr || /* The last record */
+ strcmp (hr->user, lr->user) || /* User has changed */
+ strcmp (hr->mod, lr->mod) ||/* Module has changed */
+ (working && /* If must match "workdir" */
+ (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
+ strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
+
+ return (1);
+ }
+ else if (modified)
+ {
+ if (!last_entry || /* Don't want only last rec */
+ !hr || /* Last entry is a "last entry" */
+ strcmp (hr->repos, lr->repos) || /* Repository has changed */
+ strcmp (hr->file, lr->file))/* File has changed */
+ return (1);
+
+ if (working)
+ { /* If must match "workdir" */
+ if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
+ strcmp (hr->end, lr->end)) /* the 2nd parts differ */
+ return (1);
+ }
+ }
+ else if (module_report)
+ {
+ if (!last_entry || /* Don't want only last rec */
+ !hr || /* Last entry is a "last entry" */
+ strcmp (hr->mod, lr->mod) ||/* Module has changed */
+ strcmp (hr->repos, lr->repos) || /* Repository has changed */
+ strcmp (hr->file, lr->file))/* File has changed */
+ return (1);
+ }
+ else
+ {
+ /* "extract" and "tag_report" always print selected records. */
+ return (1);
+ }
+
+ return (0);
+}
diff --git a/gnu/dist/cvs/src/ignore.c b/gnu/dist/cvs/src/ignore.c
new file mode 100644
index 00000000000..5156e30f285
--- /dev/null
+++ b/gnu/dist/cvs/src/ignore.c
@@ -0,0 +1,500 @@
+/* This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+/*
+ * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com>
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+/*
+ * Ignore file section.
+ *
+ * "!" may be included any time to reset the list (i.e. ignore nothing);
+ * "*" may be specified to ignore everything. It stays as the first
+ * element forever, unless a "!" clears it out.
+ */
+
+static char **ign_list; /* List of files to ignore in update
+ * and import */
+static char **s_ign_list = NULL;
+static int ign_count; /* Number of active entries */
+static int s_ign_count = 0;
+static int ign_size; /* This many slots available (plus
+ * one for a NULL) */
+static int ign_hold = -1; /* Index where first "temporary" item
+ * is held */
+
+const char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\
+ .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\
+ *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$";
+
+#define IGN_GROW 16 /* grow the list by 16 elements at a
+ * time */
+
+/* Nonzero if we have encountered an -I ! directive, which means one should
+ no longer ask the server about what is in CVSROOTADM_IGNORE. */
+int ign_inhibit_server;
+
+/*
+ * To the "ignore list", add the hard-coded default ignored wildcards above,
+ * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
+ * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
+ * variable.
+ */
+void
+ign_setup ()
+{
+ char *home_dir;
+ char *tmp;
+
+ ign_inhibit_server = 0;
+
+ /* Start with default list and special case */
+ tmp = xstrdup (ign_default);
+ ign_add (tmp, 0);
+ free (tmp);
+
+#ifdef CLIENT_SUPPORT
+ /* The client handles another way, by (after it does its own ignore file
+ processing, and only if !ign_inhibit_server), letting the server
+ know about the files and letting it decide whether to ignore
+ them based on CVSROOOTADM_IGNORE. */
+ if (!client_active)
+#endif
+ {
+ char *file = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM)
+ + sizeof (CVSROOTADM_IGNORE) + 10);
+ /* Then add entries found in repository, if it exists */
+ (void) sprintf (file, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_IGNORE);
+ ign_add_file (file, 0);
+ free (file);
+ }
+
+ /* Then add entries found in home dir, (if user has one) and file exists */
+ home_dir = get_homedir ();
+ /* If we can't find a home directory, ignore ~/.cvsignore. This may
+ make tracking down problems a bit of a pain, but on the other
+ hand it might be obnoxious to complain when CVS will function
+ just fine without .cvsignore (and many users won't even know what
+ .cvsignore is). */
+ if (home_dir)
+ {
+ char *file = xmalloc (strlen (home_dir) + sizeof (CVSDOTIGNORE) + 10);
+ (void) sprintf (file, "%s/%s", home_dir, CVSDOTIGNORE);
+ ign_add_file (file, 0);
+ free (file);
+ }
+
+ /* Then add entries found in CVSIGNORE environment variable. */
+ ign_add (getenv (IGNORE_ENV), 0);
+
+ /* Later, add ignore entries found in -I arguments */
+}
+
+/*
+ * Open a file and read lines, feeding each line to a line parser. Arrange
+ * for keeping a temporary list of wildcards at the end, if the "hold"
+ * argument is set.
+ */
+void
+ign_add_file (file, hold)
+ char *file;
+ int hold;
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t line_allocated = 0;
+
+ /* restore the saved list (if any) */
+ if (s_ign_list != NULL)
+ {
+ int i;
+
+ for (i = 0; i < s_ign_count; i++)
+ ign_list[i] = s_ign_list[i];
+ ign_count = s_ign_count;
+ ign_list[ign_count] = NULL;
+
+ s_ign_count = 0;
+ free (s_ign_list);
+ s_ign_list = NULL;
+ }
+
+ /* is this a temporary ignore file? */
+ if (hold)
+ {
+ /* re-set if we had already done a temporary file */
+ if (ign_hold >= 0)
+ {
+ int i;
+
+ for (i = ign_hold; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_count = ign_hold;
+ ign_list[ign_count] = NULL;
+ }
+ else
+ {
+ ign_hold = ign_count;
+ }
+ }
+
+ /* load the file */
+ fp = CVS_FOPEN (file, "r");
+ if (fp == NULL)
+ {
+ if (! existence_error (errno))
+ error (0, errno, "cannot open %s", file);
+ return;
+ }
+ while (getline (&line, &line_allocated, fp) >= 0)
+ ign_add (line, hold);
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", file);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", file);
+ free (line);
+}
+
+/* Parse a line of space-separated wildcards and add them to the list. */
+void
+ign_add (ign, hold)
+ char *ign;
+ int hold;
+{
+ if (!ign || !*ign)
+ return;
+
+ for (; *ign; ign++)
+ {
+ char *mark;
+ char save;
+
+ /* ignore whitespace before the token */
+ if (isspace ((unsigned char) *ign))
+ continue;
+
+ /*
+ * if we find a single character !, we must re-set the ignore list
+ * (saving it if necessary). We also catch * as a special case in a
+ * global ignore file as an optimization
+ */
+ if ((!*(ign+1) || isspace ((unsigned char) *(ign+1)))
+ && (*ign == '!' || *ign == '*'))
+ {
+ if (!hold)
+ {
+ /* permanently reset the ignore list */
+ int i;
+
+ for (i = 0; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_count = 0;
+ ign_list[0] = NULL;
+
+ /* if we are doing a '!', continue; otherwise add the '*' */
+ if (*ign == '!')
+ {
+ ign_inhibit_server = 1;
+ continue;
+ }
+ }
+ else if (*ign == '!')
+ {
+ /* temporarily reset the ignore list */
+ int i;
+
+ if (ign_hold >= 0)
+ {
+ for (i = ign_hold; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_hold = -1;
+ }
+ s_ign_list = (char **) xmalloc (ign_count * sizeof (char *));
+ for (i = 0; i < ign_count; i++)
+ s_ign_list[i] = ign_list[i];
+ s_ign_count = ign_count;
+ ign_count = 0;
+ ign_list[0] = NULL;
+ continue;
+ }
+ }
+
+ /* If we have used up all the space, add some more */
+ if (ign_count >= ign_size)
+ {
+ ign_size += IGN_GROW;
+ ign_list = (char **) xrealloc ((char *) ign_list,
+ (ign_size + 1) * sizeof (char *));
+ }
+
+ /* find the end of this token */
+ for (mark = ign; *mark && !isspace ((unsigned char) *mark); mark++)
+ /* do nothing */ ;
+
+ save = *mark;
+ *mark = '\0';
+
+ ign_list[ign_count++] = xstrdup (ign);
+ ign_list[ign_count] = NULL;
+
+ *mark = save;
+ if (save)
+ ign = mark;
+ else
+ ign = mark - 1;
+ }
+}
+
+/* Set to 1 if filenames should be matched in a case-insensitive
+ fashion. Note that, contrary to the name and placement in ignore.c,
+ this is no longer just for ignore patterns. */
+int ign_case;
+
+/* Return 1 if the given filename should be ignored by update or import. */
+int
+ign_name (name)
+ char *name;
+{
+ char **cpp = ign_list;
+
+ if (cpp == NULL)
+ return (0);
+
+ if (ign_case)
+ {
+ /* We do a case-insensitive match by calling fnmatch on copies of
+ the pattern and the name which have been converted to
+ lowercase. FIXME: would be much cleaner to just unify this
+ with the other case-insensitive fnmatch stuff (FOLD_FN_CHAR
+ in lib/fnmatch.c; os2_fnmatch in emx/system.c). */
+ char *name_lower;
+ char *pat_lower;
+ char *p;
+
+ name_lower = xstrdup (name);
+ for (p = name_lower; *p != '\0'; ++p)
+ *p = tolower (*p);
+ while (*cpp)
+ {
+ pat_lower = xstrdup (*cpp++);
+ for (p = pat_lower; *p != '\0'; ++p)
+ *p = tolower (*p);
+ if (CVS_FNMATCH (pat_lower, name_lower, 0) == 0)
+ goto matched;
+ free (pat_lower);
+ }
+ free (name_lower);
+ return 0;
+ matched:
+ free (name_lower);
+ free (pat_lower);
+ return 1;
+ }
+ else
+ {
+ while (*cpp)
+ if (CVS_FNMATCH (*cpp++, name, 0) == 0)
+ return 1;
+ return 0;
+ }
+}
+
+/* FIXME: This list of dirs to ignore stuff seems not to be used.
+ Really? send_dirent_proc and update_dirent_proc both call
+ ignore_directory and do_module calls ign_dir_add. No doubt could
+ use some documentation/testsuite work. */
+
+static char **dir_ign_list = NULL;
+static int dir_ign_max = 0;
+static int dir_ign_current = 0;
+
+/* Add a directory to list of dirs to ignore. */
+void
+ign_dir_add (name)
+ char *name;
+{
+ /* Make sure we've got the space for the entry. */
+ if (dir_ign_current <= dir_ign_max)
+ {
+ dir_ign_max += IGN_GROW;
+ dir_ign_list =
+ (char **) xrealloc (dir_ign_list,
+ (dir_ign_max + 1) * sizeof (char *));
+ }
+
+ dir_ign_list[dir_ign_current] = name;
+
+ dir_ign_current += 1 ;
+}
+
+
+/* Return nonzero if NAME is part of the list of directories to ignore. */
+
+int
+ignore_directory (name)
+ char *name;
+{
+ int i;
+
+ if (!dir_ign_list)
+ return 0;
+
+ i = dir_ign_current;
+ while (i--)
+ {
+ if (strncmp (name, dir_ign_list[i], strlen (dir_ign_list[i])) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Process the current directory, looking for files not in ILIST and
+ * not on the global ignore list for this directory. If we find one,
+ * call PROC passing it the name of the file and the update dir.
+ * ENTRIES is the entries list, which is used to identify known
+ * directories. ENTRIES may be NULL, in which case we assume that any
+ * directory with a CVS administration directory is known.
+ */
+void
+ignore_files (ilist, entries, update_dir, proc)
+ List *ilist;
+ List *entries;
+ char *update_dir;
+ Ignore_proc proc;
+{
+ int subdirs;
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ char *file;
+ char *xdir;
+
+ /* Set SUBDIRS if we have subdirectory information in ENTRIES. */
+ if (entries == NULL)
+ subdirs = 0;
+ else
+ {
+ struct stickydirtag *sdtp;
+
+ sdtp = (struct stickydirtag *) entries->list->data;
+ subdirs = sdtp == NULL || sdtp->subdirs;
+ }
+
+ /* we get called with update_dir set to "." sometimes... strip it */
+ if (strcmp (update_dir, ".") == 0)
+ xdir = "";
+ else
+ xdir = update_dir;
+
+ dirp = CVS_OPENDIR (".");
+ if (dirp == NULL)
+ {
+ error (0, errno, "cannot open current directory");
+ return;
+ }
+
+ ign_add_file (CVSDOTIGNORE, 1);
+ wrap_add_file (CVSDOTWRAPPER, 1);
+
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ file = dp->d_name;
+ if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
+ continue;
+ if (findnode_fn (ilist, file) != NULL)
+ continue;
+ if (subdirs)
+ {
+ Node *node;
+
+ node = findnode_fn (entries, file);
+ if (node != NULL
+ && ((Entnode *) node->data)->type == ENT_SUBDIR)
+ {
+ char *p;
+ int dir;
+
+ /* For consistency with past behaviour, we only ignore
+ this directory if there is a CVS subdirectory.
+ This will normally be the case, but the user may
+ have messed up the working directory somehow. */
+ p = xmalloc (strlen (file) + sizeof CVSADM + 10);
+ sprintf (p, "%s/%s", file, CVSADM);
+ dir = isdir (p);
+ free (p);
+ if (dir)
+ continue;
+ }
+ }
+
+ /* We could be ignoring FIFOs and other files which are neither
+ regular files nor directories here. */
+ if (ign_name (file))
+ continue;
+
+ if (
+#ifdef DT_DIR
+ dp->d_type != DT_UNKNOWN ||
+#endif
+ lstat(file, &sb) != -1)
+ {
+
+ if (
+#ifdef DT_DIR
+ dp->d_type == DT_DIR
+ || (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode))
+#else
+ S_ISDIR (sb.st_mode)
+#endif
+ )
+ {
+ if (! subdirs)
+ {
+ char *temp;
+
+ temp = xmalloc (strlen (file) + sizeof (CVSADM) + 10);
+ (void) sprintf (temp, "%s/%s", file, CVSADM);
+ if (isdir (temp))
+ {
+ free (temp);
+ continue;
+ }
+ free (temp);
+ }
+ }
+#ifdef S_ISLNK
+ else if (
+#ifdef DT_DIR
+ dp->d_type == DT_LNK
+ || (dp->d_type == DT_UNKNOWN && S_ISLNK(sb.st_mode))
+#else
+ S_ISLNK (sb.st_mode)
+#endif
+ )
+ {
+ continue;
+ }
+#endif
+ }
+
+ (*proc) (file, xdir);
+ errno = 0;
+ }
+ if (errno != 0)
+ error (0, errno, "error reading current directory");
+ (void) closedir (dirp);
+}
diff --git a/gnu/dist/cvs/src/import.c b/gnu/dist/cvs/src/import.c
new file mode 100644
index 00000000000..37e5abdffa4
--- /dev/null
+++ b/gnu/dist/cvs/src/import.c
@@ -0,0 +1,1596 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * "import" checks in the vendor release located in the current directory into
+ * the CVS source repository. The CVS vendor branch support is utilized.
+ *
+ * At least three arguments are expected to follow the options:
+ * repository Where the source belongs relative to the CVSROOT
+ * VendorTag Vendor's major tag
+ * VendorReleTag Tag for this particular release
+ *
+ * Additional arguments specify more Vendor Release Tags.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+#include <assert.h>
+
+static char *get_comment PROTO((char *user));
+static int add_rev PROTO((char *message, RCSNode *rcs, char *vfile,
+ char *vers));
+static int add_tags PROTO((RCSNode *rcs, char *vfile, char *vtag, int targc,
+ char *targv[]));
+static int import_descend PROTO((char *message, char *vtag, int targc, char *targv[]));
+static int import_descend_dir PROTO((char *message, char *dir, char *vtag,
+ int targc, char *targv[]));
+static int process_import_file PROTO((char *message, char *vfile, char *vtag,
+ int targc, char *targv[]));
+static int update_rcs_file PROTO((char *message, char *vfile, char *vtag, int targc,
+ char *targv[], int inattic));
+static void add_log PROTO((int ch, char *fname));
+
+static int repos_len;
+static char *vhead;
+static char *vbranch;
+static FILE *logfp;
+static char *repository;
+static int conflicts;
+static int use_file_modtime;
+static char *keyword_opt = NULL;
+
+static const char *const import_usage[] =
+{
+ "Usage: %s %s [-d] [-k subst] [-I ign] [-m msg] [-b branch]\n",
+ " [-W spec] repository vendor-tag release-tags...\n",
+ "\t-d\tUse the file's modification time as the time of import.\n",
+ "\t-k sub\tSet default RCS keyword substitution mode.\n",
+ "\t-I ign\tMore files to ignore (! to reset).\n",
+ "\t-b bra\tVendor branch id.\n",
+ "\t-m msg\tLog message.\n",
+ "\t-W spec\tWrappers specification line.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+import (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *message = NULL;
+ char *tmpfile;
+ char *cp;
+ int i, c, msglen, err;
+ List *ulist;
+ Node *p;
+ struct logfile_info *li;
+
+ if (argc == -1)
+ usage (import_usage);
+
+ ign_setup ();
+ wrap_setup ();
+
+ vbranch = xstrdup (CVSBRANCH);
+ optind = 0;
+ while ((c = getopt (argc, argv, "+Qqdb:m:I:k:W:")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'd':
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* CVS 1.10 and older clients will send this, but it
+ doesn't do any good. So tell the user we can't
+ cope, rather than silently losing. */
+ error (0, 0,
+ "warning: not setting the time of import from the file");
+ error (0, 0, "due to client limitations");
+ }
+#endif
+ use_file_modtime = 1;
+ break;
+ case 'b':
+ free (vbranch);
+ vbranch = xstrdup (optarg);
+ break;
+ case 'm':
+#ifdef FORCE_USE_EDITOR
+ use_editor = 1;
+#else
+ use_editor = 0;
+#endif
+ message = xstrdup(optarg);
+ break;
+ case 'I':
+ ign_add (optarg, 0);
+ break;
+ case 'k':
+ /* RCS_check_kflag returns strings of the form -kxx. We
+ only use it for validation, so we can free the value
+ as soon as it is returned. */
+ free (RCS_check_kflag (optarg));
+ keyword_opt = optarg;
+ break;
+ case 'W':
+ wrap_add (optarg, 0);
+ break;
+ case '?':
+ default:
+ usage (import_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 3)
+ usage (import_usage);
+
+#ifdef SERVER_SUPPORT
+ /* This is for handling the Checkin-time request. It might seem a
+ bit odd to enable the use_file_modtime code even in the case
+ where Checkin-time was not sent for a particular file. The
+ effect is that we use the time of upload, rather than the time
+ when we call RCS_checkin. Since those times are both during
+ CVS's run, that seems OK, and it is easier to implement than
+ putting the "was Checkin-time sent" flag in CVS/Entries or some
+ such place. */
+
+ if (server_active)
+ use_file_modtime = 1;
+#endif
+
+ for (i = 1; i < argc; i++) /* check the tags for validity */
+ {
+ int j;
+
+ RCS_check_tag (argv[i]);
+ for (j = 1; j < i; j++)
+ if (strcmp (argv[j], argv[i]) == 0)
+ error (1, 0, "tag `%s' was specified more than once", argv[i]);
+ }
+
+ /* XXX - this should be a module, not just a pathname */
+ if (! isabsolute (argv[0])
+ && pathname_levels (argv[0]) == 0)
+ {
+ if (CVSroot_directory == NULL)
+ {
+ error (0, 0, "missing CVSROOT environment variable\n");
+ error (1, 0, "Set it or specify the '-d' option to %s.",
+ program_name);
+ }
+ repository = xmalloc (strlen (CVSroot_directory) + strlen (argv[0])
+ + 10);
+ (void) sprintf (repository, "%s/%s", CVSroot_directory, argv[0]);
+ repos_len = strlen (CVSroot_directory);
+ }
+ else
+ {
+ /* It is somewhere between a security hole and "unexpected" to
+ let the client start mucking around outside the cvsroot
+ (wouldn't get the right CVSROOT configuration, &c). */
+ error (1, 0, "directory %s not relative within the repository",
+ argv[0]);
+ }
+
+ /*
+ * Consistency checks on the specified vendor branch. It must be
+ * composed of only numbers and dots ('.'). Also, for now we only
+ * support branching to a single level, so the specified vendor branch
+ * must only have two dots in it (like "1.1.1").
+ */
+ for (cp = vbranch; *cp != '\0'; cp++)
+ if (!isdigit ((unsigned char) *cp) && *cp != '.')
+ error (1, 0, "%s is not a numeric branch", vbranch);
+ if (numdots (vbranch) != 2)
+ error (1, 0, "Only branches with two dots are supported: %s", vbranch);
+ vhead = xstrdup (vbranch);
+ cp = strrchr (vhead, '.');
+ *cp = '\0';
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* For rationale behind calling start_server before do_editor, see
+ commit.c */
+ start_server ();
+ }
+#endif
+
+ if (use_editor)
+ {
+ do_editor ((char *) NULL, &message, repository,
+ (List *) NULL);
+ }
+ do_verify (message, repository);
+ msglen = message == NULL ? 0 : strlen (message);
+ if (msglen == 0 || message[msglen - 1] != '\n')
+ {
+ char *nm = xmalloc (msglen + 2);
+ *nm = '\0';
+ if (message != NULL)
+ {
+ (void) strcpy (nm, message);
+ free (message);
+ }
+ (void) strcat (nm + msglen, "\n");
+ message = nm;
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int err;
+
+ if (vbranch[0] != '\0')
+ option_with_arg ("-b", vbranch);
+ if (message)
+ option_with_arg ("-m", message);
+ if (keyword_opt != NULL)
+ option_with_arg ("-k", keyword_opt);
+ /* The only ignore processing which takes place on the server side
+ is the CVSROOT/cvsignore file. But if the user specified -I !,
+ the documented behavior is to not process said file. */
+ if (ign_inhibit_server)
+ {
+ send_arg ("-I");
+ send_arg ("!");
+ }
+ wrap_send ();
+
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ }
+
+ logfp = stdin;
+ client_import_setup (repository);
+ err = import_descend (message, argv[1], argc - 2, argv + 2);
+ client_import_done ();
+ send_to_server ("import\012", 0);
+ err += get_responses_and_close ();
+ return err;
+ }
+#endif
+
+ /*
+ * Make all newly created directories writable. Should really use a more
+ * sophisticated security mechanism here.
+ */
+ (void) umask (cvsumask);
+ make_directories (repository);
+
+ /* Create the logfile that will be logged upon completion */
+ tmpfile = cvs_temp_name ();
+ if ((logfp = CVS_FOPEN (tmpfile, "w+")) == NULL)
+ error (1, errno, "cannot create temporary file `%s'", tmpfile);
+ /* On systems where we can unlink an open file, do so, so it will go
+ away no matter how we exit. FIXME-maybe: Should be checking for
+ errors but I'm not sure which error(s) we get if we are on a system
+ where one can't unlink open files. */
+ (void) CVS_UNLINK (tmpfile);
+ (void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]);
+ (void) fprintf (logfp, "Release Tags:\t");
+ for (i = 2; i < argc; i++)
+ (void) fprintf (logfp, "%s\n\t\t", argv[i]);
+ (void) fprintf (logfp, "\n");
+
+ /* Just Do It. */
+ err = import_descend (message, argv[1], argc - 2, argv + 2);
+ if (conflicts)
+ {
+ if (!really_quiet)
+ {
+ char buf[20];
+ char *buf2;
+
+ cvs_output_tagged ("+importmergecmd", NULL);
+ cvs_output_tagged ("newline", NULL);
+ sprintf (buf, "%d", conflicts);
+ cvs_output_tagged ("conflicts", buf);
+ cvs_output_tagged ("text", " conflicts created by this import.");
+ cvs_output_tagged ("newline", NULL);
+ cvs_output_tagged ("text",
+ "Use the following command to help the merge:");
+ cvs_output_tagged ("newline", NULL);
+ cvs_output_tagged ("newline", NULL);
+ cvs_output_tagged ("text", "\t");
+ cvs_output_tagged ("text", program_name);
+ if (CVSroot_cmdline != NULL)
+ {
+ cvs_output_tagged ("text", " -d ");
+ cvs_output_tagged ("text", CVSroot_cmdline);
+ }
+ cvs_output_tagged ("text", " checkout -j");
+ buf2 = xmalloc (strlen (argv[1]) + 20);
+ sprintf (buf2, "%s:yesterday", argv[1]);
+ cvs_output_tagged ("mergetag1", buf2);
+ free (buf2);
+ cvs_output_tagged ("text", " -j");
+ cvs_output_tagged ("mergetag2", argv[1]);
+ cvs_output_tagged ("text", " ");
+ cvs_output_tagged ("repository", argv[0]);
+ cvs_output_tagged ("newline", NULL);
+ cvs_output_tagged ("newline", NULL);
+ cvs_output_tagged ("-importmergecmd", NULL);
+ }
+
+ /* FIXME: I'm not sure whether we need to put this information
+ into the loginfo. If we do, then note that it does not
+ report any required -d option. There is no particularly
+ clean way to tell the server about the -d option used by
+ the client. */
+ (void) fprintf (logfp, "\n%d conflicts created by this import.\n",
+ conflicts);
+ (void) fprintf (logfp,
+ "Use the following command to help the merge:\n\n");
+ (void) fprintf (logfp, "\t%s checkout ", program_name);
+ (void) fprintf (logfp, "-j%s:yesterday -j%s %s\n\n",
+ argv[1], argv[1], argv[0]);
+ }
+ else
+ {
+ if (!really_quiet)
+ cvs_output ("\nNo conflicts created by this import\n\n", 0);
+ (void) fprintf (logfp, "\nNo conflicts created by this import\n\n");
+ }
+
+ /*
+ * Write out the logfile and clean up.
+ */
+ ulist = getlist ();
+ p = getnode ();
+ p->type = UPDATE;
+ p->delproc = update_delproc;
+ p->key = xstrdup ("- Imported sources");
+ li = (struct logfile_info *) xmalloc (sizeof (struct logfile_info));
+ li->type = T_TITLE;
+ li->tag = xstrdup (vbranch);
+ li->rev_old = li->rev_new = NULL;
+ p->data = (char *) li;
+ (void) addnode (ulist, p);
+ Update_Logfile (repository, message, logfp, ulist);
+ dellist (&ulist);
+ if (fclose (logfp) < 0)
+ error (0, errno, "error closing %s", tmpfile);
+
+ /* Make sure the temporary file goes away, even on systems that don't let
+ you delete a file that's in use. */
+ if (CVS_UNLINK (tmpfile) < 0 && !existence_error (errno))
+ error (0, errno, "cannot remove %s", tmpfile);
+ free (tmpfile);
+
+ if (message)
+ free (message);
+ free (repository);
+ free (vbranch);
+ free (vhead);
+
+ return (err);
+}
+
+/* Process all the files in ".", then descend into other directories.
+ Returns 0 for success, or >0 on error (in which case a message
+ will have been printed). */
+static int
+import_descend (message, vtag, targc, targv)
+ char *message;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ DIR *dirp;
+ struct dirent *dp;
+ int err = 0;
+ List *dirlist = NULL;
+
+ /* first, load up any per-directory ignore lists */
+ ign_add_file (CVSDOTIGNORE, 1);
+ wrap_add_file (CVSDOTWRAPPER, 1);
+
+ if ((dirp = CVS_OPENDIR (".")) == NULL)
+ {
+ error (0, errno, "cannot open directory");
+ err++;
+ }
+ else
+ {
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0)
+ goto one_more_time_boys;
+#ifdef SERVER_SUPPORT
+ /* CVS directories are created in the temp directory by
+ server.c because it doesn't special-case import. So
+ don't print a message about them, regardless of -I!. */
+ if (server_active && strcmp (dp->d_name, CVSADM) == 0)
+ goto one_more_time_boys;
+#endif
+ if (ign_name (dp->d_name))
+ {
+ add_log ('I', dp->d_name);
+ goto one_more_time_boys;
+ }
+
+ if (
+#ifdef DT_DIR
+ (dp->d_type == DT_DIR
+ || (dp->d_type == DT_UNKNOWN && isdir (dp->d_name)))
+#else
+ isdir (dp->d_name)
+#endif
+ && !wrap_name_has (dp->d_name, WRAP_TOCVS)
+ )
+ {
+ Node *n;
+
+ if (dirlist == NULL)
+ dirlist = getlist();
+
+ n = getnode();
+ n->key = xstrdup (dp->d_name);
+ addnode(dirlist, n);
+ }
+ else if (
+#ifdef DT_DIR
+ dp->d_type == DT_LNK
+ || (dp->d_type == DT_UNKNOWN && islink (dp->d_name))
+#else
+ islink (dp->d_name)
+#endif
+ )
+ {
+ add_log ('L', dp->d_name);
+ err++;
+ }
+ else
+ {
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ err += client_process_import_file (message, dp->d_name,
+ vtag, targc, targv,
+ repository,
+ keyword_opt != NULL &&
+ keyword_opt[0] == 'b',
+ use_file_modtime);
+ else
+#endif
+ err += process_import_file (message, dp->d_name,
+ vtag, targc, targv);
+ }
+ one_more_time_boys:
+ errno = 0;
+ }
+ if (errno != 0)
+ {
+ error (0, errno, "cannot read directory");
+ ++err;
+ }
+ (void) closedir (dirp);
+ }
+
+ if (dirlist != NULL)
+ {
+ Node *head, *p;
+
+ head = dirlist->list;
+ for (p = head->next; p != head; p = p->next)
+ {
+ err += import_descend_dir (message, p->key, vtag, targc, targv);
+ }
+
+ dellist(&dirlist);
+ }
+
+ return (err);
+}
+
+/*
+ * Process the argument import file.
+ */
+static int
+process_import_file (message, vfile, vtag, targc, targv)
+ char *message;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ char *rcs;
+ int inattic = 0;
+
+ rcs = xmalloc (strlen (repository) + strlen (vfile) + sizeof (RCSEXT)
+ + 5);
+ (void) sprintf (rcs, "%s/%s%s", repository, vfile, RCSEXT);
+ if (!isfile (rcs))
+ {
+ char *attic_name;
+
+ attic_name = xmalloc (strlen (repository) + strlen (vfile) +
+ sizeof (CVSATTIC) + sizeof (RCSEXT) + 10);
+ (void) sprintf (attic_name, "%s/%s/%s%s", repository, CVSATTIC,
+ vfile, RCSEXT);
+ if (!isfile (attic_name))
+ {
+ int retval;
+ char *free_opt = NULL;
+ char *our_opt = keyword_opt;
+
+ free (attic_name);
+ /*
+ * A new import source file; it doesn't exist as a ,v within the
+ * repository nor in the Attic -- create it anew.
+ */
+ add_log ('N', vfile);
+
+#ifdef SERVER_SUPPORT
+ /* The most reliable information on whether the file is binary
+ is what the client told us. That is because if the client had
+ the wrong idea about binaryness, it corrupted the file, so
+ we might as well believe the client. */
+ if (server_active)
+ {
+ Node *node;
+ List *entries;
+
+ /* Reading all the entries for each file is fairly silly, and
+ probably slow. But I am too lazy at the moment to do
+ anything else. */
+ entries = Entries_Open (0, NULL);
+ node = findnode_fn (entries, vfile);
+ if (node != NULL)
+ {
+ Entnode *entdata = (Entnode *) node->data;
+ if (entdata->type == ENT_FILE)
+ {
+ assert (entdata->options[0] == '-'
+ && entdata->options[1] == 'k');
+ our_opt = xstrdup (entdata->options + 2);
+ free_opt = our_opt;
+ }
+ }
+ Entries_Close (entries);
+ }
+#endif
+
+ retval = add_rcs_file (message, rcs, vfile, vhead, our_opt,
+ vbranch, vtag, targc, targv,
+ NULL, 0, logfp);
+ if (free_opt != NULL)
+ free (free_opt);
+ free (rcs);
+ return retval;
+ }
+ free (attic_name);
+ inattic = 1;
+ }
+
+ free (rcs);
+ /*
+ * an rcs file exists. have to do things the official, slow, way.
+ */
+ return (update_rcs_file (message, vfile, vtag, targc, targv, inattic));
+}
+
+/*
+ * The RCS file exists; update it by adding the new import file to the
+ * (possibly already existing) vendor branch.
+ */
+static int
+update_rcs_file (message, vfile, vtag, targc, targv, inattic)
+ char *message;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+ int inattic;
+{
+ Vers_TS *vers;
+ int letter;
+ char *tocvsPath;
+ struct file_info finfo;
+
+ memset (&finfo, 0, sizeof finfo);
+ finfo.file = vfile;
+ /* Not used, so don't worry about it. */
+ finfo.update_dir = NULL;
+ finfo.fullname = finfo.file;
+ finfo.repository = repository;
+ finfo.entries = NULL;
+ finfo.rcs = NULL;
+ vers = Version_TS (&finfo, (char *) NULL, vbranch, (char *) NULL,
+ 1, 0);
+ if (vers->vn_rcs != NULL
+ && !RCS_isdead(vers->srcfile, vers->vn_rcs))
+ {
+ int different;
+
+ /*
+ * The rcs file does have a revision on the vendor branch. Compare
+ * this revision with the import file; if they match exactly, there
+ * is no need to install the new import file as a new revision to the
+ * branch. Just tag the revision with the new import tags.
+ *
+ * This is to try to cut down the number of "C" conflict messages for
+ * locally modified import source files.
+ */
+ tocvsPath = wrap_tocvs_process_file (vfile);
+ /* FIXME: Why don't we pass tocvsPath to RCS_cmp_file if it is
+ not NULL? */
+ different = RCS_cmp_file (vers->srcfile, vers->vn_rcs, "-ko", vfile);
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (!different)
+ {
+ int retval = 0;
+
+ /*
+ * The two files are identical. Just update the tags, print the
+ * "U", signifying that the file has changed, but needs no
+ * attention, and we're done.
+ */
+ if (add_tags (vers->srcfile, vfile, vtag, targc, targv))
+ retval = 1;
+ add_log ('U', vfile);
+ freevers_ts (&vers);
+ return (retval);
+ }
+ }
+
+ /* We may have failed to parse the RCS file; check just in case */
+ if (vers->srcfile == NULL ||
+ add_rev (message, vers->srcfile, vfile, vers->vn_rcs) ||
+ add_tags (vers->srcfile, vfile, vtag, targc, targv))
+ {
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ if (vers->srcfile->branch == NULL || inattic ||
+ strcmp (vers->srcfile->branch, vbranch) != 0)
+ {
+ conflicts++;
+ letter = 'C';
+ }
+ else
+ letter = 'U';
+ add_log (letter, vfile);
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Add the revision to the vendor branch
+ */
+static int
+add_rev (message, rcs, vfile, vers)
+ char *message;
+ RCSNode *rcs;
+ char *vfile;
+ char *vers;
+{
+ int locked, status, ierrno;
+ char *tocvsPath;
+
+ if (noexec)
+ return (0);
+
+ locked = 0;
+ if (vers != NULL)
+ {
+ /* Before RCS_lock existed, we were directing stdout, as well as
+ stderr, from the RCS command, to DEVNULL. I wouldn't guess that
+ was necessary, but I don't know for sure. */
+ /* Earlier versions of this function printed a `fork failed' error
+ when RCS_lock returned an error code. That's not appropriate
+ now that RCS_lock is librarified, but should the error text be
+ preserved? */
+ if (RCS_lock (rcs, vbranch, 1) != 0)
+ return 1;
+ locked = 1;
+ RCS_rewrite (rcs, NULL, NULL);
+ }
+ tocvsPath = wrap_tocvs_process_file (vfile);
+
+ status = RCS_checkin (rcs, tocvsPath == NULL ? vfile : tocvsPath,
+ message, vbranch,
+ (RCS_FLAGS_QUIET | RCS_FLAGS_KEEPFILE
+ | (use_file_modtime ? RCS_FLAGS_MODTIME : 0)));
+ ierrno = errno;
+
+ if ((tocvsPath != NULL) && (unlink_file_dir (tocvsPath) < 0))
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (status)
+ {
+ if (!noexec)
+ {
+ fperrmsg (logfp, 0, status == -1 ? ierrno : 0,
+ "ERROR: Check-in of %s failed", rcs->path);
+ error (0, status == -1 ? ierrno : 0,
+ "ERROR: Check-in of %s failed", rcs->path);
+ }
+ if (locked)
+ {
+ (void) RCS_unlock(rcs, vbranch, 0);
+ RCS_rewrite (rcs, NULL, NULL);
+ }
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Add the vendor branch tag and all the specified import release tags to the
+ * RCS file. The vendor branch tag goes on the branch root (1.1.1) while the
+ * vendor release tags go on the newly added leaf of the branch (1.1.1.1,
+ * 1.1.1.2, ...).
+ */
+static int
+add_tags (rcs, vfile, vtag, targc, targv)
+ RCSNode *rcs;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ int i, ierrno;
+ Vers_TS *vers;
+ int retcode = 0;
+ struct file_info finfo;
+
+ if (noexec)
+ return (0);
+
+ if ((retcode = RCS_settag(rcs, vtag, vbranch)) != 0)
+ {
+ ierrno = errno;
+ fperrmsg (logfp, 0, retcode == -1 ? ierrno : 0,
+ "ERROR: Failed to set tag %s in %s", vtag, rcs->path);
+ error (0, retcode == -1 ? ierrno : 0,
+ "ERROR: Failed to set tag %s in %s", vtag, rcs->path);
+ return (1);
+ }
+ RCS_rewrite (rcs, NULL, NULL);
+
+ memset (&finfo, 0, sizeof finfo);
+ finfo.file = vfile;
+ /* Not used, so don't worry about it. */
+ finfo.update_dir = NULL;
+ finfo.fullname = finfo.file;
+ finfo.repository = repository;
+ finfo.entries = NULL;
+ finfo.rcs = NULL;
+ vers = Version_TS (&finfo, NULL, vtag, NULL, 1, 0);
+ for (i = 0; i < targc; i++)
+ {
+ if ((retcode = RCS_settag (rcs, targv[i], vers->vn_rcs)) == 0)
+ RCS_rewrite (rcs, NULL, NULL);
+ else
+ {
+ ierrno = errno;
+ fperrmsg (logfp, 0, retcode == -1 ? ierrno : 0,
+ "WARNING: Couldn't add tag %s to %s", targv[i],
+ rcs->path);
+ error (0, retcode == -1 ? ierrno : 0,
+ "WARNING: Couldn't add tag %s to %s", targv[i],
+ rcs->path);
+ }
+ }
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Stolen from rcs/src/rcsfnms.c, and adapted/extended.
+ */
+struct compair
+{
+ char *suffix, *comlead;
+};
+
+static const struct compair comtable[] =
+{
+
+/*
+ * comtable pairs each filename suffix with a comment leader. The comment
+ * leader is placed before each line generated by the $Log keyword. This
+ * table is used to guess the proper comment leader from the working file's
+ * suffix during initial ci (see InitAdmin()). Comment leaders are needed for
+ * languages without multiline comments; for others they are optional.
+ *
+ * I believe that the comment leader is unused if you are using RCS 5.7, which
+ * decides what leader to use based on the text surrounding the $Log keyword
+ * rather than a specified comment leader.
+ */
+ {"a", "-- "}, /* Ada */
+ {"ada", "-- "},
+ {"adb", "-- "},
+ {"asm", ";; "}, /* assembler (MS-DOS) */
+ {"ads", "-- "}, /* Ada */
+ {"bas", "' "}, /* Visual Basic code */
+ {"bat", ":: "}, /* batch (MS-DOS) */
+ {"body", "-- "}, /* Ada */
+ {"c", " * "}, /* C */
+ {"c++", "// "}, /* C++ in all its infinite guises */
+ {"cc", "// "},
+ {"cpp", "// "},
+ {"cxx", "// "},
+ {"m", "// "}, /* Objective-C */
+ {"cl", ";;; "}, /* Common Lisp */
+ {"cmd", ":: "}, /* command (OS/2) */
+ {"cmf", "c "}, /* CM Fortran */
+ {"cs", " * "}, /* C* */
+ {"csh", "# "}, /* shell */
+ {"dlg", " * "}, /* MS Windows dialog file */
+ {"e", "# "}, /* efl */
+ {"epsf", "% "}, /* encapsulated postscript */
+ {"epsi", "% "}, /* encapsulated postscript */
+ {"el", "; "}, /* Emacs Lisp */
+ {"f", "c "}, /* Fortran */
+ {"for", "c "},
+ {"frm", "' "}, /* Visual Basic form */
+ {"h", " * "}, /* C-header */
+ {"hh", "// "}, /* C++ header */
+ {"hpp", "// "},
+ {"hxx", "// "},
+ {"in", "# "}, /* for Makefile.in */
+ {"l", " * "}, /* lex (conflict between lex and
+ * franzlisp) */
+ {"mac", ";; "}, /* macro (DEC-10, MS-DOS, PDP-11,
+ * VMS, etc) */
+ {"mak", "# "}, /* makefile, e.g. Visual C++ */
+ {"me", ".\\\" "}, /* me-macros t/nroff */
+ {"ml", "; "}, /* mocklisp */
+ {"mm", ".\\\" "}, /* mm-macros t/nroff */
+ {"ms", ".\\\" "}, /* ms-macros t/nroff */
+ {"man", ".\\\" "}, /* man-macros t/nroff */
+ {"1", ".\\\" "}, /* feeble attempt at man pages... */
+ {"2", ".\\\" "},
+ {"3", ".\\\" "},
+ {"4", ".\\\" "},
+ {"5", ".\\\" "},
+ {"6", ".\\\" "},
+ {"7", ".\\\" "},
+ {"8", ".\\\" "},
+ {"9", ".\\\" "},
+ {"p", " * "}, /* pascal */
+ {"pas", " * "},
+ {"pl", "# "}, /* perl (conflict with Prolog) */
+ {"ps", "% "}, /* postscript */
+ {"psw", "% "}, /* postscript wrap */
+ {"pswm", "% "}, /* postscript wrap */
+ {"r", "# "}, /* ratfor */
+ {"rc", " * "}, /* Microsoft Windows resource file */
+ {"red", "% "}, /* psl/rlisp */
+#ifdef sparc
+ {"s", "! "}, /* assembler */
+#endif
+#ifdef mc68000
+ {"s", "| "}, /* assembler */
+#endif
+#ifdef pdp11
+ {"s", "/ "}, /* assembler */
+#endif
+#ifdef vax
+ {"s", "# "}, /* assembler */
+#endif
+#ifdef __ksr__
+ {"s", "# "}, /* assembler */
+ {"S", "# "}, /* Macro assembler */
+#endif
+ {"sh", "# "}, /* shell */
+ {"sl", "% "}, /* psl */
+ {"spec", "-- "}, /* Ada */
+ {"tex", "% "}, /* tex */
+ {"y", " * "}, /* yacc */
+ {"ye", " * "}, /* yacc-efl */
+ {"yr", " * "}, /* yacc-ratfor */
+ {"", "# "}, /* default for empty suffix */
+ {NULL, "# "} /* default for unknown suffix; */
+/* must always be last */
+};
+
+static char *
+get_comment (user)
+ char *user;
+{
+ char *cp, *suffix;
+ char *suffix_path;
+ int i;
+ char *retval;
+
+ suffix_path = xmalloc (strlen (user) + 5);
+ cp = strrchr (user, '.');
+ if (cp != NULL)
+ {
+ cp++;
+
+ /*
+ * Convert to lower-case, since we are not concerned about the
+ * case-ness of the suffix.
+ */
+ (void) strcpy (suffix_path, cp);
+ for (cp = suffix_path; *cp; cp++)
+ if (isupper ((unsigned char) *cp))
+ *cp = tolower (*cp);
+ suffix = suffix_path;
+ }
+ else
+ suffix = ""; /* will use the default */
+ for (i = 0;; i++)
+ {
+ if (comtable[i].suffix == NULL)
+ {
+ /* Default. Note we'll always hit this case before we
+ ever return NULL. */
+ retval = comtable[i].comlead;
+ break;
+ }
+ if (strcmp (suffix, comtable[i].suffix) == 0)
+ {
+ retval = comtable[i].comlead;
+ break;
+ }
+ }
+ free (suffix_path);
+ return retval;
+}
+
+/* Create a new RCS file from scratch.
+
+ This probably should be moved to rcs.c now that it is called from
+ places outside import.c.
+
+ Return value is 0 for success, or nonzero for failure (in which
+ case an error message will have already been printed). */
+int
+add_rcs_file (message, rcs, user, add_vhead, key_opt,
+ add_vbranch, vtag, targc, targv,
+ desctext, desclen, add_logfp)
+ /* Log message for the addition. Not used if add_vhead == NULL. */
+ char *message;
+ /* Filename of the RCS file to create. */
+ char *rcs;
+ /* Filename of the file to serve as the contents of the initial
+ revision. Even if add_vhead is NULL, we use this to determine
+ the modes to give the new RCS file. */
+ char *user;
+
+ /* Revision number of head that we are adding. Normally 1.1 but
+ could be another revision as long as ADD_VBRANCH is a branch
+ from it. If NULL, then just add an empty file without any
+ revisions (similar to the one created by "rcs -i"). */
+ char *add_vhead;
+
+ /* Keyword expansion mode, e.g., "b" for binary. NULL means the
+ default behavior. */
+ char *key_opt;
+
+ /* Vendor branch to import to, or NULL if none. If non-NULL, then
+ vtag should also be non-NULL. */
+ char *add_vbranch;
+ char *vtag;
+ int targc;
+ char *targv[];
+
+ /* If non-NULL, description for the file. If NULL, the description
+ will be empty. */
+ char *desctext;
+ size_t desclen;
+
+ /* Write errors to here as well as via error (), or NULL if we should
+ use only error (). */
+ FILE *add_logfp;
+{
+ FILE *fprcs, *fpuser;
+ struct stat sb;
+ struct tm *ftm;
+ time_t now;
+ char altdate1[MAXDATELEN];
+ char *author;
+ int i, ierrno, err = 0;
+ mode_t mode;
+ char *tocvsPath;
+ char *userfile;
+ char *local_opt = key_opt;
+ char *free_opt = NULL;
+ mode_t file_type;
+
+ if (noexec)
+ return (0);
+
+ /* Note that as the code stands now, the -k option overrides any
+ settings in wrappers (whether CVSROOT/cvswrappers, -W, or
+ whatever). Some have suggested this should be the other way
+ around. As far as I know the documentation doesn't say one way
+ or the other. Before making a change of this sort, should think
+ about what is best, document it (in cvs.texinfo and NEWS), &c. */
+
+ if (local_opt == NULL)
+ {
+ if (wrap_name_has (user, WRAP_RCSOPTION))
+ {
+ local_opt = free_opt = wrap_rcsoption (user, 0);
+ }
+ }
+
+ tocvsPath = wrap_tocvs_process_file (user);
+ userfile = (tocvsPath == NULL ? user : tocvsPath);
+
+ /* Opening in text mode is probably never the right thing for the
+ server (because the protocol encodes text files in a fashion
+ which does not depend on what the client or server OS is, as
+ documented in cvsclient.texi), but as long as the server just
+ runs on unix it is a moot point. */
+
+ /* If PreservePermissions is set, then make sure that the file
+ is a plain file before trying to open it. Longstanding (although
+ often unpopular) CVS behavior has been to follow symlinks, so we
+ maintain that behavior if PreservePermissions is not on.
+
+ NOTE: this error message used to be `cannot fstat', but is now
+ `cannot lstat'. I don't see a way around this, since we must
+ stat the file before opening it. -twp */
+
+ if (CVS_LSTAT (userfile, &sb) < 0)
+ error (1, errno, "cannot lstat %s", userfile);
+ file_type = sb.st_mode & S_IFMT;
+
+ fpuser = NULL;
+ if (!preserve_perms || file_type == S_IFREG)
+ {
+ fpuser = CVS_FOPEN (userfile,
+ ((local_opt != NULL && strcmp (local_opt, "b") == 0)
+ ? "rb"
+ : "r")
+ );
+ if (fpuser == NULL)
+ {
+ /* not fatal, continue import */
+ if (add_logfp != NULL)
+ fperrmsg (add_logfp, 0, errno,
+ "ERROR: cannot read file %s", userfile);
+ error (0, errno, "ERROR: cannot read file %s", userfile);
+ goto read_error;
+ }
+ }
+
+ fprcs = CVS_FOPEN (rcs, "w+b");
+ if (fprcs == NULL)
+ {
+ ierrno = errno;
+ goto write_error_noclose;
+ }
+
+ /*
+ * putadmin()
+ */
+ if (add_vhead != NULL)
+ {
+ if (fprintf (fprcs, "head %s;\012", add_vhead) < 0)
+ goto write_error;
+ }
+ else
+ {
+ if (fprintf (fprcs, "head ;\012") < 0)
+ goto write_error;
+ }
+
+ if (add_vbranch != NULL)
+ {
+ if (fprintf (fprcs, "branch %s;\012", add_vbranch) < 0)
+ goto write_error;
+ }
+ if (fprintf (fprcs, "access ;\012") < 0 ||
+ fprintf (fprcs, "symbols ") < 0)
+ {
+ goto write_error;
+ }
+
+ for (i = targc - 1; i >= 0; i--)
+ {
+ /* RCS writes the symbols backwards */
+ assert (add_vbranch != NULL);
+ if (fprintf (fprcs, "%s:%s.1 ", targv[i], add_vbranch) < 0)
+ goto write_error;
+ }
+
+ if (add_vbranch != NULL)
+ {
+ if (fprintf (fprcs, "%s:%s", vtag, add_vbranch) < 0)
+ goto write_error;
+ }
+ if (fprintf (fprcs, ";\012") < 0)
+ goto write_error;
+
+ if (fprintf (fprcs, "locks ; strict;\012") < 0 ||
+ /* XXX - make sure @@ processing works in the RCS file */
+ fprintf (fprcs, "comment @%s@;\012", get_comment (user)) < 0)
+ {
+ goto write_error;
+ }
+
+ if (local_opt != NULL)
+ {
+ if (fprintf (fprcs, "expand @%s@;\012", local_opt) < 0)
+ {
+ goto write_error;
+ }
+ }
+
+ if (fprintf (fprcs, "\012") < 0)
+ goto write_error;
+
+ /* Write the revision(s), with the date and author and so on
+ (that is "delta" rather than "deltatext" from rcsfile(5)). */
+ if (add_vhead != NULL)
+ {
+ if (use_file_modtime)
+ now = sb.st_mtime;
+ else
+ (void) time (&now);
+ ftm = gmtime (&now);
+ (void) sprintf (altdate1, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+ author = getcaller ();
+
+ if (fprintf (fprcs, "\012%s\012", add_vhead) < 0 ||
+ fprintf (fprcs, "date %s; author %s; state Exp;\012",
+ altdate1, author) < 0)
+ goto write_error;
+
+ if (fprintf (fprcs, "branches") < 0)
+ goto write_error;
+ if (add_vbranch != NULL)
+ {
+ if (fprintf (fprcs, " %s.1", add_vbranch) < 0)
+ goto write_error;
+ }
+ if (fprintf (fprcs, ";\012") < 0)
+ goto write_error;
+
+ if (fprintf (fprcs, "next ;\012") < 0)
+ goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Store initial permissions if necessary. */
+ if (preserve_perms)
+ {
+ if (file_type == S_IFLNK)
+ {
+ char *link = xreadlink (userfile);
+ if (fprintf (fprcs, "symlink\t@") < 0 ||
+ expand_at_signs (link, strlen (link), fprcs) < 0 ||
+ fprintf (fprcs, "@;\012") < 0)
+ goto write_error;
+ free (link);
+ }
+ else
+ {
+ if (fprintf (fprcs, "owner\t%u;\012", sb.st_uid) < 0)
+ goto write_error;
+ if (fprintf (fprcs, "group\t%u;\012", sb.st_gid) < 0)
+ goto write_error;
+ if (fprintf (fprcs, "permissions\t%o;\012",
+ sb.st_mode & 07777) < 0)
+ goto write_error;
+ switch (file_type)
+ {
+ case S_IFREG: break;
+ case S_IFCHR:
+ case S_IFBLK:
+ if (fprintf (fprcs, "special\t%s %lu;\012",
+ (file_type == S_IFCHR
+ ? "character"
+ : "block"),
+ (unsigned long) sb.st_rdev) < 0)
+ goto write_error;
+ break;
+ default:
+ error (0, 0,
+ "can't import %s: unknown kind of special file",
+ userfile);
+ }
+ }
+ }
+#endif
+
+ if (add_vbranch != NULL)
+ {
+ if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 ||
+ fprintf (fprcs, "date %s; author %s; state Exp;\012",
+ altdate1, author) < 0 ||
+ fprintf (fprcs, "branches ;\012") < 0 ||
+ fprintf (fprcs, "next ;\012") < 0)
+ goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Store initial permissions if necessary. */
+ if (preserve_perms)
+ {
+ if (file_type == S_IFLNK)
+ {
+ char *link = xreadlink (userfile);
+ if (fprintf (fprcs, "symlink\t@") < 0 ||
+ expand_at_signs (link, strlen (link), fprcs) < 0 ||
+ fprintf (fprcs, "@;\012") < 0)
+ goto write_error;
+ free (link);
+ }
+ else
+ {
+ if (fprintf (fprcs, "owner\t%u;\012", sb.st_uid) < 0 ||
+ fprintf (fprcs, "group\t%u;\012", sb.st_gid) < 0 ||
+ fprintf (fprcs, "permissions\t%o;\012",
+ sb.st_mode & 07777) < 0)
+ goto write_error;
+
+ switch (file_type)
+ {
+ case S_IFREG: break;
+ case S_IFCHR:
+ case S_IFBLK:
+ if (fprintf (fprcs, "special\t%s %lu;\012",
+ (file_type == S_IFCHR
+ ? "character"
+ : "block"),
+ (unsigned long) sb.st_rdev) < 0)
+ goto write_error;
+ break;
+ default:
+ error (0, 0,
+ "cannot import %s: special file of unknown type",
+ userfile);
+ }
+ }
+ }
+#endif
+
+ if (fprintf (fprcs, "\012") < 0)
+ goto write_error;
+ }
+ }
+
+ /* Now write the description (possibly empty). */
+ if (fprintf (fprcs, "\012desc\012") < 0 ||
+ fprintf (fprcs, "@") < 0)
+ goto write_error;
+ if (desctext != NULL)
+ {
+ /* The use of off_t not size_t for the second argument is very
+ strange, since we are dealing with something which definitely
+ fits in memory. */
+ if (expand_at_signs (desctext, (off_t) desclen, fprcs) < 0)
+ goto write_error;
+ }
+ if (fprintf (fprcs, "@\012\012\012") < 0)
+ goto write_error;
+
+ /* Now write the log messages and contents for the revision(s) (that
+ is, "deltatext" rather than "delta" from rcsfile(5)). */
+ if (add_vhead != NULL)
+ {
+ if (fprintf (fprcs, "\012%s\012", add_vhead) < 0 ||
+ fprintf (fprcs, "log\012@") < 0)
+ goto write_error;
+ if (add_vbranch != NULL)
+ {
+ /* We are going to put the log message in the revision on the
+ branch. So putting it here too seems kind of redundant, I
+ guess (and that is what CVS has always done, anyway). */
+ if (fprintf (fprcs, "Initial revision\012") < 0)
+ goto write_error;
+ }
+ else
+ {
+ if (expand_at_signs (message, (off_t) strlen (message), fprcs) < 0)
+ goto write_error;
+ }
+ if (fprintf (fprcs, "@\012") < 0 ||
+ fprintf (fprcs, "text\012@") < 0)
+ {
+ goto write_error;
+ }
+
+ /* Now copy over the contents of the file, expanding at signs.
+ If preserve_perms is set, do this only for regular files. */
+ if (!preserve_perms || file_type == S_IFREG)
+ {
+ char buf[8192];
+ unsigned int len;
+
+ while (1)
+ {
+ len = fread (buf, 1, sizeof buf, fpuser);
+ if (len == 0)
+ {
+ if (ferror (fpuser))
+ error (1, errno, "cannot read file %s for copying",
+ user);
+ break;
+ }
+ if (expand_at_signs (buf, len, fprcs) < 0)
+ goto write_error;
+ }
+ }
+ if (fprintf (fprcs, "@\012\012") < 0)
+ goto write_error;
+ if (add_vbranch != NULL)
+ {
+ if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 ||
+ fprintf (fprcs, "log\012@") < 0 ||
+ expand_at_signs (message,
+ (off_t) strlen (message), fprcs) < 0 ||
+ fprintf (fprcs, "@\012text\012") < 0 ||
+ fprintf (fprcs, "@@\012") < 0)
+ goto write_error;
+ }
+ }
+
+ if (fclose (fprcs) == EOF)
+ {
+ ierrno = errno;
+ goto write_error_noclose;
+ }
+ /* Close fpuser only if we opened it to begin with. */
+ if (fpuser != NULL)
+ {
+ if (fclose (fpuser) < 0)
+ error (0, errno, "cannot close %s", user);
+ }
+
+ /*
+ * Fix the modes on the RCS files. The user modes of the original
+ * user file are propagated to the group and other modes as allowed
+ * by the repository umask, except that all write permissions are
+ * turned off.
+ */
+ mode = (sb.st_mode |
+ (sb.st_mode & S_IRWXU) >> 3 |
+ (sb.st_mode & S_IRWXU) >> 6) &
+ ~cvsumask &
+ ~(S_IWRITE | S_IWGRP | S_IWOTH);
+ if (chmod (rcs, mode) < 0)
+ {
+ ierrno = errno;
+ if (add_logfp != NULL)
+ fperrmsg (add_logfp, 0, ierrno,
+ "WARNING: cannot change mode of file %s", rcs);
+ error (0, ierrno, "WARNING: cannot change mode of file %s", rcs);
+ err++;
+ }
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+ if (free_opt != NULL)
+ free (free_opt);
+ return (err);
+
+write_error:
+ ierrno = errno;
+ if (fclose (fprcs) < 0)
+ error (0, errno, "cannot close %s", rcs);
+write_error_noclose:
+ if (fclose (fpuser) < 0)
+ error (0, errno, "cannot close %s", user);
+ if (add_logfp != NULL)
+ fperrmsg (add_logfp, 0, ierrno, "ERROR: cannot write file %s", rcs);
+ error (0, ierrno, "ERROR: cannot write file %s", rcs);
+ if (ierrno == ENOSPC)
+ {
+ if (CVS_UNLINK (rcs) < 0)
+ error (0, errno, "cannot remove %s", rcs);
+ if (add_logfp != NULL)
+ fperrmsg (add_logfp, 0, 0, "ERROR: out of space - aborting");
+ error (1, 0, "ERROR: out of space - aborting");
+ }
+read_error:
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (free_opt != NULL)
+ free (free_opt);
+
+ return (err + 1);
+}
+
+/*
+ * Write SIZE bytes at BUF to FP, expanding @ signs into double @
+ * signs. If an error occurs, return a negative value and set errno
+ * to indicate the error. If not, return a nonnegative value.
+ */
+int
+expand_at_signs (buf, size, fp)
+ char *buf;
+ off_t size;
+ FILE *fp;
+{
+ register char *cp, *next;
+
+ cp = buf;
+ while ((next = memchr (cp, '@', size)) != NULL)
+ {
+ int len;
+
+ ++next;
+ len = next - cp;
+ if (fwrite (cp, 1, len, fp) != len)
+ return EOF;
+ if (putc ('@', fp) == EOF)
+ return EOF;
+ cp = next;
+ size -= len;
+ }
+
+ if (fwrite (cp, 1, size, fp) != size)
+ return EOF;
+
+ return 1;
+}
+
+/*
+ * Write an update message to (potentially) the screen and the log file.
+ */
+static void
+add_log (ch, fname)
+ int ch;
+ char *fname;
+{
+ if (!really_quiet) /* write to terminal */
+ {
+ char buf[2];
+ buf[0] = ch;
+ buf[1] = ' ';
+ cvs_output (buf, 2);
+ if (repos_len)
+ {
+ cvs_output (repository + repos_len + 1, 0);
+ cvs_output ("/", 1);
+ }
+ else if (repository[0] != '\0')
+ {
+ cvs_output (repository, 0);
+ cvs_output ("/", 1);
+ }
+ cvs_output (fname, 0);
+ cvs_output ("\n", 1);
+ }
+
+ if (repos_len) /* write to logfile */
+ (void) fprintf (logfp, "%c %s/%s\n", ch,
+ repository + repos_len + 1, fname);
+ else if (repository[0])
+ (void) fprintf (logfp, "%c %s/%s\n", ch, repository, fname);
+ else
+ (void) fprintf (logfp, "%c %s\n", ch, fname);
+}
+
+/*
+ * This is the recursive function that walks the argument directory looking
+ * for sub-directories that have CVS administration files in them and updates
+ * them recursively.
+ *
+ * Note that we do not follow symbolic links here, which is a feature!
+ */
+static int
+import_descend_dir (message, dir, vtag, targc, targv)
+ char *message;
+ char *dir;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ struct saved_cwd cwd;
+ char *cp;
+ int ierrno, err;
+ char *rcs = NULL;
+
+ if (islink (dir))
+ return (0);
+ if (save_cwd (&cwd))
+ {
+ fperrmsg (logfp, 0, 0, "ERROR: cannot get working directory");
+ return (1);
+ }
+
+ /* Concatenate DIR to the end of REPOSITORY. */
+ if (repository[0] == '\0')
+ {
+ char *new = xstrdup (dir);
+ free (repository);
+ repository = new;
+ }
+ else
+ {
+ char *new = xmalloc (strlen (repository) + strlen (dir) + 10);
+ strcpy (new, repository);
+ (void) strcat (new, "/");
+ (void) strcat (new, dir);
+ free (repository);
+ repository = new;
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (!quiet && !client_active)
+#else
+ if (!quiet)
+#endif
+ error (0, 0, "Importing %s", repository);
+
+ if ( CVS_CHDIR (dir) < 0)
+ {
+ ierrno = errno;
+ fperrmsg (logfp, 0, ierrno, "ERROR: cannot chdir to %s", repository);
+ error (0, ierrno, "ERROR: cannot chdir to %s", repository);
+ err = 1;
+ goto out;
+ }
+#ifdef CLIENT_SUPPORT
+ if (!client_active && !isdir (repository))
+#else
+ if (!isdir (repository))
+#endif
+ {
+ rcs = xmalloc (strlen (repository) + sizeof (RCSEXT) + 5);
+ (void) sprintf (rcs, "%s%s", repository, RCSEXT);
+ if (isfile (repository) || isfile(rcs))
+ {
+ fperrmsg (logfp, 0, 0,
+ "ERROR: %s is a file, should be a directory!",
+ repository);
+ error (0, 0, "ERROR: %s is a file, should be a directory!",
+ repository);
+ err = 1;
+ goto out;
+ }
+ if (noexec == 0 && CVS_MKDIR (repository, 0777) < 0)
+ {
+ ierrno = errno;
+ fperrmsg (logfp, 0, ierrno,
+ "ERROR: cannot mkdir %s -- not added", repository);
+ error (0, ierrno,
+ "ERROR: cannot mkdir %s -- not added", repository);
+ err = 1;
+ goto out;
+ }
+ }
+ err = import_descend (message, vtag, targc, targv);
+ out:
+ if (rcs != NULL)
+ free (rcs);
+ if ((cp = strrchr (repository, '/')) != NULL)
+ *cp = '\0';
+ else
+ repository[0] = '\0';
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+ return (err);
+}
diff --git a/gnu/dist/cvs/src/lock.c b/gnu/dist/cvs/src/lock.c
new file mode 100644
index 00000000000..ef27d114ba1
--- /dev/null
+++ b/gnu/dist/cvs/src/lock.c
@@ -0,0 +1,941 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Set Lock
+ *
+ * Lock file support for CVS.
+ */
+
+/* The node Concurrency in doc/cvs.texinfo has a brief introduction to
+ how CVS locks function, and some of the user-visible consequences of
+ their existence. Here is a summary of why they exist (and therefore,
+ the consequences of hacking CVS to read a repository without creating
+ locks):
+
+ There are two uses. One is the ability to prevent there from being
+ two writers at the same time. This is necessary for any number of
+ reasons (fileattr code, probably others). Commit needs to lock the
+ whole tree so that nothing happens between the up-to-date check and
+ the actual checkin.
+
+ The second use is the ability to ensure that there is not a writer
+ and a reader at the same time (several readers are allowed). Reasons
+ for this are:
+
+ * Readlocks ensure that once CVS has found a collection of rcs
+ files using Find_Names, the files will still exist when it reads
+ them (they may have moved in or out of the attic).
+
+ * Readlocks provide some modicum of consistency, although this is
+ kind of limited--see the node Concurrency in cvs.texinfo.
+
+ * Readlocks ensure that the RCS file does not change between
+ RCS_parse and RCS_reparsercsfile time. This one strikes me as
+ important, although I haven't thought up what bad scenarios might
+ be.
+
+ * Readlocks ensure that we won't find the file in the state in
+ which it is in between the calls to add_rcs_file and RCS_checkin in
+ commit.c (when a file is being added). This state is a state in
+ which the RCS file parsing routines in rcs.c cannot parse the file.
+
+ * Readlocks ensure that a reader won't try to look at a
+ half-written fileattr file (fileattr is not updated atomically).
+
+ (see also the description of anonymous read-only access in
+ "Password authentication security" node in doc/cvs.texinfo).
+
+ While I'm here, I'll try to summarize a few random suggestions
+ which periodically get made about how locks might be different:
+
+ 1. Check for EROFS. Maybe useful, although in the presence of NFS
+ EROFS does *not* mean that the file system is unchanging.
+
+ 2. Provide a means to put the cvs locks in some directory apart from
+ the repository (CVSROOT/locks; a -l option in modules; etc.).
+
+ 3. Provide an option to disable locks for operations which only
+ read (see above for some of the consequences).
+
+ 4. Have a server internally do the locking. Probably a good
+ long-term solution, and many people have been working hard on code
+ changes which would eventually make it possible to have a server
+ which can handle various connections in one process, but there is
+ much, much work still to be done before this is feasible.
+
+ 5. Like #4 but use shared memory or something so that the servers
+ merely need to all be on the same machine. This is a much smaller
+ change to CVS (it functions much like #2; shared memory might be an
+ unneeded complication although it presumably would be faster). */
+
+#include "cvs.h"
+#include <assert.h>
+
+struct lock {
+ /* This is the directory in which we may have a lock named by the
+ readlock variable, a lock named by the writelock variable, and/or
+ a lock named CVSLCK. The storage is not allocated along with the
+ struct lock; it is allocated by the Reader_Lock caller or in the
+ case of writelocks, it is just a pointer to the storage allocated
+ for the ->key field. */
+ char *repository;
+ /* Do we have a lock named CVSLCK? */
+ int have_lckdir;
+ /* Note there is no way of knowing whether the readlock and writelock
+ exist. The code which sets the locks doesn't use SIG_beginCrSect
+ to set a flag like we do for CVSLCK. */
+};
+
+static void remove_locks PROTO((void));
+static int readers_exist PROTO((char *repository));
+static int set_lock PROTO ((struct lock *lock, int will_wait));
+static void clear_lock PROTO ((struct lock *lock));
+static void set_lockers_name PROTO((struct stat *statp));
+static int set_writelock_proc PROTO((Node * p, void *closure));
+static int unlock_proc PROTO((Node * p, void *closure));
+static int write_lock PROTO ((struct lock *lock));
+static void lock_simple_remove PROTO ((struct lock *lock));
+static void lock_wait PROTO((char *repository));
+static void lock_obtained PROTO((char *repository));
+
+/* Malloc'd array containing the username of the whoever has the lock.
+ Will always be non-NULL in the cases where it is needed. */
+static char *lockers_name;
+/* Malloc'd array specifying name of a readlock within a directory.
+ Or NULL if none. */
+static char *readlock;
+/* Malloc'd array specifying name of a writelock within a directory.
+ Or NULL if none. */
+static char *writelock;
+/* Malloc'd array specifying the name of a CVSLCK file (absolute pathname).
+ Will always be non-NULL in the cases where it is used. */
+static char *masterlock;
+static List *locklist;
+
+#define L_OK 0 /* success */
+#define L_ERROR 1 /* error condition */
+#define L_LOCKED 2 /* lock owned by someone else */
+
+/* This is the (single) readlock which is set by Reader_Lock. The
+ repository field is NULL if there is no such lock. */
+static struct lock global_readlock;
+
+/* List of locks set by lock_tree_for_write. This is redundant
+ with locklist, sort of. */
+static List *lock_tree_list;
+
+/* If we set locks with lock_dir_for_write, then locked_dir contains
+ the malloc'd name of the repository directory which we have locked.
+ locked_list is the same thing packaged into a list and is redundant
+ with locklist the same way that lock_tree_list is. */
+static char *locked_dir;
+static List *locked_list;
+
+/* LockDir from CVSROOT/config. */
+char *lock_dir;
+
+static char *lock_name PROTO ((char *repository, char *name));
+
+/* Return a newly malloc'd string containing the name of the lock for the
+ repository REPOSITORY and the lock file name within that directory
+ NAME. Also create the directories in which to put the lock file
+ if needed (if we need to, could save system call(s) by doing
+ that only if the actual operation fails. But for now we'll keep
+ things simple). */
+static char *
+lock_name (repository, name)
+ char *repository;
+ char *name;
+{
+ char *retval;
+ char *p;
+ char *q;
+ char *short_repos;
+ mode_t save_umask;
+ int saved_umask = 0;
+
+ if (lock_dir == NULL)
+ {
+ /* This is the easy case. Because the lock files go directly
+ in the repository, no need to create directories or anything. */
+ retval = xmalloc (strlen (repository) + strlen (name) + 10);
+ (void) sprintf (retval, "%s/%s", repository, name);
+ }
+ else
+ {
+ struct stat sb;
+ mode_t new_mode = 0;
+
+ /* The interesting part of the repository is the part relative
+ to CVSROOT. */
+ assert (CVSroot_directory != NULL);
+ assert (strncmp (repository, CVSroot_directory,
+ strlen (CVSroot_directory)) == 0);
+ short_repos = repository + strlen (CVSroot_directory) + 1;
+
+ if (strcmp (repository, CVSroot_directory) == 0)
+ short_repos = ".";
+ else
+ assert (short_repos[-1] == '/');
+
+ retval = xmalloc (strlen (lock_dir)
+ + strlen (short_repos)
+ + strlen (name)
+ + 10);
+ strcpy (retval, lock_dir);
+ q = retval + strlen (retval);
+ *q++ = '/';
+
+ strcpy (q, short_repos);
+
+ /* In the common case, where the directory already exists, let's
+ keep it to one system call. */
+ if (CVS_STAT (retval, &sb) < 0)
+ {
+ /* If we need to be creating more than one directory, we'll
+ get the existence_error here. */
+ if (!existence_error (errno))
+ error (1, errno, "cannot stat directory %s", retval);
+ }
+ else
+ {
+ if (S_ISDIR (sb.st_mode))
+ goto created;
+ else
+ error (1, 0, "%s is not a directory", retval);
+ }
+
+ /* Now add the directories one at a time, so we can create
+ them if needed.
+
+ The idea behind the new_mode stuff is that the directory we
+ end up creating will inherit permissions from its parent
+ directory (we re-set new_mode with each EEXIST). CVSUMASK
+ isn't right, because typically the reason for LockDir is to
+ use a different set of permissions. We probably want to
+ inherit group ownership also (but we don't try to deal with
+ that, some systems do it for us either always or when g+s is on).
+
+ We don't try to do anything about the permissions on the lock
+ files themselves. The permissions don't really matter so much
+ because the locks will generally be removed by the process
+ which created them. */
+
+ if (CVS_STAT (lock_dir, &sb) < 0)
+ error (1, errno, "cannot stat %s", lock_dir);
+ new_mode = sb.st_mode;
+ save_umask = umask (0000);
+ saved_umask = 1;
+
+ p = short_repos;
+ while (1)
+ {
+ while (!ISDIRSEP (*p) && *p != '\0')
+ ++p;
+ if (ISDIRSEP (*p))
+ {
+ strncpy (q, short_repos, p - short_repos);
+ q[p - short_repos] = '\0';
+ if (!ISDIRSEP (q[p - short_repos - 1])
+ && CVS_MKDIR (retval, new_mode) < 0)
+ {
+ int saved_errno = errno;
+ if (saved_errno != EEXIST)
+ error (1, errno, "cannot make directory %s", retval);
+ else
+ {
+ if (CVS_STAT (retval, &sb) < 0)
+ error (1, errno, "cannot stat %s", retval);
+ new_mode = sb.st_mode;
+ }
+ }
+ ++p;
+ }
+ else
+ {
+ strcpy (q, short_repos);
+ if (CVS_MKDIR (retval, new_mode) < 0
+ && errno != EEXIST)
+ error (1, errno, "cannot make directory %s", retval);
+ goto created;
+ }
+ }
+ created:;
+
+ strcat (retval, "/");
+ strcat (retval, name);
+
+ if (saved_umask)
+ {
+ assert (umask (save_umask) == 0000);
+ saved_umask = 0;
+ }
+ }
+ return retval;
+}
+
+/*
+ * Clean up all outstanding locks
+ */
+void
+Lock_Cleanup ()
+{
+ /* FIXME: error handling here is kind of bogus; we sometimes will call
+ error, which in turn can call us again. For the moment work around
+ this by refusing to reenter this function (this is a kludge). */
+ /* FIXME-reentrancy: the workaround isn't reentrant. */
+ static int in_lock_cleanup = 0;
+
+ if (in_lock_cleanup)
+ return;
+ in_lock_cleanup = 1;
+
+ remove_locks ();
+
+ dellist (&lock_tree_list);
+
+ if (locked_dir != NULL)
+ {
+ dellist (&locked_list);
+ free (locked_dir);
+ locked_dir = NULL;
+ locked_list = NULL;
+ }
+ in_lock_cleanup = 0;
+}
+
+/*
+ * Remove locks without discarding the lock information
+ */
+static void
+remove_locks ()
+{
+ /* clean up simple locks (if any) */
+ if (global_readlock.repository != NULL)
+ {
+ lock_simple_remove (&global_readlock);
+ global_readlock.repository = NULL;
+ }
+
+ /* clean up multiple locks (if any) */
+ if (locklist != (List *) NULL)
+ {
+ (void) walklist (locklist, unlock_proc, NULL);
+ locklist = (List *) NULL;
+ }
+}
+
+/*
+ * walklist proc for removing a list of locks
+ */
+static int
+unlock_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ lock_simple_remove ((struct lock *)p->data);
+ return (0);
+}
+
+/* Remove the lock files. */
+static void
+lock_simple_remove (lock)
+ struct lock *lock;
+{
+ char *tmp;
+
+ /* If readlock is set, the lock directory *might* have been created, but
+ since Reader_Lock doesn't use SIG_beginCrSect the way that set_lock
+ does, we don't know that. That is why we need to check for
+ existence_error here. */
+ if (readlock != NULL)
+ {
+ tmp = lock_name (lock->repository, readlock);
+ if ( CVS_UNLINK (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+ free (tmp);
+ }
+
+ /* If writelock is set, the lock directory *might* have been created, but
+ since write_lock doesn't use SIG_beginCrSect the way that set_lock
+ does, we don't know that. That is why we need to check for
+ existence_error here. */
+ if (writelock != NULL)
+ {
+ tmp = lock_name (lock->repository, writelock);
+ if ( CVS_UNLINK (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+ free (tmp);
+ }
+
+ if (lock->have_lckdir)
+ {
+ tmp = lock_name (lock->repository, CVSLCK);
+ SIG_beginCrSect ();
+ if (CVS_RMDIR (tmp) < 0)
+ error (0, errno, "failed to remove lock dir %s", tmp);
+ lock->have_lckdir = 0;
+ SIG_endCrSect ();
+ free (tmp);
+ }
+}
+
+/*
+ * Create a lock file for readers
+ */
+int
+Reader_Lock (xrepository)
+ char *xrepository;
+{
+ int err = 0;
+ FILE *fp;
+ char *tmp;
+
+ if (noexec)
+ return (0);
+
+ /* we only do one directory at a time for read locks! */
+ if (global_readlock.repository != NULL)
+ {
+ error (0, 0, "Reader_Lock called while read locks set - Help!");
+ return (1);
+ }
+
+ if (readlock == NULL)
+ {
+ readlock = xmalloc (strlen (hostname) + sizeof (CVSRFL) + 40);
+ (void) sprintf (readlock,
+#ifdef HAVE_LONG_FILE_NAMES
+ "%s.%s.%ld", CVSRFL, hostname,
+#else
+ "%s.%ld", CVSRFL,
+#endif
+ (long) getpid ());
+ }
+
+ /* remember what we're locking (for Lock_Cleanup) */
+ global_readlock.repository = xrepository;
+
+ /* get the lock dir for our own */
+ if (set_lock (&global_readlock, 1) != L_OK)
+ {
+ error (0, 0, "failed to obtain dir lock in repository `%s'",
+ xrepository);
+ if (readlock != NULL)
+ free (readlock);
+ readlock = NULL;
+ /* We don't set global_readlock.repository to NULL. I think this
+ only works because recurse.c will give a fatal error if we return
+ a nonzero value. */
+ return (1);
+ }
+
+ /* write a read-lock */
+ tmp = lock_name (xrepository, readlock);
+ if ((fp = CVS_FOPEN (tmp, "w+")) == NULL || fclose (fp) == EOF)
+ {
+ error (0, errno, "cannot create read lock in repository `%s'",
+ xrepository);
+ if (readlock != NULL)
+ free (readlock);
+ readlock = NULL;
+ err = 1;
+ }
+ free (tmp);
+
+ /* free the lock dir */
+ clear_lock (&global_readlock);
+
+ return (err);
+}
+
+/*
+ * Lock a list of directories for writing
+ */
+static char *lock_error_repos;
+static int lock_error;
+
+static int Writer_Lock PROTO ((List * list));
+
+static int
+Writer_Lock (list)
+ List *list;
+{
+ char *wait_repos;
+
+ if (noexec)
+ return (0);
+
+ /* We only know how to do one list at a time */
+ if (locklist != (List *) NULL)
+ {
+ error (0, 0, "Writer_Lock called while write locks set - Help!");
+ return (1);
+ }
+
+ wait_repos = NULL;
+ for (;;)
+ {
+ /* try to lock everything on the list */
+ lock_error = L_OK; /* init for set_writelock_proc */
+ lock_error_repos = (char *) NULL; /* init for set_writelock_proc */
+ locklist = list; /* init for Lock_Cleanup */
+ if (lockers_name != NULL)
+ free (lockers_name);
+ lockers_name = xstrdup ("unknown");
+
+ (void) walklist (list, set_writelock_proc, NULL);
+
+ switch (lock_error)
+ {
+ case L_ERROR: /* Real Error */
+ if (wait_repos != NULL)
+ free (wait_repos);
+ Lock_Cleanup (); /* clean up any locks we set */
+ error (0, 0, "lock failed - giving up");
+ return (1);
+
+ case L_LOCKED: /* Someone already had a lock */
+ remove_locks (); /* clean up any locks we set */
+ lock_wait (lock_error_repos); /* sleep a while and try again */
+ wait_repos = xstrdup (lock_error_repos);
+ continue;
+
+ case L_OK: /* we got the locks set */
+ if (wait_repos != NULL)
+ {
+ lock_obtained (wait_repos);
+ free (wait_repos);
+ }
+ return (0);
+
+ default:
+ if (wait_repos != NULL)
+ free (wait_repos);
+ error (0, 0, "unknown lock status %d in Writer_Lock",
+ lock_error);
+ return (1);
+ }
+ }
+}
+
+/*
+ * walklist proc for setting write locks
+ */
+static int
+set_writelock_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ /* if some lock was not OK, just skip this one */
+ if (lock_error != L_OK)
+ return (0);
+
+ /* apply the write lock */
+ lock_error_repos = p->key;
+ lock_error = write_lock ((struct lock *)p->data);
+ return (0);
+}
+
+/*
+ * Create a lock file for writers returns L_OK if lock set ok, L_LOCKED if
+ * lock held by someone else or L_ERROR if an error occurred
+ */
+static int
+write_lock (lock)
+ struct lock *lock;
+{
+ int status;
+ FILE *fp;
+ char *tmp;
+
+ if (writelock == NULL)
+ {
+ writelock = xmalloc (strlen (hostname) + sizeof (CVSWFL) + 40);
+ (void) sprintf (writelock,
+#ifdef HAVE_LONG_FILE_NAMES
+ "%s.%s.%ld", CVSWFL, hostname,
+#else
+ "%s.%ld", CVSWFL,
+#endif
+ (long) getpid());
+ }
+
+ /* make sure the lock dir is ours (not necessarily unique to us!) */
+ status = set_lock (lock, 0);
+ if (status == L_OK)
+ {
+ /* we now own a writer - make sure there are no readers */
+ if (readers_exist (lock->repository))
+ {
+ /* clean up the lock dir if we created it */
+ if (status == L_OK)
+ {
+ clear_lock (lock);
+ }
+
+ /* indicate we failed due to read locks instead of error */
+ return (L_LOCKED);
+ }
+
+ /* write the write-lock file */
+ tmp = lock_name (lock->repository, writelock);
+ if ((fp = CVS_FOPEN (tmp, "w+")) == NULL || fclose (fp) == EOF)
+ {
+ int xerrno = errno;
+
+ if ( CVS_UNLINK (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+
+ /* free the lock dir if we created it */
+ if (status == L_OK)
+ {
+ clear_lock (lock);
+ }
+
+ /* return the error */
+ error (0, xerrno, "cannot create write lock in repository `%s'",
+ lock->repository);
+ free (tmp);
+ return (L_ERROR);
+ }
+ free (tmp);
+ return (L_OK);
+ }
+ else
+ return (status);
+}
+
+/*
+ * readers_exist() returns 0 if there are no reader lock files remaining in
+ * the repository; else 1 is returned, to indicate that the caller should
+ * sleep a while and try again.
+ */
+static int
+readers_exist (repository)
+ char *repository;
+{
+ char *line;
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ int ret = 0;
+
+#ifdef CVS_FUDGELOCKS
+again:
+#endif
+
+ if ((dirp = CVS_OPENDIR (repository)) == NULL)
+ error (1, 0, "cannot open directory %s", repository);
+
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (CVS_FNMATCH (CVSRFLPAT, dp->d_name, 0) == 0)
+ {
+#ifdef CVS_FUDGELOCKS
+ time_t now;
+ (void) time (&now);
+#endif
+
+ line = xmalloc (strlen (repository) + strlen (dp->d_name) + 5);
+ (void) sprintf (line, "%s/%s", repository, dp->d_name);
+ if ( CVS_STAT (line, &sb) != -1)
+ {
+#ifdef CVS_FUDGELOCKS
+ /*
+ * If the create time of the file is more than CVSLCKAGE
+ * seconds ago, try to clean-up the lock file, and if
+ * successful, re-open the directory and try again.
+ */
+ if (now >= (sb.st_ctime + CVSLCKAGE) && CVS_UNLINK (line) != -1)
+ {
+ (void) closedir (dirp);
+ free (line);
+ goto again;
+ }
+#endif
+ set_lockers_name (&sb);
+ }
+ else
+ {
+ /* If the file doesn't exist, it just means that it disappeared
+ between the time we did the readdir and the time we did
+ the stat. */
+ if (!existence_error (errno))
+ error (0, errno, "cannot stat %s", line);
+ }
+ errno = 0;
+ free (line);
+
+ ret = 1;
+ break;
+ }
+ errno = 0;
+ }
+ if (errno != 0)
+ error (0, errno, "error reading directory %s", repository);
+
+ closedir (dirp);
+ return (ret);
+}
+
+/*
+ * Set the static variable lockers_name appropriately, based on the stat
+ * structure passed in.
+ */
+static void
+set_lockers_name (statp)
+ struct stat *statp;
+{
+ struct passwd *pw;
+
+ if (lockers_name != NULL)
+ free (lockers_name);
+ if ((pw = (struct passwd *) getpwuid (statp->st_uid)) !=
+ (struct passwd *) NULL)
+ {
+ lockers_name = xstrdup (pw->pw_name);
+ }
+ else
+ {
+ lockers_name = xmalloc (20);
+ (void) sprintf (lockers_name, "uid%lu", (unsigned long) statp->st_uid);
+ }
+}
+
+/*
+ * Persistently tries to make the directory "lckdir",, which serves as a
+ * lock. If the create time on the directory is greater than CVSLCKAGE
+ * seconds old, just try to remove the directory.
+ */
+static int
+set_lock (lock, will_wait)
+ struct lock *lock;
+ int will_wait;
+{
+ int waited;
+ struct stat sb;
+ mode_t omask;
+#ifdef CVS_FUDGELOCKS
+ time_t now;
+#endif
+
+ if (masterlock != NULL)
+ free (masterlock);
+ masterlock = lock_name (lock->repository, CVSLCK);
+
+ /*
+ * Note that it is up to the callers of set_lock() to arrange for signal
+ * handlers that do the appropriate things, like remove the lock
+ * directory before they exit.
+ */
+ waited = 0;
+ lock->have_lckdir = 0;
+ for (;;)
+ {
+ int status = -1;
+ omask = umask (cvsumask);
+ SIG_beginCrSect ();
+ if (CVS_MKDIR (masterlock, 0777) == 0)
+ {
+ lock->have_lckdir = 1;
+ SIG_endCrSect ();
+ status = L_OK;
+ if (waited)
+ lock_obtained (lock->repository);
+ goto out;
+ }
+ SIG_endCrSect ();
+ out:
+ (void) umask (omask);
+ if (status != -1)
+ return status;
+
+ if (errno != EEXIST)
+ {
+ error (0, errno,
+ "failed to create lock directory in repository `%s'",
+ lock->repository);
+ return (L_ERROR);
+ }
+
+ /* Find out who owns the lock. If the lock directory is
+ non-existent, re-try the loop since someone probably just
+ removed it (thus releasing the lock). */
+ if (CVS_STAT (masterlock, &sb) < 0)
+ {
+ if (existence_error (errno))
+ continue;
+
+ error (0, errno, "couldn't stat lock directory `%s'", masterlock);
+ return (L_ERROR);
+ }
+
+#ifdef CVS_FUDGELOCKS
+ /*
+ * If the create time of the directory is more than CVSLCKAGE seconds
+ * ago, try to clean-up the lock directory, and if successful, just
+ * quietly retry to make it.
+ */
+ (void) time (&now);
+ if (now >= (sb.st_ctime + CVSLCKAGE))
+ {
+ if (CVS_RMDIR (masterlock) >= 0)
+ continue;
+ }
+#endif
+
+ /* set the lockers name */
+ set_lockers_name (&sb);
+
+ /* if he wasn't willing to wait, return an error */
+ if (!will_wait)
+ return (L_LOCKED);
+ lock_wait (lock->repository);
+ waited = 1;
+ }
+}
+
+/*
+ * Clear master lock. We don't have to recompute the lock name since
+ * clear_lock is never called except after a successful set_lock().
+ */
+static void
+clear_lock (lock)
+ struct lock *lock;
+{
+ SIG_beginCrSect ();
+ if (CVS_RMDIR (masterlock) < 0)
+ error (0, errno, "failed to remove lock dir `%s'", masterlock);
+ lock->have_lckdir = 0;
+ SIG_endCrSect ();
+}
+
+/*
+ * Print out a message that the lock is still held, then sleep a while.
+ */
+static void
+lock_wait (repos)
+ char *repos;
+{
+ time_t now;
+ char *msg;
+
+ (void) time (&now);
+ msg = xmalloc (100 + strlen (lockers_name) + strlen (repos));
+ sprintf (msg, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11,
+ lockers_name, repos);
+ error (0, 0, "%s", msg);
+ /* Call cvs_flusherr to ensure that the user sees this message as
+ soon as possible. */
+ cvs_flusherr ();
+ free (msg);
+ (void) sleep (CVSLCKSLEEP);
+}
+
+/*
+ * Print out a message when we obtain a lock.
+ */
+static void
+lock_obtained (repos)
+ char *repos;
+{
+ time_t now;
+ char *msg;
+
+ (void) time (&now);
+ msg = xmalloc (100 + strlen (repos));
+ sprintf (msg, "[%8.8s] obtained lock in %s", ctime (&now) + 11, repos);
+ error (0, 0, "%s", msg);
+ /* Call cvs_flusherr to ensure that the user sees this message as
+ soon as possible. */
+ cvs_flusherr ();
+ free (msg);
+}
+
+static int lock_filesdoneproc PROTO ((void *callerdat, int err,
+ char *repository, char *update_dir,
+ List *entries));
+
+/*
+ * Create a list of repositories to lock
+ */
+/* ARGSUSED */
+static int
+lock_filesdoneproc (callerdat, err, repository, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repository;
+ char *update_dir;
+ List *entries;
+{
+ Node *p;
+
+ p = getnode ();
+ p->type = LOCK;
+ p->key = xstrdup (repository);
+ p->data = xmalloc (sizeof (struct lock));
+ ((struct lock *)p->data)->repository = p->key;
+ ((struct lock *)p->data)->have_lckdir = 0;
+
+ /* FIXME-KRP: this error condition should not simply be passed by. */
+ if (p->key == NULL || addnode (lock_tree_list, p) != 0)
+ freenode (p);
+ return (err);
+}
+
+void
+lock_tree_for_write (argc, argv, local, aflag)
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+{
+ int err;
+ /*
+ * Run the recursion processor to find all the dirs to lock and lock all
+ * the dirs
+ */
+ lock_tree_list = getlist ();
+ err = start_recursion ((FILEPROC) NULL, lock_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc,
+ argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0);
+ sortlist (lock_tree_list, fsortcmp);
+ if (Writer_Lock (lock_tree_list) != 0)
+ error (1, 0, "lock failed - giving up");
+}
+
+/* Lock a single directory in REPOSITORY. It is OK to call this if
+ a lock has been set with lock_dir_for_write; the new lock will replace
+ the old one. If REPOSITORY is NULL, don't do anything. */
+void
+lock_dir_for_write (repository)
+ char *repository;
+{
+ if (repository != NULL
+ && (locked_dir == NULL
+ || strcmp (locked_dir, repository) != 0))
+ {
+ Node *node;
+
+ if (locked_dir != NULL)
+ Lock_Cleanup ();
+
+ locked_dir = xstrdup (repository);
+ locked_list = getlist ();
+ node = getnode ();
+ node->type = LOCK;
+ node->key = xstrdup (repository);
+ node->data = xmalloc (sizeof (struct lock));
+ ((struct lock *)node->data)->repository = node->key;
+ ((struct lock *)node->data)->have_lckdir = 0;
+
+ (void) addnode (locked_list, node);
+ Writer_Lock (locked_list);
+ }
+}
diff --git a/gnu/dist/cvs/src/log.c b/gnu/dist/cvs/src/log.c
new file mode 100644
index 00000000000..81eba23aec4
--- /dev/null
+++ b/gnu/dist/cvs/src/log.c
@@ -0,0 +1,1466 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Print Log Information
+ *
+ * Prints the RCS "log" (rlog) information for the specified files. With no
+ * argument, prints the log information for all the files in the directory
+ * (recursive by default).
+ */
+
+#include "cvs.h"
+
+/* This structure holds information parsed from the -r option. */
+
+struct option_revlist
+{
+ /* The next -r option. */
+ struct option_revlist *next;
+ /* The first revision to print. This is NULL if the range is
+ :rev, or if no revision is given. */
+ char *first;
+ /* The last revision to print. This is NULL if the range is rev:,
+ or if no revision is given. If there is no colon, first and
+ last are the same. */
+ char *last;
+ /* Nonzero if there was a trailing `.', which means to print only
+ the head revision of a branch. */
+ int branchhead;
+};
+
+/* This structure holds information derived from option_revlist given
+ a particular RCS file. */
+
+struct revlist
+{
+ /* The next pair. */
+ struct revlist *next;
+ /* The first numeric revision to print. */
+ char *first;
+ /* The last numeric revision to print. */
+ char *last;
+ /* The number of fields in these revisions (one more than
+ numdots). */
+ int fields;
+};
+
+/* This structure holds information parsed from the -d option. */
+
+struct datelist
+{
+ /* The next date. */
+ struct datelist *next;
+ /* The starting date. */
+ char *start;
+ /* The ending date. */
+ char *end;
+ /* Nonzero if the range is inclusive rather than exclusive. */
+ int inclusive;
+};
+
+/* This structure is used to pass information through start_recursion. */
+struct log_data
+{
+ /* Nonzero if the -R option was given, meaning that only the name
+ of the RCS file should be printed. */
+ int nameonly;
+ /* Nonzero if the -h option was given, meaning that only header
+ information should be printed. */
+ int header;
+ /* Nonzero if the -t option was given, meaning that only the
+ header and the descriptive text should be printed. */
+ int long_header;
+ /* Nonzero if the -N option was seen, meaning that tag information
+ should not be printed. */
+ int notags;
+ /* Nonzero if the -b option was seen, meaning that only revisions
+ on the default branch should be printed. */
+ int default_branch;
+ /* If not NULL, the value given for the -r option, which lists
+ sets of revisions to be printed. */
+ struct option_revlist *revlist;
+ /* If not NULL, the date pairs given for the -d option, which
+ select date ranges to print. */
+ struct datelist *datelist;
+ /* If not NULL, the single dates given for the -d option, which
+ select specific revisions to print based on a date. */
+ struct datelist *singledatelist;
+ /* If not NULL, the list of states given for the -s option, which
+ only prints revisions of given states. */
+ List *statelist;
+ /* If not NULL, the list of login names given for the -w option,
+ which only prints revisions checked in by given users. */
+ List *authorlist;
+};
+
+/* This structure is used to pass information through walklist. */
+struct log_data_and_rcs
+{
+ struct log_data *log_data;
+ struct revlist *revlist;
+ RCSNode *rcs;
+};
+
+static Dtype log_dirproc PROTO ((void *callerdat, char *dir,
+ char *repository, char *update_dir,
+ List *entries));
+static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static struct option_revlist *log_parse_revlist PROTO ((const char *));
+static void log_parse_date PROTO ((struct log_data *, const char *));
+static void log_parse_list PROTO ((List **, const char *));
+static struct revlist *log_expand_revlist PROTO ((RCSNode *,
+ struct option_revlist *,
+ int));
+static void log_free_revlist PROTO ((struct revlist *));
+static int log_version_requested PROTO ((struct log_data *, struct revlist *,
+ RCSNode *, RCSVers *));
+static int log_symbol PROTO ((Node *, void *));
+static int log_count PROTO ((Node *, void *));
+static int log_fix_singledate PROTO ((Node *, void *));
+static int log_count_print PROTO ((Node *, void *));
+static void log_tree PROTO ((struct log_data *, struct revlist *,
+ RCSNode *, const char *));
+static void log_abranch PROTO ((struct log_data *, struct revlist *,
+ RCSNode *, const char *));
+static void log_version PROTO ((struct log_data *, struct revlist *,
+ RCSNode *, RCSVers *, int));
+static int log_branch PROTO ((Node *, void *));
+static int version_compare PROTO ((const char *, const char *, int));
+
+static const char *const log_usage[] =
+{
+ "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
+ " [-w[logins]] [files...]\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ "\t-R\tOnly print name of RCS file.\n",
+ "\t-h\tOnly print header.\n",
+ "\t-t\tOnly print header and descriptive text.\n",
+ "\t-N\tDo not list tags.\n",
+ "\t-b\tOnly list revisions on the default branch.\n",
+ "\t-r[revisions]\tSpecify revision(s)s to list.\n",
+ "\t-d dates\tSpecify dates (D1<D2 for range, D for latest before).\n",
+ "\t-s states\tOnly list revisions with specified states.\n",
+ "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+/* Helper function for send_arg_list. */
+static int send_one PROTO ((Node *, void *));
+
+static int
+send_one (node, closure)
+ Node *node;
+ void *closure;
+{
+ char *option = (char *) closure;
+
+ send_to_server ("Argument ", 0);
+ send_to_server (option, 0);
+ if (strcmp (node->key, "@@MYSELF") == 0)
+ /* It is a bare -w option. Note that we must send it as
+ -w rather than messing with getcaller() or something (which on
+ the client will return garbage). */
+ ;
+ else
+ send_to_server (node->key, 0);
+ send_to_server ("\012", 0);
+ return 0;
+}
+
+/* For each element in ARG, send an argument consisting of OPTION
+ concatenated with that element. */
+static void send_arg_list PROTO ((char *, List *));
+
+static void
+send_arg_list (option, arg)
+ char *option;
+ List *arg;
+{
+ if (arg == NULL)
+ return;
+ walklist (arg, send_one, (void *)option);
+}
+
+int
+cvslog (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+ int local = 0;
+ struct log_data log_data;
+ struct option_revlist *rl, **prl;
+
+ if (argc == -1)
+ usage (log_usage);
+
+ memset (&log_data, 0, sizeof log_data);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+bd:hlNRr::s:tw::")) != -1)
+ {
+ switch (c)
+ {
+ case 'b':
+ log_data.default_branch = 1;
+ break;
+ case 'd':
+ log_parse_date (&log_data, optarg);
+ break;
+ case 'h':
+ log_data.header = 1;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'N':
+ log_data.notags = 1;
+ break;
+ case 'R':
+ log_data.nameonly = 1;
+ break;
+ case 'r':
+ rl = log_parse_revlist (optarg);
+ for (prl = &log_data.revlist;
+ *prl != NULL;
+ prl = &(*prl)->next)
+ ;
+ *prl = rl;
+ break;
+ case 's':
+ log_parse_list (&log_data.statelist, optarg);
+ break;
+ case 't':
+ log_data.long_header = 1;
+ break;
+ case 'w':
+ if (optarg != NULL)
+ log_parse_list (&log_data.authorlist, optarg);
+ else
+ log_parse_list (&log_data.authorlist, "@@MYSELF");
+ break;
+ case '?':
+ default:
+ usage (log_usage);
+ break;
+ }
+ }
+
+ wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ struct datelist *p;
+ struct option_revlist *rp;
+ char datetmp[MAXDATELEN];
+
+ /* We're the local client. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (log_data.default_branch)
+ send_arg ("-b");
+
+ for (p = log_data.datelist; p != NULL; p = p->next)
+ {
+ send_to_server ("Argument -d\012", 0);
+ send_to_server ("Argument ", 0);
+ date_to_internet (datetmp, p->start);
+ send_to_server (datetmp, 0);
+ if (p->inclusive)
+ send_to_server ("<=", 0);
+ else
+ send_to_server ("<", 0);
+ date_to_internet (datetmp, p->end);
+ send_to_server (datetmp, 0);
+ send_to_server ("\012", 0);
+ }
+ for (p = log_data.singledatelist; p != NULL; p = p->next)
+ {
+ send_to_server ("Argument -d\012", 0);
+ send_to_server ("Argument ", 0);
+ date_to_internet (datetmp, p->end);
+ send_to_server (datetmp, 0);
+ send_to_server ("\012", 0);
+ }
+
+ if (log_data.header)
+ send_arg ("-h");
+ if (local)
+ send_arg("-l");
+ if (log_data.notags)
+ send_arg("-N");
+ if (log_data.nameonly)
+ send_arg("-R");
+ if (log_data.long_header)
+ send_arg("-t");
+
+ for (rp = log_data.revlist; rp != NULL; rp = rp->next)
+ {
+ send_to_server ("Argument -r", 0);
+ if (rp->branchhead)
+ {
+ if (rp->first != NULL)
+ send_to_server (rp->first, 0);
+ send_to_server (".", 1);
+ }
+ else
+ {
+ if (rp->first != NULL)
+ send_to_server (rp->first, 0);
+ send_to_server (":", 1);
+ if (rp->last != NULL)
+ send_to_server (rp->last, 0);
+ }
+ send_to_server ("\012", 0);
+ }
+ send_arg_list ("-s", log_data.statelist);
+ send_arg_list ("-w", log_data.authorlist);
+
+ send_files (argc - optind, argv + optind, local, 0, SEND_NO_CONTENTS);
+ send_file_names (argc - optind, argv + optind, SEND_EXPAND_WILD);
+
+ send_to_server ("log\012", 0);
+ err = get_responses_and_close ();
+ return err;
+ }
+#endif
+
+ /* OK, now that we know we are local/server, we can resolve @@MYSELF
+ into our user name. */
+ if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
+ log_parse_list (&log_data.authorlist, getcaller ());
+
+ err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
+ (DIRLEAVEPROC) NULL, (void *) &log_data,
+ argc - optind, argv + optind, local,
+ W_LOCAL | W_REPOS | W_ATTIC, 0, 1,
+ (char *) NULL, 1);
+ return (err);
+}
+
+/*
+ * Parse a revision list specification.
+ */
+
+static struct option_revlist *
+log_parse_revlist (argstring)
+ const char *argstring;
+{
+ char *copy;
+ struct option_revlist *ret, **pr;
+
+ /* Unfortunately, rlog accepts -r without an argument to mean that
+ latest revision on the default branch, so we must support that
+ for compatibility. */
+ if (argstring == NULL)
+ argstring = "";
+
+ ret = NULL;
+ pr = &ret;
+
+ /* Copy the argument into memory so that we can change it. We
+ don't want to change the argument because, at least as of this
+ writing, we will use it if we send the arguments to the server.
+ We never bother to free up our copy. */
+ copy = xstrdup (argstring);
+ while (copy != NULL)
+ {
+ char *comma;
+ struct option_revlist *r;
+
+ comma = strchr (copy, ',');
+ if (comma != NULL)
+ *comma++ = '\0';
+
+ r = (struct option_revlist *) xmalloc (sizeof *r);
+ r->next = NULL;
+ r->first = copy;
+ r->branchhead = 0;
+ r->last = strchr (copy, ':');
+ if (r->last != NULL)
+ *r->last++ = '\0';
+ else
+ {
+ r->last = r->first;
+ if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
+ {
+ r->branchhead = 1;
+ r->first[strlen (r->first) - 1] = '\0';
+ }
+ }
+
+ if (*r->first == '\0')
+ r->first = NULL;
+ if (*r->last == '\0')
+ r->last = NULL;
+
+ *pr = r;
+ pr = &r->next;
+
+ copy = comma;
+ }
+
+ return ret;
+}
+
+/*
+ * Parse a date specification.
+ */
+static void
+log_parse_date (log_data, argstring)
+ struct log_data *log_data;
+ const char *argstring;
+{
+ char *orig_copy, *copy;
+
+ /* Copy the argument into memory so that we can change it. We
+ don't want to change the argument because, at least as of this
+ writing, we will use it if we send the arguments to the server. */
+ copy = xstrdup (argstring);
+ orig_copy = copy;
+ while (copy != NULL)
+ {
+ struct datelist *nd, **pd;
+ char *cpend, *cp, *ds, *de;
+
+ nd = (struct datelist *) xmalloc (sizeof *nd);
+
+ cpend = strchr (copy, ';');
+ if (cpend != NULL)
+ *cpend++ = '\0';
+
+ pd = &log_data->datelist;
+ nd->inclusive = 0;
+
+ if ((cp = strchr (copy, '>')) != NULL)
+ {
+ *cp++ = '\0';
+ if (*cp == '=')
+ {
+ ++cp;
+ nd->inclusive = 1;
+ }
+ ds = cp;
+ de = copy;
+ }
+ else if ((cp = strchr (copy, '<')) != NULL)
+ {
+ *cp++ = '\0';
+ if (*cp == '=')
+ {
+ ++cp;
+ nd->inclusive = 1;
+ }
+ ds = copy;
+ de = cp;
+ }
+ else
+ {
+ ds = NULL;
+ de = copy;
+ pd = &log_data->singledatelist;
+ }
+
+ if (ds == NULL)
+ nd->start = NULL;
+ else if (*ds != '\0')
+ nd->start = Make_Date (ds);
+ else
+ {
+ /* 1970 was the beginning of time, as far as get_date and
+ Make_Date are concerned. FIXME: That is true only if time_t
+ is a POSIX-style time and there is nothing in ANSI that
+ mandates that. It would be cleaner to set a flag saying
+ whether or not there is a start date. */
+ nd->start = Make_Date ("1/1/1970 UTC");
+ }
+
+ if (*de != '\0')
+ nd->end = Make_Date (de);
+ else
+ {
+ /* We want to set the end date to some time sufficiently far
+ in the future to pick up all revisions that have been
+ created since the specified date and the time `cvs log'
+ completes. FIXME: The date in question only makes sense
+ if time_t is a POSIX-style time and it is 32 bits
+ and signed. We should instead be setting a flag saying
+ whether or not there is an end date. Note that using
+ something like "next week" would break the testsuite (and,
+ perhaps less importantly, loses if the clock is set grossly
+ wrong). */
+ nd->end = Make_Date ("2038-01-01");
+ }
+
+ nd->next = *pd;
+ *pd = nd;
+
+ copy = cpend;
+ }
+
+ free (orig_copy);
+}
+
+/*
+ * Parse a comma separated list of items, and add each one to *PLIST.
+ */
+static void
+log_parse_list (plist, argstring)
+ List **plist;
+ const char *argstring;
+{
+ while (1)
+ {
+ Node *p;
+ char *cp;
+
+ p = getnode ();
+
+ cp = strchr (argstring, ',');
+ if (cp == NULL)
+ p->key = xstrdup (argstring);
+ else
+ {
+ size_t len;
+
+ len = cp - argstring;
+ p->key = xmalloc (len + 1);
+ strncpy (p->key, argstring, len);
+ p->key[len + 1] = '\0';
+ }
+
+ if (*plist == NULL)
+ *plist = getlist ();
+ if (addnode (*plist, p) != 0)
+ freenode (p);
+
+ if (cp == NULL)
+ break;
+
+ argstring = cp + 1;
+ }
+}
+
+static int printlock_proc PROTO ((Node *, void *));
+
+static int
+printlock_proc (lock, foo)
+ Node *lock;
+ void *foo;
+{
+ cvs_output ("\n\t", 2);
+ cvs_output (lock->data, 0);
+ cvs_output (": ", 2);
+ cvs_output (lock->key, 0);
+ return 0;
+}
+
+/*
+ * Do an rlog on a file
+ */
+static int
+log_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ struct log_data *log_data = (struct log_data *) callerdat;
+ Node *p;
+ RCSNode *rcsfile;
+ char buf[50];
+ struct revlist *revlist;
+ struct log_data_and_rcs log_data_and_rcs;
+
+ if ((rcsfile = finfo->rcs) == NULL)
+ {
+ /* no rcs file. What *do* we know about this file? */
+ p = findnode (finfo->entries, finfo->file);
+ if (p != NULL)
+ {
+ Entnode *e;
+
+ e = (Entnode *) p->data;
+ if (e->version[0] == '0' && e->version[1] == '\0')
+ {
+ if (!really_quiet)
+ error (0, 0, "%s has been added, but not committed",
+ finfo->file);
+ return(0);
+ }
+ }
+
+ if (!really_quiet)
+ error (0, 0, "nothing known about %s", finfo->file);
+
+ return (1);
+ }
+
+ if (log_data->nameonly)
+ {
+ cvs_output (rcsfile->path, 0);
+ cvs_output ("\n", 1);
+ return 0;
+ }
+
+ /* We will need all the information in the RCS file. */
+ RCS_fully_parse (rcsfile);
+
+ /* Turn any symbolic revisions in the revision list into numeric
+ revisions. */
+ revlist = log_expand_revlist (rcsfile, log_data->revlist,
+ log_data->default_branch);
+
+ /* The output here is intended to be exactly compatible with the
+ output of rlog. I'm not sure whether this code should be here
+ or in rcs.c; I put it here because it is specific to the log
+ function, even though it uses information gathered by the
+ functions in rcs.c. */
+
+ cvs_output ("\n", 1);
+
+ cvs_output ("RCS file: ", 0);
+ cvs_output (rcsfile->path, 0);
+
+ cvs_output ("\nWorking file: ", 0);
+ if (finfo->update_dir[0] == '\0')
+ cvs_output (finfo->file, 0);
+ else
+ {
+ cvs_output (finfo->update_dir, 0);
+ cvs_output ("/", 0);
+ cvs_output (finfo->file, 0);
+
+ }
+
+ cvs_output ("\nhead:", 0);
+ if (rcsfile->head != NULL)
+ {
+ cvs_output (" ", 1);
+ cvs_output (rcsfile->head, 0);
+ }
+
+ cvs_output ("\nbranch:", 0);
+ if (rcsfile->branch != NULL)
+ {
+ cvs_output (" ", 1);
+ cvs_output (rcsfile->branch, 0);
+ }
+
+ cvs_output ("\nlocks:", 0);
+ if (rcsfile->strict_locks)
+ cvs_output (" strict", 0);
+ walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
+
+ cvs_output ("\naccess list:", 0);
+ if (rcsfile->access != NULL)
+ {
+ const char *cp;
+
+ cp = rcsfile->access;
+ while (*cp != '\0')
+ {
+ const char *cp2;
+
+ cvs_output ("\n\t", 2);
+ cp2 = cp;
+ while (! isspace ((unsigned char) *cp2) && *cp2 != '\0')
+ ++cp2;
+ cvs_output (cp, cp2 - cp);
+ cp = cp2;
+ while (isspace ((unsigned char) *cp) && *cp != '\0')
+ ++cp;
+ }
+ }
+
+ if (! log_data->notags)
+ {
+ List *syms;
+
+ cvs_output ("\nsymbolic names:", 0);
+ syms = RCS_symbols (rcsfile);
+ walklist (syms, log_symbol, NULL);
+ }
+
+ cvs_output ("\nkeyword substitution: ", 0);
+ if (rcsfile->expand == NULL)
+ cvs_output ("kv", 2);
+ else
+ cvs_output (rcsfile->expand, 0);
+
+ cvs_output ("\ntotal revisions: ", 0);
+ sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
+ cvs_output (buf, 0);
+
+ if (! log_data->header && ! log_data->long_header)
+ {
+ cvs_output (";\tselected revisions: ", 0);
+
+ log_data_and_rcs.log_data = log_data;
+ log_data_and_rcs.revlist = revlist;
+ log_data_and_rcs.rcs = rcsfile;
+
+ /* If any single dates were specified, we need to identify the
+ revisions they select. Each one selects the single
+ revision, which is otherwise selected, of that date or
+ earlier. The log_fix_singledate routine will fill in the
+ start date for each specific revision. */
+ if (log_data->singledatelist != NULL)
+ walklist (rcsfile->versions, log_fix_singledate,
+ (void *) &log_data_and_rcs);
+
+ sprintf (buf, "%d", walklist (rcsfile->versions, log_count_print,
+ (void *) &log_data_and_rcs));
+ cvs_output (buf, 0);
+ }
+
+ cvs_output ("\n", 1);
+
+ if (! log_data->header || log_data->long_header)
+ {
+ cvs_output ("description:\n", 0);
+ if (rcsfile->desc != NULL)
+ cvs_output (rcsfile->desc, 0);
+ }
+
+ if (! log_data->header && ! log_data->long_header && rcsfile->head != NULL)
+ {
+ p = findnode (rcsfile->versions, rcsfile->head);
+ if (p == NULL)
+ error (1, 0, "can not find head revision in `%s'",
+ finfo->fullname);
+ while (p != NULL)
+ {
+ RCSVers *vers;
+
+ vers = (RCSVers *) p->data;
+ log_version (log_data, revlist, rcsfile, vers, 1);
+ if (vers->next == NULL)
+ p = NULL;
+ else
+ {
+ p = findnode (rcsfile->versions, vers->next);
+ if (p == NULL)
+ error (1, 0, "can not find next revision `%s' in `%s'",
+ vers->next, finfo->fullname);
+ }
+ }
+
+ log_tree (log_data, revlist, rcsfile, rcsfile->head);
+ }
+
+ cvs_output("\
+=============================================================================\n",
+ 0);
+
+ /* Free up the new revlist and restore the old one. */
+ log_free_revlist (revlist);
+
+ /* If singledatelist is not NULL, free up the start dates we added
+ to it. */
+ if (log_data->singledatelist != NULL)
+ {
+ struct datelist *d;
+
+ for (d = log_data->singledatelist; d != NULL; d = d->next)
+ {
+ if (d->start != NULL)
+ free (d->start);
+ d->start = NULL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Fix up a revision list in order to compare it against versions.
+ * Expand any symbolic revisions.
+ */
+static struct revlist *
+log_expand_revlist (rcs, revlist, default_branch)
+ RCSNode *rcs;
+ struct option_revlist *revlist;
+ int default_branch;
+{
+ struct option_revlist *r;
+ struct revlist *ret, **pr;
+
+ ret = NULL;
+ pr = &ret;
+ for (r = revlist; r != NULL; r = r->next)
+ {
+ struct revlist *nr;
+
+ nr = (struct revlist *) xmalloc (sizeof *nr);
+
+ if (r->first == NULL && r->last == NULL)
+ {
+ /* If both first and last are NULL, it means that we want
+ just the head of the default branch, which is RCS_head. */
+ nr->first = RCS_head (rcs);
+ nr->last = xstrdup (nr->first);
+ nr->fields = numdots (nr->first) + 1;
+ }
+ else if (r->branchhead)
+ {
+ char *branch;
+
+ /* Print just the head of the branch. */
+ if (isdigit ((unsigned char) r->first[0]))
+ nr->first = RCS_getbranch (rcs, r->first, 1);
+ else
+ {
+ branch = RCS_whatbranch (rcs, r->first);
+ if (branch == NULL)
+ {
+ error (0, 0, "warning: `%s' is not a branch in `%s'",
+ r->first, rcs->path);
+ free (nr);
+ continue;
+ }
+ nr->first = RCS_getbranch (rcs, branch, 1);
+ free (branch);
+ }
+ if (nr->first == NULL)
+ {
+ error (0, 0, "warning: no revision `%s' in `%s'",
+ r->first, rcs->path);
+ free (nr);
+ continue;
+ }
+ nr->last = xstrdup (nr->first);
+ nr->fields = numdots (nr->first) + 1;
+ }
+ else
+ {
+ if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
+ nr->first = xstrdup (r->first);
+ else
+ {
+ if (RCS_nodeisbranch (rcs, r->first))
+ nr->first = RCS_whatbranch (rcs, r->first);
+ else
+ nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
+ if (nr->first == NULL)
+ {
+ error (0, 0, "warning: no revision `%s' in `%s'",
+ r->first, rcs->path);
+ free (nr);
+ continue;
+ }
+ }
+
+ if (r->last == r->first)
+ nr->last = xstrdup (nr->first);
+ else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
+ nr->last = xstrdup (r->last);
+ else
+ {
+ if (RCS_nodeisbranch (rcs, r->last))
+ nr->last = RCS_whatbranch (rcs, r->last);
+ else
+ nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
+ if (nr->last == NULL)
+ {
+ error (0, 0, "warning: no revision `%s' in `%s'",
+ r->last, rcs->path);
+ if (nr->first != NULL)
+ free (nr->first);
+ free (nr);
+ continue;
+ }
+ }
+
+ /* Process the revision numbers the same way that rlog
+ does. This code is a bit cryptic for my tastes, but
+ keeping the same implementation as rlog ensures a
+ certain degree of compatibility. */
+ if (r->first == NULL)
+ {
+ nr->fields = numdots (nr->last) + 1;
+ if (nr->fields < 2)
+ nr->first = xstrdup (".0");
+ else
+ {
+ char *cp;
+
+ nr->first = xstrdup (nr->last);
+ cp = strrchr (nr->first, '.');
+ strcpy (cp, ".0");
+ }
+ }
+ else if (r->last == NULL)
+ {
+ nr->fields = numdots (nr->first) + 1;
+ nr->last = xstrdup (nr->first);
+ if (nr->fields < 2)
+ nr->last[0] = '\0';
+ else
+ {
+ char *cp;
+
+ cp = strrchr (nr->last, '.');
+ *cp = '\0';
+ }
+ }
+ else
+ {
+ nr->fields = numdots (nr->first) + 1;
+ if (nr->fields != numdots (nr->last) + 1
+ || (nr->fields > 2
+ && version_compare (nr->first, nr->last,
+ nr->fields - 1) != 0))
+ {
+ error (0, 0,
+ "invalid branch or revision pair %s:%s in `%s'",
+ r->first, r->last, rcs->path);
+ free (nr->first);
+ free (nr->last);
+ free (nr);
+ continue;
+ }
+ if (version_compare (nr->first, nr->last, nr->fields) > 0)
+ {
+ char *tmp;
+
+ tmp = nr->first;
+ nr->first = nr->last;
+ nr->last = tmp;
+ }
+ }
+ }
+
+ nr->next = NULL;
+ *pr = nr;
+ pr = &nr->next;
+ }
+
+ /* If the default branch was requested, add a revlist entry for
+ it. This is how rlog handles this option. */
+ if (default_branch
+ && (rcs->head != NULL || rcs->branch != NULL))
+ {
+ struct revlist *nr;
+
+ nr = (struct revlist *) xmalloc (sizeof *nr);
+ if (rcs->branch != NULL)
+ nr->first = xstrdup (rcs->branch);
+ else
+ {
+ char *cp;
+
+ nr->first = xstrdup (rcs->head);
+ cp = strrchr (nr->first, '.');
+ *cp = '\0';
+ }
+ nr->last = xstrdup (nr->first);
+ nr->fields = numdots (nr->first) + 1;
+
+ nr->next = NULL;
+ *pr = nr;
+ }
+
+ return ret;
+}
+
+/*
+ * Free a revlist created by log_expand_revlist.
+ */
+static void
+log_free_revlist (revlist)
+ struct revlist *revlist;
+{
+ struct revlist *r;
+
+ r = revlist;
+ while (r != NULL)
+ {
+ struct revlist *next;
+
+ if (r->first != NULL)
+ free (r->first);
+ if (r->last != NULL)
+ free (r->last);
+ next = r->next;
+ free (r);
+ r = next;
+ }
+}
+
+/*
+ * Return nonzero if a revision should be printed, based on the
+ * options provided.
+ */
+static int
+log_version_requested (log_data, revlist, rcs, vnode)
+ struct log_data *log_data;
+ struct revlist *revlist;
+ RCSNode *rcs;
+ RCSVers *vnode;
+{
+ /* Handle the list of states from the -s option. */
+ if (log_data->statelist != NULL
+ && findnode (log_data->statelist, vnode->state) == NULL)
+ {
+ return 0;
+ }
+
+ /* Handle the list of authors from the -w option. */
+ if (log_data->authorlist != NULL)
+ {
+ if (vnode->author != NULL
+ && findnode (log_data->authorlist, vnode->author) == NULL)
+ {
+ return 0;
+ }
+ }
+
+ /* rlog considers all the -d options together when it decides
+ whether to print a revision, so we must be compatible. */
+ if (log_data->datelist != NULL || log_data->singledatelist != NULL)
+ {
+ struct datelist *d;
+
+ for (d = log_data->datelist; d != NULL; d = d->next)
+ {
+ int cmp;
+
+ cmp = RCS_datecmp (vnode->date, d->start);
+ if (cmp > 0 || (cmp == 0 && d->inclusive))
+ {
+ cmp = RCS_datecmp (vnode->date, d->end);
+ if (cmp < 0 || (cmp == 0 && d->inclusive))
+ break;
+ }
+ }
+
+ if (d == NULL)
+ {
+ /* Look through the list of specific dates. We want to
+ select the revision with the exact date found in the
+ start field. The commit code ensures that it is
+ impossible to check in multiple revisions of a single
+ file in a single second, so checking the date this way
+ should never select more than one revision. */
+ for (d = log_data->singledatelist; d != NULL; d = d->next)
+ {
+ if (d->start != NULL
+ && RCS_datecmp (vnode->date, d->start) == 0)
+ {
+ break;
+ }
+ }
+
+ if (d == NULL)
+ return 0;
+ }
+ }
+
+ /* If the -r or -b options were used, REVLIST will be non NULL,
+ and we print the union of the specified revisions. */
+ if (revlist != NULL)
+ {
+ char *v;
+ int vfields;
+ struct revlist *r;
+
+ /* This code is taken from rlog. */
+ v = vnode->version;
+ vfields = numdots (v) + 1;
+ for (r = revlist; r != NULL; r = r->next)
+ {
+ if (vfields == r->fields + (r->fields & 1)
+ && version_compare (v, r->first, r->fields) >= 0
+ && version_compare (v, r->last, r->fields) <= 0)
+ {
+ return 1;
+ }
+ }
+
+ /* If we get here, then the -b and/or the -r option was used,
+ but did not match this revision, so we reject it. */
+
+ return 0;
+ }
+
+ /* By default, we print all revisions. */
+ return 1;
+}
+
+/*
+ * Output a single symbol. This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_symbol (p, closure)
+ Node *p;
+ void *closure;
+{
+ cvs_output ("\n\t", 2);
+ cvs_output (p->key, 0);
+ cvs_output (": ", 2);
+ cvs_output (p->data, 0);
+ return 0;
+}
+
+/*
+ * Count the number of entries on a list. This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_count (p, closure)
+ Node *p;
+ void *closure;
+{
+ return 1;
+}
+
+/*
+ * Sort out a single date specification by narrowing down the date
+ * until we find the specific selected revision.
+ */
+static int
+log_fix_singledate (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
+ Node *pv;
+ RCSVers *vnode;
+ struct datelist *holdsingle, *holddate;
+ int requested;
+
+ pv = findnode (data->rcs->versions, p->key);
+ if (pv == NULL)
+ error (1, 0, "missing version `%s' in RCS file `%s'",
+ p->key, data->rcs->path);
+ vnode = (RCSVers *) pv->data;
+
+ /* We are only interested if this revision passes any other tests.
+ Temporarily clear log_data->singledatelist to avoid confusing
+ log_version_requested. We also clear log_data->datelist,
+ because rlog considers all the -d options together. We don't
+ want to reject a revision because it does not match a date pair
+ if we are going to select it on the basis of the singledate. */
+ holdsingle = data->log_data->singledatelist;
+ data->log_data->singledatelist = NULL;
+ holddate = data->log_data->datelist;
+ data->log_data->datelist = NULL;
+ requested = log_version_requested (data->log_data, data->revlist,
+ data->rcs, vnode);
+ data->log_data->singledatelist = holdsingle;
+ data->log_data->datelist = holddate;
+
+ if (requested)
+ {
+ struct datelist *d;
+
+ /* For each single date, if this revision is before the
+ specified date, but is closer than the previously selected
+ revision, select it instead. */
+ for (d = data->log_data->singledatelist; d != NULL; d = d->next)
+ {
+ if (RCS_datecmp (vnode->date, d->end) <= 0
+ && (d->start == NULL
+ || RCS_datecmp (vnode->date, d->start) > 0))
+ {
+ if (d->start != NULL)
+ free (d->start);
+ d->start = xstrdup (vnode->date);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Count the number of revisions we are going to print.
+ */
+static int
+log_count_print (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
+ Node *pv;
+
+ pv = findnode (data->rcs->versions, p->key);
+ if (pv == NULL)
+ error (1, 0, "missing version `%s' in RCS file `%s'",
+ p->key, data->rcs->path);
+ if (log_version_requested (data->log_data, data->revlist, data->rcs,
+ (RCSVers *) pv->data))
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Print the list of changes, not including the trunk, in reverse
+ * order for each branch.
+ */
+static void
+log_tree (log_data, revlist, rcs, ver)
+ struct log_data *log_data;
+ struct revlist *revlist;
+ RCSNode *rcs;
+ const char *ver;
+{
+ Node *p;
+ RCSVers *vnode;
+
+ p = findnode (rcs->versions, ver);
+ if (p == NULL)
+ error (1, 0, "missing version `%s' in RCS file `%s'",
+ ver, rcs->path);
+ vnode = (RCSVers *) p->data;
+ if (vnode->next != NULL)
+ log_tree (log_data, revlist, rcs, vnode->next);
+ if (vnode->branches != NULL)
+ {
+ Node *head, *branch;
+
+ /* We need to do the branches in reverse order. This breaks
+ the List abstraction, but so does most of the branch
+ manipulation in rcs.c. */
+ head = vnode->branches->list;
+ for (branch = head->prev; branch != head; branch = branch->prev)
+ {
+ log_abranch (log_data, revlist, rcs, branch->key);
+ log_tree (log_data, revlist, rcs, branch->key);
+ }
+ }
+}
+
+/*
+ * Log the changes for a branch, in reverse order.
+ */
+static void
+log_abranch (log_data, revlist, rcs, ver)
+ struct log_data *log_data;
+ struct revlist *revlist;
+ RCSNode *rcs;
+ const char *ver;
+{
+ Node *p;
+ RCSVers *vnode;
+
+ p = findnode (rcs->versions, ver);
+ if (p == NULL)
+ error (1, 0, "missing version `%s' in RCS file `%s'",
+ ver, rcs->path);
+ vnode = (RCSVers *) p->data;
+ if (vnode->next != NULL)
+ log_abranch (log_data, revlist, rcs, vnode->next);
+ log_version (log_data, revlist, rcs, vnode, 0);
+}
+
+/*
+ * Print the log output for a single version.
+ */
+static void
+log_version (log_data, revlist, rcs, ver, trunk)
+ struct log_data *log_data;
+ struct revlist *revlist;
+ RCSNode *rcs;
+ RCSVers *ver;
+ int trunk;
+{
+ Node *p;
+ int year, mon, mday, hour, min, sec;
+ char buf[100];
+ Node *padd, *pdel;
+
+ if (! log_version_requested (log_data, revlist, rcs, ver))
+ return;
+
+ cvs_output ("----------------------------\nrevision ", 0);
+ cvs_output (ver->version, 0);
+
+ p = findnode (RCS_getlocks (rcs), ver->version);
+ if (p != NULL)
+ {
+ cvs_output ("\tlocked by: ", 0);
+ cvs_output (p->data, 0);
+ cvs_output (";", 1);
+ }
+
+ cvs_output ("\ndate: ", 0);
+ (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
+ &sec);
+ if (year < 1900)
+ year += 1900;
+ sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
+ hour, min, sec);
+ cvs_output (buf, 0);
+
+ cvs_output ("; author: ", 0);
+ cvs_output (ver->author, 0);
+
+ cvs_output ("; state: ", 0);
+ cvs_output (ver->state, 0);
+ cvs_output (";", 1);
+
+ if (! trunk)
+ {
+ padd = findnode (ver->other, ";add");
+ pdel = findnode (ver->other, ";delete");
+ }
+ else if (ver->next == NULL)
+ {
+ padd = NULL;
+ pdel = NULL;
+ }
+ else
+ {
+ Node *nextp;
+ RCSVers *nextver;
+
+ nextp = findnode (rcs->versions, ver->next);
+ if (nextp == NULL)
+ error (1, 0, "missing version `%s' in `%s'", ver->next,
+ rcs->path);
+ nextver = (RCSVers *) nextp->data;
+ pdel = findnode (nextver->other, ";add");
+ padd = findnode (nextver->other, ";delete");
+ }
+
+ if (padd != NULL)
+ {
+ cvs_output (" lines: +", 0);
+ cvs_output (padd->data, 0);
+ cvs_output (" -", 2);
+ cvs_output (pdel->data, 0);
+ }
+
+ if (ver->branches != NULL)
+ {
+ cvs_output ("\nbranches:", 0);
+ walklist (ver->branches, log_branch, (void *) NULL);
+ }
+
+ cvs_output ("\n", 1);
+
+ p = findnode (ver->other, "log");
+ /* The p->date == NULL case is the normal one for an empty log
+ message (rcs-14 in sanity.sh). I don't think the case where
+ p->data is "" can happen (getrcskey in rcs.c checks for an
+ empty string and set the value to NULL in that case). My guess
+ would be the p == NULL case would mean an RCS file which was
+ missing the "log" keyword (which is illegal according to
+ rcsfile.5). */
+ if (p == NULL || p->data == NULL || p->data[0] == '\0')
+ cvs_output ("*** empty log message ***\n", 0);
+ else
+ {
+ /* FIXME: Technically, the log message could contain a null
+ byte. */
+ cvs_output (p->data, 0);
+ if (p->data[strlen (p->data) - 1] != '\n')
+ cvs_output ("\n", 1);
+ }
+}
+
+/*
+ * Output a branch version. This is called via walklist.
+ */
+/*ARGSUSED*/
+static int
+log_branch (p, closure)
+ Node *p;
+ void *closure;
+{
+ cvs_output (" ", 2);
+ if ((numdots (p->key) & 1) == 0)
+ cvs_output (p->key, 0);
+ else
+ {
+ char *f, *cp;
+
+ f = xstrdup (p->key);
+ cp = strrchr (f, '.');
+ *cp = '\0';
+ cvs_output (f, 0);
+ free (f);
+ }
+ cvs_output (";", 1);
+ return 0;
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+log_dirproc (callerdat, dir, repository, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repository;
+ char *update_dir;
+ List *entries;
+{
+ if (!isdir (dir))
+ return (R_SKIP_ALL);
+
+ if (!quiet)
+ error (0, 0, "Logging %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Compare versions. This is taken from RCS compartial.
+ */
+static int
+version_compare (v1, v2, len)
+ const char *v1;
+ const char *v2;
+ int len;
+{
+ while (1)
+ {
+ int d1, d2, r;
+
+ if (*v1 == '\0')
+ return 1;
+ if (*v2 == '\0')
+ return -1;
+
+ while (*v1 == '0')
+ ++v1;
+ for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
+ ;
+
+ while (*v2 == '0')
+ ++v2;
+ for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
+ ;
+
+ if (d1 != d2)
+ return d1 < d2 ? -1 : 1;
+
+ r = memcmp (v1, v2, d1);
+ if (r != 0)
+ return r;
+
+ --len;
+ if (len == 0)
+ return 0;
+
+ v1 += d1;
+ v2 += d1;
+
+ if (*v1 == '.')
+ ++v1;
+ if (*v2 == '.')
+ ++v2;
+ }
+}
diff --git a/gnu/dist/cvs/src/login.c b/gnu/dist/cvs/src/login.c
new file mode 100644
index 00000000000..2bbf5b95ebf
--- /dev/null
+++ b/gnu/dist/cvs/src/login.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright (c) 1995, Cyclic Software, Bloomington, IN, USA
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with CVS.
+ *
+ * Allow user to log in for an authenticating server.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+#ifdef AUTH_CLIENT_SUPPORT /* This covers the rest of the file. */
+
+/* There seems to be very little agreement on which system header
+ getpass is declared in. With a lot of fancy autoconfiscation,
+ we could perhaps detect this, but for now we'll just rely on
+ _CRAY, since Cray is perhaps the only system on which our own
+ declaration won't work (some Crays declare the 2#$@% thing as
+ varadic, believe it or not). On Cray, getpass will be declared
+ in either stdlib.h or unistd.h. */
+#ifndef _CRAY
+extern char *getpass ();
+#endif
+
+#ifndef CVS_PASSWORD_FILE
+#define CVS_PASSWORD_FILE ".cvspass"
+#endif
+
+/* If non-NULL, get_cvs_password() will just return this. */
+static char *cvs_password = NULL;
+
+static char *construct_cvspass_filename PROTO ((void));
+
+/* The return value will need to be freed. */
+static char *
+construct_cvspass_filename ()
+{
+ char *homedir;
+ char *passfile;
+
+ /* Environment should override file. */
+ if ((passfile = getenv ("CVS_PASSFILE")) != NULL)
+ return xstrdup (passfile);
+
+ /* Construct absolute pathname to user's password file. */
+ /* todo: does this work under OS/2 ? */
+ homedir = get_homedir ();
+ if (! homedir)
+ {
+ /* FIXME? This message confuses a lot of users, at least
+ on Win95 (which doesn't set HOMEDRIVE and HOMEPATH like
+ NT does). I suppose the answer for Win95 is to store the
+ passwords in the registry or something (??). And .cvsrc
+ and such too? Wonder what WinCVS does (about .cvsrc, the
+ right thing for a GUI is to just store the password in
+ memory only)... */
+ error (1, 0, "could not find out home directory");
+ return (char *) NULL;
+ }
+
+ passfile =
+ (char *) xmalloc (strlen (homedir) + strlen (CVS_PASSWORD_FILE) + 3);
+ strcpy (passfile, homedir);
+#ifndef NO_SLASH_AFTER_HOME
+ /* NO_SLASH_AFTER_HOME is defined for VMS, where foo:[bar]/.cvspass is not
+ a legal filename but foo:[bar].cvspass is. A more clean solution would
+ be something more along the lines of a "join a directory to a filename"
+ kind of thing.... */
+ strcat (passfile, "/");
+#endif
+ strcat (passfile, CVS_PASSWORD_FILE);
+
+ /* Safety first and last, Scouts. */
+ if (isfile (passfile))
+ /* xchmod() is too polite. */
+ chmod (passfile, 0600);
+
+ return passfile;
+}
+
+static const char *const login_usage[] =
+{
+ "Usage: %s %s\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+/* Prompt for a password, and store it in the file "CVS/.cvspass".
+ *
+ * Because the user might be accessing multiple repositories, with
+ * different passwords for each one, the format of ~/.cvspass is:
+ *
+ * user@host:/path Acleartext_password
+ * user@host:/path Acleartext_password
+ * ...
+ *
+ * Of course, the "user@" might be left off -- it's just based on the
+ * value of CVSroot.
+ *
+ * The "A" before "cleartext_password" is a literal capital A. It's a
+ * version number indicating which form of scrambling we're doing on
+ * the password -- someday we might provide something more secure than
+ * the trivial encoding we do now, and when that day comes, it would
+ * be nice to remain backward-compatible.
+ *
+ * Like .netrc, the file's permissions are the only thing preventing
+ * it from being read by others. Unlike .netrc, we will not be
+ * fascist about it, at most issuing a warning, and never refusing to
+ * work.
+ */
+int
+login (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *passfile;
+ FILE *fp;
+ char *typed_password, *found_password;
+ char *linebuf = (char *) NULL;
+ size_t linebuf_len;
+ int root_len, already_entered = 0;
+ int line_length;
+
+ if (argc < 0)
+ usage (login_usage);
+
+ if (CVSroot_method != pserver_method)
+ {
+ error (0, 0, "can only use pserver method with `login' command");
+ error (1, 0, "CVSROOT: %s", CVSroot_original);
+ }
+
+ if (! CVSroot_username)
+ {
+ error (0, 0, "CVSROOT \"%s\" is not fully-qualified.",
+ CVSroot_original);
+ error (1, 0, "Please make sure to specify \"user@host\"!");
+ }
+
+ printf ("(Logging in to %s@%s)\n", CVSroot_username, CVSroot_hostname);
+ fflush (stdout);
+
+ passfile = construct_cvspass_filename ();
+ typed_password = getpass ("CVS password: ");
+ typed_password = scramble (typed_password);
+
+ /* Force get_cvs_password() to use this one (when the client
+ * confirms the new password with the server), instead of
+ * consulting the file. We make a new copy because cvs_password
+ * will get zeroed by connect_to_server(). */
+
+ cvs_password = xstrdup (typed_password);
+
+ connect_to_pserver (NULL, NULL, 1, 0);
+
+ /* IF we have a password for this "[user@]host:/path" already
+ * THEN
+ * IF it's the same as the password we read from the prompt
+ * THEN
+ * do nothing
+ * ELSE
+ * replace the old password with the new one
+ * ELSE
+ * append new entry to the end of the file.
+ */
+
+ root_len = strlen (CVSroot_original);
+
+ /* Yes, the method below reads the user's password file twice. It's
+ inefficient, but we're not talking about a gig of data here. */
+
+ fp = CVS_FOPEN (passfile, "r");
+ /* FIXME: should be printing a message if fp == NULL and not
+ existence_error (errno). */
+ if (fp != NULL)
+ {
+ /* Check each line to see if we have this entry already. */
+ while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0)
+ {
+ if (strncmp (CVSroot_original, linebuf, root_len) == 0)
+ {
+ already_entered = 1;
+ break;
+ }
+ }
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", passfile);
+ }
+ else if (!existence_error (errno))
+ error (0, errno, "cannot open %s", passfile);
+
+ if (already_entered)
+ {
+ /* This user/host has a password in the file already. */
+
+ strtok (linebuf, " ");
+ found_password = strtok (NULL, "\n");
+ if (strcmp (found_password, typed_password))
+ {
+ /* typed_password and found_password don't match, so we'll
+ * have to update passfile. We replace the old password
+ * with the new one by writing a tmp file whose contents are
+ * exactly the same as passfile except that this one entry
+ * gets typed_password instead of found_password. Then we
+ * rename the tmp file on top of passfile.
+ */
+ char *tmp_name;
+ FILE *tmp_fp;
+
+ tmp_name = cvs_temp_name ();
+ if ((tmp_fp = CVS_FOPEN (tmp_name, "w")) == NULL)
+ {
+ error (1, errno, "unable to open temp file %s", tmp_name);
+ return 1;
+ }
+ chmod (tmp_name, 0600);
+
+ fp = CVS_FOPEN (passfile, "r");
+ if (fp == NULL)
+ {
+ error (1, errno, "unable to open %s", passfile);
+ if (linebuf)
+ free (linebuf);
+ return 1;
+ }
+ /* I'm not paranoid, they really ARE out to get me: */
+ chmod (passfile, 0600);
+
+ while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0)
+ {
+ if (strncmp (CVSroot_original, linebuf, root_len))
+ {
+ if (fprintf (tmp_fp, "%s", linebuf) == EOF)
+ error (0, errno, "cannot write %s", tmp_name);
+ }
+ else
+ {
+ if (fprintf (tmp_fp, "%s %s\n", CVSroot_original,
+ typed_password) == EOF)
+ error (0, errno, "cannot write %s", tmp_name);
+ }
+ }
+ if (line_length < 0 && !feof (fp))
+ error (0, errno, "cannot read %s", passfile);
+ if (linebuf)
+ free (linebuf);
+ if (fclose (tmp_fp) < 0)
+ error (0, errno, "cannot close %s", tmp_name);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", passfile);
+
+ /* FIXME: rename_file would make more sense (e.g. almost
+ always faster). */
+ copy_file (tmp_name, passfile);
+ if (unlink_file (tmp_name) < 0)
+ error (0, errno, "cannot remove %s", tmp_name);
+ chmod (passfile, 0600);
+
+ free (tmp_name);
+ }
+ }
+ else
+ {
+ if (linebuf)
+ free (linebuf);
+ if ((fp = CVS_FOPEN (passfile, "a")) == NULL)
+ {
+ error (1, errno, "could not open %s", passfile);
+ free (passfile);
+ return 1;
+ }
+
+ if (fprintf (fp, "%s %s\n", CVSroot_original, typed_password) == EOF)
+ error (0, errno, "cannot write %s", passfile);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", passfile);
+ }
+
+ /* Utter, total, raving paranoia, I know. */
+ chmod (passfile, 0600);
+ memset (typed_password, 0, strlen (typed_password));
+ free (typed_password);
+
+ free (passfile);
+ free (cvs_password);
+ cvs_password = NULL;
+ return 0;
+}
+
+/* Returns the _scrambled_ password. The server must descramble
+ before hashing and comparing. If password file not found, or
+ password not found in the file, just return NULL. */
+char *
+get_cvs_password ()
+{
+ int found_it = 0;
+ int root_len;
+ char *password = NULL;
+ char *linebuf = NULL;
+ size_t linebuf_len;
+ FILE *fp;
+ char *passfile;
+ int line_length;
+
+ /* If someone (i.e., login()) is calling connect_to_pserver() out of
+ context, then assume they have supplied the correct, scrambled
+ password. */
+ if (cvs_password)
+ return cvs_password;
+
+ if (getenv ("CVS_PASSWORD") != NULL)
+ {
+ /* In previous versions of CVS one could specify a password in
+ CVS_PASSWORD. This is a bad idea, because in BSD variants
+ of unix anyone can see the environment variable with 'ps'.
+ But for users who were using that feature we want to at
+ least let them know what is going on. After printing this
+ warning, we should fall through to the regular error where
+ we tell them to run "cvs login" (unless they already ran
+ it, of course). */
+ error (0, 0, "CVS_PASSWORD is no longer supported; ignored");
+ }
+
+ /* Else get it from the file. First make sure that the CVSROOT
+ variable has the appropriate fields filled in. */
+
+ if (CVSroot_method != pserver_method)
+ {
+ error (0, 0, "can only call GET_CVS_PASSWORD with pserver method");
+ error (1, 0, "CVSROOT: %s", CVSroot_original);
+ }
+
+ if (! CVSroot_username)
+ {
+ error (0, 0, "CVSROOT \"%s\" is not fully-qualified.",
+ CVSroot_original);
+ error (1, 0, "Please make sure to specify \"user@host\"!");
+ }
+
+ passfile = construct_cvspass_filename ();
+ fp = CVS_FOPEN (passfile, "r");
+ if (fp == NULL)
+ {
+ free (passfile);
+ return NULL;
+ }
+
+ root_len = strlen (CVSroot_original);
+
+ /* Check each line to see if we have this entry already. */
+ while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0)
+ {
+ if (strncmp (CVSroot_original, linebuf, root_len) == 0)
+ {
+ /* This is it! So break out and deal with linebuf. */
+ found_it = 1;
+ break;
+ }
+ }
+ if (line_length < 0 && !feof (fp))
+ error (0, errno, "cannot read %s", passfile);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", passfile);
+
+ if (found_it)
+ {
+ /* linebuf now contains the line with the password. */
+ char *tmp;
+
+ strtok (linebuf, " ");
+ tmp = strtok (NULL, "\n");
+
+ /* Give it permanent storage. */
+ password = xstrdup (tmp);
+ memset (tmp, 0, strlen (password));
+ }
+
+ if (linebuf)
+ free (linebuf);
+ free (passfile);
+ return password;
+}
+
+static const char *const logout_usage[] =
+{
+ "Usage: %s %s\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+/* Remove any entry for the CVSRoot repository found in .cvspass. */
+int
+logout (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *passfile;
+ FILE *fp;
+ char *tmp_name = NULL;
+ FILE *tmp_fp;
+ char *linebuf = (char *) NULL;
+ size_t linebuf_len;
+ int root_len, found = 0;
+ int line_length;
+
+ if (argc < 0)
+ usage (logout_usage);
+
+ if (CVSroot_method != pserver_method)
+ {
+ error (0, 0, "can only use pserver method with `logout' command");
+ error (1, 0, "CVSROOT: %s", CVSroot_original);
+ }
+
+ if (! CVSroot_username)
+ {
+ error (0, 0, "CVSROOT \"%s\" is not fully-qualified.",
+ CVSroot_original);
+ error (1, 0, "Please make sure to specify \"user@host\"!");
+ }
+
+ /* Hmm. Do we want a variant of this command which deletes _all_
+ the entries from the current .cvspass? Might be easier to
+ remember than "rm ~/.cvspass" but then again if people are
+ mucking with HOME (common in Win95 as the system doesn't set
+ it), then this variant of "cvs logout" might give a false sense
+ of security, in that it wouldn't delete entries from any
+ .cvspass files but the current one. */
+
+ printf ("(Logging out of %s@%s)\n", CVSroot_username, CVSroot_hostname);
+ fflush (stdout);
+
+ /* IF we have a password for this "[user@]host:/path" already
+ * THEN
+ * drop the entry
+ * ELSE
+ * do nothing
+ */
+
+ passfile = construct_cvspass_filename ();
+ /* FIXME: This should not be in /tmp; that is almost surely a security
+ hole. Probably should just keep it in memory. */
+ tmp_name = cvs_temp_name ();
+ if ((tmp_fp = CVS_FOPEN (tmp_name, "w")) == NULL)
+ {
+ error (1, errno, "unable to open temp file %s", tmp_name);
+ return 1;
+ }
+ chmod (tmp_name, 0600);
+
+ root_len = strlen (CVSroot_original);
+
+ fp = CVS_FOPEN (passfile, "r");
+ if (fp == NULL)
+ error (1, errno, "Error opening %s", passfile);
+
+ /* Check each line to see if we have this entry. */
+ /* Copy only those lines that do not match this entry */
+ while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0)
+ {
+ if (strncmp (CVSroot_original, linebuf, root_len))
+ {
+ if (fprintf (tmp_fp, "%s", linebuf) == EOF)
+ error (0, errno, "cannot write %s", tmp_name);
+ }
+ else
+ found = 1;
+ }
+ if (line_length < 0 && !feof (fp))
+ error (0, errno, "cannot read %s", passfile);
+
+ if (linebuf)
+ free (linebuf);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", passfile);
+ if (fclose (tmp_fp) < 0)
+ error (0, errno, "cannot close %s", tmp_name);
+
+ if (! found)
+ {
+ printf ("Entry not found for %s\n", CVSroot_original);
+ if (unlink_file (tmp_name) < 0)
+ error (0, errno, "cannot remove %s", tmp_name);
+ }
+ else
+ {
+ /* FIXME: rename_file would make more sense (e.g. almost
+ always faster). */
+ copy_file (tmp_name, passfile);
+ if (unlink_file (tmp_name) < 0)
+ error (0, errno, "cannot remove %s", tmp_name);
+ chmod (passfile, 0600);
+ }
+
+ if (tmp_name)
+ free (tmp_name);
+
+ return 0;
+}
+
+#endif /* AUTH_CLIENT_SUPPORT from beginning of file. */
diff --git a/gnu/dist/cvs/src/logmsg.c b/gnu/dist/cvs/src/logmsg.c
new file mode 100644
index 00000000000..50ced198ba9
--- /dev/null
+++ b/gnu/dist/cvs/src/logmsg.c
@@ -0,0 +1,886 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static int find_type PROTO((Node * p, void *closure));
+static int fmt_proc PROTO((Node * p, void *closure));
+static int logfile_write PROTO((char *repository, char *filter,
+ char *message, FILE * logfp, List * changes));
+static int rcsinfo_proc PROTO((char *repository, char *template));
+static int title_proc PROTO((Node * p, void *closure));
+static int update_logfile_proc PROTO((char *repository, char *filter));
+static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
+static int editinfo_proc PROTO((char *repository, char *template));
+static int verifymsg_proc PROTO((char *repository, char *script));
+
+static FILE *fp;
+static char *str_list;
+static char *str_list_format; /* The format for str_list's contents. */
+static char *editinfo_editor;
+static char *verifymsg_script;
+static Ctype type;
+
+/*
+ * Puts a standard header on the output which is either being prepared for an
+ * editor session, or being sent to a logfile program. The modified, added,
+ * and removed files are included (if any) and formatted to look pretty. */
+static char *prefix;
+static int col;
+static char *tag;
+static void
+setup_tmpfile (xfp, xprefix, changes)
+ FILE *xfp;
+ char *xprefix;
+ List *changes;
+{
+ /* set up statics */
+ fp = xfp;
+ prefix = xprefix;
+
+ type = T_MODIFIED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sModified Files:\n", prefix);
+ col = 0;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ if (tag != NULL)
+ {
+ free (tag);
+ tag = NULL;
+ }
+ }
+ type = T_ADDED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sAdded Files:\n", prefix);
+ col = 0;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ if (tag != NULL)
+ {
+ free (tag);
+ tag = NULL;
+ }
+ }
+ type = T_REMOVED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sRemoved Files:\n", prefix);
+ col = 0;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ if (tag != NULL)
+ {
+ free (tag);
+ tag = NULL;
+ }
+ }
+}
+
+/*
+ * Looks for nodes of a specified type and returns 1 if found
+ */
+static int
+find_type (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct logfile_info *li;
+
+ li = (struct logfile_info *) p->data;
+ if (li->type == type)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Breaks the files list into reasonable sized lines to avoid line wrap...
+ * all in the name of pretty output. It only works on nodes whose types
+ * match the one we're looking for
+ */
+static int
+fmt_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct logfile_info *li;
+
+ li = (struct logfile_info *) p->data;
+ if (li->type == type)
+ {
+ if (li->tag == NULL
+ ? tag != NULL
+ : tag == NULL || strcmp (tag, li->tag) != 0)
+ {
+ if (col > 0)
+ (void) fprintf (fp, "\n");
+ (void) fprintf (fp, "%s", prefix);
+ col = strlen (prefix);
+ while (col < 6)
+ {
+ (void) fprintf (fp, " ");
+ ++col;
+ }
+
+ if (li->tag == NULL)
+ (void) fprintf (fp, "No tag");
+ else
+ (void) fprintf (fp, "Tag: %s", li->tag);
+
+ if (tag != NULL)
+ free (tag);
+ tag = xstrdup (li->tag);
+
+ /* Force a new line. */
+ col = 70;
+ }
+
+ if (col == 0)
+ {
+ (void) fprintf (fp, "%s\t", prefix);
+ col = 8;
+ }
+ else if (col > 8 && (col + (int) strlen (p->key)) > 70)
+ {
+ (void) fprintf (fp, "\n%s\t", prefix);
+ col = 8;
+ }
+ (void) fprintf (fp, "%s ", p->key);
+ col += strlen (p->key) + 1;
+ }
+ return (0);
+}
+
+/*
+ * Builds a temporary file using setup_tmpfile() and invokes the user's
+ * editor on the file. The header garbage in the resultant file is then
+ * stripped and the log message is stored in the "message" argument.
+ *
+ * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
+ * is NULL, use the CVSADM_TEMPLATE file instead.
+ */
+void
+do_editor (dir, messagep, repository, changes)
+ char *dir;
+ char **messagep;
+ char *repository;
+ List *changes;
+{
+ static int reuse_log_message = 0;
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+ char *fname;
+ struct stat pre_stbuf, post_stbuf;
+ int retcode = 0;
+ char *p;
+
+ if (noexec || reuse_log_message)
+ return;
+
+ /* Abort creation of temp file if no editor is defined */
+ if (strcmp (Editor, "") == 0 && !editinfo_editor)
+ error(1, 0, "no editor defined, must use -e or -m");
+
+
+ /* Create a temporary file */
+ fname = cvs_temp_name ();
+ again:
+ if ((fp = CVS_FOPEN (fname, "w+")) == NULL)
+ error (1, 0, "cannot create temporary file %s", fname);
+
+ if (*messagep)
+ {
+ (void) fprintf (fp, "%s", *messagep);
+
+ if ((*messagep)[0] == '\0' ||
+ (*messagep)[strlen (*messagep) - 1] != '\n')
+ (void) fprintf (fp, "\n");
+ }
+ else
+ (void) fprintf (fp, "\n");
+
+ if (repository != NULL)
+ /* tack templates on if necessary */
+ (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
+ else
+ {
+ FILE *tfp;
+ char buf[1024];
+ char *p;
+ size_t n;
+ size_t nwrite;
+
+ /* Why "b"? */
+ tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
+ if (tfp == NULL)
+ {
+ if (!existence_error (errno))
+ error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
+ }
+ else
+ {
+ while (!feof (tfp))
+ {
+ n = fread (buf, 1, sizeof buf, tfp);
+ nwrite = n;
+ p = buf;
+ while (nwrite > 0)
+ {
+ n = fwrite (p, 1, nwrite, fp);
+ nwrite -= n;
+ p += n;
+ }
+ if (ferror (tfp))
+ error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
+ }
+ if (fclose (tfp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
+ }
+ }
+
+ (void) fprintf (fp,
+ "%s----------------------------------------------------------------------\n",
+ CVSEDITPREFIX);
+ (void) fprintf (fp,
+ "%sEnter Log. Lines beginning with `%.*s' are removed automatically\n%s\n",
+ CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
+ CVSEDITPREFIX);
+ if (dir != NULL && *dir)
+ (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
+ dir, CVSEDITPREFIX);
+ if (changes != NULL)
+ setup_tmpfile (fp, CVSEDITPREFIX, changes);
+ (void) fprintf (fp,
+ "%s----------------------------------------------------------------------\n",
+ CVSEDITPREFIX);
+
+ /* finish off the temp file */
+ if (fclose (fp) == EOF)
+ error (1, errno, "%s", fname);
+ if ( CVS_STAT (fname, &pre_stbuf) == -1)
+ pre_stbuf.st_mtime = 0;
+
+ if (editinfo_editor)
+ free (editinfo_editor);
+ editinfo_editor = (char *) NULL;
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ ; /* nothing, leave editinfo_editor NULL */
+ else
+#endif
+ if (repository != NULL)
+ (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
+
+ /* run the editor */
+ run_setup (editinfo_editor ? editinfo_editor : Editor);
+ run_arg (fname);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+ RUN_NORMAL | RUN_SIGIGNORE)) != 0)
+ error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
+ editinfo_editor ? "Logfile verification failed" :
+ "warning: editor session failed");
+
+ /* put the entire message back into the *messagep variable */
+
+ fp = open_file (fname, "r");
+
+ if (*messagep)
+ free (*messagep);
+
+ if ( CVS_STAT (fname, &post_stbuf) != 0)
+ error (1, errno, "cannot find size of temp file %s", fname);
+
+ if (post_stbuf.st_size == 0)
+ *messagep = NULL;
+ else
+ {
+ /* On NT, we might read less than st_size bytes, but we won't
+ read more. So this works. */
+ *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
+ *messagep[0] = '\0';
+ }
+
+ line = NULL;
+ line_chars_allocated = 0;
+
+ if (*messagep)
+ {
+ p = *messagep;
+ while (1)
+ {
+ line_length = getline (&line, &line_chars_allocated, fp);
+ if (line_length == -1)
+ {
+ if (ferror (fp))
+ error (0, errno, "warning: cannot read %s", fname);
+ break;
+ }
+ if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
+ continue;
+ (void) strcpy (p, line);
+ p += line_length;
+ }
+ }
+ if (fclose (fp) < 0)
+ error (0, errno, "warning: cannot close %s", fname);
+
+ if (pre_stbuf.st_mtime == post_stbuf.st_mtime ||
+ *messagep == NULL ||
+ strcmp (*messagep, "\n") == 0)
+ {
+ for (;;)
+ {
+ (void) printf ("\nLog message unchanged or not specified\n");
+ (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
+ (void) printf ("Action: (continue) ");
+ (void) fflush (stdout);
+ line_length = getline (&line, &line_chars_allocated, stdin);
+ if (line_length < 0)
+ {
+ error (0, errno, "cannot read from stdin");
+ if (unlink_file (fname) < 0)
+ error (0, errno,
+ "warning: cannot remove temp file %s", fname);
+ error (1, 0, "aborting");
+ }
+ else if (line_length == 0
+ || *line == '\n' || *line == 'c' || *line == 'C')
+ break;
+ if (*line == 'a' || *line == 'A')
+ {
+ if (unlink_file (fname) < 0)
+ error (0, errno, "warning: cannot remove temp file %s", fname);
+ error (1, 0, "aborted by user");
+ }
+ if (*line == 'e' || *line == 'E')
+ goto again;
+ if (*line == '!')
+ {
+ reuse_log_message = 1;
+ break;
+ }
+ (void) printf ("Unknown input\n");
+ }
+ }
+ if (line)
+ free (line);
+ if (unlink_file (fname) < 0)
+ error (0, errno, "warning: cannot remove temp file %s", fname);
+ free (fname);
+}
+
+/* Runs the user-defined verification script as part of the commit or import
+ process. This verification is meant to be run whether or not the user
+ included the -m atribute. unlike the do_editor function, this is
+ independant of the running of an editor for getting a message.
+ */
+void
+do_verify (message, repository)
+ char *message;
+ char *repository;
+{
+ FILE *fp;
+ char *fname;
+ int retcode = 0;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ /* The verification will happen on the server. */
+ return;
+#endif
+
+ /* FIXME? Do we really want to skip this on noexec? What do we do
+ for the other administrative files? */
+ if (noexec)
+ return;
+
+ /* If there's no message, then we have nothing to verify. Can this
+ case happen? And if so why would we print a message? */
+ if (message == NULL)
+ {
+ cvs_output ("No message to verify\n", 0);
+ return;
+ }
+
+ /* Get a temp filename, open a temporary file, write the message to the
+ temp file, and close the file. */
+
+ fname = cvs_temp_name ();
+
+ fp = fopen (fname, "w");
+ if (fp == NULL)
+ error (1, errno, "cannot create temporary file %s", fname);
+ else
+ {
+ fprintf (fp, "%s", message);
+ if ((message)[0] == '\0' ||
+ (message)[strlen (message) - 1] != '\n')
+ (void) fprintf (fp, "%s", "\n");
+ if (fclose (fp) == EOF)
+ error (1, errno, "%s", fname);
+
+ /* Get the name of the verification script to run */
+
+ if (repository != NULL)
+ (void) Parse_Info (CVSROOTADM_VERIFYMSG, repository,
+ verifymsg_proc, 0);
+
+ /* Run the verification script */
+
+ if (verifymsg_script)
+ {
+ run_setup (verifymsg_script);
+ run_arg (fname);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+ RUN_NORMAL | RUN_SIGIGNORE)) != 0)
+ {
+ /* Since following error() exits, delete the temp file
+ now. */
+ if (unlink_file (fname) < 0)
+ error (0, errno, "cannot remove %s", fname);
+
+ error (1, retcode == -1 ? errno : 0,
+ "Message verification failed");
+ }
+ }
+
+ /* Delete the temp file */
+
+ if (unlink_file (fname) < 0)
+ error (0, errno, "cannot remove %s", fname);
+ free (fname);
+ }
+}
+
+/*
+ * callback proc for Parse_Info for rcsinfo templates this routine basically
+ * copies the matching template onto the end of the tempfile we are setting
+ * up
+ */
+/* ARGSUSED */
+static int
+rcsinfo_proc (repository, template)
+ char *repository;
+ char *template;
+{
+ static char *last_template;
+ FILE *tfp;
+
+ /* nothing to do if the last one included is the same as this one */
+ if (last_template && strcmp (last_template, template) == 0)
+ return (0);
+ if (last_template)
+ free (last_template);
+ last_template = xstrdup (template);
+
+ if ((tfp = CVS_FOPEN (template, "r")) != NULL)
+ {
+ char *line = NULL;
+ size_t line_chars_allocated = 0;
+
+ while (getline (&line, &line_chars_allocated, tfp) >= 0)
+ (void) fputs (line, fp);
+ if (ferror (tfp))
+ error (0, errno, "warning: cannot read %s", template);
+ if (fclose (tfp) < 0)
+ error (0, errno, "warning: cannot close %s", template);
+ if (line)
+ free (line);
+ return (0);
+ }
+ else
+ {
+ error (0, errno, "Couldn't open rcsinfo template file %s", template);
+ return (1);
+ }
+}
+
+/*
+ * Uses setup_tmpfile() to pass the updated message on directly to any
+ * logfile programs that have a regular expression match for the checked in
+ * directory in the source repository. The log information is fed into the
+ * specified program as standard input.
+ */
+static FILE *logfp;
+static char *message;
+static List *changes;
+
+void
+Update_Logfile (repository, xmessage, xlogfp, xchanges)
+ char *repository;
+ char *xmessage;
+ FILE *xlogfp;
+ List *xchanges;
+{
+ /* nothing to do if the list is empty */
+ if (xchanges == NULL || xchanges->list->next == xchanges->list)
+ return;
+
+ /* set up static vars for update_logfile_proc */
+ message = xmessage;
+ logfp = xlogfp;
+ changes = xchanges;
+
+ /* call Parse_Info to do the actual logfile updates */
+ (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
+}
+
+/*
+ * callback proc to actually do the logfile write from Update_Logfile
+ */
+static int
+update_logfile_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ return (logfile_write (repository, filter, message, logfp, changes));
+}
+
+/*
+ * concatenate each filename/version onto str_list
+ */
+static int
+title_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct logfile_info *li;
+ char *c;
+
+ li = (struct logfile_info *) p->data;
+ if (li->type == type)
+ {
+ /* Until we decide on the correct logging solution when we add
+ directories or perform imports, T_TITLE nodes will only
+ tack on the name provided, regardless of the format string.
+ You can verify that this assumption is safe by checking the
+ code in add.c (add_directory) and import.c (import). */
+
+ str_list = xrealloc (str_list, strlen (str_list) + 5);
+ (void) strcat (str_list, " ");
+
+ if (li->type == T_TITLE)
+ {
+ str_list = xrealloc (str_list,
+ strlen (str_list) + strlen (p->key) + 5);
+ (void) strcat (str_list, p->key);
+ }
+ else
+ {
+ /* All other nodes use the format string. */
+
+ for (c = str_list_format; *c != '\0'; c++)
+ {
+ switch (*c)
+ {
+ case 's':
+ str_list =
+ xrealloc (str_list,
+ strlen (str_list) + strlen (p->key) + 5);
+ (void) strcat (str_list, p->key);
+ break;
+ case 'V':
+ str_list =
+ xrealloc (str_list,
+ (strlen (str_list)
+ + (li->rev_old ? strlen (li->rev_old) : 0)
+ + 10)
+ );
+ (void) strcat (str_list, (li->rev_old
+ ? li->rev_old : "NONE"));
+ break;
+ case 'v':
+ str_list =
+ xrealloc (str_list,
+ (strlen (str_list)
+ + (li->rev_new ? strlen (li->rev_new) : 0)
+ + 10)
+ );
+ (void) strcat (str_list, (li->rev_new
+ ? li->rev_new : "NONE"));
+ break;
+ /* All other characters, we insert an empty field (but
+ we do put in the comma separating it from other
+ fields). This way if future CVS versions add formatting
+ characters, one can write a loginfo file which at least
+ won't blow up on an old CVS. */
+ }
+ if (*(c + 1) != '\0')
+ {
+ str_list = xrealloc (str_list, strlen (str_list) + 5);
+ (void) strcat (str_list, ",");
+ }
+ }
+ }
+ }
+ return (0);
+}
+
+/*
+ * Writes some stuff to the logfile "filter" and returns the status of the
+ * filter program.
+ */
+static int
+logfile_write (repository, filter, message, logfp, changes)
+ char *repository;
+ char *filter;
+ char *message;
+ FILE *logfp;
+ List *changes;
+{
+ FILE *pipefp;
+ char *prog;
+ char *cp;
+ int c;
+ int pipestatus;
+ char *fmt_percent; /* the location of the percent sign
+ that starts the format string. */
+
+ /* The user may specify a format string as part of the filter.
+ Originally, `%s' was the only valid string. The string that
+ was substituted for it was:
+
+ <repository-name> <file1> <file2> <file3> ...
+
+ Each file was either a new directory/import (T_TITLE), or a
+ added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
+ file.
+
+ It is desirable to preserve that behavior so lots of commitlog
+ scripts won't die when they get this new code. At the same
+ time, we'd like to pass other information about the files (like
+ version numbers, statuses, or checkin times).
+
+ The solution is to allow a format string that allows us to
+ specify those other pieces of information. The format string
+ will be composed of `%' followed by a single format character,
+ or followed by a set of format characters surrounded by `{' and
+ `}' as separators. The format characters are:
+
+ s = file name
+ V = old version number (pre-checkin)
+ v = new version number (post-checkin)
+
+ For example, valid format strings are:
+
+ %{}
+ %s
+ %{s}
+ %{sVv}
+
+ There's no reason that more items couldn't be added (like
+ modification date or file status [added, modified, updated,
+ etc.]) -- the code modifications would be minimal (logmsg.c
+ (title_proc) and commit.c (check_fileproc)).
+
+ The output will be a string of tokens separated by spaces. For
+ backwards compatibility, the the first token will be the
+ repository name. The rest of the tokens will be
+ comma-delimited lists of the information requested in the
+ format string. For example, if `/u/src/master' is the
+ repository, `%{sVv}' is the format string, and three files
+ (ChangeLog, Makefile, foo.c) were modified, the output might
+ be:
+
+ /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
+
+ Why this duplicates the old behavior when the format string is
+ `%s' is left as an exercise for the reader. */
+
+ fmt_percent = strchr (filter, '%');
+ if (fmt_percent)
+ {
+ int len;
+ char *srepos;
+ char *fmt_begin, *fmt_end; /* beginning and end of the
+ format string specified in
+ filter. */
+ char *fmt_continue; /* where the string continues
+ after the format string (we
+ might skip a '}') somewhere
+ in there... */
+
+ /* Grab the format string. */
+
+ if ((*(fmt_percent + 1) == ' ') || (*(fmt_percent + 1) == '\0'))
+ {
+ /* The percent stands alone. This is an error. We could
+ be treating ' ' like any other formatting character, but
+ using it as a formatting character seems like it would be
+ a mistake. */
+
+ /* Would be nice to also be giving the line number. */
+ error (0, 0, "loginfo: '%%' not followed by formatting character");
+ fmt_begin = fmt_percent + 1;
+ fmt_end = fmt_begin;
+ fmt_continue = fmt_begin;
+ }
+ else if (*(fmt_percent + 1) == '{')
+ {
+ /* The percent has a set of characters following it. */
+
+ fmt_begin = fmt_percent + 2;
+ fmt_end = strchr (fmt_begin, '}');
+ if (fmt_end)
+ {
+ /* Skip over the '}' character. */
+
+ fmt_continue = fmt_end + 1;
+ }
+ else
+ {
+ /* There was no close brace -- assume that format
+ string continues to the end of the line. */
+
+ /* Would be nice to also be giving the line number. */
+ error (0, 0, "loginfo: '}' missing");
+ fmt_end = fmt_begin + strlen (fmt_begin);
+ fmt_continue = fmt_end;
+ }
+ }
+ else
+ {
+ /* The percent has a single character following it. FIXME:
+ %% should expand to a regular percent sign. */
+
+ fmt_begin = fmt_percent + 1;
+ fmt_end = fmt_begin + 1;
+ fmt_continue = fmt_end;
+ }
+
+ len = fmt_end - fmt_begin;
+ str_list_format = xmalloc (len + 1);
+ strncpy (str_list_format, fmt_begin, len);
+ str_list_format[len] = '\0';
+
+ /* Allocate an initial chunk of memory. As we build up the string
+ we will realloc it. */
+ if (!str_list)
+ str_list = xmalloc (1);
+ str_list[0] = '\0';
+
+ /* Add entries to the string. Don't bother looking for
+ entries if the format string is empty. */
+
+ if (str_list_format[0] != '\0')
+ {
+ type = T_TITLE;
+ (void) walklist (changes, title_proc, NULL);
+ type = T_ADDED;
+ (void) walklist (changes, title_proc, NULL);
+ type = T_MODIFIED;
+ (void) walklist (changes, title_proc, NULL);
+ type = T_REMOVED;
+ (void) walklist (changes, title_proc, NULL);
+ }
+
+ free (str_list_format);
+
+ /* Construct the final string. */
+
+ srepos = Short_Repository (repository);
+
+ prog = xmalloc ((fmt_percent - filter) + strlen (srepos)
+ + strlen (str_list) + strlen (fmt_continue)
+ + 10);
+ (void) strncpy (prog, filter, fmt_percent - filter);
+ prog[fmt_percent - filter] = '\0';
+ (void) strcat (prog, "'");
+ (void) strcat (prog, srepos);
+ (void) strcat (prog, str_list);
+ (void) strcat (prog, "'");
+ (void) strcat (prog, fmt_continue);
+
+ /* To be nice, free up some memory. */
+
+ free (str_list);
+ str_list = (char *) NULL;
+ }
+ else
+ {
+ /* There's no format string. */
+ prog = xstrdup (filter);
+ }
+
+ if ((pipefp = run_popen (prog, "w")) == NULL)
+ {
+ if (!noexec)
+ error (0, 0, "cannot write entry to log filter: %s", prog);
+ free (prog);
+ return (1);
+ }
+ (void) fprintf (pipefp, "Update of %s\n", repository);
+ (void) fprintf (pipefp, "In directory %s:", hostname);
+ cp = xgetwd ();
+ if (cp == NULL)
+ fprintf (pipefp, "<cannot get working directory: %s>\n\n",
+ strerror (errno));
+ else
+ {
+ fprintf (pipefp, "%s\n\n", cp);
+ free (cp);
+ }
+
+ setup_tmpfile (pipefp, "", changes);
+ (void) fprintf (pipefp, "Log Message:\n%s\n", message);
+ if (logfp != (FILE *) 0)
+ {
+ (void) fprintf (pipefp, "Status:\n");
+ rewind (logfp);
+ while ((c = getc (logfp)) != EOF)
+ (void) putc ((char) c, pipefp);
+ }
+ free (prog);
+ pipestatus = pclose (pipefp);
+ return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
+}
+
+/*
+ * We choose to use the *last* match within the editinfo file for this
+ * repository. This allows us to have a global editinfo program for the
+ * root of some hierarchy, for example, and different ones within different
+ * sub-directories of the root (like a special checker for changes made to
+ * the "src" directory versus changes made to the "doc" or "test"
+ * directories.
+ */
+/* ARGSUSED */
+static int
+editinfo_proc(repository, editor)
+ char *repository;
+ char *editor;
+{
+ /* nothing to do if the last match is the same as this one */
+ if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
+ return (0);
+ if (editinfo_editor)
+ free (editinfo_editor);
+
+ editinfo_editor = xstrdup (editor);
+ return (0);
+}
+
+/* This routine is calld by Parse_Info. it asigns the name of the
+ * message verification script to the global variable verify_script
+ */
+static int
+verifymsg_proc (repository, script)
+ char *repository;
+ char *script;
+{
+ if (verifymsg_script && strcmp (verifymsg_script, script) == 0)
+ return (0);
+ if (verifymsg_script)
+ free (verifymsg_script);
+ verifymsg_script = xstrdup (script);
+ return (0);
+}
diff --git a/gnu/dist/cvs/src/main.c b/gnu/dist/cvs/src/main.c
new file mode 100644
index 00000000000..ba6b1cae4ef
--- /dev/null
+++ b/gnu/dist/cvs/src/main.c
@@ -0,0 +1,1151 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS source distribution.
+ *
+ * This is the main C driver for the CVS system.
+ *
+ * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing
+ * the shell-script CVS system that this is based on.
+ *
+ */
+
+#include <assert.h>
+#include "cvs.h"
+
+#ifdef HAVE_WINSOCK_H
+#include <winsock.h>
+#else
+extern int gethostname ();
+#endif
+
+char *program_name;
+char *program_path;
+char *command_name;
+
+/* I'd dynamically allocate this, but it seems like gethostname
+ requires a fixed size array. If I'm remembering the RFCs right,
+ 256 should be enough. */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+char hostname[MAXHOSTNAMELEN];
+
+int use_editor = 1;
+int use_cvsrc = 1;
+int cvswrite = !CVSREAD_DFLT;
+int really_quiet = 0;
+int quiet = 0;
+int trace = 0;
+int noexec = 0;
+int logoff = 0;
+
+/* Set if we should be writing CVSADM directories at top level. At
+ least for now we'll make the default be off (the CVS 1.9, not CVS
+ 1.9.2, behavior). */
+int top_level_admin = 0;
+
+mode_t cvsumask = UMASK_DFLT;
+
+char *CurDir;
+
+/*
+ * Defaults, for the environment variables that are not set
+ */
+char *Tmpdir = TMPDIR_DFLT;
+char *Editor = EDITOR_DFLT;
+
+
+/* When our working directory contains subdirectories with different
+ values in CVS/Root files, we maintain a list of them. */
+List *root_directories = NULL;
+
+/* We step through the above values. This variable is set to reflect
+ the currently active value. */
+char *current_root = NULL;
+
+
+static const struct cmd
+{
+ char *fullname; /* Full name of the function (e.g. "commit") */
+
+ /* Synonyms for the command, nick1 and nick2. We supply them
+ mostly for two reasons: (1) CVS has always supported them, and
+ we need to maintain compatibility, (2) if there is a need for a
+ version which is shorter than the fullname, for ease in typing.
+ Synonyms have the disadvantage that people will see "new" and
+ then have to think about it, or look it up, to realize that is
+ the operation they know as "add". Also, this means that one
+ cannot create a command "cvs new" with a different meaning. So
+ new synonyms are probably best used sparingly, and where used
+ should be abbreviations of the fullname (preferably consisting
+ of the first 2 or 3 or so letters).
+
+ One thing that some systems do is to recognize any unique
+ abbreviation, for example "annotat" "annota", etc., for
+ "annotate". The problem with this is that scripts and user
+ habits will expect a certain abbreviation to be unique, and in
+ a future release of CVS it may not be. So it is better to
+ accept only an explicit list of abbreviations and plan on
+ supporting them in the future as well as now. */
+
+ char *nick1;
+ char *nick2;
+
+ int (*func) (); /* Function takes (argc, argv) arguments. */
+} cmds[] =
+
+{
+ { "add", "ad", "new", add },
+ { "admin", "adm", "rcs", admin },
+ { "annotate", "ann", NULL, annotate },
+ { "checkout", "co", "get", checkout },
+ { "commit", "ci", "com", commit },
+ { "diff", "di", "dif", diff },
+ { "edit", NULL, NULL, edit },
+ { "editors", NULL, NULL, editors },
+ { "export", "exp", "ex", checkout },
+ { "history", "hi", "his", history },
+ { "import", "im", "imp", import },
+ { "init", NULL, NULL, init },
+#ifdef SERVER_SUPPORT
+ { "kserver", NULL, NULL, server }, /* placeholder */
+#endif
+ { "log", "lo", "rlog", cvslog },
+#ifdef AUTH_CLIENT_SUPPORT
+ { "login", "logon", "lgn", login },
+ { "logout", NULL, NULL, logout },
+#ifdef SERVER_SUPPORT
+ { "pserver", NULL, NULL, server }, /* placeholder */
+#endif
+#endif /* AUTH_CLIENT_SUPPORT */
+ { "rdiff", "patch", "pa", patch },
+ { "release", "re", "rel", release },
+ { "remove", "rm", "delete", cvsremove },
+ { "status", "st", "stat", cvsstatus },
+ { "rtag", "rt", "rfreeze", rtag },
+ { "tag", "ta", "freeze", cvstag },
+ { "unedit", NULL, NULL, unedit },
+ { "update", "up", "upd", update },
+ { "watch", NULL, NULL, watch },
+ { "watchers", NULL, NULL, watchers },
+#ifdef SERVER_SUPPORT
+ { "server", NULL, NULL, server },
+#endif
+ { NULL, NULL, NULL, NULL },
+};
+
+static const char *const usg[] =
+{
+ /* CVS usage messages never have followed the GNU convention of
+ putting metavariables in uppercase. I don't know whether that
+ is a good convention or not, but if it changes it would have to
+ change in all the usage messages. For now, they consistently
+ use lowercase, as far as I know. Puncutation is pretty funky,
+ though. Sometimes they use none, as here. Sometimes they use
+ single quotes (not the TeX-ish `' stuff), as in --help-options.
+ Sometimes they use double quotes, as in cvs -H add.
+
+ Most (not all) of the usage messages seem to have periods at
+ the end of each line. I haven't tried to duplicate this style
+ in --help as it is a rather different format from the rest. */
+
+ "Usage: %s [cvs-options] command [command-options-and-arguments]\n",
+ " where cvs-options are -q, -n, etc.\n",
+ " (specify --help-options for a list of options)\n",
+ " where command is add, admin, etc.\n",
+ " (specify --help-commands for a list of commands\n",
+ " or --help-synonyms for a list of command synonyms)\n",
+ " where command-options-and-arguments depend on the specific command\n",
+ " (specify -H followed by a command name for command-specific help)\n",
+ " Specify --help to receive this message\n",
+ "\n",
+
+ /* Some people think that a bug-reporting address should go here. IMHO,
+ the web sites are better because anything else is very likely to go
+ obsolete in the years between a release and when someone might be
+ reading this help. Besides, we could never adequately discuss
+ bug reporting in a concise enough way to put in a help message. */
+
+ /* I was going to put this at the top, but usage() wants the %s to
+ be in the first line. */
+ "The Concurrent Versions System (CVS) is a tool for version control.\n",
+ /* I really don't think I want to try to define "version control"
+ in one line. I'm not sure one can get more concise than the
+ paragraph in ../cvs.spec without assuming the reader knows what
+ version control means. */
+
+ "For CVS updates and additional information, see\n",
+ " Cyclic Software at http://www.cyclic.com/ or\n",
+ " Pascal Molli's CVS site at http://www.loria.fr/~molli/cvs-index.html\n",
+ NULL,
+};
+
+static const char *const cmd_usage[] =
+{
+ "CVS commands are:\n",
+ " add Add a new file/directory to the repository\n",
+ " admin Administration front end for rcs\n",
+ " annotate Show last revision where each line was modified\n",
+ " checkout Checkout sources for editing\n",
+ " commit Check files into the repository\n",
+ " diff Show differences between revisions\n",
+ " edit Get ready to edit a watched file\n",
+ " editors See who is editing a watched file\n",
+ " export Export sources from CVS, similar to checkout\n",
+ " history Show repository access history\n",
+ " import Import sources into CVS, using vendor branches\n",
+ " init Create a CVS repository if it doesn't exist\n",
+ " log Print out history information for files\n",
+#ifdef AUTH_CLIENT_SUPPORT
+ " login Prompt for password for authenticating server.\n",
+ " logout Removes entry in .cvspass for remote repository.\n",
+#endif /* AUTH_CLIENT_SUPPORT */
+ " rdiff Create 'patch' format diffs between releases\n",
+ " release Indicate that a Module is no longer in use\n",
+ " remove Remove an entry from the repository\n",
+ " rtag Add a symbolic tag to a module\n",
+ " status Display status information on checked out files\n",
+ " tag Add a symbolic tag to checked out version of files\n",
+ " unedit Undo an edit command\n",
+ " update Bring work tree in sync with repository\n",
+ " watch Set watches\n",
+ " watchers See who is watching a file\n",
+ "(Specify the --help option for a list of other help options)\n",
+ NULL,
+};
+
+static const char *const opt_usage[] =
+{
+ /* Omit -b because it is just for compatibility. */
+ "CVS global options (specified before the command name) are:\n",
+ " -H Displays usage information for command.\n",
+ " -Q Cause CVS to be really quiet.\n",
+ " -q Cause CVS to be somewhat quiet.\n",
+ " -r Make checked-out files read-only.\n",
+ " -w Make checked-out files read-write (default).\n",
+ " -l Turn history logging off.\n",
+ " -n Do not execute anything that will change the disk.\n",
+ " -t Show trace of program execution -- try with -n.\n",
+ " -v CVS version and copyright.\n",
+ " -T tmpdir Use 'tmpdir' for temporary files.\n",
+ " -e editor Use 'editor' for editing log information.\n",
+ " -d CVS_root Overrides $CVSROOT as the root of the CVS tree.\n",
+ " -f Do not use the ~/.cvsrc file.\n",
+#ifdef CLIENT_SUPPORT
+ " -z # Use compression level '#' for net traffic.\n",
+#ifdef ENCRYPTION
+ " -x Encrypt all net traffic.\n",
+#endif
+ " -a Authenticate all net traffic.\n",
+#endif
+ " -s VAR=VAL Set CVS user variable.\n",
+ "(Specify the --help option for a list of other help options)\n",
+ NULL
+};
+
+
+static int
+set_root_directory (p, ignored)
+ Node *p;
+ void *ignored;
+{
+ if (current_root == NULL && p->data == NULL)
+ {
+ current_root = p->key;
+ return 1;
+ }
+ return 0;
+}
+
+
+static const char * const*
+cmd_synonyms ()
+{
+ char ** synonyms;
+ char ** line;
+ const struct cmd *c = &cmds[0];
+ /* Three more for title, "specify --help" line, and NULL. */
+ int numcmds = 3;
+
+ while (c->fullname != NULL)
+ {
+ numcmds++;
+ c++;
+ }
+
+ synonyms = (char **) xmalloc(numcmds * sizeof(char *));
+ line = synonyms;
+ *line++ = "CVS command synonyms are:\n";
+ for (c = &cmds[0]; c->fullname != NULL; c++)
+ {
+ if (c->nick1 || c->nick2)
+ {
+ *line = xmalloc (strlen (c->fullname)
+ + (c->nick1 != NULL ? strlen (c->nick1) : 0)
+ + (c->nick2 != NULL ? strlen (c->nick2) : 0)
+ + 40);
+ sprintf(*line, " %-12s %s %s\n", c->fullname,
+ c->nick1 ? c->nick1 : "",
+ c->nick2 ? c->nick2 : "");
+ line++;
+ }
+ }
+ *line++ = "(Specify the --help option for a list of other help options)\n";
+ *line = NULL;
+
+ return (const char * const*) synonyms; /* will never be freed */
+}
+
+
+unsigned long int
+lookup_command_attribute (cmd_name)
+ char *cmd_name;
+{
+ unsigned long int ret = 0;
+
+ if (strcmp (cmd_name, "import") != 0)
+ {
+ ret |= CVS_CMD_IGNORE_ADMROOT;
+ }
+
+
+ /* The following commands do not use a checked-out working
+ directory. We conservatively assume that everything else does.
+ Feel free to add to this list if you are _certain_ something
+ something doesn't use the WD. */
+ if ((strcmp (cmd_name, "checkout") != 0) &&
+ (strcmp (cmd_name, "init") != 0) &&
+ (strcmp (cmd_name, "login") != 0) &&
+ (strcmp (cmd_name, "logout") != 0) &&
+ (strcmp (cmd_name, "rdiff") != 0) &&
+ (strcmp (cmd_name, "release") != 0) &&
+ (strcmp (cmd_name, "rtag") != 0))
+ {
+ ret |= CVS_CMD_USES_WORK_DIR;
+ }
+
+
+ /* The following commands do not modify the repository; we
+ conservatively assume that everything else does. Feel free to
+ add to this list if you are _certain_ something is safe. */
+ if ((strcmp (cmd_name, "annotate") != 0) &&
+ (strcmp (cmd_name, "checkout") != 0) &&
+ (strcmp (cmd_name, "diff") != 0) &&
+ (strcmp (cmd_name, "rdiff") != 0) &&
+ (strcmp (cmd_name, "update") != 0) &&
+ (strcmp (cmd_name, "editors") != 0) &&
+ (strcmp (cmd_name, "export") != 0) &&
+ (strcmp (cmd_name, "history") != 0) &&
+ (strcmp (cmd_name, "log") != 0) &&
+ (strcmp (cmd_name, "noop") != 0) &&
+ (strcmp (cmd_name, "watchers") != 0) &&
+ (strcmp (cmd_name, "status") != 0))
+ {
+ ret |= CVS_CMD_MODIFIES_REPOSITORY;
+ }
+
+ return ret;
+}
+
+
+static RETSIGTYPE
+main_cleanup (sig)
+ int sig;
+{
+#ifndef DONT_USE_SIGNALS
+ const char *name;
+ char temp[10];
+
+ switch (sig)
+ {
+#ifdef SIGHUP
+ case SIGHUP:
+ name = "hangup";
+ break;
+#endif
+#ifdef SIGINT
+ case SIGINT:
+ name = "interrupt";
+ break;
+#endif
+#ifdef SIGQUIT
+ case SIGQUIT:
+ name = "quit";
+ break;
+#endif
+#ifdef SIGPIPE
+ case SIGPIPE:
+ name = "broken pipe";
+ break;
+#endif
+#ifdef SIGTERM
+ case SIGTERM:
+ name = "termination";
+ break;
+#endif
+ default:
+ /* This case should never be reached, because we list above all
+ the signals for which we actually establish a signal handler. */
+ sprintf (temp, "%d", sig);
+ name = temp;
+ break;
+ }
+
+ error (1, 0, "received %s signal", name);
+#endif /* !DONT_USE_SIGNALS */
+}
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *CVSroot = CVSROOT_DFLT;
+ extern char *version_string;
+ extern char *config_string;
+ char *cp, *end;
+ const struct cmd *cm;
+ int c, err = 0;
+ int tmpdir_update_env, cvs_update_env;
+ int free_CVSroot = 0;
+ int free_Editor = 0;
+ int free_Tmpdir = 0;
+
+ int help = 0; /* Has the user asked for help? This
+ lets us support the `cvs -H cmd'
+ convention to give help for cmd. */
+ static struct option long_options[] =
+ {
+ {"help", 0, NULL, 'H'},
+ {"version", 0, NULL, 'v'},
+ {"help-commands", 0, NULL, 1},
+ {"help-synonyms", 0, NULL, 2},
+ {"help-options", 0, NULL, 4},
+ {"allow-root", required_argument, NULL, 3},
+ {0, 0, 0, 0}
+ };
+ /* `getopt_long' stores the option index here, but right now we
+ don't use it. */
+ int option_index = 0;
+
+#ifdef SYSTEM_INITIALIZE
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_INITIALIZE (&argc, &argv);
+#endif
+
+#ifdef HAVE_TZSET
+ /* On systems that have tzset (which is almost all the ones I know
+ of), it's a good idea to call it. */
+ tzset ();
+#endif
+
+ /*
+ * Just save the last component of the path for error messages
+ */
+ program_path = xstrdup (argv[0]);
+#ifdef ARGV0_NOT_PROGRAM_NAME
+ /* On some systems, e.g. VMS, argv[0] is not the name of the command
+ which the user types to invoke the program. */
+ program_name = "cvs";
+#else
+ program_name = last_component (argv[0]);
+#endif
+
+ /*
+ * Query the environment variables up-front, so that
+ * they can be overridden by command line arguments
+ */
+ cvs_update_env = 0;
+ tmpdir_update_env = *Tmpdir; /* TMPDIR_DFLT must be set */
+ if ((cp = getenv (TMPDIR_ENV)) != NULL)
+ {
+ Tmpdir = cp;
+ tmpdir_update_env = 0; /* it's already there */
+ }
+ if ((cp = getenv (EDITOR1_ENV)) != NULL)
+ Editor = cp;
+ else if ((cp = getenv (EDITOR2_ENV)) != NULL)
+ Editor = cp;
+ else if ((cp = getenv (EDITOR3_ENV)) != NULL)
+ Editor = cp;
+ if ((cp = getenv (CVSROOT_ENV)) != NULL)
+ {
+ CVSroot = cp;
+ cvs_update_env = 0; /* it's already there */
+ }
+ if (getenv (CVSREAD_ENV) != NULL)
+ cvswrite = 0;
+
+ /* Set this to 0 to force getopt initialization. getopt() sets
+ this to 1 internally. */
+ optind = 0;
+
+ /* We have to parse the options twice because else there is no
+ chance to avoid reading the global options from ".cvsrc". Set
+ opterr to 0 for avoiding error messages about invalid options.
+ */
+ opterr = 0;
+
+ while ((c = getopt_long
+ (argc, argv, "+f", NULL, NULL))
+ != EOF)
+ {
+ if (c == 'f')
+ use_cvsrc = 0;
+ }
+
+ /*
+ * Scan cvsrc file for global options.
+ */
+ if (use_cvsrc)
+ read_cvsrc (&argc, &argv, "cvs");
+
+ optind = 0;
+ opterr = 1;
+
+ while ((c = getopt_long
+ (argc, argv, "+Qqrwtnlvb:T:e:d:Hfz:s:xa", long_options, &option_index))
+ != EOF)
+ {
+ switch (c)
+ {
+ case 1:
+ /* --help-commands */
+ usage (cmd_usage);
+ break;
+ case 2:
+ /* --help-synonyms */
+ usage (cmd_synonyms());
+ break;
+ case 4:
+ /* --help-options */
+ usage (opt_usage);
+ break;
+ case 3:
+ /* --allow-root */
+ root_allow_add (optarg);
+ break;
+ case 'Q':
+ really_quiet = 1;
+ /* FALL THROUGH */
+ case 'q':
+ quiet = 1;
+ break;
+ case 'r':
+ cvswrite = 0;
+ break;
+ case 'w':
+ cvswrite = 1;
+ break;
+ case 't':
+ trace = 1;
+ break;
+ case 'n':
+ noexec = 1;
+ case 'l': /* Fall through */
+ logoff = 1;
+ break;
+ case 'v':
+ /* Having the year here is a good idea, so people have
+ some idea of how long ago their version of CVS was
+ released. */
+ (void) fputs (version_string, stdout);
+ (void) fputs (config_string, stdout);
+ (void) fputs ("\n", stdout);
+ (void) fputs ("\
+Copyright (c) 1989-1998 Brian Berliner, david d `zoo' zuhn, \n\
+ Jeff Polk, and other authors\n", stdout);
+ (void) fputs ("\n", stdout);
+ (void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout);
+ (void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout);
+ (void) fputs ("\n", stdout);
+
+ (void) fputs ("Specify the --help option for further information about CVS\n", stdout);
+
+ exit (0);
+ break;
+ case 'b':
+ /* This option used to specify the directory for RCS
+ executables. But since we don't run them any more,
+ this is a noop. Silently ignore it so that .cvsrc
+ and scripts and inetd.conf and such can work with
+ either new or old CVS. */
+ break;
+ case 'T':
+ Tmpdir = xstrdup (optarg);
+ free_Tmpdir = 1;
+ tmpdir_update_env = 1; /* need to update environment */
+ break;
+ case 'e':
+ Editor = xstrdup (optarg);
+ free_Editor = 1;
+ break;
+ case 'd':
+ if (CVSroot_cmdline != NULL)
+ free (CVSroot_cmdline);
+ CVSroot_cmdline = xstrdup (optarg);
+ CVSroot = xstrdup (optarg);
+ free_CVSroot = 1;
+ cvs_update_env = 1; /* need to update environment */
+ break;
+ case 'H':
+ help = 1;
+ break;
+ case 'f':
+ use_cvsrc = 0; /* unnecessary, since we've done it above */
+ break;
+ case 'z':
+#ifdef CLIENT_SUPPORT
+ gzip_level = atoi (optarg);
+ if (gzip_level <= 0 || gzip_level > 9)
+ error (1, 0,
+ "gzip compression level must be between 1 and 9");
+#endif
+ /* If no CLIENT_SUPPORT, we just silently ignore the gzip
+ level, so that users can have it in their .cvsrc and not
+ cause any trouble. */
+ break;
+ case 's':
+ variable_set (optarg);
+ break;
+ case 'x':
+#ifdef CLIENT_SUPPORT
+ cvsencrypt = 1;
+#endif /* CLIENT_SUPPORT */
+ /* If no CLIENT_SUPPORT, ignore -x, so that users can
+ have it in their .cvsrc and not cause any trouble.
+ If no ENCRYPTION, we still accept -x, but issue an
+ error if we are being run as a client. */
+ break;
+ case 'a':
+#ifdef CLIENT_SUPPORT
+ cvsauthenticate = 1;
+#endif
+ /* If no CLIENT_SUPPORT, ignore -a, so that users can
+ have it in their .cvsrc and not cause any trouble.
+ We will issue an error later if stream
+ authentication is not supported. */
+ break;
+ case '?':
+ default:
+ usage (usg);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ usage (usg);
+
+
+ /* Look up the command name. */
+
+ command_name = argv[0];
+ for (cm = cmds; cm->fullname; cm++)
+ {
+ if (cm->nick1 && !strcmp (command_name, cm->nick1))
+ break;
+ if (cm->nick2 && !strcmp (command_name, cm->nick2))
+ break;
+ if (!strcmp (command_name, cm->fullname))
+ break;
+ }
+
+ if (!cm->fullname)
+ usage (cmd_usage); /* no match */
+ else
+ command_name = cm->fullname; /* Global pointer for later use */
+
+ /* This should probably remain a warning, rather than an error,
+ for quite a while. For one thing the version of VC distributed
+ with GNU emacs 19.34 invokes 'cvs rlog' instead of 'cvs log'. */
+ if (strcmp (argv[0], "rlog") == 0)
+ {
+ error (0, 0, "warning: the rlog command is deprecated");
+ error (0, 0, "use the synonymous log command instead");
+ }
+
+ if (help)
+ {
+ argc = -1; /* some functions only check for this */
+ err = (*(cm->func)) (argc, argv);
+ }
+ else
+ {
+ /* The user didn't ask for help, so go ahead and authenticate,
+ set up CVSROOT, and the rest of it. */
+
+ /* The UMASK environment variable isn't handled with the
+ others above, since we don't want to signal errors if the
+ user has asked for help. This won't work if somebody adds
+ a command-line flag to set the umask, since we'll have to
+ parse it before we get here. */
+
+ if ((cp = getenv (CVSUMASK_ENV)) != NULL)
+ {
+ /* FIXME: Should be accepting symbolic as well as numeric mask. */
+ cvsumask = strtol (cp, &end, 8) & 0777;
+ if (*end != '\0')
+ error (1, errno, "invalid umask value in %s (%s)",
+ CVSUMASK_ENV, cp);
+ }
+
+#if defined (HAVE_KERBEROS) && defined (SERVER_SUPPORT)
+ /* If we are invoked with a single argument "kserver", then we are
+ running as Kerberos server as root. Do the authentication as
+ the very first thing, to minimize the amount of time we are
+ running as root. */
+ if (strcmp (command_name, "kserver") == 0)
+ {
+ kserver_authenticate_connection ();
+
+ /* Pretend we were invoked as a plain server. */
+ command_name = "server";
+ }
+#endif /* HAVE_KERBEROS */
+
+
+#if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT)
+ if (strcmp (command_name, "pserver") == 0)
+ {
+ /* The reason that --allow-root is not a command option
+ is mainly the comment in server() about how argc,argv
+ might be from .cvsrc. I'm not sure about that, and
+ I'm not sure it is only true of command options, but
+ it seems easier to make it a global option. */
+
+ /* Gets username and password from client, authenticates, then
+ switches to run as that user and sends an ACK back to the
+ client. */
+ pserver_authenticate_connection ();
+
+ /* Pretend we were invoked as a plain server. */
+ command_name = "server";
+ }
+#endif /* (AUTH_SERVER_SUPPORT || HAVE_GSSAPI) && SERVER_SUPPORT */
+
+#ifdef SERVER_SUPPORT
+ server_active = strcmp (command_name, "server") == 0;
+#endif
+
+ /* This is only used for writing into the history file. For
+ remote connections, it might be nice to have hostname
+ and/or remote path, on the other hand I'm not sure whether
+ it is worth the trouble. */
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ CurDir = xstrdup ("<remote>");
+ else
+#endif
+ {
+ CurDir = xgetwd ();
+ if (CurDir == NULL)
+ error (1, errno, "cannot get working directory");
+ }
+
+ if (Tmpdir == NULL || Tmpdir[0] == '\0')
+ Tmpdir = "/tmp";
+
+#ifdef HAVE_PUTENV
+ if (tmpdir_update_env)
+ {
+ char *env;
+ env = xmalloc (strlen (TMPDIR_ENV) + strlen (Tmpdir) + 1 + 1);
+ (void) sprintf (env, "%s=%s", TMPDIR_ENV, Tmpdir);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+ }
+#endif
+
+#ifndef DONT_USE_SIGNALS
+ /* make sure we clean up on error */
+#ifdef SIGHUP
+ (void) SIG_register (SIGHUP, main_cleanup);
+ (void) SIG_register (SIGHUP, Lock_Cleanup);
+#endif
+#ifdef SIGINT
+ (void) SIG_register (SIGINT, main_cleanup);
+ (void) SIG_register (SIGINT, Lock_Cleanup);
+#endif
+#ifdef SIGQUIT
+ (void) SIG_register (SIGQUIT, main_cleanup);
+ (void) SIG_register (SIGQUIT, Lock_Cleanup);
+#endif
+#ifdef SIGPIPE
+ (void) SIG_register (SIGPIPE, main_cleanup);
+ (void) SIG_register (SIGPIPE, Lock_Cleanup);
+#endif
+#ifdef SIGTERM
+ (void) SIG_register (SIGTERM, main_cleanup);
+ (void) SIG_register (SIGTERM, Lock_Cleanup);
+#endif
+#endif /* !DONT_USE_SIGNALS */
+
+ gethostname(hostname, sizeof (hostname));
+
+#ifdef KLUDGE_FOR_WNT_TESTSUITE
+ /* Probably the need for this will go away at some point once
+ we call fflush enough places (e.g. fflush (stdout) in
+ cvs_outerr). */
+ (void) setvbuf (stdout, (char *) NULL, _IONBF, 0);
+ (void) setvbuf (stderr, (char *) NULL, _IONBF, 0);
+#endif /* KLUDGE_FOR_WNT_TESTSUITE */
+
+ if (use_cvsrc)
+ read_cvsrc (&argc, &argv, command_name);
+
+#ifdef SERVER_SUPPORT
+ /* Fiddling with CVSROOT doesn't make sense if we're running
+ in server mode, since the client will send the repository
+ directory after the connection is made. */
+
+ if (!server_active)
+#endif
+ {
+ char *CVSADM_Root;
+
+ /* See if we are able to find a 'better' value for CVSroot
+ in the CVSADM_ROOT directory. */
+
+ CVSADM_Root = NULL;
+
+ /* "cvs import" shouldn't check CVS/Root; in general it
+ ignores CVS directories and CVS/Root is likely to
+ specify a different repository than the one we are
+ importing to. */
+
+ if ((lookup_command_attribute (command_name)
+ & CVS_CMD_IGNORE_ADMROOT)
+
+ /* -d overrides CVS/Root, so don't give an error if the
+ latter points to a nonexistent repository. */
+ && CVSroot_cmdline == NULL)
+ {
+ CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
+ }
+
+ if (CVSADM_Root != NULL)
+ {
+ if (CVSroot == NULL || !cvs_update_env)
+ {
+ CVSroot = CVSADM_Root;
+ cvs_update_env = 1; /* need to update environment */
+ }
+ }
+
+ /* Now we've reconciled CVSROOT from the command line, the
+ CVS/Root file, and the environment variable. Do the
+ last sanity checks on the variable. */
+
+ if (! CVSroot)
+ {
+ error (0, 0,
+ "No CVSROOT specified! Please use the `-d' option");
+ error (1, 0,
+ "or set the %s environment variable.", CVSROOT_ENV);
+ }
+
+ if (! *CVSroot)
+ {
+ error (0, 0,
+ "CVSROOT is set but empty! Make sure that the");
+ error (0, 0,
+ "specification of CVSROOT is legal, either via the");
+ error (0, 0,
+ "`-d' option, the %s environment variable, or the",
+ CVSROOT_ENV);
+ error (1, 0,
+ "CVS/Root file (if any).");
+ }
+ }
+
+ /* Here begins the big loop over unique cvsroot values. We
+ need to call do_recursion once for each unique value found
+ in CVS/Root. Prime the list with the current value. */
+
+ /* Create the list. */
+ assert (root_directories == NULL);
+ root_directories = getlist ();
+
+ /* Prime it. */
+ if (CVSroot != NULL)
+ {
+ Node *n;
+ n = getnode ();
+ n->type = NT_UNKNOWN;
+ n->key = xstrdup (CVSroot);
+ n->data = NULL;
+
+ if (addnode (root_directories, n))
+ error (1, 0, "cannot add initial CVSROOT %s", n->key);
+ }
+
+ assert (current_root == NULL);
+
+ /* If we're running the server, we want to execute this main
+ loop once and only once (we won't be serving multiple roots
+ from this connection, so there's no need to do it more than
+ once). To get out of the loop, we perform a "break" at the
+ end of things. */
+
+ while (
+#ifdef SERVER_SUPPORT
+ server_active ||
+#endif
+ walklist (root_directories, set_root_directory, NULL)
+ )
+ {
+#ifdef SERVER_SUPPORT
+ /* Fiddling with CVSROOT doesn't make sense if we're running
+ in server mode, since the client will send the repository
+ directory after the connection is made. */
+
+ if (!server_active)
+#endif
+ {
+ /* Now we're 100% sure that we have a valid CVSROOT
+ variable. Parse it to see if we're supposed to do
+ remote accesses or use a special access method. */
+
+ if (parse_cvsroot (current_root))
+ error (1, 0, "Bad CVSROOT.");
+
+ if (trace)
+ error (0, 0, "notice: main loop with CVSROOT=%s",
+ current_root);
+
+ /*
+ * Check to see if we can write into the history file. If not,
+ * we assume that we can't work in the repository.
+ * BUT, only if the history file exists.
+ */
+
+ if (!client_active)
+ {
+ char *path;
+ int save_errno;
+
+ path = xmalloc (strlen (CVSroot_directory)
+ + sizeof (CVSROOTADM)
+ + 20
+ + sizeof (CVSROOTADM_HISTORY));
+ (void) sprintf (path, "%s/%s", CVSroot_directory, CVSROOTADM);
+ if (!isaccessible (path, R_OK | X_OK))
+ {
+ save_errno = errno;
+ /* If this is "cvs init", the root need not exist yet. */
+ if (strcmp (command_name, "init") != 0)
+ {
+ error (1, save_errno, "%s", path);
+ }
+ }
+ (void) strcat (path, "/");
+ (void) strcat (path, CVSROOTADM_HISTORY);
+ if (isfile (path) && !isaccessible (path, R_OK | W_OK))
+ {
+ save_errno = errno;
+ error (0, 0, "Sorry, you don't have read/write access to the history file");
+ error (1, save_errno, "%s", path);
+ }
+ free (path);
+ }
+
+#ifdef HAVE_PUTENV
+ /* Update the CVSROOT environment variable if necessary. */
+ /* FIXME (njc): should we always set this with the CVSROOT from the command line? */
+ if (cvs_update_env)
+ {
+ char *env;
+ env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot)
+ + 1 + 1);
+ (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+ }
+#endif
+ }
+
+ /* Parse the CVSROOT/config file, but only for local. For the
+ server, we parse it after we know $CVSROOT. For the
+ client, it doesn't get parsed at all, obviously. The
+ presence of the parse_config call here is not mean to
+ predetermine whether CVSROOT/config overrides things from
+ read_cvsrc and other such places or vice versa. That sort
+ of thing probably needs more thought. */
+ if (1
+#ifdef SERVER_SUPPORT
+ && !server_active
+#endif
+#ifdef CLIENT_SUPPORT
+ && !client_active
+#endif
+ )
+ {
+ /* If there was an error parsing the config file, parse_config
+ already printed an error. We keep going. Why? Because
+ if we didn't, then there would be no way to check in a new
+ CVSROOT/config file to fix the broken one! */
+ parse_config (CVSroot_directory);
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* Create a new list for directory names that we've
+ sent to the server. */
+ if (dirs_sent_to_server != NULL)
+ dellist (&dirs_sent_to_server);
+ dirs_sent_to_server = getlist ();
+ }
+#endif
+
+ err = (*(cm->func)) (argc, argv);
+
+ /* Mark this root directory as done. When the server is
+ active, current_root will be NULL -- don't try and
+ remove it from the list. */
+
+ if (current_root != NULL)
+ {
+ Node *n = findnode (root_directories, current_root);
+ assert (n != NULL);
+ n->data = (void *) 1;
+ current_root = NULL;
+ }
+
+#if 0
+ /* This will not work yet, since it tries to free (void *) 1. */
+ dellist (&root_directories);
+#endif
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ break;
+#endif
+ } /* end of loop for cvsroot values */
+
+ } /* end of stuff that gets done if the user DOESN'T ask for help */
+
+ Lock_Cleanup ();
+
+ free (program_path);
+ if (CVSroot_cmdline != NULL)
+ free (CVSroot_cmdline);
+ if (free_CVSroot)
+ free (CVSroot);
+ if (free_Editor)
+ free (Editor);
+ if (free_Tmpdir)
+ free (Tmpdir);
+ root_allow_free ();
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ /* This is exit rather than return because apparently that keeps
+ some tools which check for memory leaks happier. */
+ exit (err ? EXIT_FAILURE : 0);
+ /* Keep picky/stupid compilers (e.g. Visual C++ 5.0) happy. */
+ return 0;
+}
+
+char *
+Make_Date (rawdate)
+ char *rawdate;
+{
+ time_t unixtime;
+
+ unixtime = get_date (rawdate, (struct timeb *) NULL);
+ if (unixtime == (time_t) - 1)
+ error (1, 0, "Can't parse date/time: %s", rawdate);
+ return date_from_time_t (unixtime);
+}
+
+/* Convert a time_t to an RCS format date. This is mainly for the
+ use of "cvs history", because the CVSROOT/history file contains
+ time_t format dates; most parts of CVS will want to avoid using
+ time_t's directly, and instead use RCS_datecmp, Make_Date, &c.
+ Assuming that the time_t is in GMT (as it generally should be),
+ then the result will be in GMT too.
+
+ Returns a newly malloc'd string. */
+
+char *
+date_from_time_t (unixtime)
+ time_t unixtime;
+{
+ struct tm *ftm;
+ char date[MAXDATELEN];
+ char *ret;
+
+ ftm = gmtime (&unixtime);
+ if (ftm == NULL)
+ /* This is a system, like VMS, where the system clock is in local
+ time. Hopefully using localtime here matches the "zero timezone"
+ hack I added to get_date (get_date of course being the relevant
+ issue for Make_Date, and for history.c too I think). */
+ ftm = localtime (&unixtime);
+
+ (void) sprintf (date, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+ ret = xstrdup (date);
+ return (ret);
+}
+
+/* Convert a date to RFC822/1123 format. This is used in contexts like
+ dates to send in the protocol; it should not vary based on locale or
+ other such conventions for users. We should have another routine which
+ does that kind of thing.
+
+ The SOURCE date is in our internal RCS format. DEST should point to
+ storage managed by the caller, at least MAXDATELEN characters. */
+void
+date_to_internet (dest, source)
+ char *dest;
+ char *source;
+{
+ int year, month, day, hour, minute, second;
+
+ /* Just to reiterate, these strings are from RFC822 and do not vary
+ according to locale. */
+ static const char *const month_names[] =
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+ if (sscanf (source, SDATEFORM,
+ &year, &month, &day, &hour, &minute, &second)
+ != 6)
+ /* Is there a better way to handle errors here? I made this
+ non-fatal in case we are called from the code which can't
+ deal with fatal errors. */
+ error (0, 0, "internal error: bad date %s", source);
+
+ /* Always send a four digit year. */
+ if (year < 100)
+ year += 1900;
+
+ sprintf (dest, "%d %s %d %02d:%02d:%02d -0000", day,
+ month < 1 || month > 12 ? "???" : month_names[month - 1],
+ year, hour, minute, second);
+}
+
+void
+usage (cpp)
+ register const char *const *cpp;
+{
+ (void) fprintf (stderr, *cpp++, program_name, command_name);
+ for (; *cpp; cpp++)
+ (void) fprintf (stderr, *cpp);
+ error_exit ();
+}
diff --git a/gnu/dist/cvs/src/mkmodules.c b/gnu/dist/cvs/src/mkmodules.c
new file mode 100644
index 00000000000..96661845373
--- /dev/null
+++ b/gnu/dist/cvs/src/mkmodules.c
@@ -0,0 +1,946 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS kit. */
+
+#include "cvs.h"
+#include "savecwd.h"
+#include "getline.h"
+
+#ifndef DBLKSIZ
+#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */
+#endif
+
+static int checkout_file PROTO((char *file, char *temp));
+static char *make_tempfile PROTO((void));
+static void rename_rcsfile PROTO((char *temp, char *real));
+
+#ifndef MY_NDBM
+static void rename_dbmfile PROTO((char *temp));
+static void write_dbmfile PROTO((char *temp));
+#endif /* !MY_NDBM */
+
+/* Structure which describes an administrative file. */
+struct admin_file {
+ /* Name of the file, within the CVSROOT directory. */
+ char *filename;
+
+ /* This is a one line description of what the file is for. It is not
+ currently used, although one wonders whether it should be, somehow.
+ If NULL, then don't process this file in mkmodules (FIXME?: a bit of
+ a kludge; probably should replace this with a flags field). */
+ char *errormsg;
+
+ /* Contents which the file should have in a new repository. To avoid
+ problems with brain-dead compilers which choke on long string constants,
+ this is a pointer to an array of char * terminated by NULL--each of
+ the strings is concatenated.
+
+ If this field is NULL, the file is not created in a new
+ repository, but it can be added with "cvs add" (just as if one
+ had created the repository with a version of CVS which didn't
+ know about the file) and the checked-out copy will be updated
+ without having to add it to checkoutlist. */
+ const char * const *contents;
+};
+
+static const char *const loginfo_contents[] = {
+ "# The \"loginfo\" file controls where \"cvs commit\" log information\n",
+ "# is sent. The first entry on a line is a regular expression which must match\n",
+ "# the directory that the change is being made to, relative to the\n",
+ "# $CVSROOT. If a match is found, then the remainder of the line is a filter\n",
+ "# program that should expect log information on its standard input.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in this\n",
+ "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name ALL appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or DEFAULT.\n",
+ "#\n",
+ "# You may specify a format string as part of the\n",
+ "# filter. The string is composed of a `%' followed\n",
+ "# by a single format character, or followed by a set of format\n",
+ "# characters surrounded by `{' and `}' as separators. The format\n",
+ "# characters are:\n",
+ "#\n",
+ "# s = file name\n",
+ "# V = old version number (pre-checkin)\n",
+ "# v = new version number (post-checkin)\n",
+ "#\n",
+ "# For example:\n",
+ "#DEFAULT (echo \"\"; id; echo %s; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
+ "# or\n",
+ "#DEFAULT (echo \"\"; id; echo %{sVv}; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
+ NULL
+};
+
+static const char *const rcsinfo_contents[] = {
+ "# The \"rcsinfo\" file is used to control templates with which the editor\n",
+ "# is invoked on commit and import.\n",
+ "#\n",
+ "# The first entry on a line is a regular expression which is tested\n",
+ "# against the directory that the change is being made to, relative to the\n",
+ "# $CVSROOT. For the first match that is found, then the remainder of the\n",
+ "# line is the name of the file that contains the template.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in this\n",
+ "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name \"ALL\" appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or \"DEFAULT\".\n",
+ NULL
+};
+
+static const char *const editinfo_contents[] = {
+ "# The \"editinfo\" file is used to allow verification of logging\n",
+ "# information. It works best when a template (as specified in the\n",
+ "# rcsinfo file) is provided for the logging procedure. Given a\n",
+ "# template with locations for, a bug-id number, a list of people who\n",
+ "# reviewed the code before it can be checked in, and an external\n",
+ "# process to catalog the differences that were code reviewed, the\n",
+ "# following test can be applied to the code:\n",
+ "#\n",
+ "# Making sure that the entered bug-id number is correct.\n",
+ "# Validating that the code that was reviewed is indeed the code being\n",
+ "# checked in (using the bug-id number or a seperate review\n",
+ "# number to identify this particular code set.).\n",
+ "#\n",
+ "# If any of the above test failed, then the commit would be aborted.\n",
+ "#\n",
+ "# Actions such as mailing a copy of the report to each reviewer are\n",
+ "# better handled by an entry in the loginfo file.\n",
+ "#\n",
+ "# One thing that should be noted is the the ALL keyword is not\n",
+ "# supported. There can be only one entry that matches a given\n",
+ "# repository.\n",
+ NULL
+};
+
+static const char *const verifymsg_contents[] = {
+ "# The \"verifymsg\" file is used to allow verification of logging\n",
+ "# information. It works best when a template (as specified in the\n",
+ "# rcsinfo file) is provided for the logging procedure. Given a\n",
+ "# template with locations for, a bug-id number, a list of people who\n",
+ "# reviewed the code before it can be checked in, and an external\n",
+ "# process to catalog the differences that were code reviewed, the\n",
+ "# following test can be applied to the code:\n",
+ "#\n",
+ "# Making sure that the entered bug-id number is correct.\n",
+ "# Validating that the code that was reviewed is indeed the code being\n",
+ "# checked in (using the bug-id number or a seperate review\n",
+ "# number to identify this particular code set.).\n",
+ "#\n",
+ "# If any of the above test failed, then the commit would be aborted.\n",
+ "#\n",
+ "# Actions such as mailing a copy of the report to each reviewer are\n",
+ "# better handled by an entry in the loginfo file.\n",
+ "#\n",
+ "# One thing that should be noted is the the ALL keyword is not\n",
+ "# supported. There can be only one entry that matches a given\n",
+ "# repository.\n",
+ NULL
+};
+
+static const char *const commitinfo_contents[] = {
+ "# The \"commitinfo\" file is used to control pre-commit checks.\n",
+ "# The filter on the right is invoked with the repository and a list \n",
+ "# of files to check. A non-zero exit of the filter program will \n",
+ "# cause the commit to be aborted.\n",
+ "#\n",
+ "# The first entry on a line is a regular expression which is tested\n",
+ "# against the directory that the change is being committed to, relative\n",
+ "# to the $CVSROOT. For the first match that is found, then the remainder\n",
+ "# of the line is the name of the filter to run.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in this\n",
+ "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name \"ALL\" appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or \"DEFAULT\".\n",
+ NULL
+};
+
+static const char *const taginfo_contents[] = {
+ "# The \"taginfo\" file is used to control pre-tag checks.\n",
+ "# The filter on the right is invoked with the following arguments:\n",
+ "#\n",
+ "# $1 -- tagname\n",
+ "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n",
+ "# $3 -- repository\n",
+ "# $4-> file revision [file revision ...]\n",
+ "#\n",
+ "# A non-zero exit of the filter program will cause the tag to be aborted.\n",
+ "#\n",
+ "# The first entry on a line is a regular expression which is tested\n",
+ "# against the directory that the change is being committed to, relative\n",
+ "# to the $CVSROOT. For the first match that is found, then the remainder\n",
+ "# of the line is the name of the filter to run.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in this\n",
+ "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name \"ALL\" appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or \"DEFAULT\".\n",
+ NULL
+};
+
+static const char *const checkoutlist_contents[] = {
+ "# The \"checkoutlist\" file is used to support additional version controlled\n",
+ "# administrative files in $CVSROOT/CVSROOT, such as template files.\n",
+ "#\n",
+ "# The first entry on a line is a filename which will be checked out from\n",
+ "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n",
+ "# The remainder of the line is an error message to use if the file cannot\n",
+ "# be checked out.\n",
+ "#\n",
+ "# File format:\n",
+ "#\n",
+ "# [<whitespace>]<filename><whitespace><error message><end-of-line>\n",
+ "#\n",
+ "# comment lines begin with '#'\n",
+ NULL
+};
+
+static const char *const cvswrappers_contents[] = {
+ "# This file affects handling of files based on their names.\n",
+ "#\n",
+ "# The -t/-f options allow one to treat directories of files\n",
+ "# as a single file, or to transform a file in other ways on\n",
+ "# its way in and out of CVS.\n",
+ "#\n",
+ "# The -m option specifies whether CVS attempts to merge files.\n",
+ "#\n",
+ "# The -k option specifies keyword expansion (e.g. -kb for binary).\n",
+ "#\n",
+ "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n",
+ "#\n",
+ "# wildcard [option value][option value]...\n",
+ "#\n",
+ "# where option is one of\n",
+ "# -f from cvs filter value: path to filter\n",
+ "# -t to cvs filter value: path to filter\n",
+ "# -m update methodology value: MERGE or COPY\n",
+ "# -k expansion mode value: b, o, kkv, &c\n",
+ "#\n",
+ "# and value is a single-quote delimited value.\n",
+ "# For example:\n",
+ "#*.gif -k 'b'\n",
+ NULL
+};
+
+static const char *const notify_contents[] = {
+ "# The \"notify\" file controls where notifications from watches set by\n",
+ "# \"cvs watch add\" or \"cvs edit\" are sent. The first entry on a line is\n",
+ "# a regular expression which is tested against the directory that the\n",
+ "# change is being made to, relative to the $CVSROOT. If it matches,\n",
+ "# then the remainder of the line is a filter program that should contain\n",
+ "# one occurrence of %s for the user to notify, and information on its\n",
+ "# standard input.\n",
+ "#\n",
+ "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n",
+ "#\n",
+ "# For example:\n",
+ "#ALL mail %s -s \"CVS notification\"\n",
+ NULL
+};
+
+static const char *const modules_contents[] = {
+ "# Three different line formats are valid:\n",
+ "# key -a aliases...\n",
+ "# key [options] directory\n",
+ "# key [options] directory files...\n",
+ "#\n",
+ "# Where \"options\" are composed of:\n",
+ "# -i prog Run \"prog\" on \"cvs commit\" from top-level of module.\n",
+ "# -o prog Run \"prog\" on \"cvs checkout\" of module.\n",
+ "# -e prog Run \"prog\" on \"cvs export\" of module.\n",
+ "# -t prog Run \"prog\" on \"cvs rtag\" of module.\n",
+ "# -u prog Run \"prog\" on \"cvs update\" of module.\n",
+ "# -d dir Place module in directory \"dir\" instead of module name.\n",
+ "# -l Top-level directory only -- do not recurse.\n",
+ "#\n",
+ "# NOTE: If you change any of the \"Run\" options above, you'll have to\n",
+ "# release and re-checkout any working directories of these modules.\n",
+ "#\n",
+ "# And \"directory\" is a path to a directory relative to $CVSROOT.\n",
+ "#\n",
+ "# The \"-a\" option specifies an alias. An alias is interpreted as if\n",
+ "# everything on the right of the \"-a\" had been typed on the command line.\n",
+ "#\n",
+ "# You can encode a module within a module by using the special '&'\n",
+ "# character to interpose another module into the current module. This\n",
+ "# can be useful for creating a module that consists of many directories\n",
+ "# spread out over the entire source repository.\n",
+ NULL
+};
+
+static const char *const config_contents[] = {
+ "# Set this to \"no\" if pserver shouldn't check system users/passwords\n",
+ "#SystemAuth=no\n",
+ "\n",
+ "# Set `PreservePermissions' to `yes' to save file status information\n",
+ "# in the repository.\n",
+ "#PreservePermissions=no\n",
+ "\n",
+ "# Set `TopLevelAdmin' to `yes' to create a CVS directory at the top\n",
+ "# level of the new working directory when using the `cvs checkout'\n",
+ "# command.\n",
+ "#TopLevelAdmin=no\n",
+ NULL
+};
+
+static const struct admin_file filelist[] = {
+ {CVSROOTADM_LOGINFO,
+ "no logging of 'cvs commit' messages is done without a %s file",
+ &loginfo_contents[0]},
+ {CVSROOTADM_RCSINFO,
+ "a %s file can be used to configure 'cvs commit' templates",
+ rcsinfo_contents},
+ {CVSROOTADM_EDITINFO,
+ "a %s file can be used to validate log messages",
+ editinfo_contents},
+ {CVSROOTADM_VERIFYMSG,
+ "a %s file can be used to validate log messages",
+ verifymsg_contents},
+ {CVSROOTADM_COMMITINFO,
+ "a %s file can be used to configure 'cvs commit' checking",
+ commitinfo_contents},
+ {CVSROOTADM_TAGINFO,
+ "a %s file can be used to configure 'cvs tag' checking",
+ taginfo_contents},
+ {CVSROOTADM_IGNORE,
+ "a %s file can be used to specify files to ignore",
+ NULL},
+ {CVSROOTADM_CHECKOUTLIST,
+ "a %s file can specify extra CVSROOT files to auto-checkout",
+ checkoutlist_contents},
+ {CVSROOTADM_WRAPPER,
+ "a %s file can be used to specify files to treat as wrappers",
+ cvswrappers_contents},
+ {CVSROOTADM_NOTIFY,
+ "a %s file can be used to specify where notifications go",
+ notify_contents},
+ {CVSROOTADM_MODULES,
+ /* modules is special-cased in mkmodules. */
+ NULL,
+ modules_contents},
+ {CVSROOTADM_READERS,
+ "a %s file specifies read-only users",
+ NULL},
+ {CVSROOTADM_WRITERS,
+ "a %s file specifies read/write users",
+ NULL},
+
+ /* Some have suggested listing CVSROOTADM_PASSWD here too. This
+ would mean that CVS commands which operate on the
+ CVSROOTADM_PASSWD file would transmit hashed passwords over the
+ net. This might seem to be no big deal, as pserver normally
+ transmits cleartext passwords, but the difference is that
+ CVSROOTADM_PASSWD contains *all* passwords, not just the ones
+ currently being used. For example, it could be too easy to
+ accidentally give someone readonly access to CVSROOTADM_PASSWD
+ (e.g. via anonymous CVS or cvsweb), and then if there are any
+ guessable passwords for read/write access (usually there will be)
+ they get read/write access.
+
+ Another worry is the implications of storing old passwords--if
+ someone used a password in the past they might be using it
+ elsewhere, using a similar password, etc, and so saving old
+ passwords, even hashed, is probably not a good idea. */
+
+ {CVSROOTADM_CONFIG,
+ "a %s file configures various behaviors",
+ config_contents},
+ {NULL, NULL, NULL}
+};
+
+/* Rebuild the checked out administrative files in directory DIR. */
+int
+mkmodules (dir)
+ char *dir;
+{
+ struct saved_cwd cwd;
+ char *temp;
+ char *cp, *last, *fname;
+#ifdef MY_NDBM
+ DBM *db;
+#endif
+ FILE *fp;
+ char *line = NULL;
+ size_t line_allocated = 0;
+ const struct admin_file *fileptr;
+
+ if (save_cwd (&cwd))
+ error_exit ();
+
+ if ( CVS_CHDIR (dir) < 0)
+ error (1, errno, "cannot chdir to %s", dir);
+
+ /*
+ * First, do the work necessary to update the "modules" database.
+ */
+ temp = make_tempfile ();
+ switch (checkout_file (CVSROOTADM_MODULES, temp))
+ {
+
+ case 0: /* everything ok */
+#ifdef MY_NDBM
+ /* open it, to generate any duplicate errors */
+ if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
+ dbm_close (db);
+#else
+ write_dbmfile (temp);
+ rename_dbmfile (temp);
+#endif
+ rename_rcsfile (temp, CVSROOTADM_MODULES);
+ break;
+
+ default:
+ error (0, 0,
+ "'cvs checkout' is less functional without a %s file",
+ CVSROOTADM_MODULES);
+ break;
+ } /* switch on checkout_file() */
+
+ if (unlink_file (temp) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", temp);
+ free (temp);
+
+ /* Checkout the files that need it in CVSROOT dir */
+ for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
+ if (fileptr->errormsg == NULL)
+ continue;
+ temp = make_tempfile ();
+ if (checkout_file (fileptr->filename, temp) == 0)
+ rename_rcsfile (temp, fileptr->filename);
+#if 0
+ /*
+ * If there was some problem other than the file not existing,
+ * checkout_file already printed a real error message. If the
+ * file does not exist, it is harmless--it probably just means
+ * that the repository was created with an old version of CVS
+ * which didn't have so many files in CVSROOT.
+ */
+ else if (fileptr->errormsg)
+ error (0, 0, fileptr->errormsg, fileptr->filename);
+#endif
+ if (unlink_file (temp) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", temp);
+ free (temp);
+ }
+
+ fp = CVS_FOPEN (CVSROOTADM_CHECKOUTLIST, "r");
+ if (fp)
+ {
+ /*
+ * File format:
+ * [<whitespace>]<filename><whitespace><error message><end-of-line>
+ *
+ * comment lines begin with '#'
+ */
+ while (getline (&line, &line_allocated, fp) >= 0)
+ {
+ /* skip lines starting with # */
+ if (line[0] == '#')
+ continue;
+
+ if ((last = strrchr (line, '\n')) != NULL)
+ *last = '\0'; /* strip the newline */
+
+ /* Skip leading white space. */
+ for (fname = line;
+ *fname && isspace ((unsigned char) *fname);
+ fname++)
+ ;
+
+ /* Find end of filename. */
+ for (cp = fname; *cp && !isspace ((unsigned char) *cp); cp++)
+ ;
+ *cp = '\0';
+
+ temp = make_tempfile ();
+ if (checkout_file (fname, temp) == 0)
+ {
+ rename_rcsfile (temp, fname);
+ }
+ else
+ {
+ for (cp++;
+ cp < last && *last && isspace ((unsigned char) *last);
+ cp++)
+ ;
+ if (cp < last && *cp)
+ error (0, 0, cp, fname);
+ }
+ if (unlink_file (temp) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", temp);
+ free (temp);
+ }
+ if (line)
+ free (line);
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", CVSROOTADM_CHECKOUTLIST);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSROOTADM_CHECKOUTLIST);
+ }
+ else
+ {
+ /* Error from CVS_FOPEN. */
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", CVSROOTADM_CHECKOUTLIST);
+ }
+
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+
+ return (0);
+}
+
+/*
+ * Yeah, I know, there are NFS race conditions here.
+ */
+static char *
+make_tempfile ()
+{
+ static int seed = 0;
+ int fd;
+ char *temp;
+
+ if (seed == 0)
+ seed = getpid ();
+ temp = xmalloc (sizeof (BAKPREFIX) + 40);
+ while (1)
+ {
+ (void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
+ if ((fd = CVS_OPEN (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
+ break;
+ if (errno != EEXIST)
+ error (1, errno, "cannot create temporary file %s", temp);
+ }
+ if (close(fd) < 0)
+ error(1, errno, "cannot close temporary file %s", temp);
+ return temp;
+}
+
+/* Get a file. If the file does not exist, return 1 silently. If
+ there is an error, print a message and return 1 (FIXME: probably
+ not a very clean convention). On success, return 0. */
+
+static int
+checkout_file (file, temp)
+ char *file;
+ char *temp;
+{
+ char *rcs;
+ RCSNode *rcsnode;
+ int retcode = 0;
+
+ if (noexec)
+ return 0;
+
+ rcs = xmalloc (strlen (file) + 5);
+ strcpy (rcs, file);
+ strcat (rcs, RCSEXT);
+ if (!isfile (rcs))
+ {
+ free (rcs);
+ return (1);
+ }
+ rcsnode = RCS_parsercsfile (rcs);
+ retcode = RCS_checkout (rcsnode, NULL, NULL, NULL, NULL, temp,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
+ if (retcode != 0)
+ {
+ /* Probably not necessary (?); RCS_checkout already printed a
+ message. */
+ error (0, 0, "failed to check out %s file",
+ file);
+ }
+ freercsnode (&rcsnode);
+ free (rcs);
+ return (retcode);
+}
+
+#ifndef MY_NDBM
+
+static void
+write_dbmfile (temp)
+ char *temp;
+{
+ char line[DBLKSIZ], value[DBLKSIZ];
+ FILE *fp;
+ DBM *db;
+ char *cp, *vp;
+ datum key, val;
+ int len, cont, err = 0;
+
+ fp = open_file (temp, "r");
+ if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
+ error (1, errno, "cannot open dbm file %s for creation", temp);
+ for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
+ {
+ if ((cp = strrchr (line, '\n')) != NULL)
+ *cp = '\0'; /* strip the newline */
+
+ /*
+ * Add the line to the value, at the end if this is a continuation
+ * line; otherwise at the beginning, but only after any trailing
+ * backslash is removed.
+ */
+ vp = value;
+ if (cont)
+ vp += strlen (value);
+
+ /*
+ * See if the line we read is a continuation line, and strip the
+ * backslash if so.
+ */
+ len = strlen (line);
+ if (len > 0)
+ cp = &line[len - 1];
+ else
+ cp = line;
+ if (*cp == '\\')
+ {
+ cont = 1;
+ *cp = '\0';
+ }
+ else
+ {
+ cont = 0;
+ }
+ (void) strcpy (vp, line);
+ if (value[0] == '#')
+ continue; /* comment line */
+ vp = value;
+ while (*vp && isspace ((unsigned char) *vp))
+ vp++;
+ if (*vp == '\0')
+ continue; /* empty line */
+
+ /*
+ * If this was not a continuation line, add the entry to the database
+ */
+ if (!cont)
+ {
+ key.dptr = vp;
+ while (*vp && !isspace ((unsigned char) *vp))
+ vp++;
+ key.dsize = vp - key.dptr;
+ *vp++ = '\0'; /* NULL terminate the key */
+ while (*vp && isspace ((unsigned char) *vp))
+ vp++; /* skip whitespace to value */
+ if (*vp == '\0')
+ {
+ error (0, 0, "warning: NULL value for key `%s'", key.dptr);
+ continue;
+ }
+ val.dptr = vp;
+ val.dsize = strlen (vp);
+ if (dbm_store (db, key, val, DBM_INSERT) == 1)
+ {
+ error (0, 0, "duplicate key found for `%s'", key.dptr);
+ err++;
+ }
+ }
+ }
+ dbm_close (db);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", temp);
+ if (err)
+ {
+ /* I think that the size of the buffer needed here is
+ just determined by sizeof (CVSROOTADM_MODULES), the
+ filenames created by make_tempfile, and other things that won't
+ overflow. */
+ char dotdir[50], dotpag[50], dotdb[50];
+
+ (void) sprintf (dotdir, "%s.dir", temp);
+ (void) sprintf (dotpag, "%s.pag", temp);
+ (void) sprintf (dotdb, "%s.db", temp);
+ if (unlink_file (dotdir) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", dotdir);
+ if (unlink_file (dotpag) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", dotpag);
+ if (unlink_file (dotdb) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", dotdb);
+ error (1, 0, "DBM creation failed; correct above errors");
+ }
+}
+
+static void
+rename_dbmfile (temp)
+ char *temp;
+{
+ /* I think that the size of the buffer needed here is
+ just determined by sizeof (CVSROOTADM_MODULES), the
+ filenames created by make_tempfile, and other things that won't
+ overflow. */
+ char newdir[50], newpag[50], newdb[50];
+ char dotdir[50], dotpag[50], dotdb[50];
+ char bakdir[50], bakpag[50], bakdb[50];
+
+ int dir1_errno = 0, pag1_errno = 0, db1_errno = 0;
+ int dir2_errno = 0, pag2_errno = 0, db2_errno = 0;
+ int dir3_errno = 0, pag3_errno = 0, db3_errno = 0;
+
+ (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES);
+ (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES);
+ (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES);
+ (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES);
+ (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES);
+ (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES);
+ (void) sprintf (newdir, "%s.dir", temp);
+ (void) sprintf (newpag, "%s.pag", temp);
+ (void) sprintf (newdb, "%s.db", temp);
+
+ (void) chmod (newdir, 0666);
+ (void) chmod (newpag, 0666);
+ (void) chmod (newdb, 0666);
+
+ /* don't mess with me */
+ SIG_beginCrSect ();
+
+ /* rm .#modules.dir .#modules.pag */
+ if (unlink_file (bakdir) < 0)
+ dir1_errno = errno;
+ if (unlink_file (bakpag) < 0)
+ pag1_errno = errno;
+ if (unlink_file (bakdb) < 0)
+ db1_errno = errno;
+
+ /* mv modules.dir .#modules.dir */
+ if (CVS_RENAME (dotdir, bakdir) < 0)
+ dir2_errno = errno;
+ /* mv modules.pag .#modules.pag */
+ if (CVS_RENAME (dotpag, bakpag) < 0)
+ pag2_errno = errno;
+ /* mv modules.db .#modules.db */
+ if (CVS_RENAME (dotdb, bakdb) < 0)
+ db2_errno = errno;
+
+ /* mv "temp".dir modules.dir */
+ if (CVS_RENAME (newdir, dotdir) < 0)
+ dir3_errno = errno;
+ /* mv "temp".pag modules.pag */
+ if (CVS_RENAME (newpag, dotpag) < 0)
+ pag3_errno = errno;
+ /* mv "temp".db modules.db */
+ if (CVS_RENAME (newdb, dotdb) < 0)
+ db3_errno = errno;
+
+ /* OK -- make my day */
+ SIG_endCrSect ();
+
+ /* I didn't want to call error() when we had signals blocked
+ (unnecessary?), but do it now. */
+ if (dir1_errno && !existence_error (dir1_errno))
+ error (0, dir1_errno, "cannot remove %s", bakdir);
+ if (pag1_errno && !existence_error (pag1_errno))
+ error (0, pag1_errno, "cannot remove %s", bakpag);
+ if (db1_errno && !existence_error (db1_errno))
+ error (0, db1_errno, "cannot remove %s", bakdb);
+
+ if (dir2_errno && !existence_error (dir2_errno))
+ error (0, dir2_errno, "cannot remove %s", bakdir);
+ if (pag2_errno && !existence_error (pag2_errno))
+ error (0, pag2_errno, "cannot remove %s", bakpag);
+ if (db2_errno && !existence_error (db2_errno))
+ error (0, db2_errno, "cannot remove %s", bakdb);
+
+ if (dir3_errno && !existence_error (dir3_errno))
+ error (0, dir3_errno, "cannot remove %s", bakdir);
+ if (pag3_errno && !existence_error (pag3_errno))
+ error (0, pag3_errno, "cannot remove %s", bakpag);
+ if (db3_errno && !existence_error (db3_errno))
+ error (0, db3_errno, "cannot remove %s", bakdb);
+}
+
+#endif /* !MY_NDBM */
+
+static void
+rename_rcsfile (temp, real)
+ char *temp;
+ char *real;
+{
+ char *bak;
+ struct stat statbuf;
+ char *rcs;
+
+ /* Set "x" bits if set in original. */
+ rcs = xmalloc (strlen (real) + sizeof (RCSEXT) + 10);
+ (void) sprintf (rcs, "%s%s", real, RCSEXT);
+ statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
+ if (CVS_STAT (rcs, &statbuf) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot stat %s", rcs);
+ free (rcs);
+
+ if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
+ error (0, errno, "warning: cannot chmod %s", temp);
+ bak = xmalloc (strlen (real) + sizeof (BAKPREFIX) + 10);
+ (void) sprintf (bak, "%s%s", BAKPREFIX, real);
+
+ /* rm .#loginfo */
+ if (unlink_file (bak) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", bak);
+
+ /* mv loginfo .#loginfo */
+ if (CVS_RENAME (real, bak) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot rename %s to %s", real, bak);
+
+ /* mv "temp" loginfo */
+ if (CVS_RENAME (temp, real) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot rename %s to %s", temp, real);
+
+ free (bak);
+}
+
+const char *const init_usage[] = {
+ "Usage: %s %s\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+init (argc, argv)
+ int argc;
+ char **argv;
+{
+ /* Name of CVSROOT directory. */
+ char *adm;
+ /* Name of this administrative file. */
+ char *info;
+ /* Name of ,v file for this administrative file. */
+ char *info_v;
+ /* Exit status. */
+ int err;
+
+ const struct admin_file *fileptr;
+
+ umask (cvsumask);
+
+ if (argc == -1 || argc > 1)
+ usage (init_usage);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+
+ ign_setup ();
+ send_init_command ();
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ /* Note: we do *not* create parent directories as needed like the
+ old cvsinit.sh script did. Few utilities do that, and a
+ non-existent parent directory is as likely to be a typo as something
+ which needs to be created. */
+ mkdir_if_needed (CVSroot_directory);
+
+ adm = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + 10);
+ strcpy (adm, CVSroot_directory);
+ strcat (adm, "/");
+ strcat (adm, CVSROOTADM);
+ mkdir_if_needed (adm);
+
+ /* This is needed because we pass "fileptr->filename" not "info"
+ to add_rcs_file below. I think this would be easy to change,
+ thus nuking the need for CVS_CHDIR here, but I haven't looked
+ closely (e.g. see wrappers calls within add_rcs_file). */
+ if ( CVS_CHDIR (adm) < 0)
+ error (1, errno, "cannot change to directory %s", adm);
+
+ /* Make Emptydir so it's there if we need it */
+ make_directory (CVSNULLREPOS);
+
+ /* 80 is long enough for all the administrative file names, plus
+ "/" and so on. */
+ info = xmalloc (strlen (adm) + 80);
+ info_v = xmalloc (strlen (adm) + 80);
+ for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
+ {
+ if (fileptr->contents == NULL)
+ continue;
+ strcpy (info, adm);
+ strcat (info, "/");
+ strcat (info, fileptr->filename);
+ strcpy (info_v, info);
+ strcat (info_v, RCSEXT);
+ if (isfile (info_v))
+ /* We will check out this file in the mkmodules step.
+ Nothing else is required. */
+ ;
+ else
+ {
+ int retcode;
+
+ if (!isfile (info))
+ {
+ FILE *fp;
+ const char * const *p;
+
+ fp = open_file (info, "w");
+ for (p = fileptr->contents; *p != NULL; ++p)
+ if (fputs (*p, fp) < 0)
+ error (1, errno, "cannot write %s", info);
+ if (fclose (fp) < 0)
+ error (1, errno, "cannot close %s", info);
+ }
+ /* The message used to say " of " and fileptr->filename after
+ "initial checkin" but I fail to see the point as we know what
+ file it is from the name. */
+ retcode = add_rcs_file ("initial checkin", info_v,
+ fileptr->filename, "1.1", NULL,
+
+ /* No vendor branch. */
+ NULL, NULL, 0, NULL,
+
+ NULL, 0, NULL);
+ if (retcode != 0)
+ /* add_rcs_file already printed an error message. */
+ err = 1;
+ }
+ }
+
+ /* Turn on history logging by default. The user can remove the file
+ to disable it. */
+ strcpy (info, adm);
+ strcat (info, "/");
+ strcat (info, CVSROOTADM_HISTORY);
+ if (!isfile (info))
+ {
+ FILE *fp;
+
+ fp = open_file (info, "w");
+ if (fclose (fp) < 0)
+ error (1, errno, "cannot close %s", info);
+
+ /* Make the new history file world-writeable, since every CVS
+ user will need to be able to write to it. We use chmod()
+ because xchmod() is too shy. */
+ chmod (info, 0666);
+ }
+
+ free (info);
+ free (info_v);
+
+ mkmodules (adm);
+
+ free (adm);
+ return 0;
+}
diff --git a/gnu/dist/cvs/src/modules.c b/gnu/dist/cvs/src/modules.c
new file mode 100644
index 00000000000..78bc1631660
--- /dev/null
+++ b/gnu/dist/cvs/src/modules.c
@@ -0,0 +1,1074 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS source distribution.
+ *
+ * Modules
+ *
+ * Functions for accessing the modules file.
+ *
+ * The modules file supports basically three formats of lines:
+ * key [options] directory files... [ -x directory [files] ] ...
+ * key [options] directory [ -x directory [files] ] ...
+ * key -a aliases...
+ *
+ * The -a option allows an aliasing step in the parsing of the modules
+ * file. The "aliases" listed on a line following the -a are
+ * processed one-by-one, as if they were specified as arguments on the
+ * command line.
+ */
+
+#include <assert.h>
+#include "cvs.h"
+#include "savecwd.h"
+
+
+/* Defines related to the syntax of the modules file. */
+
+/* Options in modules file. Note that it is OK to use GNU getopt features;
+ we already are arranging to make sure we are using the getopt distributed
+ with CVS. */
+#define CVSMODULE_OPTS "+ad:i:lo:e:s:t:u:"
+
+/* Special delimiter. */
+#define CVSMODULE_SPEC '&'
+
+struct sortrec
+{
+ /* Name of the module, malloc'd. */
+ char *modname;
+ /* If Status variable is set, this is either def_status or the malloc'd
+ name of the status. If Status is not set, the field is left
+ uninitialized. */
+ char *status;
+ /* Pointer to a malloc'd array which contains (1) the raw contents
+ of the options and arguments, excluding comments, (2) a '\0',
+ and (3) the storage for the "comment" field. */
+ char *rest;
+ char *comment;
+};
+
+static int sort_order PROTO((const PTR l, const PTR r));
+static void save_d PROTO((char *k, int ks, char *d, int ds));
+
+
+/*
+ * Open the modules file, and die if the CVSROOT environment variable
+ * was not set. If the modules file does not exist, that's fine, and
+ * a warning message is displayed and a NULL is returned.
+ */
+DBM *
+open_module ()
+{
+ char *mfile;
+ DBM *retval;
+
+ if (CVSroot_original == NULL)
+ {
+ error (0, 0, "must set the CVSROOT environment variable");
+ error (1, 0, "or specify the '-d' global option");
+ }
+ mfile = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM)
+ + sizeof (CVSROOTADM_MODULES) + 20);
+ (void) sprintf (mfile, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_MODULES);
+ retval = dbm_open (mfile, O_RDONLY, 0666);
+ free (mfile);
+ return retval;
+}
+
+/*
+ * Close the modules file, if the open succeeded, that is
+ */
+void
+close_module (db)
+ DBM *db;
+{
+ if (db != NULL)
+ dbm_close (db);
+}
+
+/*
+ * This is the recursive function that processes a module name.
+ * It calls back the passed routine for each directory of a module
+ * It runs the post checkout or post tag proc from the modules file
+ */
+int
+do_module (db, mname, m_type, msg, callback_proc, where,
+ shorten, local_specified, run_module_prog, extra_arg)
+ DBM *db;
+ char *mname;
+ enum mtype m_type;
+ char *msg;
+ CALLBACKPROC callback_proc;
+ char *where;
+ int shorten;
+ int local_specified;
+ int run_module_prog;
+ char *extra_arg;
+{
+ char *checkin_prog = NULL;
+ char *checkout_prog = NULL;
+ char *export_prog = NULL;
+ char *tag_prog = NULL;
+ char *update_prog = NULL;
+ struct saved_cwd cwd;
+ int cwd_saved = 0;
+ char *line;
+ int modargc;
+ int xmodargc;
+ char **modargv;
+ char **xmodargv;
+ /* Found entry from modules file, including options and such. */
+ char *value = NULL;
+ char *zvalue = NULL;
+ char *mwhere = NULL;
+ char *mfile = NULL;
+ char *spec_opt = NULL;
+ char *xvalue = NULL;
+ int alias = 0;
+ datum key, val;
+ char *cp;
+ int c, err = 0;
+ int nonalias_opt = 0;
+
+#ifdef SERVER_SUPPORT
+ int restore_server_dir = 0;
+ char *server_dir_to_restore = NULL;
+ if (trace)
+ {
+ char *buf;
+
+ /* We use cvs_outerr, rather than fprintf to stderr, because
+ this may be called by server code with error_use_protocol
+ set. */
+ buf = xmalloc (100
+ + strlen (mname)
+ + strlen (msg)
+ + (where ? strlen (where) : 0)
+ + (extra_arg ? strlen (extra_arg) : 0));
+ sprintf (buf, "%s-> do_module (%s, %s, %s, %s)\n",
+ CLIENT_SERVER_STR,
+ mname, msg, where ? where : "",
+ extra_arg ? extra_arg : "");
+ cvs_outerr (buf, 0);
+ free (buf);
+ }
+#endif
+
+ /* if this is a directory to ignore, add it to that list */
+ if (mname[0] == '!' && mname[1] != '\0')
+ {
+ ign_dir_add (mname+1);
+ goto do_module_return;
+ }
+
+ /* strip extra stuff from the module name */
+ strip_trailing_slashes (mname);
+
+ /*
+ * Look up the module using the following scheme:
+ * 1) look for mname as a module name
+ * 2) look for mname as a directory
+ * 3) look for mname as a file
+ * 4) take mname up to the first slash and look it up as a module name
+ * (this is for checking out only part of a module)
+ */
+
+ /* look it up as a module name */
+ key.dptr = mname;
+ key.dsize = strlen (key.dptr);
+ if (db != NULL)
+ val = dbm_fetch (db, key);
+ else
+ val.dptr = NULL;
+ if (val.dptr != NULL)
+ {
+ /* null terminate the value XXX - is this space ours? */
+ val.dptr[val.dsize] = '\0';
+
+ /* If the line ends in a comment, strip it off */
+ if ((cp = strchr (val.dptr, '#')) != NULL)
+ *cp = '\0';
+ else
+ cp = val.dptr + val.dsize;
+
+ /* Always strip trailing spaces */
+ while (cp > val.dptr && isspace ((unsigned char) *--cp))
+ *cp = '\0';
+
+ value = val.dptr;
+ mwhere = xstrdup (mname);
+ goto found;
+ }
+ else
+ {
+ char *file;
+ char *attic_file;
+ char *acp;
+ int is_found = 0;
+
+ /* check to see if mname is a directory or file */
+ file = xmalloc (strlen (CVSroot_directory) + strlen (mname) + 10);
+ (void) sprintf (file, "%s/%s", CVSroot_directory, mname);
+ attic_file = xmalloc (strlen (CVSroot_directory) + strlen (mname)
+ + sizeof (CVSATTIC) + sizeof (RCSEXT) + 15);
+ if ((acp = strrchr (mname, '/')) != NULL)
+ {
+ *acp = '\0';
+ (void) sprintf (attic_file, "%s/%s/%s/%s%s", CVSroot_directory,
+ mname, CVSATTIC, acp + 1, RCSEXT);
+ *acp = '/';
+ }
+ else
+ (void) sprintf (attic_file, "%s/%s/%s%s", CVSroot_directory,
+ CVSATTIC, mname, RCSEXT);
+
+ if (isdir (file))
+ {
+ modargv = xmalloc (sizeof (*modargv));
+ modargv[0] = xstrdup (mname);
+ modargc = 1;
+ is_found = 1;
+ }
+ else
+ {
+ (void) strcat (file, RCSEXT);
+ if (isfile (file) || isfile (attic_file))
+ {
+ /* if mname was a file, we have to split it into "dir file" */
+ if ((cp = strrchr (mname, '/')) != NULL && cp != mname)
+ {
+ modargv = xmalloc (2 * sizeof (*modargv));
+ modargv[0] = xmalloc (strlen (mname) + 2);
+ strncpy (modargv[0], mname, cp - mname);
+ modargv[0][cp - mname] = '\0';
+ modargv[1] = xstrdup (cp + 1);
+ modargc = 2;
+ }
+ else
+ {
+ /*
+ * the only '/' at the beginning or no '/' at all
+ * means the file we are interested in is in CVSROOT
+ * itself so the directory should be '.'
+ */
+ if (cp == mname)
+ {
+ /* drop the leading / if specified */
+ modargv = xmalloc (2 * sizeof (*modargv));
+ modargv[0] = xstrdup (".");
+ modargv[1] = xstrdup (mname + 1);
+ modargc = 2;
+ }
+ else
+ {
+ /* otherwise just copy it */
+ modargv = xmalloc (2 * sizeof (*modargv));
+ modargv[0] = xstrdup (".");
+ modargv[1] = xstrdup (mname);
+ modargc = 2;
+ }
+ }
+ is_found = 1;
+ }
+ }
+ free (attic_file);
+ free (file);
+
+ if (is_found)
+ {
+ assert (value == NULL);
+
+ /* OK, we have now set up modargv with the actual
+ file/directory we want to work on. We duplicate a
+ small amount of code here because the vast majority of
+ the code after the "found" label does not pertain to
+ the case where we found a file/directory rather than
+ finding an entry in the modules file. */
+ if (save_cwd (&cwd))
+ error_exit ();
+ cwd_saved = 1;
+
+ err += callback_proc (&modargc, modargv, where, mwhere, mfile,
+ shorten,
+ local_specified, mname, msg);
+
+ free_names (&modargc, modargv);
+
+ /* cd back to where we started. */
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+ cwd_saved = 0;
+
+ goto do_module_return;
+ }
+ }
+
+ /* look up everything to the first / as a module */
+ if (mname[0] != '/' && (cp = strchr (mname, '/')) != NULL)
+ {
+ /* Make the slash the new end of the string temporarily */
+ *cp = '\0';
+ key.dptr = mname;
+ key.dsize = strlen (key.dptr);
+
+ /* do the lookup */
+ if (db != NULL)
+ val = dbm_fetch (db, key);
+ else
+ val.dptr = NULL;
+
+ /* if we found it, clean up the value and life is good */
+ if (val.dptr != NULL)
+ {
+ char *cp2;
+
+ /* null terminate the value XXX - is this space ours? */
+ val.dptr[val.dsize] = '\0';
+
+ /* If the line ends in a comment, strip it off */
+ if ((cp2 = strchr (val.dptr, '#')) != NULL)
+ *cp2 = '\0';
+ else
+ cp2 = val.dptr + val.dsize;
+
+ /* Always strip trailing spaces */
+ while (cp2 > val.dptr && isspace ((unsigned char) *--cp2))
+ *cp2 = '\0';
+
+ value = val.dptr;
+
+ /* mwhere gets just the module name */
+ mwhere = xstrdup (mname);
+ mfile = cp + 1;
+
+ /* put the / back in mname */
+ *cp = '/';
+
+ goto found;
+ }
+
+ /* put the / back in mname */
+ *cp = '/';
+ }
+
+ /* if we got here, we couldn't find it using our search, so give up */
+ error (0, 0, "cannot find module `%s' - ignored", mname);
+ err++;
+ goto do_module_return;
+
+
+ /*
+ * At this point, we found what we were looking for in one
+ * of the many different forms.
+ */
+ found:
+
+ /* remember where we start */
+ if (save_cwd (&cwd))
+ error_exit ();
+ cwd_saved = 1;
+
+ /* copy value to our own string since if we go recursive we'll be
+ really screwed if we do another dbm lookup */
+ assert (value != NULL);
+ zvalue = xstrdup (value);
+ value = zvalue;
+
+ /* search the value for the special delimiter and save for later */
+ if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL)
+ {
+ *cp = '\0'; /* null out the special char */
+ spec_opt = cp + 1; /* save the options for later */
+
+ /* strip whitespace if necessary */
+ while (cp > value && isspace ((unsigned char) *--cp))
+ *cp = '\0';
+ }
+
+ /* don't do special options only part of a module was specified */
+ if (mfile != NULL)
+ spec_opt = NULL;
+
+ /*
+ * value now contains one of the following:
+ * 1) dir
+ * 2) dir file
+ * 3) the value from modules without any special args
+ * [ args ] dir [file] [file] ...
+ * or -a module [ module ] ...
+ */
+
+ /* Put the value on a line with XXX prepended for getopt to eat */
+ line = xmalloc (strlen (value) + 10);
+ strcpy(line, "XXX ");
+ strcpy(line + 4, value);
+
+ /* turn the line into an argv[] array */
+ line2argv (&xmodargc, &xmodargv, line, " \t");
+ free (line);
+ modargc = xmodargc;
+ modargv = xmodargv;
+
+ /* parse the args */
+ optind = 0;
+ while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1)
+ {
+ switch (c)
+ {
+ case 'a':
+ alias = 1;
+ break;
+ case 'd':
+ if (mwhere)
+ free (mwhere);
+ mwhere = xstrdup (optarg);
+ nonalias_opt = 1;
+ break;
+ case 'i':
+ if (checkin_prog)
+ free (checkin_prog);
+ checkin_prog = xstrdup (optarg);
+ nonalias_opt = 1;
+ break;
+ case 'l':
+ local_specified = 1;
+ nonalias_opt = 1;
+ break;
+ case 'o':
+ if (checkout_prog)
+ free (checkout_prog);
+ checkout_prog = xstrdup (optarg);
+ nonalias_opt = 1;
+ break;
+ case 'e':
+ if (export_prog)
+ free (export_prog);
+ export_prog = xstrdup (optarg);
+ nonalias_opt = 1;
+ break;
+ case 't':
+ if (tag_prog)
+ free (tag_prog);
+ tag_prog = xstrdup (optarg);
+ nonalias_opt = 1;
+ break;
+ case 'u':
+ if (update_prog)
+ free (update_prog);
+ update_prog = xstrdup (optarg);
+ nonalias_opt = 1;
+ break;
+ case '?':
+ error (0, 0,
+ "modules file has invalid option for key %s value %s",
+ key.dptr, val.dptr);
+ err++;
+ goto do_module_return;
+ }
+ }
+ modargc -= optind;
+ modargv += optind;
+ if (modargc == 0 && spec_opt == NULL)
+ {
+ error (0, 0, "modules file missing directory for module %s", mname);
+ ++err;
+ goto do_module_return;
+ }
+
+ if (alias && nonalias_opt)
+ {
+ /* The documentation has never said it is legal to specify
+ -a along with another option. And I believe that in the past
+ CVS has ignored the options other than -a, more or less, in this
+ situation. */
+ error (0, 0, "\
+-a cannot be specified in the modules file along with other options");
+ ++err;
+ goto do_module_return;
+ }
+
+ /* if this was an alias, call ourselves recursively for each module */
+ if (alias)
+ {
+ int i;
+
+ for (i = 0; i < modargc; i++)
+ {
+ if (strcmp (mname, modargv[i]) == 0)
+ error (0, 0,
+ "module `%s' in modules file contains infinite loop",
+ mname);
+ else
+ err += do_module (db, modargv[i], m_type, msg, callback_proc,
+ where, shorten, local_specified,
+ run_module_prog, extra_arg);
+ }
+ goto do_module_return;
+ }
+
+ if (mfile != NULL && modargc > 1)
+ {
+ error (0, 0, "\
+module `%s' is a request for a file in a module which is not a directory",
+ mname);
+ ++err;
+ goto do_module_return;
+ }
+
+ /* otherwise, process this module */
+ if (modargc > 0)
+ {
+ err += callback_proc (&modargc, modargv, where, mwhere, mfile, shorten,
+ local_specified, mname, msg);
+ }
+ else
+ {
+ /*
+ * we had nothing but special options, so we must
+ * make the appropriate directory and cd to it
+ */
+ char *dir;
+
+ /* XXX - XXX - MAJOR HACK - DO NOT SHIP - this needs to
+ be !pipeout, but we don't know that here yet */
+ if (!run_module_prog)
+ goto do_special;
+
+ dir = where ? where : (mwhere ? mwhere : mname);
+ /* XXX - think about making null repositories at each dir here
+ instead of just at the bottom */
+ make_directories (dir);
+ if ( CVS_CHDIR (dir) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", dir);
+ spec_opt = NULL;
+ err++;
+ goto do_special;
+ }
+ if (!isfile (CVSADM))
+ {
+ char *nullrepos;
+
+ nullrepos = emptydir_name ();
+
+ Create_Admin (".", dir,
+ nullrepos, (char *) NULL, (char *) NULL, 0, 0);
+ if (!noexec)
+ {
+ FILE *fp;
+
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (dir, nullrepos);
+#endif
+ }
+ free (nullrepos);
+ }
+ }
+
+ /* if there were special include args, process them now */
+
+ do_special:
+
+ free_names (&xmodargc, xmodargv);
+
+ /* blow off special options if -l was specified */
+ if (local_specified)
+ spec_opt = NULL;
+
+#ifdef SERVER_SUPPORT
+ /* We want to check out into the directory named by the module.
+ So we set a global variable which tells the server to glom that
+ directory name onto the front. A cleaner approach would be some
+ way of passing it down to the recursive call, through the
+ callback_proc, to start_recursion, and then into the update_dir in
+ the struct file_info. That way the "Updating foo" message could
+ print the actual directory we are checking out into.
+
+ For local CVS, this is handled by the chdir call above
+ (directly or via the callback_proc). */
+ if (server_active && spec_opt != NULL)
+ {
+ char *change_to;
+
+ change_to = where ? where : (mwhere ? mwhere : mname);
+ server_dir_to_restore = server_dir;
+ restore_server_dir = 1;
+ server_dir =
+ xmalloc ((server_dir_to_restore != NULL
+ ? strlen (server_dir_to_restore)
+ : 0)
+ + strlen (change_to)
+ + 5);
+ server_dir[0] = '\0';
+ if (server_dir_to_restore != NULL)
+ {
+ strcat (server_dir, server_dir_to_restore);
+ strcat (server_dir, "/");
+ }
+ strcat (server_dir, change_to);
+ }
+#endif
+
+ while (spec_opt != NULL)
+ {
+ char *next_opt;
+
+ cp = strchr (spec_opt, CVSMODULE_SPEC);
+ if (cp != NULL)
+ {
+ /* save the beginning of the next arg */
+ next_opt = cp + 1;
+
+ /* strip whitespace off the end */
+ do
+ *cp = '\0';
+ while (cp > spec_opt && isspace ((unsigned char) *--cp));
+ }
+ else
+ next_opt = NULL;
+
+ /* strip whitespace from front */
+ while (isspace ((unsigned char) *spec_opt))
+ spec_opt++;
+
+ if (*spec_opt == '\0')
+ error (0, 0, "Mal-formed %c option for module %s - ignored",
+ CVSMODULE_SPEC, mname);
+ else
+ err += do_module (db, spec_opt, m_type, msg, callback_proc,
+ (char *) NULL, 0, local_specified,
+ run_module_prog, extra_arg);
+ spec_opt = next_opt;
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active && restore_server_dir)
+ {
+ free (server_dir);
+ server_dir = server_dir_to_restore;
+ }
+#endif
+
+ /* write out the checkin/update prog files if necessary */
+#ifdef SERVER_SUPPORT
+ if (err == 0 && !noexec && m_type == CHECKOUT && server_expanding)
+ {
+ if (checkin_prog != NULL)
+ server_prog (where ? where : mname, checkin_prog, PROG_CHECKIN);
+ if (update_prog != NULL)
+ server_prog (where ? where : mname, update_prog, PROG_UPDATE);
+ }
+ else
+#endif
+ if (err == 0 && !noexec && m_type == CHECKOUT && run_module_prog)
+ {
+ FILE *fp;
+
+ if (checkin_prog != NULL)
+ {
+ fp = open_file (CVSADM_CIPROG, "w+");
+ (void) fprintf (fp, "%s\n", checkin_prog);
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_CIPROG);
+ }
+ if (update_prog != NULL)
+ {
+ fp = open_file (CVSADM_UPROG, "w+");
+ (void) fprintf (fp, "%s\n", update_prog);
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_UPROG);
+ }
+ }
+
+ /* cd back to where we started */
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+ cwd_saved = 0;
+
+ /* run checkout or tag prog if appropriate */
+ if (err == 0 && run_module_prog)
+ {
+ if ((m_type == TAG && tag_prog != NULL) ||
+ (m_type == CHECKOUT && checkout_prog != NULL) ||
+ (m_type == EXPORT && export_prog != NULL))
+ {
+ /*
+ * If a relative pathname is specified as the checkout, tag
+ * or export proc, try to tack on the current "where" value.
+ * if we can't find a matching program, just punt and use
+ * whatever is specified in the modules file.
+ */
+ char *real_prog = NULL;
+ char *prog = (m_type == TAG ? tag_prog :
+ (m_type == CHECKOUT ? checkout_prog : export_prog));
+ char *real_where = (where != NULL ? where : mwhere);
+ char *expanded_path;
+
+ if ((*prog != '/') && (*prog != '.'))
+ {
+ real_prog = xmalloc (strlen (real_where) + strlen (prog)
+ + 10);
+ (void) sprintf (real_prog, "%s/%s", real_where, prog);
+ if (isfile (real_prog))
+ prog = real_prog;
+ }
+
+ /* XXX can we determine the line number for this entry??? */
+ expanded_path = expand_path (prog, "modules", 0);
+ if (expanded_path != NULL)
+ {
+ run_setup (expanded_path);
+ run_arg (real_where);
+
+ if (extra_arg)
+ run_arg (extra_arg);
+
+ if (!quiet)
+ {
+ cvs_output (program_name, 0);
+ cvs_output (" ", 1);
+ cvs_output (command_name, 0);
+ cvs_output (": Executing '", 0);
+ run_print (stdout);
+ cvs_output ("'\n", 0);
+ }
+ err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ free (expanded_path);
+ }
+ free (real_prog);
+ }
+ }
+
+ do_module_return:
+ /* clean up */
+ if (mwhere)
+ free (mwhere);
+ if (checkin_prog)
+ free (checkin_prog);
+ if (checkout_prog)
+ free (checkout_prog);
+ if (export_prog)
+ free (export_prog);
+ if (tag_prog)
+ free (tag_prog);
+ if (update_prog)
+ free (update_prog);
+ if (cwd_saved)
+ free_cwd (&cwd);
+ if (zvalue != NULL)
+ free (zvalue);
+
+ if (xvalue != NULL)
+ free (xvalue);
+ return (err);
+}
+
+/* - Read all the records from the modules database into an array.
+ - Sort the array depending on what format is desired.
+ - Print the array in the format desired.
+
+ Currently, there are only two "desires":
+
+ 1. Sort by module name and format the whole entry including switches,
+ files and the comment field: (Including aliases)
+
+ modulename -s switches, one per line, even if
+ -i it has many switches.
+ Directories and files involved, formatted
+ to cover multiple lines if necessary.
+ # Comment, also formatted to cover multiple
+ # lines if necessary.
+
+ 2. Sort by status field string and print: (*not* including aliases)
+
+ modulename STATUS Directories and files involved, formatted
+ to cover multiple lines if necessary.
+ # Comment, also formatted to cover multiple
+ # lines if necessary.
+*/
+
+static struct sortrec *s_head;
+
+static int s_max = 0; /* Number of elements allocated */
+static int s_count = 0; /* Number of elements used */
+
+static int Status; /* Nonzero if the user is
+ interested in status
+ information as well as
+ module name */
+static char def_status[] = "NONE";
+
+/* Sort routine for qsort:
+ - If we want the "Status" field to be sorted, check it first.
+ - Then compare the "module name" fields. Since they are unique, we don't
+ have to look further.
+*/
+static int
+sort_order (l, r)
+ const PTR l;
+ const PTR r;
+{
+ int i;
+ const struct sortrec *left = (const struct sortrec *) l;
+ const struct sortrec *right = (const struct sortrec *) r;
+
+ if (Status)
+ {
+ /* If Sort by status field, compare them. */
+ if ((i = strcmp (left->status, right->status)) != 0)
+ return (i);
+ }
+ return (strcmp (left->modname, right->modname));
+}
+
+static void
+save_d (k, ks, d, ds)
+ char *k;
+ int ks;
+ char *d;
+ int ds;
+{
+ char *cp, *cp2;
+ struct sortrec *s_rec;
+
+ if (Status && *d == '-' && *(d + 1) == 'a')
+ return; /* We want "cvs co -s" and it is an alias! */
+
+ if (s_count == s_max)
+ {
+ s_max += 64;
+ s_head = (struct sortrec *) xrealloc ((char *) s_head, s_max * sizeof (*s_head));
+ }
+ s_rec = &s_head[s_count];
+ s_rec->modname = cp = xmalloc (ks + 1);
+ (void) strncpy (cp, k, ks);
+ *(cp + ks) = '\0';
+
+ s_rec->rest = cp2 = xmalloc (ds + 1);
+ cp = d;
+ *(cp + ds) = '\0'; /* Assumes an extra byte at end of static dbm buffer */
+
+ while (isspace ((unsigned char) *cp))
+ cp++;
+ /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
+ while (*cp)
+ {
+ if (isspace ((unsigned char) *cp))
+ {
+ *cp2++ = ' ';
+ while (isspace ((unsigned char) *cp))
+ cp++;
+ }
+ else
+ *cp2++ = *cp++;
+ }
+ *cp2 = '\0';
+
+ /* Look for the "-s statusvalue" text */
+ if (Status)
+ {
+ s_rec->status = def_status;
+
+ for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2)
+ {
+ if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ')
+ {
+ char *status_start;
+
+ cp2 += 3;
+ status_start = cp2;
+ while (*cp2 != ' ' && *cp2 != '\0')
+ cp2++;
+ s_rec->status = xmalloc (cp2 - status_start + 1);
+ strncpy (s_rec->status, status_start, cp2 - status_start);
+ s_rec->status[cp2 - status_start] = '\0';
+ cp = cp2;
+ break;
+ }
+ }
+ }
+ else
+ cp = s_rec->rest;
+
+ /* Find comment field, clean up on all three sides & compress blanks */
+ if ((cp2 = cp = strchr (cp, '#')) != NULL)
+ {
+ if (*--cp2 == ' ')
+ *cp2 = '\0';
+ if (*++cp == ' ')
+ cp++;
+ s_rec->comment = cp;
+ }
+ else
+ s_rec->comment = "";
+
+ s_count++;
+}
+
+/* Print out the module database as we know it. If STATUS is
+ non-zero, print out status information for each module. */
+
+void
+cat_module (status)
+ int status;
+{
+ DBM *db;
+ datum key, val;
+ int i, c, wid, argc, cols = 80, indent, fill;
+ int moduleargc;
+ struct sortrec *s_h;
+ char *cp, *cp2, **argv;
+ char **moduleargv;
+
+ Status = status;
+
+ /* Read the whole modules file into allocated records */
+ if (!(db = open_module ()))
+ error (1, 0, "failed to open the modules file");
+
+ for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db))
+ {
+ val = dbm_fetch (db, key);
+ if (val.dptr != NULL)
+ save_d (key.dptr, key.dsize, val.dptr, val.dsize);
+ }
+
+ close_module (db);
+
+ /* Sort the list as requested */
+ qsort ((PTR) s_head, s_count, sizeof (struct sortrec), sort_order);
+
+ /*
+ * Run through the sorted array and format the entries
+ * indent = space for modulename + space for status field
+ */
+ indent = 12 + (status * 12);
+ fill = cols - (indent + 2);
+ for (s_h = s_head, i = 0; i < s_count; i++, s_h++)
+ {
+ char *line;
+
+ /* Print module name (and status, if wanted) */
+ line = xmalloc (strlen (s_h->modname) + 15);
+ sprintf (line, "%-12s", s_h->modname);
+ cvs_output (line, 0);
+ free (line);
+ if (status)
+ {
+ line = xmalloc (strlen (s_h->status) + 15);
+ sprintf (line, " %-11s", s_h->status);
+ cvs_output (line, 0);
+ free (line);
+ }
+
+ line = xmalloc (strlen (s_h->modname) + strlen (s_h->rest) + 15);
+ /* Parse module file entry as command line and print options */
+ (void) sprintf (line, "%s %s", s_h->modname, s_h->rest);
+ line2argv (&moduleargc, &moduleargv, line, " \t");
+ free (line);
+ argc = moduleargc;
+ argv = moduleargv;
+
+ optind = 0;
+ wid = 0;
+ while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1)
+ {
+ if (!status)
+ {
+ if (c == 'a' || c == 'l')
+ {
+ char buf[5];
+
+ sprintf (buf, " -%c", c);
+ cvs_output (buf, 0);
+ wid += 3; /* Could just set it to 3 */
+ }
+ else
+ {
+ char buf[10];
+
+ if (strlen (optarg) + 4 + wid > (unsigned) fill)
+ {
+ int j;
+
+ cvs_output ("\n", 1);
+ for (j = 0; j < indent; ++j)
+ cvs_output (" ", 1);
+ wid = 0;
+ }
+ sprintf (buf, " -%c ", c);
+ cvs_output (buf, 0);
+ cvs_output (optarg, 0);
+ wid += strlen (optarg) + 4;
+ }
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Format and Print all the files and directories */
+ for (; argc--; argv++)
+ {
+ if (strlen (*argv) + wid > (unsigned) fill)
+ {
+ int j;
+
+ cvs_output ("\n", 1);
+ for (j = 0; j < indent; ++j)
+ cvs_output (" ", 1);
+ wid = 0;
+ }
+ cvs_output (" ", 1);
+ cvs_output (*argv, 0);
+ wid += strlen (*argv) + 1;
+ }
+ cvs_output ("\n", 1);
+
+ /* Format the comment field -- save_d (), compressed spaces */
+ for (cp2 = cp = s_h->comment; *cp; cp2 = cp)
+ {
+ int j;
+
+ for (j = 0; j < indent; ++j)
+ cvs_output (" ", 1);
+ cvs_output (" # ", 0);
+ if (strlen (cp2) < (unsigned) (fill - 2))
+ {
+ cvs_output (cp2, 0);
+ cvs_output ("\n", 1);
+ break;
+ }
+ cp += fill - 2;
+ while (*cp != ' ' && cp > cp2)
+ cp--;
+ if (cp == cp2)
+ {
+ cvs_output (cp2, 0);
+ cvs_output ("\n", 1);
+ break;
+ }
+
+ *cp++ = '\0';
+ cvs_output (cp2, 0);
+ cvs_output ("\n", 1);
+ }
+
+ free_names(&moduleargc, moduleargv);
+ /* FIXME-leak: here is where we would free s_h->modname, s_h->rest,
+ and if applicable, s_h->status. Not exactly a memory leak,
+ in the sense that we are about to exit(), but may be worth
+ noting if we ever do a multithreaded server or something of
+ the sort. */
+ }
+ /* FIXME-leak: as above, here is where we would free s_head. */
+}
diff --git a/gnu/dist/cvs/src/myndbm.c b/gnu/dist/cvs/src/myndbm.c
new file mode 100644
index 00000000000..f674ac11d9f
--- /dev/null
+++ b/gnu/dist/cvs/src/myndbm.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * A simple ndbm-emulator for CVS. It parses a text file of the format:
+ *
+ * key value
+ *
+ * at dbm_open time, and loads the entire file into memory. As such, it is
+ * probably only good for fairly small modules files. Ours is about 30K in
+ * size, and this code works fine.
+ */
+
+#include <assert.h>
+#include "cvs.h"
+#include "getline.h"
+
+#ifdef MY_NDBM
+
+static void mydbm_load_file PROTO ((FILE *, List *));
+
+/* Returns NULL on error in which case errno has been set to indicate
+ the error. Can also call error() itself. */
+/* ARGSUSED */
+DBM *
+mydbm_open (file, flags, mode)
+ char *file;
+ int flags;
+ int mode;
+{
+ FILE *fp;
+ DBM *db;
+
+ fp = CVS_FOPEN (file, FOPEN_BINARY_READ);
+ if (fp == NULL && !(existence_error (errno) && (flags & O_CREAT)))
+ return ((DBM *) 0);
+
+ db = (DBM *) xmalloc (sizeof (*db));
+ db->dbm_list = getlist ();
+ db->modified = 0;
+ db->name = xstrdup (file);
+
+ if (fp != NULL)
+ {
+ mydbm_load_file (fp, db->dbm_list);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", file);
+ }
+ return (db);
+}
+
+static int write_item PROTO ((Node *, void *));
+
+static int
+write_item (node, data)
+ Node *node;
+ void *data;
+{
+ FILE *fp = (FILE *)data;
+ fputs (node->key, fp);
+ fputs (" ", fp);
+ fputs (node->data, fp);
+ fputs ("\012", fp);
+ return 0;
+}
+
+void
+mydbm_close (db)
+ DBM *db;
+{
+ if (db->modified)
+ {
+ FILE *fp;
+ fp = CVS_FOPEN (db->name, FOPEN_BINARY_WRITE);
+ if (fp == NULL)
+ error (1, errno, "cannot write %s", db->name);
+ walklist (db->dbm_list, write_item, (void *)fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", db->name);
+ }
+ free (db->name);
+ dellist (&db->dbm_list);
+ free ((char *) db);
+}
+
+datum
+mydbm_fetch (db, key)
+ DBM *db;
+ datum key;
+{
+ Node *p;
+ char *s;
+ datum val;
+
+ /* make sure it's null-terminated */
+ s = xmalloc (key.dsize + 1);
+ (void) strncpy (s, key.dptr, key.dsize);
+ s[key.dsize] = '\0';
+
+ p = findnode (db->dbm_list, s);
+ if (p)
+ {
+ val.dptr = p->data;
+ val.dsize = strlen (p->data);
+ }
+ else
+ {
+ val.dptr = (char *) NULL;
+ val.dsize = 0;
+ }
+ free (s);
+ return (val);
+}
+
+datum
+mydbm_firstkey (db)
+ DBM *db;
+{
+ Node *head, *p;
+ datum key;
+
+ head = db->dbm_list->list;
+ p = head->next;
+ if (p != head)
+ {
+ key.dptr = p->key;
+ key.dsize = strlen (p->key);
+ }
+ else
+ {
+ key.dptr = (char *) NULL;
+ key.dsize = 0;
+ }
+ db->dbm_next = p->next;
+ return (key);
+}
+
+datum
+mydbm_nextkey (db)
+ DBM *db;
+{
+ Node *head, *p;
+ datum key;
+
+ head = db->dbm_list->list;
+ p = db->dbm_next;
+ if (p != head)
+ {
+ key.dptr = p->key;
+ key.dsize = strlen (p->key);
+ }
+ else
+ {
+ key.dptr = (char *) NULL;
+ key.dsize = 0;
+ }
+ db->dbm_next = p->next;
+ return (key);
+}
+
+/* Note: only updates the in-memory copy, which is written out at
+ mydbm_close time. Note: Also differs from DBM in that on duplication,
+ it gives a warning, rather than either DBM_INSERT or DBM_REPLACE
+ behavior. */
+int
+mydbm_store (db, key, value, flags)
+ DBM *db;
+ datum key;
+ datum value;
+ int flags;
+{
+ Node *node;
+
+ node = getnode ();
+ node->type = NDBMNODE;
+
+ node->key = xmalloc (key.dsize + 1);
+ strncpy (node->key, key.dptr, key.dsize);
+ node->key[key.dsize] = '\0';
+
+ node->data = xmalloc (value.dsize + 1);
+ strncpy (node->data, value.dptr, value.dsize);
+ node->data[value.dsize] = '\0';
+
+ db->modified = 1;
+ if (addnode (db->dbm_list, node) == -1)
+ {
+ error (0, 0, "attempt to insert duplicate key `%s'", node->key);
+ freenode (node);
+ return 0;
+ }
+ return 0;
+}
+
+static void
+mydbm_load_file (fp, list)
+ FILE *fp;
+ List *list;
+{
+ char *line = NULL;
+ size_t line_size;
+ char *value;
+ size_t value_allocated;
+ char *cp, *vp;
+ int cont;
+ int line_length;
+
+ value_allocated = 1;
+ value = xmalloc (value_allocated);
+
+ cont = 0;
+ while ((line_length = getstr (&line, &line_size, fp, '\012', 0)) >= 0)
+ {
+ if (line_length > 0 && line[line_length - 1] == '\012')
+ {
+ /* Strip the newline. */
+ --line_length;
+ line[line_length] = '\0';
+ }
+ if (line_length > 0 && line[line_length - 1] == '\015')
+ {
+ /* If the file (e.g. modules) was written on an NT box, it will
+ contain CRLF at the ends of lines. Strip them (we can't do
+ this by opening the file in text mode because we might be
+ running on unix). */
+ --line_length;
+ line[line_length] = '\0';
+ }
+
+ /*
+ * Add the line to the value, at the end if this is a continuation
+ * line; otherwise at the beginning, but only after any trailing
+ * backslash is removed.
+ */
+ if (!cont)
+ value[0] = '\0';
+
+ /*
+ * See if the line we read is a continuation line, and strip the
+ * backslash if so.
+ */
+ if (line_length > 0)
+ cp = &line[line_length - 1];
+ else
+ cp = line;
+ if (*cp == '\\')
+ {
+ cont = 1;
+ *cp = '\0';
+ --line_length;
+ }
+ else
+ {
+ cont = 0;
+ }
+ expand_string (&value,
+ &value_allocated,
+ strlen (value) + line_length + 5);
+ strcat (value, line);
+
+ if (value[0] == '#')
+ continue; /* comment line */
+ vp = value;
+ while (*vp && isspace ((unsigned char) *vp))
+ vp++;
+ if (*vp == '\0')
+ continue; /* empty line */
+
+ /*
+ * If this was not a continuation line, add the entry to the database
+ */
+ if (!cont)
+ {
+ Node *p = getnode ();
+ char *kp;
+
+ kp = vp;
+ while (*vp && !isspace ((unsigned char) *vp))
+ vp++;
+ *vp++ = '\0'; /* NULL terminate the key */
+ p->type = NDBMNODE;
+ p->key = xstrdup (kp);
+ while (*vp && isspace ((unsigned char) *vp))
+ vp++; /* skip whitespace to value */
+ if (*vp == '\0')
+ {
+ error (0, 0, "warning: NULL value for key `%s'", p->key);
+ freenode (p);
+ continue;
+ }
+ p->data = xstrdup (vp);
+ if (addnode (list, p) == -1)
+ {
+ error (0, 0, "duplicate key found for `%s'", p->key);
+ freenode (p);
+ }
+ }
+ }
+ if (line_length < 0 && !feof (fp))
+ /* FIXME: should give the name of the file. */
+ error (0, errno, "cannot read file in mydbm_load_file");
+
+ free (line);
+ free (value);
+}
+
+#endif /* MY_NDBM */
diff --git a/gnu/dist/cvs/src/no_diff.c b/gnu/dist/cvs/src/no_diff.c
new file mode 100644
index 00000000000..dfca372f5cb
--- /dev/null
+++ b/gnu/dist/cvs/src/no_diff.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * No Difference
+ *
+ * The user file looks modified judging from its time stamp; however it needn't
+ * be. No_difference() finds out whether it is or not. If it is not, it
+ * updates the administration.
+ *
+ * returns 0 if no differences are found and non-zero otherwise
+ */
+
+#include "cvs.h"
+
+int
+No_Difference (finfo, vers)
+ struct file_info *finfo;
+ Vers_TS *vers;
+{
+ Node *p;
+ int ret;
+ char *ts, *options;
+ int retcode = 0;
+ char *tocvsPath;
+
+ /* If ts_user is "Is-modified", we can only conclude the files are
+ different (since we don't have the file's contents). */
+ if (vers->ts_user != NULL
+ && strcmp (vers->ts_user, "Is-modified") == 0)
+ return -1;
+
+ if (!vers->srcfile || !vers->srcfile->path)
+ return (-1); /* different since we couldn't tell */
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If special files are in use, then any mismatch of file metadata
+ information also means that the files should be considered different. */
+ if (preserve_perms && special_file_mismatch (finfo, vers->vn_user, NULL))
+ return 1;
+#endif
+
+ if (vers->entdata && vers->entdata->options)
+ options = xstrdup (vers->entdata->options);
+ else
+ options = xstrdup ("");
+
+ tocvsPath = wrap_tocvs_process_file (finfo->file);
+ retcode = RCS_cmp_file (vers->srcfile, vers->vn_user, options,
+ tocvsPath == NULL ? finfo->file : tocvsPath);
+ if (retcode == 0)
+ {
+ /* no difference was found, so fix the entries file */
+ ts = time_stamp (finfo->file);
+ Register (finfo->entries, finfo->file,
+ vers->vn_user ? vers->vn_user : vers->vn_rcs, ts,
+ options, vers->tag, vers->date, (char *) 0);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* We need to update the entries line on the client side. */
+ server_update_entries
+ (finfo->file, finfo->update_dir, finfo->repository, SERVER_UPDATED);
+ }
+#endif
+ free (ts);
+
+ /* update the entdata pointer in the vers_ts structure */
+ p = findnode (finfo->entries, finfo->file);
+ vers->entdata = (Entnode *) p->data;
+
+ ret = 0;
+ }
+ else
+ ret = 1; /* files were really different */
+
+ if (tocvsPath)
+ {
+ /* Need to call unlink myself because the noexec variable
+ * has been set to 1. */
+ if (trace)
+ (void) fprintf (stderr, "%s-> unlink (%s)\n",
+ CLIENT_SERVER_STR, tocvsPath);
+ if ( CVS_UNLINK (tocvsPath) < 0)
+ error (0, errno, "could not remove %s", tocvsPath);
+ }
+
+ free (options);
+ return (ret);
+}
diff --git a/gnu/dist/cvs/src/parseinfo.c b/gnu/dist/cvs/src/parseinfo.c
new file mode 100644
index 00000000000..1c1ab70c87e
--- /dev/null
+++ b/gnu/dist/cvs/src/parseinfo.c
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+#include <assert.h>
+
+/*
+ * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for
+ * the first line in the file that matches the REPOSITORY, or if ALL != 0, any lines
+ * matching "ALL", or if no lines match, the last line matching "DEFAULT".
+ *
+ * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
+ */
+int
+Parse_Info (infofile, repository, callproc, all)
+ char *infofile;
+ char *repository;
+ CALLPROC callproc;
+ int all;
+{
+ int err = 0;
+ FILE *fp_info;
+ char *infopath;
+ char *line = NULL;
+ size_t line_allocated = 0;
+ char *default_value = NULL;
+ char *expanded_value= NULL;
+ int callback_done, line_number;
+ char *cp, *exp, *value, *srepos;
+ const char *regex_err;
+
+ if (CVSroot_original == NULL)
+ {
+ /* XXX - should be error maybe? */
+ error (0, 0, "CVSROOT variable not set");
+ return (1);
+ }
+
+ /* find the info file and open it */
+ infopath = xmalloc (strlen (CVSroot_directory)
+ + strlen (infofile)
+ + sizeof (CVSROOTADM)
+ + 10);
+ (void) sprintf (infopath, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, infofile);
+ fp_info = CVS_FOPEN (infopath, "r");
+ if (fp_info == NULL)
+ {
+ /* If no file, don't do anything special. */
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", infopath);
+ free (infopath);
+ return 0;
+ }
+
+ /* strip off the CVSROOT if repository was absolute */
+ srepos = Short_Repository (repository);
+
+ if (trace)
+ (void) fprintf (stderr, " -> ParseInfo(%s, %s, %s)\n",
+ infopath, srepos, all ? "ALL" : "not ALL");
+
+ /* search the info file for lines that match */
+ callback_done = line_number = 0;
+ while (getline (&line, &line_allocated, fp_info) >= 0)
+ {
+ line_number++;
+
+ /* skip lines starting with # */
+ if (line[0] == '#')
+ continue;
+
+ /* skip whitespace at beginning of line */
+ for (cp = line; *cp && isspace ((unsigned char) *cp); cp++)
+ ;
+
+ /* if *cp is null, the whole line was blank */
+ if (*cp == '\0')
+ continue;
+
+ /* the regular expression is everything up to the first space */
+ for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++)
+ ;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* skip whitespace up to the start of the matching value */
+ while (*cp && isspace ((unsigned char) *cp))
+ cp++;
+
+ /* no value to match with the regular expression is an error */
+ if (*cp == '\0')
+ {
+ error (0, 0, "syntax error at line %d file %s; ignored",
+ line_number, infofile);
+ continue;
+ }
+ value = cp;
+
+ /* strip the newline off the end of the value */
+ if ((cp = strrchr (value, '\n')) != NULL)
+ *cp = '\0';
+
+ if (expanded_value != NULL)
+ free (expanded_value);
+ expanded_value = expand_path (value, infofile, line_number);
+ if (!expanded_value)
+ {
+ continue;
+ }
+
+ /*
+ * At this point, exp points to the regular expression, and value
+ * points to the value to call the callback routine with. Evaluate
+ * the regular expression against srepos and callback with the value
+ * if it matches.
+ */
+
+ /* save the default value so we have it later if we need it */
+ if (strcmp (exp, "DEFAULT") == 0)
+ {
+ /* Is it OK to silently ignore all but the last DEFAULT
+ expression? */
+ if (default_value != NULL)
+ free (default_value);
+ default_value = xstrdup (expanded_value);
+ continue;
+ }
+
+ /*
+ * For a regular expression of "ALL", do the callback always We may
+ * execute lots of ALL callbacks in addition to *one* regular matching
+ * callback or default
+ */
+ if (strcmp (exp, "ALL") == 0)
+ {
+ if (all)
+ err += callproc (repository, expanded_value);
+ else
+ error(0, 0, "Keyword `ALL' is ignored at line %d in %s file",
+ line_number, infofile);
+ continue;
+ }
+
+ if (callback_done)
+ /* only first matching, plus "ALL"'s */
+ continue;
+
+ /* see if the repository matched this regular expression */
+ if ((regex_err = re_comp (exp)) != NULL)
+ {
+ error (0, 0, "bad regular expression at line %d file %s: %s",
+ line_number, infofile, regex_err);
+ continue;
+ }
+ if (re_exec (srepos) == 0)
+ continue; /* no match */
+
+ /* it did, so do the callback and note that we did one */
+ err += callproc (repository, expanded_value);
+ callback_done = 1;
+ }
+ if (ferror (fp_info))
+ error (0, errno, "cannot read %s", infopath);
+ if (fclose (fp_info) < 0)
+ error (0, errno, "cannot close %s", infopath);
+
+ /* if we fell through and didn't callback at all, do the default */
+ if (callback_done == 0 && default_value != NULL)
+ err += callproc (repository, default_value);
+
+ /* free up space if necessary */
+ if (default_value != NULL)
+ free (default_value);
+ if (expanded_value != NULL)
+ free (expanded_value);
+ free (infopath);
+ if (line != NULL)
+ free (line);
+
+ return (err);
+}
+
+
+/* Parse the CVS config file. The syntax right now is a bit ad hoc
+ but tries to draw on the best or more common features of the other
+ *info files and various unix (or non-unix) config file syntaxes.
+ Lines starting with # are comments. Settings are lines of the form
+ KEYWORD=VALUE. There is currently no way to have a multi-line
+ VALUE (would be nice if there was, probably).
+
+ CVSROOT is the $CVSROOT directory (CVSroot_directory might not be
+ set yet).
+
+ Returns 0 for success, negative value for failure. Call
+ error(0, ...) on errors in addition to the return value. */
+int
+parse_config (cvsroot)
+ char *cvsroot;
+{
+ char *infopath;
+ FILE *fp_info;
+ char *line = NULL;
+ size_t line_allocated = 0;
+ size_t len;
+ char *p;
+ /* FIXME-reentrancy: If we do a multi-threaded server, this would need
+ to go to the per-connection data structures. */
+ static int parsed = 0;
+
+ /* Authentication code and serve_root might both want to call us.
+ Let this happen smoothly. */
+ if (parsed)
+ return 0;
+ parsed = 1;
+
+ infopath = malloc (strlen (cvsroot)
+ + sizeof (CVSROOTADM_CONFIG)
+ + sizeof (CVSROOTADM)
+ + 10);
+ if (infopath == NULL)
+ {
+ error (0, 0, "out of memory; cannot allocate infopath");
+ goto error_return;
+ }
+
+ strcpy (infopath, cvsroot);
+ strcat (infopath, "/");
+ strcat (infopath, CVSROOTADM);
+ strcat (infopath, "/");
+ strcat (infopath, CVSROOTADM_CONFIG);
+
+ fp_info = CVS_FOPEN (infopath, "r");
+ if (fp_info == NULL)
+ {
+ /* If no file, don't do anything special. */
+ if (!existence_error (errno))
+ {
+ /* Just a warning message; doesn't affect return
+ value, currently at least. */
+ error (0, errno, "cannot open %s", infopath);
+ }
+ free (infopath);
+ return 0;
+ }
+
+ while (getline (&line, &line_allocated, fp_info) >= 0)
+ {
+ /* Skip comments. */
+ if (line[0] == '#')
+ continue;
+
+ /* At least for the moment we don't skip whitespace at the start
+ of the line. Too picky? Maybe. But being insufficiently
+ picky leads to all sorts of confusion, and it is a lot easier
+ to start out picky and relax it than the other way around.
+
+ Is there any kind of written standard for the syntax of this
+ sort of config file? Anywhere in POSIX for example (I guess
+ makefiles are sort of close)? Red Hat Linux has a bunch of
+ these too (with some GUI tools which edit them)...
+
+ Along the same lines, we might want a table of keywords,
+ with various types (boolean, string, &c), as a mechanism
+ for making sure the syntax is consistent. Any good examples
+ to follow there (Apache?)? */
+
+ /* Strip the training newline. There will be one unless we
+ read a partial line without a newline, and then got end of
+ file (or error?). */
+
+ len = strlen (line) - 1;
+ if (line[len] == '\n')
+ line[len] = '\0';
+
+ /* Skip blank lines. */
+ if (line[0] == '\0')
+ continue;
+
+ /* The first '=' separates keyword from value. */
+ p = strchr (line, '=');
+ if (p == NULL)
+ {
+ /* Probably should be printing line number. */
+ error (0, 0, "syntax error in %s: line '%s' is missing '='",
+ infopath, line);
+ goto error_return;
+ }
+
+ *p++ = '\0';
+
+ if (strcmp (line, "RCSBIN") == 0)
+ {
+ /* This option used to specify the directory for RCS
+ executables. But since we don't run them any more,
+ this is a noop. Silently ignore it so that a
+ repository can work with either new or old CVS. */
+ ;
+ }
+ else if (strcmp (line, "SystemAuth") == 0)
+ {
+ if (strcmp (p, "no") == 0)
+#ifdef AUTH_SERVER_SUPPORT
+ system_auth = 0;
+#else
+ /* Still parse the syntax but ignore the
+ option. That way the same config file can
+ be used for local and server. */
+ ;
+#endif
+ else if (strcmp (p, "yes") == 0)
+#ifdef AUTH_SERVER_SUPPORT
+ system_auth = 1;
+#else
+ ;
+#endif
+ else
+ {
+ error (0, 0, "unrecognized value '%s' for SystemAuth", p);
+ goto error_return;
+ }
+ }
+ else if (strcmp (line, "PreservePermissions") == 0)
+ {
+ if (strcmp (p, "no") == 0)
+ preserve_perms = 0;
+ else if (strcmp (p, "yes") == 0)
+ {
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ preserve_perms = 1;
+#else
+ error (0, 0, "\
+warning: this CVS does not support PreservePermissions");
+#endif
+ }
+ else
+ {
+ error (0, 0, "unrecognized value '%s' for PreservePermissions",
+ p);
+ goto error_return;
+ }
+ }
+ else if (strcmp (line, "TopLevelAdmin") == 0)
+ {
+ if (strcmp (p, "no") == 0)
+ top_level_admin = 0;
+ else if (strcmp (p, "yes") == 0)
+ top_level_admin = 1;
+ else
+ {
+ error (0, 0, "unrecognized value '%s' for TopLevelAdmin", p);
+ goto error_return;
+ }
+ }
+ else if (strcmp (line, "LockDir") == 0)
+ {
+ if (lock_dir != NULL)
+ free (lock_dir);
+ lock_dir = xstrdup (p);
+ /* Could try some validity checking, like whether we can
+ opendir it or something, but I don't see any particular
+ reason to do that now rather than waiting until lock.c. */
+ }
+ else
+ {
+ /* We may be dealing with a keyword which was added in a
+ subsequent version of CVS. In that case it is a good idea
+ to complain, as (1) the keyword might enable a behavior like
+ alternate locking behavior, in which it is dangerous and hard
+ to detect if some CVS's have it one way and others have it
+ the other way, (2) in general, having us not do what the user
+ had in mind when they put in the keyword violates the
+ principle of least surprise. Note that one corollary is
+ adding new keywords to your CVSROOT/config file is not
+ particularly recommended unless you are planning on using
+ the new features. */
+ error (0, 0, "%s: unrecognized keyword '%s'",
+ infopath, line);
+ goto error_return;
+ }
+ }
+ if (ferror (fp_info))
+ {
+ error (0, errno, "cannot read %s", infopath);
+ goto error_return;
+ }
+ if (fclose (fp_info) < 0)
+ {
+ error (0, errno, "cannot close %s", infopath);
+ goto error_return;
+ }
+ free (infopath);
+ if (line != NULL)
+ free (line);
+ return 0;
+
+ error_return:
+ if (infopath != NULL)
+ free (infopath);
+ if (line != NULL)
+ free (line);
+ return -1;
+}
diff --git a/gnu/dist/cvs/src/patch.c b/gnu/dist/cvs/src/patch.c
new file mode 100644
index 00000000000..f16a3318890
--- /dev/null
+++ b/gnu/dist/cvs/src/patch.c
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Patch
+ *
+ * Create a Larry Wall format "patch" file between a previous release and the
+ * current head of a module, or between two releases. Can specify the
+ * release as either a date or a revision number.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static RETSIGTYPE patch_cleanup PROTO((void));
+static Dtype patch_dirproc PROTO ((void *callerdat, char *dir,
+ char *repos, char *update_dir,
+ List *entries));
+static int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static int patch_proc PROTO((int *pargc, char **argv, char *xwhere,
+ char *mwhere, char *mfile, int shorten,
+ int local_specified, char *mname, char *msg));
+
+static int force_tag_match = 1;
+static int patch_short = 0;
+static int toptwo_diffs = 0;
+static int local = 0;
+static char *options = NULL;
+static char *rev1 = NULL;
+static int rev1_validated = 0;
+static char *rev2 = NULL;
+static int rev2_validated = 0;
+static char *date1 = NULL;
+static char *date2 = NULL;
+static char *tmpfile1 = NULL;
+static char *tmpfile2 = NULL;
+static char *tmpfile3 = NULL;
+static int unidiff = 0;
+
+static const char *const patch_usage[] =
+{
+ "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d]\n",
+ " -r rev|-D date [-r rev2 | -D date2] modules...\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-c\tContext diffs (default)\n",
+ "\t-u\tUnidiff format.\n",
+ "\t-s\tShort patch - one liner per file.\n",
+ "\t-t\tTop two diffs - last change made to the file.\n",
+ "\t-D date\tDate.\n",
+ "\t-r rev\tRevision - symbolic or numeric.\n",
+ "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+patch (argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ int c;
+ int err = 0;
+ DBM *db;
+
+ if (argc == -1)
+ usage (patch_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 't':
+ toptwo_diffs = 1;
+ break;
+ case 's':
+ patch_short = 1;
+ break;
+ case 'D':
+ if (rev2 != NULL || date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (rev1 != NULL || date1 != NULL)
+ date2 = Make_Date (optarg);
+ else
+ date1 = Make_Date (optarg);
+ break;
+ case 'r':
+ if (rev2 != NULL || date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (rev1 != NULL || date1 != NULL)
+ rev2 = optarg;
+ else
+ rev1 = optarg;
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'V':
+ /* This option is pretty seriously broken:
+ 1. It is not clear what it does (does it change keyword
+ expansion behavior? If so, how? Or does it have
+ something to do with what version of RCS we are using?
+ Or the format we write RCS files in?).
+ 2. Because both it and -k use the options variable,
+ specifying both -V and -k doesn't work.
+ 3. At least as of CVS 1.9, it doesn't work (failed
+ assertion in RCS_checkout where it asserts that options
+ starts with -k). Few people seem to be complaining.
+ In the future (perhaps the near future), I have in mind
+ removing it entirely, and updating NEWS and cvs.texinfo,
+ but in case it is a good idea to give people more time
+ to complain if they would miss it, I'll just add this
+ quick and dirty error message for now. */
+ error (1, 0,
+ "the -V option is obsolete and should not be used");
+#if 0
+ if (atoi (optarg) <= 0)
+ error (1, 0, "must specify a version number to -V");
+ if (options)
+ free (options);
+ options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
+ (void) sprintf (options, "-V%s", optarg);
+#endif
+ break;
+ case 'u':
+ unidiff = 1; /* Unidiff */
+ break;
+ case 'c': /* Context diff */
+ unidiff = 0;
+ break;
+ case '?':
+ default:
+ usage (patch_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Sanity checks */
+ if (argc < 1)
+ usage (patch_usage);
+
+ if (toptwo_diffs && patch_short)
+ error (1, 0, "-t and -s options are mutually exclusive");
+ if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
+ rev1 != NULL || rev2 != NULL))
+ error (1, 0, "must not specify revisions/dates with -t option!");
+
+ if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
+ rev1 == NULL && rev2 == NULL))
+ error (1, 0, "must specify at least one revision/date!");
+ if (date1 != NULL && date2 != NULL)
+ if (RCS_datecmp (date1, date2) >= 0)
+ error (1, 0, "second date must come after first date!");
+
+ /* if options is NULL, make it a NULL string */
+ if (options == NULL)
+ options = xstrdup ("");
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg("-l");
+ if (!force_tag_match)
+ send_arg("-f");
+ if (toptwo_diffs)
+ send_arg("-t");
+ if (patch_short)
+ send_arg("-s");
+ if (unidiff)
+ send_arg("-u");
+
+ if (rev1)
+ option_with_arg ("-r", rev1);
+ if (date1)
+ client_senddate (date1);
+ if (rev2)
+ option_with_arg ("-r", rev2);
+ if (date2)
+ client_senddate (date2);
+ if (options[0] != '\0')
+ send_arg (options);
+
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ }
+
+ send_to_server ("rdiff\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ /* clean up if we get a signal */
+#ifdef SIGHUP
+ (void) SIG_register (SIGHUP, patch_cleanup);
+#endif
+#ifdef SIGINT
+ (void) SIG_register (SIGINT, patch_cleanup);
+#endif
+#ifdef SIGQUIT
+ (void) SIG_register (SIGQUIT, patch_cleanup);
+#endif
+#ifdef SIGPIPE
+ (void) SIG_register (SIGPIPE, patch_cleanup);
+#endif
+#ifdef SIGTERM
+ (void) SIG_register (SIGTERM, patch_cleanup);
+#endif
+
+ db = open_module ();
+ for (i = 0; i < argc; i++)
+ err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
+ (char *) NULL, 0, 0, 0, (char *) NULL);
+ close_module (db);
+ free (options);
+ patch_cleanup ();
+ return (err);
+}
+
+/*
+ * callback proc for doing the real work of patching
+ */
+/* ARGSUSED */
+static int
+patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
+ mname, msg)
+ int *pargc;
+ char **argv;
+ char *xwhere;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *mname;
+ char *msg;
+{
+ int err = 0;
+ int which;
+ char *repository;
+ char *where;
+
+ repository = xmalloc (strlen (CVSroot_directory) + strlen (argv[0])
+ + (mfile == NULL ? 0 : strlen (mfile)) + 30);
+ (void) sprintf (repository, "%s/%s", CVSroot_directory, argv[0]);
+ where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile))
+ + 10);
+ (void) strcpy (where, argv[0]);
+
+ /* if mfile isn't null, we need to set up to do only part of the module */
+ if (mfile != NULL)
+ {
+ char *cp;
+ char *path;
+
+ /* if the portion of the module is a path, put the dir part on repos */
+ if ((cp = strrchr (mfile, '/')) != NULL)
+ {
+ *cp = '\0';
+ (void) strcat (repository, "/");
+ (void) strcat (repository, mfile);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ mfile = cp + 1;
+ }
+
+ /* take care of the rest */
+ path = xmalloc (strlen (repository) + strlen (mfile) + 5);
+ (void) sprintf (path, "%s/%s", repository, mfile);
+ if (isdir (path))
+ {
+ /* directory means repository gets the dir tacked on */
+ (void) strcpy (repository, path);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ }
+ else
+ {
+ int i;
+
+ /* a file means muck argv */
+ for (i = 1; i < *pargc; i++)
+ free (argv[i]);
+ argv[1] = xstrdup (mfile);
+ (*pargc) = 2;
+ }
+ free (path);
+ }
+
+ /* cd to the starting repository */
+ if ( CVS_CHDIR (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ free (repository);
+ return (1);
+ }
+ free (repository);
+
+ if (force_tag_match)
+ which = W_REPOS | W_ATTIC;
+ else
+ which = W_REPOS;
+
+ if (rev1 != NULL && !rev1_validated)
+ {
+ tag_check_valid (rev1, *pargc - 1, argv + 1, local, 0, NULL);
+ rev1_validated = 1;
+ }
+ if (rev2 != NULL && !rev2_validated)
+ {
+ tag_check_valid (rev2, *pargc - 1, argv + 1, local, 0, NULL);
+ rev2_validated = 1;
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (patch_fileproc, (FILESDONEPROC) NULL, patch_dirproc,
+ (DIRLEAVEPROC) NULL, NULL,
+ *pargc - 1, argv + 1, local,
+ which, 0, 1, where, 1);
+ free (where);
+
+ return (err);
+}
+
+/*
+ * Called to examine a particular RCS file, as appropriate with the options
+ * that were set above.
+ */
+/* ARGSUSED */
+static int
+patch_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ struct utimbuf t;
+ char *vers_tag, *vers_head;
+ char *rcs = NULL;
+ RCSNode *rcsfile;
+ FILE *fp1, *fp2, *fp3;
+ int ret = 0;
+ int isattic = 0;
+ int retcode = 0;
+ char *file1;
+ char *file2;
+ char *strippath;
+ char *line1, *line2;
+ size_t line1_chars_allocated;
+ size_t line2_chars_allocated;
+ char *cp1, *cp2;
+ FILE *fp;
+ int line_length;
+
+ line1 = NULL;
+ line1_chars_allocated = 0;
+ line2 = NULL;
+ line2_chars_allocated = 0;
+
+ /* find the parsed rcs file */
+ if ((rcsfile = finfo->rcs) == NULL)
+ {
+ ret = 1;
+ goto out2;
+ }
+ if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
+ isattic = 1;
+
+ rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5);
+ (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);
+
+ /* if vers_head is NULL, may have been removed from the release */
+ if (isattic && rev2 == NULL && date2 == NULL)
+ vers_head = NULL;
+ else
+ {
+ vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
+ (int *) NULL);
+ if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
+ {
+ free (vers_head);
+ vers_head = NULL;
+ }
+ }
+
+ if (toptwo_diffs)
+ {
+ if (vers_head == NULL)
+ {
+ ret = 1;
+ goto out2;
+ }
+
+ if (!date1)
+ date1 = xmalloc (MAXDATELEN);
+ *date1 = '\0';
+ if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1)
+ {
+ if (!really_quiet)
+ error (0, 0, "cannot find date in rcs file %s revision %s",
+ rcs, vers_head);
+ ret = 1;
+ goto out2;
+ }
+ }
+ vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match,
+ (int *) NULL);
+ if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
+ {
+ free (vers_tag);
+ vers_tag = NULL;
+ }
+
+ if (vers_tag == NULL && vers_head == NULL)
+ {
+ /* Nothing known about specified revs. */
+ ret = 0;
+ goto out2;
+ }
+
+ if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0)
+ {
+ /* Not changed between releases. */
+ ret = 0;
+ goto out2;
+ }
+
+ if (patch_short)
+ {
+ cvs_output ("File ", 0);
+ cvs_output (finfo->fullname, 0);
+ if (vers_tag == NULL)
+ {
+ cvs_output (" is new; current revision ", 0);
+ cvs_output (vers_head, 0);
+ cvs_output ("\n", 1);
+ }
+ else if (vers_head == NULL)
+ {
+ cvs_output (" is removed; not included in ", 0);
+ if (rev2 != NULL)
+ {
+ cvs_output ("release tag ", 0);
+ cvs_output (rev2, 0);
+ }
+ else if (date2 != NULL)
+ {
+ cvs_output ("release date ", 0);
+ cvs_output (date2, 0);
+ }
+ else
+ cvs_output ("current release", 0);
+ cvs_output ("\n", 1);
+ }
+ else
+ {
+ cvs_output (" changed from revision ", 0);
+ cvs_output (vers_tag, 0);
+ cvs_output (" to ", 0);
+ cvs_output (vers_head, 0);
+ cvs_output ("\n", 1);
+ }
+ ret = 0;
+ goto out2;
+ }
+
+ /* Create 3 empty files. I'm not really sure there is any advantage
+ to doing so now rather than just waiting until later. */
+ tmpfile1 = cvs_temp_name ();
+ fp1 = CVS_FOPEN (tmpfile1, "w+");
+ if (fp1 == NULL)
+ {
+ error (0, errno, "cannot create temporary file %s", tmpfile1);
+ ret = 1;
+ goto out;
+ }
+ else
+ if (fclose (fp1) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile1);
+ tmpfile2 = cvs_temp_name ();
+ fp2 = CVS_FOPEN (tmpfile2, "w+");
+ if (fp2 == NULL)
+ {
+ error (0, errno, "cannot create temporary file %s", tmpfile2);
+ ret = 1;
+ goto out;
+ }
+ else
+ if (fclose (fp2) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile2);
+ tmpfile3 = cvs_temp_name ();
+ fp3 = CVS_FOPEN (tmpfile3, "w+");
+ if (fp3 == NULL)
+ {
+ error (0, errno, "cannot create temporary file %s", tmpfile3);
+ ret = 1;
+ goto out;
+ }
+ else
+ if (fclose (fp3) < 0)
+ error (0, errno, "warning: cannot close %s", tmpfile3);
+
+ if (vers_tag != NULL)
+ {
+ retcode = RCS_checkout (rcsfile, (char *) NULL, vers_tag,
+ rev1, options, tmpfile1,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
+ if (retcode != 0)
+ {
+ error (0, 0,
+ "cannot check out revision %s of %s", vers_tag, rcs);
+ ret = 1;
+ goto out;
+ }
+ memset ((char *) &t, 0, sizeof (t));
+ if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
+ (char *) 0, 0)) != -1)
+ /* I believe this timestamp only affects the dates in our diffs,
+ and therefore should be on the server, not the client. */
+ (void) utime (tmpfile1, &t);
+ }
+ else if (toptwo_diffs)
+ {
+ ret = 1;
+ goto out;
+ }
+ if (vers_head != NULL)
+ {
+ retcode = RCS_checkout (rcsfile, (char *) NULL, vers_head,
+ rev2, options, tmpfile2,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
+ if (retcode != 0)
+ {
+ error (0, 0,
+ "cannot check out revision %s of %s", vers_head, rcs);
+ ret = 1;
+ goto out;
+ }
+ if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
+ (char *) 0, 0)) != -1)
+ /* I believe this timestamp only affects the dates in our diffs,
+ and therefore should be on the server, not the client. */
+ (void) utime (tmpfile2, &t);
+ }
+
+ switch (diff_exec (tmpfile1, tmpfile2, unidiff ? "-u" : "-c", tmpfile3))
+ {
+ case -1: /* fork/wait failure */
+ error (1, errno, "fork for diff failed on %s", rcs);
+ break;
+ case 0: /* nothing to do */
+ break;
+ case 1:
+ /*
+ * The two revisions are really different, so read the first two
+ * lines of the diff output file, and munge them to include more
+ * reasonable file names that "patch" will understand.
+ */
+
+ /* Output an "Index:" line for patch to use */
+ cvs_output ("Index: ", 0);
+ cvs_output (finfo->fullname, 0);
+ cvs_output ("\n", 1);
+
+ fp = open_file (tmpfile3, "r");
+ if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
+ getline (&line2, &line2_chars_allocated, fp) < 0)
+ {
+ if (feof (fp))
+ error (0, 0, "\
+failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
+ else
+ error (0, errno,
+ "failed to read diff file header %s for %s",
+ tmpfile3, rcs);
+ ret = 1;
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
+ goto out;
+ }
+ if (!unidiff)
+ {
+ if (strncmp (line1, "*** ", 4) != 0 ||
+ strncmp (line2, "--- ", 4) != 0 ||
+ (cp1 = strchr (line1, '\t')) == NULL ||
+ (cp2 = strchr (line2, '\t')) == NULL)
+ {
+ error (0, 0, "invalid diff header for %s", rcs);
+ ret = 1;
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
+ goto out;
+ }
+ }
+ else
+ {
+ if (strncmp (line1, "--- ", 4) != 0 ||
+ strncmp (line2, "+++ ", 4) != 0 ||
+ (cp1 = strchr (line1, '\t')) == NULL ||
+ (cp2 = strchr (line2, '\t')) == NULL)
+ {
+ error (0, 0, "invalid unidiff header for %s", rcs);
+ ret = 1;
+ if (fclose (fp) < 0)
+ error (0, errno, "error closing %s", tmpfile3);
+ goto out;
+ }
+ }
+ if (CVSroot_directory != NULL)
+ {
+ strippath = xmalloc (strlen (CVSroot_directory) + 10);
+ (void) sprintf (strippath, "%s/", CVSroot_directory);
+ }
+ else
+ strippath = xstrdup (REPOS_STRIP);
+ if (strncmp (rcs, strippath, strlen (strippath)) == 0)
+ rcs += strlen (strippath);
+ free (strippath);
+ if (vers_tag != NULL)
+ {
+ file1 = xmalloc (strlen (finfo->fullname)
+ + strlen (vers_tag)
+ + 10);
+ (void) sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
+ }
+ else
+ {
+ file1 = xstrdup (DEVNULL);
+ }
+ file2 = xmalloc (strlen (finfo->fullname)
+ + (vers_head != NULL ? strlen (vers_head) : 10)
+ + 10);
+ (void) sprintf (file2, "%s:%s", finfo->fullname,
+ vers_head ? vers_head : "removed");
+
+ /* Note that the string "diff" is specified by POSIX (for -c)
+ and is part of the diff output format, not the name of a
+ program. */
+ if (unidiff)
+ {
+ cvs_output ("diff -u ", 0);
+ cvs_output (file1, 0);
+ cvs_output (" ", 1);
+ cvs_output (file2, 0);
+ cvs_output ("\n", 1);
+
+ cvs_output ("--- ", 0);
+ cvs_output (file1, 0);
+ cvs_output (cp1, 0);
+ cvs_output ("+++ ", 0);
+ }
+ else
+ {
+ cvs_output ("diff -c ", 0);
+ cvs_output (file1, 0);
+ cvs_output (" ", 1);
+ cvs_output (file2, 0);
+ cvs_output ("\n", 1);
+
+ cvs_output ("*** ", 0);
+ cvs_output (file1, 0);
+ cvs_output (cp1, 0);
+ cvs_output ("--- ", 0);
+ }
+
+ cvs_output (finfo->fullname, 0);
+ cvs_output (cp2, 0);
+
+ /* spew the rest of the diff out */
+ while ((line_length
+ = getline (&line1, &line1_chars_allocated, fp))
+ >= 0)
+ cvs_output (line1, 0);
+ if (line_length < 0 && !feof (fp))
+ error (0, errno, "cannot read %s", tmpfile3);
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", tmpfile3);
+ free (file1);
+ free (file2);
+ break;
+ default:
+ error (0, 0, "diff failed for %s", finfo->fullname);
+ }
+ out:
+ if (line1)
+ free (line1);
+ if (line2)
+ free (line2);
+ if (CVS_UNLINK (tmpfile1) < 0)
+ error (0, errno, "cannot unlink %s", tmpfile1);
+ if (CVS_UNLINK (tmpfile2) < 0)
+ error (0, errno, "cannot unlink %s", tmpfile2);
+ if (CVS_UNLINK (tmpfile3) < 0)
+ error (0, errno, "cannot unlink %s", tmpfile3);
+ free (tmpfile1);
+ free (tmpfile2);
+ free (tmpfile3);
+ tmpfile1 = tmpfile2 = tmpfile3 = NULL;
+
+ out2:
+ if (rcs != NULL)
+ free (rcs);
+ return (ret);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+patch_dirproc (callerdat, dir, repos, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ if (!quiet)
+ error (0, 0, "Diffing %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Clean up temporary files
+ */
+static RETSIGTYPE
+patch_cleanup ()
+{
+ /* Note that the checks for existence_error are because we are
+ called from a signal handler, without SIG_begincrsect, so
+ we don't know whether the files got created. */
+
+ if (tmpfile1 != NULL)
+ {
+ if (unlink_file (tmpfile1) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", tmpfile1);
+ free (tmpfile1);
+ }
+ if (tmpfile2 != NULL)
+ {
+ if (unlink_file (tmpfile2) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", tmpfile2);
+ free (tmpfile2);
+ }
+ if (tmpfile3 != NULL)
+ {
+ if (unlink_file (tmpfile3) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", tmpfile3);
+ free (tmpfile3);
+ }
+ tmpfile1 = tmpfile2 = tmpfile3 = NULL;
+}
diff --git a/gnu/dist/cvs/src/rcs.c b/gnu/dist/cvs/src/rcs.c
new file mode 100644
index 00000000000..3af905b0c66
--- /dev/null
+++ b/gnu/dist/cvs/src/rcs.c
@@ -0,0 +1,8926 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * The routines contained in this file do all the rcs file parsing and
+ * manipulation
+ */
+
+#include <assert.h>
+#include "cvs.h"
+#include "edit.h"
+#include "hardlink.h"
+
+int preserve_perms = 0;
+
+/* The RCS -k options, and a set of enums that must match the array.
+ These come first so that we can use enum kflag in function
+ prototypes. */
+static const char *const kflags[] =
+ {"kv", "kvl", "k", "v", "o", "b", (char *) NULL};
+enum kflag { KFLAG_KV = 0, KFLAG_KVL, KFLAG_K, KFLAG_V, KFLAG_O, KFLAG_B };
+
+/* A structure we use to buffer the contents of an RCS file. The
+ various fields are only referenced directly by the rcsbuf_*
+ functions. We declare the struct here so that we can allocate it
+ on the stack, rather than in memory. */
+
+struct rcsbuffer
+{
+ /* Points to the current position in the buffer. */
+ char *ptr;
+ /* Points just after the last valid character in the buffer. */
+ char *ptrend;
+ /* The file. */
+ FILE *fp;
+ /* The name of the file, used for error messages. */
+ const char *filename;
+ /* The starting file position of the data in the buffer. */
+ unsigned long pos;
+ /* The length of the value. */
+ size_t vlen;
+ /* Whether the value contains an '@' string. If so, we can not
+ compress whitespace characters. */
+ int at_string;
+ /* The number of embedded '@' characters in an '@' string. If
+ this is non-zero, we must search the string for pairs of '@'
+ and convert them to a single '@'. */
+ int embedded_at;
+};
+
+static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile));
+static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch));
+static void rcsbuf_open PROTO ((struct rcsbuffer *, FILE *fp,
+ const char *filename, unsigned long pos));
+static void rcsbuf_close PROTO ((struct rcsbuffer *));
+static int rcsbuf_getkey PROTO ((struct rcsbuffer *, char **keyp,
+ char **valp));
+static int rcsbuf_getrevnum PROTO ((struct rcsbuffer *, char **revp));
+static char *rcsbuf_fill PROTO ((struct rcsbuffer *, char *ptr, char **keyp,
+ char **valp));
+static char *rcsbuf_valcopy PROTO ((struct rcsbuffer *, char *val, int polish,
+ size_t *lenp));
+static void rcsbuf_valpolish PROTO ((struct rcsbuffer *, char *val, int polish,
+ size_t *lenp));
+static void rcsbuf_valpolish_internal PROTO ((struct rcsbuffer *, char *to,
+ const char *from, size_t *lenp));
+static unsigned long rcsbuf_ftell PROTO ((struct rcsbuffer *));
+static void rcsbuf_get_buffered PROTO ((struct rcsbuffer *, char **datap,
+ size_t *lenp));
+static void rcsbuf_cache PROTO ((RCSNode *, struct rcsbuffer *));
+static void rcsbuf_cache_close PROTO ((void));
+static void rcsbuf_cache_open PROTO ((RCSNode *, long, FILE **,
+ struct rcsbuffer *));
+static int checkmagic_proc PROTO((Node *p, void *closure));
+static void do_branches PROTO((List * list, char *val));
+static void do_symbols PROTO((List * list, char *val));
+static void do_locks PROTO((List * list, char *val));
+static void free_rcsnode_contents PROTO((RCSNode *));
+static void free_rcsvers_contents PROTO((RCSVers *));
+static void rcsvers_delproc PROTO((Node * p));
+static char *translate_symtag PROTO((RCSNode *, const char *));
+static char *RCS_addbranch PROTO ((RCSNode *, const char *));
+static char *truncate_revnum_in_place PROTO ((char *));
+static char *truncate_revnum PROTO ((const char *));
+static char *printable_date PROTO((const char *));
+static char *escape_keyword_value PROTO ((const char *, int *));
+static void expand_keywords PROTO((RCSNode *, RCSVers *, const char *,
+ const char *, size_t, enum kflag, char *,
+ size_t, char **, size_t *));
+static void cmp_file_buffer PROTO((void *, const char *, size_t));
+
+enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH};
+static void RCS_deltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *, char *,
+ enum rcs_delta_op, char **, size_t *,
+ char **, size_t *));
+
+/* Routines for reading, parsing and writing RCS files. */
+static RCSVers *getdelta PROTO ((struct rcsbuffer *, char *, char **,
+ char **));
+static Deltatext *RCS_getdeltatext PROTO ((RCSNode *, FILE *,
+ struct rcsbuffer *));
+static void freedeltatext PROTO ((Deltatext *));
+
+static void RCS_putadmin PROTO ((RCSNode *, FILE *));
+static void RCS_putdtree PROTO ((RCSNode *, char *, FILE *));
+static void RCS_putdesc PROTO ((RCSNode *, FILE *));
+static void putdelta PROTO ((RCSVers *, FILE *));
+static int putrcsfield_proc PROTO ((Node *, void *));
+static int putsymbol_proc PROTO ((Node *, void *));
+static void RCS_copydeltas PROTO ((RCSNode *, FILE *, struct rcsbuffer *,
+ FILE *, Deltatext *, char *));
+static int count_delta_actions PROTO ((Node *, void *));
+static void putdeltatext PROTO ((FILE *, Deltatext *));
+
+static FILE *rcs_internal_lockfile PROTO ((char *));
+static void rcs_internal_unlockfile PROTO ((FILE *, char *));
+static char *rcs_lockfilename PROTO ((char *));
+
+/* The RCS file reading functions are called a lot, and they do some
+ string comparisons. This macro speeds things up a bit by skipping
+ the function call when the first characters are different. It
+ evaluates its arguments multiple times. */
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0)
+
+/*
+ * We don't want to use isspace() from the C library because:
+ *
+ * 1. The definition of "whitespace" in RCS files includes ASCII
+ * backspace, but the C locale doesn't.
+ * 2. isspace is an very expensive function call in some implementations
+ * due to the addition of wide character support.
+ */
+static const char spacetab[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, /* 0x00 - 0x0f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 - 0xff */
+};
+
+#define whitespace(c) (spacetab[(unsigned char)c] != 0)
+
+static char *rcs_lockfile;
+
+/* A few generic thoughts on error handling, in particular the
+ printing of unexpected characters that we find in the RCS file
+ (that is, why we use '\x%x' rather than %c or some such).
+
+ * Avoiding %c means we don't have to worry about what is printable
+ and other such stuff. In error handling, often better to keep it
+ simple.
+
+ * Hex rather than decimal or octal because character set standards
+ tend to use hex.
+
+ * Saying "character 0x%x" might make it sound like we are printing
+ a file offset. So we use '\x%x'.
+
+ * Would be nice to print the offset within the file, but I can
+ imagine various portability hassles (in particular, whether
+ unsigned long is always big enough to hold file offsets). */
+
+/* Parse an rcsfile given a user file name and a repository. If there is
+ an error, we print an error message and return NULL. If the file
+ does not exist, we return NULL without printing anything (I'm not
+ sure this allows the caller to do anything reasonable, but it is
+ the current behavior). */
+RCSNode *
+RCS_parse (file, repos)
+ const char *file;
+ const char *repos;
+{
+ RCSNode *rcs;
+ FILE *fp;
+ RCSNode *retval;
+ char *rcsfile;
+
+ /* We're creating a new RCSNode, so there is no hope of finding it
+ in the cache. */
+ rcsbuf_cache_close ();
+
+ rcsfile = xmalloc (strlen (repos) + strlen (file)
+ + sizeof (RCSEXT) + sizeof (CVSATTIC) + 10);
+ (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
+ if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL)
+ {
+ rcs = RCS_parsercsfile_i(fp, rcsfile);
+ if (rcs != NULL)
+ rcs->flags |= VALID;
+
+ retval = rcs;
+ goto out;
+ }
+ else if (! existence_error (errno))
+ {
+ error (0, errno, "cannot open %s", rcsfile);
+ retval = NULL;
+ goto out;
+ }
+
+ (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT);
+ if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL)
+ {
+ rcs = RCS_parsercsfile_i(fp, rcsfile);
+ if (rcs != NULL)
+ {
+ rcs->flags |= INATTIC;
+ rcs->flags |= VALID;
+ }
+
+ retval = rcs;
+ goto out;
+ }
+ else if (! existence_error (errno))
+ {
+ error (0, errno, "cannot open %s", rcsfile);
+ retval = NULL;
+ goto out;
+ }
+#if defined (SERVER_SUPPORT) && !defined (FILENAMES_CASE_INSENSITIVE)
+ else if (ign_case)
+ {
+ int status;
+ char *found_path;
+
+ /* The client might be asking for a file which we do have
+ (which the client doesn't know about), but for which the
+ filename case differs. We only consider this case if the
+ regular CVS_FOPENs fail, because fopen_case is such an
+ expensive call. */
+ (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
+ status = fopen_case (rcsfile, "rb", &fp, &found_path);
+ if (status == 0)
+ {
+ rcs = RCS_parsercsfile_i (fp, rcsfile);
+ if (rcs != NULL)
+ rcs->flags |= VALID;
+
+ free (rcs->path);
+ rcs->path = found_path;
+ retval = rcs;
+ goto out;
+ }
+ else if (! existence_error (status))
+ {
+ error (0, status, "cannot open %s", rcsfile);
+ retval = NULL;
+ goto out;
+ }
+
+ (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT);
+ status = fopen_case (rcsfile, "rb", &fp, &found_path);
+ if (status == 0)
+ {
+ rcs = RCS_parsercsfile_i (fp, rcsfile);
+ if (rcs != NULL)
+ {
+ rcs->flags |= INATTIC;
+ rcs->flags |= VALID;
+ }
+
+ free (rcs->path);
+ rcs->path = found_path;
+ retval = rcs;
+ goto out;
+ }
+ else if (! existence_error (status))
+ {
+ error (0, status, "cannot open %s", rcsfile);
+ retval = NULL;
+ goto out;
+ }
+ }
+#endif
+ retval = NULL;
+
+ out:
+ free (rcsfile);
+
+ return retval;
+}
+
+/*
+ * Parse a specific rcsfile.
+ */
+RCSNode *
+RCS_parsercsfile (rcsfile)
+ char *rcsfile;
+{
+ FILE *fp;
+ RCSNode *rcs;
+
+ /* We're creating a new RCSNode, so there is no hope of finding it
+ in the cache. */
+ rcsbuf_cache_close ();
+
+ /* open the rcsfile */
+ if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
+ {
+ error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
+ return (NULL);
+ }
+
+ rcs = RCS_parsercsfile_i (fp, rcsfile);
+
+ return (rcs);
+}
+
+
+/*
+ */
+static RCSNode *
+RCS_parsercsfile_i (fp, rcsfile)
+ FILE *fp;
+ const char *rcsfile;
+{
+ RCSNode *rdata;
+ struct rcsbuffer rcsbuf;
+ char *key, *value;
+
+ /* make a node */
+ rdata = (RCSNode *) xmalloc (sizeof (RCSNode));
+ memset ((char *) rdata, 0, sizeof (RCSNode));
+ rdata->refcount = 1;
+ rdata->path = xstrdup (rcsfile);
+
+ /* Process HEAD, BRANCH, and EXPAND keywords from the RCS header.
+
+ Most cvs operations on the main branch don't need any more
+ information. Those that do call RCS_reparsercsfile to parse
+ the rest of the header and the deltas. */
+
+ rcsbuf_open (&rcsbuf, fp, rcsfile, 0);
+
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ goto l_error;
+ if (STREQ (key, RCSDESC))
+ goto l_error;
+
+ if (STREQ (RCSHEAD, key) && value != NULL)
+ rdata->head = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
+
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ goto l_error;
+ if (STREQ (key, RCSDESC))
+ goto l_error;
+
+ if (STREQ (RCSBRANCH, key) && value != NULL)
+ {
+ char *cp;
+
+ rdata->branch = rcsbuf_valcopy (&rcsbuf, value, 0, (size_t *) NULL);
+ if ((numdots (rdata->branch) & 1) != 0)
+ {
+ /* turn it into a branch if it's a revision */
+ cp = strrchr (rdata->branch, '.');
+ *cp = '\0';
+ }
+ }
+
+ /* Look ahead for expand, stopping when we see desc or a revision
+ number. */
+ while (1)
+ {
+ char *cp;
+
+ if (STREQ (RCSEXPAND, key))
+ {
+ rdata->expand = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ break;
+ }
+
+ for (cp = key;
+ (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
+ cp++)
+ /* do nothing */ ;
+ if (*cp == '\0')
+ break;
+
+ if (STREQ (RCSDESC, key))
+ break;
+
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ break;
+ }
+
+ rdata->flags |= PARTIAL;
+
+ rcsbuf_cache (rdata, &rcsbuf);
+
+ return rdata;
+
+l_error:
+ error (0, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ rcsbuf_close (&rcsbuf);
+ freercsnode (&rdata);
+ fclose (fp);
+ return (NULL);
+}
+
+
+/* Do the real work of parsing an RCS file.
+
+ On error, die with a fatal error; if it returns at all it was successful.
+
+ If PFP is NULL, close the file when done. Otherwise, leave it open
+ and store the FILE * in *PFP. */
+void
+RCS_reparsercsfile (rdata, pfp, rcsbufp)
+ RCSNode *rdata;
+ FILE **pfp;
+ struct rcsbuffer *rcsbufp;
+{
+ FILE *fp;
+ char *rcsfile;
+ struct rcsbuffer rcsbuf;
+ Node *q, *kv;
+ RCSVers *vnode;
+ int gotkey;
+ char *cp;
+ char *key, *value;
+
+ assert (rdata != NULL);
+ rcsfile = rdata->path;
+
+ rcsbuf_cache_open (rdata, 0, &fp, &rcsbuf);
+
+ /* make a node */
+ /* This probably shouldn't be done until later: if a file has an
+ empty revision tree (which is permissible), rdata->versions
+ should be NULL. -twp */
+ rdata->versions = getlist ();
+
+ /*
+ * process all the special header information, break out when we get to
+ * the first revision delta
+ */
+ gotkey = 0;
+ for (;;)
+ {
+ /* get the next key/value pair */
+ if (!gotkey)
+ {
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ {
+ error (1, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ }
+ }
+
+ gotkey = 0;
+
+ /* Skip head, branch and expand tags; we already have them. */
+ if (STREQ (key, RCSHEAD)
+ || STREQ (key, RCSBRANCH)
+ || STREQ (key, RCSEXPAND))
+ {
+ continue;
+ }
+
+ if (STREQ (key, "access"))
+ {
+ if (value != NULL)
+ {
+ /* We pass the POLISH parameter as 1 because
+ RCS_addaccess expects nothing but spaces. FIXME:
+ It would be easy and more efficient to change
+ RCS_addaccess. */
+ rdata->access = rcsbuf_valcopy (&rcsbuf, value, 1,
+ (size_t *) NULL);
+ }
+ continue;
+ }
+
+ /* We always save lock information, so that we can handle
+ -kkvl correctly when checking out a file. */
+ if (STREQ (key, "locks"))
+ {
+ if (value != NULL)
+ rdata->locks_data = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ if (! rcsbuf_getkey (&rcsbuf, &key, &value))
+ {
+ error (1, 0, "premature end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, "strict") && value == NULL)
+ {
+ rdata->strict_locks = 1;
+ }
+ else
+ gotkey = 1;
+ continue;
+ }
+
+ if (STREQ (RCSSYMBOLS, key))
+ {
+ if (value != NULL)
+ rdata->symbols_data = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ continue;
+ }
+
+ /*
+ * check key for '.''s and digits (probably a rev) if it is a
+ * revision or `desc', we are done with the headers and are down to the
+ * revision deltas, so we break out of the loop
+ */
+ for (cp = key;
+ (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
+ cp++)
+ /* do nothing */ ;
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK
+ since we know exactly what to expect. */
+ if (*cp == '\0' && strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) == 0)
+ break;
+
+ if (STREQ (key, RCSDESC))
+ break;
+
+ if (STREQ (key, "comment"))
+ {
+ rdata->comment = rcsbuf_valcopy (&rcsbuf, value, 0,
+ (size_t *) NULL);
+ continue;
+ }
+ if (rdata->other == NULL)
+ rdata->other = getlist ();
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (key);
+ kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL);
+ if (addnode (rdata->other, kv) != 0)
+ {
+ error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
+ key, rcsfile);
+ freenode (kv);
+ }
+
+ /* if we haven't grabbed it yet, we didn't want it */
+ }
+
+ /* We got out of the loop, so we have the first part of the first
+ revision delta in KEY (the revision) and VALUE (the date key
+ and its value). This is what getdelta expects to receive. */
+
+ while ((vnode = getdelta (&rcsbuf, rcsfile, &key, &value)) != NULL)
+ {
+ /* get the node */
+ q = getnode ();
+ q->type = RCSVERS;
+ q->delproc = rcsvers_delproc;
+ q->data = (char *) vnode;
+ q->key = vnode->version;
+
+ /* add the nodes to the list */
+ if (addnode (rdata->versions, q) != 0)
+ {
+#if 0
+ purify_printf("WARNING: Adding duplicate version: %s (%s)\n",
+ q->key, rcsfile);
+ freenode (q);
+#endif
+ }
+ }
+
+ /* Here KEY and VALUE are whatever caused getdelta to return NULL. */
+
+ if (STREQ (key, RCSDESC))
+ {
+ if (rdata->desc != NULL)
+ {
+ error (0, 0,
+ "warning: duplicate key `%s' in RCS file `%s'",
+ key, rcsfile);
+ free (rdata->desc);
+ }
+ /* Don't need to rcsbuf_valcopy `value' because
+ getdelta already did that. */
+ rdata->desc = xstrdup (value);
+ }
+
+ rdata->delta_pos = rcsbuf_ftell (&rcsbuf);
+
+ if (pfp == NULL)
+ rcsbuf_cache (rdata, &rcsbuf);
+ else
+ {
+ *pfp = fp;
+ *rcsbufp = rcsbuf;
+ }
+ rdata->flags &= ~PARTIAL;
+}
+
+/* Move RCS into or out of the Attic, depending on TOATTIC. If the
+ file is already in the desired place, return without doing
+ anything. At some point may want to think about how this relates
+ to RCS_rewrite but that is a bit hairy (if one wants renames to be
+ atomic, or that kind of thing). If there is an error, print a message
+ and return 1. On success, return 0. */
+int
+RCS_setattic (rcs, toattic)
+ RCSNode *rcs;
+ int toattic;
+{
+ char *newpath;
+ char *p;
+ char *q;
+
+ /* Some systems aren't going to let us rename an open file. */
+ rcsbuf_cache_close ();
+
+ /* Could make the pathname computations in this file, and probably
+ in other parts of rcs.c too, easier if the REPOS and FILE
+ arguments to RCS_parse got stashed in the RCSNode. */
+
+ if (toattic)
+ {
+ mode_t omask;
+
+ if (rcs->flags & INATTIC)
+ return 0;
+
+ /* Example: rcs->path is "/foo/bar/baz,v". */
+ newpath = xmalloc (strlen (rcs->path) + sizeof CVSATTIC + 5);
+ p = last_component (rcs->path);
+ strncpy (newpath, rcs->path, p - rcs->path);
+ strcpy (newpath + (p - rcs->path), CVSATTIC);
+
+ /* Create the Attic directory if it doesn't exist. */
+ omask = umask (cvsumask);
+ if (CVS_MKDIR (newpath, 0777) < 0 && errno != EEXIST)
+ error (0, errno, "cannot make directory %s", newpath);
+ (void) umask (omask);
+
+ strcat (newpath, "/");
+ strcat (newpath, p);
+
+ if (CVS_RENAME (rcs->path, newpath) < 0)
+ {
+ int save_errno = errno;
+
+ /* The checks for isreadable look awfully fishy, but
+ I'm going to leave them here for now until I
+ can think harder about whether they take care of
+ some cases which should be handled somehow. */
+
+ if (isreadable (rcs->path) || !isreadable (newpath))
+ {
+ error (0, save_errno, "cannot rename %s to %s",
+ rcs->path, newpath);
+ free (newpath);
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ if (!(rcs->flags & INATTIC))
+ return 0;
+
+ newpath = xmalloc (strlen (rcs->path));
+
+ /* Example: rcs->path is "/foo/bar/Attic/baz,v". */
+ p = last_component (rcs->path);
+ strncpy (newpath, rcs->path, p - rcs->path - 1);
+ newpath[p - rcs->path - 1] = '\0';
+ q = newpath + (p - rcs->path - 1) - (sizeof CVSATTIC - 1);
+ assert (strncmp (q, CVSATTIC, sizeof CVSATTIC - 1) == 0);
+ strcpy (q, p);
+
+ if (CVS_RENAME (rcs->path, newpath) < 0)
+ {
+ error (0, errno, "failed to move `%s' out of the attic",
+ rcs->path);
+ free (newpath);
+ return 1;
+ }
+ }
+
+ free (rcs->path);
+ rcs->path = newpath;
+
+ return 0;
+}
+
+/*
+ * Fully parse the RCS file. Store all keyword/value pairs, fetch the
+ * log messages for each revision, and fetch add and delete counts for
+ * each revision (we could fetch the entire text for each revision,
+ * but the only caller, log_fileproc, doesn't need that information,
+ * so we don't waste the memory required to store it). The add and
+ * delete counts are stored on the OTHER field of the RCSVERSNODE
+ * structure, under the names ";add" and ";delete", so that we don't
+ * waste the memory space of extra fields in RCSVERSNODE for code
+ * which doesn't need this information.
+ */
+
+void
+RCS_fully_parse (rcs)
+ RCSNode *rcs;
+{
+ FILE *fp;
+ struct rcsbuffer rcsbuf;
+
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
+
+ while (1)
+ {
+ char *key, *value;
+ Node *vers;
+ RCSVers *vnode;
+
+ /* Rather than try to keep track of how much information we
+ have read, just read to the end of the file. */
+ if (! rcsbuf_getrevnum (&rcsbuf, &key))
+ break;
+
+ vers = findnode (rcs->versions, key);
+ if (vers == NULL)
+ error (1, 0,
+ "mismatch in rcs file %s between deltas and deltatexts",
+ rcs->path);
+
+ vnode = (RCSVers *) vers->data;
+
+ while (rcsbuf_getkey (&rcsbuf, &key, &value))
+ {
+ if (! STREQ (key, "text"))
+ {
+ Node *kv;
+
+ if (vnode->other == NULL)
+ vnode->other = getlist ();
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (key);
+ kv->data = rcsbuf_valcopy (&rcsbuf, value, 1, (size_t *) NULL);
+ if (addnode (vnode->other, kv) != 0)
+ {
+ error (0, 0,
+ "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+ key, vnode->version, rcs->path);
+ freenode (kv);
+ }
+
+ continue;
+ }
+
+ if (! STREQ (vnode->version, rcs->head))
+ {
+ unsigned long add, del;
+ char buf[50];
+ Node *kv;
+
+ /* This is a change text. Store the add and delete
+ counts. */
+ add = 0;
+ del = 0;
+ if (value != NULL)
+ {
+ size_t vallen;
+ const char *cp;
+
+ rcsbuf_valpolish (&rcsbuf, value, 0, &vallen);
+ cp = value;
+ while (cp < value + vallen)
+ {
+ char op;
+ unsigned long count;
+
+ op = *cp++;
+ if (op != 'a' && op != 'd')
+ error (1, 0, "\
+unrecognized operation '\\x%x' in %s",
+ op, rcs->path);
+ (void) strtoul (cp, (char **) &cp, 10);
+ if (*cp++ != ' ')
+ error (1, 0, "space expected in %s",
+ rcs->path);
+ count = strtoul (cp, (char **) &cp, 10);
+ if (*cp++ != '\012')
+ error (1, 0, "linefeed expected in %s",
+ rcs->path);
+
+ if (op == 'd')
+ del += count;
+ else
+ {
+ add += count;
+ while (count != 0)
+ {
+ if (*cp == '\012')
+ --count;
+ else if (cp == value + vallen)
+ {
+ if (count != 1)
+ error (1, 0, "\
+invalid rcs file %s: premature end of value",
+ rcs->path);
+ else
+ break;
+ }
+ ++cp;
+ }
+ }
+ }
+ }
+
+ sprintf (buf, "%lu", add);
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (";add");
+ kv->data = xstrdup (buf);
+ if (addnode (vnode->other, kv) != 0)
+ {
+ error (0, 0,
+ "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+ key, vnode->version, rcs->path);
+ freenode (kv);
+ }
+
+ sprintf (buf, "%lu", del);
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = xstrdup (";delete");
+ kv->data = xstrdup (buf);
+ if (addnode (vnode->other, kv) != 0)
+ {
+ error (0, 0,
+ "\
+warning: duplicate key `%s' in version `%s' of RCS file `%s'",
+ key, vnode->version, rcs->path);
+ freenode (kv);
+ }
+ }
+
+ /* We have found the "text" key which ends the data for
+ this revision. Break out of the loop and go on to the
+ next revision. */
+ break;
+ }
+ }
+
+ rcsbuf_cache (rcs, &rcsbuf);
+}
+
+/*
+ * freercsnode - free up the info for an RCSNode
+ */
+void
+freercsnode (rnodep)
+ RCSNode **rnodep;
+{
+ if (rnodep == NULL || *rnodep == NULL)
+ return;
+
+ ((*rnodep)->refcount)--;
+ if ((*rnodep)->refcount != 0)
+ {
+ *rnodep = (RCSNode *) NULL;
+ return;
+ }
+ free ((*rnodep)->path);
+ if ((*rnodep)->head != (char *) NULL)
+ free ((*rnodep)->head);
+ if ((*rnodep)->branch != (char *) NULL)
+ free ((*rnodep)->branch);
+ free_rcsnode_contents (*rnodep);
+ free ((char *) *rnodep);
+ *rnodep = (RCSNode *) NULL;
+}
+
+/*
+ * free_rcsnode_contents - free up the contents of an RCSNode without
+ * freeing the node itself, or the file name, or the head, or the
+ * path. This returns the RCSNode to the state it is in immediately
+ * after a call to RCS_parse.
+ */
+static void
+free_rcsnode_contents (rnode)
+ RCSNode *rnode;
+{
+ dellist (&rnode->versions);
+ if (rnode->symbols != (List *) NULL)
+ dellist (&rnode->symbols);
+ if (rnode->symbols_data != (char *) NULL)
+ free (rnode->symbols_data);
+ if (rnode->expand != NULL)
+ free (rnode->expand);
+ if (rnode->other != (List *) NULL)
+ dellist (&rnode->other);
+ if (rnode->access != NULL)
+ free (rnode->access);
+ if (rnode->locks_data != NULL)
+ free (rnode->locks_data);
+ if (rnode->locks != (List *) NULL)
+ dellist (&rnode->locks);
+ if (rnode->comment != NULL)
+ free (rnode->comment);
+ if (rnode->desc != NULL)
+ free (rnode->desc);
+}
+
+/* free_rcsvers_contents -- free up the contents of an RCSVers node,
+ but also free the pointer to the node itself. */
+/* Note: The `hardlinks' list is *not* freed, since it is merely a
+ pointer into the `hardlist' structure (defined in hardlink.c), and
+ that structure is freed elsewhere in the program. */
+
+static void
+free_rcsvers_contents (rnode)
+ RCSVers *rnode;
+{
+ if (rnode->branches != (List *) NULL)
+ dellist (&rnode->branches);
+ if (rnode->date != (char *) NULL)
+ free (rnode->date);
+ if (rnode->next != (char *) NULL)
+ free (rnode->next);
+ if (rnode->author != (char *) NULL)
+ free (rnode->author);
+ if (rnode->state != (char *) NULL)
+ free (rnode->state);
+ if (rnode->other != (List *) NULL)
+ dellist (&rnode->other);
+ if (rnode->other_delta != NULL)
+ dellist (&rnode->other_delta);
+ if (rnode->text != NULL)
+ freedeltatext (rnode->text);
+ free ((char *) rnode);
+}
+
+/*
+ * rcsvers_delproc - free up an RCSVers type node
+ */
+static void
+rcsvers_delproc (p)
+ Node *p;
+{
+ free_rcsvers_contents ((RCSVers *) p->data);
+}
+
+/* These functions retrieve keys and values from an RCS file using a
+ buffer. We use this somewhat complex approach because it turns out
+ that for many common operations, CVS spends most of its time
+ reading keys, so it's worth doing some fairly hairy optimization. */
+
+/* The number of bytes we try to read each time we need more data. */
+
+#define RCSBUF_BUFSIZE (8192)
+
+/* The buffer we use to store data. This grows as needed. */
+
+static char *rcsbuf_buffer = NULL;
+static size_t rcsbuf_buffer_size = 0;
+
+/* Whether rcsbuf_buffer is in use. This is used as a sanity check. */
+
+static int rcsbuf_inuse;
+
+/* Set up to start gathering keys and values from an RCS file. This
+ initializes RCSBUF. */
+
+static void
+rcsbuf_open (rcsbuf, fp, filename, pos)
+ struct rcsbuffer *rcsbuf;
+ FILE *fp;
+ const char *filename;
+ unsigned long pos;
+{
+ if (rcsbuf_inuse)
+ error (1, 0, "rcsbuf_open: internal error");
+ rcsbuf_inuse = 1;
+
+ if (rcsbuf_buffer_size < RCSBUF_BUFSIZE)
+ expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size, RCSBUF_BUFSIZE);
+
+ rcsbuf->ptr = rcsbuf_buffer;
+ rcsbuf->ptrend = rcsbuf_buffer;
+ rcsbuf->fp = fp;
+ rcsbuf->filename = filename;
+ rcsbuf->pos = pos;
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+}
+
+/* Stop gathering keys from an RCS file. */
+
+static void
+rcsbuf_close (rcsbuf)
+ struct rcsbuffer *rcsbuf;
+{
+ if (! rcsbuf_inuse)
+ error (1, 0, "rcsbuf_close: internal error");
+ rcsbuf_inuse = 0;
+}
+
+/* Read a key/value pair from an RCS file. This sets *KEYP to point
+ to the key, and *VALUEP to point to the value. A missing or empty
+ value is indicated by setting *VALUEP to NULL.
+
+ This function returns 1 on success, or 0 on EOF. If there is an
+ error reading the file, or an EOF in an unexpected location, it
+ gives a fatal error.
+
+ This sets *KEYP and *VALUEP to point to storage managed by
+ rcsbuf_getkey. Moreover, *VALUEP has not been massaged from the
+ RCS format: it may contain embedded whitespace and embedded '@'
+ characters. Call rcsbuf_valcopy or rcsbuf_valpolish to do
+ appropriate massaging. */
+
+static int
+rcsbuf_getkey (rcsbuf, keyp, valp)
+ struct rcsbuffer *rcsbuf;
+ char **keyp;
+ char **valp;
+{
+ register const char * const my_spacetab = spacetab;
+ register char *ptr, *ptrend;
+ char c;
+
+#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
+
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ /* Sanity check. */
+ if (ptr < rcsbuf_buffer || ptr > rcsbuf_buffer + rcsbuf_buffer_size)
+ abort ();
+
+ /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
+ buffer, move back to the start of the buffer. This keeps the
+ buffer from growing indefinitely. */
+ if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
+ {
+ int len;
+
+ len = ptrend - ptr;
+
+ /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
+ at a time, so we can't have more bytes than that past PTR. */
+ if (len > RCSBUF_BUFSIZE)
+ abort ();
+
+ /* Update the POS field, which holds the file offset of the
+ first byte in the RCSBUF_BUFFER buffer. */
+ rcsbuf->pos += ptr - rcsbuf_buffer;
+
+ memcpy (rcsbuf_buffer, ptr, len);
+ ptr = rcsbuf_buffer;
+ ptrend = ptr + len;
+ rcsbuf->ptrend = ptrend;
+ }
+
+ /* Skip leading whitespace. */
+
+ while (1)
+ {
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ return 0;
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ if (! my_whitespace (c))
+ break;
+
+ ++ptr;
+ }
+
+ /* We've found the start of the key. */
+
+ *keyp = ptr;
+
+ if (c != ';')
+ {
+ while (1)
+ {
+ ++ptr;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "EOF in key in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ c = *ptr;
+ if (c == ';' || my_whitespace (c))
+ break;
+ }
+ }
+
+ /* Here *KEYP points to the key in the buffer, C is the character
+ we found at the of the key, and PTR points to the location in
+ the buffer where we found C. We must set *PTR to \0 in order
+ to terminate the key. If the key ended with ';', then there is
+ no value. */
+
+ *ptr = '\0';
+ ++ptr;
+
+ if (c == ';')
+ {
+ *valp = NULL;
+ rcsbuf->ptr = ptr;
+ return 1;
+ }
+
+ /* C must be whitespace. Skip whitespace between the key and the
+ value. If we find ';' now, there is no value. */
+
+ while (1)
+ {
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "EOF while looking for value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ c = *ptr;
+ if (c == ';')
+ {
+ *valp = NULL;
+ rcsbuf->ptr = ptr + 1;
+ return 1;
+ }
+ if (! my_whitespace (c))
+ break;
+ ++ptr;
+ }
+
+ /* Now PTR points to the start of the value, and C is the first
+ character of the value. */
+
+ if (c != '@')
+ *valp = ptr;
+ else
+ {
+ char *pat;
+ size_t vlen;
+
+ /* Optimize the common case of a value composed of a single
+ '@' string. */
+
+ rcsbuf->at_string = 1;
+
+ ++ptr;
+
+ *valp = ptr;
+
+ while (1)
+ {
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+ {
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to
+ the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ /* Note that we pass PAT, not PTR, here. */
+ pat = rcsbuf_fill (rcsbuf, pat, keyp, valp);
+ if (pat == NULL)
+ {
+ /* EOF here is OK; it just means that the last
+ character of the file was an '@' terminating a
+ value for a key type which does not require a
+ trailing ';'. */
+ pat = rcsbuf->ptrend - 1;
+
+ }
+ ptrend = rcsbuf->ptrend;
+
+ /* Note that the value of PTR is bogus here. This is
+ OK, because we don't use it. */
+ }
+
+ if (pat + 1 >= ptrend || pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
+ }
+
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = '\0';
+
+ vlen = pat - *valp;
+ if (vlen == 0)
+ *valp = NULL;
+ rcsbuf->vlen = vlen;
+
+ ptr = pat + 1;
+ }
+
+ /* Certain keywords only have a '@' string. If there is no '@'
+ string, then the old getrcskey function assumed that they had
+ no value, and we do the same. */
+
+ {
+ char *k;
+
+ k = *keyp;
+ if (STREQ (k, RCSDESC)
+ || STREQ (k, "text")
+ || STREQ (k, "log"))
+ {
+ if (c != '@')
+ *valp = NULL;
+ rcsbuf->ptr = ptr;
+ return 1;
+ }
+ }
+
+ /* If we've already gathered a '@' string, try to skip whitespace
+ and find a ';'. */
+ if (c == '@')
+ {
+ while (1)
+ {
+ char n;
+
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ n = *ptr;
+ if (n == ';')
+ {
+ /* We're done. We already set everything up for this
+ case above. */
+ rcsbuf->ptr = ptr + 1;
+ return 1;
+ }
+ if (! my_whitespace (n))
+ break;
+ ++ptr;
+ }
+
+ /* The value extends past the '@' string. We need to undo the
+ closing of the '@' done in the default case above. This
+ case never happens in a plain RCS file, but it can happen
+ if user defined phrases are used. */
+ if (rcsbuf->vlen != 0)
+ (*valp)[rcsbuf->vlen] = ' ';
+ else
+ *valp = ptr;
+ }
+
+ /* Here we have a value which is not a simple '@' string. We need
+ to gather up everything until the next ';', including any '@'
+ strings. *VALP points to the start of the value. If
+ RCSBUF->VLEN is not zero, then we have already read an '@'
+ string, and PTR points to the data following the '@' string.
+ Otherwise, PTR points to the start of the value. */
+
+ while (1)
+ {
+ char *start, *psemi, *pat;
+
+ /* Find the ';' which must end the value. */
+ start = ptr;
+ while ((psemi = memchr (ptr, ';', ptrend - ptr)) == NULL)
+ {
+ int slen;
+
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to the
+ location corresponding to the old PTREND, so that we
+ don't search the same bytes again. */
+ slen = start - *valp;
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s", rcsbuf->filename);
+ start = *valp + slen;
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* See if there are any '@' strings in the value. */
+ pat = memchr (start, '@', psemi - start);
+
+ if (pat == NULL)
+ {
+ size_t vlen;
+
+ /* We're done with the value. Trim any trailing
+ whitespace. */
+
+ rcsbuf->ptr = psemi + 1;
+
+ start = *valp;
+ while (psemi > start && my_whitespace (psemi[-1]))
+ --psemi;
+ *psemi = '\0';
+
+ vlen = psemi - start;
+ if (vlen == 0)
+ *valp = NULL;
+ rcsbuf->vlen = vlen;
+
+ return 1;
+ }
+
+ /* We found an '@' string in the value. We set
+ RCSBUF->AT_STRING, which means that we won't be able to
+ compress whitespace correctly for this type of value.
+ Since this type of value never arises in a normal RCS file,
+ this should not be a big deal. It means that if anybody
+ adds a phrase which can have both an '@' string and regular
+ text, they will have to handle whitespace compression
+ themselves. */
+
+ rcsbuf->at_string = 1;
+
+ *pat = ' ';
+
+ ptr = pat + 1;
+
+ while (1)
+ {
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+ {
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuff_fill, so that we will wind up setting PTR
+ to the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, keyp, valp);
+ if (ptr == NULL)
+ error (1, 0, "EOF in value in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ if (pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
+ }
+
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = ' ';
+
+ ptr = pat + 1;
+ }
+
+#undef my_whitespace
+}
+
+/* TODO: Eliminate redundant code in rcsbuf_getkey, rcsbuf_getid,
+ rcsbuf_getstring, rcsbuf_getword. These last three functions were
+ all created by hacking monstrous swaths of code from rcsbuf_getkey,
+ and some engineering would make the code easier to read and
+ maintain.
+
+ Note that the extreme hair in rcsbuf_getkey is because profiling
+ statistics show that it was worth it.
+
+ We probably could be processing "hardlinks" by first calling
+ rcsbuf_getkey, and breaking up the value afterwards; the code to
+ break it up would not need to be hacked for speed. This would
+ remove the need for rcsbuf_getword, rcsbuf_getid, and
+ rcsbuf_getstring, as the other calls are easy to remove. */
+
+/* Read an `id' (in the sense of rcsfile(5)) from RCSBUF, and store in
+ IDP. */
+
+static int
+rcsbuf_getid (rcsbuf, idp)
+ struct rcsbuffer *rcsbuf;
+ char **idp;
+{
+ register const char * const my_spacetab = spacetab;
+ register char *ptr, *ptrend;
+ char c;
+
+#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
+
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ /* Sanity check. */
+ if (ptr < rcsbuf_buffer || ptr > rcsbuf_buffer + rcsbuf_buffer_size)
+ abort ();
+
+ /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
+ buffer, move back to the start of the buffer. This keeps the
+ buffer from growing indefinitely. */
+ if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
+ {
+ int len;
+
+ len = ptrend - ptr;
+
+ /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
+ at a time, so we can't have more bytes than that past PTR. */
+ if (len > RCSBUF_BUFSIZE)
+ abort ();
+
+ /* Update the POS field, which holds the file offset of the
+ first byte in the RCSBUF_BUFFER buffer. */
+ rcsbuf->pos += ptr - rcsbuf_buffer;
+
+ memcpy (rcsbuf_buffer, ptr, len);
+ ptr = rcsbuf_buffer;
+ ptrend = ptr + len;
+ rcsbuf->ptrend = ptrend;
+ }
+
+ /* Skip leading whitespace. */
+
+ while (1)
+ {
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ return 0;
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ if (! my_whitespace (c))
+ break;
+
+ ++ptr;
+ }
+
+ /* We've found the start of the key. */
+
+ *idp = ptr;
+
+ if (c != ';')
+ {
+ while (1)
+ {
+ ++ptr;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, idp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "EOF in key in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+ c = *ptr;
+ if (c == ';' || my_whitespace (c))
+ break;
+ }
+ }
+
+ /* Here *IDP points to the id in the buffer, C is the character
+ we found at the end of the key, and PTR points to the location in
+ the buffer where we found C. We may not set *PTR to \0, because
+ it may overwrite a terminating semicolon. The calling function
+ must copy and terminate the id on its own. */
+
+ rcsbuf->ptr = ptr;
+ return 1;
+
+#undef my_whitespace
+}
+
+/* Read an RCS @-delimited string. Store the result in STRP. */
+
+static int
+rcsbuf_getstring (rcsbuf, strp)
+ struct rcsbuffer *rcsbuf;
+ char **strp;
+{
+ register const char * const my_spacetab = spacetab;
+ register char *ptr, *ptrend;
+ char *pat;
+ size_t vlen;
+ char c;
+
+#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
+
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ /* Sanity check. */
+ if (ptr < rcsbuf_buffer || ptr > rcsbuf_buffer + rcsbuf_buffer_size)
+ abort ();
+
+ /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
+ buffer, move back to the start of the buffer. This keeps the
+ buffer from growing indefinitely. */
+ if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
+ {
+ int len;
+
+ len = ptrend - ptr;
+
+ /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
+ at a time, so we can't have more bytes than that past PTR. */
+ if (len > RCSBUF_BUFSIZE)
+ abort ();
+
+ /* Update the POS field, which holds the file offset of the
+ first byte in the RCSBUF_BUFFER buffer. */
+ rcsbuf->pos += ptr - rcsbuf_buffer;
+
+ memcpy (rcsbuf_buffer, ptr, len);
+ ptr = rcsbuf_buffer;
+ ptrend = ptr + len;
+ rcsbuf->ptrend = ptrend;
+ }
+
+ /* Skip leading whitespace. */
+
+ while (1)
+ {
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "unexpected end of file reading %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ if (! my_whitespace (c))
+ break;
+
+ ++ptr;
+ }
+
+ /* PTR should now point to the start of a string. */
+ if (c != '@')
+ error (1, 0, "expected @-string at '\\x%x' in %s",
+ c, rcsbuf->filename);
+
+ /* Optimize the common case of a value composed of a single
+ '@' string. */
+
+ rcsbuf->at_string = 1;
+
+ ++ptr;
+
+ *strp = ptr;
+
+ while (1)
+ {
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+ {
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to
+ the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, NULL, strp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ /* Note that we pass PAT, not PTR, here. */
+ pat = rcsbuf_fill (rcsbuf, pat, NULL, strp);
+ if (pat == NULL)
+ {
+ /* EOF here is OK; it just means that the last
+ character of the file was an '@' terminating a
+ value for a key type which does not require a
+ trailing ';'. */
+ pat = rcsbuf->ptrend - 1;
+
+ }
+ ptrend = rcsbuf->ptrend;
+
+ /* Note that the value of PTR is bogus here. This is
+ OK, because we don't use it. */
+ }
+
+ if (pat + 1 >= ptrend || pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
+ }
+
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = '\0';
+
+ vlen = pat - *strp;
+ if (vlen == 0)
+ *strp = NULL;
+ rcsbuf->vlen = vlen;
+ rcsbuf->ptr = pat + 1;
+
+ return 1;
+
+#undef my_whitespace
+}
+
+/* Read an RCS `word', in the sense of rcsfile(5) (an id, a num, a
+ @-delimited string, or `:'). Store the result in WORDP. If a
+ `;' is reached without reading any text, the result is NULL. */
+
+static int
+rcsbuf_getword (rcsbuf, wordp)
+ struct rcsbuffer *rcsbuf;
+ char **wordp;
+{
+ register const char * const my_spacetab = spacetab;
+ register char *ptr, *ptrend;
+ char c;
+
+#define my_whitespace(c) (my_spacetab[(unsigned char)c] != 0)
+
+ rcsbuf->vlen = 0;
+ rcsbuf->at_string = 0;
+ rcsbuf->embedded_at = 0;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ /* Sanity check. */
+ if (ptr < rcsbuf_buffer || ptr > rcsbuf_buffer + rcsbuf_buffer_size)
+ abort ();
+
+ /* If the pointer is more than RCSBUF_BUFSIZE bytes into the
+ buffer, move back to the start of the buffer. This keeps the
+ buffer from growing indefinitely. */
+ if (ptr - rcsbuf_buffer >= RCSBUF_BUFSIZE)
+ {
+ int len;
+
+ len = ptrend - ptr;
+
+ /* Sanity check: we don't read more than RCSBUF_BUFSIZE bytes
+ at a time, so we can't have more bytes than that past PTR. */
+ if (len > RCSBUF_BUFSIZE)
+ abort ();
+
+ /* Update the POS field, which holds the file offset of the
+ first byte in the RCSBUF_BUFFER buffer. */
+ rcsbuf->pos += ptr - rcsbuf_buffer;
+
+ memcpy (rcsbuf_buffer, ptr, len);
+ ptr = rcsbuf_buffer;
+ ptrend = ptr + len;
+ rcsbuf->ptrend = ptrend;
+ }
+
+ /* Skip leading whitespace. */
+
+ while (1)
+ {
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0, "unexpected end of file reading %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ if (! my_whitespace (c))
+ break;
+
+ ++ptr;
+ }
+
+ /* If we have reached `;', there is no value. */
+ if (c == ';')
+ {
+ *wordp = NULL;
+ *ptr++ = '\0';
+ rcsbuf->ptr = ptr;
+ rcsbuf->vlen = 0;
+ return 1;
+ }
+
+ /* PTR now points to the start of a value. Find out whether it is
+ a num, an id, a string or a colon. */
+ if (c == ':')
+ {
+ *wordp = ptr++;
+ rcsbuf->ptr = ptr;
+ rcsbuf->vlen = 1;
+ return 1;
+ }
+
+ if (c == '@')
+ {
+ char *pat;
+ size_t vlen;
+
+ /* Optimize the common case of a value composed of a single
+ '@' string. */
+
+ rcsbuf->at_string = 1;
+
+ ++ptr;
+
+ *wordp = ptr;
+
+ while (1)
+ {
+ while ((pat = memchr (ptr, '@', ptrend - ptr)) == NULL)
+ {
+ /* Note that we pass PTREND as the PTR value to
+ rcsbuf_fill, so that we will wind up setting PTR to
+ the location corresponding to the old PTREND, so
+ that we don't search the same bytes again. */
+ ptr = rcsbuf_fill (rcsbuf, ptrend, NULL, wordp);
+ if (ptr == NULL)
+ error (1, 0,
+ "EOF while looking for end of string in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* Handle the special case of an '@' right at the end of
+ the known bytes. */
+ if (pat + 1 >= ptrend)
+ {
+ /* Note that we pass PAT, not PTR, here. */
+ pat = rcsbuf_fill (rcsbuf, pat, NULL, wordp);
+ if (pat == NULL)
+ {
+ /* EOF here is OK; it just means that the last
+ character of the file was an '@' terminating a
+ value for a key type which does not require a
+ trailing ';'. */
+ pat = rcsbuf->ptrend - 1;
+
+ }
+ ptrend = rcsbuf->ptrend;
+
+ /* Note that the value of PTR is bogus here. This is
+ OK, because we don't use it. */
+ }
+
+ if (pat + 1 >= ptrend || pat[1] != '@')
+ break;
+
+ /* We found an '@' pair in the string. Keep looking. */
+ ++rcsbuf->embedded_at;
+ ptr = pat + 2;
+ }
+
+ /* Here PAT points to the final '@' in the string. */
+
+ *pat = '\0';
+
+ vlen = pat - *wordp;
+ if (vlen == 0)
+ *wordp = NULL;
+ rcsbuf->vlen = vlen;
+ rcsbuf->ptr = pat + 1;
+
+ return 1;
+ }
+
+ /* C is neither `:', `;' nor `@', so it should be the start of a num
+ or an id. Make sure it is not another special character. */
+ if (c == '$' || c == '.' || c == ',')
+ {
+ error (1, 0, "illegal special character in RCS field in %s",
+ rcsbuf->filename);
+ }
+
+ *wordp = ptr;
+ while (1)
+ {
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, wordp);
+ if (ptr == NULL)
+ error (1, 0, "unexpected end of file reading %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ /* Legitimate ID characters are digits, dots and any `graphic
+ printing character that is not a special.' This test ought
+ to do the trick. */
+ c = *ptr;
+ if (isprint ((unsigned char) c) &&
+ c != ';' && c != '$' && c != ',' && c != '@' && c != ':')
+ {
+ ++ptr;
+ continue;
+ }
+ break;
+ }
+
+ /* PTR points to the last non-id character in this word, and C is
+ the character in its memory cell. Check to make sure that it
+ is a legitimate word delimiter -- whitespace or semicolon. */
+ if (c == ';' || my_whitespace (c))
+ {
+ rcsbuf->vlen = ptr - *wordp;
+ rcsbuf->ptr = ptr;
+ return 1;
+ }
+
+ error (1, 0, "illegal special character in RCS field in %s",
+ rcsbuf->filename);
+ /* Shut up compiler warnings. */
+ return 0;
+
+#undef my_whitespace
+}
+
+/* Read an RCS revision number from an RCS file. This sets *REVP to
+ point to the revision number; it will point to space that is
+ managed by the rcsbuf functions, and is only good until the next
+ call to rcsbuf_getkey or rcsbuf_getrevnum.
+
+ This function returns 1 on success, or 0 on EOF. If there is an
+ error reading the file, or an EOF in an unexpected location, it
+ gives a fatal error. */
+
+static int
+rcsbuf_getrevnum (rcsbuf, revp)
+ struct rcsbuffer *rcsbuf;
+ char **revp;
+{
+ char *ptr, *ptrend;
+ char c;
+
+ ptr = rcsbuf->ptr;
+ ptrend = rcsbuf->ptrend;
+
+ *revp = NULL;
+
+ /* Skip leading whitespace. */
+
+ while (1)
+ {
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, (char **) NULL, (char **) NULL);
+ if (ptr == NULL)
+ return 0;
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ if (! whitespace (c))
+ break;
+
+ ++ptr;
+ }
+
+ if (! isdigit ((unsigned char) c) && c != '.')
+ error (1, 0,
+ "\
+unexpected '\\x%x' reading revision number in RCS file %s",
+ c, rcsbuf->filename);
+
+ *revp = ptr;
+
+ do
+ {
+ ++ptr;
+ if (ptr >= ptrend)
+ {
+ ptr = rcsbuf_fill (rcsbuf, ptr, revp, (char **) NULL);
+ if (ptr == NULL)
+ error (1, 0,
+ "unexpected EOF reading revision number in RCS file %s",
+ rcsbuf->filename);
+ ptrend = rcsbuf->ptrend;
+ }
+
+ c = *ptr;
+ }
+ while (isdigit ((unsigned char) c) || c == '.');
+
+ if (! whitespace (c))
+ error (1, 0, "\
+unexpected '\\x%x' reading revision number in RCS file %s",
+ c, rcsbuf->filename);
+
+ *ptr = '\0';
+
+ rcsbuf->ptr = ptr + 1;
+
+ return 1;
+}
+
+/* Fill RCSBUF_BUFFER with bytes from the file associated with RCSBUF,
+ updating PTR and the PTREND field. If KEYP and *KEYP are not NULL,
+ then *KEYP points into the buffer, and must be adjusted if the
+ buffer is changed. Likewise for VALP. Returns the new value of
+ PTR, or NULL on error. */
+
+static char *
+rcsbuf_fill (rcsbuf, ptr, keyp, valp)
+ struct rcsbuffer *rcsbuf;
+ char *ptr;
+ char **keyp;
+ char **valp;
+{
+ int got;
+
+ if (rcsbuf->ptrend - rcsbuf_buffer + RCSBUF_BUFSIZE > rcsbuf_buffer_size)
+ {
+ int poff, peoff, koff, voff;
+
+ poff = ptr - rcsbuf_buffer;
+ peoff = rcsbuf->ptrend - rcsbuf_buffer;
+ if (keyp != NULL && *keyp != NULL)
+ koff = *keyp - rcsbuf_buffer;
+ if (valp != NULL && *valp != NULL)
+ voff = *valp - rcsbuf_buffer;
+
+ expand_string (&rcsbuf_buffer, &rcsbuf_buffer_size,
+ rcsbuf_buffer_size + RCSBUF_BUFSIZE);
+
+ ptr = rcsbuf_buffer + poff;
+ rcsbuf->ptrend = rcsbuf_buffer + peoff;
+ if (keyp != NULL && *keyp != NULL)
+ *keyp = rcsbuf_buffer + koff;
+ if (valp != NULL && *valp != NULL)
+ *valp = rcsbuf_buffer + voff;
+ }
+
+ got = fread (rcsbuf->ptrend, 1, RCSBUF_BUFSIZE, rcsbuf->fp);
+ if (got == 0)
+ {
+ if (ferror (rcsbuf->fp))
+ error (1, errno, "cannot read %s", rcsbuf->filename);
+ return NULL;
+ }
+
+ rcsbuf->ptrend += got;
+
+ return ptr;
+}
+
+/* Copy the value VAL returned by rcsbuf_getkey into a memory buffer,
+ returning the memory buffer. Polish the value like
+ rcsbuf_valpolish, q.v. */
+
+static char *
+rcsbuf_valcopy (rcsbuf, val, polish, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *val;
+ int polish;
+ size_t *lenp;
+{
+ size_t vlen;
+ int embedded_at;
+ char *ret;
+
+ if (val == NULL)
+ {
+ if (lenp != NULL)
+ *lenp = 0;
+ return NULL;
+ }
+
+ vlen = rcsbuf->vlen;
+ embedded_at = rcsbuf->embedded_at;
+
+ ret = xmalloc (vlen - embedded_at + 1);
+
+ if (rcsbuf->at_string ? embedded_at == 0 : ! polish)
+ {
+ /* No special action to take. */
+ memcpy (ret, val, vlen + 1);
+ if (lenp != NULL)
+ *lenp = vlen;
+ return ret;
+ }
+
+ rcsbuf_valpolish_internal (rcsbuf, ret, val, lenp);
+ return ret;
+}
+
+/* Polish the value VAL returned by rcsbuf_getkey. The POLISH
+ parameter is non-zero if multiple embedded whitespace characters
+ should be compressed into a single whitespace character. Note that
+ leading and trailing whitespace was already removed by
+ rcsbuf_getkey. Within an '@' string, pairs of '@' characters are
+ compressed into a single '@' character regardless of the value of
+ POLISH. If LENP is not NULL, set *LENP to the length of the value. */
+
+static void
+rcsbuf_valpolish (rcsbuf, val, polish, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *val;
+ int polish;
+ size_t *lenp;
+{
+ if (val == NULL)
+ {
+ if (lenp != NULL)
+ *lenp= 0;
+ return;
+ }
+
+ if (rcsbuf->at_string ? rcsbuf->embedded_at == 0 : ! polish)
+ {
+ /* No special action to take. */
+ if (lenp != NULL)
+ *lenp = rcsbuf->vlen;
+ return;
+ }
+
+ rcsbuf_valpolish_internal (rcsbuf, val, val, lenp);
+}
+
+/* Internal polishing routine, called from rcsbuf_valcopy and
+ rcsbuf_valpolish. */
+
+static void
+rcsbuf_valpolish_internal (rcsbuf, to, from, lenp)
+ struct rcsbuffer *rcsbuf;
+ char *to;
+ const char *from;
+ size_t *lenp;
+{
+ size_t len;
+
+ len = rcsbuf->vlen;
+
+ if (! rcsbuf->at_string)
+ {
+ char *orig_to;
+ size_t clen;
+
+ orig_to = to;
+
+ for (clen = len; clen > 0; ++from, --clen)
+ {
+ char c;
+
+ c = *from;
+ if (whitespace (c))
+ {
+ /* Note that we know that clen can not drop to zero
+ while we have whitespace, because we know there is
+ no trailing whitespace. */
+ while (whitespace (from[1]))
+ {
+ ++from;
+ --clen;
+ }
+ c = ' ';
+ }
+ *to++ = c;
+ }
+
+ *to = '\0';
+
+ if (lenp != NULL)
+ *lenp = to - orig_to;
+ }
+ else
+ {
+ const char *orig_from;
+ char *orig_to;
+ int embedded_at;
+ size_t clen;
+
+ orig_from = from;
+ orig_to = to;
+
+ embedded_at = rcsbuf->embedded_at;
+
+ if (lenp != NULL)
+ *lenp = len - embedded_at;
+
+ for (clen = len; clen > 0; ++from, --clen)
+ {
+ char c;
+
+ c = *from;
+ *to++ = c;
+ if (c == '@')
+ {
+ ++from;
+
+ /* Sanity check. */
+ if (*from != '@' || clen == 0)
+ abort ();
+
+ --clen;
+
+ --embedded_at;
+ if (embedded_at == 0)
+ {
+ /* We've found all the embedded '@' characters.
+ We can just memcpy the rest of the buffer after
+ this '@' character. */
+ if (orig_to != orig_from)
+ memcpy (to, from + 1, clen - 1);
+ else
+ memmove (to, from + 1, clen - 1);
+ from += clen;
+ to += clen - 1;
+ break;
+ }
+ }
+ }
+
+ /* Sanity check. */
+ if (from != orig_from + len
+ || to != orig_to + (len - rcsbuf->embedded_at))
+ {
+ abort ();
+ }
+
+ *to = '\0';
+ }
+}
+
+/* Return the current position of an rcsbuf. */
+
+static unsigned long
+rcsbuf_ftell (rcsbuf)
+ struct rcsbuffer *rcsbuf;
+{
+ return rcsbuf->pos + (rcsbuf->ptr - rcsbuf_buffer);
+}
+
+/* Return a pointer to any data buffered for RCSBUF, along with the
+ length. */
+
+static void
+rcsbuf_get_buffered (rcsbuf, datap, lenp)
+ struct rcsbuffer *rcsbuf;
+ char **datap;
+ size_t *lenp;
+{
+ *datap = rcsbuf->ptr;
+ *lenp = rcsbuf->ptrend - rcsbuf->ptr;
+}
+
+/* CVS optimizes by quickly reading some header information from a
+ file. If it decides it needs to do more with the file, it reopens
+ it. We speed that up here by maintaining a cache of a single open
+ file, to save the time it takes to reopen the file in the common
+ case. */
+
+static RCSNode *cached_rcs;
+static struct rcsbuffer cached_rcsbuf;
+
+/* Cache RCS and RCSBUF. This takes responsibility for closing
+ RCSBUF->FP. */
+
+static void
+rcsbuf_cache (rcs, rcsbuf)
+ RCSNode *rcs;
+ struct rcsbuffer *rcsbuf;
+{
+ if (cached_rcs != NULL)
+ rcsbuf_cache_close ();
+ cached_rcs = rcs;
+ ++rcs->refcount;
+ cached_rcsbuf = *rcsbuf;
+}
+
+/* If there is anything in the cache, close it. */
+
+static void
+rcsbuf_cache_close ()
+{
+ if (cached_rcs != NULL)
+ {
+ if (fclose (cached_rcsbuf.fp) != 0)
+ error (0, errno, "cannot close %s", cached_rcsbuf.filename);
+ rcsbuf_close (&cached_rcsbuf);
+ freercsnode (&cached_rcs);
+ cached_rcs = NULL;
+ }
+}
+
+/* Open an rcsbuffer for RCS, getting it from the cache if possible.
+ Set *FPP to the file, and *RCSBUFP to the rcsbuf. The file should
+ be put at position POS. */
+
+static void
+rcsbuf_cache_open (rcs, pos, pfp, prcsbuf)
+ RCSNode *rcs;
+ long pos;
+ FILE **pfp;
+ struct rcsbuffer *prcsbuf;
+{
+ if (cached_rcs == rcs)
+ {
+ if (rcsbuf_ftell (&cached_rcsbuf) != pos)
+ {
+ if (fseek (cached_rcsbuf.fp, pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file %s",
+ cached_rcsbuf.filename);
+ cached_rcsbuf.ptr = rcsbuf_buffer;
+ cached_rcsbuf.ptrend = rcsbuf_buffer;
+ cached_rcsbuf.pos = pos;
+ }
+ *pfp = cached_rcsbuf.fp;
+
+ /* When RCS_parse opens a file using fopen_case, it frees the
+ filename which we cached in CACHED_RCSBUF and stores a new
+ file name in RCS->PATH. We avoid problems here by always
+ copying the filename over. FIXME: This is hackish. */
+ cached_rcsbuf.filename = rcs->path;
+
+ *prcsbuf = cached_rcsbuf;
+
+ cached_rcs = NULL;
+
+ /* Removing RCS from the cache removes a reference to it. */
+ --rcs->refcount;
+ if (rcs->refcount <= 0)
+ error (1, 0, "rcsbuf_cache_open: internal error");
+ }
+ else
+ {
+ if (cached_rcs != NULL)
+ rcsbuf_cache_close ();
+
+ *pfp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
+ if (*pfp == NULL)
+ error (1, 0, "unable to reopen `%s'", rcs->path);
+ if (pos != 0)
+ {
+ if (fseek (*pfp, pos, SEEK_SET) != 0)
+ error (1, 0, "cannot fseek RCS file %s", rcs->path);
+ }
+ rcsbuf_open (prcsbuf, *pfp, rcs->path, pos);
+ }
+}
+
+
+/*
+ * process the symbols list of the rcs file
+ */
+static void
+do_symbols (list, val)
+ List *list;
+ char *val;
+{
+ Node *p;
+ char *cp = val;
+ char *tag, *rev;
+
+ for (;;)
+ {
+ /* skip leading whitespace */
+ while (whitespace (*cp))
+ cp++;
+
+ /* if we got to the end, we are done */
+ if (*cp == '\0')
+ break;
+
+ /* split it up into tag and rev */
+ tag = cp;
+ cp = strchr (cp, ':');
+ *cp++ = '\0';
+ rev = cp;
+ while (!whitespace (*cp) && *cp != '\0')
+ cp++;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* make a new node and add it to the list */
+ p = getnode ();
+ p->key = xstrdup (tag);
+ p->data = xstrdup (rev);
+ (void) addnode (list, p);
+ }
+}
+
+/*
+ * process the locks list of the rcs file
+ * Like do_symbols, but hash entries are keyed backwards: i.e.
+ * an entry like `user:rev' is keyed on REV rather than on USER.
+ */
+static void
+do_locks (list, val)
+ List *list;
+ char *val;
+{
+ Node *p;
+ char *cp = val;
+ char *user, *rev;
+
+ for (;;)
+ {
+ /* skip leading whitespace */
+ while (whitespace (*cp))
+ cp++;
+
+ /* if we got to the end, we are done */
+ if (*cp == '\0')
+ break;
+
+ /* split it up into user and rev */
+ user = cp;
+ cp = strchr (cp, ':');
+ *cp++ = '\0';
+ rev = cp;
+ while (!whitespace (*cp) && *cp != '\0')
+ cp++;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* make a new node and add it to the list */
+ p = getnode ();
+ p->key = xstrdup (rev);
+ p->data = xstrdup (user);
+ (void) addnode (list, p);
+ }
+}
+
+/*
+ * process the branches list of a revision delta
+ */
+static void
+do_branches (list, val)
+ List *list;
+ char *val;
+{
+ Node *p;
+ char *cp = val;
+ char *branch;
+
+ for (;;)
+ {
+ /* skip leading whitespace */
+ while (whitespace (*cp))
+ cp++;
+
+ /* if we got to the end, we are done */
+ if (*cp == '\0')
+ break;
+
+ /* find the end of this branch */
+ branch = cp;
+ while (!whitespace (*cp) && *cp != '\0')
+ cp++;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* make a new node and add it to the list */
+ p = getnode ();
+ p->key = xstrdup (branch);
+ (void) addnode (list, p);
+ }
+}
+
+/*
+ * Version Number
+ *
+ * Returns the requested version number of the RCS file, satisfying tags and/or
+ * dates, and walking branches, if necessary.
+ *
+ * The result is returned; null-string if error.
+ */
+char *
+RCS_getversion (rcs, tag, date, force_tag_match, simple_tag)
+ RCSNode *rcs;
+ char *tag;
+ char *date;
+ int force_tag_match;
+ int *simple_tag;
+{
+ if (simple_tag != NULL)
+ *simple_tag = 0;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (tag && date)
+ {
+ char *branch, *rev;
+
+ if (! RCS_nodeisbranch (rcs, tag))
+ {
+ /* We can't get a particular date if the tag is not a
+ branch. */
+ return NULL;
+ }
+
+ /* Work out the branch. */
+ if (! isdigit ((unsigned char) tag[0]))
+ branch = RCS_whatbranch (rcs, tag);
+ else
+ branch = xstrdup (tag);
+
+ /* Fetch the revision of branch as of date. */
+ rev = RCS_getdatebranch (rcs, date, branch);
+ free (branch);
+ return (rev);
+ }
+ else if (tag)
+ return (RCS_gettag (rcs, tag, force_tag_match, simple_tag));
+ else if (date)
+ return (RCS_getdate (rcs, date, force_tag_match));
+ else
+ return (RCS_head (rcs));
+
+}
+
+/*
+ * Get existing revision number corresponding to tag or revision.
+ * Similar to RCS_gettag but less interpretation imposed.
+ * For example:
+ * -- If tag designates a magic branch, RCS_tag2rev
+ * returns the magic branch number.
+ * -- If tag is a branch tag, returns the branch number, not
+ * the revision of the head of the branch.
+ * If tag or revision is not valid or does not exist in file,
+ * exit with error.
+ */
+char *
+RCS_tag2rev (rcs, tag)
+ RCSNode *rcs;
+ char *tag;
+{
+ char *rev, *pa, *pb;
+ int i;
+
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* If a valid revision, try to look it up */
+ if ( RCS_valid_rev (tag) )
+ {
+ /* Make a copy so we can scribble on it */
+ rev = xstrdup (tag);
+
+ /* If revision exists, return the copy */
+ if (RCS_exist_rev (rcs, tag))
+ return rev;
+
+ /* Nope, none such. If tag is not a branch we're done. */
+ i = numdots (rev);
+ if ((i & 1) == 1 )
+ {
+ pa = strrchr (rev, '.');
+ if (i == 1 || *(pa-1) != RCS_MAGIC_BRANCH || *(pa-2) != '.')
+ {
+ free (rev);
+ error (1, 0, "revision `%s' does not exist", tag);
+ }
+ }
+
+ /* Try for a real (that is, exists in the RCS deltas) branch
+ (RCS_exist_rev just checks for real revisions and revisions
+ which have tags pointing to them). */
+ pa = RCS_getbranch (rcs, rev, 1);
+ if (pa != NULL)
+ {
+ free (pa);
+ return rev;
+ }
+
+ /* Tag is branch, but does not exist, try corresponding
+ * magic branch tag.
+ *
+ * FIXME: assumes all magic branches are of
+ * form "n.n.n ... .0.n". I'll fix if somebody can
+ * send me a method to get a magic branch tag with
+ * the 0 in some other position -- <dan@gasboy.com>
+ */
+ pa = strrchr (rev, '.');
+ pb = xmalloc (strlen (rev) + 3);
+ *pa++ = 0;
+ (void) sprintf (pb, "%s.%d.%s", rev, RCS_MAGIC_BRANCH, pa);
+ free (rev);
+ rev = pb;
+ if (RCS_exist_rev (rcs, rev))
+ return rev;
+ error (1, 0, "revision `%s' does not exist", tag);
+ }
+
+
+ RCS_check_tag (tag); /* exit if not a valid tag */
+
+ /* If tag is "HEAD", special case to get head RCS revision */
+ if (tag && STREQ (tag, TAG_HEAD))
+ return (RCS_head (rcs));
+
+ /* If valid tag let translate_symtag say yea or nay. */
+ rev = translate_symtag (rcs, tag);
+
+ if (rev)
+ return rev;
+
+ error (1, 0, "tag `%s' does not exist", tag);
+ /* NOT REACHED -- error (1 ... ) does not return here */
+ return 0;
+}
+
+/*
+ * Find the revision for a specific tag.
+ * If force_tag_match is set, return NULL if an exact match is not
+ * possible otherwise return RCS_head (). We are careful to look for
+ * and handle "magic" revisions specially.
+ *
+ * If the matched tag is a branch tag, find the head of the branch.
+ *
+ * Returns pointer to newly malloc'd string, or NULL.
+ */
+char *
+RCS_gettag (rcs, symtag, force_tag_match, simple_tag)
+ RCSNode *rcs;
+ char *symtag;
+ int force_tag_match;
+ int *simple_tag;
+{
+ char *tag = symtag;
+ int tag_allocated = 0;
+
+ if (simple_tag != NULL)
+ *simple_tag = 0;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ /* XXX this is probably not necessary, --jtc */
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* If tag is "HEAD", special case to get head RCS revision */
+ if (tag && (STREQ (tag, TAG_HEAD) || *tag == '\0'))
+#if 0 /* This #if 0 is only in the Cygnus code. Why? Death support? */
+ if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
+ return ((char *) NULL); /* head request for removed file */
+ else
+#endif
+ return (RCS_head (rcs));
+
+ if (!isdigit ((unsigned char) tag[0]))
+ {
+ char *version;
+
+ /* If we got a symbolic tag, resolve it to a numeric */
+ version = translate_symtag (rcs, tag);
+ if (version != NULL)
+ {
+ int dots;
+ char *magic, *branch, *cp;
+
+ tag = version;
+ tag_allocated = 1;
+
+ /*
+ * If this is a magic revision, we turn it into either its
+ * physical branch equivalent (if one exists) or into
+ * its base revision, which we assume exists.
+ */
+ dots = numdots (tag);
+ if (dots > 2 && (dots & 1) != 0)
+ {
+ branch = strrchr (tag, '.');
+ cp = branch++ - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (tag) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ /* it's magic. See if the branch exists */
+ *cp = '\0'; /* turn it into a revision */
+ (void) sprintf (magic, "%s.%s", tag, branch);
+ branch = RCS_getbranch (rcs, magic, 1);
+ free (magic);
+ if (branch != NULL)
+ {
+ free (tag);
+ return (branch);
+ }
+ return (tag);
+ }
+ free (magic);
+ }
+ }
+ else
+ {
+ /* The tag wasn't there, so return the head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ }
+
+ /*
+ * numeric tag processing:
+ * 1) revision number - just return it
+ * 2) branch number - find head of branch
+ */
+
+ /* strip trailing dots */
+ while (tag[strlen (tag) - 1] == '.')
+ tag[strlen (tag) - 1] = '\0';
+
+ if ((numdots (tag) & 1) == 0)
+ {
+ char *branch;
+
+ /* we have a branch tag, so we need to walk the branch */
+ branch = RCS_getbranch (rcs, tag, force_tag_match);
+ if (tag_allocated)
+ free (tag);
+ return branch;
+ }
+ else
+ {
+ Node *p;
+
+ /* we have a revision tag, so make sure it exists */
+ p = findnode (rcs->versions, tag);
+ if (p != NULL)
+ {
+ /* We have found a numeric revision for the revision tag.
+ To support expanding the RCS keyword Name, if
+ SIMPLE_TAG is not NULL, tell the the caller that this
+ is a simple tag which co will recognize. FIXME: Are
+ there other cases in which we should set this? In
+ particular, what if we expand RCS keywords internally
+ without calling co? */
+ if (simple_tag != NULL)
+ *simple_tag = 1;
+ if (! tag_allocated)
+ tag = xstrdup (tag);
+ return (tag);
+ }
+ else
+ {
+ /* The revision wasn't there, so return the head or NULL */
+ if (tag_allocated)
+ free (tag);
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ }
+}
+
+/*
+ * Return a "magic" revision as a virtual branch off of REV for the RCS file.
+ * A "magic" revision is one which is unique in the RCS file. By unique, I
+ * mean we return a revision which:
+ * - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH)
+ * - has a revision component which is not an existing branch off REV
+ * - has a revision component which is not an existing magic revision
+ * - is an even-numbered revision, to avoid conflicts with vendor branches
+ * The first point is what makes it "magic".
+ *
+ * As an example, if we pass in 1.37 as REV, we will look for an existing
+ * branch called 1.37.2. If it did not exist, we would look for an
+ * existing symbolic tag with a numeric part equal to 1.37.0.2. If that
+ * didn't exist, then we know that the 1.37.2 branch can be reserved by
+ * creating a symbolic tag with 1.37.0.2 as the numeric part.
+ *
+ * This allows us to fork development with very little overhead -- just a
+ * symbolic tag is used in the RCS file. When a commit is done, a physical
+ * branch is dynamically created to hold the new revision.
+ *
+ * Note: We assume that REV is an RCS revision and not a branch number.
+ */
+static char *check_rev;
+char *
+RCS_magicrev (rcs, rev)
+ RCSNode *rcs;
+ char *rev;
+{
+ int rev_num;
+ char *xrev, *test_branch;
+
+ xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */
+ check_rev = xrev;
+
+ /* only look at even numbered branches */
+ for (rev_num = 2; ; rev_num += 2)
+ {
+ /* see if the physical branch exists */
+ (void) sprintf (xrev, "%s.%d", rev, rev_num);
+ test_branch = RCS_getbranch (rcs, xrev, 1);
+ if (test_branch != NULL) /* it did, so keep looking */
+ {
+ free (test_branch);
+ continue;
+ }
+
+ /* now, create a "magic" revision */
+ (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num);
+
+ /* walk the symbols list to see if a magic one already exists */
+ if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0)
+ continue;
+
+ /* we found a free magic branch. Claim it as ours */
+ return (xrev);
+ }
+}
+
+/*
+ * walklist proc to look for a match in the symbols list.
+ * Returns 0 if the symbol does not match, 1 if it does.
+ */
+static int
+checkmagic_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (STREQ (check_rev, p->data))
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Given an RCSNode, returns non-zero if the specified revision number
+ * or symbolic tag resolves to a "branch" within the rcs file.
+ *
+ * FIXME: this is the same as RCS_nodeisbranch except for the special
+ * case for handling a null rcsnode.
+ */
+int
+RCS_isbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ /* numeric revisions are easy -- even number of dots is a branch */
+ if (isdigit ((unsigned char) *rev))
+ return ((numdots (rev) & 1) == 0);
+
+ /* assume a revision if you can't find the RCS info */
+ if (rcs == NULL)
+ return (0);
+
+ /* now, look for a match in the symbols list */
+ return (RCS_nodeisbranch (rcs, rev));
+}
+
+/*
+ * Given an RCSNode, returns non-zero if the specified revision number
+ * or symbolic tag resolves to a "branch" within the rcs file. We do
+ * take into account any magic branches as well.
+ */
+int
+RCS_nodeisbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ int dots;
+ char *version;
+
+ assert (rcs != NULL);
+
+ /* numeric revisions are easy -- even number of dots is a branch */
+ if (isdigit ((unsigned char) *rev))
+ return ((numdots (rev) & 1) == 0);
+
+ version = translate_symtag (rcs, rev);
+ if (version == NULL)
+ return (0);
+ dots = numdots (version);
+ if ((dots & 1) == 0)
+ {
+ free (version);
+ return (1);
+ }
+
+ /* got a symbolic tag match, but it's not a branch; see if it's magic */
+ if (dots > 2)
+ {
+ char *magic;
+ char *branch = strrchr (version, '.');
+ char *cp = branch - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (version) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ free (magic);
+ free (version);
+ return (1);
+ }
+ free (magic);
+ free (version);
+ }
+ return (0);
+}
+
+/*
+ * Returns a pointer to malloc'ed memory which contains the branch
+ * for the specified *symbolic* tag. Magic branches are handled correctly.
+ */
+char *
+RCS_whatbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ char *version;
+ int dots;
+
+ /* assume no branch if you can't find the RCS info */
+ if (rcs == NULL)
+ return ((char *) NULL);
+
+ /* now, look for a match in the symbols list */
+ version = translate_symtag (rcs, rev);
+ if (version == NULL)
+ return ((char *) NULL);
+ dots = numdots (version);
+ if ((dots & 1) == 0)
+ return (version);
+
+ /* got a symbolic tag match, but it's not a branch; see if it's magic */
+ if (dots > 2)
+ {
+ char *magic;
+ char *branch = strrchr (version, '.');
+ char *cp = branch++ - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (version) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ /* yep. it's magic. now, construct the real branch */
+ *cp = '\0'; /* turn it into a revision */
+ (void) sprintf (magic, "%s.%s", version, branch);
+ free (version);
+ return (magic);
+ }
+ free (magic);
+ free (version);
+ }
+ return ((char *) NULL);
+}
+
+/*
+ * Get the head of the specified branch. If the branch does not exist,
+ * return NULL or RCS_head depending on force_tag_match.
+ * Returns NULL or a newly malloc'd string.
+ */
+char *
+RCS_getbranch (rcs, tag, force_tag_match)
+ RCSNode *rcs;
+ char *tag;
+ int force_tag_match;
+{
+ Node *p, *head;
+ RCSVers *vn;
+ char *xtag;
+ char *nextvers;
+ char *cp;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* find out if the tag contains a dot, or is on the trunk */
+ cp = strrchr (tag, '.');
+
+ /* trunk processing is the special case */
+ if (cp == NULL)
+ {
+ xtag = xmalloc (strlen (tag) + 1 + 1); /* +1 for an extra . */
+ (void) strcpy (xtag, tag);
+ (void) strcat (xtag, ".");
+ for (cp = rcs->head; cp != NULL;)
+ {
+ if (strncmp (xtag, cp, strlen (xtag)) == 0)
+ break;
+ p = findnode (rcs->versions, cp);
+ if (p == NULL)
+ {
+ free (xtag);
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ vn = (RCSVers *) p->data;
+ cp = vn->next;
+ }
+ free (xtag);
+ if (cp == NULL)
+ {
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ return (xstrdup (cp));
+ }
+
+ /* if it had a `.', terminate the string so we have the base revision */
+ *cp = '\0';
+
+ /* look up the revision this branch is based on */
+ p = findnode (rcs->versions, tag);
+
+ /* put the . back so we have the branch again */
+ *cp = '.';
+
+ if (p == NULL)
+ {
+ /* if the base revision didn't exist, return head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+
+ /* find the first element of the branch we are looking for */
+ vn = (RCSVers *) p->data;
+ if (vn->branches == NULL)
+ return (NULL);
+ xtag = xmalloc (strlen (tag) + 1 + 1); /* 1 for the extra '.' */
+ (void) strcpy (xtag, tag);
+ (void) strcat (xtag, ".");
+ head = vn->branches->list;
+ for (p = head->next; p != head; p = p->next)
+ if (strncmp (p->key, xtag, strlen (xtag)) == 0)
+ break;
+ free (xtag);
+
+ if (p == head)
+ {
+ /* we didn't find a match so return head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+
+ /* now walk the next pointers of the branch */
+ nextvers = p->key;
+ do
+ {
+ p = findnode (rcs->versions, nextvers);
+ if (p == NULL)
+ {
+ /* a link in the chain is missing - return head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ vn = (RCSVers *) p->data;
+ nextvers = vn->next;
+ } while (nextvers != NULL);
+
+ /* we have the version in our hand, so go for it */
+ return (xstrdup (vn->version));
+}
+
+/* Returns the head of the branch which REV is on. REV can be a
+ branch tag or non-branch tag; symbolic or numeric.
+
+ Returns a newly malloc'd string. Returns NULL if a symbolic name
+ isn't found. */
+
+char *
+RCS_branch_head (rcs, rev)
+ RCSNode *rcs;
+ char *rev;
+{
+ char *num;
+ char *br;
+ char *retval;
+
+ assert (rcs != NULL);
+
+ if (RCS_nodeisbranch (rcs, rev))
+ return RCS_getbranch (rcs, rev, 1);
+
+ if (isdigit ((unsigned char) *rev))
+ num = xstrdup (rev);
+ else
+ {
+ num = translate_symtag (rcs, rev);
+ if (num == NULL)
+ return NULL;
+ }
+ br = truncate_revnum (num);
+ retval = RCS_getbranch (rcs, br, 1);
+ free (br);
+ free (num);
+ return retval;
+}
+
+/* Get the branch point for a particular branch, that is the first
+ revision on that branch. For example, RCS_getbranchpoint (rcs,
+ "1.3.2") will normally return "1.3.2.1". TARGET may be either a
+ branch number or a revision number; if a revnum, find the
+ branchpoint of the branch to which TARGET belongs.
+
+ Return RCS_head if TARGET is on the trunk or if the root node could
+ not be found (this is sort of backwards from our behavior on a branch;
+ the rationale is that the return value is a revision from which you
+ can start walking the next fields and end up at TARGET).
+ Return NULL on error. */
+
+static char *
+RCS_getbranchpoint (rcs, target)
+ RCSNode *rcs;
+ char *target;
+{
+ char *branch, *bp;
+ Node *vp;
+ RCSVers *rev;
+ int dots, isrevnum, brlen;
+
+ dots = numdots (target);
+ isrevnum = dots & 1;
+
+ if (dots == 1)
+ /* TARGET is a trunk revision; return rcs->head. */
+ return (RCS_head (rcs));
+
+ /* Get the revision number of the node at which TARGET's branch is
+ rooted. If TARGET is a branch number, lop off the last field;
+ if it's a revision number, lop off the last *two* fields. */
+ branch = xstrdup (target);
+ bp = strrchr (branch, '.');
+ if (bp == NULL)
+ error (1, 0, "%s: confused revision number %s",
+ rcs->path, target);
+ if (isrevnum)
+ while (*--bp != '.')
+ ;
+ *bp = '\0';
+
+ vp = findnode (rcs->versions, branch);
+ if (vp == NULL)
+ {
+ error (0, 0, "%s: can't find branch point %s", rcs->path, target);
+ return NULL;
+ }
+ rev = (RCSVers *) vp->data;
+
+ *bp++ = '.';
+ while (*bp && *bp != '.')
+ ++bp;
+ brlen = bp - branch;
+
+ vp = rev->branches->list->next;
+ while (vp != rev->branches->list)
+ {
+ /* BRANCH may be a genuine branch number, e.g. `1.1.3', or
+ maybe a full revision number, e.g. `1.1.3.6'. We have
+ found our branch point if the first BRANCHLEN characters
+ of the revision number match, *and* if the following
+ character is a dot. */
+ if (strncmp (vp->key, branch, brlen) == 0 && vp->key[brlen] == '.')
+ break;
+ vp = vp->next;
+ }
+
+ free (branch);
+ if (vp == rev->branches->list)
+ {
+ error (0, 0, "%s: can't find branch point %s", rcs->path, target);
+ return NULL;
+ }
+ else
+ return (xstrdup (vp->key));
+}
+
+/*
+ * Get the head of the RCS file. If branch is set, this is the head of the
+ * branch, otherwise the real head.
+ * Returns NULL or a newly malloc'd string.
+ */
+char *
+RCS_head (rcs)
+ RCSNode *rcs;
+{
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ /*
+ * NOTE: we call getbranch with force_tag_match set to avoid any
+ * possibility of recursion
+ */
+ if (rcs->branch)
+ return (RCS_getbranch (rcs, rcs->branch, 1));
+ else
+ return (xstrdup (rcs->head));
+}
+
+/*
+ * Get the most recent revision, based on the supplied date, but use some
+ * funky stuff and follow the vendor branch maybe
+ */
+char *
+RCS_getdate (rcs, date, force_tag_match)
+ RCSNode *rcs;
+ char *date;
+ int force_tag_match;
+{
+ char *cur_rev = NULL;
+ char *retval = NULL;
+ Node *p;
+ RCSVers *vers = NULL;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* if the head is on a branch, try the branch first */
+ if (rcs->branch != NULL)
+ retval = RCS_getdatebranch (rcs, date, rcs->branch);
+
+ /* if we found a match, we are done */
+ if (retval != NULL)
+ return (retval);
+
+ /* otherwise if we have a trunk, try it */
+ if (rcs->head)
+ {
+ p = findnode (rcs->versions, rcs->head);
+ while (p != NULL)
+ {
+ /* if the date of this one is before date, take it */
+ vers = (RCSVers *) p->data;
+ if (RCS_datecmp (vers->date, date) <= 0)
+ {
+ cur_rev = vers->version;
+ break;
+ }
+
+ /* if there is a next version, find the node */
+ if (vers->next != NULL)
+ p = findnode (rcs->versions, vers->next);
+ else
+ p = (Node *) NULL;
+ }
+ }
+
+ /*
+ * at this point, either we have the revision we want, or we have the
+ * first revision on the trunk (1.1?) in our hands
+ */
+
+ /* if we found what we're looking for, and it's not 1.1 return it */
+ if (cur_rev != NULL)
+ {
+ if (! STREQ (cur_rev, "1.1"))
+ return (xstrdup (cur_rev));
+
+ /* This is 1.1; if the date of 1.1 is not the same as that for the
+ 1.1.1.1 version, then return 1.1. This happens when the first
+ version of a file is created by a regular cvs add and commit,
+ and there is a subsequent cvs import of the same file. */
+ p = findnode (rcs->versions, "1.1.1.1");
+ if (p)
+ {
+ vers = (RCSVers *) p->data;
+ if (RCS_datecmp (vers->date, date) != 0)
+ return xstrdup ("1.1");
+ }
+ }
+
+ /* look on the vendor branch */
+ retval = RCS_getdatebranch (rcs, date, CVSBRANCH);
+
+ /*
+ * if we found a match, return it; otherwise, we return the first
+ * revision on the trunk or NULL depending on force_tag_match and the
+ * date of the first rev
+ */
+ if (retval != NULL)
+ return (retval);
+
+ if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0)
+ return (xstrdup (vers->version));
+ else
+ return (NULL);
+}
+
+/*
+ * Look up the last element on a branch that was put in before the specified
+ * date (return the rev or NULL)
+ */
+static char *
+RCS_getdatebranch (rcs, date, branch)
+ RCSNode *rcs;
+ char *date;
+ char *branch;
+{
+ char *cur_rev = NULL;
+ char *cp;
+ char *xbranch, *xrev;
+ Node *p;
+ RCSVers *vers;
+
+ /* look up the first revision on the branch */
+ xrev = xstrdup (branch);
+ cp = strrchr (xrev, '.');
+ if (cp == NULL)
+ {
+ free (xrev);
+ return (NULL);
+ }
+ *cp = '\0'; /* turn it into a revision */
+
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ p = findnode (rcs->versions, xrev);
+ free (xrev);
+ if (p == NULL)
+ return (NULL);
+ vers = (RCSVers *) p->data;
+
+ /* Tentatively use this revision, if it is early enough. */
+ if (RCS_datecmp (vers->date, date) <= 0)
+ cur_rev = vers->version;
+
+ /* If no branches list, return now. This is what happens if the branch
+ is a (magic) branch with no revisions yet. */
+ if (vers->branches == NULL)
+ return xstrdup (cur_rev);
+
+ /* walk the branches list looking for the branch number */
+ xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */
+ (void) strcpy (xbranch, branch);
+ (void) strcat (xbranch, ".");
+ for (p = vers->branches->list->next; p != vers->branches->list; p = p->next)
+ if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
+ break;
+ free (xbranch);
+ if (p == vers->branches->list)
+ {
+ /* This is what happens if the branch is a (magic) branch with
+ no revisions yet. Similar to the case where vers->branches ==
+ NULL, except here there was a another branch off the same
+ branchpoint. */
+ return xstrdup (cur_rev);
+ }
+
+ p = findnode (rcs->versions, p->key);
+
+ /* walk the next pointers until you find the end, or the date is too late */
+ while (p != NULL)
+ {
+ vers = (RCSVers *) p->data;
+ if (RCS_datecmp (vers->date, date) <= 0)
+ cur_rev = vers->version;
+ else
+ break;
+
+ /* if there is a next version, find the node */
+ if (vers->next != NULL)
+ p = findnode (rcs->versions, vers->next);
+ else
+ p = (Node *) NULL;
+ }
+
+ /* Return whatever we found, which may be NULL. */
+ return xstrdup (cur_rev);
+}
+
+/*
+ * Compare two dates in RCS format. Beware the change in format on January 1,
+ * 2000, when years go from 2-digit to full format.
+ */
+int
+RCS_datecmp (date1, date2)
+ char *date1, *date2;
+{
+ int length_diff = strlen (date1) - strlen (date2);
+
+ return (length_diff ? length_diff : strcmp (date1, date2));
+}
+
+/* Look up revision REV in RCS and return the date specified for the
+ revision minus FUDGE seconds (FUDGE will generally be one, so that the
+ logically previous revision will be found later, or zero, if we want
+ the exact date).
+
+ The return value is the date being returned as a time_t, or (time_t)-1
+ on error (previously was documented as zero on error; I haven't checked
+ the callers to make sure that they really check for (time_t)-1, but
+ the latter is what this function really returns). If DATE is non-NULL,
+ then it must point to MAXDATELEN characters, and we store the same
+ return value there in DATEFORM format. */
+time_t
+RCS_getrevtime (rcs, rev, date, fudge)
+ RCSNode *rcs;
+ char *rev;
+ char *date;
+ int fudge;
+{
+ char tdate[MAXDATELEN];
+ struct tm xtm, *ftm;
+ time_t revdate = 0;
+ Node *p;
+ RCSVers *vers;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* look up the revision */
+ p = findnode (rcs->versions, rev);
+ if (p == NULL)
+ return (-1);
+ vers = (RCSVers *) p->data;
+
+ /* split up the date */
+ ftm = &xtm;
+ (void) sscanf (vers->date, SDATEFORM, &ftm->tm_year, &ftm->tm_mon,
+ &ftm->tm_mday, &ftm->tm_hour, &ftm->tm_min,
+ &ftm->tm_sec);
+
+ /* If the year is from 1900 to 1999, RCS files contain only two
+ digits, and sscanf gives us a year from 0-99. If the year is
+ 2000+, RCS files contain all four digits and we subtract 1900,
+ because the tm_year field should contain years since 1900. */
+
+ if (ftm->tm_year > 1900)
+ ftm->tm_year -= 1900;
+
+ /* put the date in a form getdate can grok */
+ (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon,
+ ftm->tm_mday, ftm->tm_year + 1900, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+
+ /* turn it into seconds since the epoch */
+ revdate = get_date (tdate, (struct timeb *) NULL);
+ if (revdate != (time_t) -1)
+ {
+ revdate -= fudge; /* remove "fudge" seconds */
+ if (date)
+ {
+ /* put an appropriate string into ``date'' if we were given one */
+ ftm = gmtime (&revdate);
+ (void) sprintf (date, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+ }
+ }
+ return (revdate);
+}
+
+List *
+RCS_getlocks (rcs)
+ RCSNode *rcs;
+{
+ assert(rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rcs->locks_data) {
+ rcs->locks = getlist ();
+ do_locks (rcs->locks, rcs->locks_data);
+ free(rcs->locks_data);
+ rcs->locks_data = NULL;
+ }
+
+ return rcs->locks;
+}
+
+List *
+RCS_symbols(rcs)
+ RCSNode *rcs;
+{
+ assert(rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rcs->symbols_data) {
+ rcs->symbols = getlist ();
+ do_symbols (rcs->symbols, rcs->symbols_data);
+ free(rcs->symbols_data);
+ rcs->symbols_data = NULL;
+ }
+
+ return rcs->symbols;
+}
+
+/*
+ * Return the version associated with a particular symbolic tag.
+ * Returns NULL or a newly malloc'd string.
+ */
+static char *
+translate_symtag (rcs, tag)
+ RCSNode *rcs;
+ const char *tag;
+{
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rcs->symbols != NULL)
+ {
+ Node *p;
+
+ /* The symbols have already been converted into a list. */
+ p = findnode (rcs->symbols, tag);
+ if (p == NULL)
+ return NULL;
+
+ return xstrdup (p->data);
+ }
+
+ if (rcs->symbols_data != NULL)
+ {
+ size_t len;
+ char *cp;
+
+ /* Look through the RCS symbols information. This is like
+ do_symbols, but we don't add the information to a list. In
+ most cases, we will only be called once for this file, so
+ generating the list is unnecessary overhead. */
+
+ len = strlen (tag);
+ cp = rcs->symbols_data;
+ while ((cp = strchr (cp, tag[0])) != NULL)
+ {
+ if ((cp == rcs->symbols_data || whitespace (cp[-1]))
+ && strncmp (cp, tag, len) == 0
+ && cp[len] == ':')
+ {
+ char *v, *r;
+
+ /* We found the tag. Return the version number. */
+
+ cp += len + 1;
+ v = cp;
+ while (! whitespace (*cp) && *cp != '\0')
+ ++cp;
+ r = xmalloc (cp - v + 1);
+ strncpy (r, v, cp - v);
+ r[cp - v] = '\0';
+ return r;
+ }
+
+ while (! whitespace (*cp) && *cp != '\0')
+ ++cp;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * The argument ARG is the getopt remainder of the -k option specified on the
+ * command line. This function returns malloc'ed space that can be used
+ * directly in calls to RCS V5, with the -k flag munged correctly.
+ */
+char *
+RCS_check_kflag (arg)
+ const char *arg;
+{
+ static const char *const keyword_usage[] =
+ {
+ "%s %s: invalid RCS keyword expansion mode\n",
+ "Valid expansion modes include:\n",
+ " -kkv\tGenerate keywords using the default form.\n",
+ " -kkvl\tLike -kkv, except locker's name inserted.\n",
+ " -kk\tGenerate only keyword names in keyword strings.\n",
+ " -kv\tGenerate only keyword values in keyword strings.\n",
+ " -ko\tGenerate the old keyword string (no changes from checked in file).\n",
+ " -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL,
+ };
+ /* Big enough to hold any of the strings from kflags. */
+ char karg[10];
+ char const *const *cpp = NULL;
+
+ if (arg)
+ {
+ for (cpp = kflags; *cpp != NULL; cpp++)
+ {
+ if (STREQ (arg, *cpp))
+ break;
+ }
+ }
+
+ if (arg == NULL || *cpp == NULL)
+ {
+ usage (keyword_usage);
+ }
+
+ (void) sprintf (karg, "-k%s", *cpp);
+ return (xstrdup (karg));
+}
+
+/*
+ * Do some consistency checks on the symbolic tag... These should equate
+ * pretty close to what RCS checks, though I don't know for certain.
+ */
+void
+RCS_check_tag (tag)
+ const char *tag;
+{
+ char *invalid = "$,.:;@"; /* invalid RCS tag characters */
+ const char *cp;
+
+ /*
+ * The first character must be an alphabetic letter. The remaining
+ * characters cannot be non-visible graphic characters, and must not be
+ * in the set of "invalid" RCS identifier characters.
+ */
+ if (isalpha ((unsigned char) *tag))
+ {
+ for (cp = tag; *cp; cp++)
+ {
+ if (!isgraph ((unsigned char) *cp))
+ error (1, 0, "tag `%s' has non-visible graphic characters",
+ tag);
+ if (strchr (invalid, *cp))
+ error (1, 0, "tag `%s' must not contain the characters `%s'",
+ tag, invalid);
+ }
+ }
+ else
+ error (1, 0, "tag `%s' must start with a letter", tag);
+}
+
+/*
+ * TRUE if argument has valid syntax for an RCS revision or
+ * branch number. All characters must be digits or dots, first
+ * and last characters must be digits, and no two consecutive
+ * characters may be dots.
+ *
+ * Intended for classifying things, so this function doesn't
+ * call error.
+ */
+int
+RCS_valid_rev (rev)
+ char *rev;
+{
+ char last, c;
+ last = *rev++;
+ if (!isdigit ((unsigned char) last))
+ return 0;
+ while ((c = *rev++)) /* Extra parens placate -Wall gcc option */
+ {
+ if (c == '.')
+ {
+ if (last == '.')
+ return 0;
+ continue;
+ }
+ last = c;
+ if (!isdigit ((unsigned char) c))
+ return 0;
+ }
+ if (!isdigit ((unsigned char) last))
+ return 0;
+ return 1;
+}
+
+/*
+ * Return true if RCS revision with TAG is a dead revision.
+ */
+int
+RCS_isdead (rcs, tag)
+ RCSNode *rcs;
+ const char *tag;
+{
+ Node *p;
+ RCSVers *version;
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ p = findnode (rcs->versions, tag);
+ if (p == NULL)
+ return (0);
+
+ version = (RCSVers *) p->data;
+ return (version->dead);
+}
+
+/* Return the RCS keyword expansion mode. For example "b" for binary.
+ Returns a pointer into storage which is allocated and freed along with
+ the rest of the RCS information; the caller should not modify this
+ storage. Returns NULL if the RCS file does not specify a keyword
+ expansion mode; for all other errors, die with a fatal error. */
+char *
+RCS_getexpand (rcs)
+ RCSNode *rcs;
+{
+ /* Since RCS_parsercsfile_i now reads expand, don't need to worry
+ about RCS_reparsercsfile. */
+ assert (rcs != NULL);
+ return rcs->expand;
+}
+
+/* Set keyword expansion mode to EXPAND. For example "b" for binary. */
+void
+RCS_setexpand (rcs, expand)
+ RCSNode *rcs;
+ char *expand;
+{
+ /* Since RCS_parsercsfile_i now reads expand, don't need to worry
+ about RCS_reparsercsfile. */
+ assert (rcs != NULL);
+ if (rcs->expand != NULL)
+ free (rcs->expand);
+ rcs->expand = xstrdup (expand);
+}
+
+/* RCS keywords, and a matching enum. */
+struct rcs_keyword
+{
+ const char *string;
+ size_t len;
+};
+#define KEYWORD_INIT(s) (s), sizeof (s) - 1
+static const struct rcs_keyword keywords[] =
+{
+ { KEYWORD_INIT ("Author") },
+ { KEYWORD_INIT ("Date") },
+ { KEYWORD_INIT ("Header") },
+ { KEYWORD_INIT ("Id") },
+ { KEYWORD_INIT ("Locker") },
+ { KEYWORD_INIT ("Log") },
+ { KEYWORD_INIT ("Name") },
+ { KEYWORD_INIT ("RCSfile") },
+ { KEYWORD_INIT ("Revision") },
+ { KEYWORD_INIT ("Source") },
+ { KEYWORD_INIT ("State") },
+ { NULL, 0 }
+};
+enum keyword
+{
+ KEYWORD_AUTHOR = 0,
+ KEYWORD_DATE,
+ KEYWORD_HEADER,
+ KEYWORD_ID,
+ KEYWORD_LOCKER,
+ KEYWORD_LOG,
+ KEYWORD_NAME,
+ KEYWORD_RCSFILE,
+ KEYWORD_REVISION,
+ KEYWORD_SOURCE,
+ KEYWORD_STATE
+};
+
+/* Convert an RCS date string into a readable string. This is like
+ the RCS date2str function. */
+
+static char *
+printable_date (rcs_date)
+ const char *rcs_date;
+{
+ int year, mon, mday, hour, min, sec;
+ char buf[100];
+
+ (void) sscanf (rcs_date, SDATEFORM, &year, &mon, &mday, &hour, &min,
+ &sec);
+ if (year < 1900)
+ year += 1900;
+ sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
+ hour, min, sec);
+ return xstrdup (buf);
+}
+
+/* Escape the characters in a string so that it can be included in an
+ RCS value. */
+
+static char *
+escape_keyword_value (value, free_value)
+ const char *value;
+ int *free_value;
+{
+ char *ret, *t;
+ const char *s;
+
+ for (s = value; *s != '\0'; s++)
+ {
+ char c;
+
+ c = *s;
+ if (c == '\t'
+ || c == '\n'
+ || c == '\\'
+ || c == ' '
+ || c == '$')
+ {
+ break;
+ }
+ }
+
+ if (*s == '\0')
+ {
+ *free_value = 0;
+ return (char *) value;
+ }
+
+ ret = xmalloc (strlen (value) * 4 + 1);
+ *free_value = 1;
+
+ for (s = value, t = ret; *s != '\0'; s++, t++)
+ {
+ switch (*s)
+ {
+ default:
+ *t = *s;
+ break;
+ case '\t':
+ *t++ = '\\';
+ *t = 't';
+ break;
+ case '\n':
+ *t++ = '\\';
+ *t = 'n';
+ break;
+ case '\\':
+ *t++ = '\\';
+ *t = '\\';
+ break;
+ case ' ':
+ *t++ = '\\';
+ *t++ = '0';
+ *t++ = '4';
+ *t = '0';
+ break;
+ case '$':
+ *t++ = '\\';
+ *t++ = '0';
+ *t++ = '4';
+ *t = '4';
+ break;
+ }
+ }
+
+ *t = '\0';
+
+ return ret;
+}
+
+/* Expand RCS keywords in the memory buffer BUF of length LEN. This
+ applies to file RCS and version VERS. If NAME is not NULL, and is
+ not a numeric revision, then it is the symbolic tag used for the
+ checkout. EXPAND indicates how to expand the keywords. This
+ function sets *RETBUF and *RETLEN to the new buffer and length.
+ This function may modify the buffer BUF. If BUF != *RETBUF, then
+ RETBUF is a newly allocated buffer. */
+
+static void
+expand_keywords (rcs, ver, name, log, loglen, expand, buf, len, retbuf, retlen)
+ RCSNode *rcs;
+ RCSVers *ver;
+ const char *name;
+ const char *log;
+ size_t loglen;
+ enum kflag expand;
+ char *buf;
+ size_t len;
+ char **retbuf;
+ size_t *retlen;
+{
+ struct expand_buffer
+ {
+ struct expand_buffer *next;
+ char *data;
+ size_t len;
+ int free_data;
+ } *ebufs = NULL;
+ struct expand_buffer *ebuf_last = NULL;
+ size_t ebuf_len = 0;
+ char *locker;
+ char *srch, *srch_next;
+ size_t srch_len;
+
+ if (expand == KFLAG_O || expand == KFLAG_B)
+ {
+ *retbuf = buf;
+ *retlen = len;
+ return;
+ }
+
+ /* If we are using -kkvl, dig out the locker information if any. */
+ locker = NULL;
+ if (expand == KFLAG_KVL)
+ {
+ Node *lock;
+ lock = findnode (RCS_getlocks(rcs), ver->version);
+ if (lock != NULL)
+ locker = xstrdup (lock->data);
+ }
+
+ /* RCS keywords look like $STRING$ or $STRING: VALUE$. */
+ srch = buf;
+ srch_len = len;
+ while ((srch_next = memchr (srch, '$', srch_len)) != NULL)
+ {
+ char *s, *send;
+ size_t slen;
+ const struct rcs_keyword *keyword;
+ enum keyword kw;
+ char *value;
+ int free_value;
+ char *sub;
+ size_t sublen;
+
+ srch_len -= (srch_next + 1) - srch;
+ srch = srch_next + 1;
+
+ /* Look for the first non alphabetic character after the '$'. */
+ send = srch + srch_len;
+ for (s = srch; s < send; s++)
+ if (! isalpha ((unsigned char) *s))
+ break;
+
+ /* If the first non alphabetic character is not '$' or ':',
+ then this is not an RCS keyword. */
+ if (s == send || (*s != '$' && *s != ':'))
+ continue;
+
+ /* See if this is one of the keywords. */
+ slen = s - srch;
+ for (keyword = keywords; keyword->string != NULL; keyword++)
+ {
+ if (keyword->len == slen
+ && strncmp (keyword->string, srch, slen) == 0)
+ {
+ break;
+ }
+ }
+ if (keyword->string == NULL)
+ continue;
+
+ kw = (enum keyword) (keyword - keywords);
+
+ /* If the keyword ends with a ':', then the old value consists
+ of the characters up to the next '$'. If there is no '$'
+ before the end of the line, though, then this wasn't an RCS
+ keyword after all. */
+ if (*s == ':')
+ {
+ for (; s < send; s++)
+ if (*s == '$' || *s == '\n')
+ break;
+ if (s == send || *s != '$')
+ continue;
+ }
+
+ /* At this point we must replace the string from SRCH to S
+ with the expansion of the keyword KW. */
+
+ /* Get the value to use. */
+ free_value = 0;
+ if (expand == KFLAG_K)
+ value = NULL;
+ else
+ {
+ switch (kw)
+ {
+ default:
+ abort ();
+
+ case KEYWORD_AUTHOR:
+ value = ver->author;
+ break;
+
+ case KEYWORD_DATE:
+ value = printable_date (ver->date);
+ free_value = 1;
+ break;
+
+ case KEYWORD_HEADER:
+ case KEYWORD_ID:
+ {
+ char *path;
+ int free_path;
+ char *date;
+
+ if (kw == KEYWORD_HEADER)
+ path = rcs->path;
+ else
+ path = last_component (rcs->path);
+ path = escape_keyword_value (path, &free_path);
+ date = printable_date (ver->date);
+ value = xmalloc (strlen (path)
+ + strlen (ver->version)
+ + strlen (date)
+ + strlen (ver->author)
+ + strlen (ver->state)
+ + (locker == NULL ? 0 : strlen (locker))
+ + 20);
+
+ sprintf (value, "%s %s %s %s %s%s%s",
+ path, ver->version, date, ver->author,
+ ver->state,
+ locker != NULL ? " " : "",
+ locker != NULL ? locker : "");
+ if (free_path)
+ free (path);
+ free (date);
+ free_value = 1;
+ }
+ break;
+
+ case KEYWORD_LOCKER:
+ value = locker;
+ break;
+
+ case KEYWORD_LOG:
+ case KEYWORD_RCSFILE:
+ value = escape_keyword_value (last_component (rcs->path),
+ &free_value);
+ break;
+
+ case KEYWORD_NAME:
+ if (name != NULL && ! isdigit ((unsigned char) *name))
+ value = (char *) name;
+ else
+ value = NULL;
+ break;
+
+ case KEYWORD_REVISION:
+ value = ver->version;
+ break;
+
+ case KEYWORD_SOURCE:
+ value = escape_keyword_value (rcs->path, &free_value);
+ break;
+
+ case KEYWORD_STATE:
+ value = ver->state;
+ break;
+ }
+ }
+
+ sub = xmalloc (keyword->len
+ + (value == NULL ? 0 : strlen (value))
+ + 10);
+ if (expand == KFLAG_V)
+ {
+ /* Decrement SRCH and increment S to remove the $
+ characters. */
+ --srch;
+ ++srch_len;
+ ++s;
+ sublen = 0;
+ }
+ else
+ {
+ strcpy (sub, keyword->string);
+ sublen = strlen (keyword->string);
+ if (expand != KFLAG_K)
+ {
+ sub[sublen] = ':';
+ sub[sublen + 1] = ' ';
+ sublen += 2;
+ }
+ }
+ if (value != NULL)
+ {
+ strcpy (sub + sublen, value);
+ sublen += strlen (value);
+ }
+ if (expand != KFLAG_V && expand != KFLAG_K)
+ {
+ sub[sublen] = ' ';
+ ++sublen;
+ sub[sublen] = '\0';
+ }
+
+ if (free_value)
+ free (value);
+
+ /* The Log keyword requires special handling. This behaviour
+ is taken from RCS 5.7. The special log message is what RCS
+ uses for ci -k. */
+ if (kw == KEYWORD_LOG
+ && (sizeof "checked in with -k by " <= loglen
+ || strncmp (log, "checked in with -k by ",
+ sizeof "checked in with -k by " - 1) != 0))
+ {
+ char *start;
+ char *leader;
+ size_t leader_len, leader_sp_len;
+ const char *logend;
+ const char *snl;
+ int cnl;
+ char *date;
+ const char *sl;
+
+ /* We are going to insert the trailing $ ourselves, before
+ the log message, so we must remove it from S, if we
+ haven't done so already. */
+ if (expand != KFLAG_V)
+ ++s;
+
+ /* Find the start of the line. */
+ start = srch;
+ while (start > buf && start[-1] != '\n')
+ --start;
+
+ /* Copy the start of the line to use as a comment leader. */
+ leader_len = srch - start;
+ if (expand != KFLAG_V)
+ --leader_len;
+ leader = xmalloc (leader_len);
+ memcpy (leader, start, leader_len);
+ leader_sp_len = leader_len;
+ while (leader_sp_len > 0 && leader[leader_sp_len - 1] == ' ')
+ --leader_sp_len;
+
+ /* RCS does some checking for an old style of Log here,
+ but we don't bother. RCS issues a warning if it
+ changes anything. */
+
+ /* Count the number of newlines in the log message so that
+ we know how many copies of the leader we will need. */
+ cnl = 0;
+ logend = log + loglen;
+ for (snl = log; snl < logend; snl++)
+ if (*snl == '\n')
+ ++cnl;
+
+ date = printable_date (ver->date);
+ sub = xrealloc (sub,
+ (sublen
+ + sizeof "Revision"
+ + strlen (ver->version)
+ + strlen (date)
+ + strlen (ver->author)
+ + loglen
+ + (cnl + 2) * leader_len
+ + 20));
+ if (expand != KFLAG_V)
+ {
+ sub[sublen] = '$';
+ ++sublen;
+ }
+ sub[sublen] = '\n';
+ ++sublen;
+ memcpy (sub + sublen, leader, leader_len);
+ sublen += leader_len;
+ sprintf (sub + sublen, "Revision %s %s %s\n",
+ ver->version, date, ver->author);
+ sublen += strlen (sub + sublen);
+ free (date);
+
+ sl = log;
+ while (sl < logend)
+ {
+ if (*sl == '\n')
+ {
+ memcpy (sub + sublen, leader, leader_sp_len);
+ sublen += leader_sp_len;
+ sub[sublen] = '\n';
+ ++sublen;
+ ++sl;
+ }
+ else
+ {
+ const char *slnl;
+
+ memcpy (sub + sublen, leader, leader_len);
+ sublen += leader_len;
+ for (slnl = sl; slnl < logend && *slnl != '\n'; ++slnl)
+ ;
+ if (slnl < logend)
+ ++slnl;
+ memcpy (sub + sublen, sl, slnl - sl);
+ sublen += slnl - sl;
+ sl = slnl;
+ }
+ }
+
+ memcpy (sub + sublen, leader, leader_sp_len);
+ sublen += leader_sp_len;
+
+ free (leader);
+ }
+
+ /* Now SUB contains a string which is to replace the string
+ from SRCH to S. SUBLEN is the length of SUB. */
+
+ if (srch + sublen == s)
+ {
+ memcpy (srch, sub, sublen);
+ free (sub);
+ }
+ else
+ {
+ struct expand_buffer *ebuf;
+
+ /* We need to change the size of the buffer. We build a
+ list of expand_buffer structures. Each expand_buffer
+ structure represents a portion of the final output. We
+ concatenate them back into a single buffer when we are
+ done. This minimizes the number of potentially large
+ buffer copies we must do. */
+
+ if (ebufs == NULL)
+ {
+ ebufs = (struct expand_buffer *) xmalloc (sizeof *ebuf);
+ ebufs->next = NULL;
+ ebufs->data = buf;
+ ebufs->free_data = 0;
+ ebuf_len = srch - buf;
+ ebufs->len = ebuf_len;
+ ebuf_last = ebufs;
+ }
+ else
+ {
+ assert (srch >= ebuf_last->data);
+ assert (srch <= ebuf_last->data + ebuf_last->len);
+ ebuf_len -= ebuf_last->len - (srch - ebuf_last->data);
+ ebuf_last->len = srch - ebuf_last->data;
+ }
+
+ ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf);
+ ebuf->data = sub;
+ ebuf->len = sublen;
+ ebuf->free_data = 1;
+ ebuf->next = NULL;
+ ebuf_last->next = ebuf;
+ ebuf_last = ebuf;
+ ebuf_len += sublen;
+
+ ebuf = (struct expand_buffer *) xmalloc (sizeof *ebuf);
+ ebuf->data = s;
+ ebuf->len = srch_len - (s - srch);
+ ebuf->free_data = 0;
+ ebuf->next = NULL;
+ ebuf_last->next = ebuf;
+ ebuf_last = ebuf;
+ ebuf_len += srch_len - (s - srch);
+ }
+
+ srch_len -= (s - srch);
+ srch = s;
+ }
+
+ if (locker != NULL)
+ free (locker);
+
+ if (ebufs == NULL)
+ {
+ *retbuf = buf;
+ *retlen = len;
+ }
+ else
+ {
+ char *ret;
+
+ ret = xmalloc (ebuf_len);
+ *retbuf = ret;
+ *retlen = ebuf_len;
+ while (ebufs != NULL)
+ {
+ struct expand_buffer *next;
+
+ memcpy (ret, ebufs->data, ebufs->len);
+ ret += ebufs->len;
+ if (ebufs->free_data)
+ free (ebufs->data);
+ next = ebufs->next;
+ free (ebufs);
+ ebufs = next;
+ }
+ }
+}
+
+/* Check out a revision from an RCS file.
+
+ If PFN is not NULL, then ignore WORKFILE and SOUT. Call PFN zero
+ or more times with the contents of the file. CALLERDAT is passed,
+ uninterpreted, to PFN. (The current code will always call PFN
+ exactly once for a non empty file; however, the current code
+ assumes that it can hold the entire file contents in memory, which
+ is not a good assumption, and might change in the future).
+
+ Otherwise, if WORKFILE is not NULL, check out the revision to
+ WORKFILE. However, if WORKFILE is not NULL, and noexec is set,
+ then don't do anything.
+
+ Otherwise, if WORKFILE is NULL, check out the revision to SOUT. If
+ SOUT is RUN_TTY, then write the contents of the revision to
+ standard output. When using SOUT, the output is generally a
+ temporary file; don't bother to get the file modes correct.
+
+ REV is the numeric revision to check out. It may be NULL, which
+ means to check out the head of the default branch.
+
+ If NAMETAG is not NULL, and is not a numeric revision, then it is
+ the tag that should be used when expanding the RCS Name keyword.
+
+ OPTIONS is a string such as "-kb" or "-kv" for keyword expansion
+ options. It may be NULL to use the default expansion mode of the
+ file, typically "-kkv".
+
+ On an error which prevented checking out the file, either print a
+ nonfatal error and return 1, or give a fatal error. On success,
+ return 0. */
+
+/* This function mimics the behavior of `rcs co' almost exactly. The
+ chief difference is in its support for preserving file ownership,
+ permissions, and special files across checkin and checkout -- see
+ comments in RCS_checkin for some issues about this. -twp */
+
+int
+RCS_checkout (rcs, workfile, rev, nametag, options, sout, pfn, callerdat)
+ RCSNode *rcs;
+ char *workfile;
+ char *rev;
+ char *nametag;
+ char *options;
+ char *sout;
+ RCSCHECKOUTPROC pfn;
+ void *callerdat;
+{
+ int free_rev = 0;
+ enum kflag expand;
+ FILE *fp, *ofp;
+ struct stat sb;
+ struct rcsbuffer rcsbuf;
+ char *key;
+ char *value;
+ size_t len;
+ int free_value = 0;
+ char *log = NULL;
+ size_t loglen;
+ Node *vp = NULL;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ uid_t rcs_owner = (uid_t) -1;
+ gid_t rcs_group = (gid_t) -1;
+ mode_t rcs_mode;
+ int change_rcs_owner_or_group = 0;
+ int change_rcs_mode = 0;
+ int special_file = 0;
+ unsigned long devnum_long;
+ dev_t devnum = 0;
+#endif
+
+ if (trace)
+ {
+ (void) fprintf (stderr, "%s-> checkout (%s, %s, %s, %s)\n",
+#ifdef SERVER_SUPPORT
+ server_active ? "S" : " ",
+#else
+ "",
+#endif
+ rcs->path,
+ rev != NULL ? rev : "",
+ options != NULL ? options : "",
+ (pfn != NULL ? "(function)"
+ : (workfile != NULL
+ ? workfile
+ : (sout != RUN_TTY ? sout : "(stdout)"))));
+ }
+
+ assert (rev == NULL || isdigit ((unsigned char) *rev));
+
+ if (noexec && workfile != NULL)
+ return 0;
+
+ assert (sout == RUN_TTY || workfile == NULL);
+ assert (pfn == NULL || (sout == RUN_TTY && workfile == NULL));
+
+ /* Some callers, such as Checkin or remove_file, will pass us a
+ branch. */
+ if (rev != NULL && (numdots (rev) & 1) == 0)
+ {
+ rev = RCS_getbranch (rcs, rev, 1);
+ if (rev == NULL)
+ error (1, 0, "internal error: bad branch tag in checkout");
+ free_rev = 1;
+ }
+
+ if (rev == NULL || STREQ (rev, rcs->head))
+ {
+ int gothead;
+
+ /* We want the head revision. Try to read it directly. */
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
+ else
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf);
+
+ gothead = 0;
+ if (! rcsbuf_getrevnum (&rcsbuf, &key))
+ error (1, 0, "unexpected EOF reading %s", rcs->path);
+ while (rcsbuf_getkey (&rcsbuf, &key, &value))
+ {
+ if (STREQ (key, "log"))
+ log = rcsbuf_valcopy (&rcsbuf, value, 0, &loglen);
+ else if (STREQ (key, "text"))
+ {
+ gothead = 1;
+ break;
+ }
+ }
+
+ if (! gothead)
+ {
+ error (0, 0, "internal error: cannot find head text");
+ if (free_rev)
+ free (rev);
+ return 1;
+ }
+
+ rcsbuf_valpolish (&rcsbuf, value, 0, &len);
+
+ if (fstat (fileno (fp), &sb) < 0)
+ error (1, errno, "cannot fstat %s", rcs->path);
+
+ rcsbuf_cache (rcs, &rcsbuf);
+ }
+ else
+ {
+ struct rcsbuffer *rcsbufp;
+
+ /* It isn't the head revision of the trunk. We'll need to
+ walk through the deltas. */
+
+ fp = NULL;
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, &fp, &rcsbuf);
+
+ if (fp == NULL)
+ {
+ /* If RCS_deltas didn't close the file, we could use fstat
+ here too. Probably should change it thusly.... */
+ if (stat (rcs->path, &sb) < 0)
+ error (1, errno, "cannot stat %s", rcs->path);
+ rcsbufp = NULL;
+ }
+ else
+ {
+ if (fstat (fileno (fp), &sb) < 0)
+ error (1, errno, "cannot fstat %s", rcs->path);
+ rcsbufp = &rcsbuf;
+ }
+
+ RCS_deltas (rcs, fp, rcsbufp, rev, RCS_FETCH, &value, &len,
+ &log, &loglen);
+ free_value = 1;
+ }
+
+ /* If OPTIONS is NULL or the empty string, then the old code would
+ invoke the RCS co program with no -k option, which means that
+ co would use the string we have stored in rcs->expand. */
+ if ((options == NULL || options[0] == '\0') && rcs->expand == NULL)
+ expand = KFLAG_KV;
+ else
+ {
+ const char *ouroptions;
+ const char * const *cpp;
+
+ if (options != NULL && options[0] != '\0')
+ {
+ assert (options[0] == '-' && options[1] == 'k');
+ ouroptions = options + 2;
+ }
+ else
+ ouroptions = rcs->expand;
+
+ for (cpp = kflags; *cpp != NULL; cpp++)
+ if (STREQ (*cpp, ouroptions))
+ break;
+
+ if (*cpp != NULL)
+ expand = (enum kflag) (cpp - kflags);
+ else
+ {
+ error (0, 0,
+ "internal error: unsupported substitution string -k%s",
+ ouroptions);
+ expand = KFLAG_KV;
+ }
+ }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Handle special files and permissions, if that is desired. */
+ if (preserve_perms)
+ {
+ RCSVers *vers;
+ Node *info;
+
+ vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+ if (vp == NULL)
+ error (1, 0, "internal error: no revision information for %s",
+ rev == NULL ? rcs->head : rev);
+ vers = (RCSVers *) vp->data;
+
+ /* First we look for symlinks, which are simplest to handle. */
+ info = findnode (vers->other_delta, "symlink");
+ if (info != NULL)
+ {
+ char *dest;
+
+ if (pfn != NULL || (workfile == NULL && sout == RUN_TTY))
+ error (1, 0, "symbolic link %s:%s cannot be piped",
+ rcs->path, vers->version);
+ if (workfile == NULL)
+ dest = sout;
+ else
+ dest = workfile;
+
+ /* Remove `dest', just in case. It's okay to get ENOENT here,
+ since we just want the file not to be there. (TODO: decide
+ whether it should be considered an error for `dest' to exist
+ at this point. If so, the unlink call should be removed and
+ `symlink' should signal the error. -twp) */
+ if (unlink (dest) < 0 && !existence_error (errno))
+ error (1, errno, "cannot remove %s", dest);
+ if (symlink (info->data, dest) < 0)
+ error (1, errno, "cannot create symbolic link from %s to %s",
+ dest, info->data);
+ if (free_value)
+ free (value);
+ if (free_rev)
+ free (rev);
+ return 0;
+ }
+
+ /* Next, we look at this file's hardlinks field, and see whether
+ it is linked to any other file that has been checked out.
+ If so, we don't do anything else -- just link it to that file.
+
+ If we are checking out a file to a pipe or temporary storage,
+ none of this should matter. Hence the `workfile != NULL'
+ wrapper around the whole thing. -twp */
+
+ if (workfile != NULL)
+ {
+ List *links = vers->hardlinks;
+ if (links != NULL)
+ {
+ Node *uptodate_link;
+
+ /* For each file in the hardlinks field, check to see
+ if it exists, and if so, if it has been checked out
+ this iteration. When walklist returns, uptodate_link
+ should point to a hardlist node representing a file
+ in `links' which has recently been checked out, or
+ NULL if no file in `links' has yet been checked out. */
+
+ uptodate_link = NULL;
+ (void) walklist (links, find_checkedout_proc, &uptodate_link);
+ dellist (&links);
+
+ /* If we've found a file that `workfile' is supposed to be
+ linked to, and it has been checked out since CVS was
+ invoked, then simply link workfile to that file and return.
+
+ If one of these conditions is not met, then
+ workfile is the first one in its hardlink group to
+ be checked out, and we must continue with a full
+ checkout. */
+
+ if (uptodate_link != NULL)
+ {
+ struct hardlink_info *hlinfo =
+ (struct hardlink_info *) uptodate_link->data;
+
+ if (link (uptodate_link->key, workfile) < 0)
+ error (1, errno, "cannot link %s to %s",
+ workfile, uptodate_link->key);
+ hlinfo->checked_out = 1; /* probably unnecessary */
+ if (free_value)
+ free (value);
+ if (free_rev)
+ free (rev);
+ return 0;
+ }
+ }
+ }
+
+ info = findnode (vers->other_delta, "owner");
+ if (info != NULL)
+ {
+ change_rcs_owner_or_group = 1;
+ rcs_owner = (uid_t) strtoul (info->data, NULL, 10);
+ }
+ info = findnode (vers->other_delta, "group");
+ if (info != NULL)
+ {
+ change_rcs_owner_or_group = 1;
+ rcs_group = (gid_t) strtoul (info->data, NULL, 10);
+ }
+ info = findnode (vers->other_delta, "permissions");
+ if (info != NULL)
+ {
+ change_rcs_mode = 1;
+ rcs_mode = (mode_t) strtoul (info->data, NULL, 8);
+ }
+ info = findnode (vers->other_delta, "special");
+ if (info != NULL)
+ {
+ /* If the size of `devtype' changes, fix the sscanf call also */
+ char devtype[16];
+
+ if (sscanf (info->data, "%16s %lu",
+ devtype, &devnum_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ workfile, vers->version, info->data);
+ devnum = devnum_long;
+ if (STREQ (devtype, "character"))
+ special_file = S_IFCHR;
+ else if (STREQ (devtype, "block"))
+ special_file = S_IFBLK;
+ else
+ error (0, 0, "%s is a special file of unsupported type `%s'",
+ workfile, info->data);
+ }
+ }
+#endif
+
+ if (expand != KFLAG_O && expand != KFLAG_B)
+ {
+ char *newvalue;
+
+ /* Don't fetch the delta node again if we already have it. */
+ if (vp == NULL)
+ {
+ vp = findnode (rcs->versions, rev == NULL ? rcs->head : rev);
+ if (vp == NULL)
+ error (1, 0, "internal error: no revision information for %s",
+ rev == NULL ? rcs->head : rev);
+ }
+
+ expand_keywords (rcs, (RCSVers *) vp->data, nametag, log, loglen,
+ expand, value, len, &newvalue, &len);
+
+ if (newvalue != value)
+ {
+ if (free_value)
+ free (value);
+ value = newvalue;
+ free_value = 1;
+ }
+ }
+
+ if (free_rev)
+ free (rev);
+
+ if (log != NULL)
+ {
+ free (log);
+ log = NULL;
+ }
+
+ if (pfn != NULL)
+ {
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (special_file)
+ error (1, 0, "special file %s cannot be piped to anything",
+ rcs->path);
+#endif
+ /* The PFN interface is very simple to implement right now, as
+ we always have the entire file in memory. */
+ if (len != 0)
+ pfn (callerdat, value, len);
+ }
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ else if (special_file)
+ {
+ char *dest;
+
+ /* Can send either to WORKFILE or to SOUT, as long as SOUT is
+ not RUN_TTY. */
+ dest = workfile;
+ if (dest == NULL)
+ {
+ if (sout == RUN_TTY)
+ error (1, 0, "special file %s cannot be written to stdout",
+ rcs->path);
+ dest = sout;
+ }
+
+ /* Unlink `dest', just in case. It's okay if this provokes a
+ ENOENT error. */
+ if (unlink (dest) < 0 && existence_error (errno))
+ error (1, errno, "cannot remove %s", dest);
+ if (mknod (dest, special_file, devnum) < 0)
+ error (1, errno, "could not create special file %s",
+ dest);
+ }
+#endif
+ else
+ {
+ /* Not a special file: write to WORKFILE or SOUT. */
+ if (workfile == NULL)
+ {
+ if (sout == RUN_TTY)
+ ofp = stdout;
+ else
+ {
+ /* Symbolic links should be removed before replacement, so that
+ `fopen' doesn't follow the link and open the wrong file. */
+ if (islink (sout))
+ if (unlink_file (sout) < 0)
+ error (1, errno, "cannot remove %s", sout);
+ ofp = CVS_FOPEN (sout, expand == KFLAG_B ? "wb" : "w");
+ if (ofp == NULL)
+ error (1, errno, "cannot open %s", sout);
+ }
+ }
+ else
+ {
+ /* Output is supposed to go to WORKFILE, so we should open that
+ file. Symbolic links should be removed first (see above). */
+ if (islink (workfile))
+ if (unlink_file (workfile) < 0)
+ error (1, errno, "cannot remove %s", workfile);
+
+ ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
+
+ /* If the open failed because the existing workfile was not
+ writable, try to chmod the file and retry the open. */
+ if (ofp == NULL && errno == EACCES
+ && isfile (workfile) && !iswritable (workfile))
+ {
+ xchmod (workfile, 1);
+ ofp = CVS_FOPEN (workfile, expand == KFLAG_B ? "wb" : "w");
+ }
+
+ if (ofp == NULL)
+ {
+ error (0, errno, "cannot open %s", workfile);
+ if (free_value)
+ free (value);
+ return 1;
+ }
+ }
+
+ if (workfile == NULL && sout == RUN_TTY)
+ {
+ if (expand == KFLAG_B)
+ cvs_output_binary (value, len);
+ else
+ {
+ /* cvs_output requires the caller to check for zero
+ length. */
+ if (len > 0)
+ cvs_output (value, len);
+ }
+ }
+ else
+ {
+ /* NT 4.0 is said to have trouble writing 2099999 bytes
+ (for example) in a single fwrite. So break it down
+ (there is no need to be writing that much at once
+ anyway; it is possible that LARGEST_FWRITE should be
+ somewhat larger for good performance, but for testing I
+ want to start with a small value until/unless a bigger
+ one proves useful). */
+#define LARGEST_FWRITE 8192
+ size_t nleft = len;
+ size_t nstep = (len < LARGEST_FWRITE ? len : LARGEST_FWRITE);
+ char *p = value;
+
+ while (nleft > 0)
+ {
+ if (fwrite (p, 1, nstep, ofp) != nstep)
+ {
+ error (0, errno, "cannot write %s",
+ (workfile != NULL
+ ? workfile
+ : (sout != RUN_TTY ? sout : "stdout")));
+ if (free_value)
+ free (value);
+ return 1;
+ }
+ p += nstep;
+ nleft -= nstep;
+ if (nleft < nstep)
+ nstep = nleft;
+ }
+ }
+ }
+
+ if (free_value)
+ free (value);
+
+ if (workfile != NULL)
+ {
+ int ret;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (!special_file && fclose (ofp) < 0)
+ {
+ error (0, errno, "cannot close %s", workfile);
+ return 1;
+ }
+
+ if (change_rcs_owner_or_group)
+ {
+ if (chown (workfile, rcs_owner, rcs_group) < 0)
+ error (0, errno, "could not change owner or group of %s",
+ workfile);
+ }
+
+ ret = chmod (workfile,
+ change_rcs_mode
+ ? rcs_mode
+ : sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#else
+ if (fclose (ofp) < 0)
+ {
+ error (0, errno, "cannot close %s", workfile);
+ return 1;
+ }
+
+ ret = chmod (workfile,
+ sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH));
+#endif
+ if (ret < 0)
+ {
+ error (0, errno, "cannot change mode of file %s",
+ workfile);
+ }
+ }
+ else if (sout != RUN_TTY)
+ {
+ if (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ !special_file &&
+#endif
+ fclose (ofp) < 0)
+ {
+ error (0, errno, "cannot close %s", sout);
+ return 1;
+ }
+ }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If we are in the business of preserving hardlinks, then
+ mark this file as having been checked out. */
+ if (preserve_perms && workfile != NULL)
+ update_hardlink_info (workfile);
+#endif
+
+ return 0;
+}
+
+static RCSVers *RCS_findlock_or_tip PROTO ((RCSNode *rcs));
+
+/* Find the delta currently locked by the user. From the `ci' man page:
+
+ "If rev is omitted, 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 1.
+
+ If rev is omitted and the caller has no lock, but owns the
+ file and locking is not set to strict, then the revision
+ is appended to the default branch (normally the trunk; see
+ the -b option of rcs(1))."
+
+ RCS_findlock_or_tip finds the unique revision locked by the caller
+ and returns its delta node. If the caller has not locked any
+ revisions (and is permitted to commit to an unlocked delta, as
+ described above), return the tip of the default branch. */
+
+static RCSVers *
+RCS_findlock_or_tip (rcs)
+ RCSNode *rcs;
+{
+ char *user = getcaller();
+ Node *lock, *p;
+ List *locklist;
+
+ /* Find unique delta locked by caller. This code is very similar
+ to the code in RCS_unlock -- perhaps it could be abstracted
+ into a RCS_findlock function. */
+ locklist = RCS_getlocks (rcs);
+ lock = NULL;
+ for (p = locklist->list->next; p != locklist->list; p = p->next)
+ {
+ if (STREQ (p->data, user))
+ {
+ if (lock != NULL)
+ {
+ error (0, 0, "\
+%s: multiple revisions locked by %s; please specify one", rcs->path, user);
+ return NULL;
+ }
+ lock = p;
+ }
+ }
+
+ if (lock != NULL)
+ {
+ /* Found an old lock, but check that the revision still exists. */
+ p = findnode (rcs->versions, lock->key);
+ if (p == NULL)
+ {
+ error (0, 0, "%s: can't unlock nonexistent revision %s",
+ rcs->path,
+ lock->key);
+ return NULL;
+ }
+ return (RCSVers *) p->data;
+ }
+
+ /* No existing lock. The RCS rule is that this is an error unless
+ locking is nonstrict AND the file is owned by the current
+ user. Trying to determine the latter is a portability nightmare
+ in the face of NT, VMS, AFS, and other systems with non-unix-like
+ ideas of users and owners. In the case of CVS, we should never get
+ here (as long as the traditional behavior of making sure to call
+ RCS_lock persists). Anyway, we skip the RCS error checks
+ and just return the default branch or head. The reasoning is that
+ those error checks are to make users lock before a checkin, and we do
+ that in other ways if at all anyway (e.g. rcslock.pl). */
+
+ p = findnode (rcs->versions, RCS_getbranch (rcs, rcs->branch, 0));
+ return (RCSVers *) p->data;
+}
+
+/* Revision number string, R, must contain a `.'.
+ Return a newly-malloc'd copy of the prefix of R up
+ to but not including the final `.'. */
+
+static char *
+truncate_revnum (r)
+ const char *r;
+{
+ size_t len;
+ char *new_r;
+ char *dot = strrchr (r, '.');
+
+ assert (dot);
+ len = dot - r;
+ new_r = xmalloc (len + 1);
+ memcpy (new_r, r, len);
+ *(new_r + len) = '\0';
+ return new_r;
+}
+
+/* Revision number string, R, must contain a `.'.
+ R must be writable. Replace the rightmost `.' in R with
+ the NUL byte and return a pointer to that NUL byte. */
+
+static char *
+truncate_revnum_in_place (r)
+ char *r;
+{
+ char *dot = strrchr (r, '.');
+ assert (dot);
+ *dot = '\0';
+ return dot;
+}
+
+/* Revision number strings, R and S, must each contain a `.'.
+ R and S must be writable and must have the same number of dots.
+ Truncate R and S for the comparison, then restored them to their
+ original state.
+ Return the result (see compare_revnums) of comparing R and S
+ ignoring differences in any component after the rightmost `.'. */
+
+static int
+compare_truncated_revnums (r, s)
+ char *r;
+ char *s;
+{
+ char *r_dot = truncate_revnum_in_place (r);
+ char *s_dot = truncate_revnum_in_place (s);
+ int cmp;
+
+ assert (numdots (r) == numdots (s));
+
+ cmp = compare_revnums (r, s);
+
+ *r_dot = '.';
+ *s_dot = '.';
+
+ return cmp;
+}
+
+/* Return a malloc'd copy of the string representing the highest branch
+ number on BRANCHNODE. If there are no branches on BRANCHNODE, return NULL.
+ FIXME: isn't the max rev always the last one?
+ If so, we don't even need a loop. */
+
+static char *max_rev PROTO ((const RCSVers *));
+
+static char *
+max_rev (branchnode)
+ const RCSVers *branchnode;
+{
+ Node *head;
+ Node *bp;
+ char *max;
+
+ if (branchnode->branches == NULL)
+ {
+ return NULL;
+ }
+
+ max = NULL;
+ head = branchnode->branches->list;
+ for (bp = head->next; bp != head; bp = bp->next)
+ {
+ if (max == NULL || compare_truncated_revnums (max, bp->key) < 0)
+ {
+ max = bp->key;
+ }
+ }
+ assert (max);
+
+ return truncate_revnum (max);
+}
+
+/* Create BRANCH in RCS's delta tree. BRANCH may be either a branch
+ number or a revision number. In the former case, create the branch
+ with the specified number; in the latter case, create a new branch
+ rooted at node BRANCH with a higher branch number than any others.
+ Return the number of the tip node on the new branch. */
+
+static char *
+RCS_addbranch (rcs, branch)
+ RCSNode *rcs;
+ const char *branch;
+{
+ char *branchpoint, *newrevnum;
+ Node *nodep, *bp;
+ Node *marker;
+ RCSVers *branchnode;
+
+ /* Append to end by default. */
+ marker = NULL;
+
+ branchpoint = xstrdup (branch);
+ if ((numdots (branchpoint) & 1) == 0)
+ {
+ truncate_revnum_in_place (branchpoint);
+ }
+
+ /* Find the branch rooted at BRANCHPOINT. */
+ nodep = findnode (rcs->versions, branchpoint);
+ if (nodep == NULL)
+ {
+ error (0, 0, "%s: can't find branch point %s", rcs->path, branchpoint);
+ return NULL;
+ }
+ branchnode = (RCSVers *) nodep->data;
+
+ /* If BRANCH was a full branch number, make sure it is higher than MAX. */
+ if ((numdots (branch) & 1) == 1)
+ {
+ if (branchnode->branches == NULL)
+ {
+ /* We have to create the first branch on this node, which means
+ appending ".2" to the revision number. */
+ newrevnum = (char *) xmalloc (strlen (branch) + 3);
+ strcpy (newrevnum, branch);
+ strcat (newrevnum, ".2");
+ }
+ else
+ {
+ char *max = max_rev (branchnode);
+ assert (max);
+ newrevnum = increment_revnum (max);
+ free (max);
+ }
+ }
+ else
+ {
+ newrevnum = xstrdup (branch);
+
+ if (branchnode->branches != NULL)
+ {
+ Node *head;
+ Node *bp;
+
+ /* Find the position of this new branch in the sorted list
+ of branches. */
+ head = branchnode->branches->list;
+ for (bp = head->next; bp != head; bp = bp->next)
+ {
+ char *dot;
+ int found_pos;
+
+ /* The existing list must be sorted on increasing revnum. */
+ assert (bp->next == head
+ || compare_truncated_revnums (bp->key,
+ bp->next->key) < 0);
+ dot = truncate_revnum_in_place (bp->key);
+ found_pos = (compare_revnums (branch, bp->key) < 0);
+ *dot = '.';
+
+ if (found_pos)
+ {
+ break;
+ }
+ }
+ marker = bp;
+ }
+ }
+
+ newrevnum = (char *) xrealloc (newrevnum, strlen (newrevnum) + 3);
+ strcat (newrevnum, ".1");
+
+ /* Add this new revision number to BRANCHPOINT's branches list. */
+ if (branchnode->branches == NULL)
+ branchnode->branches = getlist();
+ bp = getnode();
+ bp->key = xstrdup (newrevnum);
+
+ /* Append to the end of the list by default, that is, just before
+ the header node, `list'. */
+ if (marker == NULL)
+ marker = branchnode->branches->list;
+
+ {
+ int fail;
+ fail = insert_before (branchnode->branches, marker, bp);
+ assert (!fail);
+ }
+
+ return newrevnum;
+}
+
+/* Check in to RCSFILE with revision REV (which must be greater than
+ the largest revision) and message MESSAGE (which is checked for
+ legality). If FLAGS & RCS_FLAGS_DEAD, check in a dead revision.
+ If FLAGS & RCS_FLAGS_QUIET, tell ci to be quiet. If FLAGS &
+ RCS_FLAGS_MODTIME, use the working file's modification time for the
+ checkin time. WORKFILE is the working file to check in from, or
+ NULL to use the usual RCS rules for deriving it from the RCSFILE.
+ If FLAGS & RCS_FLAGS_KEEPFILE, don't unlink the working file;
+ unlinking the working file is standard RCS behavior, but is rarely
+ appropriate for CVS.
+
+ This function should almost exactly mimic the behavior of `rcs ci'. The
+ principal point of difference is the support here for preserving file
+ ownership and permissions in the delta nodes. This is not a clean
+ solution -- precisely because it diverges from RCS's behavior -- but
+ it doesn't seem feasible to do this anywhere else in the code. [-twp]
+
+ Return value is -1 for error (and errno is set to indicate the
+ error), positive for error (and an error message has been printed),
+ or zero for success. */
+
+int
+RCS_checkin (rcs, workfile, message, rev, flags)
+ RCSNode *rcs;
+ char *workfile;
+ char *message;
+ char *rev;
+ int flags;
+{
+ RCSVers *delta, *commitpt;
+ Deltatext *dtext;
+ Node *nodep;
+ char *tmpfile, *changefile, *chtext;
+ char *diffopts;
+ size_t bufsize;
+ int buflen, chtextlen;
+ int status, checkin_quiet, allocated_workfile;
+ struct tm *ftm;
+ time_t modtime;
+ int adding_branch = 0;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ struct stat sb;
+#endif
+
+ commitpt = NULL;
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* Get basename of working file. Is there a library function to
+ do this? I couldn't find one. -twp */
+ allocated_workfile = 0;
+ if (workfile == NULL)
+ {
+ char *p;
+ int extlen = strlen (RCSEXT);
+ workfile = xstrdup (last_component (rcs->path));
+ p = workfile + (strlen (workfile) - extlen);
+ assert (strncmp (p, RCSEXT, extlen) == 0);
+ *p = '\0';
+ allocated_workfile = 1;
+ }
+
+ /* If the filename is a symbolic link, follow it and replace it
+ with the destination of the link. We need to do this before
+ calling rcs_internal_lockfile, or else we won't put the lock in
+ the right place. */
+ resolve_symlink (&(rcs->path));
+
+ checkin_quiet = flags & RCS_FLAGS_QUIET;
+ if (!checkin_quiet)
+ {
+ cvs_output (rcs->path, 0);
+ cvs_output (" <-- ", 7);
+ cvs_output (workfile, 0);
+ cvs_output ("\n", 1);
+ }
+
+ /* Create new delta node. */
+ delta = (RCSVers *) xmalloc (sizeof (RCSVers));
+ memset (delta, 0, sizeof (RCSVers));
+ delta->author = xstrdup (getcaller ());
+ if (flags & RCS_FLAGS_MODTIME)
+ {
+ struct stat ws;
+ if (stat (workfile, &ws) < 0)
+ {
+ error (1, errno, "cannot stat %s", workfile);
+ }
+ modtime = ws.st_mtime;
+ }
+ else
+ (void) time (&modtime);
+ ftm = gmtime (&modtime);
+ delta->date = (char *) xmalloc (MAXDATELEN);
+ (void) sprintf (delta->date, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+ if (flags & RCS_FLAGS_DEAD)
+ {
+ delta->state = xstrdup (RCSDEAD);
+ delta->dead = 1;
+ }
+ else
+ delta->state = xstrdup ("Exp");
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If permissions should be preserved on this project, then
+ save the permission info. */
+ if (preserve_perms)
+ {
+ Node *np;
+ char buf[64]; /* static buffer should be safe: see usage. -twp */
+
+ delta->other_delta = getlist();
+
+ if (CVS_LSTAT (workfile, &sb) < 0)
+ error (1, 1, "cannot lstat %s", workfile);
+
+ if (S_ISLNK (sb.st_mode))
+ {
+ np = getnode();
+ np->key = xstrdup ("symlink");
+ np->data = xreadlink (workfile);
+ addnode (delta->other_delta, np);
+ }
+ else
+ {
+ (void) sprintf (buf, "%u", sb.st_uid);
+ np = getnode();
+ np->key = xstrdup ("owner");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ (void) sprintf (buf, "%u", sb.st_gid);
+ np = getnode();
+ np->key = xstrdup ("group");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ (void) sprintf (buf, "%o", sb.st_mode & 07777);
+ np = getnode();
+ np->key = xstrdup ("permissions");
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+
+ /* Save device number. */
+ switch (sb.st_mode & S_IFMT)
+ {
+ case S_IFREG: break;
+ case S_IFCHR:
+ case S_IFBLK:
+ np = getnode();
+ np->key = xstrdup ("special");
+ sprintf (buf, "%s %lu",
+ ((sb.st_mode & S_IFMT) == S_IFCHR
+ ? "character" : "block"),
+ (unsigned long) sb.st_rdev);
+ np->data = xstrdup (buf);
+ addnode (delta->other_delta, np);
+ break;
+
+ default:
+ error (0, 0, "special file %s has unknown type", workfile);
+ }
+
+ /* Save hardlinks. */
+ delta->hardlinks = list_linked_files_on_disk (workfile);
+ }
+ }
+#endif
+
+ /* Create a new deltatext node. */
+ dtext = (Deltatext *) xmalloc (sizeof (Deltatext));
+ memset (dtext, 0, sizeof (Deltatext));
+
+ dtext->log = make_message_rcslegal (message);
+
+ /* If the delta tree is empty, then there's nothing to link the
+ new delta into. So make a new delta tree, snarf the working
+ file contents, and just write the new RCS file. */
+ if (rcs->head == NULL)
+ {
+ char *newrev;
+ FILE *fout;
+
+ /* Figure out what the first revision number should be. */
+ if (rev == NULL || *rev == '\0')
+ newrev = xstrdup ("1.1");
+ else if (numdots (rev) == 0)
+ {
+ newrev = (char *) xmalloc (strlen (rev) + 3);
+ strcpy (newrev, rev);
+ strcat (newrev, ".1");
+ }
+ else
+ newrev = xstrdup (rev);
+
+ /* Don't need to xstrdup NEWREV because it's already dynamic, and
+ not used for anything else. (Don't need to free it, either.) */
+ rcs->head = newrev;
+ delta->version = xstrdup (newrev);
+ nodep = getnode();
+ nodep->type = RCSVERS;
+ nodep->key = xstrdup (newrev);
+ nodep->data = (char *) delta;
+ (void) addnode (rcs->versions, nodep);
+
+ dtext->version = xstrdup (newrev);
+ bufsize = 0;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms && !S_ISREG (sb.st_mode))
+ /* Pretend file is empty. */
+ bufsize = 0;
+ else
+#endif
+ get_file (workfile, workfile,
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+ &dtext->text, &bufsize, &dtext->len);
+
+ if (!checkin_quiet)
+ {
+ cvs_output ("initial revision: ", 0);
+ cvs_output (rcs->head, 0);
+ cvs_output ("\n", 1);
+ }
+
+ /* We are probably about to invalidate any cached file. */
+ rcsbuf_cache_close ();
+
+ fout = rcs_internal_lockfile (rcs->path);
+ RCS_putadmin (rcs, fout);
+ RCS_putdtree (rcs, rcs->head, fout);
+ RCS_putdesc (rcs, fout);
+ rcs->delta_pos = ftell (fout);
+ if (rcs->delta_pos == -1)
+ error (1, errno, "cannot ftell for %s", rcs->path);
+ putdeltatext (fout, dtext);
+ rcs_internal_unlockfile (fout, rcs->path);
+ freedeltatext (dtext);
+
+ if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+ {
+ if (unlink_file (workfile) < 0)
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (0, errno, "cannot remove %s", workfile);
+ }
+
+ if (!checkin_quiet)
+ cvs_output ("done\n", 5);
+
+ return 0;
+ }
+
+ /* Derive a new revision number. From the `ci' man page:
+
+ "If rev is a revision number, it must be higher than the
+ latest one on the branch to which rev belongs, or must
+ start a new branch.
+
+ If 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 rev indicates a non-existing branch, that
+ branch is created with the initial revision numbered
+ rev.1."
+
+ RCS_findlock_or_tip handles the case where REV is omitted.
+ RCS 5.7 also permits REV to be "$" or to begin with a dot, but
+ we do not address those cases -- every routine that calls
+ RCS_checkin passes it a numeric revision. */
+
+ if (rev == NULL || *rev == '\0')
+ {
+ /* Figure out where the commit point is by looking for locks.
+ If the commit point is at the tip of a branch (or is the
+ head of the delta tree), then increment its revision number
+ to obtain the new revnum. Otherwise, start a new
+ branch. */
+ commitpt = RCS_findlock_or_tip (rcs);
+ if (commitpt == NULL)
+ {
+ status = 1;
+ goto checkin_done;
+ }
+ else if (commitpt->next == NULL
+ || STREQ (commitpt->version, rcs->head))
+ delta->version = increment_revnum (commitpt->version);
+ else
+ delta->version = RCS_addbranch (rcs, commitpt->version);
+ }
+ else
+ {
+ /* REV is either a revision number or a branch number. Find the
+ tip of the target branch. */
+ char *branch, *tip, *newrev, *p;
+ int dots, isrevnum;
+
+ assert (isdigit ((unsigned char) *rev));
+
+ newrev = xstrdup (rev);
+ dots = numdots (newrev);
+ isrevnum = dots & 1;
+
+ branch = xstrdup (rev);
+ if (isrevnum)
+ {
+ p = strrchr (branch, '.');
+ *p = '\0';
+ }
+
+ /* Find the tip of the target branch. If we got a one- or two-digit
+ revision number, this will be the head of the tree. Exception:
+ if rev is a single-field revision equal to the branch number of
+ the trunk (usually "1") then we want to treat it like an ordinary
+ branch revision. */
+ if (dots == 0)
+ {
+ tip = xstrdup (rcs->head);
+ if (atoi (tip) != atoi (branch))
+ {
+ newrev = (char *) xrealloc (newrev, strlen (newrev) + 3);
+ strcat (newrev, ".1");
+ dots = isrevnum = 1;
+ }
+ }
+ else if (dots == 1)
+ tip = xstrdup (rcs->head);
+ else
+ tip = RCS_getbranch (rcs, branch, 1);
+
+ /* If the branch does not exist, and we were supplied an exact
+ revision number, signal an error. Otherwise, if we were
+ given only a branch number, create it and set COMMITPT to
+ the branch point. */
+ if (tip == NULL)
+ {
+ if (isrevnum)
+ {
+ error (0, 0, "%s: can't find branch point %s",
+ rcs->path, branch);
+ free (branch);
+ free (newrev);
+ status = 1;
+ goto checkin_done;
+ }
+ delta->version = RCS_addbranch (rcs, branch);
+ if (!delta->version)
+ {
+ free (branch);
+ free (newrev);
+ status = 1;
+ goto checkin_done;
+ }
+ adding_branch = 1;
+ p = strrchr (branch, '.');
+ *p = '\0';
+ tip = xstrdup (branch);
+ }
+ else
+ {
+ if (isrevnum)
+ {
+ /* NEWREV must be higher than TIP. */
+ if (compare_revnums (tip, newrev) >= 0)
+ {
+ error (0, 0,
+ "%s: revision %s too low; must be higher than %s",
+ rcs->path,
+ newrev, tip);
+ free (branch);
+ free (newrev);
+ free (tip);
+ status = 1;
+ goto checkin_done;
+ }
+ delta->version = xstrdup (newrev);
+ }
+ else
+ /* Just increment the tip number to get the new revision. */
+ delta->version = increment_revnum (tip);
+ }
+
+ nodep = findnode (rcs->versions, tip);
+ commitpt = (RCSVers *) nodep->data;
+
+ free (branch);
+ free (newrev);
+ free (tip);
+ }
+
+ assert (delta->version != NULL);
+
+ /* If COMMITPT is locked by us, break the lock. If it's locked
+ by someone else, signal an error. */
+ nodep = findnode (RCS_getlocks (rcs), commitpt->version);
+ if (nodep != NULL)
+ {
+ if (! STREQ (nodep->data, delta->author))
+ {
+ /* If we are adding a branch, then leave the old lock around.
+ That is sensible in the sense that when adding a branch,
+ we don't need to use the lock to tell us where to check
+ in. It is fishy in the sense that if it is our own lock,
+ we break it. However, this is the RCS 5.7 behavior (at
+ the end of addbranch in ci.c in RCS 5.7, it calls
+ removelock only if it is our own lock, not someone
+ else's). */
+
+ if (!adding_branch)
+ {
+ error (0, 0, "%s: revision %s locked by %s",
+ rcs->path,
+ nodep->key, nodep->data);
+ status = 1;
+ goto checkin_done;
+ }
+ }
+ else
+ delnode (nodep);
+ }
+
+ dtext->version = xstrdup (delta->version);
+
+ /* Obtain the change text for the new delta. If DELTA is to be the
+ new head of the tree, then its change text should be the contents
+ of the working file, and LEAFNODE's change text should be a diff.
+ Else, DELTA's change text should be a diff between LEAFNODE and
+ the working file. */
+
+ tmpfile = cvs_temp_name();
+ status = RCS_checkout (rcs, NULL, commitpt->version, NULL,
+ ((rcs->expand != NULL
+ && STREQ (rcs->expand, "b"))
+ ? "-kb"
+ : "-ko"),
+ tmpfile,
+ (RCSCHECKOUTPROC)0, NULL);
+ if (status != 0)
+ error (1, 0,
+ "could not check out revision %s of `%s'",
+ commitpt->version, rcs->path);
+
+ bufsize = buflen = 0;
+ chtext = NULL;
+ chtextlen = 0;
+ changefile = cvs_temp_name();
+
+ /* Diff options should include --binary if the RCS file has -kb set
+ in its `expand' field. */
+ diffopts = (rcs->expand != NULL && STREQ (rcs->expand, "b")
+ ? "-a -n --binary"
+ : "-a -n");
+
+ if (STREQ (commitpt->version, rcs->head) &&
+ numdots (delta->version) == 1)
+ {
+ /* If this revision is being inserted on the trunk, the change text
+ for the new delta should be the contents of the working file ... */
+ bufsize = 0;
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms && !S_ISREG (sb.st_mode))
+ /* Pretend file is empty. */
+ ;
+ else
+#endif
+ get_file (workfile, workfile,
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+ &dtext->text, &bufsize, &dtext->len);
+
+ /* ... and the change text for the old delta should be a diff. */
+ commitpt->text = (Deltatext *) xmalloc (sizeof (Deltatext));
+ memset (commitpt->text, 0, sizeof (Deltatext));
+
+ bufsize = 0;
+ switch (diff_exec (workfile, tmpfile, diffopts, changefile))
+ {
+ case 0:
+ case 1:
+ break;
+ case -1:
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (1, errno, "error diffing %s", workfile);
+ break;
+ default:
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (1, 0, "error diffing %s", workfile);
+ break;
+ }
+
+ /* OK, the text file case here is really dumb. Logically
+ speaking we want diff to read the files in text mode,
+ convert them to the canonical form found in RCS files
+ (which, we hope at least, is independent of OS--always
+ bare linefeeds), and then work with change texts in that
+ format. However, diff_exec both generates change
+ texts and produces output for user purposes (e.g. patch.c),
+ and there is no way to distinguish between the two cases.
+ So we actually implement the text file case by writing the
+ change text as a text file, then reading it as a text file.
+ This should cause no harm, but doesn't strike me as
+ immensely clean. */
+ get_file (changefile, changefile,
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+ &commitpt->text->text, &bufsize, &commitpt->text->len);
+
+ /* If COMMITPT->TEXT->TEXT is NULL, it means that CHANGEFILE
+ was empty and that there are no differences between revisions.
+ In that event, we want to force RCS_rewrite to write an empty
+ string for COMMITPT's change text. Leaving the change text
+ field set NULL won't work, since that means "preserve the original
+ change text for this delta." */
+ if (commitpt->text->text == NULL)
+ {
+ commitpt->text->text = xstrdup ("");
+ commitpt->text->len = 0;
+ }
+ }
+ else
+ {
+ /* This file is not being inserted at the head, but on a side
+ branch somewhere. Make a diff from the previous revision
+ to the working file. */
+ switch (diff_exec (tmpfile, workfile, diffopts, changefile))
+ {
+ case 0:
+ case 1:
+ break;
+ case -1:
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (1, errno, "error diffing %s", workfile);
+ break;
+ default:
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (1, 0, "error diffing %s", workfile);
+ break;
+ }
+ /* See the comment above, at the other get_file invocation,
+ regarding binary vs. text. */
+ get_file (changefile, changefile,
+ rcs->expand != NULL && STREQ (rcs->expand, "b") ? "rb" : "r",
+ &dtext->text, &bufsize,
+ &dtext->len);
+ if (dtext->text == NULL)
+ {
+ dtext->text = xstrdup ("");
+ dtext->len = 0;
+ }
+ }
+
+ /* Update DELTA linkage. It is important not to do this before
+ the very end of RCS_checkin; if an error arises that forces
+ us to abort checking in, we must not have malformed deltas
+ partially linked into the tree.
+
+ If DELTA and COMMITPT are on different branches, do nothing --
+ DELTA is linked to the tree through COMMITPT->BRANCHES, and we
+ don't want to change `next' pointers.
+
+ Otherwise, if the nodes are both on the trunk, link DELTA to
+ COMMITPT; otherwise, link COMMITPT to DELTA. */
+
+ if (numdots (commitpt->version) == numdots (delta->version))
+ {
+ if (STREQ (commitpt->version, rcs->head))
+ {
+ delta->next = rcs->head;
+ rcs->head = xstrdup (delta->version);
+ }
+ else
+ commitpt->next = xstrdup (delta->version);
+ }
+
+ /* Add DELTA to RCS->VERSIONS. */
+ if (rcs->versions == NULL)
+ rcs->versions = getlist();
+ nodep = getnode();
+ nodep->type = RCSVERS;
+ nodep->key = xstrdup (delta->version);
+ nodep->data = (char *) delta;
+ (void) addnode (rcs->versions, nodep);
+
+ /* Write the new RCS file, inserting the new delta at COMMITPT. */
+ if (!checkin_quiet)
+ {
+ cvs_output ("new revision: ", 14);
+ cvs_output (delta->version, 0);
+ cvs_output ("; previous revision: ", 21);
+ cvs_output (commitpt->version, 0);
+ cvs_output ("\n", 1);
+ }
+
+ RCS_rewrite (rcs, dtext, commitpt->version);
+
+ if ((flags & RCS_FLAGS_KEEPFILE) == 0)
+ {
+ if (unlink_file (workfile) < 0)
+ /* FIXME-update-dir: message does not include update_dir. */
+ error (1, errno, "cannot remove %s", workfile);
+ }
+ if (unlink_file (tmpfile) < 0)
+ error (0, errno, "cannot remove %s", tmpfile);
+ if (unlink_file (changefile) < 0)
+ error (0, errno, "cannot remove %s", changefile);
+
+ if (!checkin_quiet)
+ cvs_output ("done\n", 5);
+
+ checkin_done:
+ if (allocated_workfile)
+ free (workfile);
+
+ if (commitpt != NULL && commitpt->text != NULL)
+ {
+ freedeltatext (commitpt->text);
+ commitpt->text = NULL;
+ }
+
+ freedeltatext (dtext);
+ if (status != 0)
+ free_rcsvers_contents (delta);
+
+ return status;
+}
+
+/* This structure is passed between RCS_cmp_file and cmp_file_buffer. */
+
+struct cmp_file_data
+{
+ const char *filename;
+ FILE *fp;
+ int different;
+};
+
+/* Compare the contents of revision REV of RCS file RCS with the
+ contents of the file FILENAME. OPTIONS is a string for the keyword
+ expansion options. Return 0 if the contents of the revision are
+ the same as the contents of the file, 1 if they are different. */
+
+int
+RCS_cmp_file (rcs, rev, options, filename)
+ RCSNode *rcs;
+ char *rev;
+ char *options;
+ const char *filename;
+{
+ int binary;
+ FILE *fp;
+ struct cmp_file_data data;
+ int retcode;
+
+ if (options != NULL && options[0] != '\0')
+ binary = STREQ (options, "-kb");
+ else
+ {
+ char *expand;
+
+ expand = RCS_getexpand (rcs);
+ if (expand != NULL && STREQ (expand, "b"))
+ binary = 1;
+ else
+ binary = 0;
+ }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If CVS is to deal properly with special files (when
+ PreservePermissions is on), the best way is to check out the
+ revision to a temporary file and call `xcmp' on the two disk
+ files. xcmp needs to handle non-regular files properly anyway,
+ so calling it simplifies RCS_cmp_file. We *could* just yank
+ the delta node out of the version tree and look for device
+ numbers, but writing to disk and calling xcmp is a better
+ abstraction (therefore probably more robust). -twp */
+
+ if (preserve_perms)
+ {
+ char *tmp;
+
+ tmp = cvs_temp_name();
+ retcode = RCS_checkout(rcs, NULL, rev, NULL, options, tmp, NULL, NULL);
+ if (retcode != 0)
+ return 1;
+
+ retcode = xcmp (tmp, filename);
+ if (CVS_UNLINK (tmp) < 0)
+ error (0, errno, "cannot remove %s", tmp);
+ return retcode;
+ }
+ else
+#endif
+ {
+ fp = CVS_FOPEN (filename, binary ? FOPEN_BINARY_READ : "r");
+ if (fp == NULL)
+ /* FIXME-update-dir: should include update_dir in message. */
+ error (1, errno, "cannot open file %s for comparing", filename);
+
+ data.filename = filename;
+ data.fp = fp;
+ data.different = 0;
+
+ retcode = RCS_checkout (rcs, (char *) NULL, rev, (char *) NULL,
+ options, RUN_TTY, cmp_file_buffer,
+ (void *) &data);
+
+ /* If we have not yet found a difference, make sure that we are at
+ the end of the file. */
+ if (! data.different)
+ {
+ if (getc (fp) != EOF)
+ data.different = 1;
+ }
+
+ fclose (fp);
+
+ if (retcode != 0)
+ return 1;
+
+ return data.different;
+ }
+}
+
+/* This is a subroutine of RCS_cmp_file. It is passed to
+ RCS_checkout. */
+
+#define CMP_BUF_SIZE (8 * 1024)
+
+static void
+cmp_file_buffer (callerdat, buffer, len)
+ void *callerdat;
+ const char *buffer;
+ size_t len;
+{
+ struct cmp_file_data *data = (struct cmp_file_data *) callerdat;
+ char *filebuf;
+
+ /* If we've already found a difference, we don't need to check
+ further. */
+ if (data->different)
+ return;
+
+ filebuf = xmalloc (len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len);
+
+ while (len > 0)
+ {
+ size_t checklen;
+
+ checklen = len > CMP_BUF_SIZE ? CMP_BUF_SIZE : len;
+ if (fread (filebuf, 1, checklen, data->fp) != checklen)
+ {
+ if (ferror (data->fp))
+ error (1, errno, "cannot read file %s for comparing",
+ data->filename);
+ data->different = 1;
+ free (filebuf);
+ return;
+ }
+
+ if (memcmp (filebuf, buffer, checklen) != 0)
+ {
+ data->different = 1;
+ free (filebuf);
+ return;
+ }
+
+ buffer += checklen;
+ len -= checklen;
+ }
+
+ free (filebuf);
+}
+
+/* For RCS file RCS, make symbolic tag TAG point to revision REV.
+ This validates that TAG is OK for a user to use. Return value is
+ -1 for error (and errno is set to indicate the error), positive for
+ error (and an error message has been printed), or zero for success. */
+
+int
+RCS_settag (rcs, tag, rev)
+ RCSNode *rcs;
+ const char *tag;
+ const char *rev;
+{
+ List *symbols;
+ Node *node;
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* FIXME: This check should be moved to RCS_check_tag. There is no
+ reason for it to be here. */
+ if (STREQ (tag, TAG_BASE)
+ || STREQ (tag, TAG_HEAD))
+ {
+ /* Print the name of the tag might be considered redundant
+ with the caller, which also prints it. Perhaps this helps
+ clarify why the tag name is considered reserved, I don't
+ know. */
+ error (0, 0, "Attempt to add reserved tag name %s", tag);
+ return 1;
+ }
+
+ /* A revision number of NULL means use the head or default branch.
+ If rev is not NULL, it may be a symbolic tag or branch number;
+ expand it to the correct numeric revision or branch head. */
+ if (rev == NULL)
+ rev = rcs->branch ? rcs->branch : rcs->head;
+
+ /* At this point rcs->symbol_data may not have been parsed.
+ Calling RCS_symbols will force it to be parsed into a list
+ which we can easily manipulate. */
+ symbols = RCS_symbols (rcs);
+ if (symbols == NULL)
+ {
+ symbols = getlist ();
+ rcs->symbols = symbols;
+ }
+ node = findnode (symbols, tag);
+ if (node != NULL)
+ {
+ free (node->data);
+ node->data = xstrdup (rev);
+ }
+ else
+ {
+ node = getnode ();
+ node->key = xstrdup (tag);
+ node->data = xstrdup (rev);
+ (void) addnode_at_front (symbols, node);
+ }
+
+ return 0;
+}
+
+/* Delete the symbolic tag TAG from the RCS file RCS. Return 0 if
+ the tag was found (and removed), or 1 if it was not present. (In
+ either case, the tag will no longer be in RCS->SYMBOLS.) */
+
+int
+RCS_deltag (rcs, tag)
+ RCSNode *rcs;
+ const char *tag;
+{
+ List *symbols;
+ Node *node;
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ symbols = RCS_symbols (rcs);
+ if (symbols == NULL)
+ return 1;
+
+ node = findnode (symbols, tag);
+ if (node == NULL)
+ return 1;
+
+ delnode (node);
+
+ return 0;
+}
+
+/* Set the default branch of RCS to REV. */
+
+int
+RCS_setbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rev && ! *rev)
+ rev = NULL;
+
+ if (rev == NULL && rcs->branch == NULL)
+ return 0;
+ if (rev != NULL && rcs->branch != NULL && STREQ (rev, rcs->branch))
+ return 0;
+
+ if (rcs->branch != NULL)
+ free (rcs->branch);
+ rcs->branch = xstrdup (rev);
+
+ return 0;
+}
+
+/* Lock revision REV. LOCK_QUIET is 1 to suppress output. FIXME:
+ Most of the callers only call us because RCS_checkin still tends to
+ like a lock (a relic of old behavior inherited from the RCS ci
+ program). If we clean this up, only "cvs admin -l" will still need
+ to call RCS_lock. */
+
+/* FIXME-twp: if a lock owned by someone else is broken, should this
+ send mail to the lock owner? Prompt user? It seems like such an
+ obscure situation for CVS as almost not worth worrying much
+ about. */
+
+int
+RCS_lock (rcs, rev, lock_quiet)
+ RCSNode *rcs;
+ const char *rev;
+ int lock_quiet;
+{
+ List *locks;
+ Node *p;
+ char *user;
+ char *xrev = NULL;
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ locks = RCS_getlocks (rcs);
+ if (locks == NULL)
+ locks = rcs->locks = getlist();
+ user = getcaller();
+
+ /* A revision number of NULL means lock the head or default branch. */
+ if (rev == NULL)
+ xrev = RCS_head (rcs);
+
+ /* If rev is a branch number, lock the latest revision on that
+ branch. I think that if the branch doesn't exist, it's
+ okay to return 0 -- that just means that the branch is new,
+ so we don't need to lock it anyway. -twp */
+ else if (RCS_nodeisbranch (rcs, rev))
+ {
+ xrev = RCS_getbranch (rcs, (char *) rev, 1);
+ if (xrev == NULL)
+ {
+ if (!lock_quiet)
+ error (0, 0, "%s: branch %s absent", rcs->path, rev);
+ return 1;
+ }
+ }
+
+ if (xrev == NULL)
+ xrev = xstrdup (rev);
+
+ /* Make sure that the desired revision exists. Technically,
+ we can update the locks list without even checking this,
+ but RCS 5.7 did this. And it can't hurt. */
+ if (findnode (rcs->versions, xrev) == NULL)
+ {
+ if (!lock_quiet)
+ error (0, 0, "%s: revision %s absent", rcs->path, xrev);
+ free (xrev);
+ return 1;
+ }
+
+ /* Is this rev already locked? */
+ p = findnode (locks, xrev);
+ if (p != NULL)
+ {
+ if (STREQ (p->data, user))
+ {
+ /* We already own the lock on this revision, so do nothing. */
+ free (xrev);
+ return 0;
+ }
+
+#if 0
+ /* Well, first of all, "rev" below should be "xrev" to avoid
+ core dumps. But more importantly, should we really be
+ breaking the lock unconditionally? What CVS 1.9 does (via
+ RCS) is to prompt "Revision 1.1 is already locked by fred.
+ Do you want to break the lock? [ny](n): ". Well, we don't
+ want to interact with the user (certainly not at the
+ server/protocol level, and probably not in the command-line
+ client), but isn't it more sensible to give an error and
+ let the user run "cvs admin -u" if they want to break the
+ lock? */
+
+ /* Break the lock. */
+ if (!lock_quiet)
+ {
+ cvs_output (rev, 0);
+ cvs_output (" unlocked\n", 0);
+ }
+ delnode (p);
+#else
+ error (1, 0, "Revision %s is already locked by %s", xrev, p->data);
+#endif
+ }
+
+ /* Create a new lock. */
+ p = getnode();
+ p->key = xrev; /* already xstrdupped */
+ p->data = xstrdup (getcaller());
+ (void) addnode_at_front (locks, p);
+
+ if (!lock_quiet)
+ {
+ cvs_output (xrev, 0);
+ cvs_output (" locked\n", 0);
+ }
+
+ return 0;
+}
+
+/* Unlock revision REV. UNLOCK_QUIET is 1 to suppress output. FIXME:
+ Like RCS_lock, this can become a no-op if we do the checkin
+ ourselves.
+
+ If REV is not null and is locked by someone else, break their
+ lock and notify them. It is an open issue whether RCS_unlock
+ queries the user about whether or not to break the lock. */
+
+int
+RCS_unlock (rcs, rev, unlock_quiet)
+ RCSNode *rcs;
+ const char *rev;
+ int unlock_quiet;
+{
+ Node *lock;
+ List *locks;
+ char *user;
+ char *xrev = NULL;
+
+ user = getcaller();
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ /* If rev is NULL, unlock the latest revision (first in
+ rcs->locks) held by the caller. */
+ if (rev == NULL)
+ {
+ Node *p;
+
+ /* No-ops: attempts to unlock an empty tree or an unlocked file. */
+ if (rcs->head == NULL)
+ {
+ if (!unlock_quiet)
+ cvs_outerr ("can't unlock an empty tree\n", 0);
+ return 0;
+ }
+
+ locks = RCS_getlocks (rcs);
+ if (locks == NULL)
+ {
+ if (!unlock_quiet)
+ cvs_outerr ("No locks are set.\n", 0);
+ return 0;
+ }
+
+ lock = NULL;
+ for (p = locks->list->next; p != locks->list; p = p->next)
+ {
+ if (lock != NULL)
+ {
+ if (!unlock_quiet)
+ error (0, 0, "\
+%s: multiple revisions locked by %s; please specify one", rcs->path, user);
+ return 1;
+ }
+ lock = p;
+ }
+ if (lock == NULL)
+ return 0; /* no lock found, ergo nothing to do */
+ xrev = xstrdup (lock->key);
+ }
+ else if (RCS_nodeisbranch (rcs, rev))
+ {
+ /* If rev is a branch number, unlock the latest revision on that
+ branch. */
+ xrev = RCS_getbranch (rcs, (char *) rev, 1);
+ if (xrev == NULL)
+ {
+ error (0, 0, "%s: branch %s absent", rcs->path, rev);
+ return 1;
+ }
+ }
+ else
+ /* REV is an exact revision number. */
+ xrev = xstrdup (rev);
+
+ lock = findnode (RCS_getlocks (rcs), xrev);
+ if (lock == NULL)
+ {
+ /* This revision isn't locked. */
+ free (xrev);
+ return 0;
+ }
+
+ if (! STREQ (lock->data, user))
+ {
+ /* If the revision is locked by someone else, notify
+ them. Note that this shouldn't ever happen if RCS_unlock
+ is called with a NULL revision, since that means "whatever
+ revision is currently locked by the caller." */
+ char *repos, *workfile;
+ repos = xstrdup (rcs->path);
+ workfile = strrchr (repos, '/');
+ *workfile++ = '\0';
+ notify_do ('C', workfile, user, NULL, NULL, repos);
+ free (repos);
+ }
+
+ delnode (lock);
+ if (!unlock_quiet)
+ {
+ cvs_output (xrev, 0);
+ cvs_output (" unlocked\n", 0);
+ }
+
+ free (xrev);
+ return 0;
+}
+
+/* Add USER to the access list of RCS. Do nothing if already present.
+ FIXME-twp: check syntax of USER to make sure it's a valid id. */
+
+void
+RCS_addaccess (rcs, user)
+ RCSNode *rcs;
+ char *user;
+{
+ char *access, *a;
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rcs->access == NULL)
+ rcs->access = xstrdup (user);
+ else
+ {
+ access = xstrdup (rcs->access);
+ for (a = strtok (access, " "); a != NULL; a = strtok (NULL, " "))
+ {
+ if (STREQ (a, user))
+ {
+ free (access);
+ return;
+ }
+ }
+ rcs->access = (char *) xrealloc
+ (rcs->access, strlen (rcs->access) + strlen (user) + 2);
+ strcat (rcs->access, " ");
+ strcat (rcs->access, user);
+ }
+}
+
+/* Remove USER from the access list of RCS. */
+
+void
+RCS_delaccess (rcs, user)
+ RCSNode *rcs;
+ char *user;
+{
+ char *p, *s;
+ int ulen;
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (rcs->access == NULL)
+ return;
+
+ if (user == NULL)
+ {
+ free (rcs->access);
+ rcs->access = NULL;
+ return;
+ }
+
+ p = rcs->access;
+ ulen = strlen (user);
+ while (p != NULL)
+ {
+ if (strncmp (p, user, ulen) == 0 && (p[ulen] == '\0' || p[ulen] == ' '))
+ break;
+ p = strchr (p, ' ');
+ if (p != NULL)
+ ++p;
+ }
+
+ if (p == NULL)
+ return;
+
+ s = p + ulen;
+ while (*s != '\0')
+ *p++ = *s++;
+ *p = '\0';
+}
+
+char *
+RCS_getaccess (rcs)
+ RCSNode *rcs;
+{
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ return rcs->access;
+}
+
+static int findtag PROTO ((Node *, void *));
+
+/* Return a nonzero value if the revision specified by ARG is found. */
+
+static int
+findtag (node, arg)
+ Node *node;
+ void *arg;
+{
+ char *rev = (char *)arg;
+
+ if (STREQ (node->data, rev))
+ return 1;
+ else
+ return 0;
+}
+
+/* Delete revisions between REV1 and REV2. The changes between the two
+ revisions must be collapsed, and the result stored in the revision
+ immediately preceding the lower one. Return 0 for successful completion,
+ 1 otherwise.
+
+ Solution: check out the revision preceding REV1 and the revision
+ following REV2. Use call_diff to find aggregate diffs between
+ these two revisions, and replace the delta text for the latter one
+ with the new aggregate diff. Alternatively, we could write a
+ function that takes two change texts and combines them to produce a
+ new change text, without checking out any revs or calling diff. It
+ would be hairy, but so, so cool.
+
+ If INCLUSIVE is set, then TAG1 and TAG2, if non-NULL, tell us to
+ delete that revision as well (cvs admin -o tag1:tag2). If clear,
+ delete up to but not including that revision (cvs admin -o tag1::tag2).
+ This does not affect TAG1 or TAG2 being NULL; the meaning of the start
+ point in ::tag2 and :tag2 is the same and likewise for end points. */
+
+int
+RCS_delete_revs (rcs, tag1, tag2, inclusive)
+ RCSNode *rcs;
+ char *tag1;
+ char *tag2;
+ int inclusive;
+{
+ char *next;
+ Node *nodep;
+ RCSVers *revp = NULL;
+ RCSVers *beforep;
+ int status, found;
+ int save_noexec;
+
+ char *branchpoint = NULL;
+ char *rev1 = NULL;
+ char *rev2 = NULL;
+ int rev1_inclusive = inclusive;
+ int rev2_inclusive = inclusive;
+ char *before = NULL;
+ char *after = NULL;
+ char *beforefile = NULL;
+ char *afterfile = NULL;
+ char *outfile = NULL;
+
+ if (tag1 == NULL && tag2 == NULL)
+ return 0;
+
+ /* Assume error status until everything is finished. */
+ status = 1;
+
+ /* Make sure both revisions exist. */
+ if (tag1 != NULL)
+ {
+ rev1 = RCS_gettag (rcs, tag1, 1, NULL);
+ if (rev1 == NULL || (nodep = findnode (rcs->versions, rev1)) == NULL)
+ {
+ error (0, 0, "%s: Revision %s doesn't exist.", rcs->path, tag1);
+ goto delrev_done;
+ }
+ }
+ if (tag2 != NULL)
+ {
+ rev2 = RCS_gettag (rcs, tag2, 1, NULL);
+ if (rev2 == NULL || (nodep = findnode (rcs->versions, rev2)) == NULL)
+ {
+ error (0, 0, "%s: Revision %s doesn't exist.", rcs->path, tag2);
+ goto delrev_done;
+ }
+ }
+
+ /* If rev1 is on the trunk and rev2 is NULL, rev2 should be
+ RCS->HEAD. (*Not* RCS_head(rcs), which may return rcs->branch
+ instead.) We need to check this special case early, in order
+ to make sure that rev1 and rev2 get ordered correctly. */
+ if (rev2 == NULL && numdots (rev1) == 1)
+ {
+ rev2 = xstrdup (rcs->head);
+ rev2_inclusive = 1;
+ }
+
+ if (rev2 == NULL)
+ rev2_inclusive = 1;
+
+ if (rev1 != NULL && rev2 != NULL)
+ {
+ /* A range consisting of a branch number means the latest revision
+ on that branch. */
+ if (RCS_isbranch (rcs, rev1) && STREQ (rev1, rev2))
+ rev1 = rev2 = RCS_getbranch (rcs, rev1, 0);
+ else
+ {
+ /* Make sure REV1 and REV2 are ordered correctly (in the
+ same order as the next field). For revisions on the
+ trunk, REV1 should be higher than REV2; for branches,
+ REV1 should be lower. */
+ /* Shouldn't we just be giving an error in the case where
+ the user specifies the revisions in the wrong order
+ (that is, always swap on the trunk, never swap on a
+ branch, in the non-error cases)? It is not at all
+ clear to me that users who specify -o 1.4:1.2 really
+ meant to type -o 1.2:1.4, and the out of order usage
+ has never been documented, either by cvs.texinfo or
+ rcs(1). */
+ char *temp;
+ int temp_inclusive;
+ if (numdots (rev1) == 1)
+ {
+ if (compare_revnums (rev1, rev2) <= 0)
+ {
+ temp = rev2;
+ rev2 = rev1;
+ rev1 = temp;
+
+ temp_inclusive = rev2_inclusive;
+ rev2_inclusive = rev1_inclusive;
+ rev1_inclusive = temp_inclusive;
+ }
+ }
+ else if (compare_revnums (rev1, rev2) > 0)
+ {
+ temp = rev2;
+ rev2 = rev1;
+ rev1 = temp;
+
+ temp_inclusive = rev2_inclusive;
+ rev2_inclusive = rev1_inclusive;
+ rev1_inclusive = temp_inclusive;
+ }
+ }
+ }
+
+ /* Basically the same thing; make sure that the ordering is what we
+ need. */
+ if (rev1 == NULL)
+ {
+ assert (rev2 != NULL);
+ if (numdots (rev2) == 1)
+ {
+ /* Swap rev1 and rev2. */
+ int temp_inclusive;
+
+ rev1 = rev2;
+ rev2 = NULL;
+
+ temp_inclusive = rev2_inclusive;
+ rev2_inclusive = rev1_inclusive;
+ rev1_inclusive = temp_inclusive;
+ }
+ }
+
+ /* Put the revision number preceding the first one to delete into
+ BEFORE (where "preceding" means according to the next field).
+ If the first revision to delete is the first revision on its
+ branch (e.g. 1.3.2.1), BEFORE should be the node on the trunk
+ at which the branch is rooted. If the first revision to delete
+ is the head revision of the trunk, set BEFORE to NULL.
+
+ Note that because BEFORE may not be on the same branch as REV1,
+ it is not very handy for navigating the revision tree. It's
+ most useful just for checking out the revision preceding REV1. */
+ before = NULL;
+ branchpoint = RCS_getbranchpoint (rcs, rev1 != NULL ? rev1 : rev2);
+ if (rev1 == NULL)
+ {
+ rev1 = xstrdup (branchpoint);
+ if (numdots (branchpoint) > 1)
+ {
+ char *bp;
+ bp = strrchr (branchpoint, '.');
+ while (*--bp != '.')
+ ;
+ *bp = '\0';
+ /* Note that this is exclusive, always, because the inclusive
+ flag doesn't affect the meaning when rev1 == NULL. */
+ before = xstrdup (branchpoint);
+ *bp = '.';
+ }
+ }
+ else if (! STREQ (rev1, branchpoint))
+ {
+ /* Walk deltas from BRANCHPOINT on, looking for REV1. */
+ nodep = findnode (rcs->versions, branchpoint);
+ revp = (RCSVers *) nodep->data;
+ while (revp->next != NULL && ! STREQ (revp->next, rev1))
+ {
+ revp = (RCSVers *) nodep->data;
+ nodep = findnode (rcs->versions, revp->next);
+ }
+ if (revp->next == NULL)
+ {
+ error (0, 0, "%s: Revision %s doesn't exist.", rcs->path, rev1);
+ goto delrev_done;
+ }
+ if (rev1_inclusive)
+ before = xstrdup (revp->version);
+ else
+ {
+ before = rev1;
+ nodep = findnode (rcs->versions, before);
+ rev1 = xstrdup (((RCSVers *)nodep->data)->next);
+ }
+ }
+ else if (!rev1_inclusive)
+ {
+ before = rev1;
+ nodep = findnode (rcs->versions, before);
+ rev1 = xstrdup (((RCSVers *)nodep->data)->next);
+ }
+ else if (numdots (branchpoint) > 1)
+ {
+ /* Example: rev1 is "1.3.2.1", branchpoint is "1.3.2.1".
+ Set before to "1.3". */
+ char *bp;
+ bp = strrchr (branchpoint, '.');
+ while (*--bp != '.')
+ ;
+ *bp = '\0';
+ before = xstrdup (branchpoint);
+ *bp = '.';
+ }
+
+ /* If any revision between REV1 and REV2 is locked or is a branch point,
+ we can't delete that revision and must abort. */
+ after = NULL;
+ next = rev1;
+ found = 0;
+ while (!found && next != NULL)
+ {
+ nodep = findnode (rcs->versions, next);
+ revp = (RCSVers *) nodep->data;
+
+ if (rev2 != NULL)
+ found = STREQ (revp->version, rev2);
+ next = revp->next;
+
+ if ((!found && next != NULL) || rev2_inclusive || rev2 == NULL)
+ {
+ if (findnode (RCS_getlocks (rcs), revp->version))
+ {
+ error (0, 0, "%s: can't remove locked revision %s",
+ rcs->path,
+ revp->version);
+ goto delrev_done;
+ }
+ if (revp->branches != NULL)
+ {
+ error (0, 0, "%s: can't remove branch point %s",
+ rcs->path,
+ revp->version);
+ goto delrev_done;
+ }
+
+ /* Doing this only for the :: syntax is for compatibility.
+ See cvs.texinfo for somewhat more discussion. */
+ if (!inclusive
+ && walklist (RCS_symbols (rcs), findtag, revp->version))
+ {
+ /* We don't print which file this happens to on the theory
+ that the caller will print the name of the file in a
+ more useful fashion (fullname not rcs->path). */
+ error (0, 0, "cannot remove revision %s because it has tags",
+ revp->version);
+ goto delrev_done;
+ }
+
+ /* It's misleading to print the `deleting revision' output
+ here, since we may not actually delete these revisions.
+ But that's how RCS does it. Bleah. Someday this should be
+ moved to the point where the revs are actually marked for
+ deletion. -twp */
+ cvs_output ("deleting revision ", 0);
+ cvs_output (revp->version, 0);
+ cvs_output ("\n", 1);
+ }
+ }
+
+ if (rev2 == NULL)
+ ;
+ else if (found)
+ {
+ if (rev2_inclusive)
+ after = xstrdup (next);
+ else
+ after = xstrdup (revp->version);
+ }
+ else if (!inclusive)
+ {
+ /* In the case of an empty range, for example 1.2::1.2 or
+ 1.2::1.3, we want to just do nothing. */
+ status = 0;
+ goto delrev_done;
+ }
+ else
+ {
+ /* This looks fishy in the cases where tag1 == NULL or tag2 == NULL.
+ Are those cases really impossible? */
+ assert (tag1 != NULL);
+ assert (tag2 != NULL);
+
+ error (0, 0, "%s: invalid revision range %s:%s", rcs->path,
+ tag1, tag2);
+ goto delrev_done;
+ }
+
+ if (after == NULL && before == NULL)
+ {
+ /* The user is trying to delete all revisions. While an
+ RCS file without revisions makes sense to RCS (e.g. the
+ state after "rcs -i"), CVS has never been able to cope with
+ it. So at least for now we just make this an error.
+
+ We don't include rcs->path in the message since "cvs admin"
+ already printed "RCS file:" and the name. */
+ error (1, 0, "attempt to delete all revisions");
+ }
+
+ /* The conditionals at this point get really hairy. Here is the
+ general idea:
+
+ IF before != NULL and after == NULL
+ THEN don't check out any revisions, just delete them
+ IF before == NULL and after != NULL
+ THEN only check out after's revision, and use it for the new deltatext
+ ELSE
+ check out both revisions and diff -n them. This could use
+ RCS_exec_rcsdiff with some changes, like being able
+ to suppress diagnostic messages and to direct output. */
+
+ if (after != NULL)
+ {
+ char *diffbuf;
+ size_t bufsize, len;
+
+#if defined (__CYGWIN32__) || defined (_WIN32)
+ /* FIXME: This is an awful kludge, but at least until I have
+ time to work on it a little more and test it, I'd rather
+ give a fatal error than corrupt the file. I think that we
+ need to use "-kb" and "--binary" and "rb" to get_file
+ (probably can do it always, not just for binary files, if
+ we are consistent between the RCS_checkout and the diff). */
+ {
+ char *expand = RCS_getexpand (rcs);
+ if (expand != NULL && STREQ (expand, "b"))
+ error (1, 0,
+ "admin -o not implemented yet for binary on this system");
+ }
+#endif
+
+ afterfile = cvs_temp_name();
+ status = RCS_checkout (rcs, NULL, after, NULL, "-ko", afterfile,
+ (RCSCHECKOUTPROC)0, NULL);
+ if (status > 0)
+ goto delrev_done;
+
+ if (before == NULL)
+ {
+ /* We are deleting revisions from the head of the tree,
+ so must create a new head. */
+ diffbuf = NULL;
+ bufsize = 0;
+ get_file (afterfile, afterfile, "r", &diffbuf, &bufsize, &len);
+
+ save_noexec = noexec;
+ noexec = 0;
+ if (unlink_file (afterfile) < 0)
+ error (0, errno, "cannot remove %s", afterfile);
+ noexec = save_noexec;
+
+ free (afterfile);
+ afterfile = NULL;
+
+ free (rcs->head);
+ rcs->head = xstrdup (after);
+ }
+ else
+ {
+ beforefile = cvs_temp_name();
+ status = RCS_checkout (rcs, NULL, before, NULL, "-ko", beforefile,
+ (RCSCHECKOUTPROC)0, NULL);
+ if (status > 0)
+ goto delrev_done;
+
+ outfile = cvs_temp_name();
+ status = diff_exec (beforefile, afterfile, "-an", outfile);
+
+ if (status == 2)
+ {
+ /* Not sure we need this message; will diff_exec already
+ have printed an error? */
+ error (0, 0, "%s: could not diff", rcs->path);
+ status = 1;
+ goto delrev_done;
+ }
+
+ diffbuf = NULL;
+ bufsize = 0;
+ get_file (outfile, outfile, "r", &diffbuf, &bufsize, &len);
+ }
+
+ /* Save the new change text in after's delta node. */
+ nodep = findnode (rcs->versions, after);
+ revp = (RCSVers *) nodep->data;
+
+ assert (revp->text == NULL);
+
+ revp->text = (Deltatext *) xmalloc (sizeof (Deltatext));
+ memset ((Deltatext *) revp->text, 0, sizeof (Deltatext));
+ revp->text->version = xstrdup (revp->version);
+ revp->text->text = diffbuf;
+ revp->text->len = len;
+
+ /* If DIFFBUF is NULL, it means that OUTFILE is empty and that
+ there are no differences between the two revisions. In that
+ case, we want to force RCS_copydeltas to write an empty string
+ for the new change text (leaving the text field set NULL
+ means "preserve the original change text for this delta," so
+ we don't want that). */
+ if (revp->text->text == NULL)
+ revp->text->text = xstrdup ("");
+ }
+
+ /* Walk through the revisions (again) to mark each one as
+ outdated. (FIXME: would it be safe to use the `dead' field for
+ this? Doubtful.) */
+ for (next = rev1;
+ next != NULL && (after == NULL || ! STREQ (next, after));
+ next = revp->next)
+ {
+ nodep = findnode (rcs->versions, next);
+ revp = (RCSVers *) nodep->data;
+ revp->outdated = 1;
+ }
+
+ /* Update delta links. If BEFORE == NULL, we're changing the
+ head of the tree and don't need to update any `next' links. */
+ if (before != NULL)
+ {
+ /* If REV1 is the first node on its branch, then BEFORE is its
+ root node (on the trunk) and we have to update its branches
+ list. Otherwise, BEFORE is on the same branch as AFTER, and
+ we can just change BEFORE's `next' field to point to AFTER.
+ (This should be safe: since findnode manages its lists via
+ the `hashnext' and `hashprev' fields, rather than `next' and
+ `prev', mucking with `next' and `prev' should not corrupt the
+ delta tree's internal structure. Much. -twp) */
+
+ if (rev1 == NULL)
+ /* beforep's ->next field already should be equal to after,
+ which I think is always NULL in this case. */
+ ;
+ else if (STREQ (rev1, branchpoint))
+ {
+ nodep = findnode (rcs->versions, before);
+ revp = (RCSVers *) nodep->data;
+ nodep = revp->branches->list->next;
+ while (nodep != revp->branches->list &&
+ ! STREQ (nodep->key, rev1))
+ nodep = nodep->next;
+ assert (nodep != revp->branches->list);
+ if (after == NULL)
+ delnode (nodep);
+ else
+ {
+ free (nodep->key);
+ nodep->key = xstrdup (after);
+ }
+ }
+ else
+ {
+ nodep = findnode (rcs->versions, before);
+ beforep = (RCSVers *) nodep->data;
+ free (beforep->next);
+ beforep->next = xstrdup (after);
+ }
+ }
+
+ status = 0;
+
+ delrev_done:
+ if (rev1 != NULL)
+ free (rev1);
+ if (rev2 != NULL)
+ free (rev2);
+ if (branchpoint != NULL)
+ free (branchpoint);
+ if (before != NULL)
+ free (before);
+ if (after != NULL)
+ free (after);
+
+ save_noexec = noexec;
+ noexec = 0;
+ if (beforefile != NULL)
+ {
+ if (unlink_file (beforefile) < 0)
+ error (0, errno, "cannot remove %s", beforefile);
+ free (beforefile);
+ }
+ if (afterfile != NULL)
+ {
+ if (unlink_file (afterfile) < 0)
+ error (0, errno, "cannot remove %s", afterfile);
+ free (afterfile);
+ }
+ if (outfile != NULL)
+ {
+ if (unlink_file (outfile) < 0)
+ error (0, errno, "cannot remove %s", outfile);
+ free (outfile);
+ }
+ noexec = save_noexec;
+
+ return status;
+}
+
+/*
+ * TRUE if there exists a symbolic tag "tag" in file.
+ */
+int
+RCS_exist_tag (rcs, tag)
+ RCSNode *rcs;
+ char *tag;
+{
+
+ assert (rcs != NULL);
+
+ if (findnode (RCS_symbols (rcs), tag))
+ return 1;
+ return 0;
+
+}
+
+/*
+ * TRUE if RCS revision number "rev" exists.
+ * This includes magic branch revisions, not found in rcs->versions,
+ * but only in rcs->symbols, requiring a list walk to find them.
+ * Take advantage of list walk callback function already used by
+ * RCS_delete_revs, above.
+ */
+int
+RCS_exist_rev (rcs, rev)
+ RCSNode *rcs;
+ char *rev;
+{
+
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL);
+
+ if (findnode(rcs->versions, rev) != 0)
+ return 1;
+
+ if (walklist (RCS_symbols(rcs), findtag, rev) != 0)
+ return 1;
+
+ return 0;
+
+}
+
+
+/* RCS_deltas and friends. Processing of the deltas in RCS files. */
+
+struct line
+{
+ /* Text of this line. Part of the same malloc'd block as the struct
+ line itself (we probably should use the "struct hack" (char text[1])
+ and save ourselves sizeof (char *) bytes). Does not include \n;
+ instead has_newline indicates the presence or absence of \n. */
+ char *text;
+ /* Length of this line, not counting \n if has_newline is true. */
+ size_t len;
+ /* Version in which it was introduced. */
+ RCSVers *vers;
+ /* Nonzero if this line ends with \n. This will always be true
+ except possibly for the last line. */
+ int has_newline;
+ /* Number of pointers to this struct line. */
+ int refcount;
+};
+
+struct linevector
+{
+ /* How many lines in use for this linevector? */
+ unsigned int nlines;
+ /* How many lines allocated for this linevector? */
+ unsigned int lines_alloced;
+ /* Pointer to array containing a pointer to each line. */
+ struct line **vector;
+};
+
+static void linevector_init PROTO ((struct linevector *));
+
+/* Initialize *VEC to be a linevector with no lines. */
+static void
+linevector_init (vec)
+ struct linevector *vec;
+{
+ vec->lines_alloced = 0;
+ vec->nlines = 0;
+ vec->vector = NULL;
+}
+
+static int linevector_add PROTO ((struct linevector *vec, const char *text,
+ size_t len, RCSVers *vers,
+ unsigned int pos));
+
+/* Given some text TEXT, add each of its lines to VEC before line POS
+ (where line 0 is the first line). The last line in TEXT may or may
+ not be \n terminated.
+ Set the version for each of the new lines to VERS. This
+ function returns non-zero for success. It returns zero if the line
+ number is out of range.
+
+ Each of the lines in TEXT are copied to space which is managed with
+ the linevector (and freed by linevector_free). So the caller doesn't
+ need to keep TEXT around after the call to this function. */
+static int
+linevector_add (vec, text, len, vers, pos)
+ struct linevector *vec;
+ const char *text;
+ size_t len;
+ RCSVers *vers;
+ unsigned int pos;
+{
+ const char *textend;
+ unsigned int i;
+ unsigned int nnew;
+ const char *p;
+ const char *nextline_text;
+ size_t nextline_len;
+ int nextline_newline;
+ struct line *q;
+
+ if (len == 0)
+ return 1;
+
+ textend = text + len;
+
+ /* Count the number of lines we will need to add. */
+ nnew = 1;
+ for (p = text; p < textend; ++p)
+ if (*p == '\n' && p + 1 < textend)
+ ++nnew;
+
+ /* Expand VEC->VECTOR if needed. */
+ if (vec->nlines + nnew >= vec->lines_alloced)
+ {
+ if (vec->lines_alloced == 0)
+ vec->lines_alloced = 10;
+ while (vec->nlines + nnew >= vec->lines_alloced)
+ vec->lines_alloced *= 2;
+ vec->vector = xrealloc (vec->vector,
+ vec->lines_alloced * sizeof (*vec->vector));
+ }
+
+ /* Make room for the new lines in VEC->VECTOR. */
+ for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i)
+ vec->vector[i] = vec->vector[i - nnew];
+
+ if (pos > vec->nlines)
+ return 0;
+
+ /* Actually add the lines, to VEC->VECTOR. */
+ i = pos;
+ nextline_text = text;
+ nextline_newline = 0;
+ for (p = text; p < textend; ++p)
+ if (*p == '\n')
+ {
+ nextline_newline = 1;
+ if (p + 1 == textend)
+ /* If there are no characters beyond the last newline, we
+ don't consider it another line. */
+ break;
+ nextline_len = p - nextline_text;
+ q = (struct line *) xmalloc (sizeof (struct line) + nextline_len);
+ q->vers = vers;
+ q->text = (char *)q + sizeof (struct line);
+ q->len = nextline_len;
+ q->has_newline = nextline_newline;
+ q->refcount = 1;
+ memcpy (q->text, nextline_text, nextline_len);
+ vec->vector[i++] = q;
+
+ nextline_text = (char *)p + 1;
+ nextline_newline = 0;
+ }
+ nextline_len = p - nextline_text;
+ q = (struct line *) xmalloc (sizeof (struct line) + nextline_len);
+ q->vers = vers;
+ q->text = (char *)q + sizeof (struct line);
+ q->len = nextline_len;
+ q->has_newline = nextline_newline;
+ q->refcount = 1;
+ memcpy (q->text, nextline_text, nextline_len);
+ vec->vector[i] = q;
+
+ vec->nlines += nnew;
+
+ return 1;
+}
+
+static void linevector_delete PROTO ((struct linevector *, unsigned int,
+ unsigned int));
+
+/* Remove NLINES lines from VEC at position POS (where line 0 is the
+ first line). */
+static void
+linevector_delete (vec, pos, nlines)
+ struct linevector *vec;
+ unsigned int pos;
+ unsigned int nlines;
+{
+ unsigned int i;
+ unsigned int last;
+
+ last = vec->nlines - nlines;
+ for (i = pos; i < pos + nlines; ++i)
+ {
+ if (--vec->vector[i]->refcount == 0)
+ free (vec->vector[i]);
+ }
+ for (i = pos; i < last; ++i)
+ vec->vector[i] = vec->vector[i + nlines];
+ vec->nlines -= nlines;
+}
+
+static void linevector_copy PROTO ((struct linevector *, struct linevector *));
+
+/* Copy FROM to TO, copying the vectors but not the lines pointed to. */
+static void
+linevector_copy (to, from)
+ struct linevector *to;
+ struct linevector *from;
+{
+ unsigned int ln;
+
+ for (ln = 0; ln < to->nlines; ++ln)
+ {
+ if (--to->vector[ln]->refcount == 0)
+ free (to->vector[ln]);
+ }
+ if (from->nlines > to->lines_alloced)
+ {
+ if (to->lines_alloced == 0)
+ to->lines_alloced = 10;
+ while (from->nlines > to->lines_alloced)
+ to->lines_alloced *= 2;
+ to->vector = (struct line **)
+ xrealloc (to->vector, to->lines_alloced * sizeof (*to->vector));
+ }
+ memcpy (to->vector, from->vector,
+ from->nlines * sizeof (*to->vector));
+ to->nlines = from->nlines;
+ for (ln = 0; ln < to->nlines; ++ln)
+ ++to->vector[ln]->refcount;
+}
+
+static void linevector_free PROTO ((struct linevector *));
+
+/* Free storage associated with linevector. */
+static void
+linevector_free (vec)
+ struct linevector *vec;
+{
+ unsigned int ln;
+
+ if (vec->vector != NULL)
+ {
+ for (ln = 0; ln < vec->nlines; ++ln)
+ if (--vec->vector[ln]->refcount == 0)
+ free (vec->vector[ln]);
+
+ free (vec->vector);
+ }
+}
+
+static char *month_printname PROTO ((char *));
+
+/* Given a textual string giving the month (1-12), terminated with any
+ character not recognized by atoi, return the 3 character name to
+ print it with. I do not think it is a good idea to change these
+ strings based on the locale; they are standard abbreviations (for
+ example in rfc822 mail messages) which should be widely understood.
+ Returns a pointer into static readonly storage. */
+static char *
+month_printname (month)
+ char *month;
+{
+ static const char *const months[] =
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+ int mnum;
+
+ mnum = atoi (month);
+ if (mnum < 1 || mnum > 12)
+ return "???";
+ return (char *)months[mnum - 1];
+}
+
+static int
+apply_rcs_changes PROTO ((struct linevector *, const char *, size_t,
+ const char *, RCSVers *, RCSVers *));
+
+/* Apply changes to the line vector LINES. DIFFBUF is a buffer of
+ length DIFFLEN holding the change text from an RCS file (the output
+ of diff -n). NAME is used in error messages. The VERS field of
+ any line added is set to ADDVERS. The VERS field of any line
+ deleted is set to DELVERS, unless DELVERS is NULL, in which case
+ the VERS field of deleted lines is unchanged. The function returns
+ non-zero if the change text is applied successfully. It returns
+ zero if the change text does not appear to apply to LINES (e.g., a
+ line number is invalid). If the change text is improperly
+ formatted (e.g., it is not the output of diff -n), the function
+ calls error with a status of 1, causing the program to exit. */
+
+static int
+apply_rcs_changes (lines, diffbuf, difflen, name, addvers, delvers)
+ struct linevector *lines;
+ const char *diffbuf;
+ size_t difflen;
+ const char *name;
+ RCSVers *addvers;
+ RCSVers *delvers;
+{
+ const char *p;
+ const char *q;
+ int op;
+ /* The RCS format throws us for a loop in that the deltafrags (if
+ we define a deltafrag as an add or a delete) need to be applied
+ in reverse order. So we stick them into a linked list. */
+ struct deltafrag {
+ enum {FRAG_ADD, FRAG_DELETE} type;
+ unsigned long pos;
+ unsigned long nlines;
+ const char *new_lines;
+ size_t len;
+ struct deltafrag *next;
+ };
+ struct deltafrag *dfhead;
+ struct deltafrag *df;
+
+ dfhead = NULL;
+ for (p = diffbuf; p != NULL && p < diffbuf + difflen; )
+ {
+ op = *p++;
+ if (op != 'a' && op != 'd')
+ /* Can't just skip over the deltafrag, because the value
+ of op determines the syntax. */
+ error (1, 0, "unrecognized operation '\\x%x' in %s",
+ op, name);
+ df = (struct deltafrag *) xmalloc (sizeof (struct deltafrag));
+ df->next = dfhead;
+ dfhead = df;
+ df->pos = strtoul (p, (char **) &q, 10);
+
+ if (p == q)
+ error (1, 0, "number expected in %s", name);
+ p = q;
+ if (*p++ != ' ')
+ error (1, 0, "space expected in %s", name);
+ df->nlines = strtoul (p, (char **) &q, 10);
+ if (p == q)
+ error (1, 0, "number expected in %s", name);
+ p = q;
+ if (*p++ != '\012')
+ error (1, 0, "linefeed expected in %s", name);
+
+ if (op == 'a')
+ {
+ unsigned int i;
+
+ df->type = FRAG_ADD;
+ i = df->nlines;
+ /* The text we want is the number of lines specified, or
+ until the end of the value, whichever comes first (it
+ will be the former except in the case where we are
+ adding a line which does not end in newline). */
+ for (q = p; i != 0; ++q)
+ if (*q == '\n')
+ --i;
+ else if (q == diffbuf + difflen)
+ {
+ if (i != 1)
+ error (1, 0, "premature end of change in %s", name);
+ else
+ break;
+ }
+
+ /* Stash away a pointer to the text we are adding. */
+ df->new_lines = p;
+ df->len = q - p;
+
+ p = q;
+ }
+ else
+ {
+ /* Correct for the fact that line numbers in RCS files
+ start with 1. */
+ --df->pos;
+
+ assert (op == 'd');
+ df->type = FRAG_DELETE;
+ }
+ }
+
+ for (df = dfhead; df != NULL;)
+ {
+ unsigned int ln;
+
+ switch (df->type)
+ {
+ case FRAG_ADD:
+ if (! linevector_add (lines, df->new_lines, df->len, addvers,
+ df->pos))
+ return 0;
+ break;
+ case FRAG_DELETE:
+ if (df->pos > lines->nlines
+ || df->pos + df->nlines > lines->nlines)
+ return 0;
+ if (delvers != NULL)
+ for (ln = df->pos; ln < df->pos + df->nlines; ++ln)
+ lines->vector[ln]->vers = delvers;
+ linevector_delete (lines, df->pos, df->nlines);
+ break;
+ }
+ df = df->next;
+ free (dfhead);
+ dfhead = df;
+ }
+
+ return 1;
+}
+
+/* Apply an RCS change text to a buffer. The function name starts
+ with rcs rather than RCS because this does not take an RCSNode
+ argument. NAME is used in error messages. TEXTBUF is the text
+ buffer to change, and TEXTLEN is the size. DIFFBUF and DIFFLEN are
+ the change buffer and size. The new buffer is returned in *RETBUF
+ and *RETLEN. The new buffer is allocated by xmalloc.
+
+ Return 1 for success. On failure, call error and return 0. */
+
+int
+rcs_change_text (name, textbuf, textlen, diffbuf, difflen, retbuf, retlen)
+ const char *name;
+ char *textbuf;
+ size_t textlen;
+ const char *diffbuf;
+ size_t difflen;
+ char **retbuf;
+ size_t *retlen;
+{
+ struct linevector lines;
+ int ret;
+
+ *retbuf = NULL;
+ *retlen = 0;
+
+ linevector_init (&lines);
+
+ if (! linevector_add (&lines, textbuf, textlen, NULL, 0))
+ error (1, 0, "cannot initialize line vector");
+
+ if (! apply_rcs_changes (&lines, diffbuf, difflen, name, NULL, NULL))
+ {
+ error (0, 0, "invalid change text in %s", name);
+ ret = 0;
+ }
+ else
+ {
+ char *p;
+ size_t n;
+ unsigned int ln;
+
+ n = 0;
+ for (ln = 0; ln < lines.nlines; ++ln)
+ /* 1 for \n */
+ n += lines.vector[ln]->len + 1;
+
+ p = xmalloc (n);
+ *retbuf = p;
+
+ for (ln = 0; ln < lines.nlines; ++ln)
+ {
+ memcpy (p, lines.vector[ln]->text, lines.vector[ln]->len);
+ p += lines.vector[ln]->len;
+ if (lines.vector[ln]->has_newline)
+ *p++ = '\n';
+ }
+
+ *retlen = p - *retbuf;
+ assert (*retlen <= n);
+
+ ret = 1;
+ }
+
+ linevector_free (&lines);
+
+ return ret;
+}
+
+/* Walk the deltas in RCS to get to revision VERSION.
+
+ If OP is RCS_ANNOTATE, then write annotations using cvs_output.
+
+ If OP is RCS_FETCH, then put the contents of VERSION into a
+ newly-malloc'd array and put a pointer to it in *TEXT. Each line
+ is \n terminated; the caller is responsible for converting text
+ files if desired. The total length is put in *LEN.
+
+ If FP is non-NULL, it should be a file descriptor open to the file
+ RCS with file position pointing to the deltas. We close the file
+ when we are done.
+
+ If LOG is non-NULL, then *LOG is set to the log message of VERSION,
+ and *LOGLEN is set to the length of the log message.
+
+ On error, give a fatal error. */
+
+static void
+RCS_deltas (rcs, fp, rcsbuf, version, op, text, len, log, loglen)
+ RCSNode *rcs;
+ FILE *fp;
+ struct rcsbuffer *rcsbuf;
+ char *version;
+ enum rcs_delta_op op;
+ char **text;
+ size_t *len;
+ char **log;
+ size_t *loglen;
+{
+ struct rcsbuffer rcsbuf_local;
+ char *branchversion;
+ char *cpversion;
+ char *key;
+ char *value;
+ size_t vallen;
+ RCSVers *vers;
+ RCSVers *prev_vers;
+ RCSVers *trunk_vers;
+ char *next;
+ int ishead, isnext, isversion, onbranch;
+ Node *node;
+ struct linevector headlines;
+ struct linevector curlines;
+ struct linevector trunklines;
+ int foundhead;
+
+ if (fp == NULL)
+ {
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fp, &rcsbuf_local);
+ rcsbuf = &rcsbuf_local;
+ }
+
+ ishead = 1;
+ vers = NULL;
+ prev_vers = NULL;
+ trunk_vers = NULL;
+ next = NULL;
+ onbranch = 0;
+ foundhead = 0;
+
+ linevector_init (&curlines);
+ linevector_init (&headlines);
+ linevector_init (&trunklines);
+
+ /* We set BRANCHVERSION to the version we are currently looking
+ for. Initially, this is the version on the trunk from which
+ VERSION branches off. If VERSION is not a branch, then
+ BRANCHVERSION is just VERSION. */
+ branchversion = xstrdup (version);
+ cpversion = strchr (branchversion, '.');
+ if (cpversion != NULL)
+ cpversion = strchr (cpversion + 1, '.');
+ if (cpversion != NULL)
+ *cpversion = '\0';
+
+ do {
+ if (! rcsbuf_getrevnum (rcsbuf, &key))
+ error (1, 0, "unexpected EOF reading RCS file %s", rcs->path);
+
+ if (next != NULL && ! STREQ (next, key))
+ {
+ /* This is not the next version we need. It is a branch
+ version which we want to ignore. */
+ isnext = 0;
+ isversion = 0;
+ }
+ else
+ {
+ isnext = 1;
+
+ /* look up the revision */
+ node = findnode (rcs->versions, key);
+ if (node == NULL)
+ error (1, 0,
+ "mismatch in rcs file %s between deltas and deltatexts",
+ rcs->path);
+
+ /* Stash the previous version. */
+ prev_vers = vers;
+
+ vers = (RCSVers *) node->data;
+ next = vers->next;
+
+ /* Compare key and trunkversion now, because key points to
+ storage controlled by rcsbuf_getkey. */
+ if (STREQ (branchversion, key))
+ isversion = 1;
+ else
+ isversion = 0;
+ }
+
+ while (1)
+ {
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s does not appear to be a valid rcs file",
+ rcs->path);
+
+ if (log != NULL
+ && isversion
+ && STREQ (key, "log")
+ && STREQ (branchversion, version))
+ {
+ *log = rcsbuf_valcopy (rcsbuf, value, 0, loglen);
+ }
+
+ if (STREQ (key, "text"))
+ {
+ rcsbuf_valpolish (rcsbuf, value, 0, &vallen);
+ if (ishead)
+ {
+ if (! linevector_add (&curlines, value, vallen, NULL, 0))
+ error (1, 0, "invalid rcs file %s", rcs->path);
+
+ ishead = 0;
+ }
+ else if (isnext)
+ {
+ if (! apply_rcs_changes (&curlines, value, vallen,
+ rcs->path,
+ onbranch ? vers : NULL,
+ onbranch ? NULL : prev_vers))
+ error (1, 0, "invalid change text in %s", rcs->path);
+ }
+ break;
+ }
+ }
+
+ if (isversion)
+ {
+ /* This is either the version we want, or it is the
+ branchpoint to the version we want. */
+ if (STREQ (branchversion, version))
+ {
+ /* This is the version we want. */
+ linevector_copy (&headlines, &curlines);
+ foundhead = 1;
+ if (onbranch)
+ {
+ /* We have found this version by tracking up a
+ branch. Restore back to the lines we saved
+ when we left the trunk, and continue tracking
+ down the trunk. */
+ onbranch = 0;
+ vers = trunk_vers;
+ next = vers->next;
+ linevector_copy (&curlines, &trunklines);
+ }
+ }
+ else
+ {
+ Node *p;
+
+ /* We need to look up the branch. */
+ onbranch = 1;
+
+ if (numdots (branchversion) < 2)
+ {
+ unsigned int ln;
+
+ /* We are leaving the trunk; save the current
+ lines so that we can restore them when we
+ continue tracking down the trunk. */
+ trunk_vers = vers;
+ linevector_copy (&trunklines, &curlines);
+
+ /* Reset the version information we have
+ accumulated so far. It only applies to the
+ changes from the head to this version. */
+ for (ln = 0; ln < curlines.nlines; ++ln)
+ curlines.vector[ln]->vers = NULL;
+ }
+
+ /* The next version we want is the entry on
+ VERS->branches which matches this branch. For
+ example, suppose VERSION is 1.21.4.3 and
+ BRANCHVERSION was 1.21. Then we look for an entry
+ starting with "1.21.4" and we'll put it (probably
+ 1.21.4.1) in NEXT. We'll advance BRANCHVERSION by
+ two dots (in this example, to 1.21.4.3). */
+
+ if (vers->branches == NULL)
+ error (1, 0, "missing expected branches in %s",
+ rcs->path);
+ *cpversion = '.';
+ ++cpversion;
+ cpversion = strchr (cpversion, '.');
+ if (cpversion == NULL)
+ error (1, 0, "version number confusion in %s",
+ rcs->path);
+ for (p = vers->branches->list->next;
+ p != vers->branches->list;
+ p = p->next)
+ if (strncmp (p->key, branchversion,
+ cpversion - branchversion) == 0)
+ break;
+ if (p == vers->branches->list)
+ error (1, 0, "missing expected branch in %s",
+ rcs->path);
+
+ next = p->key;
+
+ cpversion = strchr (cpversion + 1, '.');
+ if (cpversion != NULL)
+ *cpversion = '\0';
+ }
+ }
+ if (op == RCS_FETCH && foundhead)
+ break;
+ } while (next != NULL);
+
+ free (branchversion);
+
+ rcsbuf_cache (rcs, rcsbuf);
+
+ if (! foundhead)
+ error (1, 0, "could not find desired version %s in %s",
+ version, rcs->path);
+
+ /* Now print out or return the data we have just computed. */
+ switch (op)
+ {
+ case RCS_ANNOTATE:
+ {
+ unsigned int ln;
+
+ for (ln = 0; ln < headlines.nlines; ++ln)
+ {
+ char buf[80];
+ /* Period which separates year from month in date. */
+ char *ym;
+ /* Period which separates month from day in date. */
+ char *md;
+ RCSVers *prvers;
+
+ prvers = headlines.vector[ln]->vers;
+ if (prvers == NULL)
+ prvers = vers;
+
+ sprintf (buf, "%-12s (%-8.8s ",
+ prvers->version,
+ prvers->author);
+ cvs_output (buf, 0);
+
+ /* Now output the date. */
+ ym = strchr (prvers->date, '.');
+ if (ym == NULL)
+ {
+ /* ??- is an ANSI trigraph. The ANSI way to
+ avoid it is \? but some pre ANSI compilers
+ complain about the unrecognized escape
+ sequence. Of course string concatenation
+ ("??" "-???") is also an ANSI-ism. Testing
+ __STDC__ seems to be a can of worms, since
+ compilers do all kinds of things with it. */
+ cvs_output ("??", 0);
+ cvs_output ("-???", 0);
+ cvs_output ("-??", 0);
+ }
+ else
+ {
+ md = strchr (ym + 1, '.');
+ if (md == NULL)
+ cvs_output ("??", 0);
+ else
+ cvs_output (md + 1, 2);
+
+ cvs_output ("-", 1);
+ cvs_output (month_printname (ym + 1), 0);
+ cvs_output ("-", 1);
+ /* Only output the last two digits of the year. Our output
+ lines are long enough as it is without printing the
+ century. */
+ cvs_output (ym - 2, 2);
+ }
+ cvs_output ("): ", 0);
+ if (headlines.vector[ln]->len != 0)
+ cvs_output (headlines.vector[ln]->text,
+ headlines.vector[ln]->len);
+ cvs_output ("\n", 1);
+ }
+ }
+ break;
+ case RCS_FETCH:
+ {
+ char *p;
+ size_t n;
+ unsigned int ln;
+
+ assert (text != NULL);
+ assert (len != NULL);
+
+ n = 0;
+ for (ln = 0; ln < headlines.nlines; ++ln)
+ /* 1 for \n */
+ n += headlines.vector[ln]->len + 1;
+ p = xmalloc (n);
+ *text = p;
+ for (ln = 0; ln < headlines.nlines; ++ln)
+ {
+ memcpy (p, headlines.vector[ln]->text,
+ headlines.vector[ln]->len);
+ p += headlines.vector[ln]->len;
+ if (headlines.vector[ln]->has_newline)
+ *p++ = '\n';
+ }
+ *len = p - *text;
+ assert (*len <= n);
+ }
+ break;
+ }
+
+ linevector_free (&curlines);
+ linevector_free (&headlines);
+ linevector_free (&trunklines);
+
+ return;
+}
+
+/* Read the information for a single delta from the RCS buffer RCSBUF,
+ whose name is RCSFILE. *KEYP and *VALP are either NULL, or the
+ first key/value pair to read, as set by rcsbuf_getkey. Return NULL
+ if there are no more deltas. Store the key/value pair which
+ terminated the read in *KEYP and *VALP. */
+
+static RCSVers *
+getdelta (rcsbuf, rcsfile, keyp, valp)
+ struct rcsbuffer *rcsbuf;
+ char *rcsfile;
+ char **keyp;
+ char **valp;
+{
+ RCSVers *vnode;
+ char *key, *value, *keybuf, *valbuf, *cp;
+ Node *kv;
+
+ /* Get revision number if it wasn't passed in. This uses
+ rcsbuf_getkey because it doesn't croak when encountering
+ unexpected input. As a result, we have to play unholy games
+ with `key' and `value'. */
+ if (*keyp != NULL)
+ {
+ key = *keyp;
+ value = *valp;
+ }
+ else
+ {
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s: unexpected EOF", rcsfile);
+ }
+
+ /* Make sure that it is a revision number and not a cabbage
+ or something. */
+ for (cp = key;
+ (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
+ cp++)
+ /* do nothing */ ;
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK since
+ we know exactly what to expect. */
+ if (*cp != '\0' || strncmp (RCSDATE, value, (sizeof RCSDATE) - 1) != 0)
+ {
+ *keyp = key;
+ *valp = value;
+ return NULL;
+ }
+
+ vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
+ memset (vnode, 0, sizeof (RCSVers));
+
+ vnode->version = xstrdup (key);
+
+ /* Grab the value of the date from value. Note that we are not
+ massaging VALUE from the string found in the RCS file. */
+ cp = value + (sizeof RCSDATE) - 1; /* skip the "date" keyword */
+ while (whitespace (*cp)) /* take space off front of value */
+ cp++;
+
+ vnode->date = xstrdup (cp);
+
+ /* Get author field. */
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (! STREQ (key, "author"))
+ error (1, 0, "\
+unable to parse %s; `author' not in the expected place", rcsfile);
+ vnode->author = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
+
+ /* Get state field. */
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (! STREQ (key, "state"))
+ error (1, 0, "\
+unable to parse %s; `state' not in the expected place", rcsfile);
+ vnode->state = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
+ /* The value is optional, according to rcsfile(5). */
+ if (value != NULL && STREQ (value, "dead"))
+ {
+ vnode->dead = 1;
+ }
+
+ /* Note that "branches" and "next" are in fact mandatory, according
+ to doc/RCSFILES. */
+
+ /* fill in the branch list (if any branches exist) */
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, RCSDESC))
+ {
+ *keyp = key;
+ *valp = value;
+ /* Probably could/should be a fatal error. */
+ error (0, 0, "warning: 'branches' keyword missing from %s", rcsfile);
+ return vnode;
+ }
+ if (value != (char *) NULL)
+ {
+ vnode->branches = getlist ();
+ /* Note that we are not massaging VALUE from the string found
+ in the RCS file. */
+ do_branches (vnode->branches, value);
+ }
+
+ /* fill in the next field if there is a next revision */
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ {
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ }
+ if (STREQ (key, RCSDESC))
+ {
+ *keyp = key;
+ *valp = value;
+ /* Probably could/should be a fatal error. */
+ error (0, 0, "warning: 'next' keyword missing from %s", rcsfile);
+ return vnode;
+ }
+ if (value != (char *) NULL)
+ vnode->next = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
+
+ /*
+ * XXX - this is where we put the symbolic link stuff???
+ * (into newphrases in the deltas).
+ */
+ while (1)
+ {
+ int len;
+ size_t valbuflen;
+
+ key = NULL;
+
+ if (! rcsbuf_getid (rcsbuf, &keybuf))
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+
+ /* rcsbuf_getid did not terminate the key, so copy it to new space. */
+ len = rcsbuf->ptr - keybuf;
+ key = (char *) xmalloc (len + 1);
+ strncpy (key, keybuf, len);
+ key[len] = '\0';
+
+ /* The `desc' keyword has only a single string value, with no
+ trailing semicolon, so it must be handled specially. */
+ if (STREQ (key, RCSDESC))
+ {
+ (void) rcsbuf_getstring (rcsbuf, &valbuf);
+ value = rcsbuf_valcopy (rcsbuf, valbuf, 1, &valbuflen);
+ break;
+ }
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* The `hardlinks' value is a group of words, which must
+ be parsed separately and added as a list to vnode->hardlinks. */
+ if (STREQ (key, "hardlinks"))
+ {
+ Node *n;
+
+ vnode->hardlinks = getlist();
+ while (1)
+ {
+ if (! rcsbuf_getword (rcsbuf, &valbuf))
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ if (valbuf == NULL)
+ break;
+ n = getnode();
+ n->key = rcsbuf_valcopy (rcsbuf, valbuf, 1, NULL);
+ addnode (vnode->hardlinks, n);
+ }
+ continue;
+ }
+#endif
+
+ /* Get the value. */
+ value = NULL;
+ while (1)
+ {
+ if (! rcsbuf_getword (rcsbuf, &valbuf))
+ error (1, 0, "unexpected end of file reading %s", rcsfile);
+ if (valbuf == NULL)
+ break;
+
+ /* Copy valbuf to new space so we can polish it, then
+ append it to value. */
+
+ if (value == NULL)
+ {
+ value = rcsbuf_valcopy (rcsbuf, valbuf, 1, &valbuflen);
+ }
+ else
+ {
+ char *temp_value;
+
+ temp_value = rcsbuf_valcopy (rcsbuf, valbuf, 1, &valbuflen);
+ len = strlen (value);
+ value = (char *) xrealloc (value, len + valbuflen + 2);
+ value[len] = ' ';
+ strcpy (value + len + 1, temp_value);
+ free (temp_value);
+ }
+ }
+
+ /* Enable use of repositories created by certain obsolete
+ versions of CVS. This code should remain indefinately;
+ there is no procedure for converting old repositories, and
+ checking for it is harmless. */
+ if (STREQ (key, RCSDEAD))
+ {
+ vnode->dead = 1;
+ if (vnode->state != NULL)
+ free (vnode->state);
+ vnode->state = xstrdup ("dead");
+ continue;
+ }
+ /* if we have a new revision number, we're done with this delta */
+ for (cp = key;
+ (isdigit ((unsigned char) *cp) || *cp == '.') && *cp != '\0';
+ cp++)
+ /* do nothing */ ;
+ /* Note that when comparing with RCSDATE, we are not massaging
+ VALUE from the string found in the RCS file. This is OK
+ since we know exactly what to expect. */
+ if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+ break;
+
+ /* At this point, key and value represent a user-defined field
+ in the delta node. */
+ if (vnode->other_delta == NULL)
+ vnode->other_delta = getlist ();
+ kv = getnode ();
+ kv->type = RCSFIELD;
+ kv->key = key;
+ kv->data = value;
+ if (addnode (vnode->other_delta, kv) != 0)
+ {
+ /* Complaining about duplicate keys in newphrases seems
+ questionable, in that we don't know what they mean and
+ doc/RCSFILES has no prohibition on several newphrases
+ with the same key. But we can't store more than one as
+ long as we store them in a List *. */
+ error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
+ key, rcsfile);
+ freenode (kv);
+ }
+ }
+
+ /* Return the key which caused us to fail back to the caller. */
+ *keyp = key;
+ *valp = value;
+
+ return vnode;
+}
+
+static void
+freedeltatext (d)
+ Deltatext *d;
+{
+ if (d->version != NULL)
+ free (d->version);
+ if (d->log != NULL)
+ free (d->log);
+ if (d->text != NULL)
+ free (d->text);
+ if (d->other != (List *) NULL)
+ dellist (&d->other);
+ free (d);
+}
+
+static Deltatext *
+RCS_getdeltatext (rcs, fp, rcsbuf)
+ RCSNode *rcs;
+ FILE *fp;
+ struct rcsbuffer *rcsbuf;
+{
+ char *num;
+ char *key, *value;
+ Node *p;
+ Deltatext *d;
+
+ /* Get the revision number. */
+ if (! rcsbuf_getrevnum (rcsbuf, &num))
+ {
+ /* If num == NULL, it means we reached EOF naturally. That's
+ fine. */
+ if (num == NULL)
+ return NULL;
+ else
+ error (1, 0, "%s: unexpected EOF", rcs->path);
+ }
+
+ p = findnode (rcs->versions, num);
+ if (p == NULL)
+ error (1, 0, "mismatch in rcs file %s between deltas and deltatexts",
+ rcs->path);
+
+ d = (Deltatext *) xmalloc (sizeof (Deltatext));
+ d->version = xstrdup (num);
+
+ /* Get the log message. */
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
+ if (! STREQ (key, "log"))
+ error (1, 0, "%s, delta %s: expected `log', got `%s'",
+ rcs->path, num, key);
+ d->log = rcsbuf_valcopy (rcsbuf, value, 0, (size_t *) NULL);
+
+ /* Get random newphrases. */
+ d->other = getlist();
+ while (1)
+ {
+ if (! rcsbuf_getkey (rcsbuf, &key, &value))
+ error (1, 0, "%s, delta %s: unexpected EOF", rcs->path, num);
+
+ if (STREQ (key, "text"))
+ break;
+
+ p = getnode();
+ p->type = RCSFIELD;
+ p->key = xstrdup (key);
+ p->data = rcsbuf_valcopy (rcsbuf, value, 1, (size_t *) NULL);
+ if (addnode (d->other, p) < 0)
+ {
+ error (0, 0, "warning: %s, delta %s: duplicate field `%s'",
+ rcs->path, num, key);
+ }
+ }
+
+ /* Get the change text. We already know that this key is `text'. */
+ d->text = rcsbuf_valcopy (rcsbuf, value, 0, &d->len);
+
+ return d;
+}
+
+/* RCS output functions, for writing RCS format files from RCSNode
+ structures.
+
+ For most of this work, RCS 5.7 uses an `aprintf' function which aborts
+ program upon error. Instead, these functions check the output status
+ of the stream right before closing it, and aborts if an error condition
+ is found. The RCS solution is probably the better one: it produces
+ more overhead, but will produce a clearer diagnostic in the case of
+ catastrophic error. In either case, however, the repository will probably
+ not get corrupted. */
+
+static int
+putsymbol_proc (symnode, fparg)
+ Node *symnode;
+ void *fparg;
+{
+ FILE *fp = (FILE *) fparg;
+
+ /* A fiddly optimization: this code used to just call fprintf, but
+ in an old repository with hundreds of tags this can get called
+ hundreds of thousands of times when doing a cvs tag. Since
+ tagging is a relatively common operation, and using putc and
+ fputs is just as comprehensible, the change is worthwhile. */
+ putc ('\n', fp);
+ putc ('\t', fp);
+ fputs (symnode->key, fp);
+ putc (':', fp);
+ fputs (symnode->data, fp);
+ return 0;
+}
+
+static int putlock_proc PROTO ((Node *, void *));
+
+/* putlock_proc is like putsymbol_proc, but key and data are reversed. */
+
+static int
+putlock_proc (symnode, fp)
+ Node *symnode;
+ void *fp;
+{
+ return fprintf ((FILE *) fp, "\n\t%s:%s", symnode->data, symnode->key);
+}
+
+static int
+putrcsfield_proc (node, vfp)
+ Node *node;
+ void *vfp;
+{
+ FILE *fp = (FILE *) vfp;
+
+ /* Some magic keys used internally by CVS start with `;'. Skip them. */
+ if (node->key[0] == ';')
+ return 0;
+
+ fprintf (fp, "\n%s\t", node->key);
+ if (node->data != NULL)
+ {
+ /* If the field's value contains evil characters,
+ it must be stringified. */
+ /* FIXME: This does not quite get it right. "7jk8f" is not a legal
+ value for a value in a newpharse, according to doc/RCSFILES,
+ because digits are not valid in an "id". We might do OK by
+ always writing strings (enclosed in @@). Would be nice to
+ explicitly mention this one way or another in doc/RCSFILES.
+ A case where we are wrong in a much more clear-cut way is that
+ we let through non-graphic characters such as whitespace and
+ control characters. */
+ int n = strcspn (node->data, "$,.:;@");
+ if (node->data[n] == 0)
+ fputs (node->data, fp);
+ else
+ {
+ putc ('@', fp);
+ expand_at_signs (node->data, (off_t) strlen (node->data), fp);
+ putc ('@', fp);
+ }
+ }
+
+ /* desc, log and text fields should not be terminated with semicolon;
+ all other fields should be. */
+ if (! STREQ (node->key, "desc") &&
+ ! STREQ (node->key, "log") &&
+ ! STREQ (node->key, "text"))
+ {
+ putc (';', fp);
+ }
+ return 0;
+}
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+
+/* Save a filename in a `hardlinks' RCS field. NODE->KEY will contain
+ a full pathname, but currently only basenames are stored in the RCS
+ node. Assume that the filename includes nasty characters and
+ @-escape it. */
+
+static int
+puthardlink_proc (node, vfp)
+ Node *node;
+ void *vfp;
+{
+ FILE *fp = (FILE *) vfp;
+ char *basename = strrchr (node->key, '/');
+
+ if (basename == NULL)
+ basename = node->key;
+ else
+ ++basename;
+
+ putc ('\t', fp);
+ putc ('@', fp);
+ (void) expand_at_signs (basename, strlen (basename), fp);
+ putc ('@', fp);
+
+ return 0;
+}
+
+#endif
+
+/* Output the admin node for RCS into stream FP. */
+
+static void
+RCS_putadmin (rcs, fp)
+ RCSNode *rcs;
+ FILE *fp;
+{
+ fprintf (fp, "%s\t%s;\n", RCSHEAD, rcs->head ? rcs->head : "");
+ if (rcs->branch)
+ fprintf (fp, "%s\t%s;\n", RCSBRANCH, rcs->branch);
+
+ fputs ("access", fp);
+ if (rcs->access)
+ {
+ char *p, *s;
+ s = xstrdup (rcs->access);
+ for (p = strtok (s, " \n\t"); p != NULL; p = strtok (NULL, " \n\t"))
+ fprintf (fp, "\n\t%s", p);
+ free (s);
+ }
+ fputs (";\n", fp);
+
+ fputs (RCSSYMBOLS, fp);
+ /* If we haven't had to convert the symbols to a list yet, don't
+ force a conversion now; just write out the string. */
+ if (rcs->symbols == NULL && rcs->symbols_data != NULL)
+ {
+ fputs ("\n\t", fp);
+ fputs (rcs->symbols_data, fp);
+ }
+ else
+ walklist (RCS_symbols (rcs), putsymbol_proc, (void *) fp);
+ fputs (";\n", fp);
+
+ fputs ("locks", fp);
+ if (rcs->locks_data)
+ fprintf (fp, "\t%s", rcs->locks_data);
+ else if (rcs->locks)
+ walklist (rcs->locks, putlock_proc, (void *) fp);
+ if (rcs->strict_locks)
+ fprintf (fp, "; strict");
+ fputs (";\n", fp);
+
+ if (rcs->comment)
+ {
+ fprintf (fp, "comment\t@");
+ expand_at_signs (rcs->comment, (off_t) strlen (rcs->comment), fp);
+ fputs ("@;\n", fp);
+ }
+ if (rcs->expand && ! STREQ (rcs->expand, "kv"))
+ fprintf (fp, "%s\t@%s@;\n", RCSEXPAND, rcs->expand);
+
+ walklist (rcs->other, putrcsfield_proc, (void *) fp);
+
+ putc ('\n', fp);
+}
+
+static void
+putdelta (vers, fp)
+ RCSVers *vers;
+ FILE *fp;
+{
+ Node *bp, *start;
+
+ /* Skip if no revision was supplied, or if it is outdated (cvs admin -o) */
+ if (vers == NULL || vers->outdated)
+ return;
+
+ fprintf (fp, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
+ vers->version,
+ RCSDATE, vers->date,
+ "author", vers->author,
+ "state", vers->state ? vers->state : "");
+
+ if (vers->branches != NULL)
+ {
+ start = vers->branches->list;
+ for (bp = start->next; bp != start; bp = bp->next)
+ fprintf (fp, "\n\t%s", bp->key);
+ }
+
+ fprintf (fp, ";\nnext\t%s;", vers->next ? vers->next : "");
+
+ walklist (vers->other_delta, putrcsfield_proc, fp);
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (vers->hardlinks)
+ {
+ fprintf (fp, "\nhardlinks");
+ walklist (vers->hardlinks, puthardlink_proc, fp);
+ putc (';', fp);
+ }
+#endif
+ putc ('\n', fp);
+}
+
+static void
+RCS_putdtree (rcs, rev, fp)
+ RCSNode *rcs;
+ char *rev;
+ FILE *fp;
+{
+ RCSVers *versp;
+ Node *p, *branch;
+
+ if (rev == NULL)
+ return;
+
+ /* Find the delta node for this revision. */
+ p = findnode (rcs->versions, rev);
+ if(p == NULL){
+ /* This shouldn't happen if the repository is in good shape.
+ We need to clean up the lockfile, but we can't call
+ rcs_internal_unlockfile, because that assumes that things
+ are behaving normally. rcs_internal_unlockfile copies the
+ lockfile back onto the repository (,v) file.
+ Since the repository file is corrupt, this isn't a terribly
+ destructive thing to do, but just in case the user isn't
+ maintaining backups, and the ,v file does contain useful
+ information, we'll just delete the lockfile and let the
+ user sort out the problem. */
+
+ if (fclose(fp) == EOF)
+ error (1, 0, "error closing lock file %s", rcs_lockfile);
+ unlink_file(rcs_lockfile);
+ error (1, 0,
+ "error parsing repository file %s, file may be corrupt.",
+ rcs->path);
+ }
+
+ versp = (RCSVers *) p->data;
+
+ /* Print the delta node and recurse on its `next' node. This prints
+ the trunk. If there are any branches printed on this revision,
+ print those trunks as well. */
+ putdelta (versp, fp);
+ RCS_putdtree (rcs, versp->next, fp);
+ if (versp->branches != NULL)
+ {
+ branch = versp->branches->list;
+ for (p = branch->next; p != branch; p = p->next)
+ RCS_putdtree (rcs, p->key, fp);
+ }
+}
+
+static void
+RCS_putdesc (rcs, fp)
+ RCSNode *rcs;
+ FILE *fp;
+{
+ fprintf (fp, "\n\n%s\n@", RCSDESC);
+ if (rcs->desc != NULL)
+ {
+ off_t len = (off_t) strlen (rcs->desc);
+ if (len > 0)
+ {
+ expand_at_signs (rcs->desc, len, fp);
+ if (rcs->desc[len-1] != '\n')
+ putc ('\n', fp);
+ }
+ }
+ fputs ("@\n", fp);
+}
+
+static void
+putdeltatext (fp, d)
+ FILE *fp;
+ Deltatext *d;
+{
+ fprintf (fp, "\n\n%s\nlog\n@", d->version);
+ if (d->log != NULL)
+ {
+ int loglen = strlen (d->log);
+ expand_at_signs (d->log, (off_t) loglen, fp);
+ if (d->log[loglen-1] != '\n')
+ putc ('\n', fp);
+ }
+ putc ('@', fp);
+
+ walklist (d->other, putrcsfield_proc, fp);
+
+ fputs ("\ntext\n@", fp);
+ if (d->text != NULL)
+ expand_at_signs (d->text, (off_t) d->len, fp);
+ fputs ("@\n", fp);
+}
+
+/* TODO: the whole mechanism for updating deltas is kludgey... more
+ sensible would be to supply all the necessary info in a `newdeltatext'
+ field for RCSVers nodes. -twp */
+
+/* Copy delta text nodes from FIN to FOUT. If NEWDTEXT is non-NULL, it
+ is a new delta text node, and should be added to the tree at the
+ node whose revision number is INSERTPT. (Note that trunk nodes are
+ written in decreasing order, and branch nodes are written in
+ increasing order.) */
+
+static void
+RCS_copydeltas (rcs, fin, rcsbufin, fout, newdtext, insertpt)
+ RCSNode *rcs;
+ FILE *fin;
+ struct rcsbuffer *rcsbufin;
+ FILE *fout;
+ Deltatext *newdtext;
+ char *insertpt;
+{
+ int actions;
+ RCSVers *dadmin;
+ Node *np;
+ int insertbefore, found;
+ char *bufrest;
+ int nls;
+ size_t buflen;
+ char buf[8192];
+ int got;
+
+ /* Count the number of versions for which we have to do some
+ special operation. */
+ actions = walklist (rcs->versions, count_delta_actions, (void *) NULL);
+
+ /* Make a note of whether NEWDTEXT should be inserted
+ before or after its INSERTPT. */
+ insertbefore = (newdtext != NULL && numdots (newdtext->version) == 1);
+
+ while (actions != 0 || newdtext != NULL)
+ {
+ Deltatext *dtext;
+
+ dtext = RCS_getdeltatext (rcs, fin, rcsbufin);
+
+ /* We shouldn't hit EOF here, because that would imply that
+ some action was not taken, or that we could not insert
+ NEWDTEXT. */
+ if (dtext == NULL)
+ error (1, 0, "internal error: EOF too early in RCS_copydeltas");
+
+ found = (insertpt != NULL && STREQ (dtext->version, insertpt));
+ if (found && insertbefore)
+ {
+ putdeltatext (fout, newdtext);
+ newdtext = NULL;
+ insertpt = NULL;
+ }
+
+ np = findnode (rcs->versions, dtext->version);
+ dadmin = (RCSVers *) np->data;
+
+ /* If this revision has been outdated, just skip it. */
+ if (dadmin->outdated)
+ {
+ --actions;
+ continue;
+ }
+
+ /* Update the change text for this delta. New change text
+ data may come from cvs admin -m, cvs admin -o, or cvs ci. */
+ if (dadmin->text != NULL)
+ {
+ if (dadmin->text->log != NULL || dadmin->text->text != NULL)
+ --actions;
+ if (dadmin->text->log != NULL)
+ {
+ free (dtext->log);
+ dtext->log = dadmin->text->log;
+ dadmin->text->log = NULL;
+ }
+ if (dadmin->text->text != NULL)
+ {
+ free (dtext->text);
+ dtext->text = dadmin->text->text;
+ dtext->len = dadmin->text->len;
+ dadmin->text->text = NULL;
+ }
+ }
+ putdeltatext (fout, dtext);
+ freedeltatext (dtext);
+
+ if (found && !insertbefore)
+ {
+ putdeltatext (fout, newdtext);
+ newdtext = NULL;
+ insertpt = NULL;
+ }
+ }
+
+ /* Copy the rest of the file directly, without bothering to
+ interpret it. The caller will handle error checking by calling
+ ferror.
+
+ We just wrote a newline to the file, either in putdeltatext or
+ in the caller. However, we may not have read the corresponding
+ newline from the file, because rcsbuf_getkey returns as soon as
+ it finds the end of the '@' string for the desc or text key.
+ Therefore, we may read three newlines when we should really
+ only write two, and we check for that case here. This is not
+ an semantically important issue; we only do it to make our RCS
+ files look traditional. */
+
+ nls = 3;
+
+ rcsbuf_get_buffered (rcsbufin, &bufrest, &buflen);
+ if (buflen > 0)
+ {
+ if (bufrest[0] != '\n'
+ || strncmp (bufrest, "\n\n\n", buflen < 3 ? buflen : 3) != 0)
+ {
+ nls = 0;
+ }
+ else
+ {
+ if (buflen < 3)
+ nls -= buflen;
+ else
+ {
+ ++bufrest;
+ --buflen;
+ nls = 0;
+ }
+ }
+
+ fwrite (bufrest, 1, buflen, fout);
+ }
+
+ while ((got = fread (buf, 1, sizeof buf, fin)) != 0)
+ {
+ if (nls > 0
+ && got >= nls
+ && buf[0] == '\n'
+ && strncmp (buf, "\n\n\n", nls) == 0)
+ {
+ fwrite (buf + 1, 1, got - 1, fout);
+ }
+ else
+ {
+ fwrite (buf, 1, got, fout);
+ }
+
+ nls = 0;
+ }
+}
+
+/* A helper procedure for RCS_copydeltas. This is called via walklist
+ to count the number of RCS revisions for which some special action
+ is required. */
+
+static int
+count_delta_actions (np, ignore)
+ Node *np;
+ void *ignore;
+{
+ RCSVers *dadmin;
+
+ dadmin = (RCSVers *) np->data;
+
+ if (dadmin->outdated)
+ return 1;
+
+ if (dadmin->text != NULL
+ && (dadmin->text->log != NULL || dadmin->text->text != NULL))
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Clean up temporary files
+ */
+static RETSIGTYPE
+rcs_cleanup ()
+{
+ /* Note that the checks for existence_error are because we are
+ called from a signal handler, so we don't know whether the
+ files got created. */
+
+ /* FIXME: Do not perform buffered I/O from an interrupt handler like
+ this (via error). However, I'm leaving the error-calling code there
+ in the hope that on the rare occasion the error call is actually made
+ (e.g., a fluky I/O error or permissions problem prevents the deletion
+ of a just-created file) reentrancy won't be an issue. */
+ if (rcs_lockfile != NULL)
+ {
+ if (unlink_file (rcs_lockfile) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", rcs_lockfile);
+ }
+ rcs_lockfile = NULL;
+}
+
+/* RCS_internal_lockfile and RCS_internal_unlockfile perform RCS-style
+ locking on the specified RCSFILE: for a file called `foo,v', open
+ for writing a file called `,foo,'.
+
+ Note that we what do here is quite different from what RCS does.
+ RCS creates the ,foo, file before it reads the RCS file (if it
+ knows that it will be writing later), so that it actually serves as
+ a lock. We don't; instead we rely on CVS writelocks. This means
+ that if someone is running RCS on the file at the same time they
+ are running CVS on it, they might lose (we read the file,
+ then RCS writes it, then we write it, clobbering the
+ changes made by RCS). I believe the current sentiment about this
+ is "well, don't do that".
+
+ A concern has been expressed about whether adopting the RCS
+ strategy would slow us down. I don't think so, since we need to
+ write the ,foo, file anyway (unless perhaps if O_EXCL is slower or
+ something).
+
+ These do not perform quite the same function as the RCS -l option
+ for locking files: they are intended to prevent competing RCS
+ processes from stomping all over each other's laundry. Hence,
+ they are `internal' locking functions.
+
+ If there is an error, give a fatal error; if we return we always
+ return a non-NULL value. */
+
+static FILE *
+rcs_internal_lockfile (rcsfile)
+ char *rcsfile;
+{
+ int fd;
+ struct stat rstat;
+ FILE *fp;
+ static int first_call = 1;
+
+ if (first_call)
+ {
+ first_call = 0;
+ /* clean up if we get a signal */
+#ifdef SIGHUP
+ (void) SIG_register (SIGHUP, rcs_cleanup);
+#endif
+#ifdef SIGINT
+ (void) SIG_register (SIGINT, rcs_cleanup);
+#endif
+#ifdef SIGQUIT
+ (void) SIG_register (SIGQUIT, rcs_cleanup);
+#endif
+#ifdef SIGPIPE
+ (void) SIG_register (SIGPIPE, rcs_cleanup);
+#endif
+#ifdef SIGTERM
+ (void) SIG_register (SIGTERM, rcs_cleanup);
+#endif
+ }
+
+ /* Get the lock file name: `,file,' for RCS file `file,v'. */
+ assert (rcs_lockfile == NULL);
+ rcs_lockfile = rcs_lockfilename (rcsfile);
+
+ /* Use the existing RCS file mode, or read-only if this is a new
+ file. (Really, this is a lie -- if this is a new file,
+ RCS_checkin uses the permissions from the working copy. For
+ actually creating the file, we use 0444 as a safe default mode.) */
+ if (stat (rcsfile, &rstat) < 0)
+ {
+ if (existence_error (errno))
+ rstat.st_mode = S_IRUSR | S_IRGRP | S_IROTH;
+ else
+ error (1, errno, "cannot stat %s", rcsfile);
+ }
+
+ /* Try to open exclusively. POSIX.1 guarantees that O_EXCL|O_CREAT
+ guarantees an exclusive open. According to the RCS source, with
+ NFS v2 we must also throw in O_TRUNC and use an open mask that makes
+ the file unwriteable. For extensive justification, see the comments for
+ rcswriteopen() in rcsedit.c, in RCS 5.7. This is kind of pointless
+ in the CVS case; see comment at the start of this file concerning
+ general ,foo, file strategy.
+
+ There is some sentiment that with NFSv3 and such, that one can
+ rely on O_EXCL these days. This might be true for unix (I
+ don't really know), but I am still pretty skeptical in the case
+ of the non-unix systems. */
+ fd = open (rcs_lockfile,
+ OPEN_BINARY | O_WRONLY | O_CREAT | O_EXCL | O_TRUNC,
+ S_IRUSR | S_IRGRP | S_IROTH);
+
+ if (fd < 0)
+ {
+ error (1, errno, "could not open lock file `%s'", rcs_lockfile);
+ }
+
+ /* Force the file permissions, and return a stream object. */
+ /* Because we change the modes later, we don't worry about
+ this in the non-HAVE_FCHMOD case. */
+#ifdef HAVE_FCHMOD
+ if (fchmod (fd, rstat.st_mode) < 0)
+ error (1, errno, "cannot change mode for %s", rcs_lockfile);
+#endif
+ fp = fdopen (fd, FOPEN_BINARY_WRITE);
+ if (fp == NULL)
+ error (1, errno, "cannot fdopen %s", rcs_lockfile);
+
+ return fp;
+}
+
+static void
+rcs_internal_unlockfile (fp, rcsfile)
+ FILE *fp;
+ char *rcsfile;
+{
+ assert (rcs_lockfile != NULL);
+
+ /* Abort if we could not write everything successfully to LOCKFILE.
+ This is not a great error-handling mechanism, but should prevent
+ corrupting the repository. */
+
+ if (ferror (fp))
+ /* The only case in which using errno here would be meaningful
+ is if we happen to have left errno unmolested since the call
+ which produced the error (e.g. fprintf). That is pretty
+ fragile even if it happens to sometimes be true. The real
+ solution is to check each call to fprintf rather than waiting
+ until the end like this. */
+ error (1, 0, "error writing to lock file %s", rcs_lockfile);
+ if (fclose (fp) == EOF)
+ error (1, errno, "error closing lock file %s", rcs_lockfile);
+
+ rename_file (rcs_lockfile, rcsfile);
+
+ {
+ /* Use a temporary to make sure there's no interval
+ (after rcs_lockfile has been freed but before it's set to NULL)
+ during which the signal handler's use of rcs_lockfile would
+ reference freed memory. */
+ char *tmp = rcs_lockfile;
+ rcs_lockfile = NULL;
+ free (tmp);
+ }
+}
+
+static char *
+rcs_lockfilename (rcsfile)
+ char *rcsfile;
+{
+ char *lockfile, *lockp;
+ char *rcsbase, *rcsp, *rcsend;
+ int rcslen;
+
+ /* Create the lockfile name. */
+ rcslen = strlen (rcsfile);
+ lockfile = (char *) xmalloc (rcslen + 10);
+ rcsbase = last_component (rcsfile);
+ rcsend = rcsfile + rcslen - sizeof(RCSEXT);
+ for (lockp = lockfile, rcsp = rcsfile; rcsp < rcsbase; ++rcsp)
+ *lockp++ = *rcsp;
+ *lockp++ = ',';
+ while (rcsp <= rcsend)
+ *lockp++ = *rcsp++;
+ *lockp++ = ',';
+ *lockp = '\0';
+
+ return lockfile;
+}
+
+/* Rewrite an RCS file. The basic idea here is that the caller should
+ first call RCS_reparsercsfile, then munge the data structures as
+ desired (via RCS_delete_revs, RCS_settag, &c), then call RCS_rewrite. */
+
+void
+RCS_rewrite (rcs, newdtext, insertpt)
+ RCSNode *rcs;
+ Deltatext *newdtext;
+ char *insertpt;
+{
+ FILE *fin, *fout;
+ struct rcsbuffer rcsbufin;
+
+ if (noexec)
+ return;
+
+ /* Make sure we're operating on an actual file and not a symlink. */
+ resolve_symlink (&(rcs->path));
+
+ fout = rcs_internal_lockfile (rcs->path);
+
+ RCS_putadmin (rcs, fout);
+ RCS_putdtree (rcs, rcs->head, fout);
+ RCS_putdesc (rcs, fout);
+
+ /* Open the original RCS file and seek to the first delta text. */
+ rcsbuf_cache_open (rcs, rcs->delta_pos, &fin, &rcsbufin);
+
+ /* Update delta_pos to the current position in the output file.
+ Do NOT move these statements: they must be done after fin has
+ been positioned at the old delta_pos, but before any delta
+ texts have been written to fout. */
+ rcs->delta_pos = ftell (fout);
+ if (rcs->delta_pos == -1)
+ error (1, errno, "cannot ftell in RCS file %s", rcs->path);
+
+ RCS_copydeltas (rcs, fin, &rcsbufin, fout, newdtext, insertpt);
+
+ /* We don't want to call rcsbuf_cache here, since we're about to
+ delete the file. */
+ rcsbuf_close (&rcsbufin);
+ if (ferror (fin))
+ /* The only case in which using errno here would be meaningful
+ is if we happen to have left errno unmolested since the call
+ which produced the error (e.g. fread). That is pretty
+ fragile even if it happens to sometimes be true. The real
+ solution is to make sure that all the code which reads
+ from fin checks for errors itself (some does, some doesn't). */
+ error (0, 0, "warning: when closing RCS file `%s'", rcs->path);
+ if (fclose (fin) < 0)
+ error (0, errno, "warning: closing RCS file `%s'", rcs->path);
+
+ rcs_internal_unlockfile (fout, rcs->path);
+}
+
+
+/* Annotate command. In rcs.c for historical reasons (from back when
+ what is now RCS_deltas was part of annotate_fileproc). */
+
+/* Options from the command line. */
+
+static int force_tag_match = 1;
+static char *tag = NULL;
+static char *date = NULL;
+
+static int annotate_fileproc PROTO ((void *callerdat, struct file_info *));
+
+static int
+annotate_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ FILE *fp = NULL;
+ struct rcsbuffer *rcsbufp = NULL;
+ struct rcsbuffer rcsbuf;
+ char *version;
+
+ if (finfo->rcs == NULL)
+ return (1);
+
+ if (finfo->rcs->flags & PARTIAL)
+ {
+ RCS_reparsercsfile (finfo->rcs, &fp, &rcsbuf);
+ rcsbufp = &rcsbuf;
+ }
+
+ version = RCS_getversion (finfo->rcs, tag, date, force_tag_match,
+ (int *) NULL);
+ if (version == NULL)
+ return 0;
+
+ /* Distinguish output for various files if we are processing
+ several files. */
+ cvs_outerr ("Annotations for ", 0);
+ cvs_outerr (finfo->fullname, 0);
+ cvs_outerr ("\n***************\n", 0);
+
+ RCS_deltas (finfo->rcs, fp, rcsbufp, version, RCS_ANNOTATE, NULL,
+ NULL, NULL, NULL);
+ free (version);
+ return 0;
+}
+
+static const char *const annotate_usage[] =
+{
+ "Usage: %s %s [-lRf] [-r rev|-D date] [files...]\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-f\tUse head revision if tag/date not found.\n",
+ "\t-r rev\tAnnotate file as of specified revision/tag.\n",
+ "\t-D date\tAnnotate file as of specified date.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+/* Command to show the revision, date, and author where each line of a
+ file was modified. */
+
+int
+annotate (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (annotate_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+lr:D:fR")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'r':
+ tag = optarg;
+ break;
+ case 'D':
+ date = Make_Date (optarg);
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case '?':
+ default:
+ usage (annotate_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ if (!force_tag_match)
+ send_arg ("-f");
+ option_with_arg ("-r", tag);
+ if (date)
+ client_senddate (date);
+ send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_to_server ("annotate\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ if (tag != NULL)
+ tag_check_valid (tag, argc, argv, local, 0, "");
+
+ return start_recursion (annotate_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 1);
+}
+
+/*
+ * For a given file with full pathname PATH and revision number REV,
+ * produce a file label suitable for passing to diff. The default
+ * file label as used by RCS 5.7 looks like this:
+ *
+ * FILENAME <tab> YYYY/MM/DD <sp> HH:MM:SS <tab> REVNUM
+ *
+ * The date and time used are the revision's last checkin date and time.
+ * If REV is NULL, use the working copy's mtime instead.
+ */
+char *
+make_file_label (path, rev, rcs)
+ char *path;
+ char *rev;
+ RCSNode *rcs;
+{
+ char datebuf[MAXDATELEN];
+ char *label;
+ char *file;
+
+ file = last_component (path);
+ label = (char *) xmalloc (strlen (path)
+ + (rev == NULL ? 0 : strlen (rev))
+ + 50);
+
+ if (rev)
+ {
+ char *date;
+ RCS_getrevtime (rcs, rev, datebuf, 0);
+ date = printable_date (datebuf);
+ (void) sprintf (label, "-L%s\t%s\t%s", path, date, rev);
+ free (date);
+ }
+ else
+ {
+ struct stat sb;
+ struct tm *wm;
+
+ if (CVS_STAT (file, &sb) < 0)
+ error (0, 1, "could not get info for `%s'", path);
+ else
+ {
+ wm = gmtime (&sb.st_mtime);
+ (void) sprintf (datebuf, "%04d/%02d/%02d %02d:%02d:%02d",
+ wm->tm_year + 1900, wm->tm_mon + 1,
+ wm->tm_mday, wm->tm_hour,
+ wm->tm_min, wm->tm_sec);
+ (void) sprintf (label, "-L%s\t%s", path, datebuf);
+ }
+ }
+ return label;
+}
diff --git a/gnu/dist/cvs/src/rcscmds.c b/gnu/dist/cvs/src/rcscmds.c
new file mode 100644
index 00000000000..ab94e407bd7
--- /dev/null
+++ b/gnu/dist/cvs/src/rcscmds.c
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * The functions in this file provide an interface for performing
+ * operations directly on RCS files.
+ */
+
+#include "cvs.h"
+#include <assert.h>
+#include <stdio.h>
+#include "diffrun.h"
+
+/* This file, rcs.h, and rcs.c, together sometimes known as the "RCS
+ library", are intended to define our interface to RCS files.
+
+ Whether there will also be a version of RCS which uses this
+ library, or whether the library will be packaged for uses beyond
+ CVS or RCS (many people would like such a thing) is an open
+ question. Some considerations:
+
+ 1. An RCS library for CVS must have the capabilities of the
+ existing CVS code which accesses RCS files. In particular, simple
+ approaches will often be slow.
+
+ 2. An RCS library should not use code from the current RCS
+ (5.7 and its ancestors). The code has many problems. Too few
+ comments, too many layers of abstraction, too many global variables
+ (the correct number for a library is zero), too much intricately
+ interwoven functionality, and too many clever hacks. Paul Eggert,
+ the current RCS maintainer, agrees.
+
+ 3. More work needs to be done in terms of separating out the RCS
+ library from the rest of CVS (for example, cvs_output should be
+ replaced by a callback, and the declarations should be centralized
+ into rcs.h, and probably other such cleanups).
+
+ 4. To be useful for RCS and perhaps for other uses, the library
+ may need features beyond those needed by CVS.
+
+ 5. Any changes to the RCS file format *must* be compatible. Many,
+ many tools (not just CVS and RCS) can at least import this format.
+ RCS and CVS must preserve the current ability to import/export it
+ (preferably improved--magic branches are currently a roadblock).
+ See doc/RCSFILES in the CVS distribution for documentation of this
+ file format.
+
+ On a related note, see the comments at diff_exec, later in this file,
+ for more on the diff library. */
+
+static void RCS_output_diff_options PROTO ((char *, char *, char *, char *));
+
+
+/* Stuff to deal with passing arguments the way libdiff.a wants to deal
+ with them. This is a crufty interface; there is no good reason for it
+ to resemble a command line rather than something closer to "struct
+ log_data" in log.c. */
+
+/* First call call_diff_setup to setup any initial arguments. The
+ argument will be parsed into whitespace separated words and added
+ to the global call_diff_argv list.
+
+ Then, optionally, call call_diff_arg for each additional argument
+ that you'd like to pass to the diff library.
+
+ Finally, call call_diff or call_diff3 to produce the diffs. */
+
+static char **call_diff_argv;
+static int call_diff_argc;
+static int call_diff_argc_allocated;
+
+static void call_diff_add_arg PROTO ((const char *));
+static void call_diff_setup PROTO ((const char *prog));
+static int call_diff PROTO ((char *out));
+static int call_diff3 PROTO ((char *out));
+
+static void call_diff_write_output PROTO((const char *, size_t));
+static void call_diff_flush_output PROTO((void));
+static void call_diff_write_stdout PROTO((const char *));
+static void call_diff_error PROTO((const char *, const char *, const char *));
+
+/* VARARGS */
+static void
+call_diff_setup (prog)
+ const char *prog;
+{
+ char *cp;
+ int i;
+ char *call_diff_prog;
+
+ /* clean out any malloc'ed values from call_diff_argv */
+ for (i = 0; i < call_diff_argc; i++)
+ {
+ if (call_diff_argv[i])
+ {
+ free (call_diff_argv[i]);
+ call_diff_argv[i] = (char *) 0;
+ }
+ }
+ call_diff_argc = 0;
+
+ call_diff_prog = xstrdup (prog);
+
+ /* put each word into call_diff_argv, allocating it as we go */
+ for (cp = strtok (call_diff_prog, " \t");
+ cp != NULL;
+ cp = strtok ((char *) NULL, " \t"))
+ call_diff_add_arg (cp);
+ free (call_diff_prog);
+}
+
+static void
+call_diff_arg (s)
+ const char *s;
+{
+ call_diff_add_arg (s);
+}
+
+static void
+call_diff_add_arg (s)
+ const char *s;
+{
+ /* allocate more argv entries if we've run out */
+ if (call_diff_argc >= call_diff_argc_allocated)
+ {
+ call_diff_argc_allocated += 50;
+ call_diff_argv = (char **)
+ xrealloc ((char *) call_diff_argv,
+ call_diff_argc_allocated * sizeof (char **));
+ }
+
+ if (s)
+ call_diff_argv[call_diff_argc++] = xstrdup (s);
+ else
+ /* Not post-incremented on purpose! */
+ call_diff_argv[call_diff_argc] = (char *) 0;
+}
+
+/* Callback function for the diff library to write data to the output
+ file. This is used when we are producing output to stdout. */
+
+static void
+call_diff_write_output (text, len)
+ const char *text;
+ size_t len;
+{
+ if (len > 0)
+ cvs_output (text, len);
+}
+
+/* Call back function for the diff library to flush the output file.
+ This is used when we are producing output to stdout. */
+
+static void
+call_diff_flush_output ()
+{
+ cvs_flushout ();
+}
+
+/* Call back function for the diff library to write to stdout. */
+
+static void
+call_diff_write_stdout (text)
+ const char *text;
+{
+ cvs_output (text, 0);
+}
+
+/* Call back function for the diff library to write to stderr. */
+
+static void
+call_diff_error (format, a1, a2)
+ const char *format;
+ const char *a1;
+ const char *a2;
+{
+ /* FIXME: Should we somehow indicate that this error is coming from
+ the diff library? */
+ error (0, 0, format, a1, a2);
+}
+
+/* This set of callback functions is used if we are sending the diff
+ to stdout. */
+
+static struct diff_callbacks call_diff_stdout_callbacks =
+{
+ call_diff_write_output,
+ call_diff_flush_output,
+ call_diff_write_stdout,
+ call_diff_error
+};
+
+/* This set of callback functions is used if we are sending the diff
+ to a file. */
+
+static struct diff_callbacks call_diff_file_callbacks =
+{
+ (void (*) PROTO((const char *, size_t))) NULL,
+ (void (*) PROTO((void))) NULL,
+ call_diff_write_stdout,
+ call_diff_error
+};
+
+static int
+call_diff (out)
+ char *out;
+{
+ if (out == RUN_TTY)
+ return diff_run (call_diff_argc, call_diff_argv, NULL,
+ &call_diff_stdout_callbacks);
+ else
+ return diff_run (call_diff_argc, call_diff_argv, out,
+ &call_diff_file_callbacks);
+}
+
+static int
+call_diff3 (out)
+ char *out;
+{
+ if (out == RUN_TTY)
+ return diff3_run (call_diff_argc, call_diff_argv, NULL,
+ &call_diff_stdout_callbacks);
+ else
+ return diff3_run (call_diff_argc, call_diff_argv, out,
+ &call_diff_file_callbacks);
+}
+
+
+
+/* Merge revisions REV1 and REV2. */
+
+int
+RCS_merge(rcs, path, workfile, options, rev1, rev2)
+ RCSNode *rcs;
+ char *path;
+ char *workfile;
+ char *options;
+ char *rev1;
+ char *rev2;
+{
+ char *xrev1, *xrev2;
+ char *tmp1, *tmp2;
+ char *diffout = NULL;
+ int retval;
+
+ if (options != NULL && options[0] != '\0')
+ assert (options[0] == '-' && options[1] == 'k');
+
+ cvs_output ("RCS file: ", 0);
+ cvs_output (rcs->path, 0);
+ cvs_output ("\n", 1);
+
+ /* Calculate numeric revision numbers from rev1 and rev2 (may be
+ symbolic). */
+ xrev1 = RCS_gettag (rcs, rev1, 0, NULL);
+ xrev2 = RCS_gettag (rcs, rev2, 0, NULL);
+
+ /* Check out chosen revisions. The error message when RCS_checkout
+ fails is not very informative -- it is taken verbatim from RCS 5.7,
+ and relies on RCS_checkout saying something intelligent upon failure. */
+ cvs_output ("retrieving revision ", 0);
+ cvs_output (xrev1, 0);
+ cvs_output ("\n", 1);
+
+ tmp1 = cvs_temp_name();
+ if (RCS_checkout (rcs, NULL, xrev1, rev1, options, tmp1,
+ (RCSCHECKOUTPROC)0, NULL))
+ {
+ cvs_outerr ("rcsmerge: co failed\n", 0);
+ error_exit();
+ }
+
+ cvs_output ("retrieving revision ", 0);
+ cvs_output (xrev2, 0);
+ cvs_output ("\n", 1);
+
+ tmp2 = cvs_temp_name();
+ if (RCS_checkout (rcs, NULL, xrev2, rev2, options, tmp2,
+ (RCSCHECKOUTPROC)0, NULL))
+ {
+ cvs_outerr ("rcsmerge: co failed\n", 0);
+ error_exit();
+ }
+
+ /* Merge changes. */
+ cvs_output ("Merging differences between ", 0);
+ cvs_output (xrev1, 0);
+ cvs_output (" and ", 0);
+ cvs_output (xrev2, 0);
+ cvs_output (" into ", 0);
+ cvs_output (workfile, 0);
+ cvs_output ("\n", 1);
+
+ /* Remember that the first word in the `call_diff_setup' string is used now
+ only for diagnostic messages -- CVS no longer forks to run diff3. */
+ diffout = cvs_temp_name();
+ call_diff_setup ("diff3");
+ call_diff_arg ("-E");
+ call_diff_arg ("-am");
+
+ call_diff_arg ("-L");
+ call_diff_arg (workfile);
+ call_diff_arg ("-L");
+ call_diff_arg (xrev1);
+ call_diff_arg ("-L");
+ call_diff_arg (xrev2);
+
+ call_diff_arg (workfile);
+ call_diff_arg (tmp1);
+ call_diff_arg (tmp2);
+
+ retval = call_diff3 (diffout);
+
+ if (retval == 1)
+ cvs_outerr ("rcsmerge: warning: conflicts during merge\n", 0);
+ else if (retval == 2)
+ error_exit();
+
+ if (diffout)
+ copy_file (diffout, workfile);
+
+ /* Clean up. */
+ {
+ int save_noexec = noexec;
+ noexec = 0;
+ if (unlink_file (tmp1) < 0)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot remove temp file %s", tmp1);
+ }
+ free (tmp1);
+ if (unlink_file (tmp2) < 0)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot remove temp file %s", tmp2);
+ }
+ free (tmp2);
+ if (diffout)
+ {
+ if (unlink_file (diffout) < 0)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot remove temp file %s", diffout);
+ }
+ free (diffout);
+ }
+ free (xrev1);
+ free (xrev2);
+ noexec = save_noexec;
+ }
+
+ return retval;
+}
+
+/* Diff revisions and/or files. OPTS controls the format of the diff
+ (it contains options such as "-w -c", &c), or "" for the default.
+ OPTIONS controls keyword expansion, as a string starting with "-k",
+ or "" to use the default. REV1 is the first revision to compare
+ against; it must be non-NULL. If REV2 is non-NULL, compare REV1
+ and REV2; if REV2 is NULL compare REV1 with the file in the working
+ directory, whose name is WORKFILE. LABEL1 and LABEL2 are default
+ file labels, and (if non-NULL) should be added as -L options
+ to diff. Output goes to stdout.
+
+ Return value is 0 for success, -1 for a failure which set errno,
+ or positive for a failure which printed a message on stderr.
+
+ This used to exec rcsdiff, but now calls RCS_checkout and diff_exec.
+
+ An issue is what timezone is used for the dates which appear in the
+ diff output. rcsdiff uses the -z flag, which is not presently
+ processed by CVS diff, but I'm not sure exactly how hard to worry
+ about this--any such features are undocumented in the context of
+ CVS, and I'm not sure how important to users. */
+int
+RCS_exec_rcsdiff (rcsfile, opts, options, rev1, rev2, label1, label2, workfile)
+ RCSNode *rcsfile;
+ char *opts;
+ char *options;
+ char *rev1;
+ char *rev2;
+ char *label1;
+ char *label2;
+ char *workfile;
+{
+ char *tmpfile1;
+ char *tmpfile2;
+ char *use_file2;
+ int status, retval;
+
+ tmpfile1 = cvs_temp_name ();
+ tmpfile2 = NULL;
+
+ cvs_output ("\
+===================================================================\n\
+RCS file: ", 0);
+ cvs_output (rcsfile->path, 0);
+ cvs_output ("\n", 1);
+
+ /* Historically, `cvs diff' has expanded the $Name keyword to the
+ empty string when checking out revisions. This is an accident,
+ but no one has considered the issue thoroughly enough to determine
+ what the best behavior is. Passing NULL for the `nametag' argument
+ preserves the existing behavior. */
+
+ cvs_output ("retrieving revision ", 0);
+ cvs_output (rev1, 0);
+ cvs_output ("\n", 1);
+ status = RCS_checkout (rcsfile, NULL, rev1, NULL, options, tmpfile1,
+ (RCSCHECKOUTPROC)0, NULL);
+ if (status > 0)
+ {
+ retval = status;
+ goto error_return;
+ }
+ else if (status < 0)
+ {
+ error (0, errno,
+ "cannot check out revision %s of %s", rev1, rcsfile->path);
+ retval = 1;
+ goto error_return;
+ }
+
+ if (rev2 == NULL)
+ {
+ assert (workfile != NULL);
+ use_file2 = workfile;
+ }
+ else
+ {
+ tmpfile2 = cvs_temp_name ();
+ cvs_output ("retrieving revision ", 0);
+ cvs_output (rev2, 0);
+ cvs_output ("\n", 1);
+ status = RCS_checkout (rcsfile, NULL, rev2, NULL, options,
+ tmpfile2, (RCSCHECKOUTPROC)0, NULL);
+ if (status > 0)
+ {
+ retval = status;
+ goto error_return;
+ }
+ else if (status < 0)
+ {
+ error (0, errno,
+ "cannot check out revision %s of %s", rev2, rcsfile->path);
+ return 1;
+ }
+ use_file2 = tmpfile2;
+ }
+
+ RCS_output_diff_options (opts, rev1, rev2, workfile);
+ status = diff_execv (tmpfile1, use_file2, label1, label2, opts, RUN_TTY);
+ if (status >= 0)
+ {
+ retval = status;
+ goto error_return;
+ }
+ else if (status < 0)
+ {
+ error (0, errno,
+ "cannot diff %s and %s", tmpfile1, use_file2);
+ retval = 1;
+ goto error_return;
+ }
+
+ error_return:
+ {
+ int save_noexec = noexec;
+ noexec = 0;
+ if (unlink_file (tmpfile1) < 0)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot remove temp file %s", tmpfile1);
+ }
+ noexec = save_noexec;
+ }
+ free (tmpfile1);
+ if (tmpfile2 != NULL)
+ {
+ int save_noexec = noexec;
+ noexec = 0;
+ if (unlink_file (tmpfile2) < 0)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot remove temp file %s", tmpfile2);
+ }
+ noexec = save_noexec;
+ free (tmpfile2);
+ }
+
+ return retval;
+}
+
+
+/* Show differences between two files. This is the start of a diff library.
+
+ Some issues:
+
+ * Should option parsing be part of the library or the caller? The
+ former allows the library to add options without changing the callers,
+ but it causes various problems. One is that something like --brief really
+ wants special handling in CVS, and probably the caller should retain
+ some flexibility in this area. Another is online help (the library could
+ have some feature for providing help, but how does that interact with
+ the help provided by the caller directly?). Another is that as things
+ stand currently, there is no separate namespace for diff options versus
+ "cvs diff" options like -l (that is, if the library adds an option which
+ conflicts with a CVS option, it is trouble).
+
+ * This isn't required for a first-cut diff library, but if there
+ would be a way for the caller to specify the timestamps that appear
+ in the diffs (rather than the library getting them from the files),
+ that would clean up the kludgy utime() calls in patch.c.
+
+ Show differences between FILE1 and FILE2. Either one can be
+ DEVNULL to indicate a nonexistent file (same as an empty file
+ currently, I suspect, but that may be an issue in and of itself).
+ OPTIONS is a list of diff options, or "" if none. At a minimum,
+ CVS expects that -c (update.c, patch.c) and -n (update.c) will be
+ supported. Other options, like -u, --speed-large-files, &c, will
+ be specified if the user specified them.
+
+ OUT is a filename to send the diffs to, or RUN_TTY to send them to
+ stdout. Error messages go to stderr. Return value is 0 for
+ success, -1 for a failure which set errno, 1 for success (and some
+ differences were found), or >1 for a failure which printed a
+ message on stderr. */
+
+int
+diff_exec (file1, file2, options, out)
+ char *file1;
+ char *file2;
+ char *options;
+ char *out;
+{
+ char *args;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* If either file1 or file2 are special files, pretend they are
+ /dev/null. Reason: suppose a file that represents a block
+ special device in one revision becomes a regular file. CVS
+ must find the `difference' between these files, but a special
+ file contains no data useful for calculating this metric. The
+ safe thing to do is to treat the special file as an empty file,
+ thus recording the regular file's full contents. Doing so will
+ create extremely large deltas at the point of transition
+ between device files and regular files, but this is probably
+ very rare anyway.
+
+ There may be ways around this, but I think they are fraught
+ with danger. -twp */
+
+ if (preserve_perms &&
+ strcmp (file1, DEVNULL) != 0 &&
+ strcmp (file2, DEVNULL) != 0)
+ {
+ struct stat sb1, sb2;
+
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot get file information for %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot get file information for %s", file2);
+
+ if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
+ file1 = DEVNULL;
+ if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
+ file2 = DEVNULL;
+ }
+#endif
+
+ args = xmalloc (strlen (options) + 10);
+ /* The first word in this string is used only for error reporting. */
+ sprintf (args, "diff %s", options);
+ call_diff_setup (args);
+ call_diff_arg (file1);
+ call_diff_arg (file2);
+ free (args);
+
+ return call_diff (out);
+}
+
+int
+diff_execv (file1, file2, label1, label2, options, out)
+ char *file1;
+ char *file2;
+ char *label1;
+ char *label2;
+ char *options;
+ char *out;
+{
+ char *args;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ /* Pretend that special files are /dev/null for purposes of making
+ diffs. See comments in diff_exec. */
+
+ if (preserve_perms &&
+ strcmp (file1, DEVNULL) != 0 &&
+ strcmp (file2, DEVNULL) != 0)
+ {
+ struct stat sb1, sb2;
+
+ if (CVS_LSTAT (file1, &sb1) < 0)
+ error (1, errno, "cannot get file information for %s", file1);
+ if (CVS_LSTAT (file2, &sb2) < 0)
+ error (1, errno, "cannot get file information for %s", file2);
+
+ if (!S_ISREG (sb1.st_mode) && !S_ISDIR (sb1.st_mode))
+ file1 = DEVNULL;
+ if (!S_ISREG (sb2.st_mode) && !S_ISDIR (sb2.st_mode))
+ file2 = DEVNULL;
+ }
+#endif
+
+ args = xmalloc (strlen (options) + 10);
+ /* The first word in this string is used only for error reporting. */
+ /* I guess we are pretty confident that options starts with a space. */
+ sprintf (args, "diff%s", options);
+ call_diff_setup (args);
+ if (label1)
+ call_diff_arg (label1);
+ if (label2)
+ call_diff_arg (label2);
+ call_diff_arg (file1);
+ call_diff_arg (file2);
+ free (args);
+
+ return call_diff (out);
+}
+
+/* Print the options passed to DIFF, in the format used by rcsdiff.
+ The rcsdiff code that produces this output is extremely hairy, and
+ it is not clear how rcsdiff decides which options to print and
+ which not to print. The code below reproduces every rcsdiff run
+ that I have seen. */
+
+static void
+RCS_output_diff_options (opts, rev1, rev2, workfile)
+ char *opts;
+ char *rev1;
+ char *rev2;
+ char *workfile;
+{
+ char *tmp;
+
+ tmp = (char *) xmalloc (strlen (opts) + strlen (rev1) + 10);
+
+ sprintf (tmp, "diff%s -r%s", opts, rev1);
+ cvs_output (tmp, 0);
+ free (tmp);
+
+ if (rev2)
+ {
+ cvs_output (" -r", 3);
+ cvs_output (rev2, 0);
+ }
+ else
+ {
+ assert (workfile != NULL);
+ cvs_output (" ", 1);
+ cvs_output (workfile, 0);
+ }
+ cvs_output ("\n", 1);
+}
diff --git a/gnu/dist/cvs/src/recurse.c b/gnu/dist/cvs/src/recurse.c
new file mode 100644
index 00000000000..ca43bf995b0
--- /dev/null
+++ b/gnu/dist/cvs/src/recurse.c
@@ -0,0 +1,1209 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * General recursion handler
+ *
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+#include "fileattr.h"
+#include "edit.h"
+
+static int do_dir_proc PROTO((Node * p, void *closure));
+static int do_file_proc PROTO((Node * p, void *closure));
+static void addlist PROTO((List ** listp, char *key));
+static int unroll_files_proc PROTO((Node *p, void *closure));
+static void addfile PROTO((List **listp, char *dir, char *file));
+
+static char *update_dir;
+static char *repository = NULL;
+static List *filelist = NULL; /* holds list of files on which to operate */
+static List *dirlist = NULL; /* holds list of directories on which to operate */
+
+struct recursion_frame {
+ FILEPROC fileproc;
+ FILESDONEPROC filesdoneproc;
+ DIRENTPROC direntproc;
+ DIRLEAVEPROC dirleaveproc;
+ void *callerdat;
+ Dtype flags;
+ int which;
+ int aflag;
+ int readlock;
+ int dosrcs;
+};
+
+static int do_recursion PROTO ((struct recursion_frame *frame));
+
+/* I am half tempted to shove a struct file_info * into the struct
+ recursion_frame (but then we would need to modify or create a
+ recursion_frame for each file), or shove a struct recursion_frame *
+ into the struct file_info (more tempting, although it isn't completely
+ clear that the struct file_info should contain info about recursion
+ processor internals). So instead use this struct. */
+
+struct frame_and_file {
+ struct recursion_frame *frame;
+ struct file_info *finfo;
+};
+
+/* Similarly, we need to pass the entries list to do_dir_proc. */
+
+struct frame_and_entries {
+ struct recursion_frame *frame;
+ List *entries;
+};
+
+
+/* Start a recursive command.
+
+ Command line arguments (ARGC, ARGV) dictate the directories and
+ files on which we operate. In the special case of no arguments, we
+ default to ".". */
+int
+start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, callerdat,
+ argc, argv, local, which, aflag, readlock,
+ update_preload, dosrcs)
+ FILEPROC fileproc;
+ FILESDONEPROC filesdoneproc;
+ DIRENTPROC direntproc;
+ DIRLEAVEPROC dirleaveproc;
+ void *callerdat;
+
+ int argc;
+ char **argv;
+ int local;
+
+ /* This specifies the kind of recursion. There are several cases:
+
+ 1. W_LOCAL is not set but W_REPOS or W_ATTIC is. The current
+ directory when we are called must be the repository and
+ recursion proceeds according to what exists in the repository.
+
+ 2a. W_LOCAL is set but W_REPOS and W_ATTIC are not. The
+ current directory when we are called must be the working
+ directory. Recursion proceeds according to what exists in the
+ working directory, never (I think) consulting any part of the
+ repository which does not correspond to the working directory
+ ("correspond" == Name_Repository).
+
+ 2b. W_LOCAL is set and so is W_REPOS or W_ATTIC. This is the
+ weird one. The current directory when we are called must be
+ the working directory. We recurse through working directories,
+ but we recurse into a directory if it is exists in the working
+ directory *or* it exists in the repository. If a directory
+ does not exist in the working directory, the direntproc must
+ either tell us to skip it (R_SKIP_ALL), or must create it (I
+ think those are the only two cases). */
+ int which;
+
+ int aflag;
+ int readlock;
+ char *update_preload;
+ int dosrcs;
+{
+ int i, err = 0;
+#ifdef CLIENT_SUPPORT
+ List *args_to_send_when_finished = NULL;
+#endif
+ List *files_by_dir = NULL;
+ struct recursion_frame frame;
+
+ frame.fileproc = fileproc;
+ frame.filesdoneproc = filesdoneproc;
+ frame.direntproc = direntproc;
+ frame.dirleaveproc = dirleaveproc;
+ frame.callerdat = callerdat;
+ frame.flags = local ? R_SKIP_DIRS : R_PROCESS;
+ frame.which = which;
+ frame.aflag = aflag;
+ frame.readlock = readlock;
+ frame.dosrcs = dosrcs;
+
+ expand_wild (argc, argv, &argc, &argv);
+
+ if (update_preload == NULL)
+ update_dir = xstrdup ("");
+ else
+ update_dir = xstrdup (update_preload);
+
+ /* clean up from any previous calls to start_recursion */
+ if (repository)
+ {
+ free (repository);
+ repository = (char *) NULL;
+ }
+ if (filelist)
+ dellist (&filelist); /* FIXME-krp: no longer correct. */
+ if (dirlist)
+ dellist (&dirlist);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ for (i = 0; i < argc; ++i)
+ server_pathname_check (argv[i]);
+ }
+#endif
+
+ if (argc == 0)
+ {
+ int just_subdirs = (which & W_LOCAL) && !isdir (CVSADM);
+
+#ifdef CLIENT_SUPPORT
+ if (!just_subdirs
+ && CVSroot_cmdline == NULL
+ && client_active)
+ {
+ char *root = Name_Root (NULL, update_dir);
+ if (root && strcmp (root, current_root) != 0)
+ /* We're skipping this directory because it is for
+ a different root. Therefore, we just want to
+ do the subdirectories only. Processing files would
+ cause a working directory from one repository to be
+ processed against a different repository, which could
+ cause all kinds of spurious conflicts and such.
+
+ Question: what about the case of "cvs update foo"
+ where we process foo/bar and not foo itself? That
+ seems to be handled somewhere (else) but why should
+ it be a separate case? Needs investigation... */
+ just_subdirs = 1;
+ }
+#endif
+
+ /*
+ * There were no arguments, so we'll probably just recurse. The
+ * exception to the rule is when we are called from a directory
+ * without any CVS administration files. That has always meant to
+ * process each of the sub-directories, so we pretend like we were
+ * called with the list of sub-dirs of the current dir as args
+ */
+ if (just_subdirs)
+ {
+ dirlist = Find_Directories ((char *) NULL, W_LOCAL, (List *) NULL);
+ /* If there are no sub-directories, there is a certain logic in
+ favor of doing nothing, but in fact probably the user is just
+ confused about what directory they are in, or whether they
+ cvs add'd a new directory. In the case of at least one
+ sub-directory, at least when we recurse into them we
+ notice (hopefully) whether they are under CVS control. */
+ if (list_isempty (dirlist))
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0, "in directory .:");
+ else
+ error (0, 0, "in directory %s:", update_dir);
+ error (1, 0,
+ "there is no version here; run '%s checkout' first",
+ program_name);
+ }
+#ifdef CLIENT_SUPPORT
+ else if (client_active && server_started)
+ {
+ /* In the the case "cvs update foo bar baz", a call to
+ send_file_names in update.c will have sent the
+ appropriate "Argument" commands to the server. In
+ this case, that won't have happened, so we need to
+ do it here. While this example uses "update", this
+ generalizes to other commands. */
+
+ /* This is the same call to Find_Directories as above.
+ FIXME: perhaps it would be better to write a
+ function that duplicates a list. */
+ args_to_send_when_finished = Find_Directories ((char *) NULL,
+ W_LOCAL,
+ (List *) NULL);
+ }
+#endif
+ }
+ else
+ addlist (&dirlist, ".");
+
+ goto do_the_work;
+ }
+
+
+ /*
+ * There were arguments, so we have to handle them by hand. To do
+ * that, we set up the filelist and dirlist with the arguments and
+ * call do_recursion. do_recursion recognizes the fact that the
+ * lists are non-null when it starts and doesn't update them.
+ *
+ * explicitly named directories are stored in dirlist.
+ * explicitly named files are stored in filelist.
+ * other possibility is named entities whicha are not currently in
+ * the working directory.
+ */
+
+ for (i = 0; i < argc; i++)
+ {
+ /* if this argument is a directory, then add it to the list of
+ directories. */
+
+ if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
+ addlist (&dirlist, argv[i]);
+ else
+ {
+ /* otherwise, split argument into directory and component names. */
+ char *dir;
+ char *comp;
+ char *file_to_try;
+
+ /* Now break out argv[i] into directory part (DIR) and file part (COMP).
+ DIR and COMP will each point to a newly malloc'd string. */
+ dir = xstrdup (argv[i]);
+ comp = last_component (dir);
+ if (comp == dir)
+ {
+ /* no dir component. What we have is an implied "./" */
+ dir = xstrdup(".");
+ }
+ else
+ {
+ char *p = comp;
+
+ p[-1] = '\0';
+ comp = xstrdup (p);
+ }
+
+ /* if this argument exists as a file in the current
+ working directory tree, then add it to the files list. */
+
+ if (!(which & W_LOCAL))
+ {
+ /* If doing rtag, we've done a chdir to the repository. */
+ file_to_try = xmalloc (strlen (argv[i]) + sizeof (RCSEXT) + 5);
+ sprintf (file_to_try, "%s%s", argv[i], RCSEXT);
+ }
+ else
+ file_to_try = xstrdup (argv[i]);
+
+ if (isfile (file_to_try))
+ addfile (&files_by_dir, dir, comp);
+ else if (isdir (dir))
+ {
+ if ((which & W_LOCAL) && isdir (CVSADM)
+#ifdef CLIENT_SUPPORT
+ && !client_active
+#endif
+ )
+ {
+ /* otherwise, look for it in the repository. */
+ char *tmp_update_dir;
+ char *repos;
+ char *reposfile;
+
+ tmp_update_dir = xmalloc (strlen (update_dir)
+ + strlen (dir)
+ + 5);
+ strcpy (tmp_update_dir, update_dir);
+
+ if (*tmp_update_dir != '\0')
+ (void) strcat (tmp_update_dir, "/");
+
+ (void) strcat (tmp_update_dir, dir);
+
+ /* look for it in the repository. */
+ repos = Name_Repository (dir, tmp_update_dir);
+ reposfile = xmalloc (strlen (repos)
+ + strlen (comp)
+ + 5);
+ (void) sprintf (reposfile, "%s/%s", repos, comp);
+ free (repos);
+
+ if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
+ addlist (&dirlist, argv[i]);
+ else
+ addfile (&files_by_dir, dir, comp);
+
+ free (tmp_update_dir);
+ free (reposfile);
+ }
+ else
+ addfile (&files_by_dir, dir, comp);
+ }
+ else
+ error (1, 0, "no such directory `%s'", dir);
+
+ free (file_to_try);
+ free (dir);
+ free (comp);
+ }
+ }
+
+ /* At this point we have looped over all named arguments and built
+ a coupla lists. Now we unroll the lists, setting up and
+ calling do_recursion. */
+
+ err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
+ dellist(&files_by_dir);
+
+ /* then do_recursion on the dirlist. */
+ if (dirlist != NULL)
+ {
+ do_the_work:
+ err += do_recursion (&frame);
+ }
+
+ /* Free the data which expand_wild allocated. */
+ free_names (&argc, argv);
+
+ free (update_dir);
+ update_dir = NULL;
+
+#ifdef CLIENT_SUPPORT
+ if (args_to_send_when_finished != NULL)
+ {
+ /* FIXME (njc): in the multiroot case, we don't want to send
+ argument commands for those top-level directories which do
+ not contain any subdirectories which have files checked out
+ from current_root. If we do, and two repositories have a
+ module with the same name, nasty things could happen.
+
+ This is hard. Perhaps we should send the Argument commands
+ later in this procedure, after we've had a chance to notice
+ which directores we're using (after do_recursion has been
+ called once). This means a _lot_ of rewriting, however.
+
+ What we need to do for that to happen is descend the tree
+ and construct a list of directories which are checked out
+ from current_cvsroot. Now, we eliminate from the list all
+ of those directories which are immediate subdirectories of
+ another directory in the list. To say that the opposite
+ way, we keep the directories which are not immediate
+ subdirectories of any other in the list. Here's a picture:
+
+ a
+ / \
+ B C
+ / \
+ D e
+ / \
+ F G
+ / \
+ H I
+
+ The node in capitals are those directories which are
+ checked out from current_cvsroot. We want the list to
+ contain B, C, F, and G. D, H, and I are not included,
+ because their parents are also checked out from
+ current_cvsroot.
+
+ The algorithm should be:
+
+ 1) construct a tree of all directory names where each
+ element contains a directory name and a flag which notes if
+ that directory is checked out from current_cvsroot
+
+ a0
+ / \
+ B1 C1
+ / \
+ D1 e0
+ / \
+ F1 G1
+ / \
+ H1 I1
+
+ 2) Recursively descend the tree. For each node, recurse
+ before processing the node. If the flag is zero, do
+ nothing. If the flag is 1, check the node's parent. If
+ the parent's flag is one, change the current entry's flag
+ to zero.
+
+ a0
+ / \
+ B1 C1
+ / \
+ D0 e0
+ / \
+ F1 G1
+ / \
+ H0 I0
+
+ 3) Walk the tree and spit out "Argument" commands to tell
+ the server which directories to munge.
+
+ Yuck. It's not clear this is worth spending time on, since
+ we might want to disable cvs commands entirely from
+ directories that do not have CVSADM files...
+
+ Anyways, the solution as it stands has modified server.c
+ (dirswitch) to create admin files [via server.c
+ (create_adm_p)] in all path elements for a client's
+ "Directory xxx" command, which forces the server to descend
+ and serve the files there. client.c (send_file_names) has
+ also been modified to send only those arguments which are
+ appropriate to current_root.
+
+ */
+
+ /* Construct a fake argc/argv pair. */
+
+ int our_argc = 0, i;
+ char **our_argv = NULL;
+
+ if (! list_isempty (args_to_send_when_finished))
+ {
+ Node *head, *p;
+
+ head = args_to_send_when_finished->list;
+
+ /* count the number of nodes */
+ i = 0;
+ for (p = head->next; p != head; p = p->next)
+ i++;
+ our_argc = i;
+
+ /* create the argument vector */
+ our_argv = (char **) xmalloc (sizeof (char *) * our_argc);
+
+ /* populate it */
+ i = 0;
+ for (p = head->next; p != head; p = p->next)
+ our_argv[i++] = xstrdup (p->key);
+ }
+
+ /* We don't want to expand widcards, since we've just created
+ a list of directories directly from the filesystem. */
+ send_file_names (our_argc, our_argv, 0);
+
+ /* Free our argc/argv. */
+ if (our_argv != NULL)
+ {
+ for (i = 0; i < our_argc; i++)
+ free (our_argv[i]);
+ free (our_argv);
+ }
+
+ dellist (&args_to_send_when_finished);
+ }
+#endif
+
+ return (err);
+}
+
+/*
+ * Implement the recursive policies on the local directory. This may be
+ * called directly, or may be called by start_recursion
+ */
+static int
+do_recursion (frame)
+ struct recursion_frame *frame;
+{
+ int err = 0;
+ int dodoneproc = 1;
+ char *srepository;
+ List *entries = NULL;
+ int should_readlock;
+ int process_this_directory = 1;
+
+ /* do nothing if told */
+ if (frame->flags == R_SKIP_ALL)
+ return (0);
+
+ should_readlock = noexec ? 0 : frame->readlock;
+
+ /* The fact that locks are not active here is what makes us fail to have
+ the
+
+ If someone commits some changes in one cvs command,
+ then an update by someone else will either get all the
+ changes, or none of them.
+
+ property (see node Concurrency in cvs.texinfo).
+
+ The most straightforward fix would just to readlock the whole
+ tree before starting an update, but that means that if a commit
+ gets blocked on a big update, it might need to wait a *long*
+ time.
+
+ A more adequate fix would be a two-pass design for update,
+ checkout, etc. The first pass would go through the repository,
+ with the whole tree readlocked, noting what versions of each
+ file we want to get. The second pass would release all locks
+ (except perhaps short-term locks on one file at a
+ time--although I think RCS already deals with this) and
+ actually get the files, specifying the particular versions it wants.
+
+ This could be sped up by separating out the data needed for the
+ first pass into a separate file(s)--for example a file
+ attribute for each file whose value contains the head revision
+ for each branch. The structure should be designed so that
+ commit can relatively quickly update the information for a
+ single file or a handful of files (file attributes, as
+ implemented in Jan 96, are probably acceptable; improvements
+ would be possible such as branch attributes which are in
+ separate files for each branch). */
+
+#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
+ /*
+ * Now would be a good time to check to see if we need to stop
+ * generating data, to give the buffers a chance to drain to the
+ * remote client. We should not have locks active at this point.
+ */
+ if (server_active
+ /* If there are writelocks around, we cannot pause here. */
+ && (should_readlock || noexec))
+ server_pause_check();
+#endif
+
+ /* Check the value in CVSADM_ROOT and see if it's in the list. If
+ not, add it to our lists of CVS/Root directories and do not
+ process the files in this directory. Otherwise, continue as
+ usual. THIS_ROOT might be NULL if we're doing an initial
+ checkout -- check before using it. The default should be that
+ we process a directory's contents and only skip those contents
+ if a CVS/Root file exists.
+
+ If we're running the server, we want to process all
+ directories, since we're guaranteed to have only one CVSROOT --
+ our own. */
+
+ if (
+ /* If -d was specified, it should override CVS/Root.
+
+ In the single-repository case, it is long-standing CVS behavior
+ and makes sense - the user might want another access method,
+ another server (which mounts the same repository), &c.
+
+ In the multiple-repository case, -d overrides all CVS/Root
+ files. That is the only plausible generalization I can
+ think of. */
+ CVSroot_cmdline == NULL
+
+#ifdef SERVER_SUPPORT
+ && ! server_active
+#endif
+ )
+ {
+ char *this_root = Name_Root ((char *) NULL, update_dir);
+ if (this_root != NULL)
+ {
+ if (findnode (root_directories, this_root) == NULL)
+ {
+ /* Add it to our list. */
+
+ Node *n = getnode ();
+ n->type = NT_UNKNOWN;
+ n->key = xstrdup (this_root);
+
+ if (addnode (root_directories, n))
+ error (1, 0, "cannot add new CVSROOT %s", this_root);
+
+ }
+
+ process_this_directory = (strcmp (current_root, this_root) == 0);
+
+ free (this_root);
+ }
+ }
+
+ /*
+ * Fill in repository with the current repository
+ */
+ if (frame->which & W_LOCAL)
+ {
+ if (isdir (CVSADM))
+ repository = Name_Repository ((char *) NULL, update_dir);
+ else
+ repository = NULL;
+ }
+ else
+ {
+ repository = xgetwd ();
+ if (repository == NULL)
+ error (1, errno, "could not get working directory");
+ }
+ srepository = repository; /* remember what to free */
+
+ fileattr_startdir (repository);
+
+ /*
+ * The filesdoneproc needs to be called for each directory where files
+ * processed, or each directory that is processed by a call where no
+ * directories were passed in. In fact, the only time we don't want to
+ * call back the filesdoneproc is when we are processing directories that
+ * were passed in on the command line (or in the special case of `.' when
+ * we were called with no args
+ */
+ if (dirlist != NULL && filelist == NULL)
+ dodoneproc = 0;
+
+ /*
+ * If filelist or dirlist is already set, we don't look again. Otherwise,
+ * find the files and directories
+ */
+ if (filelist == NULL && dirlist == NULL)
+ {
+ /* both lists were NULL, so start from scratch */
+ if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
+ {
+ int lwhich = frame->which;
+
+ /* be sure to look in the attic if we have sticky tags/date */
+ if ((lwhich & W_ATTIC) == 0)
+ if (isreadable (CVSADM_TAG))
+ lwhich |= W_ATTIC;
+
+ /* In the !(which & W_LOCAL) case, we filled in repository
+ earlier in the function. In the (which & W_LOCAL) case,
+ the Find_Names function is going to look through the
+ Entries file. If we do not have a repository, that
+ does not make sense, so we insist upon having a
+ repository at this point. Name_Repository will give a
+ reasonable error message. */
+ if (repository == NULL)
+ repository = Name_Repository ((char *) NULL, update_dir);
+
+ /* find the files and fill in entries if appropriate */
+ if (process_this_directory)
+ {
+ filelist = Find_Names (repository, lwhich, frame->aflag,
+ &entries);
+ if (filelist == NULL)
+ {
+ error (0, 0, "skipping directory %s", update_dir);
+ /* Note that Find_Directories and the filesdoneproc
+ in particular would do bad things ("? foo.c" in
+ the case of some filesdoneproc's). */
+ goto skip_directory;
+ }
+ }
+ }
+
+ /* find sub-directories if we will recurse */
+ if (frame->flags != R_SKIP_DIRS)
+ dirlist = Find_Directories (
+ process_this_directory ? repository : NULL,
+ frame->which, entries);
+ }
+ else
+ {
+ /* something was passed on the command line */
+ if (filelist != NULL && frame->fileproc != NULL)
+ {
+ /* we will process files, so pre-parse entries */
+ if (frame->which & W_LOCAL)
+ entries = Entries_Open (frame->aflag, NULL);
+ }
+ }
+
+ /* process the files (if any) */
+ if (process_this_directory && filelist != NULL && frame->fileproc)
+ {
+ struct file_info finfo_struct;
+ struct frame_and_file frfile;
+
+ /* read lock it if necessary */
+ if (should_readlock && repository && Reader_Lock (repository) != 0)
+ error (1, 0, "read lock failed - giving up");
+
+#ifdef CLIENT_SUPPORT
+ /* For the server, we handle notifications in a completely different
+ place (server_notify). For local, we can't do them here--we don't
+ have writelocks in place, and there is no way to get writelocks
+ here. */
+ if (client_active)
+ notify_check (repository, update_dir);
+#endif /* CLIENT_SUPPORT */
+
+ finfo_struct.repository = repository;
+ finfo_struct.update_dir = update_dir;
+ finfo_struct.entries = entries;
+ /* do_file_proc will fill in finfo_struct.file. */
+
+ frfile.finfo = &finfo_struct;
+ frfile.frame = frame;
+
+ /* process the files */
+ err += walklist (filelist, do_file_proc, &frfile);
+
+ /* unlock it */
+ if (should_readlock)
+ Lock_Cleanup ();
+
+ /* clean up */
+ dellist (&filelist);
+ }
+
+ /* call-back files done proc (if any) */
+ if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL)
+ err = frame->filesdoneproc (frame->callerdat, err, repository,
+ update_dir[0] ? update_dir : ".",
+ entries);
+
+ skip_directory:
+ fileattr_write ();
+ fileattr_free ();
+
+ /* process the directories (if necessary) */
+ if (dirlist != NULL)
+ {
+ struct frame_and_entries frent;
+
+ frent.frame = frame;
+ frent.entries = entries;
+ err += walklist (dirlist, do_dir_proc, (void *) &frent);
+ }
+#if 0
+ else if (frame->dirleaveproc != NULL)
+ err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
+#endif
+ dellist (&dirlist);
+
+ if (entries)
+ {
+ Entries_Close (entries);
+ entries = NULL;
+ }
+
+ /* free the saved copy of the pointer if necessary */
+ if (srepository)
+ {
+ free (srepository);
+ repository = (char *) NULL;
+ }
+
+ return (err);
+}
+
+/*
+ * Process each of the files in the list with the callback proc
+ */
+static int
+do_file_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct frame_and_file *frfile = (struct frame_and_file *)closure;
+ struct file_info *finfo = frfile->finfo;
+ int ret;
+
+ finfo->file = p->key;
+ finfo->fullname = xmalloc (strlen (finfo->file)
+ + strlen (finfo->update_dir)
+ + 2);
+ finfo->fullname[0] = '\0';
+ if (finfo->update_dir[0] != '\0')
+ {
+ strcat (finfo->fullname, finfo->update_dir);
+ strcat (finfo->fullname, "/");
+ }
+ strcat (finfo->fullname, finfo->file);
+
+ if (frfile->frame->dosrcs && repository)
+ {
+ finfo->rcs = RCS_parse (finfo->file, repository);
+
+ /* OK, without W_LOCAL the error handling becomes relatively
+ simple. The file names came from readdir() on the
+ repository and so we know any ENOENT is an error
+ (e.g. symlink pointing to nothing). Now, the logic could
+ be simpler - since we got the name from readdir, we could
+ just be calling RCS_parsercsfile. */
+ if (finfo->rcs == NULL
+ && !(frfile->frame->which & W_LOCAL))
+ {
+ error (0, 0, "could not read RCS file for %s", finfo->fullname);
+ free (finfo->fullname);
+ cvs_flushout ();
+ return 0;
+ }
+ }
+ else
+ finfo->rcs = (RCSNode *) NULL;
+ ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
+
+ freercsnode(&finfo->rcs);
+ free (finfo->fullname);
+
+ /* Allow the user to monitor progress with tail -f. Doing this once
+ per file should be no big deal, but we don't want the performance
+ hit of flushing on every line like previous versions of CVS. */
+ cvs_flushout ();
+
+ return (ret);
+}
+
+/*
+ * Process each of the directories in the list (recursing as we go)
+ */
+static int
+do_dir_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct frame_and_entries *frent = (struct frame_and_entries *) closure;
+ struct recursion_frame *frame = frent->frame;
+ struct recursion_frame xframe;
+ char *dir = p->key;
+ char *newrepos;
+ List *sdirlist;
+ char *srepository;
+ Dtype dir_return = R_PROCESS;
+ int stripped_dot = 0;
+ int err = 0;
+ struct saved_cwd cwd;
+ char *saved_update_dir;
+ int process_this_directory = 1;
+
+ if (fncmp (dir, CVSADM) == 0)
+ {
+ /* This seems to most often happen when users (beginning users,
+ generally), try "cvs ci *" or something similar. On that
+ theory, it is possible that we should just silently skip the
+ CVSADM directories, but on the other hand, using a wildcard
+ like this isn't necessarily a practice to encourage (it operates
+ only on files which exist in the working directory, unlike
+ regular CVS recursion). */
+
+ /* FIXME-reentrancy: printed_cvs_msg should be in a "command
+ struct" or some such, so that it gets cleared for each new
+ command (this is possible using the remote protocol and a
+ custom-written client). The struct recursion_frame is not
+ far back enough though, some commands (commit at least)
+ will call start_recursion several times. An alternate solution
+ would be to take this whole check and move it to a new function
+ validate_arguments or some such that all the commands call
+ and which snips the offending directory from the argc,argv
+ vector. */
+ static int printed_cvs_msg = 0;
+ if (!printed_cvs_msg)
+ {
+ error (0, 0, "warning: directory %s specified in argument",
+ dir);
+ error (0, 0, "\
+but CVS uses %s for its own purposes; skipping %s directory",
+ CVSADM, dir);
+ printed_cvs_msg = 1;
+ }
+ return 0;
+ }
+
+ saved_update_dir = update_dir;
+ update_dir = xmalloc (strlen (saved_update_dir)
+ + strlen (dir)
+ + 5);
+ strcpy (update_dir, saved_update_dir);
+
+ /* set up update_dir - skip dots if not at start */
+ if (strcmp (dir, ".") != 0)
+ {
+ if (update_dir[0] != '\0')
+ {
+ (void) strcat (update_dir, "/");
+ (void) strcat (update_dir, dir);
+ }
+ else
+ (void) strcpy (update_dir, dir);
+
+ /*
+ * Here we need a plausible repository name for the sub-directory. We
+ * create one by concatenating the new directory name onto the
+ * previous repository name. The only case where the name should be
+ * used is in the case where we are creating a new sub-directory for
+ * update -d and in that case the generated name will be correct.
+ */
+ if (repository == NULL)
+ newrepos = xstrdup ("");
+ else
+ {
+ newrepos = xmalloc (strlen (repository) + strlen (dir) + 5);
+ sprintf (newrepos, "%s/%s", repository, dir);
+ }
+ }
+ else
+ {
+ if (update_dir[0] == '\0')
+ (void) strcpy (update_dir, dir);
+
+ if (repository == NULL)
+ newrepos = xstrdup ("");
+ else
+ newrepos = xstrdup (repository);
+ }
+
+ /* Check to see that the CVSADM directory, if it exists, seems to be
+ well-formed. It can be missing files if the user hit ^C in the
+ middle of a previous run. We want to (a) make this a nonfatal
+ error, and (b) make sure we print which directory has the
+ problem.
+
+ Do this before the direntproc, so that (1) the direntproc
+ doesn't have to guess/deduce whether we will skip the directory
+ (e.g. send_dirent_proc and whether to send the directory), and
+ (2) so that the warm fuzzy doesn't get printed if we skip the
+ directory. */
+ if (frame->which & W_LOCAL)
+ {
+ char *cvsadmdir;
+
+ cvsadmdir = xmalloc (strlen (dir)
+ + sizeof (CVSADM_REP)
+ + sizeof (CVSADM_ENT)
+ + 80);
+
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM);
+ if (isdir (cvsadmdir))
+ {
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM_REP);
+ if (!isfile (cvsadmdir))
+ {
+ /* Some commands like update may have printed "? foo" but
+ if we were planning to recurse, and don't on account of
+ CVS/Repository, we want to say why. */
+ error (0, 0, "ignoring %s (%s missing)", update_dir,
+ CVSADM_REP);
+ dir_return = R_SKIP_ALL;
+ }
+
+ /* Likewise for CVS/Entries. */
+ if (dir_return != R_SKIP_ALL)
+ {
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM_ENT);
+ if (!isfile (cvsadmdir))
+ {
+ /* Some commands like update may have printed "? foo" but
+ if we were planning to recurse, and don't on account of
+ CVS/Repository, we want to say why. */
+ error (0, 0, "ignoring %s (%s missing)", update_dir,
+ CVSADM_ENT);
+ dir_return = R_SKIP_ALL;
+ }
+ }
+ }
+ free (cvsadmdir);
+ }
+
+ /* Only process this directory if the root matches. This nearly
+ duplicates code in do_recursion. */
+
+ if (
+ /* If -d was specified, it should override CVS/Root.
+
+ In the single-repository case, it is long-standing CVS behavior
+ and makes sense - the user might want another access method,
+ another server (which mounts the same repository), &c.
+
+ In the multiple-repository case, -d overrides all CVS/Root
+ files. That is the only plausible generalization I can
+ think of. */
+ CVSroot_cmdline == NULL
+
+#ifdef SERVER_SUPPORT
+ && ! server_active
+#endif
+ )
+ {
+ char *this_root = Name_Root (dir, update_dir);
+ if (this_root != NULL)
+ {
+ if (findnode (root_directories, this_root) == NULL)
+ {
+ /* Add it to our list. */
+
+ Node *n = getnode ();
+ n->type = NT_UNKNOWN;
+ n->key = xstrdup (this_root);
+
+ if (addnode (root_directories, n))
+ error (1, 0, "cannot add new CVSROOT %s", this_root);
+
+ }
+
+ process_this_directory = (strcmp (current_root, this_root) == 0);
+ free (this_root);
+ }
+ }
+
+ /* call-back dir entry proc (if any) */
+ if (dir_return == R_SKIP_ALL)
+ ;
+ else if (frame->direntproc != NULL)
+ {
+ /* If we're doing the actual processing, call direntproc.
+ Otherwise, assume that we need to process this directory
+ and recurse. FIXME. */
+
+ if (process_this_directory)
+ dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
+ update_dir, frent->entries);
+ else
+ dir_return = R_PROCESS;
+ }
+ else
+ {
+ /* Generic behavior. I don't see a reason to make the caller specify
+ a direntproc just to get this. */
+ if ((frame->which & W_LOCAL) && !isdir (dir))
+ dir_return = R_SKIP_ALL;
+ }
+
+ free (newrepos);
+
+ /* only process the dir if the return code was 0 */
+ if (dir_return != R_SKIP_ALL)
+ {
+ /* save our current directory and static vars */
+ if (save_cwd (&cwd))
+ error_exit ();
+ sdirlist = dirlist;
+ srepository = repository;
+ dirlist = NULL;
+
+ /* cd to the sub-directory */
+ if ( CVS_CHDIR (dir) < 0)
+ error (1, errno, "could not chdir to %s", dir);
+
+ /* honor the global SKIP_DIRS (a.k.a. local) */
+ if (frame->flags == R_SKIP_DIRS)
+ dir_return = R_SKIP_DIRS;
+
+ /* remember if the `.' will be stripped for subsequent dirs */
+ if (strcmp (update_dir, ".") == 0)
+ {
+ update_dir[0] = '\0';
+ stripped_dot = 1;
+ }
+
+ /* make the recursive call */
+ xframe = *frame;
+ xframe.flags = dir_return;
+ err += do_recursion (&xframe);
+
+ /* put the `.' back if necessary */
+ if (stripped_dot)
+ (void) strcpy (update_dir, ".");
+
+ /* call-back dir leave proc (if any) */
+ if (process_this_directory && frame->dirleaveproc != NULL)
+ err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
+ frent->entries);
+
+ /* get back to where we started and restore state vars */
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+ dirlist = sdirlist;
+ repository = srepository;
+ }
+
+ free (update_dir);
+ update_dir = saved_update_dir;
+
+ return (err);
+}
+
+/*
+ * Add a node to a list allocating the list if necessary.
+ */
+static void
+addlist (listp, key)
+ List **listp;
+ char *key;
+{
+ Node *p;
+
+ if (*listp == NULL)
+ *listp = getlist ();
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (key);
+ if (addnode (*listp, p) != 0)
+ freenode (p);
+}
+
+static void
+addfile (listp, dir, file)
+ List **listp;
+ char *dir;
+ char *file;
+{
+ Node *n;
+
+ /* add this dir. */
+ addlist (listp, dir);
+
+ n = findnode (*listp, dir);
+ if (n == NULL)
+ {
+ error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
+ dir);
+ }
+
+ n->type = DIRS;
+ addlist ((List **) &n->data, file);
+ return;
+}
+
+static int
+unroll_files_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ Node *n;
+ struct recursion_frame *frame = (struct recursion_frame *) closure;
+ int err = 0;
+ List *save_dirlist;
+ char *save_update_dir = NULL;
+ struct saved_cwd cwd;
+
+ /* if this dir was also an explicitly named argument, then skip
+ it. We'll catch it later when we do dirs. */
+ n = findnode (dirlist, p->key);
+ if (n != NULL)
+ return (0);
+
+ /* otherwise, call dorecusion for this list of files. */
+ filelist = (List *) p->data;
+ p->data = NULL;
+ save_dirlist = dirlist;
+ dirlist = NULL;
+
+ if (strcmp(p->key, ".") != 0)
+ {
+ if (save_cwd (&cwd))
+ error_exit ();
+ if ( CVS_CHDIR (p->key) < 0)
+ error (1, errno, "could not chdir to %s", p->key);
+
+ save_update_dir = update_dir;
+ update_dir = xmalloc (strlen (save_update_dir)
+ + strlen (p->key)
+ + 5);
+ strcpy (update_dir, save_update_dir);
+
+ if (*update_dir != '\0')
+ (void) strcat (update_dir, "/");
+
+ (void) strcat (update_dir, p->key);
+ }
+
+ err += do_recursion (frame);
+
+ if (save_update_dir != NULL)
+ {
+ free (update_dir);
+ update_dir = save_update_dir;
+
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+ }
+
+ dirlist = save_dirlist;
+ filelist = NULL;
+ return(err);
+}
diff --git a/gnu/dist/cvs/src/release.c b/gnu/dist/cvs/src/release.c
new file mode 100644
index 00000000000..06d582f4e5b
--- /dev/null
+++ b/gnu/dist/cvs/src/release.c
@@ -0,0 +1,299 @@
+/*
+ * Release: "cancel" a checkout in the history log.
+ *
+ * - Enter a line in the history log indicating the "release". - If asked to,
+ * delete the local working directory.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+#include "getline.h"
+
+static const char *const release_usage[] =
+{
+ "Usage: %s %s [-d] directories...\n",
+ "\t-d\tDelete the given directory.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+#ifdef SERVER_SUPPORT
+static int release_server PROTO ((int argc, char **argv));
+
+/* This is the server side of cvs release. */
+static int
+release_server (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+
+ /* Note that we skip argv[0]. */
+ for (i = 1; i < argc; ++i)
+ history_write ('F', argv[i], "", argv[i], "");
+ return 0;
+}
+
+#endif /* SERVER_SUPPORT */
+
+/* There are various things to improve about this implementation:
+
+ 1. Using run_popen to run "cvs update" could be replaced by a
+ fairly simple start_recursion/classify_file loop--a win for
+ portability, performance, and cleanliness. In particular, there is
+ no particularly good way to find the right "cvs".
+
+ 2. The fact that "cvs update" contacts the server slows things down;
+ it undermines the case for using "cvs release" rather than "rm -rf".
+ However, for correctly printing "? foo" and correctly handling
+ CVSROOTADM_IGNORE, we currently need to contact the server. (One
+ idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
+ the working directories; see comment at base_* in entries.c for a
+ few thoughts on that).
+
+ 3. Would be nice to take processing things on the client side one step
+ further, and making it like edit/unedit in terms of working well if
+ disconnected from the network, and then sending a delayed
+ notification.
+
+ 4. Having separate network turnarounds for the "Notify" request
+ which we do as part of unedit, and for the "release" itself, is slow
+ and unnecessary. */
+
+int
+release (argc, argv)
+ int argc;
+ char **argv;
+{
+ FILE *fp;
+ int i, c;
+ char *repository;
+ char *line = NULL;
+ size_t line_allocated = 0;
+ char *update_cmd;
+ char *thisarg;
+ int arg_start_idx;
+ int err = 0;
+ short delete_flag = 0;
+ struct saved_cwd cwd;
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ return release_server (argc, argv);
+#endif
+
+ /* Everything from here on is client or local. */
+ if (argc == -1)
+ usage (release_usage);
+ optind = 0;
+ while ((c = getopt (argc, argv, "+Qdq")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'd':
+ delete_flag++;
+ break;
+ case '?':
+ default:
+ usage (release_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* We're going to run "cvs -n -q update" and check its output; if
+ * the output is sufficiently unalarming, then we release with no
+ * questions asked. Else we prompt, then maybe release.
+ * (Well, actually we ask no matter what. Our notion of "sufficiently
+ * unalarming" doesn't take into account "? foo.c" files, so it is
+ * up to the user to take note of them, at least currently
+ * (ignore-193 in testsuite)).
+ */
+ /* Construct the update command. */
+ update_cmd = xmalloc (strlen (program_path)
+ + strlen (CVSroot_original)
+ + 20);
+ sprintf (update_cmd, "%s -n -q -d %s update",
+ program_path, CVSroot_original);
+
+#ifdef CLIENT_SUPPORT
+ /* Start the server; we'll close it after looping. */
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ /* Remember the directory where "cvs release" was invoked because
+ all args are relative to this directory and we chdir around.
+ */
+ if (save_cwd (&cwd))
+ error_exit ();
+
+ arg_start_idx = 0;
+
+ for (i = arg_start_idx; i < argc; i++)
+ {
+ thisarg = argv[i];
+
+ if (isdir (thisarg))
+ {
+ if (CVS_CHDIR (thisarg) < 0)
+ {
+ if (!really_quiet)
+ error (0, errno, "can't chdir to: %s", thisarg);
+ continue;
+ }
+ if (!isdir (CVSADM))
+ {
+ if (!really_quiet)
+ error (0, 0, "no repository directory: %s", thisarg);
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ continue;
+ }
+ }
+ else
+ {
+ if (!really_quiet)
+ error (0, 0, "no such directory: %s", thisarg);
+ continue;
+ }
+
+ repository = Name_Repository ((char *) NULL, (char *) NULL);
+
+ if (!really_quiet)
+ {
+ int line_length;
+
+ /* The "release" command piggybacks on "update", which
+ does the real work of finding out if anything is not
+ up-to-date with the repository. Then "release" prompts
+ the user, telling her how many files have been
+ modified, and asking if she still wants to do the
+ release. */
+ fp = run_popen (update_cmd, "r");
+ if (fp == NULL)
+ error (1, 0, "cannot run command %s", update_cmd);
+
+ c = 0;
+
+ while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
+ {
+ if (strchr ("MARCZ", *line))
+ c++;
+ (void) fputs (line, stdout);
+ }
+ if (line_length < 0 && !feof (fp))
+ error (0, errno, "cannot read from subprocess");
+
+ /* If the update exited with an error, then we just want to
+ complain and go on to the next arg. Especially, we do
+ not want to delete the local copy, since it's obviously
+ not what the user thinks it is. */
+ if ((pclose (fp)) != 0)
+ {
+ error (0, 0, "unable to release `%s'", thisarg);
+ free (repository);
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ continue;
+ }
+
+ printf ("You have [%d] altered files in this repository.\n",
+ c);
+ printf ("Are you sure you want to release %sdirectory `%s': ",
+ delete_flag ? "(and delete) " : "", thisarg);
+ c = !yesno ();
+ if (c) /* "No" */
+ {
+ (void) fprintf (stderr, "** `%s' aborted by user choice.\n",
+ command_name);
+ free (repository);
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ continue;
+ }
+ }
+
+ if (1
+#ifdef CLIENT_SUPPORT
+ && !(client_active
+ && (!supported_request ("noop")
+ || !supported_request ("Notify")))
+#endif
+ )
+ {
+ /* We are chdir'ed into the directory in question.
+ So don't pass args to unedit. */
+ int argc = 1;
+ char *argv[3];
+ argv[0] = "dummy";
+ argv[1] = NULL;
+ err += unedit (argc, argv);
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ send_to_server ("Argument ", 0);
+ send_to_server (thisarg, 0);
+ send_to_server ("\012", 1);
+ send_to_server ("release\012", 0);
+ }
+ else
+#endif /* CLIENT_SUPPORT */
+ {
+ history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
+ }
+
+ free (repository);
+
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+
+ if (delete_flag)
+ {
+ /* FIXME? Shouldn't this just delete the CVS-controlled
+ files and, perhaps, the files that would normally be
+ ignored and leave everything else? */
+
+ if (unlink_file_dir (thisarg) < 0)
+ error (0, errno, "deletion of directory %s failed", thisarg);
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ err += get_server_responses ();
+#endif /* CLIENT_SUPPORT */
+ }
+
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* Unfortunately, client.c doesn't offer a way to close
+ the connection without waiting for responses. The extra
+ network turnaround here is quite unnecessary other than
+ that.... */
+ send_to_server ("noop\012", 0);
+ err += get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ free (update_cmd);
+ if (line != NULL)
+ free (line);
+ return err;
+}
diff --git a/gnu/dist/cvs/src/remove.c b/gnu/dist/cvs/src/remove.c
new file mode 100644
index 00000000000..2dacdf15988
--- /dev/null
+++ b/gnu/dist/cvs/src/remove.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Remove a File
+ *
+ * Removes entries from the present version. The entries will be removed from
+ * the RCS repository upon the next "commit".
+ *
+ * "remove" accepts no options, only file names that are to be removed. The
+ * file must not exist in the current directory for "remove" to work
+ * correctly.
+ */
+
+#include "cvs.h"
+
+#ifdef CLIENT_SUPPORT
+static int remove_force_fileproc PROTO ((void *callerdat,
+ struct file_info *finfo));
+#endif
+static int remove_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static Dtype remove_dirproc PROTO ((void *callerdat, char *dir,
+ char *repos, char *update_dir,
+ List *entries));
+
+static int force;
+static int local;
+static int removed_files;
+static int existing_files;
+
+static const char *const remove_usage[] =
+{
+ "Usage: %s %s [-flR] [files...]\n",
+ "\t-f\tDelete the file before removing it.\n",
+ "\t-l\tProcess this directory only (not recursive).\n",
+ "\t-R\tProcess directories recursively.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+cvsremove (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c, err;
+
+ if (argc == -1)
+ usage (remove_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+flR")) != -1)
+ {
+ switch (c)
+ {
+ case 'f':
+ force = 1;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (remove_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+ if (client_active) {
+ /* Call expand_wild so that the local removal of files will
+ work. It's ok to do it always because we have to send the
+ file names expanded anyway. */
+ expand_wild (argc, argv, &argc, &argv);
+
+ if (force)
+ {
+ if (!noexec)
+ {
+ start_recursion (remove_force_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ (void *) NULL, argc, argv, local, W_LOCAL,
+ 0, 0, (char *) NULL, 0);
+ }
+ /* else FIXME should probably act as if the file doesn't exist
+ in doing the following checks. */
+ }
+
+ start_server ();
+ ign_setup ();
+ if (local)
+ send_arg("-l");
+ /* FIXME: Can't we set SEND_NO_CONTENTS here? Needs investigation. */
+ send_files (argc, argv, local, 0, 0);
+ send_file_names (argc, argv, 0);
+ send_to_server ("remove\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ /* start the recursion processor */
+ err = start_recursion (remove_fileproc, (FILESDONEPROC) NULL,
+ remove_dirproc, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv,
+ local, W_LOCAL, 0, 1, (char *) NULL, 1);
+
+ if (removed_files)
+ error (0, 0, "use '%s commit' to remove %s permanently", program_name,
+ (removed_files == 1) ? "this file" : "these files");
+
+ if (existing_files)
+ error (0, 0,
+ ((existing_files == 1) ?
+ "%d file exists; remove it first" :
+ "%d files exist; remove them first"),
+ existing_files);
+
+ return (err);
+}
+
+#ifdef CLIENT_SUPPORT
+
+/*
+ * This is called via start_recursion if we are running as the client
+ * and the -f option was used. We just physically remove the file.
+ */
+
+/*ARGSUSED*/
+static int
+remove_force_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ if (CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
+ error (0, errno, "unable to remove %s", finfo->fullname);
+ return 0;
+}
+
+#endif
+
+/*
+ * remove the file, only if it has already been physically removed
+ */
+/* ARGSUSED */
+static int
+remove_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ Vers_TS *vers;
+
+ if (force)
+ {
+ if (!noexec)
+ {
+ if ( CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
+ {
+ error (0, errno, "unable to remove %s", finfo->fullname);
+ }
+ }
+ /* else FIXME should probably act as if the file doesn't exist
+ in doing the following checks. */
+ }
+
+ vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+
+ if (vers->ts_user != NULL)
+ {
+ existing_files++;
+ if (!quiet)
+ error (0, 0, "file `%s' still in working directory",
+ finfo->fullname);
+ }
+ else if (vers->vn_user == NULL)
+ {
+ if (!quiet)
+ error (0, 0, "nothing known about `%s'", finfo->fullname);
+ }
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ {
+ char *fname;
+
+ /*
+ * It's a file that has been added, but not commited yet. So,
+ * remove the ,t file for it and scratch it from the
+ * entries file. */
+ Scratch_Entry (finfo->entries, finfo->file);
+ fname = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSEXT_LOG)
+ + 10);
+ (void) sprintf (fname, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
+ if (unlink_file (fname) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", CVSEXT_LOG);
+ if (!quiet)
+ error (0, 0, "removed `%s'", finfo->fullname);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
+#endif
+ free (fname);
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ if (!quiet)
+ error (0, 0, "file `%s' already scheduled for removal",
+ finfo->fullname);
+ }
+ else if (vers->tag != NULL && isdigit ((unsigned char) *vers->tag))
+ {
+ /* Commit will just give an error, and so there seems to be
+ little reason to allow the remove. I mean, conflicts that
+ arise out of parallel development are one thing, but conflicts
+ that arise from sticky tags are quite another.
+
+ I would have thought that non-branch sticky tags should be the
+ same but at least now, removing a file with a non-branch sticky
+ tag means to delete the tag from the file. I'm not sure that
+ is a good behavior, but until it is changed, we need to allow
+ it. */
+ error (0, 0, "\
+cannot remove file `%s' which has a numeric sticky tag of `%s'",
+ finfo->fullname, vers->tag);
+ }
+ else
+ {
+ char *fname;
+
+ /* Re-register it with a negative version number. */
+ fname = xmalloc (strlen (vers->vn_user) + 5);
+ (void) strcpy (fname, "-");
+ (void) strcat (fname, vers->vn_user);
+ Register (finfo->entries, finfo->file, fname, vers->ts_rcs, vers->options,
+ vers->tag, vers->date, vers->ts_conflict);
+ if (!quiet)
+ error (0, 0, "scheduling `%s' for removal", finfo->fullname);
+ removed_files++;
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
+#endif
+ free (fname);
+ }
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+remove_dirproc (callerdat, dir, repos, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ if (!quiet)
+ error (0, 0, "Removing %s", update_dir);
+ return (R_PROCESS);
+}
diff --git a/gnu/dist/cvs/src/repos.c b/gnu/dist/cvs/src/repos.c
new file mode 100644
index 00000000000..2738665758a
--- /dev/null
+++ b/gnu/dist/cvs/src/repos.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ */
+
+#include <assert.h>
+#include "cvs.h"
+#include "getline.h"
+
+/* Determine the name of the RCS repository for directory DIR in the
+ current working directory, or for the current working directory
+ itself if DIR is NULL. Returns the name in a newly-malloc'd
+ string. On error, gives a fatal error and does not return.
+ UPDATE_DIR is the path from where cvs was invoked (for use in error
+ messages), and should contain DIR as its last component.
+ UPDATE_DIR can be NULL to signify the directory in which cvs was
+ invoked. */
+
+char *
+Name_Repository (dir, update_dir)
+ char *dir;
+ char *update_dir;
+{
+ FILE *fpin;
+ char *xupdate_dir;
+ char *repos = NULL;
+ size_t repos_allocated = 0;
+ char *tmp;
+ char *cp;
+
+ if (update_dir && *update_dir)
+ xupdate_dir = update_dir;
+ else
+ xupdate_dir = ".";
+
+ if (dir != NULL)
+ {
+ tmp = xmalloc (strlen (dir) + sizeof (CVSADM_REP) + 10);
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP);
+ }
+ else
+ tmp = xstrdup (CVSADM_REP);
+
+ /*
+ * The assumption here is that the repository is always contained in the
+ * first line of the "Repository" file.
+ */
+ fpin = CVS_FOPEN (tmp, "r");
+
+ if (fpin == NULL)
+ {
+ int save_errno = errno;
+ char *cvsadm;
+
+ if (dir != NULL)
+ {
+ cvsadm = xmalloc (strlen (dir) + sizeof (CVSADM) + 10);
+ (void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
+ }
+ else
+ cvsadm = xstrdup (CVSADM);
+
+ if (!isdir (cvsadm))
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "there is no version here; do '%s checkout' first",
+ program_name);
+ }
+ free (cvsadm);
+
+ if (existence_error (save_errno))
+ {
+ /* FIXME: This is a very poorly worded error message. It
+ occurs at least in the case where the user manually
+ creates a directory named CVS, so the error message
+ should be more along the lines of "CVS directory found
+ without administrative files; use CVS to create the CVS
+ directory, or rename it to something else if the
+ intention is to store something besides CVS
+ administrative files". */
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "*PANIC* administration files missing");
+ }
+
+ error (1, save_errno, "cannot open %s", tmp);
+ }
+
+ if (getline (&repos, &repos_allocated, fpin) < 0)
+ {
+ /* FIXME: should be checking for end of file separately. */
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, errno, "cannot read %s", CVSADM_REP);
+ }
+ if (fclose (fpin) < 0)
+ error (0, errno, "cannot close %s", tmp);
+ free (tmp);
+
+ if ((cp = strrchr (repos, '\n')) != NULL)
+ *cp = '\0'; /* strip the newline */
+
+ /*
+ * If this is a relative repository pathname, turn it into an absolute
+ * one by tacking on the CVSROOT environment variable. If the CVSROOT
+ * environment variable is not set, die now.
+ */
+ if (strcmp (repos, "..") == 0 || strncmp (repos, "../", 3) == 0)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0, "`..'-relative repositories are not supported.");
+ error (1, 0, "illegal source repository");
+ }
+ if (! isabsolute(repos))
+ {
+ char *newrepos;
+
+ if (CVSroot_original == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0, "must set the CVSROOT environment variable\n");
+ error (0, 0, "or specify the '-d' option to %s.", program_name);
+ error (1, 0, "illegal repository setting");
+ }
+ newrepos = xmalloc (strlen (CVSroot_directory) + strlen (repos) + 10);
+ (void) sprintf (newrepos, "%s/%s", CVSroot_directory, repos);
+ free (repos);
+ repos = newrepos;
+ }
+
+ Sanitize_Repository_Name (repos);
+
+ return repos;
+}
+
+/*
+ * Return a pointer to the repository name relative to CVSROOT from a
+ * possibly fully qualified repository
+ */
+char *
+Short_Repository (repository)
+ char *repository;
+{
+ if (repository == NULL)
+ return (NULL);
+
+ /* If repository matches CVSroot at the beginning, strip off CVSroot */
+ /* And skip leading '/' in rep, in case CVSroot ended with '/'. */
+ if (strncmp (CVSroot_directory, repository,
+ strlen (CVSroot_directory)) == 0)
+ {
+ char *rep = repository + strlen (CVSroot_directory);
+ return (*rep == '/') ? rep+1 : rep;
+ }
+ else
+ return (repository);
+}
+
+/* Sanitize the repository name (in place) by removing trailing
+ * slashes and a trailing "." if present. It should be safe for
+ * callers to use strcat and friends to create repository names.
+ * Without this check, names like "/path/to/repos/./foo" and
+ * "/path/to/repos//foo" would be created. For example, one
+ * significant case is the CVSROOT-detection code in commit.c. It
+ * decides whether or not it needs to rebuild the administrative file
+ * database by doing a string compare. If we've done a `cvs co .' to
+ * get the CVSROOT files, "/path/to/repos/./CVSROOT" and
+ * "/path/to/repos/CVSROOT" are the arguments that are compared!
+ *
+ * This function ends up being called from the same places as
+ * strip_path, though what it does is much more conservative. Many
+ * comments about this operation (which was scattered around in
+ * several places in the source code) ran thus:
+ *
+ * ``repository ends with "/."; omit it. This sort of thing used
+ * to be taken care of by strip_path. Now we try to be more
+ * selective. I suspect that it would be even better to push it
+ * back further someday, so that the trailing "/." doesn't get into
+ * repository in the first place, but we haven't taken things that
+ * far yet.'' --Jim Kingdon (recurse.c, 07-Sep-97)
+ *
+ * Ahh, all too true. The major consideration is RELATIVE_REPOS. If
+ * the "/." doesn't end up in the repository while RELATIVE_REPOS is
+ * defined, there will be nothing in the CVS/Repository file. I
+ * haven't verified that the remote protocol will handle that
+ * correctly yet, so I've not made that change. */
+
+void
+Sanitize_Repository_Name (repository)
+ char *repository;
+{
+ size_t len;
+
+ assert (repository != NULL);
+
+ strip_trailing_slashes (repository);
+
+ len = strlen (repository);
+ if (len >= 2
+ && repository[len - 1] == '.'
+ && ISDIRSEP (repository[len - 2]))
+ {
+ repository[len - 2] = '\0';
+ }
+}
diff --git a/gnu/dist/cvs/src/root.c b/gnu/dist/cvs/src/root.c
new file mode 100644
index 00000000000..d88c6f4b55a
--- /dev/null
+++ b/gnu/dist/cvs/src/root.c
@@ -0,0 +1,574 @@
+/*
+ * Copyright (c) 1992, Mark D. Baushke
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Name of Root
+ *
+ * Determine the path to the CVSROOT and set "Root" accordingly.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+/* Printable names for things in the CVSroot_method enum variable.
+ Watch out if the enum is changed in cvs.h! */
+
+char *method_names[] = {
+ "local", "server (rsh)", "pserver", "kserver", "gserver", "ext", "fork"
+};
+
+#ifndef DEBUG
+
+char *
+Name_Root (dir, update_dir)
+ char *dir;
+ char *update_dir;
+{
+ FILE *fpin;
+ char *ret, *xupdate_dir;
+ char *root = NULL;
+ size_t root_allocated = 0;
+ char *tmp;
+ char *cvsadm;
+ char *cp;
+
+ if (update_dir && *update_dir)
+ xupdate_dir = update_dir;
+ else
+ xupdate_dir = ".";
+
+ if (dir != NULL)
+ {
+ cvsadm = xmalloc (strlen (dir) + sizeof (CVSADM) + 10);
+ (void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
+ tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10);
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
+ }
+ else
+ {
+ cvsadm = xstrdup (CVSADM);
+ tmp = xstrdup (CVSADM_ROOT);
+ }
+
+ /*
+ * Do not bother looking for a readable file if there is no cvsadm
+ * directory present.
+ *
+ * It is possible that not all repositories will have a CVS/Root
+ * file. This is ok, but the user will need to specify -d
+ * /path/name or have the environment variable CVSROOT set in
+ * order to continue. */
+ if ((!isdir (cvsadm)) || (!isreadable (tmp)))
+ {
+ ret = NULL;
+ goto out;
+ }
+
+ /*
+ * The assumption here is that the CVS Root is always contained in the
+ * first line of the "Root" file.
+ */
+ fpin = open_file (tmp, "r");
+
+ if (getline (&root, &root_allocated, fpin) < 0)
+ {
+ /* FIXME: should be checking for end of file separately; errno
+ is not set in that case. */
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, errno, "cannot read %s", CVSADM_ROOT);
+ error (0, 0, "please correct this problem");
+ ret = NULL;
+ goto out;
+ }
+ (void) fclose (fpin);
+ if ((cp = strrchr (root, '\n')) != NULL)
+ *cp = '\0'; /* strip the newline */
+
+ /*
+ * root now contains a candidate for CVSroot. It must be an
+ * absolute pathname or specify a remote server.
+ */
+
+ if (
+#ifdef CLIENT_SUPPORT
+ (strchr (root, ':') == NULL) &&
+#endif
+ ! isabsolute (root))
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0,
+ "ignoring %s because it does not contain an absolute pathname.",
+ CVSADM_ROOT);
+ ret = NULL;
+ goto out;
+ }
+
+#ifdef CLIENT_SUPPORT
+ if ((strchr (root, ':') == NULL) && !isdir (root))
+#else /* ! CLIENT_SUPPORT */
+ if (!isdir (root))
+#endif /* CLIENT_SUPPORT */
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0,
+ "ignoring %s because it specifies a non-existent repository %s",
+ CVSADM_ROOT, root);
+ ret = NULL;
+ goto out;
+ }
+
+ /* allocate space to return and fill it in */
+ strip_trailing_slashes (root);
+ ret = xstrdup (root);
+ out:
+ free (cvsadm);
+ free (tmp);
+ if (root != NULL)
+ free (root);
+ return (ret);
+}
+
+/*
+ * Write the CVS/Root file so that the environment variable CVSROOT
+ * and/or the -d option to cvs will be validated or not necessary for
+ * future work.
+ */
+void
+Create_Root (dir, rootdir)
+ char *dir;
+ char *rootdir;
+{
+ FILE *fout;
+ char *tmp;
+
+ if (noexec)
+ return;
+
+ /* record the current cvs root */
+
+ if (rootdir != NULL)
+ {
+ if (dir != NULL)
+ {
+ tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10);
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
+ }
+ else
+ tmp = xstrdup (CVSADM_ROOT);
+
+ fout = open_file (tmp, "w+");
+ if (fprintf (fout, "%s\n", rootdir) < 0)
+ error (1, errno, "write to %s failed", tmp);
+ if (fclose (fout) == EOF)
+ error (1, errno, "cannot close %s", tmp);
+ free (tmp);
+ }
+}
+
+#endif /* ! DEBUG */
+
+
+/* The root_allow_* stuff maintains a list of legal CVSROOT
+ directories. Then we can check against them when a remote user
+ hands us a CVSROOT directory. */
+
+static unsigned int root_allow_count;
+static char **root_allow_vector;
+static unsigned int root_allow_size;
+
+void
+root_allow_add (arg)
+ char *arg;
+{
+ char *p;
+
+ if (root_allow_size <= root_allow_count)
+ {
+ if (root_allow_size == 0)
+ {
+ root_allow_size = 1;
+ root_allow_vector =
+ (char **) malloc (root_allow_size * sizeof (char *));
+ }
+ else
+ {
+ root_allow_size *= 2;
+ root_allow_vector =
+ (char **) realloc (root_allow_vector,
+ root_allow_size * sizeof (char *));
+ }
+
+ if (root_allow_vector == NULL)
+ {
+ no_memory:
+ /* Strictly speaking, we're not supposed to output anything
+ now. But we're about to exit(), give it a try. */
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket
+ subsystems on NT and OS2 or dealing with windows
+ and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+ }
+ p = malloc (strlen (arg) + 1);
+ if (p == NULL)
+ goto no_memory;
+ strcpy (p, arg);
+ root_allow_vector[root_allow_count++] = p;
+}
+
+void
+root_allow_free ()
+{
+ if (root_allow_vector != NULL)
+ free (root_allow_vector);
+ root_allow_count = 0;
+ root_allow_size = 0;
+}
+
+int
+root_allow_ok (arg)
+ char *arg;
+{
+ unsigned int i;
+
+ if (root_allow_count == 0)
+ {
+ /* Probably someone upgraded from CVS before 1.9.10 to 1.9.10
+ or later without reading the documentation about
+ --allow-root. Printing an error here doesn't disclose any
+ particularly useful information to an attacker because a
+ CVS server configured in this way won't let *anyone* in. */
+
+ /* Note that we are called from a context where we can spit
+ back "error" rather than waiting for the next request which
+ expects responses. */
+ printf ("\
+error 0 Server configuration missing --allow-root in inetd.conf\n");
+ error_exit ();
+ }
+
+ for (i = 0; i < root_allow_count; ++i)
+ if (strcmp (root_allow_vector[i], arg) == 0)
+ return 1;
+ return 0;
+}
+
+/* This global variable holds the global -d option. It is NULL if -d
+ was not used, which means that we must get the CVSroot information
+ from the CVSROOT environment variable or from a CVS/Root file. */
+
+char *CVSroot_cmdline;
+
+/* Parse a CVSROOT variable into its constituent parts -- method,
+ * username, hostname, directory. The prototypical CVSROOT variable
+ * looks like:
+ *
+ * :method:user@host:path
+ *
+ * Some methods may omit fields; local, for example, doesn't need user
+ * and host.
+ *
+ * Returns zero on success, non-zero on failure. */
+
+char *CVSroot_original = NULL; /* the CVSroot that was passed in */
+int client_active; /* nonzero if we are doing remote access */
+CVSmethod CVSroot_method; /* one of the enum values defined in cvs.h */
+char *CVSroot_username; /* the username or NULL if method == local */
+char *CVSroot_hostname; /* the hostname or NULL if method == local */
+char *CVSroot_directory; /* the directory name */
+
+int
+parse_cvsroot (CVSroot)
+ char *CVSroot;
+{
+ static int cvsroot_parsed = 0;
+ char *cvsroot_copy, *p;
+ int check_hostname;
+
+ /* Don't go through the trouble twice. */
+ if (cvsroot_parsed)
+ {
+ error (0, 0, "WARNING (parse_cvsroot): someone called me twice!\n");
+ return 0;
+ }
+
+ CVSroot_original = xstrdup (CVSroot);
+ cvsroot_copy = xstrdup (CVSroot);
+
+ if ((*cvsroot_copy == ':'))
+ {
+ char *method = ++cvsroot_copy;
+
+ /* Access method specified, as in
+ * "cvs -d :pserver:user@host:/path",
+ * "cvs -d :local:e:\path",
+ * "cvs -d :kserver:user@host:/path", or
+ * "cvs -d :fork:/path".
+ * We need to get past that part of CVSroot before parsing the
+ * rest of it.
+ */
+
+ if (! (p = strchr (method, ':')))
+ {
+ error (0, 0, "bad CVSroot: %s", CVSroot);
+ return 1;
+ }
+ *p = '\0';
+ cvsroot_copy = ++p;
+
+ /* Now we have an access method -- see if it's valid. */
+
+ if (strcmp (method, "local") == 0)
+ CVSroot_method = local_method;
+ else if (strcmp (method, "pserver") == 0)
+ CVSroot_method = pserver_method;
+ else if (strcmp (method, "kserver") == 0)
+ CVSroot_method = kserver_method;
+ else if (strcmp (method, "gserver") == 0)
+ CVSroot_method = gserver_method;
+ else if (strcmp (method, "server") == 0)
+ CVSroot_method = server_method;
+ else if (strcmp (method, "ext") == 0)
+ CVSroot_method = ext_method;
+ else if (strcmp (method, "fork") == 0)
+ CVSroot_method = fork_method;
+ else
+ {
+ error (0, 0, "unknown method in CVSroot: %s", CVSroot);
+ return 1;
+ }
+ }
+ else
+ {
+ /* If the method isn't specified, assume
+ SERVER_METHOD/EXT_METHOD if the string contains a colon or
+ LOCAL_METHOD otherwise. */
+
+ CVSroot_method = ((strchr (cvsroot_copy, ':'))
+#ifdef RSH_NOT_TRANSPARENT
+ ? server_method
+#else
+ ? ext_method
+#endif
+ : local_method);
+ }
+
+ client_active = (CVSroot_method != local_method);
+
+ /* Check for username/hostname if we're not LOCAL_METHOD. */
+
+ CVSroot_username = NULL;
+ CVSroot_hostname = NULL;
+
+ if ((CVSroot_method != local_method)
+ && (CVSroot_method != fork_method))
+ {
+ /* Check to see if there is a username in the string. */
+
+ if ((p = strchr (cvsroot_copy, '@')))
+ {
+ CVSroot_username = cvsroot_copy;
+ *p = '\0';
+ cvsroot_copy = ++p;
+ if (*CVSroot_username == '\0')
+ CVSroot_username = NULL;
+ }
+
+ if ((p = strchr (cvsroot_copy, ':')))
+ {
+ CVSroot_hostname = cvsroot_copy;
+ *p = '\0';
+ cvsroot_copy = ++p;
+
+ if (*CVSroot_hostname == '\0')
+ CVSroot_hostname = NULL;
+ }
+ }
+
+ CVSroot_directory = cvsroot_copy;
+
+#if ! defined (CLIENT_SUPPORT) && ! defined (DEBUG)
+ if (CVSroot_method != local_method)
+ {
+ error (0, 0, "Your CVSROOT is set for a remote access method");
+ error (0, 0, "but your CVS executable doesn't support it");
+ error (0, 0, "(%s)", CVSroot);
+ return 1;
+ }
+#endif
+
+ /* Do various sanity checks. */
+
+ if (CVSroot_username && ! CVSroot_hostname)
+ {
+ error (0, 0, "missing hostname in CVSROOT: %s", CVSroot);
+ return 1;
+ }
+
+ check_hostname = 0;
+ switch (CVSroot_method)
+ {
+ case local_method:
+ case fork_method:
+ if (CVSroot_username || CVSroot_hostname)
+ {
+ error (0, 0, "can't specify hostname and username in CVSROOT");
+ error (0, 0, "when using %s access method",
+ CVSroot_method == local_method ? "local" : "fork");
+ error (0, 0, "(%s)", CVSroot);
+ return 1;
+ }
+ /* cvs.texinfo has always told people that CVSROOT must be an
+ absolute pathname. Furthermore, attempts to use a relative
+ pathname produced various errors (I couldn't get it to work),
+ so there would seem to be little risk in making this a fatal
+ error. */
+ if (!isabsolute (CVSroot_directory))
+ error (1, 0, "CVSROOT %s must be an absolute pathname",
+ CVSroot_directory);
+ break;
+ case kserver_method:
+#ifndef HAVE_KERBEROS
+ error (0, 0, "Your CVSROOT is set for a kerberos access method");
+ error (0, 0, "but your CVS executable doesn't support it");
+ error (0, 0, "(%s)", CVSroot);
+ return 1;
+#else
+ check_hostname = 1;
+ break;
+#endif
+ case gserver_method:
+#ifndef HAVE_GSSAPI
+ error (0, 0, "Your CVSROOT is set for a GSSAPI access method");
+ error (0, 0, "but your CVS executable doesn't support it");
+ error (0, 0, "(%s)", CVSroot);
+ return 1;
+#else
+ check_hostname = 1;
+ break;
+#endif
+ case server_method:
+ case ext_method:
+ case pserver_method:
+ check_hostname = 1;
+ break;
+ }
+
+ if (check_hostname)
+ {
+ if (! CVSroot_hostname)
+ {
+ error (0, 0, "didn't specify hostname in CVSROOT: %s", CVSroot);
+ return 1;
+ }
+ }
+
+ if (*CVSroot_directory == '\0')
+ {
+ error (0, 0, "missing directory in CVSROOT: %s", CVSroot);
+ return 1;
+ }
+
+ /* Hooray! We finally parsed it! */
+ return 0;
+}
+
+
+/* Set up the global CVSroot* variables as if we're using the local
+ repository DIR. DIR must point to storage which will last for the
+ rest of the CVS invocation (for example, the caller might malloc it
+ and never free it, or free it just before exiting CVS). */
+
+void
+set_local_cvsroot (dir)
+ char *dir;
+{
+ CVSroot_original = dir;
+ CVSroot_method = local_method;
+ CVSroot_directory = CVSroot_original;
+ CVSroot_username = NULL;
+ CVSroot_hostname = NULL;
+ client_active = 0;
+}
+
+
+#ifdef DEBUG
+/* This is for testing the parsing function. Use
+
+ gcc -I. -I.. -I../lib -DDEBUG root.c -o root
+
+ to compile. */
+
+#include <stdio.h>
+
+char *CVSroot;
+char *program_name = "testing";
+char *command_name = "parse_cvsroot"; /* XXX is this used??? */
+
+/* Toy versions of various functions when debugging under unix. Yes,
+ these make various bad assumptions, but they're pretty easy to
+ debug when something goes wrong. */
+
+void
+error_exit PROTO ((void))
+{
+ exit (1);
+}
+
+char *
+xstrdup (str)
+ const char *str;
+{
+ return strdup (str);
+}
+
+int
+isabsolute (dir)
+ const char *dir;
+{
+ return (dir && (*dir == '/'));
+}
+
+void
+main (argc, argv)
+ int argc;
+ char *argv[];
+{
+ program_name = argv[0];
+
+ if (argc != 2)
+ {
+ fprintf (stderr, "Usage: %s <CVSROOT>\n", program_name);
+ exit (2);
+ }
+
+ if (parse_cvsroot (argv[1]))
+ {
+ fprintf (stderr, "%s: Parsing failed.\n", program_name);
+ exit (1);
+ }
+ printf ("CVSroot: %s\n", argv[1]);
+ printf ("CVSroot_method: %s\n", method_names[CVSroot_method]);
+ printf ("CVSroot_username: %s\n",
+ CVSroot_username ? CVSroot_username : "NULL");
+ printf ("CVSroot_hostname: %s\n",
+ CVSroot_hostname ? CVSroot_hostname : "NULL");
+ printf ("CVSroot_directory: %s\n", CVSroot_directory);
+
+ exit (0);
+ /* NOTREACHED */
+}
+#endif
diff --git a/gnu/dist/cvs/src/rtag.c b/gnu/dist/cvs/src/rtag.c
new file mode 100644
index 00000000000..5fd825f471d
--- /dev/null
+++ b/gnu/dist/cvs/src/rtag.c
@@ -0,0 +1,766 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Rtag
+ *
+ * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
+ * Uses the modules database, if necessary.
+ */
+
+#include "cvs.h"
+
+static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static int check_filesdoneproc PROTO ((void *callerdat, int err,
+ char *repos, char *update_dir,
+ List *entries));
+static int pretag_proc PROTO((char *repository, char *filter));
+static void masterlist_delproc PROTO((Node *p));
+static void tag_delproc PROTO((Node *p));
+static int pretag_list_proc PROTO((Node *p, void *closure));
+
+static Dtype rtag_dirproc PROTO ((void *callerdat, char *dir,
+ char *repos, char *update_dir,
+ List *entries));
+static int rtag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static int rtag_filesdoneproc PROTO ((void *callerdat, int err,
+ char *repos, char *update_dir,
+ List *entries));
+static int rtag_proc PROTO((int *pargc, char **argv, char *xwhere,
+ char *mwhere, char *mfile, int shorten,
+ int local_specified, char *mname, char *msg));
+static int rtag_delete PROTO((RCSNode *rcsfile));
+
+
+struct tag_info
+{
+ Ctype status;
+ char *rev;
+ char *tag;
+ char *options;
+};
+
+struct master_lists
+{
+ List *tlist;
+};
+
+static List *mtlist;
+static List *tlist;
+
+static char *symtag;
+static char *numtag;
+static int numtag_validated = 0;
+static int delete_flag; /* adding a tag by default */
+static int attic_too; /* remove tag from Attic files */
+static int branch_mode; /* make an automagic "branch" tag */
+static char *date;
+static int local; /* recursive by default */
+static int force_tag_match = 1; /* force by default */
+static int force_tag_move; /* don't move existing tags by default */
+
+static const char *const rtag_usage[] =
+{
+ "Usage: %s %s [-aflRnF] [-b] [-d] [-r rev|-D date] tag modules...\n",
+ "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-n\tNo execution of 'tag program'\n",
+ "\t-d\tDelete the given Tag.\n",
+ "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
+ "\t-r rev\tExisting revision/tag.\n",
+ "\t-D\tExisting date.\n",
+ "\t-F\tMove tag if it already exists\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+rtag (argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ int c;
+ DBM *db;
+ int run_module_prog = 1;
+ int err = 0;
+
+ if (argc == -1)
+ usage (rtag_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+FanfQqlRdbr:D:")) != -1)
+ {
+ switch (c)
+ {
+ case 'a':
+ attic_too = 1;
+ break;
+ case 'n':
+ run_module_prog = 0;
+ break;
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'd':
+ delete_flag = 1;
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'b':
+ branch_mode = 1;
+ break;
+ case 'r':
+ numtag = optarg;
+ break;
+ case 'D':
+ if (date)
+ free (date);
+ date = Make_Date (optarg);
+ break;
+ case 'F':
+ force_tag_move = 1;
+ break;
+ case '?':
+ default:
+ usage (rtag_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 2)
+ usage (rtag_usage);
+ symtag = argv[0];
+ argc--;
+ argv++;
+
+ if (date && numtag)
+ error (1, 0, "-r and -D options are mutually exclusive");
+ if (delete_flag && branch_mode)
+ error (0, 0, "warning: -b ignored with -d options");
+ RCS_check_tag (symtag);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (!force_tag_match)
+ send_arg ("-f");
+ if (local)
+ send_arg("-l");
+ if (delete_flag)
+ send_arg("-d");
+ if (branch_mode)
+ send_arg("-b");
+ if (force_tag_move)
+ send_arg("-F");
+ if (!run_module_prog)
+ send_arg("-n");
+ if (attic_too)
+ send_arg("-a");
+
+ if (numtag)
+ option_with_arg ("-r", numtag);
+ if (date)
+ client_senddate (date);
+
+ send_arg (symtag);
+
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ }
+
+ send_to_server ("rtag\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ db = open_module ();
+ for (i = 0; i < argc; i++)
+ {
+ /* XXX last arg should be repository, but doesn't make sense here */
+ history_write ('T', (delete_flag ? "D" : (numtag ? numtag :
+ (date ? date : "A"))), symtag, argv[i], "");
+ err += do_module (db, argv[i], TAG,
+ delete_flag ? "Untagging" : "Tagging",
+ rtag_proc, (char *) NULL, 0, 0, run_module_prog,
+ symtag);
+ }
+ close_module (db);
+ return (err);
+}
+
+/*
+ * callback proc for doing the real work of tagging
+ */
+/* ARGSUSED */
+static int
+rtag_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
+ mname, msg)
+ int *pargc;
+ char **argv;
+ char *xwhere;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *mname;
+ char *msg;
+{
+ /* Begin section which is identical to patch_proc--should this
+ be abstracted out somehow? */
+ int err = 0;
+ int which;
+ char *repository;
+ char *where;
+
+ repository = xmalloc (strlen (CVSroot_directory) + strlen (argv[0])
+ + (mfile == NULL ? 0 : strlen (mfile)) + 30);
+ (void) sprintf (repository, "%s/%s", CVSroot_directory, argv[0]);
+ where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile))
+ + 10);
+ (void) strcpy (where, argv[0]);
+
+ /* if mfile isn't null, we need to set up to do only part of the module */
+ if (mfile != NULL)
+ {
+ char *cp;
+ char *path;
+
+ /* if the portion of the module is a path, put the dir part on repos */
+ if ((cp = strrchr (mfile, '/')) != NULL)
+ {
+ *cp = '\0';
+ (void) strcat (repository, "/");
+ (void) strcat (repository, mfile);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ mfile = cp + 1;
+ }
+
+ /* take care of the rest */
+ path = xmalloc (strlen (repository) + strlen (mfile) + 5);
+ (void) sprintf (path, "%s/%s", repository, mfile);
+ if (isdir (path))
+ {
+ /* directory means repository gets the dir tacked on */
+ (void) strcpy (repository, path);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ }
+ else
+ {
+ int i;
+
+ /* a file means muck argv */
+ for (i = 1; i < *pargc; i++)
+ free (argv[i]);
+ argv[1] = xstrdup (mfile);
+ (*pargc) = 2;
+ }
+ free (path);
+ }
+
+ /* cd to the starting repository */
+ if ( CVS_CHDIR (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ free (repository);
+ return (1);
+ }
+ free (repository);
+ /* End section which is identical to patch_proc. */
+
+ if (delete_flag || attic_too || (force_tag_match && numtag))
+ which = W_REPOS | W_ATTIC;
+ else
+ which = W_REPOS;
+
+ if (numtag != NULL && !numtag_validated)
+ {
+ tag_check_valid (numtag, *pargc - 1, argv + 1, local, 0, NULL);
+ numtag_validated = 1;
+ }
+
+ /* check to make sure they are authorized to tag all the
+ specified files in the repository */
+
+ mtlist = getlist();
+ err = start_recursion (check_fileproc, check_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ *pargc - 1, argv + 1, local, which, 0, 1,
+ where, 1);
+
+ if (err)
+ {
+ error (1, 0, "correct the above errors first!");
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (rtag_fileproc, rtag_filesdoneproc, rtag_dirproc,
+ (DIRLEAVEPROC) NULL, NULL,
+ *pargc - 1, argv + 1, local,
+ which, 0, 0, where, 1);
+ free (where);
+ dellist(&mtlist);
+
+ return (err);
+}
+
+/* check file that is to be tagged */
+/* All we do here is add it to our list */
+
+static int
+check_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *xdir;
+ Node *p;
+ Vers_TS *vers;
+
+ if (finfo->update_dir[0] == '\0')
+ xdir = ".";
+ else
+ xdir = finfo->update_dir;
+ if ((p = findnode (mtlist, xdir)) != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ struct master_lists *ml;
+
+ tlist = getlist ();
+ p = getnode ();
+ p->key = xstrdup (xdir);
+ p->type = UPDATE;
+ ml = (struct master_lists *)
+ xmalloc (sizeof (struct master_lists));
+ ml->tlist = tlist;
+ p->data = (char *) ml;
+ p->delproc = masterlist_delproc;
+ (void) addnode (mtlist, p);
+ }
+ /* do tlist */
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = tag_delproc;
+ vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+ p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match,
+ (int *) NULL);
+ if (p->data != NULL)
+ {
+ int addit = 1;
+ char *oversion;
+
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
+ (int *) NULL);
+ if (oversion == NULL)
+ {
+ if (delete_flag)
+ {
+ /* Deleting a tag which did not exist is a noop and
+ should not be logged. */
+ addit = 0;
+ }
+ }
+ else if (delete_flag)
+ {
+ free (p->data);
+ p->data = xstrdup (oversion);
+ }
+ else if (strcmp(oversion, p->data) == 0)
+ {
+ addit = 0;
+ }
+ else if (!force_tag_move)
+ {
+ addit = 0;
+ }
+ if (oversion != NULL)
+ {
+ free(oversion);
+ }
+ if (!addit)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ }
+ freevers_ts (&vers);
+ (void) addnode (tlist, p);
+ return (0);
+}
+
+static int
+check_filesdoneproc (callerdat, err, repos, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ int n;
+ Node *p;
+
+ p = findnode(mtlist, update_dir);
+ if (p != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ tlist = (List *) NULL;
+ }
+ if ((tlist == NULL) || (tlist->list->next == tlist->list))
+ {
+ return (err);
+ }
+ if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
+ {
+ error (0, 0, "Pre-tag check failed");
+ err += n;
+ }
+ return (err);
+}
+
+static int
+pretag_proc(repository, filter)
+ char *repository;
+ char *filter;
+{
+ if (filter[0] == '/')
+ {
+ char *s, *cp;
+
+ s = xstrdup(filter);
+ for (cp=s; *cp; cp++)
+ {
+ if (isspace ((unsigned char) *cp))
+ {
+ *cp = '\0';
+ break;
+ }
+ }
+ if (!isfile(s))
+ {
+ error (0, errno, "cannot find pre-tag filter '%s'", s);
+ free(s);
+ return (1);
+ }
+ free(s);
+ }
+ run_setup (filter);
+ run_arg (symtag);
+ run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add");
+ run_arg (repository);
+ walklist(tlist, pretag_list_proc, NULL);
+ return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
+}
+
+static void
+masterlist_delproc(p)
+ Node *p;
+{
+ struct master_lists *ml;
+
+ ml = (struct master_lists *)p->data;
+ dellist(&ml->tlist);
+ free(ml);
+ return;
+}
+
+static void
+tag_delproc(p)
+ Node *p;
+{
+ if (p->data != NULL)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ return;
+}
+
+static int
+pretag_list_proc(p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data != NULL)
+ {
+ run_arg(p->key);
+ run_arg(p->data);
+ }
+ return (0);
+}
+
+/*
+ * Called to tag a particular file, as appropriate with the options that were
+ * set above.
+ */
+/* ARGSUSED */
+static int
+rtag_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ RCSNode *rcsfile;
+ char *version, *rev;
+ int retcode = 0;
+
+ /* Lock the directory if it is not already locked. We might be
+ able to rely on rtag_dirproc for this. */
+
+ /* It would be nice to provide consistency with respect to
+ commits; however CVS lacks the infrastructure to do that (see
+ Concurrency in cvs.texinfo and comment in do_recursion). We
+ can and will prevent simultaneous tag operations from
+ interfering with each other, by write locking each directory as
+ we enter it, and unlocking it as we leave it. */
+
+ lock_dir_for_write (finfo->repository);
+
+ /* find the parsed RCS data */
+ if ((rcsfile = finfo->rcs) == NULL)
+ return (1);
+
+ /*
+ * For tagging an RCS file which is a symbolic link, you'd best be
+ * running with RCS 5.6, since it knows how to handle symbolic links
+ * correctly without breaking your link!
+ */
+
+ if (delete_flag)
+ return (rtag_delete (rcsfile));
+
+ /*
+ * If we get here, we are adding a tag. But, if -a was specified, we
+ * need to check to see if a -r or -D option was specified. If neither
+ * was specified and the file is in the Attic, remove the tag.
+ */
+ if (attic_too && (!numtag && !date))
+ {
+ if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
+ return (rtag_delete (rcsfile));
+ }
+
+ version = RCS_getversion (rcsfile, numtag, date, force_tag_match,
+ (int *) NULL);
+ if (version == NULL)
+ {
+ /* If -a specified, clean up any old tags */
+ if (attic_too)
+ (void) rtag_delete (rcsfile);
+
+ if (!quiet && !force_tag_match)
+ {
+ error (0, 0, "cannot find tag `%s' in `%s'",
+ numtag ? numtag : "head", rcsfile->path);
+ return (1);
+ }
+ return (0);
+ }
+ if (numtag
+ && isdigit ((unsigned char) *numtag)
+ && strcmp (numtag, version) != 0)
+ {
+
+ /*
+ * We didn't find a match for the numeric tag that was specified, but
+ * that's OK. just pass the numeric tag on to rcs, to be tagged as
+ * specified. Could get here if one tried to tag "1.1.1" and there
+ * was a 1.1.1 branch with some head revision. In this case, we want
+ * the tag to reference "1.1.1" and not the revision at the head of
+ * the branch. Use a symbolic tag for that.
+ */
+ rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
+ retcode = RCS_settag(rcsfile, symtag, numtag);
+ if (retcode == 0)
+ RCS_rewrite (rcsfile, NULL, NULL);
+ }
+ else
+ {
+ char *oversion;
+
+ /*
+ * As an enhancement for the case where a tag is being re-applied to
+ * a large body of a module, make one extra call to RCS_getversion to
+ * see if the tag is already set in the RCS file. If so, check to
+ * see if it needs to be moved. If not, do nothing. This will
+ * likely save a lot of time when simply moving the tag to the
+ * "current" head revisions of a module -- which I have found to be a
+ * typical tagging operation.
+ */
+ rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
+ oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
+ (int *) NULL);
+ if (oversion != NULL)
+ {
+ int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
+
+ /*
+ * if versions the same and neither old or new are branches don't
+ * have to do anything
+ */
+ if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
+ {
+ free (oversion);
+ free (version);
+ return (0);
+ }
+
+ if (!force_tag_move)
+ {
+ /* we're NOT going to move the tag */
+ (void) printf ("W %s", finfo->fullname);
+
+ (void) printf (" : %s already exists on %s %s",
+ symtag, isbranch ? "branch" : "version",
+ oversion);
+ (void) printf (" : NOT MOVING tag to %s %s\n",
+ branch_mode ? "branch" : "version", rev);
+ free (oversion);
+ free (version);
+ return (0);
+ }
+ free (oversion);
+ }
+ retcode = RCS_settag(rcsfile, symtag, rev);
+ if (retcode == 0)
+ RCS_rewrite (rcsfile, NULL, NULL);
+ }
+
+ if (retcode != 0)
+ {
+ error (1, retcode == -1 ? errno : 0,
+ "failed to set tag `%s' to revision `%s' in `%s'",
+ symtag, rev, rcsfile->path);
+ if (branch_mode)
+ free (rev);
+ free (version);
+ return (1);
+ }
+ if (branch_mode)
+ free (rev);
+ free (version);
+ return (0);
+}
+
+/*
+ * If -d is specified, "force_tag_match" is set, so that this call to
+ * RCS_getversion() will return a NULL version string if the symbolic
+ * tag does not exist in the RCS file.
+ *
+ * If the -r flag was used, numtag is set, and we only delete the
+ * symtag from files that have numtag.
+ *
+ * This is done here because it's MUCH faster than just blindly calling
+ * "rcs" to remove the tag... trust me.
+ */
+static int
+rtag_delete (rcsfile)
+ RCSNode *rcsfile;
+{
+ char *version;
+ int retcode;
+
+ if (numtag)
+ {
+ version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1,
+ (int *) NULL);
+ if (version == NULL)
+ return (0);
+ free (version);
+ }
+
+ version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1,
+ (int *) NULL);
+ if (version == NULL)
+ return (0);
+ free (version);
+
+ if ((retcode = RCS_deltag(rcsfile, symtag)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag `%s' from `%s'", symtag,
+ rcsfile->path);
+ return (1);
+ }
+ RCS_rewrite (rcsfile, NULL, NULL);
+ return (0);
+}
+
+/* Clear any lock we may hold on the current directory. */
+
+static int
+rtag_filesdoneproc (callerdat, err, repos, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ Lock_Cleanup ();
+
+ return (err);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+rtag_dirproc (callerdat, dir, repos, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ if (ignore_directory (update_dir))
+ {
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Ignoring %s", update_dir);
+ return R_SKIP_ALL;
+ }
+
+ if (!quiet)
+ error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging",
+ update_dir);
+ return (R_PROCESS);
+}
+
+
+
diff --git a/gnu/dist/cvs/src/run.c b/gnu/dist/cvs/src/run.c
new file mode 100644
index 00000000000..41f445756d6
--- /dev/null
+++ b/gnu/dist/cvs/src/run.c
@@ -0,0 +1,439 @@
+/* run.c --- routines for executing subprocesses.
+
+ This file is part of GNU CVS.
+
+ GNU CVS is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+#include "cvs.h"
+
+#ifndef HAVE_UNISTD_H
+extern int execvp PROTO((char *file, char **argv));
+#endif
+
+static void run_add_arg PROTO((const char *s));
+
+extern char *strtok ();
+
+/*
+ * To exec a program under CVS, first call run_setup() to setup initial
+ * arguments. The argument to run_setup will be parsed into whitespace
+ * separated words and added to the global run_argv list.
+ *
+ * Then, optionally call run_arg() for each additional argument that you'd like
+ * to pass to the executed program.
+ *
+ * Finally, call run_exec() to execute the program with the specified arguments.
+ * The execvp() syscall will be used, so that the PATH is searched correctly.
+ * File redirections can be performed in the call to run_exec().
+ */
+static char **run_argv;
+static int run_argc;
+static int run_argc_allocated;
+
+/* VARARGS */
+void
+run_setup (prog)
+ const char *prog;
+{
+ char *cp;
+ int i;
+ char *run_prog;
+
+ /* clean out any malloc'ed values from run_argv */
+ for (i = 0; i < run_argc; i++)
+ {
+ if (run_argv[i])
+ {
+ free (run_argv[i]);
+ run_argv[i] = (char *) 0;
+ }
+ }
+ run_argc = 0;
+
+ run_prog = xstrdup (prog);
+
+ /* put each word into run_argv, allocating it as we go */
+ for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t"))
+ run_add_arg (cp);
+ free (run_prog);
+}
+
+void
+run_arg (s)
+ const char *s;
+{
+ run_add_arg (s);
+}
+
+static void
+run_add_arg (s)
+ const char *s;
+{
+ /* allocate more argv entries if we've run out */
+ if (run_argc >= run_argc_allocated)
+ {
+ run_argc_allocated += 50;
+ run_argv = (char **) xrealloc ((char *) run_argv,
+ run_argc_allocated * sizeof (char **));
+ }
+
+ if (s)
+ run_argv[run_argc++] = xstrdup (s);
+ else
+ run_argv[run_argc] = (char *) 0; /* not post-incremented on purpose! */
+}
+
+int
+run_exec (stin, stout, sterr, flags)
+ const char *stin;
+ const char *stout;
+ const char *sterr;
+ int flags;
+{
+ int shin, shout, sherr;
+ int mode_out, mode_err;
+ int status;
+ int rc = -1;
+ int rerrno = 0;
+ int pid, w;
+
+#ifdef POSIX_SIGNALS
+ sigset_t sigset_mask, sigset_omask;
+ struct sigaction act, iact, qact;
+
+#else
+#ifdef BSD_SIGNALS
+ int mask;
+ struct sigvec vec, ivec, qvec;
+
+#else
+ RETSIGTYPE (*istat) (), (*qstat) ();
+#endif
+#endif
+
+ if (trace)
+ {
+#ifdef SERVER_SUPPORT
+ cvs_outerr (server_active ? "S" : " ", 1);
+#endif
+ cvs_outerr ("-> system(", 0);
+ run_print (stderr);
+ cvs_outerr (")\n", 0);
+ }
+ if (noexec && (flags & RUN_REALLY) == 0)
+ return (0);
+
+ /* make sure that we are null terminated, since we didn't calloc */
+ run_add_arg ((char *) 0);
+
+ /* setup default file descriptor numbers */
+ shin = 0;
+ shout = 1;
+ sherr = 2;
+
+ /* set the file modes for stdout and stderr */
+ mode_out = mode_err = O_WRONLY | O_CREAT;
+ mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC);
+ mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC);
+
+ if (stin && (shin = open (stin, O_RDONLY)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for reading (prog %s)",
+ stin, run_argv[0]);
+ goto out0;
+ }
+ if (stout && (shout = open (stout, mode_out, 0666)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for writing (prog %s)",
+ stout, run_argv[0]);
+ goto out1;
+ }
+ if (sterr && (flags & RUN_COMBINED) == 0)
+ {
+ if ((sherr = open (sterr, mode_err, 0666)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for writing (prog %s)",
+ sterr, run_argv[0]);
+ goto out2;
+ }
+ }
+
+ /* Make sure we don't flush this twice, once in the subprocess. */
+ fflush (stdout);
+ fflush (stderr);
+
+ /* The output files, if any, are now created. Do the fork and dups.
+
+ We use vfork not so much for a performance boost (the
+ performance boost, if any, is modest on most modern unices),
+ but for the sake of systems without a memory management unit,
+ which find it difficult or impossible to implement fork at all
+ (e.g. Amiga). The other solution is spawn (see
+ windows-NT/run.c). */
+
+#ifdef HAVE_VFORK
+ pid = vfork ();
+#else
+ pid = fork ();
+#endif
+ if (pid == 0)
+ {
+ if (shin != 0)
+ {
+ (void) dup2 (shin, 0);
+ (void) close (shin);
+ }
+ if (shout != 1)
+ {
+ (void) dup2 (shout, 1);
+ (void) close (shout);
+ }
+ if (flags & RUN_COMBINED)
+ (void) dup2 (1, 2);
+ else if (sherr != 2)
+ {
+ (void) dup2 (sherr, 2);
+ (void) close (sherr);
+ }
+
+ /* dup'ing is done. try to run it now */
+ (void) execvp (run_argv[0], run_argv);
+ error (0, errno, "cannot exec %s", run_argv[0]);
+ _exit (127);
+ }
+ else if (pid == -1)
+ {
+ rerrno = errno;
+ goto out;
+ }
+
+ /* the parent. Ignore some signals for now */
+#ifdef POSIX_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ act.sa_handler = SIG_IGN;
+ (void) sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ (void) sigaction (SIGINT, &act, &iact);
+ (void) sigaction (SIGQUIT, &act, &qact);
+ }
+ else
+ {
+ (void) sigemptyset (&sigset_mask);
+ (void) sigaddset (&sigset_mask, SIGINT);
+ (void) sigaddset (&sigset_mask, SIGQUIT);
+ (void) sigprocmask (SIG_SETMASK, &sigset_mask, &sigset_omask);
+ }
+#else
+#ifdef BSD_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ memset ((char *) &vec, 0, sizeof (vec));
+ vec.sv_handler = SIG_IGN;
+ (void) sigvec (SIGINT, &vec, &ivec);
+ (void) sigvec (SIGQUIT, &vec, &qvec);
+ }
+ else
+ mask = sigblock (sigmask (SIGINT) | sigmask (SIGQUIT));
+#else
+ istat = signal (SIGINT, SIG_IGN);
+ qstat = signal (SIGQUIT, SIG_IGN);
+#endif
+#endif
+
+ /* wait for our process to die and munge return status */
+#ifdef POSIX_SIGNALS
+ while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR)
+ ;
+#else
+ while ((w = wait (&status)) != pid)
+ {
+ if (w == -1 && errno != EINTR)
+ break;
+ }
+#endif
+
+ if (w == -1)
+ {
+ rc = -1;
+ rerrno = errno;
+ }
+#ifndef VMS /* status is return status */
+ else if (WIFEXITED (status))
+ rc = WEXITSTATUS (status);
+ else if (WIFSIGNALED (status))
+ {
+ if (WTERMSIG (status) == SIGPIPE)
+ error (1, 0, "broken pipe");
+ rc = 2;
+ }
+ else
+ rc = 1;
+#else /* VMS */
+ rc = WEXITSTATUS (status);
+#endif /* VMS */
+
+ /* restore the signals */
+#ifdef POSIX_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ (void) sigaction (SIGINT, &iact, (struct sigaction *) NULL);
+ (void) sigaction (SIGQUIT, &qact, (struct sigaction *) NULL);
+ }
+ else
+ (void) sigprocmask (SIG_SETMASK, &sigset_omask, (sigset_t *) NULL);
+#else
+#ifdef BSD_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ (void) sigvec (SIGINT, &ivec, (struct sigvec *) NULL);
+ (void) sigvec (SIGQUIT, &qvec, (struct sigvec *) NULL);
+ }
+ else
+ (void) sigsetmask (mask);
+#else
+ (void) signal (SIGINT, istat);
+ (void) signal (SIGQUIT, qstat);
+#endif
+#endif
+
+ /* cleanup the open file descriptors */
+ out:
+ if (sterr)
+ (void) close (sherr);
+ out2:
+ if (stout)
+ (void) close (shout);
+ out1:
+ if (stin)
+ (void) close (shin);
+
+ out0:
+ if (rerrno)
+ errno = rerrno;
+ return (rc);
+}
+
+void
+run_print (fp)
+ FILE *fp;
+{
+ int i;
+ void (*outfn) PROTO ((const char *, size_t));
+
+ if (fp == stderr)
+ outfn = cvs_outerr;
+ else if (fp == stdout)
+ outfn = cvs_output;
+ else
+ {
+ error (1, 0, "internal error: bad argument to run_print");
+ /* Solely to placate gcc -Wall.
+ FIXME: it'd be better to use a function named `fatal' that
+ is known never to return. Then kludges wouldn't be necessary. */
+ outfn = NULL;
+ }
+
+ for (i = 0; i < run_argc; i++)
+ {
+ (*outfn) ("'", 1);
+ (*outfn) (run_argv[i], 0);
+ (*outfn) ("'", 1);
+ if (i != run_argc - 1)
+ (*outfn) (" ", 1);
+ }
+}
+
+/* Return value is NULL for error, or if noexec was set. If there was an
+ error, return NULL and I'm not sure whether errno was set (the Red Hat
+ Linux 4.1 popen manpage was kind of vague but discouraging; and the noexec
+ case complicates this even aside from popen behavior). */
+
+FILE *
+run_popen (cmd, mode)
+ const char *cmd;
+ const char *mode;
+{
+ if (trace)
+ (void) fprintf (stderr, "%s-> run_popen(%s,%s)\n",
+ CLIENT_SERVER_STR, cmd, mode);
+ if (noexec)
+ return (NULL);
+
+ return (popen (cmd, mode));
+}
+
+int
+piped_child (command, tofdp, fromfdp)
+ char **command;
+ int *tofdp;
+ int *fromfdp;
+{
+ int pid;
+ int to_child_pipe[2];
+ int from_child_pipe[2];
+
+ if (pipe (to_child_pipe) < 0)
+ error (1, errno, "cannot create pipe");
+ if (pipe (from_child_pipe) < 0)
+ error (1, errno, "cannot create pipe");
+
+#ifdef USE_SETMODE_BINARY
+ setmode (to_child_pipe[0], O_BINARY);
+ setmode (to_child_pipe[1], O_BINARY);
+ setmode (from_child_pipe[0], O_BINARY);
+ setmode (from_child_pipe[1], O_BINARY);
+#endif
+
+#ifdef HAVE_VFORK
+ pid = vfork ();
+#else
+ pid = fork ();
+#endif
+ if (pid < 0)
+ error (1, errno, "cannot fork");
+ if (pid == 0)
+ {
+ if (dup2 (to_child_pipe[0], STDIN_FILENO) < 0)
+ error (1, errno, "cannot dup2 pipe");
+ if (close (to_child_pipe[1]) < 0)
+ error (1, errno, "cannot close pipe");
+ if (close (from_child_pipe[0]) < 0)
+ error (1, errno, "cannot close pipe");
+ if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0)
+ error (1, errno, "cannot dup2 pipe");
+
+ execvp (command[0], command);
+ error (1, errno, "cannot exec %s", command[0]);
+ }
+ if (close (to_child_pipe[0]) < 0)
+ error (1, errno, "cannot close pipe");
+ if (close (from_child_pipe[1]) < 0)
+ error (1, errno, "cannot close pipe");
+
+ *tofdp = to_child_pipe[1];
+ *fromfdp = from_child_pipe[0];
+ return pid;
+}
+
+
+void
+close_on_exec (fd)
+ int fd;
+{
+#if defined (FD_CLOEXEC) && defined (F_SETFD)
+ if (fcntl (fd, F_SETFD, 1))
+ error (1, errno, "can't set close-on-exec flag on %d", fd);
+#endif
+}
diff --git a/gnu/dist/cvs/src/scramble.c b/gnu/dist/cvs/src/scramble.c
new file mode 100644
index 00000000000..77376faa82b
--- /dev/null
+++ b/gnu/dist/cvs/src/scramble.c
@@ -0,0 +1,245 @@
+/*
+ * Trivially encode strings to protect them from innocent eyes (i.e.,
+ * inadvertent password compromises, like a network administrator
+ * who's watching packets for legitimate reasons and accidentally sees
+ * the password protocol go by).
+ *
+ * This is NOT secure encryption.
+ *
+ * It would be tempting to encode the password according to username
+ * and repository, so that the same password would encode to a
+ * different string when used with different usernames and/or
+ * repositories. However, then users would not be able to cut and
+ * paste passwords around. They're not supposed to anyway, but we all
+ * know they will, and there's no reason to make it harder for them if
+ * we're not trying to provide real security anyway.
+ */
+
+/* Set this to test as a standalone program. */
+/* #define DIAGNOSTIC */
+
+#ifndef DIAGNOSTIC
+#include "cvs.h"
+#else /* ! DIAGNOSTIC */
+/* cvs.h won't define this for us */
+#define AUTH_CLIENT_SUPPORT
+#define xmalloc malloc
+/* Use "gcc -fwritable-strings". */
+#include <stdio.h>
+#include <stdio.h>
+#include <string.h>
+#endif /* ! DIAGNOSTIC */
+
+#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT)
+
+/* Map characters to each other randomly and symmetrically, A <--> B.
+ *
+ * We divide the ASCII character set into 3 domains: control chars (0
+ * thru 31), printing chars (32 through 126), and "meta"-chars (127
+ * through 255). The control chars map _to_ themselves, the printing
+ * chars map _among_ themselves, and the meta chars map _among_
+ * themselves. Why is this thus?
+ *
+ * No character in any of these domains maps to a character in another
+ * domain, because I'm not sure what characters are legal in
+ * passwords, or what tools people are likely to use to cut and paste
+ * them. It seems prudent not to introduce control or meta chars,
+ * unless the user introduced them first. And having the control
+ * chars all map to themselves insures that newline and
+ * carriage-return are safely handled.
+ */
+
+static unsigned char
+shifts[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
+ 111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
+ 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
+ 125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
+ 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
+ 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
+ 225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
+ 199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
+ 174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
+ 207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
+ 192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
+ 227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
+ 182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
+ 243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152 };
+
+
+/* SCRAMBLE and DESCRAMBLE work like this:
+ *
+ * scramble(STR) returns SCRM, a scrambled copy of STR. SCRM[0] is a
+ * single letter indicating the scrambling method. As of this
+ * writing, the only legal method is 'A', but check the code for more
+ * up-to-date information. The copy will have been allocated with
+ * malloc().
+ *
+ * descramble(SCRM) returns STR, again in its own malloc'd space.
+ * descramble() uses SCRM[0] to determine which method of unscrambling
+ * to use. If it does not recognize the method, it dies with error.
+ */
+
+/* Return a malloc'd, scrambled version of STR. */
+char *
+scramble (str)
+ char *str;
+{
+ int i;
+ char *s;
+
+ /* +2 to hold the 'A' prefix that indicates which version of
+ scrambling this is (the first, obviously, since we only do one
+ kind of scrambling so far), and then the '\0' of course. */
+ s = (char *) xmalloc (strlen (str) + 2);
+
+ /* Scramble (TM) version prefix. */
+ s[0] = 'A';
+ strcpy (s + 1, str);
+
+ for (i = 1; s[i]; i++)
+ s[i] = shifts[(unsigned char)(s[i])];
+
+ return s;
+}
+
+/* Decode the string in place. */
+char *
+descramble (str)
+ char *str;
+{
+ char *s;
+ int i;
+
+ /* For now we can only handle one kind of scrambling. In the future
+ there may be other kinds, and this `if' will become a `switch'. */
+ if (str[0] != 'A')
+#ifndef DIAGNOSTIC
+ error (1, 0, "descramble: unknown scrambling method");
+#else /* DIAGNOSTIC */
+ {
+ fprintf (stderr, "descramble: unknown scrambling method\n", str);
+ fflush (stderr);
+ exit (EXIT_FAILURE);
+ }
+#endif /* DIAGNOSTIC */
+
+ /* Method `A' is symmetrical, so scramble again to decrypt. */
+ s = scramble (str + 1);
+
+ /* Shift the whole string one char to the left, pushing the unwanted
+ 'A' off the left end. Safe, because s is null-terminated. */
+ for (i = 0; s[i]; i++)
+ s[i] = s[i + 1];
+
+ return s;
+}
+
+#endif /* (AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT) from top of file */
+
+#ifdef DIAGNOSTIC
+int
+main ()
+{
+ int i;
+ char *e, *m, biggie[256];
+
+ char *cleartexts[5];
+ cleartexts[0] = "first";
+ cleartexts[1] = "the second";
+ cleartexts[2] = "this is the third";
+ cleartexts[3] = "$#% !!\\3";
+ cleartexts[4] = biggie;
+
+ /* Set up the most important test string: */
+ /* Can't have a real ASCII zero in the string, because we want to
+ use printf, so we substitute the character zero. */
+ biggie[0] = '0';
+ /* The rest of the string gets straight ascending ASCII. */
+ for (i = 1; i < 256; i++)
+ biggie[i] = i;
+
+ /* Test all the strings. */
+ for (i = 0; i < 5; i++)
+ {
+ printf ("clear%d: %s\n", i, cleartexts[i]);
+ e = scramble (cleartexts[i]);
+ printf ("scram%d: %s\n", i, e);
+ m = descramble (e);
+ free (e);
+ printf ("clear%d: %s\n\n", i, m);
+ free (m);
+ }
+
+ fflush (stdout);
+ return 0;
+}
+#endif /* DIAGNOSTIC */
+
+/*
+ * ;;; The Emacs Lisp that did the dirty work ;;;
+ * (progn
+ *
+ * ;; Helper func.
+ * (defun random-elt (lst)
+ * (let* ((len (length lst))
+ * (rnd (random len)))
+ * (nth rnd lst)))
+ *
+ * ;; A list of all characters under 127, each appearing once.
+ * (setq non-meta-chars
+ * (let ((i 0)
+ * (l nil))
+ * (while (< i 127)
+ * (setq l (cons i l)
+ * i (1+ i)))
+ * l))
+ *
+ * ;; A list of all characters 127 and above, each appearing once.
+ * (setq meta-chars
+ * (let ((i 127)
+ * (l nil))
+ * (while (< i 256)
+ * (setq l (cons i l)
+ * i (1+ i)))
+ * l))
+ *
+ * ;; A vector that will hold the chars in a random order.
+ * (setq scrambled-chars (make-vector 256 0))
+ *
+ * ;; These characters should map to themselves.
+ * (let ((i 0))
+ * (while (< i 32)
+ * (aset scrambled-chars i i)
+ * (setq non-meta-chars (delete i non-meta-chars)
+ * i (1+ i))))
+ *
+ * ;; Assign random (but unique) values, within the non-meta chars.
+ * (let ((i 32))
+ * (while (< i 127)
+ * (let ((ch (random-elt non-meta-chars)))
+ * (if (= 0 (aref scrambled-chars i))
+ * (progn
+ * (aset scrambled-chars i ch)
+ * (aset scrambled-chars ch i)
+ * (setq non-meta-chars (delete ch non-meta-chars)
+ * non-meta-chars (delete i non-meta-chars))))
+ * (setq i (1+ i)))))
+ *
+ * ;; Assign random (but unique) values, within the non-meta chars.
+ * (let ((i 127))
+ * (while (< i 256)
+ * (let ((ch (random-elt meta-chars)))
+ * (if (= 0 (aref scrambled-chars i))
+ * (progn
+ * (aset scrambled-chars i ch)
+ * (aset scrambled-chars ch i)
+ * (setq meta-chars (delete ch meta-chars)
+ * meta-chars (delete i meta-chars))))
+ * (setq i (1+ i)))))
+ *
+ * ;; Now use the `scrambled-chars' vector to get your C array.
+ * )
+ */
diff --git a/gnu/dist/cvs/src/server.c b/gnu/dist/cvs/src/server.c
new file mode 100644
index 00000000000..1d1fe11b92f
--- /dev/null
+++ b/gnu/dist/cvs/src/server.c
@@ -0,0 +1,6376 @@
+/* This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+#include <assert.h>
+#include "cvs.h"
+#include "watch.h"
+#include "edit.h"
+#include "fileattr.h"
+#include "getline.h"
+#include "buffer.h"
+
+#ifdef SERVER_SUPPORT
+
+#ifdef HAVE_WINSOCK_H
+#include <winsock.h>
+#endif
+
+#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_KERBEROS) || defined (HAVE_GSSAPI)
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_KERBEROS
+# include <netinet/in.h>
+# include <krb.h>
+# ifndef HAVE_KRB_GET_ERR_TEXT
+# define krb_get_err_text(status) krb_err_txt[status]
+# endif
+
+/* Information we need if we are going to use Kerberos encryption. */
+static C_Block kblock;
+static Key_schedule sched;
+
+#endif
+
+#ifdef HAVE_GSSAPI
+
+#include <netdb.h>
+
+#ifdef HAVE_GSSAPI_H
+#include <gssapi.h>
+#endif
+#ifdef HAVE_GSSAPI_GSSAPI_H
+#include <gssapi/gssapi.h>
+#endif
+#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
+#include <gssapi/gssapi_generic.h>
+#endif
+
+#ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
+#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
+#endif
+
+/* We use Kerberos 5 routines to map the GSSAPI credential to a user
+ name. */
+#include <krb5.h>
+
+/* We need this to wrap data. */
+static gss_ctx_id_t gcontext;
+
+static void gserver_authenticate_connection PROTO((void));
+
+/* Whether we are already wrapping GSSAPI communication. */
+static int cvs_gssapi_wrapping;
+
+# ifdef ENCRYPTION
+/* Whether to encrypt GSSAPI communication. We use a global variable
+ like this because we use the same buffer type (gssapi_wrap) to
+ handle both authentication and encryption, and we don't want
+ multiple instances of that buffer in the communication stream. */
+int cvs_gssapi_encrypt;
+# endif
+
+#endif
+
+/* for select */
+#include <sys/types.h>
+#ifdef HAVE_SYS_BSDTYPES_H
+#include <sys/bsdtypes.h>
+#endif
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#ifndef O_NONBLOCK
+#define O_NONBLOCK O_NDELAY
+#endif
+
+/* EWOULDBLOCK is not defined by POSIX, but some BSD systems will
+ return it, rather than EAGAIN, for nonblocking writes. */
+#ifdef EWOULDBLOCK
+#define blocking_error(err) ((err) == EWOULDBLOCK || (err) == EAGAIN)
+#else
+#define blocking_error(err) ((err) == EAGAIN)
+#endif
+
+#ifdef AUTH_SERVER_SUPPORT
+#ifdef HAVE_GETSPNAM
+#include <shadow.h>
+#endif
+#endif /* AUTH_SERVER_SUPPORT */
+
+/* For initgroups(). */
+#if HAVE_INITGROUPS
+#include <grp.h>
+#endif /* HAVE_INITGROUPS */
+
+#ifdef AUTH_SERVER_SUPPORT
+
+/* The cvs username sent by the client, which might or might not be
+ the same as the system username the server eventually switches to
+ run as. CVS_Username gets set iff password authentication is
+ successful. */
+char *CVS_Username = NULL;
+
+/* Used to check that same repos is transmitted in pserver auth and in
+ later CVS protocol. Exported because root.c also uses. */
+static char *Pserver_Repos = NULL;
+
+/* Should we check for system usernames/passwords? Can be changed by
+ CVSROOT/config. */
+int system_auth = 1;
+
+#endif /* AUTH_SERVER_SUPPORT */
+
+
+/* While processing requests, this buffer accumulates data to be sent to
+ the client, and then once we are in do_cvs_command, we use it
+ for all the data to be sent. */
+static struct buffer *buf_to_net;
+
+/* This buffer is used to read input from the client. */
+static struct buffer *buf_from_net;
+
+/*
+ * This is where we stash stuff we are going to use. Format string
+ * which expects a single directory within it, starting with a slash.
+ */
+static char *server_temp_dir;
+
+/* This is the original value of server_temp_dir, before any possible
+ changes inserted by serve_max_dotdot. */
+static char *orig_server_temp_dir;
+
+/* Nonzero if we should keep the temp directory around after we exit. */
+static int dont_delete_temp;
+
+static void server_write_entries PROTO((void));
+
+/* All server communication goes through buffer structures. Most of
+ the buffers are built on top of a file descriptor. This structure
+ is used as the closure field in a buffer. */
+
+struct fd_buffer
+{
+ /* The file descriptor. */
+ int fd;
+ /* Nonzero if the file descriptor is in blocking mode. */
+ int blocking;
+};
+
+static struct buffer *fd_buffer_initialize
+ PROTO ((int, int, void (*) (struct buffer *)));
+static int fd_buffer_input PROTO((void *, char *, int, int, int *));
+static int fd_buffer_output PROTO((void *, const char *, int, int *));
+static int fd_buffer_flush PROTO((void *));
+static int fd_buffer_block PROTO((void *, int));
+
+/* Initialize a buffer built on a file descriptor. FD is the file
+ descriptor. INPUT is nonzero if this is for input, zero if this is
+ for output. MEMORY is the function to call when a memory error
+ occurs. */
+
+static struct buffer *
+fd_buffer_initialize (fd, input, memory)
+ int fd;
+ int input;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct fd_buffer *n;
+
+ n = (struct fd_buffer *) xmalloc (sizeof *n);
+ n->fd = fd;
+ n->blocking = 1;
+ return buf_initialize (input ? fd_buffer_input : NULL,
+ input ? NULL : fd_buffer_output,
+ input ? NULL : fd_buffer_flush,
+ fd_buffer_block,
+ (int (*) PROTO((void *))) NULL,
+ memory,
+ n);
+}
+
+/* The buffer input function for a buffer built on a file descriptor. */
+
+static int
+fd_buffer_input (closure, data, need, size, got)
+ void *closure;
+ char *data;
+ int need;
+ int size;
+ int *got;
+{
+ struct fd_buffer *fd = (struct fd_buffer *) closure;
+ int nbytes;
+
+ if (! fd->blocking)
+ nbytes = read (fd->fd, data, size);
+ else
+ {
+ /* This case is not efficient. Fortunately, I don't think it
+ ever actually happens. */
+ nbytes = read (fd->fd, data, need == 0 ? 1 : need);
+ }
+
+ if (nbytes > 0)
+ {
+ *got = nbytes;
+ return 0;
+ }
+
+ *got = 0;
+
+ if (nbytes == 0)
+ {
+ /* End of file. This assumes that we are using POSIX or BSD
+ style nonblocking I/O. On System V we will get a zero
+ return if there is no data, even when not at EOF. */
+ return -1;
+ }
+
+ /* Some error occurred. */
+
+ if (blocking_error (errno))
+ {
+ /* Everything's fine, we just didn't get any data. */
+ return 0;
+ }
+
+ return errno;
+}
+
+/* The buffer output function for a buffer built on a file descriptor. */
+
+static int
+fd_buffer_output (closure, data, have, wrote)
+ void *closure;
+ const char *data;
+ int have;
+ int *wrote;
+{
+ struct fd_buffer *fd = (struct fd_buffer *) closure;
+
+ *wrote = 0;
+
+ while (have > 0)
+ {
+ int nbytes;
+
+ nbytes = write (fd->fd, data, have);
+
+ if (nbytes <= 0)
+ {
+ if (! fd->blocking
+ && (nbytes == 0 || blocking_error (errno)))
+ {
+ /* A nonblocking write failed to write any data. Just
+ return. */
+ return 0;
+ }
+
+ /* Some sort of error occurred. */
+
+ if (nbytes == 0)
+ return EIO;
+
+ return errno;
+ }
+
+ *wrote += nbytes;
+ data += nbytes;
+ have -= nbytes;
+ }
+
+ return 0;
+}
+
+/* The buffer flush function for a buffer built on a file descriptor. */
+
+/*ARGSUSED*/
+static int
+fd_buffer_flush (closure)
+ void *closure;
+{
+ /* Nothing to do. File descriptors are always flushed. */
+ return 0;
+}
+
+/* The buffer block function for a buffer built on a file descriptor. */
+
+static int
+fd_buffer_block (closure, block)
+ void *closure;
+ int block;
+{
+ struct fd_buffer *fd = (struct fd_buffer *) closure;
+ int flags;
+
+ flags = fcntl (fd->fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+
+ if (block)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (fcntl (fd->fd, F_SETFL, flags) < 0)
+ return errno;
+
+ fd->blocking = block;
+
+ return 0;
+}
+
+/* Populate all of the directories between BASE_DIR and its relative
+ subdirectory DIR with CVSADM directories. Return 0 for success or
+ errno value. */
+static int create_adm_p PROTO((char *, char *));
+
+static int
+create_adm_p (base_dir, dir)
+ char *base_dir;
+ char *dir;
+{
+ char *dir_where_cvsadm_lives, *dir_to_register, *p, *tmp;
+ int retval, done;
+ FILE *f;
+
+ if (strcmp (dir, ".") == 0)
+ return 0; /* nothing to do */
+
+ /* Allocate some space for our directory-munging string. */
+ p = malloc (strlen (dir) + 1);
+ if (p == NULL)
+ return ENOMEM;
+
+ dir_where_cvsadm_lives = malloc (strlen (base_dir) + strlen (dir) + 100);
+ if (dir_where_cvsadm_lives == NULL)
+ return ENOMEM;
+
+ /* Allocate some space for the temporary string in which we will
+ construct filenames. */
+ tmp = malloc (strlen (base_dir) + strlen (dir) + 100);
+ if (tmp == NULL)
+ return ENOMEM;
+
+
+ /* We make several passes through this loop. On the first pass,
+ we simply create the CVSADM directory in the deepest directory.
+ For each subsequent pass, we try to remove the last path
+ element from DIR, create the CVSADM directory in the remaining
+ pathname, and register the subdirectory in the newly created
+ CVSADM directory. */
+
+ retval = done = 0;
+
+ strcpy (p, dir);
+ strcpy (dir_where_cvsadm_lives, base_dir);
+ strcat (dir_where_cvsadm_lives, "/");
+ strcat (dir_where_cvsadm_lives, p);
+ dir_to_register = NULL;
+
+ while (1)
+ {
+ /* Create CVSADM. */
+ (void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM);
+ if ((CVS_MKDIR (tmp, 0777) < 0) && (errno != EEXIST))
+ {
+ retval = errno;
+ goto finish;
+ }
+
+ /* Create CVSADM_REP. */
+ (void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_REP);
+ if (! isfile (tmp))
+ {
+ /* Use Emptydir as the placeholder until the client sends
+ us the real value. This code is similar to checkout.c
+ (emptydir_name), but the code below returns errors
+ differently. */
+
+ char *empty;
+ empty = malloc (strlen (CVSroot_directory)
+ + sizeof (CVSROOTADM)
+ + sizeof (CVSNULLREPOS)
+ + 10);
+ if (! empty)
+ {
+ retval = ENOMEM;
+ goto finish;
+ }
+
+ /* Create the directory name. */
+ (void) sprintf (empty, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSNULLREPOS);
+
+ /* Create the directory if it doesn't exist. */
+ if (! isfile (empty))
+ {
+ mode_t omask;
+ omask = umask (cvsumask);
+ if (CVS_MKDIR (empty, 0777) < 0)
+ {
+ retval = errno;
+ free (empty);
+ goto finish;
+ }
+ (void) umask (omask);
+ }
+
+
+ f = CVS_FOPEN (tmp, "w");
+ if (f == NULL)
+ {
+ retval = errno;
+ free (empty);
+ goto finish;
+ }
+ /* Write the directory name to CVSADM_REP. */
+ if (fprintf (f, "%s\n", empty) < 0)
+ {
+ retval = errno;
+ fclose (f);
+ free (empty);
+ goto finish;
+ }
+ if (fclose (f) == EOF)
+ {
+ retval = errno;
+ free (empty);
+ goto finish;
+ }
+
+ /* Clean up after ourselves. */
+ free (empty);
+ }
+
+ /* Create CVSADM_ENT. We open in append mode because we
+ don't want to clobber an existing Entries file. */
+ (void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_ENT);
+ f = CVS_FOPEN (tmp, "a");
+ if (f == NULL)
+ {
+ retval = errno;
+ goto finish;
+ }
+ if (fclose (f) == EOF)
+ {
+ retval = errno;
+ goto finish;
+ }
+
+ if (dir_to_register != NULL)
+ {
+ /* FIXME: Yes, this results in duplicate entries in the
+ Entries.Log file, but it doesn't currently matter. We
+ might need to change this later on to make sure that we
+ only write one entry. */
+
+ Subdir_Register ((List *) NULL, dir_where_cvsadm_lives,
+ dir_to_register);
+ }
+
+ if (done)
+ break;
+
+ dir_to_register = strrchr (p, '/');
+ if (dir_to_register == NULL)
+ {
+ dir_to_register = p;
+ strcpy (dir_where_cvsadm_lives, base_dir);
+ done = 1;
+ }
+ else
+ {
+ *dir_to_register = '\0';
+ dir_to_register++;
+ strcpy (dir_where_cvsadm_lives, base_dir);
+ strcat (dir_where_cvsadm_lives, "/");
+ strcat (dir_where_cvsadm_lives, p);
+ }
+ }
+
+ finish:
+ free (tmp);
+ free (dir_where_cvsadm_lives);
+ free (p);
+ return retval;
+}
+
+/*
+ * Make directory DIR, including all intermediate directories if necessary.
+ * Returns 0 for success or errno code.
+ */
+static int mkdir_p PROTO((char *));
+
+static int
+mkdir_p (dir)
+ char *dir;
+{
+ char *p;
+ char *q = malloc (strlen (dir) + 1);
+ int retval;
+
+ if (q == NULL)
+ return ENOMEM;
+
+ retval = 0;
+
+ /*
+ * Skip over leading slash if present. We won't bother to try to
+ * make '/'.
+ */
+ p = dir + 1;
+ while (1)
+ {
+ while (*p != '/' && *p != '\0')
+ ++p;
+ if (*p == '/')
+ {
+ strncpy (q, dir, p - dir);
+ q[p - dir] = '\0';
+ if (q[p - dir - 1] != '/' && CVS_MKDIR (q, 0777) < 0)
+ {
+ int saved_errno = errno;
+
+ if (saved_errno != EEXIST
+ && ((saved_errno != EACCES && saved_errno != EROFS)
+ || !isdir (q)))
+ {
+ retval = saved_errno;
+ goto done;
+ }
+ }
+ ++p;
+ }
+ else
+ {
+ if (CVS_MKDIR (dir, 0777) < 0)
+ retval = errno;
+ goto done;
+ }
+ }
+ done:
+ free (q);
+ return retval;
+}
+
+/*
+ * Print the error response for error code STATUS. The caller is
+ * reponsible for making sure we get back to the command loop without
+ * any further output occuring.
+ * Must be called only in contexts where it is OK to send output.
+ */
+static void
+print_error (status)
+ int status;
+{
+ char *msg;
+ buf_output0 (buf_to_net, "error ");
+ msg = strerror (status);
+ if (msg)
+ buf_output0 (buf_to_net, msg);
+ buf_append_char (buf_to_net, '\n');
+
+ buf_flush (buf_to_net, 0);
+}
+
+static int pending_error;
+/*
+ * Malloc'd text for pending error. Each line must start with "E ". The
+ * last line should not end with a newline.
+ */
+static char *pending_error_text;
+
+/* If an error is pending, print it and return 1. If not, return 0.
+ Must be called only in contexts where it is OK to send output. */
+static int
+print_pending_error ()
+{
+ if (pending_error_text)
+ {
+ buf_output0 (buf_to_net, pending_error_text);
+ buf_append_char (buf_to_net, '\n');
+ if (pending_error)
+ print_error (pending_error);
+ else
+ buf_output0 (buf_to_net, "error \n");
+
+ buf_flush (buf_to_net, 0);
+
+ pending_error = 0;
+ free (pending_error_text);
+ pending_error_text = NULL;
+ return 1;
+ }
+ else if (pending_error)
+ {
+ print_error (pending_error);
+ pending_error = 0;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+/* Is an error pending? */
+#define error_pending() (pending_error || pending_error_text)
+
+static int alloc_pending PROTO ((size_t size));
+
+/* Allocate SIZE bytes for pending_error_text and return nonzero
+ if we could do it. */
+static int
+alloc_pending (size)
+ size_t size;
+{
+ if (error_pending ())
+ /* Probably alloc_pending callers will have already checked for
+ this case. But we might as well handle it if they don't, I
+ guess. */
+ return 0;
+ pending_error_text = malloc (size);
+ if (pending_error_text == NULL)
+ {
+ pending_error = ENOMEM;
+ return 0;
+ }
+ return 1;
+}
+
+static void serve_is_modified PROTO ((char *));
+
+static int supported_response PROTO ((char *));
+
+static int
+supported_response (name)
+ char *name;
+{
+ struct response *rs;
+
+ for (rs = responses; rs->name != NULL; ++rs)
+ if (strcmp (rs->name, name) == 0)
+ return rs->status == rs_supported;
+ error (1, 0, "internal error: testing support for unknown response?");
+ /* NOTREACHED */
+ return 0;
+}
+
+static void
+serve_valid_responses (arg)
+ char *arg;
+{
+ char *p = arg;
+ char *q;
+ struct response *rs;
+ do
+ {
+ q = strchr (p, ' ');
+ if (q != NULL)
+ *q++ = '\0';
+ for (rs = responses; rs->name != NULL; ++rs)
+ {
+ if (strcmp (rs->name, p) == 0)
+ break;
+ }
+ if (rs->name == NULL)
+ /*
+ * It is a response we have never heard of (and thus never
+ * will want to use). So don't worry about it.
+ */
+ ;
+ else
+ rs->status = rs_supported;
+ p = q;
+ } while (q != NULL);
+ for (rs = responses; rs->name != NULL; ++rs)
+ {
+ if (rs->status == rs_essential)
+ {
+ buf_output0 (buf_to_net, "E response `");
+ buf_output0 (buf_to_net, rs->name);
+ buf_output0 (buf_to_net, "' not supported by client\nerror \n");
+
+ /* FIXME: This call to buf_flush could conceivably
+ cause deadlock, as noted in server_cleanup. */
+ buf_flush (buf_to_net, 1);
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+ else if (rs->status == rs_optional)
+ rs->status = rs_not_supported;
+ }
+}
+
+static void
+serve_root (arg)
+ char *arg;
+{
+ char *env;
+ char *path;
+ int save_errno;
+ char *arg_dup;
+
+ if (error_pending()) return;
+
+ if (!isabsolute (arg))
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E Root %s must be an absolute pathname", arg);
+ return;
+ }
+
+ /* Sending "Root" twice is illegal.
+
+ The other way to handle a duplicate Root requests would be as a
+ request to clear out all state and start over as if it was a
+ new connection. Doing this would cause interoperability
+ headaches, so it should be a different request, if there is
+ any reason why such a feature is needed. */
+ if (CVSroot_directory != NULL)
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E Protocol error: Duplicate Root request, for %s", arg);
+ return;
+ }
+
+#ifdef AUTH_SERVER_SUPPORT
+ if (Pserver_Repos != NULL)
+ {
+ if (strcmp (Pserver_Repos, arg) != 0)
+ {
+ if (alloc_pending (80 + strlen (Pserver_Repos) + strlen (arg)))
+ /* The explicitness is to aid people who are writing clients.
+ I don't see how this information could help an
+ attacker. */
+ sprintf (pending_error_text, "\
+E Protocol error: Root says \"%s\" but pserver says \"%s\"",
+ arg, Pserver_Repos);
+ }
+ }
+#endif
+ arg_dup = malloc (strlen (arg) + 1);
+ if (arg_dup == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (arg_dup, arg);
+ set_local_cvsroot (arg_dup);
+
+ /* For pserver, this will already have happened, and the call will do
+ nothing. But for rsh, we need to do it now. */
+ parse_config (CVSroot_directory);
+
+ path = malloc (strlen (CVSroot_directory)
+ + sizeof (CVSROOTADM)
+ + sizeof (CVSROOTADM_HISTORY)
+ + 10);
+ if (path == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ (void) sprintf (path, "%s/%s", CVSroot_directory, CVSROOTADM);
+ if (!isaccessible (path, R_OK | X_OK))
+ {
+ save_errno = errno;
+ pending_error_text = malloc (80 + strlen (path));
+ if (pending_error_text != NULL)
+ sprintf (pending_error_text, "E Cannot access %s", path);
+ pending_error = save_errno;
+ }
+ (void) strcat (path, "/");
+ (void) strcat (path, CVSROOTADM_HISTORY);
+ if (isfile (path) && !isaccessible (path, R_OK | W_OK))
+ {
+ save_errno = errno;
+ pending_error_text = malloc (80 + strlen (path));
+ if (pending_error_text != NULL)
+ sprintf (pending_error_text, "E \
+Sorry, you don't have read/write access to the history file %s", path);
+ pending_error = save_errno;
+ }
+ free (path);
+
+#ifdef HAVE_PUTENV
+ env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot_directory) + 1 + 1);
+ if (env == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot_directory);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+#endif
+}
+
+static int max_dotdot_limit = 0;
+
+/* Is this pathname OK to recurse into when we are running as the server?
+ If not, call error() with a fatal error. */
+void
+server_pathname_check (path)
+ char *path;
+{
+ /* An absolute pathname is almost surely a path on the *client* machine,
+ and is unlikely to do us any good here. It also is probably capable
+ of being a security hole in the anonymous readonly case. */
+ if (isabsolute (path))
+ /* Giving an error is actually kind of a cop-out, in the sense
+ that it would be nice for "cvs co -d /foo/bar/baz" to work.
+ A quick fix in the server would be requiring Max-dotdot of
+ at least one if pathnames are absolute, and then putting
+ /abs/foo/bar/baz in the temp dir beside the /d/d/d stuff.
+ A cleaner fix in the server might be to decouple the
+ pathnames we pass back to the client from pathnames in our
+ temp directory (this would also probably remove the need
+ for Max-dotdot). A fix in the client would have the client
+ turn it into "cd /foo/bar; cvs co -d baz" (more or less).
+ This probably has some problems with pathnames which appear
+ in messages. */
+ error (1, 0, "absolute pathname `%s' illegal for server", path);
+ if (pathname_levels (path) > max_dotdot_limit)
+ {
+ /* Similar to the isabsolute case in security implications. */
+ error (0, 0, "protocol error: `%s' contains more leading ..", path);
+ error (1, 0, "than the %d which Max-dotdot specified",
+ max_dotdot_limit);
+ }
+}
+
+static int outside_root PROTO ((char *));
+
+/* Is file or directory REPOS an absolute pathname within the
+ CVSroot_directory? If yes, return 0. If no, set pending_error
+ and return 1. */
+static int
+outside_root (repos)
+ char *repos;
+{
+ size_t repos_len = strlen (repos);
+ size_t root_len = strlen (CVSroot_directory);
+
+ /* I think isabsolute (repos) should always be true, and that
+ any RELATIVE_REPOS stuff should only be in CVS/Repository
+ files, not the protocol (for compatibility), but I'm putting
+ in the isabsolute check just in case. */
+ if (!isabsolute (repos))
+ {
+ if (alloc_pending (repos_len + 80))
+ sprintf (pending_error_text, "\
+E protocol error: %s is not absolute", repos);
+ return 1;
+ }
+
+ if (repos_len < root_len
+ || strncmp (CVSroot_directory, repos, root_len) != 0)
+ {
+ not_within:
+ if (alloc_pending (strlen (CVSroot_directory)
+ + strlen (repos)
+ + 80))
+ sprintf (pending_error_text, "\
+E protocol error: directory '%s' not within root '%s'",
+ repos, CVSroot_directory);
+ return 1;
+ }
+ if (repos_len > root_len)
+ {
+ if (repos[root_len] != '/')
+ goto not_within;
+ if (pathname_levels (repos + root_len + 1) > 0)
+ goto not_within;
+ }
+ return 0;
+}
+
+static int outside_dir PROTO ((char *));
+
+/* Is file or directory FILE outside the current directory (that is, does
+ it contain '/')? If no, return 0. If yes, set pending_error
+ and return 1. */
+static int
+outside_dir (file)
+ char *file;
+{
+ if (strchr (file, '/') != NULL)
+ {
+ if (alloc_pending (strlen (file)
+ + 80))
+ sprintf (pending_error_text, "\
+E protocol error: directory '%s' not within current directory",
+ file);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Add as many directories to the temp directory as the client tells us it
+ * will use "..", so we never try to access something outside the temp
+ * directory via "..".
+ */
+static void
+serve_max_dotdot (arg)
+ char *arg;
+{
+ int lim = atoi (arg);
+ int i;
+ char *p;
+
+ if (lim < 0)
+ return;
+ p = malloc (strlen (server_temp_dir) + 2 * lim + 10);
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (p, server_temp_dir);
+ for (i = 0; i < lim; ++i)
+ strcat (p, "/d");
+ if (server_temp_dir != orig_server_temp_dir)
+ free (server_temp_dir);
+ server_temp_dir = p;
+ max_dotdot_limit = lim;
+}
+
+static char *dir_name;
+
+static void
+dirswitch (dir, repos)
+ char *dir;
+ char *repos;
+{
+ int status;
+ FILE *f;
+ size_t dir_len;
+
+ server_write_entries ();
+
+ if (error_pending()) return;
+
+ /* Check for bad directory name.
+
+ FIXME: could/should unify these checks with server_pathname_check
+ except they need to report errors differently. */
+ if (isabsolute (dir))
+ {
+ if (alloc_pending (80 + strlen (dir)))
+ sprintf (pending_error_text,
+ "E absolute pathname `%s' illegal for server", dir);
+ return;
+ }
+ if (pathname_levels (dir) > max_dotdot_limit)
+ {
+ if (alloc_pending (80 + strlen (dir)))
+ sprintf (pending_error_text,
+ "E protocol error: `%s' has too many ..", dir);
+ return;
+ }
+
+ if (dir_name != NULL)
+ free (dir_name);
+
+ dir_len = strlen (dir);
+
+ /* Check for a trailing '/'. This is not ISDIRSEP because \ in the
+ protocol is an ordinary character, not a directory separator (of
+ course, it is perhaps unwise to use it in directory names, but that
+ is another issue). */
+ if (dir_len > 0
+ && dir[dir_len - 1] == '/')
+ {
+ if (alloc_pending (80 + dir_len))
+ sprintf (pending_error_text,
+ "E protocol error: invalid directory syntax in %s", dir);
+ return;
+ }
+
+ dir_name = malloc (strlen (server_temp_dir) + dir_len + 40);
+ if (dir_name == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+
+ strcpy (dir_name, server_temp_dir);
+ strcat (dir_name, "/");
+ strcat (dir_name, dir);
+
+ status = mkdir_p (dir_name);
+ if (status != 0
+ && status != EEXIST)
+ {
+ pending_error = status;
+ if (alloc_pending (80 + strlen (dir_name)))
+ sprintf (pending_error_text, "E cannot mkdir %s", dir_name);
+ return;
+ }
+
+ /* We need to create adm directories in all path elements because
+ we want the server to descend them, even if the client hasn't
+ sent the appropriate "Argument xxx" command to match the
+ already-sent "Directory xxx" command. See recurse.c
+ (start_recursion) for a big discussion of this. */
+
+ status = create_adm_p (server_temp_dir, dir);
+ if (status != 0)
+ {
+ pending_error = status;
+ if (alloc_pending (80 + strlen (dir_name)))
+ sprintf (pending_error_text, "E cannot create_adm_p %s", dir_name);
+ return;
+ }
+
+ if ( CVS_CHDIR (dir_name) < 0)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (dir_name)))
+ sprintf (pending_error_text, "E cannot change to %s", dir_name);
+ return;
+ }
+ /*
+ * This is pretty much like calling Create_Admin, but Create_Admin doesn't
+ * report errors in the right way for us.
+ */
+ if ((CVS_MKDIR (CVSADM, 0777) < 0) && (errno != EEXIST))
+ {
+ pending_error = errno;
+ return;
+ }
+
+ /* The following will overwrite the contents of CVSADM_REP. This
+ is the correct behavior -- mkdir_p may have written a
+ placeholder value to this file and we need to insert the
+ correct value. */
+
+ f = CVS_FOPEN (CVSADM_REP, "w");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ return;
+ }
+ if (fprintf (f, "%s", repos) < 0)
+ {
+ pending_error = errno;
+ fclose (f);
+ return;
+ }
+ /* Non-remote CVS handles a module representing the entire tree
+ (e.g., an entry like ``world -a .'') by putting /. at the end
+ of the Repository file, so we do the same. */
+ if (strcmp (dir, ".") == 0
+ && CVSroot_directory != NULL
+ && strcmp (CVSroot_directory, repos) == 0)
+ {
+ if (fprintf (f, "/.") < 0)
+ {
+ pending_error = errno;
+ fclose (f);
+ return;
+ }
+ }
+ if (fprintf (f, "\n") < 0)
+ {
+ pending_error = errno;
+ fclose (f);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ return;
+ }
+ /* We open in append mode because we don't want to clobber an
+ existing Entries file. */
+ f = CVS_FOPEN (CVSADM_ENT, "a");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
+ return;
+ }
+}
+
+static void
+serve_repository (arg)
+ char *arg;
+{
+ pending_error_text = malloc (80);
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
+ else
+ strcpy (pending_error_text,
+ "E Repository request is obsolete; aborted");
+ return;
+}
+
+static void
+serve_directory (arg)
+ char *arg;
+{
+ int status;
+ char *repos;
+
+ status = buf_read_line (buf_from_net, &repos, (int *) NULL);
+ if (status == 0)
+ {
+ if (outside_root (repos))
+ return;
+ dirswitch (arg, repos);
+ free (repos);
+ }
+ else if (status == -2)
+ {
+ pending_error = ENOMEM;
+ }
+ else
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text == NULL)
+ {
+ pending_error = ENOMEM;
+ }
+ else if (status == -1)
+ {
+ sprintf (pending_error_text,
+ "E end of file reading mode for %s", arg);
+ }
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading mode for %s", arg);
+ pending_error = status;
+ }
+ }
+}
+
+static void
+serve_static_directory (arg)
+ char *arg;
+{
+ FILE *f;
+
+ if (error_pending ()) return;
+
+ f = CVS_FOPEN (CVSADM_ENTSTAT, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
+ return;
+ }
+}
+
+static void
+serve_sticky (arg)
+ char *arg;
+{
+ FILE *f;
+
+ if (error_pending ()) return;
+
+ f = CVS_FOPEN (CVSADM_TAG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_TAG)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_TAG);
+ return;
+ }
+ if (fprintf (f, "%s\n", arg) < 0)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_TAG)))
+ sprintf (pending_error_text, "E cannot write to %s", CVSADM_TAG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_TAG)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_TAG);
+ return;
+ }
+}
+
+/*
+ * Read SIZE bytes from buf_from_net, write them to FILE.
+ *
+ * Currently this isn't really used for receiving parts of a file --
+ * the file is still sent over in one chunk. But if/when we get
+ * spiffy in-process gzip support working, perhaps the compressed
+ * pieces could be sent over as they're ready, if the network is fast
+ * enough. Or something.
+ */
+static void
+receive_partial_file (size, file)
+ int size;
+ int file;
+{
+ while (size > 0)
+ {
+ int status, nread;
+ char *data;
+
+ status = buf_read_data (buf_from_net, size, &data, &nread);
+ if (status != 0)
+ {
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
+ {
+ pending_error_text = malloc (80);
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
+ else if (status == -1)
+ {
+ sprintf (pending_error_text,
+ "E premature end of file from client");
+ pending_error = 0;
+ }
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading from client");
+ pending_error = status;
+ }
+ }
+ return;
+ }
+
+ size -= nread;
+
+ while (nread > 0)
+ {
+ int nwrote;
+
+ nwrote = write (file, data, nread);
+ if (nwrote < 0)
+ {
+ pending_error_text = malloc (40);
+ if (pending_error_text != NULL)
+ sprintf (pending_error_text, "E unable to write");
+ pending_error = errno;
+
+ /* Read and discard the file data. */
+ while (size > 0)
+ {
+ int status, nread;
+ char *data;
+
+ status = buf_read_data (buf_from_net, size, &data, &nread);
+ if (status != 0)
+ return;
+ size -= nread;
+ }
+
+ return;
+ }
+ nread -= nwrote;
+ data += nwrote;
+ }
+ }
+}
+
+/* Receive SIZE bytes, write to filename FILE. */
+static void
+receive_file (size, file, gzipped)
+ int size;
+ char *file;
+ int gzipped;
+{
+ int fd;
+ char *arg = file;
+
+ /* Write the file. */
+ fd = CVS_OPEN (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd < 0)
+ {
+ pending_error_text = malloc (40 + strlen (arg));
+ if (pending_error_text)
+ sprintf (pending_error_text, "E cannot open %s", arg);
+ pending_error = errno;
+ return;
+ }
+
+ if (gzipped)
+ {
+ /* Using gunzip_and_write isn't really a high-performance
+ approach, because it keeps the whole thing in memory
+ (contiguous memory, worse yet). But it seems easier to
+ code than the alternative (and less vulnerable to subtle
+ bugs). Given that this feature is mainly for
+ compatibility, that is the better tradeoff. */
+
+ int toread = size;
+ char *filebuf;
+ char *p;
+
+ filebuf = malloc (size);
+ p = filebuf;
+ /* If NULL, we still want to read the data and discard it. */
+
+ while (toread > 0)
+ {
+ int status, nread;
+ char *data;
+
+ status = buf_read_data (buf_from_net, toread, &data, &nread);
+ if (status != 0)
+ {
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
+ {
+ pending_error_text = malloc (80);
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
+ else if (status == -1)
+ {
+ sprintf (pending_error_text,
+ "E premature end of file from client");
+ pending_error = 0;
+ }
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading from client");
+ pending_error = status;
+ }
+ }
+ return;
+ }
+
+ toread -= nread;
+
+ if (filebuf != NULL)
+ {
+ memcpy (p, data, nread);
+ p += nread;
+ }
+ }
+ if (filebuf == NULL)
+ {
+ pending_error = ENOMEM;
+ goto out;
+ }
+
+ if (gunzip_and_write (fd, file, filebuf, size))
+ {
+ if (alloc_pending (80))
+ sprintf (pending_error_text,
+ "E aborting due to compression error");
+ }
+ free (filebuf);
+ }
+ else
+ receive_partial_file (size, fd);
+
+ if (pending_error_text)
+ {
+ char *p = realloc (pending_error_text,
+ strlen (pending_error_text) + strlen (arg) + 30);
+ if (p)
+ {
+ pending_error_text = p;
+ sprintf (p + strlen (p), ", file %s", arg);
+ }
+ /* else original string is supposed to be unchanged */
+ }
+
+ out:
+ if (close (fd) < 0 && !error_pending ())
+ {
+ pending_error_text = malloc (40 + strlen (arg));
+ if (pending_error_text)
+ sprintf (pending_error_text, "E cannot close %s", arg);
+ pending_error = errno;
+ return;
+ }
+}
+
+/* Kopt for the next file sent in Modified or Is-modified. */
+static char *kopt;
+
+/* Timestamp (Checkin-time) for next file sent in Modified or
+ Is-modified. */
+static int checkin_time_valid;
+static time_t checkin_time;
+
+static void serve_modified PROTO ((char *));
+
+static void
+serve_modified (arg)
+ char *arg;
+{
+ int size, status;
+ char *size_text;
+ char *mode_text;
+
+ int gzipped = 0;
+
+ /*
+ * This used to return immediately if error_pending () was true.
+ * However, that fails, because it causes each line of the file to
+ * be echoed back to the client as an unrecognized command. The
+ * client isn't reading from the socket, so eventually both
+ * processes block trying to write to the other. Now, we try to
+ * read the file if we can.
+ */
+
+ status = buf_read_line (buf_from_net, &mode_text, (int *) NULL);
+ if (status != 0)
+ {
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
+ else
+ {
+ if (status == -1)
+ sprintf (pending_error_text,
+ "E end of file reading mode for %s", arg);
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading mode for %s", arg);
+ pending_error = status;
+ }
+ }
+ }
+ return;
+ }
+
+ status = buf_read_line (buf_from_net, &size_text, (int *) NULL);
+ if (status != 0)
+ {
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
+ else
+ {
+ if (status == -1)
+ sprintf (pending_error_text,
+ "E end of file reading size for %s", arg);
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading size for %s", arg);
+ pending_error = errno;
+ }
+ }
+ }
+ return;
+ }
+ if (size_text[0] == 'z')
+ {
+ gzipped = 1;
+ size = atoi (size_text + 1);
+ }
+ else
+ size = atoi (size_text);
+ free (size_text);
+
+ if (error_pending ())
+ {
+ /* Now that we know the size, read and discard the file data. */
+ while (size > 0)
+ {
+ int status, nread;
+ char *data;
+
+ status = buf_read_data (buf_from_net, size, &data, &nread);
+ if (status != 0)
+ return;
+ size -= nread;
+ }
+ return;
+ }
+
+ if (outside_dir (arg))
+ return;
+
+ if (size >= 0)
+ {
+ receive_file (size, arg, gzipped);
+ if (error_pending ()) return;
+ }
+
+ if (checkin_time_valid)
+ {
+ struct utimbuf t;
+
+ memset (&t, 0, sizeof (t));
+ t.modtime = t.actime = checkin_time;
+ if (utime (arg, &t) < 0)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text, "E cannot utime %s", arg);
+ return;
+ }
+ checkin_time_valid = 0;
+ }
+
+ {
+ int status = change_mode (arg, mode_text, 0);
+ free (mode_text);
+ if (status)
+ {
+ pending_error_text = malloc (40 + strlen (arg));
+ if (pending_error_text)
+ sprintf (pending_error_text,
+ "E cannot change mode for %s", arg);
+ pending_error = status;
+ return;
+ }
+ }
+
+ /* Make sure that the Entries indicate the right kopt. We probably
+ could do this even in the non-kopt case and, I think, save a stat()
+ call in time_stamp_server. But for conservatism I'm leaving the
+ non-kopt case alone. */
+ if (kopt != NULL)
+ serve_is_modified (arg);
+}
+
+
+static void
+serve_enable_unchanged (arg)
+ char *arg;
+{
+}
+
+struct an_entry {
+ struct an_entry *next;
+ char *entry;
+};
+
+static struct an_entry *entries;
+
+static void serve_unchanged PROTO ((char *));
+
+static void
+serve_unchanged (arg)
+ char *arg;
+{
+ struct an_entry *p;
+ char *name;
+ char *cp;
+ char *timefield;
+
+ if (error_pending ())
+ return;
+
+ if (outside_dir (arg))
+ return;
+
+ /* Rewrite entries file to have `=' in timestamp field. */
+ for (p = entries; p != NULL; p = p->next)
+ {
+ name = p->entry + 1;
+ cp = strchr (name, '/');
+ if (cp != NULL
+ && strlen (arg) == cp - name
+ && strncmp (arg, name, cp - name) == 0)
+ {
+ timefield = strchr (cp + 1, '/') + 1;
+ if (*timefield != '=')
+ {
+ cp = timefield + strlen (timefield);
+ cp[1] = '\0';
+ while (cp > timefield)
+ {
+ *cp = cp[-1];
+ --cp;
+ }
+ *timefield = '=';
+ }
+ break;
+ }
+ }
+}
+
+static void
+serve_is_modified (arg)
+ char *arg;
+{
+ struct an_entry *p;
+ char *name;
+ char *cp;
+ char *timefield;
+ /* Have we found this file in "entries" yet. */
+ int found;
+
+ if (error_pending ())
+ return;
+
+ if (outside_dir (arg))
+ return;
+
+ /* Rewrite entries file to have `M' in timestamp field. */
+ found = 0;
+ for (p = entries; p != NULL; p = p->next)
+ {
+ name = p->entry + 1;
+ cp = strchr (name, '/');
+ if (cp != NULL
+ && strlen (arg) == cp - name
+ && strncmp (arg, name, cp - name) == 0)
+ {
+ timefield = strchr (cp + 1, '/') + 1;
+ if (!(timefield[0] == 'M' && timefield[1] == '/'))
+ {
+ cp = timefield + strlen (timefield);
+ cp[1] = '\0';
+ while (cp > timefield)
+ {
+ *cp = cp[-1];
+ --cp;
+ }
+ *timefield = 'M';
+ }
+ if (kopt != NULL)
+ {
+ if (alloc_pending (strlen (name) + 80))
+ sprintf (pending_error_text,
+ "E protocol error: both Kopt and Entry for %s",
+ arg);
+ free (kopt);
+ kopt = NULL;
+ return;
+ }
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ {
+ /* We got Is-modified but no Entry. Add a dummy entry.
+ The "D" timestamp is what makes it a dummy. */
+ p = (struct an_entry *) malloc (sizeof (struct an_entry));
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ p->entry = malloc (strlen (arg) + 80);
+ if (p->entry == NULL)
+ {
+ pending_error = ENOMEM;
+ free (p);
+ return;
+ }
+ strcpy (p->entry, "/");
+ strcat (p->entry, arg);
+ strcat (p->entry, "//D/");
+ if (kopt != NULL)
+ {
+ strcat (p->entry, kopt);
+ free (kopt);
+ kopt = NULL;
+ }
+ strcat (p->entry, "/");
+ p->next = entries;
+ entries = p;
+ }
+}
+
+static void serve_entry PROTO ((char *));
+
+static void
+serve_entry (arg)
+ char *arg;
+{
+ struct an_entry *p;
+ char *cp;
+ if (error_pending()) return;
+ p = (struct an_entry *) malloc (sizeof (struct an_entry));
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ /* Leave space for serve_unchanged to write '=' if it wants. */
+ cp = malloc (strlen (arg) + 2);
+ if (cp == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (cp, arg);
+ p->next = entries;
+ p->entry = cp;
+ entries = p;
+}
+
+static void serve_kopt PROTO ((char *));
+
+static void
+serve_kopt (arg)
+ char *arg;
+{
+ if (error_pending ())
+ return;
+
+ if (kopt != NULL)
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E protocol error: duplicate Kopt request: %s", arg);
+ return;
+ }
+
+ /* Do some sanity checks. In particular, that it is not too long.
+ This lets the rest of the code not worry so much about buffer
+ overrun attacks. Probably should call RCS_check_kflag here,
+ but that would mean changing RCS_check_kflag to handle errors
+ other than via exit(), fprintf(), and such. */
+ if (strlen (arg) > 10)
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E protocol error: invalid Kopt request: %s", arg);
+ return;
+ }
+
+ kopt = malloc (strlen (arg) + 1);
+ if (kopt == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (kopt, arg);
+}
+
+static void serve_checkin_time PROTO ((char *));
+
+static void
+serve_checkin_time (arg)
+ char *arg;
+{
+ if (error_pending ())
+ return;
+
+ if (checkin_time_valid)
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E protocol error: duplicate Checkin-time request: %s",
+ arg);
+ return;
+ }
+
+ checkin_time = get_date (arg, NULL);
+ if (checkin_time == (time_t)-1)
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text, "E cannot parse date %s", arg);
+ return;
+ }
+ checkin_time_valid = 1;
+}
+
+static void
+server_write_entries ()
+{
+ FILE *f;
+ struct an_entry *p;
+ struct an_entry *q;
+
+ if (entries == NULL)
+ return;
+
+ f = NULL;
+ /* Note that we free all the entries regardless of errors. */
+ if (!error_pending ())
+ {
+ /* We open in append mode because we don't want to clobber an
+ existing Entries file. If we are checking out a module
+ which explicitly lists more than one file in a particular
+ directory, then we will wind up calling
+ server_write_entries for each such file. */
+ f = CVS_FOPEN (CVSADM_ENT, "a");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
+ }
+ }
+ for (p = entries; p != NULL;)
+ {
+ if (!error_pending ())
+ {
+ if (fprintf (f, "%s\n", p->entry) < 0)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen(CVSADM_ENT)))
+ sprintf (pending_error_text,
+ "E cannot write to %s", CVSADM_ENT);
+ }
+ }
+ free (p->entry);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+ entries = NULL;
+ if (f != NULL && fclose (f) == EOF && !error_pending ())
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
+ }
+}
+
+struct notify_note {
+ /* Directory in which this notification happens. malloc'd*/
+ char *dir;
+
+ /* malloc'd. */
+ char *filename;
+
+ /* The following three all in one malloc'd block, pointed to by TYPE.
+ Each '\0' terminated. */
+ /* "E" or "U". */
+ char *type;
+ /* time+host+dir */
+ char *val;
+ char *watches;
+
+ struct notify_note *next;
+};
+
+static struct notify_note *notify_list;
+/* Used while building list, to point to the last node that already exists. */
+static struct notify_note *last_node;
+
+static void serve_notify PROTO ((char *));
+
+static void
+serve_notify (arg)
+ char *arg;
+{
+ struct notify_note *new;
+ char *data;
+ int status;
+
+ if (error_pending ()) return;
+
+ if (outside_dir (arg))
+ return;
+
+ new = (struct notify_note *) malloc (sizeof (struct notify_note));
+ if (new == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ if (dir_name == NULL)
+ goto error;
+ new->dir = malloc (strlen (dir_name) + 1);
+ if (new->dir == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (new->dir, dir_name);
+ new->filename = malloc (strlen (arg) + 1);
+ if (new->filename == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (new->filename, arg);
+
+ status = buf_read_line (buf_from_net, &data, (int *) NULL);
+ if (status != 0)
+ {
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
+ else
+ {
+ if (status == -1)
+ sprintf (pending_error_text,
+ "E end of file reading notification for %s", arg);
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading notification for %s", arg);
+ pending_error = status;
+ }
+ }
+ }
+ }
+ else
+ {
+ char *cp;
+
+ if (strchr (data, '+'))
+ goto error;
+
+ new->type = data;
+ if (data[1] != '\t')
+ goto error;
+ data[1] = '\0';
+ cp = data + 2;
+ new->val = cp;
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ goto error;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ goto error;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ goto error;
+ *cp++ = '\0';
+ new->watches = cp;
+ /* If there is another tab, ignore everything after it,
+ for future expansion. */
+ cp = strchr (cp, '\t');
+ if (cp != NULL)
+ {
+ *cp = '\0';
+ }
+
+ new->next = NULL;
+
+ if (last_node == NULL)
+ {
+ notify_list = new;
+ }
+ else
+ last_node->next = new;
+ last_node = new;
+ }
+ return;
+ error:
+ pending_error_text = malloc (80);
+ if (pending_error_text)
+ strcpy (pending_error_text,
+ "E Protocol error; misformed Notify request");
+ pending_error = 0;
+ return;
+}
+
+/* Process all the Notify requests that we have stored up. Returns 0
+ if successful, if not prints error message (via error()) and
+ returns negative value. */
+static int
+server_notify ()
+{
+ struct notify_note *p;
+ char *repos;
+
+ while (notify_list != NULL)
+ {
+ if ( CVS_CHDIR (notify_list->dir) < 0)
+ {
+ error (0, errno, "cannot change to %s", notify_list->dir);
+ return -1;
+ }
+ repos = Name_Repository (NULL, NULL);
+
+ lock_dir_for_write (repos);
+
+ fileattr_startdir (repos);
+
+ notify_do (*notify_list->type, notify_list->filename, getcaller(),
+ notify_list->val, notify_list->watches, repos);
+
+ buf_output0 (buf_to_net, "Notified ");
+ {
+ char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
+ if (dir[0] == '\0')
+ buf_append_char (buf_to_net, '.');
+ else
+ buf_output0 (buf_to_net, dir);
+ buf_append_char (buf_to_net, '/');
+ buf_append_char (buf_to_net, '\n');
+ }
+ buf_output0 (buf_to_net, repos);
+ buf_append_char (buf_to_net, '/');
+ buf_output0 (buf_to_net, notify_list->filename);
+ buf_append_char (buf_to_net, '\n');
+
+ p = notify_list->next;
+ free (notify_list->filename);
+ free (notify_list->dir);
+ free (notify_list->type);
+ free (notify_list);
+ notify_list = p;
+
+ fileattr_write ();
+ fileattr_free ();
+
+ Lock_Cleanup ();
+ }
+
+ last_node = NULL;
+
+ /* The code used to call fflush (stdout) here, but that is no
+ longer necessary. The data is now buffered in buf_to_net,
+ which will be flushed by the caller, do_cvs_command. */
+
+ return 0;
+}
+
+static int argument_count;
+static char **argument_vector;
+static int argument_vector_size;
+
+static void
+serve_argument (arg)
+ char *arg;
+{
+ char *p;
+
+ if (error_pending()) return;
+
+ if (argument_vector_size <= argument_count)
+ {
+ argument_vector_size *= 2;
+ argument_vector =
+ (char **) realloc ((char *)argument_vector,
+ argument_vector_size * sizeof (char *));
+ if (argument_vector == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ }
+ p = malloc (strlen (arg) + 1);
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (p, arg);
+ argument_vector[argument_count++] = p;
+}
+
+static void
+serve_argumentx (arg)
+ char *arg;
+{
+ char *p;
+
+ if (error_pending()) return;
+
+ p = argument_vector[argument_count - 1];
+ p = realloc (p, strlen (p) + 1 + strlen (arg) + 1);
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcat (p, "\n");
+ strcat (p, arg);
+ argument_vector[argument_count - 1] = p;
+}
+
+static void
+serve_global_option (arg)
+ char *arg;
+{
+ if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0')
+ {
+ error_return:
+ if (alloc_pending (strlen (arg) + 80))
+ sprintf (pending_error_text,
+ "E Protocol error: bad global option %s",
+ arg);
+ return;
+ }
+ switch (arg[1])
+ {
+ case 'n':
+ noexec = 1;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'r':
+ cvswrite = 0;
+ break;
+ case 'Q':
+ really_quiet = 1;
+ break;
+ case 'l':
+ logoff = 1;
+ break;
+ case 't':
+ trace = 1;
+ break;
+ default:
+ goto error_return;
+ }
+}
+
+static void
+serve_set (arg)
+ char *arg;
+{
+ /* FIXME: This sends errors immediately (I think); they should be
+ put into pending_error. */
+ variable_set (arg);
+}
+
+#ifdef ENCRYPTION
+
+#ifdef HAVE_KERBEROS
+
+static void
+serve_kerberos_encrypt (arg)
+ char *arg;
+{
+ /* All future communication with the client will be encrypted. */
+
+ buf_to_net = krb_encrypt_buffer_initialize (buf_to_net, 0, sched,
+ kblock,
+ buf_to_net->memory_error);
+ buf_from_net = krb_encrypt_buffer_initialize (buf_from_net, 1, sched,
+ kblock,
+ buf_from_net->memory_error);
+}
+
+#endif /* HAVE_KERBEROS */
+
+#ifdef HAVE_GSSAPI
+
+static void
+serve_gssapi_encrypt (arg)
+ char *arg;
+{
+ if (cvs_gssapi_wrapping)
+ {
+ /* We're already using a gssapi_wrap buffer for stream
+ authentication. Flush everything we've output so far, and
+ turn on encryption for future data. On the input side, we
+ should only have unwrapped as far as the Gssapi-encrypt
+ command, so future unwrapping will become encrypted. */
+ buf_flush (buf_to_net, 1);
+ cvs_gssapi_encrypt = 1;
+ return;
+ }
+
+ /* All future communication with the client will be encrypted. */
+
+ cvs_gssapi_encrypt = 1;
+
+ buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0,
+ gcontext,
+ buf_to_net->memory_error);
+ buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1,
+ gcontext,
+ buf_from_net->memory_error);
+
+ cvs_gssapi_wrapping = 1;
+}
+
+#endif /* HAVE_GSSAPI */
+
+#endif /* ENCRYPTION */
+
+#ifdef HAVE_GSSAPI
+
+static void
+serve_gssapi_authenticate (arg)
+ char *arg;
+{
+ if (cvs_gssapi_wrapping)
+ {
+ /* We're already using a gssapi_wrap buffer for encryption.
+ That includes authentication, so we don't have to do
+ anything further. */
+ return;
+ }
+
+ buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0,
+ gcontext,
+ buf_to_net->memory_error);
+ buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1,
+ gcontext,
+ buf_from_net->memory_error);
+
+ cvs_gssapi_wrapping = 1;
+}
+
+#endif /* HAVE_GSSAPI */
+
+#ifdef SERVER_FLOWCONTROL
+/* The maximum we'll queue to the remote client before blocking. */
+# ifndef SERVER_HI_WATER
+# define SERVER_HI_WATER (2 * 1024 * 1024)
+# endif /* SERVER_HI_WATER */
+/* When the buffer drops to this, we restart the child */
+# ifndef SERVER_LO_WATER
+# define SERVER_LO_WATER (1 * 1024 * 1024)
+# endif /* SERVER_LO_WATER */
+
+static int set_nonblock_fd PROTO((int));
+
+/*
+ * Set buffer BUF to non-blocking I/O. Returns 0 for success or errno
+ * code.
+ */
+
+static int
+set_nonblock_fd (fd)
+ int fd;
+{
+ int flags;
+
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+ if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ return errno;
+ return 0;
+}
+
+#endif /* SERVER_FLOWCONTROL */
+
+static void serve_questionable PROTO((char *));
+
+static void
+serve_questionable (arg)
+ char *arg;
+{
+ static int initted;
+
+ if (!initted)
+ {
+ /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server,
+ and CVSIGNORE on server. */
+ ign_setup ();
+ initted = 1;
+ }
+
+ if (dir_name == NULL)
+ {
+ buf_output0 (buf_to_net, "E Protocol error: 'Directory' missing");
+ return;
+ }
+
+ if (outside_dir (arg))
+ return;
+
+ if (!ign_name (arg))
+ {
+ char *update_dir;
+
+ buf_output (buf_to_net, "M ? ", 4);
+ update_dir = dir_name + strlen (server_temp_dir) + 1;
+ if (!(update_dir[0] == '.' && update_dir[1] == '\0'))
+ {
+ buf_output0 (buf_to_net, update_dir);
+ buf_output (buf_to_net, "/", 1);
+ }
+ buf_output0 (buf_to_net, arg);
+ buf_output (buf_to_net, "\n", 1);
+ }
+}
+
+static void serve_case PROTO ((char *));
+
+static void
+serve_case (arg)
+ char *arg;
+{
+ ign_case = 1;
+}
+
+static struct buffer *protocol;
+
+/* This is the output which we are saving up to send to the server, in the
+ child process. We will push it through, via the `protocol' buffer, when
+ we have a complete line. */
+static struct buffer *saved_output;
+/* Likewise, but stuff which will go to stderr. */
+static struct buffer *saved_outerr;
+
+static void
+protocol_memory_error (buf)
+ struct buffer *buf;
+{
+ error (1, ENOMEM, "Virtual memory exhausted");
+}
+
+/*
+ * Process IDs of the subprocess, or negative if that subprocess
+ * does not exist.
+ */
+static pid_t command_pid;
+
+static void
+outbuf_memory_error (buf)
+ struct buffer *buf;
+{
+ static const char msg[] = "E Fatal server error\n\
+error ENOMEM Virtual memory exhausted.\n";
+ if (command_pid > 0)
+ kill (command_pid, SIGTERM);
+
+ /*
+ * We have arranged things so that printing this now either will
+ * be legal, or the "E fatal error" line will get glommed onto the
+ * end of an existing "E" or "M" response.
+ */
+
+ /* If this gives an error, not much we could do. syslog() it? */
+ write (STDOUT_FILENO, msg, sizeof (msg) - 1);
+ error_exit ();
+}
+
+static void
+input_memory_error (buf)
+ struct buffer *buf;
+{
+ outbuf_memory_error (buf);
+}
+
+
+
+/* If command is legal, return 1.
+ * Else if command is illegal and croak_on_illegal is set, then die.
+ * Else just return 0 to indicate that command is illegal.
+ */
+static int
+check_command_legal_p (cmd_name)
+ char *cmd_name;
+{
+ /* Right now, only pserver notices illegal commands -- namely,
+ * write attempts by a read-only user. Therefore, if CVS_Username
+ * is not set, this just returns 1, because CVS_Username unset
+ * means pserver is not active.
+ */
+#ifdef AUTH_SERVER_SUPPORT
+ if (CVS_Username == NULL)
+ return 1;
+
+ if (lookup_command_attribute (cmd_name) & CVS_CMD_MODIFIES_REPOSITORY)
+ {
+ /* This command has the potential to modify the repository, so
+ * we check if the user have permission to do that.
+ *
+ * (Only relevant for remote users -- local users can do
+ * whatever normal Unix file permissions allow them to do.)
+ *
+ * The decision method:
+ *
+ * If $CVSROOT/CVSADMROOT_READERS exists and user is listed
+ * in it, then read-only access for user.
+ *
+ * Or if $CVSROOT/CVSADMROOT_WRITERS exists and user NOT
+ * listed in it, then also read-only access for user.
+ *
+ * Else read-write access for user.
+ */
+
+ char *linebuf = NULL;
+ int num_red = 0;
+ size_t linebuf_len = 0;
+ char *fname;
+ size_t flen;
+ FILE *fp;
+ int found_it = 0;
+
+ /* else */
+ flen = strlen (CVSroot_directory)
+ + strlen (CVSROOTADM)
+ + strlen (CVSROOTADM_READERS)
+ + 3;
+
+ fname = xmalloc (flen);
+ (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_READERS);
+
+ fp = fopen (fname, "r");
+
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ {
+ /* Need to deny access, so that attackers can't fool
+ us with some sort of denial of service attack. */
+ error (0, errno, "cannot open %s", fname);
+ free (fname);
+ return 0;
+ }
+ }
+ else /* successfully opened readers file */
+ {
+ while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
+ {
+ /* Hmmm, is it worth importing my own readline
+ library into CVS? It takes care of chopping
+ leading and trailing whitespace, "#" comments, and
+ newlines automatically when so requested. Would
+ save some code here... -kff */
+
+ /* Chop newline by hand, for strcmp()'s sake. */
+ if (linebuf[num_red - 1] == '\n')
+ linebuf[num_red - 1] = '\0';
+
+ if (strcmp (linebuf, CVS_Username) == 0)
+ goto handle_illegal;
+ }
+ if (num_red < 0 && !feof (fp))
+ error (0, errno, "cannot read %s", fname);
+
+ /* If not listed specifically as a reader, then this user
+ has write access by default unless writers are also
+ specified in a file . */
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ }
+ free (fname);
+
+ /* Now check the writers file. */
+
+ flen = strlen (CVSroot_directory)
+ + strlen (CVSROOTADM)
+ + strlen (CVSROOTADM_WRITERS)
+ + 3;
+
+ fname = xmalloc (flen);
+ (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_WRITERS);
+
+ fp = fopen (fname, "r");
+
+ if (fp == NULL)
+ {
+ if (linebuf)
+ free (linebuf);
+ if (existence_error (errno))
+ {
+ /* Writers file does not exist, so everyone is a writer,
+ by default. */
+ free (fname);
+ return 1;
+ }
+ else
+ {
+ /* Need to deny access, so that attackers can't fool
+ us with some sort of denial of service attack. */
+ error (0, errno, "cannot read %s", fname);
+ free (fname);
+ return 0;
+ }
+ }
+
+ found_it = 0;
+ while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
+ {
+ /* Chop newline by hand, for strcmp()'s sake. */
+ if (linebuf[num_red - 1] == '\n')
+ linebuf[num_red - 1] = '\0';
+
+ if (strcmp (linebuf, CVS_Username) == 0)
+ {
+ found_it = 1;
+ break;
+ }
+ }
+ if (num_red < 0 && !feof (fp))
+ error (0, errno, "cannot read %s", fname);
+
+ if (found_it)
+ {
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ if (linebuf)
+ free (linebuf);
+ free (fname);
+ return 1;
+ }
+ else /* writers file exists, but this user not listed in it */
+ {
+ handle_illegal:
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ if (linebuf)
+ free (linebuf);
+ free (fname);
+ return 0;
+ }
+ }
+#endif /* AUTH_SERVER_SUPPORT */
+
+ /* If ever reach end of this function, command must be legal. */
+ return 1;
+}
+
+
+
+/* Execute COMMAND in a subprocess with the approriate funky things done. */
+
+static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain;
+static int max_command_fd;
+
+#ifdef SERVER_FLOWCONTROL
+static int flowcontrol_pipe[2];
+#endif /* SERVER_FLOWCONTROL */
+
+static void
+do_cvs_command (cmd_name, command)
+ char *cmd_name;
+ int (*command) PROTO((int argc, char **argv));
+{
+ /*
+ * The following file descriptors are set to -1 if that file is not
+ * currently open.
+ */
+
+ /* Data on these pipes is a series of '\n'-terminated lines. */
+ int stdout_pipe[2];
+ int stderr_pipe[2];
+
+ /*
+ * Data on this pipe is a series of counted (see buf_send_counted)
+ * packets. Each packet must be processed atomically (i.e. not
+ * interleaved with data from stdout_pipe or stderr_pipe).
+ */
+ int protocol_pipe[2];
+
+ int dev_null_fd = -1;
+
+ int errs;
+
+ command_pid = -1;
+ stdout_pipe[0] = -1;
+ stdout_pipe[1] = -1;
+ stderr_pipe[0] = -1;
+ stderr_pipe[1] = -1;
+ protocol_pipe[0] = -1;
+ protocol_pipe[1] = -1;
+
+ server_write_entries ();
+
+ if (print_pending_error ())
+ goto free_args_and_return;
+
+ /* Global `command_name' is probably "server" right now -- only
+ serve_export() sets it to anything else. So we will use local
+ parameter `cmd_name' to determine if this command is legal for
+ this user. */
+ if (!check_command_legal_p (cmd_name))
+ {
+ buf_output0 (buf_to_net, "E ");
+ buf_output0 (buf_to_net, program_name);
+ buf_output0 (buf_to_net, " [server aborted]: \"");
+ buf_output0 (buf_to_net, cmd_name);
+ buf_output0 (buf_to_net, "\" requires write access to the repository\n\
+error \n");
+ goto free_args_and_return;
+ }
+
+ (void) server_notify ();
+
+ /*
+ * We use a child process which actually does the operation. This
+ * is so we can intercept its standard output. Even if all of CVS
+ * were written to go to some special routine instead of writing
+ * to stdout or stderr, we would still need to do the same thing
+ * for the RCS commands.
+ */
+
+ if (pipe (stdout_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ if (pipe (stderr_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ if (pipe (protocol_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+#ifdef SERVER_FLOWCONTROL
+ if (pipe (flowcontrol_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ set_nonblock_fd (flowcontrol_pipe[0]);
+ set_nonblock_fd (flowcontrol_pipe[1]);
+#endif /* SERVER_FLOWCONTROL */
+
+ dev_null_fd = CVS_OPEN (DEVNULL, O_RDONLY);
+ if (dev_null_fd < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+
+ /* We shouldn't have any partial lines from cvs_output and
+ cvs_outerr, but we handle them here in case there is a bug. */
+ /* FIXME: appending a newline, rather than using "MT" as we
+ do in the child process, is probably not really a very good
+ way to "handle" them. */
+ if (! buf_empty_p (saved_output))
+ {
+ buf_append_char (saved_output, '\n');
+ buf_copy_lines (buf_to_net, saved_output, 'M');
+ }
+ if (! buf_empty_p (saved_outerr))
+ {
+ buf_append_char (saved_outerr, '\n');
+ buf_copy_lines (buf_to_net, saved_outerr, 'E');
+ }
+
+ /* Flush out any pending data. */
+ buf_flush (buf_to_net, 1);
+
+ /* Don't use vfork; we're not going to exec(). */
+ command_pid = fork ();
+ if (command_pid < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ if (command_pid == 0)
+ {
+ int exitstatus;
+
+ /* Since we're in the child, and the parent is going to take
+ care of packaging up our error messages, we can clear this
+ flag. */
+ error_use_protocol = 0;
+
+ protocol = fd_buffer_initialize (protocol_pipe[1], 0,
+ protocol_memory_error);
+
+ /* At this point we should no longer be using buf_to_net and
+ buf_from_net. Instead, everything should go through
+ protocol. */
+ buf_to_net = NULL;
+ buf_from_net = NULL;
+
+ /* These were originally set up to use outbuf_memory_error.
+ Since we're now in the child, we should use the simpler
+ protocol_memory_error function. */
+ saved_output->memory_error = protocol_memory_error;
+ saved_outerr->memory_error = protocol_memory_error;
+
+ if (dup2 (dev_null_fd, STDIN_FILENO) < 0)
+ error (1, errno, "can't set up pipes");
+ if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0)
+ error (1, errno, "can't set up pipes");
+ if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0)
+ error (1, errno, "can't set up pipes");
+ close (stdout_pipe[0]);
+ close (stderr_pipe[0]);
+ close (protocol_pipe[0]);
+#ifdef SERVER_FLOWCONTROL
+ close (flowcontrol_pipe[1]);
+#endif /* SERVER_FLOWCONTROL */
+
+ /*
+ * Set this in .bashrc if you want to give yourself time to attach
+ * to the subprocess with a debugger.
+ */
+ if (getenv ("CVS_SERVER_SLEEP"))
+ {
+ int secs = atoi (getenv ("CVS_SERVER_SLEEP"));
+ sleep (secs);
+ }
+
+ exitstatus = (*command) (argument_count, argument_vector);
+
+ /* Output any partial lines. If the client doesn't support
+ "MT", we just throw out the partial line, like old versions
+ of CVS did, since the protocol can't support this. */
+ if (supported_response ("MT") && ! buf_empty_p (saved_output))
+ {
+ buf_output0 (protocol, "MT text ");
+ buf_append_buffer (protocol, saved_output);
+ buf_output (protocol, "\n", 1);
+ buf_send_counted (protocol);
+ }
+ /* For now we just discard partial lines on stderr. I suspect
+ that CVS can't write such lines unless there is a bug. */
+
+ /*
+ * When we exit, that will close the pipes, giving an EOF to
+ * the parent.
+ */
+ exit (exitstatus);
+ }
+
+ /* OK, sit around getting all the input from the child. */
+ {
+ struct buffer *stdoutbuf;
+ struct buffer *stderrbuf;
+ struct buffer *protocol_inbuf;
+ /* Number of file descriptors to check in select (). */
+ int num_to_check;
+ int count_needed = 0;
+#ifdef SERVER_FLOWCONTROL
+ int have_flowcontrolled = 0;
+#endif /* SERVER_FLOWCONTROL */
+
+ FD_ZERO (&command_fds_to_drain.fds);
+ num_to_check = stdout_pipe[0];
+ FD_SET (stdout_pipe[0], &command_fds_to_drain.fds);
+ if (stderr_pipe[0] > num_to_check)
+ num_to_check = stderr_pipe[0];
+ FD_SET (stderr_pipe[0], &command_fds_to_drain.fds);
+ if (protocol_pipe[0] > num_to_check)
+ num_to_check = protocol_pipe[0];
+ FD_SET (protocol_pipe[0], &command_fds_to_drain.fds);
+ if (STDOUT_FILENO > num_to_check)
+ num_to_check = STDOUT_FILENO;
+ max_command_fd = num_to_check;
+ /*
+ * File descriptors are numbered from 0, so num_to_check needs to
+ * be one larger than the largest descriptor.
+ */
+ ++num_to_check;
+ if (num_to_check > FD_SETSIZE)
+ {
+ buf_output0 (buf_to_net,
+ "E internal error: FD_SETSIZE not big enough.\n\
+error \n");
+ goto error_exit;
+ }
+
+ stdoutbuf = fd_buffer_initialize (stdout_pipe[0], 1,
+ input_memory_error);
+
+ stderrbuf = fd_buffer_initialize (stderr_pipe[0], 1,
+ input_memory_error);
+
+ protocol_inbuf = fd_buffer_initialize (protocol_pipe[0], 1,
+ input_memory_error);
+
+ set_nonblock (buf_to_net);
+ set_nonblock (stdoutbuf);
+ set_nonblock (stderrbuf);
+ set_nonblock (protocol_inbuf);
+
+ if (close (stdout_pipe[1]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ stdout_pipe[1] = -1;
+
+ if (close (stderr_pipe[1]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ stderr_pipe[1] = -1;
+
+ if (close (protocol_pipe[1]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ protocol_pipe[1] = -1;
+
+#ifdef SERVER_FLOWCONTROL
+ if (close (flowcontrol_pipe[0]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ flowcontrol_pipe[0] = -1;
+#endif /* SERVER_FLOWCONTROL */
+
+ if (close (dev_null_fd) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ dev_null_fd = -1;
+
+ while (stdout_pipe[0] >= 0
+ || stderr_pipe[0] >= 0
+ || protocol_pipe[0] >= 0)
+ {
+ fd_set readfds;
+ fd_set writefds;
+ int numfds;
+#ifdef SERVER_FLOWCONTROL
+ int bufmemsize;
+
+ /*
+ * See if we are swamping the remote client and filling our VM.
+ * Tell child to hold off if we do.
+ */
+ bufmemsize = buf_count_mem (buf_to_net);
+ if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER))
+ {
+ if (write(flowcontrol_pipe[1], "S", 1) == 1)
+ have_flowcontrolled = 1;
+ }
+ else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER))
+ {
+ if (write(flowcontrol_pipe[1], "G", 1) == 1)
+ have_flowcontrolled = 0;
+ }
+#endif /* SERVER_FLOWCONTROL */
+
+ FD_ZERO (&readfds);
+ FD_ZERO (&writefds);
+ if (! buf_empty_p (buf_to_net))
+ FD_SET (STDOUT_FILENO, &writefds);
+
+ if (stdout_pipe[0] >= 0)
+ {
+ FD_SET (stdout_pipe[0], &readfds);
+ }
+ if (stderr_pipe[0] >= 0)
+ {
+ FD_SET (stderr_pipe[0], &readfds);
+ }
+ if (protocol_pipe[0] >= 0)
+ {
+ FD_SET (protocol_pipe[0], &readfds);
+ }
+
+ /* This process of selecting on the three pipes means that
+ we might not get output in the same order in which it
+ was written, thus producing the well-known
+ "out-of-order" bug. If the child process uses
+ cvs_output and cvs_outerr, it will send everything on
+ the protocol_pipe and avoid this problem, so the
+ solution is to use cvs_output and cvs_outerr in the
+ child process. */
+ do {
+ /* This used to select on exceptions too, but as far
+ as I know there was never any reason to do that and
+ SCO doesn't let you select on exceptions on pipes. */
+ numfds = select (num_to_check, &readfds, &writefds,
+ (fd_set *)0, (struct timeval *)NULL);
+ if (numfds < 0
+ && errno != EINTR)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ } while (numfds < 0);
+
+ if (FD_ISSET (STDOUT_FILENO, &writefds))
+ {
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (buf_to_net);
+ }
+
+ if (stdout_pipe[0] >= 0
+ && (FD_ISSET (stdout_pipe[0], &readfds)))
+ {
+ int status;
+
+ status = buf_input_data (stdoutbuf, (int *) NULL);
+
+ buf_copy_lines (buf_to_net, stdoutbuf, 'M');
+
+ if (status == -1)
+ stdout_pipe[0] = -1;
+ else if (status > 0)
+ {
+ print_error (status);
+ goto error_exit;
+ }
+
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (buf_to_net);
+ }
+
+ if (stderr_pipe[0] >= 0
+ && (FD_ISSET (stderr_pipe[0], &readfds)))
+ {
+ int status;
+
+ status = buf_input_data (stderrbuf, (int *) NULL);
+
+ buf_copy_lines (buf_to_net, stderrbuf, 'E');
+
+ if (status == -1)
+ stderr_pipe[0] = -1;
+ else if (status > 0)
+ {
+ print_error (status);
+ goto error_exit;
+ }
+
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (buf_to_net);
+ }
+
+ if (protocol_pipe[0] >= 0
+ && (FD_ISSET (protocol_pipe[0], &readfds)))
+ {
+ int status;
+ int count_read;
+ int special;
+
+ status = buf_input_data (protocol_inbuf, &count_read);
+
+ if (status == -1)
+ protocol_pipe[0] = -1;
+ else if (status > 0)
+ {
+ print_error (status);
+ goto error_exit;
+ }
+
+ /*
+ * We only call buf_copy_counted if we have read
+ * enough bytes to make it worthwhile. This saves us
+ * from continually recounting the amount of data we
+ * have.
+ */
+ count_needed -= count_read;
+ while (count_needed <= 0)
+ {
+ count_needed = buf_copy_counted (buf_to_net,
+ protocol_inbuf,
+ &special);
+
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (buf_to_net);
+
+ /* If SPECIAL got set to -1, it means that the child
+ wants us to flush the pipe. We don't want to block
+ on the network, but we flush what we can. If the
+ client supports the 'F' command, we send it. */
+ if (special == -1)
+ {
+ if (supported_response ("F"))
+ {
+ buf_append_char (buf_to_net, 'F');
+ buf_append_char (buf_to_net, '\n');
+ }
+
+ cvs_flusherr ();
+ }
+ }
+ }
+ }
+
+ /*
+ * OK, we've gotten EOF on all the pipes. If there is
+ * anything left on stdoutbuf or stderrbuf (this could only
+ * happen if there was no trailing newline), send it over.
+ */
+ if (! buf_empty_p (stdoutbuf))
+ {
+ buf_append_char (stdoutbuf, '\n');
+ buf_copy_lines (buf_to_net, stdoutbuf, 'M');
+ }
+ if (! buf_empty_p (stderrbuf))
+ {
+ buf_append_char (stderrbuf, '\n');
+ buf_copy_lines (buf_to_net, stderrbuf, 'E');
+ }
+ if (! buf_empty_p (protocol_inbuf))
+ buf_output0 (buf_to_net,
+ "E Protocol error: uncounted data discarded\n");
+
+ errs = 0;
+
+ while (command_pid > 0)
+ {
+ int status;
+ pid_t waited_pid;
+ waited_pid = waitpid (command_pid, &status, 0);
+ if (waited_pid < 0)
+ {
+ /*
+ * Intentionally ignoring EINTR. Other errors
+ * "can't happen".
+ */
+ continue;
+ }
+
+ if (WIFEXITED (status))
+ errs += WEXITSTATUS (status);
+ else
+ {
+ int sig = WTERMSIG (status);
+ char buf[50];
+ /*
+ * This is really evil, because signals might be numbered
+ * differently on the two systems. We should be using
+ * signal names (either of the "Terminated" or the "SIGTERM"
+ * variety). But cvs doesn't currently use libiberty...we
+ * could roll our own.... FIXME.
+ */
+ buf_output0 (buf_to_net, "E Terminated with fatal signal ");
+ sprintf (buf, "%d\n", sig);
+ buf_output0 (buf_to_net, buf);
+
+ /* Test for a core dump. Is this portable? */
+ if (status & 0x80)
+ {
+ buf_output0 (buf_to_net, "E Core dumped; preserving ");
+ buf_output0 (buf_to_net, orig_server_temp_dir);
+ buf_output0 (buf_to_net, " on server.\n\
+E CVS locks may need cleaning up.\n");
+ dont_delete_temp = 1;
+ }
+ ++errs;
+ }
+ if (waited_pid == command_pid)
+ command_pid = -1;
+ }
+
+ /*
+ * OK, we've waited for the child. By now all CVS locks are free
+ * and it's OK to block on the network.
+ */
+ set_block (buf_to_net);
+ buf_flush (buf_to_net, 1);
+ }
+
+ if (errs)
+ /* We will have printed an error message already. */
+ buf_output0 (buf_to_net, "error \n");
+ else
+ buf_output0 (buf_to_net, "ok\n");
+ goto free_args_and_return;
+
+ error_exit:
+ if (command_pid > 0)
+ kill (command_pid, SIGTERM);
+
+ while (command_pid > 0)
+ {
+ pid_t waited_pid;
+ waited_pid = waitpid (command_pid, (int *) 0, 0);
+ if (waited_pid < 0 && errno == EINTR)
+ continue;
+ if (waited_pid == command_pid)
+ command_pid = -1;
+ }
+
+ close (dev_null_fd);
+ close (protocol_pipe[0]);
+ close (protocol_pipe[1]);
+ close (stderr_pipe[0]);
+ close (stderr_pipe[1]);
+ close (stdout_pipe[0]);
+ close (stdout_pipe[1]);
+
+ free_args_and_return:
+ /* Now free the arguments. */
+ {
+ /* argument_vector[0] is a dummy argument, we don't mess with it. */
+ char **cp;
+ for (cp = argument_vector + 1;
+ cp < argument_vector + argument_count;
+ ++cp)
+ free (*cp);
+
+ argument_count = 1;
+ }
+
+ /* Flush out any data not yet sent. */
+ set_block (buf_to_net);
+ buf_flush (buf_to_net, 1);
+
+ return;
+}
+
+#ifdef SERVER_FLOWCONTROL
+/*
+ * Called by the child at convenient points in the server's execution for
+ * the server child to block.. ie: when it has no locks active.
+ */
+void
+server_pause_check()
+{
+ int paused = 0;
+ char buf[1];
+
+ while (read (flowcontrol_pipe[0], buf, 1) == 1)
+ {
+ if (*buf == 'S') /* Stop */
+ paused = 1;
+ else if (*buf == 'G') /* Go */
+ paused = 0;
+ else
+ return; /* ??? */
+ }
+ while (paused) {
+ int numfds, numtocheck;
+ fd_set fds;
+
+ FD_ZERO (&fds);
+ FD_SET (flowcontrol_pipe[0], &fds);
+ numtocheck = flowcontrol_pipe[0] + 1;
+
+ do {
+ numfds = select (numtocheck, &fds, (fd_set *)0,
+ (fd_set *)0, (struct timeval *)NULL);
+ if (numfds < 0
+ && errno != EINTR)
+ {
+ print_error (errno);
+ return;
+ }
+ } while (numfds < 0);
+
+ if (FD_ISSET (flowcontrol_pipe[0], &fds))
+ {
+ int got;
+
+ while ((got = read (flowcontrol_pipe[0], buf, 1)) == 1)
+ {
+ if (*buf == 'S') /* Stop */
+ paused = 1;
+ else if (*buf == 'G') /* Go */
+ paused = 0;
+ else
+ return; /* ??? */
+ }
+
+ /* This assumes that we are using BSD or POSIX nonblocking
+ I/O. System V nonblocking I/O returns zero if there is
+ nothing to read. */
+ if (got == 0)
+ error (1, 0, "flow control EOF");
+ if (got < 0 && ! blocking_error (errno))
+ {
+ error (1, errno, "flow control read failed");
+ }
+ }
+ }
+}
+#endif /* SERVER_FLOWCONTROL */
+
+/* This variable commented in server.h. */
+char *server_dir = NULL;
+
+static void output_dir PROTO((char *, char *));
+
+static void
+output_dir (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ if (server_dir != NULL)
+ {
+ buf_output0 (protocol, server_dir);
+ buf_output0 (protocol, "/");
+ }
+ if (update_dir[0] == '\0')
+ buf_output0 (protocol, ".");
+ else
+ buf_output0 (protocol, update_dir);
+ buf_output0 (protocol, "/\n");
+ buf_output0 (protocol, repository);
+ buf_output0 (protocol, "/");
+}
+
+/*
+ * Entries line that we are squirreling away to send to the client when
+ * we are ready.
+ */
+static char *entries_line;
+
+/*
+ * File which has been Scratch_File'd, we are squirreling away that fact
+ * to inform the client when we are ready.
+ */
+static char *scratched_file;
+
+/*
+ * The scratched_file will need to be removed as well as having its entry
+ * removed.
+ */
+static int kill_scratched_file;
+
+void
+server_register (name, version, timestamp, options, tag, date, conflict)
+ char *name;
+ char *version;
+ char *timestamp;
+ char *options;
+ char *tag;
+ char *date;
+ char *conflict;
+{
+ int len;
+
+ if (options == NULL)
+ options = "";
+
+ if (trace)
+ {
+ (void) fprintf (stderr,
+ "%s-> server_register(%s, %s, %s, %s, %s, %s, %s)\n",
+ CLIENT_SERVER_STR,
+ name, version, timestamp ? timestamp : "", options,
+ tag ? tag : "", date ? date : "",
+ conflict ? conflict : "");
+ }
+
+ if (entries_line != NULL)
+ {
+ /*
+ * If CVS decides to Register it more than once (which happens
+ * on "cvs update foo/foo.c" where foo and foo.c are already
+ * checked out), use the last of the entries lines Register'd.
+ */
+ free (entries_line);
+ }
+
+ /*
+ * I have reports of Scratch_Entry and Register both happening, in
+ * two different cases. Using the last one which happens is almost
+ * surely correct; I haven't tracked down why they both happen (or
+ * even verified that they are for the same file).
+ */
+ if (scratched_file != NULL)
+ {
+ free (scratched_file);
+ scratched_file = NULL;
+ }
+
+ len = (strlen (name) + strlen (version) + strlen (options) + 80);
+ if (tag)
+ len += strlen (tag);
+ if (date)
+ len += strlen (date);
+
+ entries_line = xmalloc (len);
+ sprintf (entries_line, "/%s/%s/", name, version);
+ if (conflict != NULL)
+ {
+ strcat (entries_line, "+=");
+ }
+ strcat (entries_line, "/");
+ strcat (entries_line, options);
+ strcat (entries_line, "/");
+ if (tag != NULL)
+ {
+ strcat (entries_line, "T");
+ strcat (entries_line, tag);
+ }
+ else if (date != NULL)
+ {
+ strcat (entries_line, "D");
+ strcat (entries_line, date);
+ }
+}
+
+void
+server_scratch (fname)
+ char *fname;
+{
+ /*
+ * I have reports of Scratch_Entry and Register both happening, in
+ * two different cases. Using the last one which happens is almost
+ * surely correct; I haven't tracked down why they both happen (or
+ * even verified that they are for the same file).
+ */
+ if (entries_line != NULL)
+ {
+ free (entries_line);
+ entries_line = NULL;
+ }
+
+ if (scratched_file != NULL)
+ {
+ buf_output0 (protocol,
+ "E CVS server internal error: duplicate Scratch_Entry\n");
+ buf_send_counted (protocol);
+ return;
+ }
+ scratched_file = xstrdup (fname);
+ kill_scratched_file = 1;
+}
+
+void
+server_scratch_entry_only ()
+{
+ kill_scratched_file = 0;
+}
+
+/* Print a new entries line, from a previous server_register. */
+static void
+new_entries_line ()
+{
+ if (entries_line)
+ {
+ buf_output0 (protocol, entries_line);
+ buf_output (protocol, "\n", 1);
+ }
+ else
+ /* Return the error message as the Entries line. */
+ buf_output0 (protocol,
+ "CVS server internal error: Register missing\n");
+ free (entries_line);
+ entries_line = NULL;
+}
+
+
+static void
+serve_ci (arg)
+ char *arg;
+{
+ do_cvs_command ("commit", commit);
+}
+
+static void
+checked_in_response (file, update_dir, repository)
+ char *file;
+ char *update_dir;
+ char *repository;
+{
+ if (supported_response ("Mode"))
+ {
+ struct stat sb;
+ char *mode_string;
+
+ if ( CVS_STAT (file, &sb) < 0)
+ {
+ /* Not clear to me why the file would fail to exist, but it
+ was happening somewhere in the testsuite. */
+ if (!existence_error (errno))
+ error (0, errno, "cannot stat %s", file);
+ }
+ else
+ {
+ buf_output0 (protocol, "Mode ");
+ mode_string = mode_to_string (sb.st_mode);
+ buf_output0 (protocol, mode_string);
+ buf_output0 (protocol, "\n");
+ free (mode_string);
+ }
+ }
+
+ buf_output0 (protocol, "Checked-in ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, file);
+ buf_output (protocol, "\n", 1);
+ new_entries_line ();
+}
+
+void
+server_checked_in (file, update_dir, repository)
+ char *file;
+ char *update_dir;
+ char *repository;
+{
+ if (noexec)
+ return;
+ if (scratched_file != NULL && entries_line == NULL)
+ {
+ /*
+ * This happens if we are now doing a "cvs remove" after a previous
+ * "cvs add" (without a "cvs ci" in between).
+ */
+ buf_output0 (protocol, "Remove-entry ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, file);
+ buf_output (protocol, "\n", 1);
+ free (scratched_file);
+ scratched_file = NULL;
+ }
+ else
+ {
+ checked_in_response (file, update_dir, repository);
+ }
+ buf_send_counted (protocol);
+}
+
+void
+server_update_entries (file, update_dir, repository, updated)
+ char *file;
+ char *update_dir;
+ char *repository;
+ enum server_updated_arg4 updated;
+{
+ if (noexec)
+ return;
+ if (updated == SERVER_UPDATED)
+ checked_in_response (file, update_dir, repository);
+ else
+ {
+ if (!supported_response ("New-entry"))
+ return;
+ buf_output0 (protocol, "New-entry ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, file);
+ buf_output (protocol, "\n", 1);
+ new_entries_line ();
+ }
+
+ buf_send_counted (protocol);
+}
+
+static void
+serve_update (arg)
+ char *arg;
+{
+ do_cvs_command ("update", update);
+}
+
+static void
+serve_diff (arg)
+ char *arg;
+{
+ do_cvs_command ("diff", diff);
+}
+
+static void
+serve_log (arg)
+ char *arg;
+{
+ do_cvs_command ("log", cvslog);
+}
+
+static void
+serve_add (arg)
+ char *arg;
+{
+ do_cvs_command ("add", add);
+}
+
+static void
+serve_remove (arg)
+ char *arg;
+{
+ do_cvs_command ("remove", cvsremove);
+}
+
+static void
+serve_status (arg)
+ char *arg;
+{
+ do_cvs_command ("status", cvsstatus);
+}
+
+static void
+serve_rdiff (arg)
+ char *arg;
+{
+ do_cvs_command ("rdiff", patch);
+}
+
+static void
+serve_tag (arg)
+ char *arg;
+{
+ do_cvs_command ("cvstag", cvstag);
+}
+
+static void
+serve_rtag (arg)
+ char *arg;
+{
+ do_cvs_command ("rtag", rtag);
+}
+
+static void
+serve_import (arg)
+ char *arg;
+{
+ do_cvs_command ("import", import);
+}
+
+static void
+serve_admin (arg)
+ char *arg;
+{
+ do_cvs_command ("admin", admin);
+}
+
+static void
+serve_history (arg)
+ char *arg;
+{
+ do_cvs_command ("history", history);
+}
+
+static void
+serve_release (arg)
+ char *arg;
+{
+ do_cvs_command ("release", release);
+}
+
+static void serve_watch_on PROTO ((char *));
+
+static void
+serve_watch_on (arg)
+ char *arg;
+{
+ do_cvs_command ("watch_on", watch_on);
+}
+
+static void serve_watch_off PROTO ((char *));
+
+static void
+serve_watch_off (arg)
+ char *arg;
+{
+ do_cvs_command ("watch_off", watch_off);
+}
+
+static void serve_watch_add PROTO ((char *));
+
+static void
+serve_watch_add (arg)
+ char *arg;
+{
+ do_cvs_command ("watch_add", watch_add);
+}
+
+static void serve_watch_remove PROTO ((char *));
+
+static void
+serve_watch_remove (arg)
+ char *arg;
+{
+ do_cvs_command ("watch_remove", watch_remove);
+}
+
+static void serve_watchers PROTO ((char *));
+
+static void
+serve_watchers (arg)
+ char *arg;
+{
+ do_cvs_command ("watchers", watchers);
+}
+
+static void serve_editors PROTO ((char *));
+
+static void
+serve_editors (arg)
+ char *arg;
+{
+ do_cvs_command ("editors", editors);
+}
+
+static int noop PROTO ((int, char **));
+
+static int
+noop (argc, argv)
+ int argc;
+ char **argv;
+{
+ return 0;
+}
+
+static void serve_noop PROTO ((char *));
+
+static void
+serve_noop (arg)
+ char *arg;
+{
+ do_cvs_command ("noop", noop);
+}
+
+static void serve_init PROTO ((char *));
+
+static void
+serve_init (arg)
+ char *arg;
+{
+ if (!isabsolute (arg))
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E Root %s must be an absolute pathname", arg);
+ /* Fall through to do_cvs_command which will return the
+ actual error. */
+ }
+ set_local_cvsroot (arg);
+
+ do_cvs_command ("init", init);
+}
+
+static void serve_annotate PROTO ((char *));
+
+static void
+serve_annotate (arg)
+ char *arg;
+{
+ do_cvs_command ("annotate", annotate);
+}
+
+static void
+serve_co (arg)
+ char *arg;
+{
+ char *tempdir;
+ int status;
+
+ if (print_pending_error ())
+ return;
+
+ if (!isdir (CVSADM))
+ {
+ /*
+ * The client has not sent a "Repository" line. Check out
+ * into a pristine directory.
+ */
+ tempdir = malloc (strlen (server_temp_dir) + 80);
+ if (tempdir == NULL)
+ {
+ buf_output0 (buf_to_net, "E Out of memory\n");
+ return;
+ }
+ strcpy (tempdir, server_temp_dir);
+ strcat (tempdir, "/checkout-dir");
+ status = mkdir_p (tempdir);
+ if (status != 0 && status != EEXIST)
+ {
+ buf_output0 (buf_to_net, "E Cannot create ");
+ buf_output0 (buf_to_net, tempdir);
+ buf_append_char (buf_to_net, '\n');
+ print_error (errno);
+ free (tempdir);
+ return;
+ }
+
+ if ( CVS_CHDIR (tempdir) < 0)
+ {
+ buf_output0 (buf_to_net, "E Cannot change to directory ");
+ buf_output0 (buf_to_net, tempdir);
+ buf_append_char (buf_to_net, '\n');
+ print_error (errno);
+ free (tempdir);
+ return;
+ }
+ free (tempdir);
+ }
+
+ /* Compensate for server_export()'s setting of command_name.
+ *
+ * [It probably doesn't matter if do_cvs_command() gets "export"
+ * or "checkout", but we ought to be accurate where possible.]
+ */
+ do_cvs_command ((strcmp (command_name, "export") == 0) ?
+ "export" : "checkout",
+ checkout);
+}
+
+static void
+serve_export (arg)
+ char *arg;
+{
+ /* Tell checkout() to behave like export not checkout. */
+ command_name = "export";
+ serve_co (arg);
+}
+
+void
+server_copy_file (file, update_dir, repository, newfile)
+ char *file;
+ char *update_dir;
+ char *repository;
+ char *newfile;
+{
+ /* At least for now, our practice is to have the server enforce
+ noexec for the repository and the client enforce it for the
+ working directory. This might want more thought, and/or
+ documentation in cvsclient.texi (other responses do it
+ differently). */
+
+ if (!supported_response ("Copy-file"))
+ return;
+ buf_output0 (protocol, "Copy-file ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, file);
+ buf_output0 (protocol, "\n");
+ buf_output0 (protocol, newfile);
+ buf_output0 (protocol, "\n");
+}
+
+/* See server.h for description. */
+
+void
+server_modtime (finfo, vers_ts)
+ struct file_info *finfo;
+ Vers_TS *vers_ts;
+{
+ char date[MAXDATELEN];
+ char outdate[MAXDATELEN];
+
+ assert (vers_ts->vn_rcs != NULL);
+
+ if (!supported_response ("Mod-time"))
+ return;
+
+ if (RCS_getrevtime (finfo->rcs, vers_ts->vn_rcs, date, 0) == (time_t) -1)
+ /* FIXME? should we be printing some kind of warning? For one
+ thing I'm not 100% sure whether this happens in non-error
+ circumstances. */
+ return;
+ date_to_internet (outdate, date);
+ buf_output0 (protocol, "Mod-time ");
+ buf_output0 (protocol, outdate);
+ buf_output0 (protocol, "\n");
+}
+
+/* See server.h for description. */
+
+#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
+/* Need to prototype because mode_t might be smaller than int. */
+void
+server_updated (
+ struct file_info *finfo,
+ Vers_TS *vers,
+ enum server_updated_arg4 updated,
+ mode_t mode,
+ unsigned char *checksum,
+ struct buffer *filebuf)
+#else
+void
+server_updated (finfo, vers, updated, mode, checksum, filebuf)
+ struct file_info *finfo;
+ Vers_TS *vers;
+ enum server_updated_arg4 updated;
+ mode_t mode;
+ unsigned char *checksum;
+ struct buffer *filebuf;
+#endif
+{
+ if (noexec)
+ {
+ /* Hmm, maybe if we did the same thing for entries_file, we
+ could get rid of the kludges in server_register and
+ server_scratch which refrain from warning if both
+ Scratch_Entry and Register get called. Maybe. */
+ if (scratched_file)
+ {
+ free (scratched_file);
+ scratched_file = NULL;
+ }
+ return;
+ }
+
+ if (entries_line != NULL && scratched_file == NULL)
+ {
+ FILE *f;
+ struct buffer_data *list, *last;
+ unsigned long size;
+ char size_text[80];
+
+ /* The contents of the file will be in one of filebuf,
+ list/last, or here. */
+ unsigned char *file;
+ size_t file_allocated;
+ size_t file_used;
+
+ if (filebuf != NULL)
+ {
+ size = buf_length (filebuf);
+ if (mode == (mode_t) -1)
+ error (1, 0, "\
+CVS server internal error: no mode in server_updated");
+ }
+ else
+ {
+ struct stat sb;
+
+ if ( CVS_STAT (finfo->file, &sb) < 0)
+ {
+ if (existence_error (errno))
+ {
+ /* If we have a sticky tag for a branch on which
+ the file is dead, and cvs update the directory,
+ it gets a T_CHECKOUT but no file. So in this
+ case just forget the whole thing. */
+ free (entries_line);
+ entries_line = NULL;
+ goto done;
+ }
+ error (1, errno, "reading %s", finfo->fullname);
+ }
+ size = sb.st_size;
+ if (mode == (mode_t) -1)
+ {
+ /* FIXME: When we check out files the umask of the
+ server (set in .bashrc if rsh is in use) affects
+ what mode we send, and it shouldn't. */
+ mode = sb.st_mode;
+ }
+ }
+
+ if (checksum != NULL)
+ {
+ static int checksum_supported = -1;
+
+ if (checksum_supported == -1)
+ {
+ checksum_supported = supported_response ("Checksum");
+ }
+
+ if (checksum_supported)
+ {
+ int i;
+ char buf[3];
+
+ buf_output0 (protocol, "Checksum ");
+ for (i = 0; i < 16; i++)
+ {
+ sprintf (buf, "%02x", (unsigned int) checksum[i]);
+ buf_output0 (protocol, buf);
+ }
+ buf_append_char (protocol, '\n');
+ }
+ }
+
+ if (updated == SERVER_UPDATED)
+ {
+ Node *node;
+ Entnode *entnode;
+
+ if (!(supported_response ("Created")
+ && supported_response ("Update-existing")))
+ buf_output0 (protocol, "Updated ");
+ else
+ {
+ assert (vers != NULL);
+ if (vers->ts_user == NULL)
+ buf_output0 (protocol, "Created ");
+ else
+ buf_output0 (protocol, "Update-existing ");
+ }
+
+ /* Now munge the entries to say that the file is unmodified,
+ in case we end up processing it again (e.g. modules3-6
+ in the testsuite). */
+ node = findnode_fn (finfo->entries, finfo->file);
+ entnode = (Entnode *)node->data;
+ free (entnode->timestamp);
+ entnode->timestamp = xstrdup ("=");
+ }
+ else if (updated == SERVER_MERGED)
+ buf_output0 (protocol, "Merged ");
+ else if (updated == SERVER_PATCHED)
+ buf_output0 (protocol, "Patched ");
+ else if (updated == SERVER_RCS_DIFF)
+ buf_output0 (protocol, "Rcs-diff ");
+ else
+ abort ();
+ output_dir (finfo->update_dir, finfo->repository);
+ buf_output0 (protocol, finfo->file);
+ buf_output (protocol, "\n", 1);
+
+ new_entries_line ();
+
+ {
+ char *mode_string;
+
+ mode_string = mode_to_string (mode);
+ buf_output0 (protocol, mode_string);
+ buf_output0 (protocol, "\n");
+ free (mode_string);
+ }
+
+ list = last = NULL;
+
+ file = NULL;
+ file_allocated = 0;
+ file_used = 0;
+
+ if (size > 0)
+ {
+ /* Throughout this section we use binary mode to read the
+ file we are sending. The client handles any line ending
+ translation if necessary. */
+
+ if (file_gzip_level
+ /*
+ * For really tiny files, the gzip process startup
+ * time will outweigh the compression savings. This
+ * might be computable somehow; using 100 here is just
+ * a first approximation.
+ */
+ && size > 100)
+ {
+ /* Basing this routine on read_and_gzip is not a
+ high-performance approach. But it seems easier
+ to code than the alternative (and less
+ vulnerable to subtle bugs). Given that this feature
+ is mainly for compatibility, that is the better
+ tradeoff. */
+
+ int fd;
+
+ /* Callers must avoid passing us a buffer if
+ file_gzip_level is set. We could handle this case,
+ but it's not worth it since this case never arises
+ with a current client and server. */
+ if (filebuf != NULL)
+ error (1, 0, "\
+CVS server internal error: unhandled case in server_updated");
+
+ fd = CVS_OPEN (finfo->file, O_RDONLY | OPEN_BINARY, 0);
+ if (fd < 0)
+ error (1, errno, "reading %s", finfo->fullname);
+ if (read_and_gzip (fd, finfo->fullname, &file,
+ &file_allocated, &file_used,
+ file_gzip_level))
+ error (1, 0, "aborting due to compression error");
+ size = file_used;
+ if (close (fd) < 0)
+ error (1, errno, "reading %s", finfo->fullname);
+ /* Prepending length with "z" is flag for using gzip here. */
+ buf_output0 (protocol, "z");
+ }
+ else if (filebuf == NULL)
+ {
+ long status;
+
+ f = CVS_FOPEN (finfo->file, "rb");
+ if (f == NULL)
+ error (1, errno, "reading %s", finfo->fullname);
+ status = buf_read_file (f, size, &list, &last);
+ if (status == -2)
+ (*protocol->memory_error) (protocol);
+ else if (status != 0)
+ error (1, ferror (f) ? errno : 0, "reading %s",
+ finfo->fullname);
+ if (fclose (f) == EOF)
+ error (1, errno, "reading %s", finfo->fullname);
+ }
+ }
+
+ sprintf (size_text, "%lu\n", size);
+ buf_output0 (protocol, size_text);
+
+ if (file != NULL)
+ {
+ buf_output (protocol, file, file_used);
+ free (file);
+ file = NULL;
+ }
+ else if (filebuf == NULL)
+ buf_append_data (protocol, list, last);
+ else
+ {
+ buf_append_buffer (protocol, filebuf);
+ buf_free (filebuf);
+ }
+ /* Note we only send a newline here if the file ended with one. */
+
+ /*
+ * Avoid using up too much disk space for temporary files.
+ * A file which does not exist indicates that the file is up-to-date,
+ * which is now the case. If this is SERVER_MERGED, the file is
+ * not up-to-date, and we indicate that by leaving the file there.
+ * I'm thinking of cases like "cvs update foo/foo.c foo".
+ */
+ if ((updated == SERVER_UPDATED
+ || updated == SERVER_PATCHED
+ || updated == SERVER_RCS_DIFF)
+ && filebuf == NULL
+ /* But if we are joining, we'll need the file when we call
+ join_file. */
+ && !joining ())
+ {
+ if (CVS_UNLINK (finfo->file) < 0)
+ error (0, errno, "cannot remove temp file for %s",
+ finfo->fullname);
+ }
+ }
+ else if (scratched_file != NULL && entries_line == NULL)
+ {
+ if (strcmp (scratched_file, finfo->file) != 0)
+ error (1, 0,
+ "CVS server internal error: `%s' vs. `%s' scratched",
+ scratched_file,
+ finfo->file);
+ free (scratched_file);
+ scratched_file = NULL;
+
+ if (kill_scratched_file)
+ buf_output0 (protocol, "Removed ");
+ else
+ buf_output0 (protocol, "Remove-entry ");
+ output_dir (finfo->update_dir, finfo->repository);
+ buf_output0 (protocol, finfo->file);
+ buf_output (protocol, "\n", 1);
+ }
+ else if (scratched_file == NULL && entries_line == NULL)
+ {
+ /*
+ * This can happen with death support if we were processing
+ * a dead file in a checkout.
+ */
+ }
+ else
+ error (1, 0,
+ "CVS server internal error: Register *and* Scratch_Entry.\n");
+ buf_send_counted (protocol);
+ done:;
+}
+
+/* Return whether we should send patches in RCS format. */
+
+int
+server_use_rcs_diff ()
+{
+ return supported_response ("Rcs-diff");
+}
+
+void
+server_set_entstat (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ static int set_static_supported = -1;
+ if (set_static_supported == -1)
+ set_static_supported = supported_response ("Set-static-directory");
+ if (!set_static_supported) return;
+
+ buf_output0 (protocol, "Set-static-directory ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, "\n");
+ buf_send_counted (protocol);
+}
+
+void
+server_clear_entstat (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ static int clear_static_supported = -1;
+ if (clear_static_supported == -1)
+ clear_static_supported = supported_response ("Clear-static-directory");
+ if (!clear_static_supported) return;
+
+ if (noexec)
+ return;
+
+ buf_output0 (protocol, "Clear-static-directory ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, "\n");
+ buf_send_counted (protocol);
+}
+
+void
+server_set_sticky (update_dir, repository, tag, date, nonbranch)
+ char *update_dir;
+ char *repository;
+ char *tag;
+ char *date;
+ int nonbranch;
+{
+ static int set_sticky_supported = -1;
+
+ assert (update_dir != NULL);
+
+ if (set_sticky_supported == -1)
+ set_sticky_supported = supported_response ("Set-sticky");
+ if (!set_sticky_supported) return;
+
+ if (noexec)
+ return;
+
+ if (tag == NULL && date == NULL)
+ {
+ buf_output0 (protocol, "Clear-sticky ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, "\n");
+ }
+ else
+ {
+ buf_output0 (protocol, "Set-sticky ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, "\n");
+ if (tag != NULL)
+ {
+ if (nonbranch)
+ buf_output0 (protocol, "N");
+ else
+ buf_output0 (protocol, "T");
+ buf_output0 (protocol, tag);
+ }
+ else
+ {
+ buf_output0 (protocol, "D");
+ buf_output0 (protocol, date);
+ }
+ buf_output0 (protocol, "\n");
+ }
+ buf_send_counted (protocol);
+}
+
+struct template_proc_data
+{
+ char *update_dir;
+ char *repository;
+};
+
+/* Here as a static until we get around to fixing Parse_Info to pass along
+ a void * for it. */
+static struct template_proc_data *tpd;
+
+static int
+template_proc (repository, template)
+ char *repository;
+ char *template;
+{
+ FILE *fp;
+ char buf[1024];
+ size_t n;
+ struct stat sb;
+ struct template_proc_data *data = tpd;
+
+ if (!supported_response ("Template"))
+ /* Might want to warn the user that the rcsinfo feature won't work. */
+ return 0;
+ buf_output0 (protocol, "Template ");
+ output_dir (data->update_dir, data->repository);
+ buf_output0 (protocol, "\n");
+
+ fp = CVS_FOPEN (template, "rb");
+ if (fp == NULL)
+ {
+ error (0, errno, "Couldn't open rcsinfo template file %s", template);
+ return 1;
+ }
+ if (fstat (fileno (fp), &sb) < 0)
+ {
+ error (0, errno, "cannot stat rcsinfo template file %s", template);
+ return 1;
+ }
+ sprintf (buf, "%ld\n", (long) sb.st_size);
+ buf_output0 (protocol, buf);
+ while (!feof (fp))
+ {
+ n = fread (buf, 1, sizeof buf, fp);
+ buf_output (protocol, buf, n);
+ if (ferror (fp))
+ {
+ error (0, errno, "cannot read rcsinfo template file %s", template);
+ (void) fclose (fp);
+ return 1;
+ }
+ }
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close rcsinfo template file %s", template);
+ return 0;
+}
+
+void
+server_template (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ struct template_proc_data data;
+ data.update_dir = update_dir;
+ data.repository = repository;
+ tpd = &data;
+ (void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc, 1);
+}
+
+static void
+serve_gzip_contents (arg)
+ char *arg;
+{
+ int level;
+ level = atoi (arg);
+ if (level == 0)
+ level = 6;
+ file_gzip_level = level;
+}
+
+static void
+serve_gzip_stream (arg)
+ char *arg;
+{
+ int level;
+ level = atoi (arg);
+ if (level == 0)
+ level = 6;
+
+ /* All further communication with the client will be compressed. */
+
+ buf_to_net = compress_buffer_initialize (buf_to_net, 0, level,
+ buf_to_net->memory_error);
+ buf_from_net = compress_buffer_initialize (buf_from_net, 1, level,
+ buf_from_net->memory_error);
+}
+
+/* Tell the client about RCS options set in CVSROOT/cvswrappers. */
+static void
+serve_wrapper_sendme_rcs_options (arg)
+ char *arg;
+{
+ /* Actually, this is kind of sdrawkcab-ssa: the client wants
+ * verbatim lines from a cvswrappers file, but the server has
+ * already parsed the cvswrappers file into the wrap_list struct.
+ * Therefore, the server loops over wrap_list, unparsing each
+ * entry before sending it.
+ */
+ char *wrapper_line = NULL;
+
+ wrap_setup ();
+
+ for (wrap_unparse_rcs_options (&wrapper_line, 1);
+ wrapper_line;
+ wrap_unparse_rcs_options (&wrapper_line, 0))
+ {
+ buf_output0 (buf_to_net, "Wrapper-rcsOption ");
+ buf_output0 (buf_to_net, wrapper_line);
+ buf_output0 (buf_to_net, "\012");;
+ free (wrapper_line);
+ }
+
+ buf_output0 (buf_to_net, "ok\012");
+
+ /* The client is waiting for us, so we better send the data now. */
+ buf_flush (buf_to_net, 1);
+}
+
+
+static void
+serve_ignore (arg)
+ char *arg;
+{
+ /*
+ * Just ignore this command. This is used to support the
+ * update-patches command, which is not a real command, but a signal
+ * to the client that update will accept the -u argument.
+ */
+}
+
+static int
+expand_proc (pargc, argv, where, mwhere, mfile, shorten,
+ local_specified, omodule, msg)
+ int *pargc;
+ char **argv;
+ char *where;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *omodule;
+ char *msg;
+{
+ int i;
+ char *dir = argv[0];
+
+ /* If mwhere has been specified, the thing we're expanding is a
+ module -- just return its name so the client will ask for the
+ right thing later. If it is an alias or a real directory,
+ mwhere will not be set, so send out the appropriate
+ expansion. */
+
+ if (mwhere != NULL)
+ {
+ buf_output0 (buf_to_net, "Module-expansion ");
+ if (server_dir != NULL)
+ {
+ buf_output0 (buf_to_net, server_dir);
+ buf_output0 (buf_to_net, "/");
+ }
+ buf_output0 (buf_to_net, mwhere);
+ if (mfile != NULL)
+ {
+ buf_append_char (buf_to_net, '/');
+ buf_output0 (buf_to_net, mfile);
+ }
+ buf_append_char (buf_to_net, '\n');
+ }
+ else
+ {
+ /* We may not need to do this anymore -- check the definition
+ of aliases before removing */
+ if (*pargc == 1)
+ {
+ buf_output0 (buf_to_net, "Module-expansion ");
+ if (server_dir != NULL)
+ {
+ buf_output0 (buf_to_net, server_dir);
+ buf_output0 (buf_to_net, "/");
+ }
+ buf_output0 (buf_to_net, dir);
+ buf_append_char (buf_to_net, '\n');
+ }
+ else
+ {
+ for (i = 1; i < *pargc; ++i)
+ {
+ buf_output0 (buf_to_net, "Module-expansion ");
+ if (server_dir != NULL)
+ {
+ buf_output0 (buf_to_net, server_dir);
+ buf_output0 (buf_to_net, "/");
+ }
+ buf_output0 (buf_to_net, dir);
+ buf_append_char (buf_to_net, '/');
+ buf_output0 (buf_to_net, argv[i]);
+ buf_append_char (buf_to_net, '\n');
+ }
+ }
+ }
+ return 0;
+}
+
+static void
+serve_expand_modules (arg)
+ char *arg;
+{
+ int i;
+ int err;
+ DBM *db;
+ err = 0;
+
+ server_expanding = 1;
+ db = open_module ();
+ for (i = 1; i < argument_count; i++)
+ err += do_module (db, argument_vector[i],
+ CHECKOUT, "Updating", expand_proc,
+ NULL, 0, 0, 0,
+ (char *) NULL);
+ close_module (db);
+ server_expanding = 0;
+ {
+ /* argument_vector[0] is a dummy argument, we don't mess with it. */
+ char **cp;
+ for (cp = argument_vector + 1;
+ cp < argument_vector + argument_count;
+ ++cp)
+ free (*cp);
+
+ argument_count = 1;
+ }
+ if (err)
+ /* We will have printed an error message already. */
+ buf_output0 (buf_to_net, "error \n");
+ else
+ buf_output0 (buf_to_net, "ok\n");
+
+ /* The client is waiting for the module expansions, so we must
+ send the output now. */
+ buf_flush (buf_to_net, 1);
+}
+
+void
+server_prog (dir, name, which)
+ char *dir;
+ char *name;
+ enum progs which;
+{
+ if (!supported_response ("Set-checkin-prog"))
+ {
+ buf_output0 (buf_to_net, "E \
+warning: this client does not support -i or -u flags in the modules file.\n");
+ return;
+ }
+ switch (which)
+ {
+ case PROG_CHECKIN:
+ buf_output0 (buf_to_net, "Set-checkin-prog ");
+ break;
+ case PROG_UPDATE:
+ buf_output0 (buf_to_net, "Set-update-prog ");
+ break;
+ }
+ buf_output0 (buf_to_net, dir);
+ buf_append_char (buf_to_net, '\n');
+ buf_output0 (buf_to_net, name);
+ buf_append_char (buf_to_net, '\n');
+}
+
+static void
+serve_checkin_prog (arg)
+ char *arg;
+{
+ FILE *f;
+ f = CVS_FOPEN (CVSADM_CIPROG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_CIPROG)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_CIPROG);
+ return;
+ }
+ if (fprintf (f, "%s\n", arg) < 0)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_CIPROG)))
+ sprintf (pending_error_text,
+ "E cannot write to %s", CVSADM_CIPROG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_CIPROG)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_CIPROG);
+ return;
+ }
+}
+
+static void
+serve_update_prog (arg)
+ char *arg;
+{
+ FILE *f;
+
+ /* Before we do anything we need to make sure we are not in readonly
+ mode. */
+ if (!check_command_legal_p ("commit"))
+ {
+ /* I might be willing to make this a warning, except we lack the
+ machinery to do so. */
+ if (alloc_pending (80))
+ sprintf (pending_error_text, "\
+E Flag -u in modules not allowed in readonly mode");
+ return;
+ }
+
+ f = CVS_FOPEN (CVSADM_UPROG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_UPROG)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_UPROG);
+ return;
+ }
+ if (fprintf (f, "%s\n", arg) < 0)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_UPROG)))
+ sprintf (pending_error_text, "E cannot write to %s", CVSADM_UPROG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ if (alloc_pending (80 + strlen (CVSADM_UPROG)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_UPROG);
+ return;
+ }
+}
+
+static void serve_valid_requests PROTO((char *arg));
+
+#endif /* SERVER_SUPPORT */
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+
+/*
+ * Parts of this table are shared with the client code,
+ * but the client doesn't need to know about the handler
+ * functions.
+ */
+
+struct request requests[] =
+{
+#ifdef SERVER_SUPPORT
+#define REQ_LINE(n, f, s) {n, f, s}
+#else
+#define REQ_LINE(n, f, s) {n, s}
+#endif
+
+ REQ_LINE("Root", serve_root, RQ_ESSENTIAL | RQ_ROOTLESS),
+ REQ_LINE("Valid-responses", serve_valid_responses,
+ RQ_ESSENTIAL | RQ_ROOTLESS),
+ REQ_LINE("valid-requests", serve_valid_requests,
+ RQ_ESSENTIAL | RQ_ROOTLESS),
+ REQ_LINE("Repository", serve_repository, 0),
+ REQ_LINE("Directory", serve_directory, RQ_ESSENTIAL),
+ REQ_LINE("Max-dotdot", serve_max_dotdot, 0),
+ REQ_LINE("Static-directory", serve_static_directory, 0),
+ REQ_LINE("Sticky", serve_sticky, 0),
+ REQ_LINE("Checkin-prog", serve_checkin_prog, 0),
+ REQ_LINE("Update-prog", serve_update_prog, 0),
+ REQ_LINE("Entry", serve_entry, RQ_ESSENTIAL),
+ REQ_LINE("Kopt", serve_kopt, 0),
+ REQ_LINE("Checkin-time", serve_checkin_time, 0),
+ REQ_LINE("Modified", serve_modified, RQ_ESSENTIAL),
+ REQ_LINE("Is-modified", serve_is_modified, 0),
+
+ /* The client must send this request to interoperate with CVS 1.5
+ through 1.9 servers. The server must support it (although it can
+ be and is a noop) to interoperate with CVS 1.5 to 1.9 clients. */
+ REQ_LINE("UseUnchanged", serve_enable_unchanged, RQ_ENABLEME | RQ_ROOTLESS),
+
+ REQ_LINE("Unchanged", serve_unchanged, RQ_ESSENTIAL),
+ REQ_LINE("Notify", serve_notify, 0),
+ REQ_LINE("Questionable", serve_questionable, 0),
+ REQ_LINE("Case", serve_case, 0),
+ REQ_LINE("Argument", serve_argument, RQ_ESSENTIAL),
+ REQ_LINE("Argumentx", serve_argumentx, RQ_ESSENTIAL),
+ REQ_LINE("Global_option", serve_global_option, 0),
+ REQ_LINE("Gzip-stream", serve_gzip_stream, 0),
+ REQ_LINE("wrapper-sendme-rcsOptions",
+ serve_wrapper_sendme_rcs_options,
+ 0),
+ REQ_LINE("Set", serve_set, RQ_ROOTLESS),
+#ifdef ENCRYPTION
+# ifdef HAVE_KERBEROS
+ REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, 0),
+# endif
+# ifdef HAVE_GSSAPI
+ REQ_LINE("Gssapi-encrypt", serve_gssapi_encrypt, 0),
+# endif
+#endif
+#ifdef HAVE_GSSAPI
+ REQ_LINE("Gssapi-authenticate", serve_gssapi_authenticate, 0),
+#endif
+ REQ_LINE("expand-modules", serve_expand_modules, 0),
+ REQ_LINE("ci", serve_ci, RQ_ESSENTIAL),
+ REQ_LINE("co", serve_co, RQ_ESSENTIAL),
+ REQ_LINE("update", serve_update, RQ_ESSENTIAL),
+ REQ_LINE("diff", serve_diff, 0),
+ REQ_LINE("log", serve_log, 0),
+ REQ_LINE("add", serve_add, 0),
+ REQ_LINE("remove", serve_remove, 0),
+ REQ_LINE("update-patches", serve_ignore, 0),
+ REQ_LINE("gzip-file-contents", serve_gzip_contents, 0),
+ REQ_LINE("status", serve_status, 0),
+ REQ_LINE("rdiff", serve_rdiff, 0),
+ REQ_LINE("tag", serve_tag, 0),
+ REQ_LINE("rtag", serve_rtag, 0),
+ REQ_LINE("import", serve_import, 0),
+ REQ_LINE("admin", serve_admin, 0),
+ REQ_LINE("export", serve_export, 0),
+ REQ_LINE("history", serve_history, 0),
+ REQ_LINE("release", serve_release, 0),
+ REQ_LINE("watch-on", serve_watch_on, 0),
+ REQ_LINE("watch-off", serve_watch_off, 0),
+ REQ_LINE("watch-add", serve_watch_add, 0),
+ REQ_LINE("watch-remove", serve_watch_remove, 0),
+ REQ_LINE("watchers", serve_watchers, 0),
+ REQ_LINE("editors", serve_editors, 0),
+ REQ_LINE("init", serve_init, RQ_ROOTLESS),
+ REQ_LINE("annotate", serve_annotate, 0),
+ REQ_LINE("noop", serve_noop, 0),
+ REQ_LINE(NULL, NULL, 0)
+
+#undef REQ_LINE
+};
+
+#endif /* SERVER_SUPPORT or CLIENT_SUPPORT */
+#ifdef SERVER_SUPPORT
+
+static void
+serve_valid_requests (arg)
+ char *arg;
+{
+ struct request *rq;
+ if (print_pending_error ())
+ return;
+ buf_output0 (buf_to_net, "Valid-requests");
+ for (rq = requests; rq->name != NULL; rq++)
+ {
+ if (rq->func != NULL)
+ {
+ buf_append_char (buf_to_net, ' ');
+ buf_output0 (buf_to_net, rq->name);
+ }
+ }
+ buf_output0 (buf_to_net, "\nok\n");
+
+ /* The client is waiting for the list of valid requests, so we
+ must send the output now. */
+ buf_flush (buf_to_net, 1);
+}
+
+#ifdef sun
+/*
+ * Delete temporary files. SIG is the signal making this happen, or
+ * 0 if not called as a result of a signal.
+ */
+static int command_pid_is_dead;
+static void wait_sig (sig)
+ int sig;
+{
+ int status;
+ pid_t r = wait (&status);
+ if (r == command_pid)
+ command_pid_is_dead++;
+}
+#endif
+
+void
+server_cleanup (sig)
+ int sig;
+{
+ /* Do "rm -rf" on the temp directory. */
+ int status;
+ int save_noexec;
+
+ if (buf_to_net != NULL)
+ {
+ /* FIXME: If this is not the final call from server, this
+ could deadlock, because the client might be blocked writing
+ to us. This should not be a problem in practice, because
+ we do not generate much output when the client is not
+ waiting for it. */
+ set_block (buf_to_net);
+ buf_flush (buf_to_net, 1);
+
+ /* The calls to buf_shutdown are currently only meaningful
+ when we are using compression. First we shut down
+ BUF_FROM_NET. That will pick up the checksum generated
+ when the client shuts down its buffer. Then, after we have
+ generated any final output, we shut down BUF_TO_NET. */
+
+ status = buf_shutdown (buf_from_net);
+ if (status != 0)
+ {
+ error (0, status, "shutting down buffer from client");
+ buf_flush (buf_to_net, 1);
+ }
+ }
+
+ if (dont_delete_temp)
+ {
+ if (buf_to_net != NULL)
+ (void) buf_shutdown (buf_to_net);
+ return;
+ }
+
+ /* What a bogus kludge. This disgusting code makes all kinds of
+ assumptions about SunOS, and is only for a bug in that system.
+ So only enable it on Suns. */
+#ifdef sun
+ if (command_pid > 0)
+ {
+ /* To avoid crashes on SunOS due to bugs in SunOS tmpfs
+ triggered by the use of rename() in RCS, wait for the
+ subprocess to die. Unfortunately, this means draining output
+ while waiting for it to unblock the signal we sent it. Yuck! */
+ int status;
+ pid_t r;
+
+ signal (SIGCHLD, wait_sig);
+ if (sig)
+ /* Perhaps SIGTERM would be more correct. But the child
+ process will delay the SIGINT delivery until its own
+ children have exited. */
+ kill (command_pid, SIGINT);
+ /* The caller may also have sent a signal to command_pid, so
+ always try waiting. First, though, check and see if it's still
+ there.... */
+ do_waitpid:
+ r = waitpid (command_pid, &status, WNOHANG);
+ if (r == 0)
+ ;
+ else if (r == command_pid)
+ command_pid_is_dead++;
+ else if (r == -1)
+ switch (errno)
+ {
+ case ECHILD:
+ command_pid_is_dead++;
+ break;
+ case EINTR:
+ goto do_waitpid;
+ }
+ else
+ /* waitpid should always return one of the above values */
+ abort ();
+ while (!command_pid_is_dead)
+ {
+ struct timeval timeout;
+ struct fd_set_wrapper readfds;
+ char buf[100];
+ int i;
+
+ /* Use a non-zero timeout to avoid eating up CPU cycles. */
+ timeout.tv_sec = 2;
+ timeout.tv_usec = 0;
+ readfds = command_fds_to_drain;
+ switch (select (max_command_fd + 1, &readfds.fds,
+ (fd_set *)0, (fd_set *)0,
+ &timeout))
+ {
+ case -1:
+ if (errno != EINTR)
+ abort ();
+ case 0:
+ /* timeout */
+ break;
+ case 1:
+ for (i = 0; i <= max_command_fd; i++)
+ {
+ if (!FD_ISSET (i, &readfds.fds))
+ continue;
+ /* this fd is non-blocking */
+ while (read (i, buf, sizeof (buf)) >= 1)
+ ;
+ }
+ break;
+ default:
+ abort ();
+ }
+ }
+ }
+#endif
+
+ CVS_CHDIR (Tmpdir);
+ /* Temporarily clear noexec, so that we clean up our temp directory
+ regardless of it (this could more cleanly be handled by moving
+ the noexec check to all the unlink_file_dir callers from
+ unlink_file_dir itself). */
+ save_noexec = noexec;
+ noexec = 0;
+ /* FIXME? Would be nice to not ignore errors. But what should we do?
+ We could try to do this before we shut down the network connection,
+ and try to notify the client (but the client might not be waiting
+ for responses). We could try something like syslog() or our own
+ log file. */
+ unlink_file_dir (orig_server_temp_dir);
+ noexec = save_noexec;
+
+ if (buf_to_net != NULL)
+ (void) buf_shutdown (buf_to_net);
+}
+
+int server_active = 0;
+int server_expanding = 0;
+
+int
+server (argc, argv)
+ int argc;
+ char **argv;
+{
+ if (argc == -1)
+ {
+ static const char *const msg[] =
+ {
+ "Usage: %s %s\n",
+ " Normally invoked by a cvs client on a remote machine.\n",
+ NULL
+ };
+ usage (msg);
+ }
+ /* Ignore argc and argv. They might be from .cvsrc. */
+
+ buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0,
+ outbuf_memory_error);
+ buf_from_net = stdio_buffer_initialize (stdin, 1, outbuf_memory_error);
+
+ saved_output = buf_nonio_initialize (outbuf_memory_error);
+ saved_outerr = buf_nonio_initialize (outbuf_memory_error);
+
+ /* Since we're in the server parent process, error should use the
+ protocol to report error messages. */
+ error_use_protocol = 1;
+
+ /* OK, now figure out where we stash our temporary files. */
+ {
+ char *p;
+
+ /* The code which wants to chdir into server_temp_dir is not set
+ up to deal with it being a relative path. So give an error
+ for that case. */
+ if (!isabsolute (Tmpdir))
+ {
+ pending_error_text = malloc (80 + strlen (Tmpdir));
+ if (pending_error_text == NULL)
+ {
+ pending_error = ENOMEM;
+ }
+ else
+ {
+ sprintf (pending_error_text,
+ "E Value of %s for TMPDIR is not absolute", Tmpdir);
+ }
+ /* FIXME: we would like this error to be persistent, that
+ is, not cleared by print_pending_error. The current client
+ will exit as soon as it gets an error, but the protocol spec
+ does not require a client to do so. */
+ }
+ else
+ {
+ int status;
+
+ server_temp_dir = malloc (strlen (Tmpdir) + 80);
+ if (server_temp_dir == NULL)
+ {
+ /*
+ * Strictly speaking, we're not supposed to output anything
+ * now. But we're about to exit(), give it a try.
+ */
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket
+ subsystems on NT and OS2 or dealing with windows
+ and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+ strcpy (server_temp_dir, Tmpdir);
+
+ /* Remove a trailing slash from TMPDIR if present. */
+ p = server_temp_dir + strlen (server_temp_dir) - 1;
+ if (*p == '/')
+ *p = '\0';
+
+ /*
+ * I wanted to use cvs-serv/PID, but then you have to worry about
+ * the permissions on the cvs-serv directory being right. So
+ * use cvs-servPID.
+ */
+ strcat (server_temp_dir, "/cvs-serv");
+
+ p = server_temp_dir + strlen (server_temp_dir);
+ sprintf (p, "%ld", (long) getpid ());
+
+ orig_server_temp_dir = server_temp_dir;
+
+ /* Create the temporary directory, and set the mode to
+ 700, to discourage random people from tampering with
+ it. */
+ status = mkdir_p (server_temp_dir);
+ if (status != 0 && status != EEXIST)
+ {
+ if (alloc_pending (80))
+ strcpy (pending_error_text,
+ "E can't create temporary directory");
+ pending_error = status;
+ }
+#ifndef CHMOD_BROKEN
+ else
+ {
+ if (chmod (server_temp_dir, S_IRWXU) < 0)
+ {
+ int save_errno = errno;
+ if (alloc_pending (80))
+ strcpy (pending_error_text, "\
+E cannot change permissions on temporary directory");
+ pending_error = save_errno;
+ }
+ }
+#endif
+ }
+ }
+
+#ifdef SIGHUP
+ (void) SIG_register (SIGHUP, server_cleanup);
+#endif
+#ifdef SIGINT
+ (void) SIG_register (SIGINT, server_cleanup);
+#endif
+#ifdef SIGQUIT
+ (void) SIG_register (SIGQUIT, server_cleanup);
+#endif
+#ifdef SIGPIPE
+ (void) SIG_register (SIGPIPE, server_cleanup);
+#endif
+#ifdef SIGTERM
+ (void) SIG_register (SIGTERM, server_cleanup);
+#endif
+
+ /* Now initialize our argument vector (for arguments from the client). */
+
+ /* Small for testing. */
+ argument_vector_size = 1;
+ argument_vector =
+ (char **) malloc (argument_vector_size * sizeof (char *));
+ if (argument_vector == NULL)
+ {
+ /*
+ * Strictly speaking, we're not supposed to output anything
+ * now. But we're about to exit(), give it a try.
+ */
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+
+ argument_count = 1;
+ /* This gets printed if the client supports an option which the
+ server doesn't, causing the server to print a usage message.
+ FIXME: probably should be using program_name here.
+ FIXME: just a nit, I suppose, but the usage message the server
+ prints isn't literally true--it suggests "cvs server" followed
+ by options which are for a particular command. Might be nice to
+ say something like "client apparently supports an option not supported
+ by this server" or something like that instead of usage message. */
+ argument_vector[0] = "cvs server";
+
+ while (1)
+ {
+ char *cmd, *orig_cmd;
+ struct request *rq;
+ int status;
+
+ status = buf_read_line (buf_from_net, &cmd, (int *) NULL);
+ if (status == -2)
+ {
+ buf_output0 (buf_to_net, "E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+ break;
+ }
+ if (status != 0)
+ break;
+
+ orig_cmd = cmd;
+ for (rq = requests; rq->name != NULL; ++rq)
+ if (strncmp (cmd, rq->name, strlen (rq->name)) == 0)
+ {
+ int len = strlen (rq->name);
+ if (cmd[len] == '\0')
+ cmd += len;
+ else if (cmd[len] == ' ')
+ cmd += len + 1;
+ else
+ /*
+ * The first len characters match, but it's a different
+ * command. e.g. the command is "cooperate" but we matched
+ * "co".
+ */
+ continue;
+
+ if (!(rq->flags & RQ_ROOTLESS)
+ && CVSroot_directory == NULL)
+ {
+ /* For commands which change the way in which data
+ is sent and received, for example Gzip-stream,
+ this does the wrong thing. Since the client
+ assumes that everything is being compressed,
+ unconditionally, there is no way to give this
+ error to the client without turning on
+ compression. The obvious fix would be to make
+ Gzip-stream RQ_ROOTLESS (with the corresponding
+ change to the spec), and that might be a good
+ idea but then again I can see some settings in
+ CVSROOT about what compression level to allow.
+ I suppose a more baroque answer would be to
+ turn on compression (say, at level 1), just
+ enough to give the "Root request missing"
+ error. For now we just lose. */
+ if (alloc_pending (80))
+ sprintf (pending_error_text,
+ "E Protocol error: Root request missing");
+ }
+ else
+ (*rq->func) (cmd);
+ break;
+ }
+ if (rq->name == NULL)
+ {
+ if (!print_pending_error ())
+ {
+ buf_output0 (buf_to_net, "error unrecognized request `");
+ buf_output0 (buf_to_net, cmd);
+ buf_append_char (buf_to_net, '\'');
+ buf_append_char (buf_to_net, '\n');
+ }
+ }
+ free (orig_cmd);
+ }
+ server_cleanup (0);
+ return 0;
+}
+
+
+#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
+static void switch_to_user PROTO((const char *));
+
+static void
+switch_to_user (username)
+ const char *username;
+{
+ struct passwd *pw;
+
+ pw = getpwnam (username);
+ if (pw == NULL)
+ {
+ /* Normally this won't be reached; check_password contains
+ a similar check. */
+
+ printf ("E Fatal error, aborting.\n\
+error 0 %s: no such user\n", username);
+ /* Don't worry about server_cleanup; server_active isn't set yet. */
+ error_exit ();
+ }
+
+#if HAVE_INITGROUPS
+ if (initgroups (pw->pw_name, pw->pw_gid) < 0
+# ifdef EPERM
+ /* At least on the system I tried, initgroups() only works as root.
+ But we do still want to report ENOMEM and whatever other
+ errors initgroups() might dish up. */
+ && errno != EPERM
+# endif
+ )
+ {
+ /* This could be a warning, but I'm not sure I see the point
+ in doing that instead of an error given that it would happen
+ on every connection. We could log it somewhere and not tell
+ the user. But at least for now make it an error. */
+ printf ("error 0 initgroups failed: %s\n", strerror (errno));
+ /* Don't worry about server_cleanup; server_active isn't set yet. */
+ error_exit ();
+ }
+#endif /* HAVE_INITGROUPS */
+
+#ifdef SETXID_SUPPORT
+ /* honor the setgid bit iff set*/
+ if (getgid() != getegid())
+ {
+ if (setgid (getegid ()) < 0)
+ {
+ /* See comments at setuid call below for more discussion. */
+ printf ("error 0 setgid failed: %s\n", strerror (errno));
+ /* Don't worry about server_cleanup;
+ server_active isn't set yet. */
+ error_exit ();
+ }
+ }
+ else
+#endif
+ {
+ if (setgid (pw->pw_gid) < 0)
+ {
+ /* See comments at setuid call below for more discussion. */
+ printf ("error 0 setgid failed: %s\n", strerror (errno));
+ /* Don't worry about server_cleanup;
+ server_active isn't set yet. */
+ error_exit ();
+ }
+ }
+
+ if (setuid (pw->pw_uid) < 0)
+ {
+ /* Note that this means that if run as a non-root user,
+ CVSROOT/passwd must contain the user we are running as
+ (e.g. "joe:FsEfVcu:cvs" if run as "cvs" user). This seems
+ cleaner than ignoring the error like CVS 1.10 and older but
+ it does mean that some people might need to update their
+ CVSROOT/passwd file. */
+ printf ("error 0 setuid failed: %s\n", strerror (errno));
+ /* Don't worry about server_cleanup; server_active isn't set yet. */
+ error_exit ();
+ }
+
+ /* We don't want our umask to change file modes. The modes should
+ be set by the modes used in the repository, and by the umask of
+ the client. */
+ umask (0);
+
+#if HAVE_PUTENV
+ /* Set LOGNAME and USER in the environment, in case they are
+ already set to something else. */
+ {
+ char *env;
+
+ env = xmalloc (sizeof "LOGNAME=" + strlen (username));
+ (void) sprintf (env, "LOGNAME=%s", username);
+ (void) putenv (env);
+
+ env = xmalloc (sizeof "USER=" + strlen (username));
+ (void) sprintf (env, "USER=%s", username);
+ (void) putenv (env);
+ }
+#endif /* HAVE_PUTENV */
+}
+#endif
+
+#ifdef AUTH_SERVER_SUPPORT
+
+extern char *crypt PROTO((const char *, const char *));
+
+
+/*
+ * 0 means no entry found for this user.
+ * 1 means entry found and password matches (or found password is empty)
+ * 2 means entry found, but password does not match.
+ *
+ * If 1, host_user_ptr will be set to point at the system
+ * username (i.e., the "real" identity, which may or may not be the
+ * CVS username) of this user; caller may free this. Global
+ * CVS_Username will point at an allocated copy of cvs username (i.e.,
+ * the username argument below).
+ * kff todo: FIXME: last sentence is not true, it applies to caller.
+ */
+static int
+check_repository_password (username, password, repository, host_user_ptr)
+ char *username, *password, *repository, **host_user_ptr;
+{
+ int retval = 0;
+ FILE *fp;
+ char *filename;
+ char *linebuf = NULL;
+ size_t linebuf_len;
+ int found_it = 0;
+ int namelen;
+
+ /* We don't use CVSroot_directory because it hasn't been set yet
+ * -- our `repository' argument came from the authentication
+ * protocol, not the regular CVS protocol.
+ */
+
+ filename = xmalloc (strlen (repository)
+ + 1
+ + strlen (CVSROOTADM)
+ + 1
+ + strlen (CVSROOTADM_PASSWD)
+ + 1);
+
+ (void) sprintf (filename, "%s/%s/%s", repository,
+ CVSROOTADM, CVSROOTADM_PASSWD);
+
+ fp = CVS_FOPEN (filename, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", filename);
+ return 0;
+ }
+
+ /* Look for a relevant line -- one with this user's name. */
+ namelen = strlen (username);
+ while (getline (&linebuf, &linebuf_len, fp) >= 0)
+ {
+ if ((strncmp (linebuf, username, namelen) == 0)
+ && (linebuf[namelen] == ':'))
+ {
+ found_it = 1;
+ break;
+ }
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", filename);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", filename);
+
+ /* If found_it, then linebuf contains the information we need. */
+ if (found_it)
+ {
+ char *found_password, *host_user_tmp;
+ char *non_cvsuser_portion;
+
+ /* We need to make sure lines such as
+ *
+ * "username::sysuser\n"
+ * "username:\n"
+ * "username: \n"
+ *
+ * all result in a found_password of NULL, but we also need to
+ * make sure that
+ *
+ * "username: :sysuser\n"
+ * "username: <whatever>:sysuser\n"
+ *
+ * continues to result in an impossible password. That way,
+ * an admin would be on safe ground by going in and tacking a
+ * space onto the front of a password to disable the account
+ * (a technique some people use to close accounts
+ * temporarily).
+ */
+
+ /* Make `non_cvsuser_portion' contain everything after the CVS
+ username, but null out any final newline. */
+ non_cvsuser_portion = linebuf + namelen;
+ strtok (non_cvsuser_portion, "\n");
+
+ /* If there's a colon now, we just want to inch past it. */
+ if (strchr (non_cvsuser_portion, ':') == non_cvsuser_portion)
+ non_cvsuser_portion++;
+
+ /* Okay, after this conditional chain, found_password and
+ host_user_tmp will have useful values: */
+
+ if ((non_cvsuser_portion == NULL)
+ || (strlen (non_cvsuser_portion) == 0)
+ || ((strspn (non_cvsuser_portion, " \t"))
+ == strlen (non_cvsuser_portion)))
+ {
+ found_password = NULL;
+ host_user_tmp = NULL;
+ }
+ else if (strncmp (non_cvsuser_portion, ":", 1) == 0)
+ {
+ found_password = NULL;
+ host_user_tmp = non_cvsuser_portion + 1;
+ if (strlen (host_user_tmp) == 0)
+ host_user_tmp = NULL;
+ }
+ else
+ {
+ found_password = strtok (non_cvsuser_portion, ":");
+ host_user_tmp = strtok (NULL, ":");
+ }
+
+ /* Of course, maybe there was no system user portion... */
+ if (host_user_tmp == NULL)
+ host_user_tmp = username;
+
+ /* Verify blank passwords directly, otherwise use crypt(). */
+ if ((found_password == NULL)
+ || ((strcmp (found_password, crypt (password, found_password))
+ == 0)))
+ {
+ /* Give host_user_ptr permanent storage. */
+ *host_user_ptr = xstrdup (host_user_tmp);
+ retval = 1;
+ }
+ else
+ {
+ *host_user_ptr = NULL;
+ retval = 2;
+ }
+ }
+ else /* Didn't find this user, so deny access. */
+ {
+ *host_user_ptr = NULL;
+ retval = 0;
+ }
+
+ free (filename);
+ if (linebuf)
+ free (linebuf);
+
+ return retval;
+}
+
+
+/* Return a hosting username if password matches, else NULL. */
+static char *
+check_password (username, password, repository)
+ char *username, *password, *repository;
+{
+ int rc;
+ char *host_user = NULL;
+
+ /* First we see if this user has a password in the CVS-specific
+ password file. If so, that's enough to authenticate with. If
+ not, we'll check /etc/passwd. */
+
+ rc = check_repository_password (username, password, repository,
+ &host_user);
+
+ if (rc == 2)
+ return NULL;
+
+ /* else */
+
+ if (rc == 1)
+ {
+ /* host_user already set by reference, so just return. */
+ goto handle_return;
+ }
+ else if (rc == 0 && system_auth)
+ {
+ /* No cvs password found, so try /etc/passwd. */
+
+ const char *found_passwd = NULL;
+ struct passwd *pw;
+#ifdef HAVE_GETSPNAM
+ struct spwd *spw;
+
+ spw = getspnam (username);
+ if (spw != NULL)
+ {
+ found_passwd = spw->sp_pwdp;
+ }
+#endif
+
+ if (found_passwd == NULL && (pw = getpwnam (username)) != NULL)
+ {
+ found_passwd = pw->pw_passwd;
+ }
+
+ if (found_passwd == NULL)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 %s: no such user\n", username);
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+
+ if (*found_passwd)
+ {
+ /* user exists and has a password */
+ host_user = ((! strcmp (found_passwd,
+ crypt (password, found_passwd)))
+ ? username : NULL);
+ goto handle_return;
+ }
+ else if (password && *password)
+ {
+ /* user exists and has no system password, but we got
+ one as parameter */
+ host_user = username;
+ goto handle_return;
+ }
+ else
+ {
+ /* user exists but has no password at all */
+ host_user = NULL;
+ goto handle_return;
+ }
+ }
+ else if (rc == 0)
+ {
+ /* Note that the message _does_ distinguish between the case in
+ which we check for a system password and the case in which
+ we do not. It is a real pain to track down why it isn't
+ letting you in if it won't say why, and I am not convinced
+ that the potential information disclosure to an attacker
+ outweighs this. */
+ printf ("error 0 no such user %s in CVSROOT/passwd\n", username);
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (EXIT_FAILURE);
+ }
+ else
+ {
+ /* Something strange happened. We don't know what it was, but
+ we certainly won't grant authorization. */
+ host_user = NULL;
+ goto handle_return;
+ }
+
+handle_return:
+ if (host_user)
+ {
+ /* Set CVS_Username here, in allocated space.
+ It might or might not be the same as host_user. */
+ CVS_Username = xmalloc (strlen (username) + 1);
+ strcpy (CVS_Username, username);
+ }
+
+ return host_user;
+}
+
+#endif /* AUTH_SERVER_SUPPORT */
+
+#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
+
+/* Read username and password from client (i.e., stdin).
+ If correct, then switch to run as that user and send an ACK to the
+ client via stdout, else send NACK and die. */
+void
+pserver_authenticate_connection ()
+{
+ char *tmp = NULL;
+ size_t tmp_allocated = 0;
+#ifdef AUTH_SERVER_SUPPORT
+ char *repository = NULL;
+ size_t repository_allocated = 0;
+ char *username = NULL;
+ size_t username_allocated = 0;
+ char *password = NULL;
+ size_t password_allocated = 0;
+
+ char *host_user;
+ char *descrambled_password;
+#endif /* AUTH_SERVER_SUPPORT */
+ int verify_and_exit = 0;
+
+ /* The Authentication Protocol. Client sends:
+ *
+ * BEGIN AUTH REQUEST\n
+ * <REPOSITORY>\n
+ * <USERNAME>\n
+ * <PASSWORD>\n
+ * END AUTH REQUEST\n
+ *
+ * Server uses above information to authenticate, then sends
+ *
+ * I LOVE YOU\n
+ *
+ * if it grants access, else
+ *
+ * I HATE YOU\n
+ *
+ * if it denies access (and it exits if denying).
+ *
+ * When the client is "cvs login", the user does not desire actual
+ * repository access, but would like to confirm the password with
+ * the server. In this case, the start and stop strings are
+ *
+ * BEGIN VERIFICATION REQUEST\n
+ *
+ * and
+ *
+ * END VERIFICATION REQUEST\n
+ *
+ * On a verification request, the server's responses are the same
+ * (with the obvious semantics), but it exits immediately after
+ * sending the response in both cases.
+ *
+ * Why is the repository sent? Well, note that the actual
+ * client/server protocol can't start up until authentication is
+ * successful. But in order to perform authentication, the server
+ * needs to look up the password in the special CVS passwd file,
+ * before trying /etc/passwd. So the client transmits the
+ * repository as part of the "authentication protocol". The
+ * repository will be redundantly retransmitted later, but that's no
+ * big deal.
+ */
+
+#ifdef SO_KEEPALIVE
+ /* Set SO_KEEPALIVE on the socket, so that we don't hang forever
+ if the client dies while we are waiting for input. */
+ {
+ int on = 1;
+
+ (void) setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &on, sizeof on);
+ }
+#endif
+
+ /* Make sure the protocol starts off on the right foot... */
+ if (getline (&tmp, &tmp_allocated, stdin) < 0)
+ /* FIXME: what? We could try writing error/eof, but chances
+ are the network connection is dead bidirectionally. log it
+ somewhere? */
+ ;
+
+ if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
+ verify_and_exit = 1;
+ else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") == 0)
+ ;
+ else if (strcmp (tmp, "BEGIN GSSAPI REQUEST\n") == 0)
+ {
+#ifdef HAVE_GSSAPI
+ free (tmp);
+ gserver_authenticate_connection ();
+ return;
+#else
+ error (1, 0, "GSSAPI authentication not supported by this server");
+#endif
+ }
+ else
+ error (1, 0, "bad auth protocol start: %s", tmp);
+
+#ifndef AUTH_SERVER_SUPPORT
+
+ error (1, 0, "Password authentication not supported by this server");
+
+#else /* AUTH_SERVER_SUPPORT */
+
+ /* Get the three important pieces of information in order. */
+ /* See above comment about error handling. */
+ getline (&repository, &repository_allocated, stdin);
+ getline (&username, &username_allocated, stdin);
+ getline (&password, &password_allocated, stdin);
+
+ /* Make them pure. */
+ strip_trailing_newlines (repository);
+ strip_trailing_newlines (username);
+ strip_trailing_newlines (password);
+
+ /* ... and make sure the protocol ends on the right foot. */
+ /* See above comment about error handling. */
+ getline (&tmp, &tmp_allocated, stdin);
+ if (strcmp (tmp,
+ verify_and_exit ?
+ "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n")
+ != 0)
+ {
+ error (1, 0, "bad auth protocol end: %s", tmp);
+ }
+ if (!root_allow_ok (repository))
+ /* Just give a generic I HATE YOU. This is because CVS 1.9.10
+ and older clients do not support "error". Once more recent
+ clients are more widespread, probably want to fix this (it is
+ a real pain to track down why it isn't letting you in if it
+ won't say why, and I am not convinced that the potential
+ information disclosure to an attacker outweighs this). */
+ goto i_hate_you;
+
+ /* OK, now parse the config file, so we can use it to control how
+ to check passwords. If there was an error parsing the config
+ file, parse_config already printed an error. We keep going.
+ Why? Because if we didn't, then there would be no way to check
+ in a new CVSROOT/config file to fix the broken one! */
+ parse_config (repository);
+
+ /* We need the real cleartext before we hash it. */
+ descrambled_password = descramble (password);
+ host_user = check_password (username, descrambled_password, repository);
+ memset (descrambled_password, 0, strlen (descrambled_password));
+ free (descrambled_password);
+ if (host_user == NULL)
+ {
+ i_hate_you:
+ printf ("I HATE YOU\n");
+ fflush (stdout);
+
+ /* Don't worry about server_cleanup, server_active isn't set
+ yet. */
+ error_exit ();
+ }
+
+ /* Don't go any farther if we're just responding to "cvs login". */
+ if (verify_and_exit)
+ {
+ printf ("I LOVE YOU\n");
+ fflush (stdout);
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (0);
+ }
+
+ /* Set Pserver_Repos so that we can check later that the same
+ repository is sent in later client/server protocol. */
+ Pserver_Repos = xmalloc (strlen (repository) + 1);
+ strcpy (Pserver_Repos, repository);
+
+ /* Switch to run as this user. */
+ switch_to_user (host_user);
+ free (tmp);
+ free (repository);
+ free (username);
+ free (password);
+
+ printf ("I LOVE YOU\n");
+ fflush (stdout);
+#endif /* AUTH_SERVER_SUPPORT */
+}
+
+#endif /* AUTH_SERVER_SUPPORT || HAVE_GSSAPI */
+
+
+#ifdef HAVE_KERBEROS
+void
+kserver_authenticate_connection ()
+{
+ int status;
+ char instance[INST_SZ];
+ struct sockaddr_in peer;
+ struct sockaddr_in laddr;
+ int len;
+ KTEXT_ST ticket;
+ AUTH_DAT auth;
+ char version[KRB_SENDAUTH_VLEN];
+ char user[ANAME_SZ];
+
+ strcpy (instance, "*");
+ len = sizeof peer;
+ if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0
+ || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr,
+ &len) < 0)
+ {
+ printf ("E Fatal error, aborting.\n\
+error %s getpeername or getsockname failed\n", strerror (errno));
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (EXIT_FAILURE);
+ }
+
+#ifdef SO_KEEPALIVE
+ /* Set SO_KEEPALIVE on the socket, so that we don't hang forever
+ if the client dies while we are waiting for input. */
+ {
+ int on = 1;
+
+ (void) setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &on, sizeof on);
+ }
+#endif
+
+ status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd",
+ instance, &peer, &laddr, &auth, "", sched,
+ version);
+ if (status != KSUCCESS)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 kerberos: %s\n", krb_get_err_text(status));
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (EXIT_FAILURE);
+ }
+
+ memcpy (kblock, auth.session, sizeof (C_Block));
+
+ /* Get the local name. */
+ status = krb_kntoln (&auth, user);
+ if (status != KSUCCESS)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status));
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (EXIT_FAILURE);
+ }
+
+ /* Switch to run as this user. */
+ switch_to_user (user);
+}
+#endif /* HAVE_KERBEROS */
+
+#ifdef HAVE_GSSAPI
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN (256)
+#endif
+
+/* Authenticate a GSSAPI connection. This is called from
+ pserver_authenticate_connection, and it handles success and failure
+ the same way. */
+
+static void
+gserver_authenticate_connection ()
+{
+ char hostname[MAXHOSTNAMELEN];
+ struct hostent *hp;
+ gss_buffer_desc tok_in, tok_out;
+ char buf[1024];
+ OM_uint32 stat_min, ret;
+ gss_name_t server_name, client_name;
+ gss_cred_id_t server_creds;
+ int nbytes;
+ gss_OID mechid;
+
+ gethostname (hostname, sizeof hostname);
+ hp = gethostbyname (hostname);
+ if (hp == NULL)
+ error (1, 0, "can't get canonical hostname");
+
+ sprintf (buf, "cvs@%s", hp->h_name);
+ tok_in.value = buf;
+ tok_in.length = strlen (buf);
+
+ if (gss_import_name (&stat_min, &tok_in, GSS_C_NT_HOSTBASED_SERVICE,
+ &server_name) != GSS_S_COMPLETE)
+ error (1, 0, "could not import GSSAPI service name %s", buf);
+
+ /* Acquire the server credential to verify the client's
+ authentication. */
+ if (gss_acquire_cred (&stat_min, server_name, 0, GSS_C_NULL_OID_SET,
+ GSS_C_ACCEPT, &server_creds,
+ NULL, NULL) != GSS_S_COMPLETE)
+ error (1, 0, "could not acquire GSSAPI server credentials");
+
+ gss_release_name (&stat_min, &server_name);
+
+ /* The client will send us a two byte length followed by that many
+ bytes. */
+ if (fread (buf, 1, 2, stdin) != 2)
+ error (1, errno, "read of length failed");
+
+ nbytes = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
+ assert (nbytes <= sizeof buf);
+
+ if (fread (buf, 1, nbytes, stdin) != nbytes)
+ error (1, errno, "read of data failed");
+
+ gcontext = GSS_C_NO_CONTEXT;
+ tok_in.length = nbytes;
+ tok_in.value = buf;
+
+ if (gss_accept_sec_context (&stat_min,
+ &gcontext, /* context_handle */
+ server_creds, /* verifier_cred_handle */
+ &tok_in, /* input_token */
+ NULL, /* channel bindings */
+ &client_name, /* src_name */
+ &mechid, /* mech_type */
+ &tok_out, /* output_token */
+ &ret,
+ NULL, /* ignore time_rec */
+ NULL) /* ignore del_cred_handle */
+ != GSS_S_COMPLETE)
+ {
+ error (1, 0, "could not verify credentials");
+ }
+
+ /* FIXME: Use Kerberos v5 specific code to authenticate to a user.
+ We could instead use an authentication to access mapping. */
+ {
+ krb5_context kc;
+ krb5_principal p;
+ gss_buffer_desc desc;
+
+ krb5_init_context (&kc);
+ if (gss_display_name (&stat_min, client_name, &desc,
+ &mechid) != GSS_S_COMPLETE
+ || krb5_parse_name (kc, ((gss_buffer_t) &desc)->value, &p) != 0
+ || krb5_aname_to_localname (kc, p, sizeof buf, buf) != 0
+ || krb5_kuserok (kc, p, buf) != TRUE)
+ {
+ error (1, 0, "access denied");
+ }
+ krb5_free_principal (kc, p);
+ krb5_free_context (kc);
+ }
+
+ if (tok_out.length != 0)
+ {
+ char cbuf[2];
+
+ cbuf[0] = (tok_out.length >> 8) & 0xff;
+ cbuf[1] = tok_out.length & 0xff;
+ if (fwrite (cbuf, 1, 2, stdout) != 2
+ || (fwrite (tok_out.value, 1, tok_out.length, stdout)
+ != tok_out.length))
+ error (1, errno, "fwrite failed");
+ }
+
+ switch_to_user (buf);
+
+ printf ("I LOVE YOU\n");
+ fflush (stdout);
+}
+
+#endif /* HAVE_GSSAPI */
+
+#endif /* SERVER_SUPPORT */
+
+#if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
+
+/* This global variable is non-zero if the user requests encryption on
+ the command line. */
+int cvsencrypt;
+
+/* This global variable is non-zero if the users requests stream
+ authentication on the command line. */
+int cvsauthenticate;
+
+#ifdef HAVE_GSSAPI
+
+/* An buffer interface using GSSAPI. This is built on top of a
+ packetizing buffer. */
+
+/* This structure is the closure field of the GSSAPI translation
+ routines. */
+
+struct cvs_gssapi_wrap_data
+{
+ /* The GSSAPI context. */
+ gss_ctx_id_t gcontext;
+};
+
+static int cvs_gssapi_wrap_input PROTO((void *, const char *, char *, int));
+static int cvs_gssapi_wrap_output PROTO((void *, const char *, char *, int,
+ int *));
+
+/* Create a GSSAPI wrapping buffer. We use a packetizing buffer with
+ GSSAPI wrapping routines. */
+
+struct buffer *
+cvs_gssapi_wrap_buffer_initialize (buf, input, gcontext, memory)
+ struct buffer *buf;
+ int input;
+ gss_ctx_id_t gcontext;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct cvs_gssapi_wrap_data *gd;
+
+ gd = (struct cvs_gssapi_wrap_data *) xmalloc (sizeof *gd);
+ gd->gcontext = gcontext;
+
+ return (packetizing_buffer_initialize
+ (buf,
+ input ? cvs_gssapi_wrap_input : NULL,
+ input ? NULL : cvs_gssapi_wrap_output,
+ gd,
+ memory));
+}
+
+/* Unwrap data using GSSAPI. */
+
+static int
+cvs_gssapi_wrap_input (fnclosure, input, output, size)
+ void *fnclosure;
+ const char *input;
+ char *output;
+ int size;
+{
+ struct cvs_gssapi_wrap_data *gd =
+ (struct cvs_gssapi_wrap_data *) fnclosure;
+ gss_buffer_desc inbuf, outbuf;
+ OM_uint32 stat_min;
+ int conf;
+
+ inbuf.value = (void *) input;
+ inbuf.length = size;
+
+ if (gss_unwrap (&stat_min, gd->gcontext, &inbuf, &outbuf, &conf, NULL)
+ != GSS_S_COMPLETE)
+ {
+ error (1, 0, "gss_unwrap failed");
+ }
+
+ if (outbuf.length > size)
+ abort ();
+
+ memcpy (output, outbuf.value, outbuf.length);
+
+ /* The real packet size is stored in the data, so we don't need to
+ remember outbuf.length. */
+
+ gss_release_buffer (&stat_min, &outbuf);
+
+ return 0;
+}
+
+/* Wrap data using GSSAPI. */
+
+static int
+cvs_gssapi_wrap_output (fnclosure, input, output, size, translated)
+ void *fnclosure;
+ const char *input;
+ char *output;
+ int size;
+ int *translated;
+{
+ struct cvs_gssapi_wrap_data *gd =
+ (struct cvs_gssapi_wrap_data *) fnclosure;
+ gss_buffer_desc inbuf, outbuf;
+ OM_uint32 stat_min;
+ int conf_req, conf;
+
+ inbuf.value = (void *) input;
+ inbuf.length = size;
+
+#ifdef ENCRYPTION
+ conf_req = cvs_gssapi_encrypt;
+#else
+ conf_req = 0;
+#endif
+
+ if (gss_wrap (&stat_min, gd->gcontext, conf_req, GSS_C_QOP_DEFAULT,
+ &inbuf, &conf, &outbuf) != GSS_S_COMPLETE)
+ error (1, 0, "gss_wrap failed");
+
+ /* The packetizing buffer only permits us to add 100 bytes.
+ FIXME: I don't know what, if anything, is guaranteed by GSSAPI.
+ This may need to be increased for a different GSSAPI
+ implementation, or we may need a different algorithm. */
+ if (outbuf.length > size + 100)
+ abort ();
+
+ memcpy (output, outbuf.value, outbuf.length);
+
+ *translated = outbuf.length;
+
+ gss_release_buffer (&stat_min, &outbuf);
+
+ return 0;
+}
+
+#endif /* HAVE_GSSAPI */
+
+#ifdef ENCRYPTION
+
+#ifdef HAVE_KERBEROS
+
+/* An encryption interface using Kerberos. This is built on top of a
+ packetizing buffer. */
+
+/* This structure is the closure field of the Kerberos translation
+ routines. */
+
+struct krb_encrypt_data
+{
+ /* The Kerberos key schedule. */
+ Key_schedule sched;
+ /* The Kerberos DES block. */
+ C_Block block;
+};
+
+static int krb_encrypt_input PROTO((void *, const char *, char *, int));
+static int krb_encrypt_output PROTO((void *, const char *, char *, int,
+ int *));
+
+/* Create a Kerberos encryption buffer. We use a packetizing buffer
+ with Kerberos encryption translation routines. */
+
+struct buffer *
+krb_encrypt_buffer_initialize (buf, input, sched, block, memory)
+ struct buffer *buf;
+ int input;
+ Key_schedule sched;
+ C_Block block;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct krb_encrypt_data *kd;
+
+ kd = (struct krb_encrypt_data *) xmalloc (sizeof *kd);
+ memcpy (kd->sched, sched, sizeof (Key_schedule));
+ memcpy (kd->block, block, sizeof (C_Block));
+
+ return packetizing_buffer_initialize (buf,
+ input ? krb_encrypt_input : NULL,
+ input ? NULL : krb_encrypt_output,
+ kd,
+ memory);
+}
+
+/* Decrypt Kerberos data. */
+
+static int
+krb_encrypt_input (fnclosure, input, output, size)
+ void *fnclosure;
+ const char *input;
+ char *output;
+ int size;
+{
+ struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure;
+ int tcount;
+
+ des_cbc_encrypt ((C_Block *) input, (C_Block *) output,
+ size, kd->sched, &kd->block, 0);
+
+ /* SIZE is the size of the buffer, which is set by the encryption
+ routine. The packetizing buffer will arrange for the first two
+ bytes in the decrypted buffer to be the real (unaligned)
+ length. As a safety check, make sure that the length in the
+ buffer corresponds to SIZE. Note that the length in the buffer
+ is just the length of the data. We must add 2 to account for
+ the buffer count itself. */
+ tcount = ((output[0] & 0xff) << 8) + (output[1] & 0xff);
+ if (((tcount + 2 + 7) & ~7) != size)
+ error (1, 0, "Decryption failure");
+
+ return 0;
+}
+
+/* Encrypt Kerberos data. */
+
+static int
+krb_encrypt_output (fnclosure, input, output, size, translated)
+ void *fnclosure;
+ const char *input;
+ char *output;
+ int size;
+ int *translated;
+{
+ struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure;
+ int aligned;
+
+ /* For security against a known plaintext attack, we should
+ initialize any padding bytes to random values. Instead, we
+ just pick up whatever is on the stack, which is at least better
+ than using zero. */
+
+ /* Align SIZE to an 8 byte boundary. Note that SIZE includes the
+ two byte buffer count at the start of INPUT which was added by
+ the packetizing buffer. */
+ aligned = (size + 7) & ~7;
+
+ /* We use des_cbc_encrypt rather than krb_mk_priv because the
+ latter sticks a timestamp in the block, and krb_rd_priv expects
+ that timestamp to be within five minutes of the current time.
+ Given the way the CVS server buffers up data, that can easily
+ fail over a long network connection. We trust krb_recvauth to
+ guard against a replay attack. */
+
+ des_cbc_encrypt ((C_Block *) input, (C_Block *) output, aligned,
+ kd->sched, &kd->block, 1);
+
+ *translated = aligned;
+
+ return 0;
+}
+
+#endif /* HAVE_KERBEROS */
+#endif /* ENCRYPTION */
+#endif /* defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
+
+/* Output LEN bytes at STR. If LEN is zero, then output up to (not including)
+ the first '\0' byte. */
+
+void
+cvs_output (str, len)
+ const char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ buf_output (saved_output, str, len);
+ buf_copy_lines (buf_to_net, saved_output, 'M');
+ }
+ else if (server_active)
+ {
+ buf_output (saved_output, str, len);
+ buf_copy_lines (protocol, saved_output, 'M');
+ buf_send_counted (protocol);
+ }
+ else
+#endif
+ {
+ size_t written;
+ size_t to_write = len;
+ const char *p = str;
+
+ /* For symmetry with cvs_outerr we would call fflush (stderr)
+ here. I guess the assumption is that stderr will be
+ unbuffered, so we don't need to. That sounds like a sound
+ assumption from the manpage I looked at, but if there was
+ something fishy about it, my guess is that calling fflush
+ would not produce a significant performance problem. */
+
+ while (to_write > 0)
+ {
+ written = fwrite (p, 1, to_write, stdout);
+ if (written == 0)
+ break;
+ p += written;
+ to_write -= written;
+ }
+ }
+}
+
+/* Output LEN bytes at STR in binary mode. If LEN is zero, then
+ output zero bytes. */
+
+void
+cvs_output_binary (str, len)
+ char *str;
+ size_t len;
+{
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol || server_active)
+ {
+ struct buffer *buf;
+ char size_text[40];
+
+ if (error_use_protocol)
+ buf = buf_to_net;
+ else
+ buf = protocol;
+
+ if (!supported_response ("Mbinary"))
+ {
+ error (0, 0, "\
+this client does not support writing binary files to stdout");
+ return;
+ }
+
+ buf_output0 (buf, "Mbinary\012");
+ sprintf (size_text, "%lu\012", (unsigned long) len);
+ buf_output0 (buf, size_text);
+
+ /* Not sure what would be involved in using buf_append_data here
+ without stepping on the toes of our caller (which is responsible
+ for the memory allocation of STR). */
+ buf_output (buf, str, len);
+
+ if (!error_use_protocol)
+ buf_send_counted (protocol);
+ }
+ else
+#endif
+ {
+ size_t written;
+ size_t to_write = len;
+ const char *p = str;
+
+ /* For symmetry with cvs_outerr we would call fflush (stderr)
+ here. I guess the assumption is that stderr will be
+ unbuffered, so we don't need to. That sounds like a sound
+ assumption from the manpage I looked at, but if there was
+ something fishy about it, my guess is that calling fflush
+ would not produce a significant performance problem. */
+#ifdef USE_SETMODE_STDOUT
+ int oldmode;
+
+ /* It is possible that this should be the same ifdef as
+ USE_SETMODE_BINARY but at least for the moment we keep them
+ separate. Mostly this is just laziness and/or a question
+ of what has been tested where. Also there might be an
+ issue of setmode vs. _setmode. */
+ /* The Windows doc says to call setmode only right after startup.
+ I assume that what they are talking about can also be helped
+ by flushing the stream before changing the mode. */
+ fflush (stdout);
+ oldmode = _setmode (_fileno (stdout), OPEN_BINARY);
+ if (oldmode < 0)
+ error (0, errno, "failed to setmode on stdout");
+#endif
+
+ while (to_write > 0)
+ {
+ written = fwrite (p, 1, to_write, stdout);
+ if (written == 0)
+ break;
+ p += written;
+ to_write -= written;
+ }
+#ifdef USE_SETMODE_STDOUT
+ fflush (stdout);
+ if (_setmode (_fileno (stdout), oldmode) != OPEN_BINARY)
+ error (0, errno, "failed to setmode on stdout");
+#endif
+ }
+}
+
+/* Like CVS_OUTPUT but output is for stderr not stdout. */
+
+void
+cvs_outerr (str, len)
+ const char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ buf_output (saved_outerr, str, len);
+ buf_copy_lines (buf_to_net, saved_outerr, 'E');
+ }
+ else if (server_active)
+ {
+ buf_output (saved_outerr, str, len);
+ buf_copy_lines (protocol, saved_outerr, 'E');
+ buf_send_counted (protocol);
+ }
+ else
+#endif
+ {
+ size_t written;
+ size_t to_write = len;
+ const char *p = str;
+
+ /* Make sure that output appears in order if stdout and stderr
+ point to the same place. For the server case this is taken
+ care of by the fact that saved_outerr always holds less
+ than a line. */
+ fflush (stdout);
+
+ while (to_write > 0)
+ {
+ written = fwrite (p, 1, to_write, stderr);
+ if (written == 0)
+ break;
+ p += written;
+ to_write -= written;
+ }
+ }
+}
+
+/* Flush stderr. stderr is normally flushed automatically, of course,
+ but this function is used to flush information from the server back
+ to the client. */
+
+void
+cvs_flusherr ()
+{
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ /* Flush what we can to the network, but don't block. */
+ buf_flush (buf_to_net, 0);
+ }
+ else if (server_active)
+ {
+ /* Send a special count to tell the parent to flush. */
+ buf_send_special_count (protocol, -1);
+ }
+ else
+#endif
+ fflush (stderr);
+}
+
+/* Make it possible for the user to see what has been written to
+ stdout (it is up to the implementation to decide exactly how far it
+ should go to ensure this). */
+
+void
+cvs_flushout ()
+{
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ /* Flush what we can to the network, but don't block. */
+ buf_flush (buf_to_net, 0);
+ }
+ else if (server_active)
+ {
+ /* Just do nothing. This is because the code which
+ cvs_flushout replaces, setting stdout to line buffering in
+ main.c, didn't get called in the server child process. But
+ in the future it is quite plausible that we'll want to make
+ this case work analogously to cvs_flusherr. */
+ }
+ else
+#endif
+ fflush (stdout);
+}
+
+/* Output TEXT, tagging it according to TAG. There are lots more
+ details about what TAG means in cvsclient.texi but for the simple
+ case (e.g. non-client/server), TAG is just "newline" to output a
+ newline (in which case TEXT must be NULL), and any other tag to
+ output normal text.
+
+ Note that there is no way to output either \0 or \n as part of TEXT. */
+
+void
+cvs_output_tagged (tag, text)
+ char *tag;
+ char *text;
+{
+ if (text != NULL && strchr (text, '\n') != NULL)
+ /* Uh oh. The protocol has no way to cope with this. For now
+ we dump core, although that really isn't such a nice
+ response given that this probably can be caused by newlines
+ in filenames and other causes other than bugs in CVS. Note
+ that we don't want to turn this into "MT newline" because
+ this case is a newline within a tagged item, not a newline
+ as extraneous sugar for the user. */
+ assert (0);
+
+ /* Start and end tags don't take any text, per cvsclient.texi. */
+ if (tag[0] == '+' || tag[0] == '-')
+ assert (text == NULL);
+
+#ifdef SERVER_SUPPORT
+ if (server_active && supported_response ("MT"))
+ {
+ struct buffer *buf;
+
+ if (error_use_protocol)
+ buf = buf_to_net;
+ else
+ buf = protocol;
+
+ buf_output0 (buf, "MT ");
+ buf_output0 (buf, tag);
+ if (text != NULL)
+ {
+ buf_output (buf, " ", 1);
+ buf_output0 (buf, text);
+ }
+ buf_output (buf, "\n", 1);
+
+ if (!error_use_protocol)
+ buf_send_counted (protocol);
+ }
+ else
+#endif
+ {
+ if (strcmp (tag, "newline") == 0)
+ cvs_output ("\n", 1);
+ else if (text != NULL)
+ cvs_output (text, 0);
+ }
+}
diff --git a/gnu/dist/cvs/src/status.c b/gnu/dist/cvs/src/status.c
new file mode 100644
index 00000000000..4e5ddcbd658
--- /dev/null
+++ b/gnu/dist/cvs/src/status.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Status Information
+ */
+
+#include "cvs.h"
+
+static Dtype status_dirproc PROTO ((void *callerdat, char *dir,
+ char *repos, char *update_dir,
+ List *entries));
+static int status_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static int tag_list_proc PROTO((Node * p, void *closure));
+
+static int local = 0;
+static int long_format = 0;
+static RCSNode *xrcsnode;
+
+static const char *const status_usage[] =
+{
+ "Usage: %s %s [-vlR] [files...]\n",
+ "\t-v\tVerbose format; includes tag information for the file\n",
+ "\t-l\tProcess this directory only (not recursive).\n",
+ "\t-R\tProcess directories recursively.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+cvsstatus (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+
+ if (argc == -1)
+ usage (status_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+vlR")) != -1)
+ {
+ switch (c)
+ {
+ case 'v':
+ long_format = 1;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (status_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+
+ ign_setup ();
+
+ if (long_format)
+ send_arg("-v");
+ if (local)
+ send_arg("-l");
+
+ /* For a while, we tried setting SEND_NO_CONTENTS here so this
+ could be a fast operation. That prevents the
+ server from updating our timestamp if the timestamp is
+ changed but the file is unmodified. Worse, it is user-visible
+ (shows "locally modified" instead of "up to date" if
+ timestamp is changed but file is not). And there is no good
+ workaround (you might not want to run "cvs update"; "cvs -n
+ update" doesn't update CVS/Entries; "cvs diff --brief" or
+ something perhaps could be made to work but somehow that
+ seems nonintuitive to me even if so). Given that timestamps
+ seem to have the potential to get munged for any number of
+ reasons, it seems better to not rely too much on them. */
+
+ send_files (argc, argv, local, 0, 0);
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+
+ send_to_server ("status\012", 0);
+ err = get_responses_and_close ();
+
+ return err;
+ }
+#endif
+
+ /* start the recursion processor */
+ err = start_recursion (status_fileproc, (FILESDONEPROC) NULL,
+ status_dirproc, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local,
+ W_LOCAL, 0, 1, (char *) NULL, 1);
+
+ return (err);
+}
+
+/*
+ * display the status of a file
+ */
+/* ARGSUSED */
+static int
+status_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ Ctype status;
+ char *sstat;
+ Vers_TS *vers;
+
+ status = Classify_File (finfo, (char *) NULL, (char *) NULL, (char *) NULL,
+ 1, 0, &vers, 0);
+ sstat = "Classify Error";
+ switch (status)
+ {
+ case T_UNKNOWN:
+ sstat = "Unknown";
+ break;
+ case T_CHECKOUT:
+ sstat = "Needs Checkout";
+ break;
+#ifdef SERVER_SUPPORT
+ case T_PATCH:
+ sstat = "Needs Patch";
+ break;
+#endif
+ case T_CONFLICT:
+ /* I _think_ that "unresolved" is correct; that if it has
+ been resolved then the status will change. But I'm not
+ sure about that. */
+ sstat = "Unresolved Conflict";
+ break;
+ case T_ADDED:
+ sstat = "Locally Added";
+ break;
+ case T_REMOVED:
+ sstat = "Locally Removed";
+ break;
+ case T_MODIFIED:
+ if (vers->ts_conflict)
+ sstat = "File had conflicts on merge";
+ else
+ sstat = "Locally Modified";
+ break;
+ case T_REMOVE_ENTRY:
+ sstat = "Entry Invalid";
+ break;
+ case T_UPTODATE:
+ sstat = "Up-to-date";
+ break;
+ case T_NEEDS_MERGE:
+ sstat = "Needs Merge";
+ break;
+ case T_TITLE:
+ /* I don't think this case can occur here. Just print
+ "Classify Error". */
+ break;
+ }
+
+ cvs_output ("\
+===================================================================\n", 0);
+ if (vers->ts_user == NULL)
+ {
+ cvs_output ("File: no file ", 0);
+ cvs_output (finfo->file, 0);
+ cvs_output ("\t\tStatus: ", 0);
+ cvs_output (sstat, 0);
+ cvs_output ("\n\n", 0);
+ }
+ else
+ {
+ char *buf;
+ buf = xmalloc (strlen (finfo->file) + strlen (sstat) + 80);
+ sprintf (buf, "File: %-17s\tStatus: %s\n\n", finfo->file, sstat);
+ cvs_output (buf, 0);
+ free (buf);
+ }
+
+ if (vers->vn_user == NULL)
+ {
+ cvs_output (" Working revision:\tNo entry for ", 0);
+ cvs_output (finfo->file, 0);
+ cvs_output ("\n", 0);
+ }
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ cvs_output (" Working revision:\tNew file!\n", 0);
+#ifdef SERVER_SUPPORT
+ else if (server_active)
+ {
+ cvs_output (" Working revision:\t", 0);
+ cvs_output (vers->vn_user, 0);
+ cvs_output ("\n", 0);
+ }
+#endif
+ else
+ {
+ cvs_output (" Working revision:\t", 0);
+ cvs_output (vers->vn_user, 0);
+ cvs_output ("\t", 0);
+ cvs_output (vers->ts_rcs, 0);
+ cvs_output ("\n", 0);
+ }
+
+ if (vers->vn_rcs == NULL)
+ cvs_output (" Repository revision:\tNo revision control file\n", 0);
+ else
+ {
+ cvs_output (" Repository revision:\t", 0);
+ cvs_output (vers->vn_rcs, 0);
+ cvs_output ("\t", 0);
+ cvs_output (vers->srcfile->path, 0);
+ cvs_output ("\n", 0);
+ }
+
+ if (vers->entdata)
+ {
+ Entnode *edata;
+
+ edata = vers->entdata;
+ if (edata->tag)
+ {
+ if (vers->vn_rcs == NULL)
+ {
+ cvs_output (" Sticky Tag:\t\t", 0);
+ cvs_output (edata->tag, 0);
+ cvs_output (" - MISSING from RCS file!\n", 0);
+ }
+ else
+ {
+ if (isdigit ((unsigned char) edata->tag[0]))
+ {
+ cvs_output (" Sticky Tag:\t\t", 0);
+ cvs_output (edata->tag, 0);
+ cvs_output ("\n", 0);
+ }
+ else
+ {
+ char *branch = NULL;
+
+ if (RCS_nodeisbranch (finfo->rcs, edata->tag))
+ branch = RCS_whatbranch(finfo->rcs, edata->tag);
+
+ cvs_output (" Sticky Tag:\t\t", 0);
+ cvs_output (edata->tag, 0);
+ cvs_output (" (", 0);
+ cvs_output (branch ? "branch" : "revision", 0);
+ cvs_output (": ", 0);
+ cvs_output (branch ? branch : vers->vn_rcs, 0);
+ cvs_output (")\n", 0);
+
+ if (branch)
+ free (branch);
+ }
+ }
+ }
+ else if (!really_quiet)
+ cvs_output (" Sticky Tag:\t\t(none)\n", 0);
+
+ if (edata->date)
+ {
+ cvs_output (" Sticky Date:\t\t", 0);
+ cvs_output (edata->date, 0);
+ cvs_output ("\n", 0);
+ }
+ else if (!really_quiet)
+ cvs_output (" Sticky Date:\t\t(none)\n", 0);
+
+ if (edata->options && edata->options[0])
+ {
+ cvs_output (" Sticky Options:\t", 0);
+ cvs_output (edata->options, 0);
+ cvs_output ("\n", 0);
+ }
+ else if (!really_quiet)
+ cvs_output (" Sticky Options:\t(none)\n", 0);
+ }
+
+ if (long_format && vers->srcfile)
+ {
+ List *symbols = RCS_symbols(vers->srcfile);
+
+ cvs_output ("\n Existing Tags:\n", 0);
+ if (symbols)
+ {
+ xrcsnode = finfo->rcs;
+ (void) walklist (symbols, tag_list_proc, NULL);
+ }
+ else
+ cvs_output ("\tNo Tags Exist\n", 0);
+ }
+
+ cvs_output ("\n", 0);
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+status_dirproc (callerdat, dir, repos, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ if (!quiet)
+ error (0, 0, "Examining %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Print out a tag and its type
+ */
+static int
+tag_list_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ char *branch = NULL;
+ char *buf;
+
+ if (RCS_nodeisbranch (xrcsnode, p->key))
+ branch = RCS_whatbranch(xrcsnode, p->key) ;
+
+ buf = xmalloc (80 + strlen (p->key)
+ + (branch ? strlen (branch) : strlen (p->data)));
+ sprintf (buf, "\t%-25s\t(%s: %s)\n", p->key,
+ branch ? "branch" : "revision",
+ branch ? branch : p->data);
+ cvs_output (buf, 0);
+ free (buf);
+
+ if (branch)
+ free (branch);
+
+ return (0);
+}
diff --git a/gnu/dist/cvs/src/subr.c b/gnu/dist/cvs/src/subr.c
new file mode 100644
index 00000000000..7228d4b2666
--- /dev/null
+++ b/gnu/dist/cvs/src/subr.c
@@ -0,0 +1,780 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Various useful functions for the CVS support code.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+extern char *getlogin ();
+
+/*
+ * malloc some data and die if it fails
+ */
+void *
+xmalloc (bytes)
+ size_t bytes;
+{
+ char *cp;
+
+ /* Parts of CVS try to xmalloc zero bytes and then free it. Some
+ systems have a malloc which returns NULL for zero byte
+ allocations but a free which can't handle NULL, so compensate. */
+ if (bytes == 0)
+ bytes = 1;
+
+ cp = malloc (bytes);
+ if (cp == NULL)
+ {
+ char buf[80];
+ sprintf (buf, "out of memory; can not allocate %lu bytes",
+ (unsigned long) bytes);
+ error (1, 0, buf);
+ }
+ return (cp);
+}
+
+/*
+ * realloc data and die if it fails [I've always wanted to have "realloc" do
+ * a "malloc" if the argument is NULL, but you can't depend on it. Here, I
+ * can *force* it.
+ */
+void *
+xrealloc (ptr, bytes)
+ void *ptr;
+ size_t bytes;
+{
+ char *cp;
+
+ if (!ptr)
+ cp = malloc (bytes);
+ else
+ cp = realloc (ptr, bytes);
+
+ if (cp == NULL)
+ {
+ char buf[80];
+ sprintf (buf, "out of memory; can not reallocate %lu bytes",
+ (unsigned long) bytes);
+ error (1, 0, buf);
+ }
+ return (cp);
+}
+
+/* Two constants which tune expand_string. Having MIN_INCR as large
+ as 1024 might waste a bit of memory, but it shouldn't be too bad
+ (CVS used to allocate arrays of, say, 3000, PATH_MAX (8192, often),
+ or other such sizes). Probably anything which is going to allocate
+ memory which is likely to get as big as MAX_INCR shouldn't be doing
+ it in one block which must be contiguous, but since getrcskey does
+ so, we might as well limit the wasted memory to MAX_INCR or so
+ bytes.
+
+ MIN_INCR and MAX_INCR should both be powers of two and we generally
+ try to keep our allocations to powers of two for the most part.
+ Most malloc implementations these days tend to like that. */
+
+#define MIN_INCR 1024
+#define MAX_INCR (2*1024*1024)
+
+/* *STRPTR is a pointer returned from malloc (or NULL), pointing to *N
+ characters of space. Reallocate it so that points to at least
+ NEWSIZE bytes of space. Gives a fatal error if out of memory;
+ if it returns it was successful. */
+void
+expand_string (strptr, n, newsize)
+ char **strptr;
+ size_t *n;
+ size_t newsize;
+{
+ if (*n < newsize)
+ {
+ while (*n < newsize)
+ {
+ if (*n < MIN_INCR)
+ *n = MIN_INCR;
+ else if (*n >= MAX_INCR)
+ *n += MAX_INCR;
+ else
+ {
+ *n *= 2;
+ if (*n > MAX_INCR)
+ *n = MAX_INCR;
+ }
+ }
+ *strptr = xrealloc (*strptr, *n);
+ }
+}
+
+/*
+ * Duplicate a string, calling xmalloc to allocate some dynamic space
+ */
+char *
+xstrdup (str)
+ const char *str;
+{
+ char *s;
+
+ if (str == NULL)
+ return ((char *) NULL);
+ s = xmalloc (strlen (str) + 1);
+ (void) strcpy (s, str);
+ return (s);
+}
+
+/* Remove trailing newlines from STRING, destructively. */
+void
+strip_trailing_newlines (str)
+ char *str;
+{
+ int len;
+ len = strlen (str) - 1;
+
+ while (str[len] == '\n')
+ str[len--] = '\0';
+}
+
+/* Return the number of levels that path ascends above where it starts.
+ For example:
+ "../../foo" -> 2
+ "foo/../../bar" -> 1
+ */
+/* FIXME: Should be using ISDIRSEP, last_component, or some other
+ mechanism which is more general than just looking at slashes,
+ particularly for the client.c caller. The server.c caller might
+ want something different, so be careful. */
+int
+pathname_levels (path)
+ char *path;
+{
+ char *p;
+ char *q;
+ int level;
+ int max_level;
+
+ max_level = 0;
+ p = path;
+ level = 0;
+ do
+ {
+ q = strchr (p, '/');
+ if (q != NULL)
+ ++q;
+ if (p[0] == '.' && p[1] == '.' && (p[2] == '\0' || p[2] == '/'))
+ {
+ --level;
+ if (-level > max_level)
+ max_level = -level;
+ }
+ else if (p[0] == '.' && (p[1] == '\0' || p[1] == '/'))
+ ;
+ else
+ ++level;
+ p = q;
+ } while (p != NULL);
+ return max_level;
+}
+
+
+/* Free a vector, where (*ARGV)[0], (*ARGV)[1], ... (*ARGV)[*PARGC - 1]
+ are malloc'd and so is *ARGV itself. Such a vector is allocated by
+ line2argv or expand_wild, for example. */
+void
+free_names (pargc, argv)
+ int *pargc;
+ char **argv;
+{
+ register int i;
+
+ for (i = 0; i < *pargc; i++)
+ { /* only do through *pargc */
+ free (argv[i]);
+ }
+ free (argv);
+ *pargc = 0; /* and set it to zero when done */
+}
+
+/* Convert LINE into arguments separated by SEPCHARS. Set *ARGC
+ to the number of arguments found, and (*ARGV)[0] to the first argument,
+ (*ARGV)[1] to the second, etc. *ARGV is malloc'd and so are each of
+ (*ARGV)[0], (*ARGV)[1], ... Use free_names() to return the memory
+ allocated here back to the free pool. */
+void
+line2argv (pargc, argv, line, sepchars)
+ int *pargc;
+ char ***argv;
+ char *line;
+ char *sepchars;
+{
+ char *cp;
+ /* Could make a case for size_t or some other unsigned type, but
+ we'll stick with int to avoid signed/unsigned warnings when
+ comparing with *pargc. */
+ int argv_allocated;
+
+ /* Small for testing. */
+ /* argv_allocated must be at least 3 because at some places
+ (e.g. checkout_proc) cvs alters argv[2]. */
+ argv_allocated = 4;
+ *argv = (char **) xmalloc (argv_allocated * sizeof (**argv));
+
+ *pargc = 0;
+ for (cp = strtok (line, sepchars); cp; cp = strtok ((char *) NULL, sepchars))
+ {
+ if (*pargc == argv_allocated)
+ {
+ argv_allocated *= 2;
+ *argv = xrealloc (*argv, argv_allocated * sizeof (**argv));
+ }
+ (*argv)[*pargc] = xstrdup (cp);
+ (*pargc)++;
+ }
+}
+
+/*
+ * Returns the number of dots ('.') found in an RCS revision number
+ */
+int
+numdots (s)
+ const char *s;
+{
+ int dots = 0;
+
+ for (; *s; s++)
+ {
+ if (*s == '.')
+ dots++;
+ }
+ return (dots);
+}
+
+/* Compare revision numbers REV1 and REV2 by consecutive fields.
+ Return negative, zero, or positive in the manner of strcmp. The
+ two revision numbers must have the same number of fields, or else
+ compare_revnums will return an inaccurate result. */
+int
+compare_revnums (rev1, rev2)
+ const char *rev1;
+ const char *rev2;
+{
+ const char *s, *sp;
+ const char *t, *tp;
+ char *snext, *tnext;
+ int result = 0;
+
+ sp = s = rev1;
+ tp = t = rev2;
+ while (result == 0)
+ {
+ result = strtoul (sp, &snext, 10) - strtoul (tp, &tnext, 10);
+ if (*snext == '\0' || *tnext == '\0')
+ break;
+ sp = snext + 1;
+ tp = tnext + 1;
+ }
+
+ return result;
+}
+
+char *
+increment_revnum (rev)
+ const char *rev;
+{
+ char *newrev, *p;
+ int lastfield;
+ size_t len = strlen (rev);
+
+ newrev = (char *) xmalloc (len + 2);
+ memcpy (newrev, rev, len + 1);
+ p = strrchr (newrev, '.');
+ if (p == NULL)
+ {
+ free (newrev);
+ return NULL;
+ }
+ lastfield = atoi (++p);
+ sprintf (p, "%d", lastfield + 1);
+
+ return newrev;
+}
+
+/* Return the username by which the caller should be identified in
+ CVS, in contexts such as the author field of RCS files, various
+ logs, etc. */
+char *
+getcaller ()
+{
+#ifndef SYSTEM_GETCALLER
+ static char *cache;
+ struct passwd *pw;
+ uid_t uid;
+#endif
+
+ /* If there is a CVS username, return it. */
+#ifdef AUTH_SERVER_SUPPORT
+ if (CVS_Username != NULL)
+ return CVS_Username;
+#endif
+
+#ifdef SYSTEM_GETCALLER
+ return SYSTEM_GETCALLER ();
+#else
+ /* Get the caller's login from his uid. If the real uid is "root"
+ try LOGNAME USER or getlogin(). If getlogin() and getpwuid()
+ both fail, return the uid as a string. */
+
+ if (cache != NULL)
+ return cache;
+
+ uid = getuid ();
+ if (uid == (uid_t) 0)
+ {
+ char *name;
+
+ /* super-user; try getlogin() to distinguish */
+ if (((name = getlogin ()) || (name = getenv("LOGNAME")) ||
+ (name = getenv("USER"))) && *name)
+ {
+ cache = xstrdup (name);
+ return cache;
+ }
+ }
+ if ((pw = (struct passwd *) getpwuid (uid)) == NULL)
+ {
+ char uidname[20];
+
+ (void) sprintf (uidname, "uid%lu", (unsigned long) uid);
+ cache = xstrdup (uidname);
+ return cache;
+ }
+ cache = xstrdup (pw->pw_name);
+ return cache;
+#endif
+}
+
+#ifdef lint
+#ifndef __GNUC__
+/* ARGSUSED */
+time_t
+get_date (date, now)
+ char *date;
+ struct timeb *now;
+{
+ time_t foo = 0;
+
+ return (foo);
+}
+#endif
+#endif
+
+/* Given two revisions, find their greatest common ancestor. If the
+ two input revisions exist, then rcs guarantees that the gca will
+ exist. */
+
+char *
+gca (rev1, rev2)
+ const char *rev1;
+ const char *rev2;
+{
+ int dots;
+ char *gca;
+ const char *p[2];
+ int j[2];
+ char *retval;
+
+ if (rev1 == NULL || rev2 == NULL)
+ {
+ error (0, 0, "sanity failure in gca");
+ abort();
+ }
+
+ /* The greatest common ancestor will have no more dots, and numbers
+ of digits for each component no greater than the arguments. Therefore
+ this string will be big enough. */
+ gca = xmalloc (strlen (rev1) + strlen (rev2) + 100);
+
+ /* walk the strings, reading the common parts. */
+ gca[0] = '\0';
+ p[0] = rev1;
+ p[1] = rev2;
+ do
+ {
+ int i;
+ char c[2];
+ char *s[2];
+
+ for (i = 0; i < 2; ++i)
+ {
+ /* swap out the dot */
+ s[i] = strchr (p[i], '.');
+ if (s[i] != NULL) {
+ c[i] = *s[i];
+ }
+
+ /* read an int */
+ j[i] = atoi (p[i]);
+
+ /* swap back the dot... */
+ if (s[i] != NULL) {
+ *s[i] = c[i];
+ p[i] = s[i] + 1;
+ }
+ else
+ {
+ /* or mark us at the end */
+ p[i] = NULL;
+ }
+
+ }
+
+ /* use the lowest. */
+ (void) sprintf (gca + strlen (gca), "%d.",
+ j[0] < j[1] ? j[0] : j[1]);
+
+ } while (j[0] == j[1]
+ && p[0] != NULL
+ && p[1] != NULL);
+
+ /* back up over that last dot. */
+ gca[strlen(gca) - 1] = '\0';
+
+ /* numbers differ, or we ran out of strings. we're done with the
+ common parts. */
+
+ dots = numdots (gca);
+ if (dots == 0)
+ {
+ /* revisions differ in trunk major number. */
+
+ char *q;
+ const char *s;
+
+ s = (j[0] < j[1]) ? p[0] : p[1];
+
+ if (s == NULL)
+ {
+ /* we only got one number. this is strange. */
+ error (0, 0, "bad revisions %s or %s", rev1, rev2);
+ abort();
+ }
+ else
+ {
+ /* we have a minor number. use it. */
+ q = gca + strlen (gca);
+
+ *q++ = '.';
+ for ( ; *s != '.' && *s != '\0'; )
+ *q++ = *s++;
+
+ *q = '\0';
+ }
+ }
+ else if ((dots & 1) == 0)
+ {
+ /* if we have an even number of dots, then we have a branch.
+ remove the last number in order to make it a revision. */
+
+ char *s;
+
+ s = strrchr(gca, '.');
+ *s = '\0';
+ }
+
+ retval = xstrdup (gca);
+ free (gca);
+ return retval;
+}
+
+/* Give fatal error if REV is numeric and ARGC,ARGV imply we are
+ planning to operate on more than one file. The current directory
+ should be the working directory. Note that callers assume that we
+ will only be checking the first character of REV; it need not have
+ '\0' at the end of the tag name and other niceties. Right now this
+ is only called from admin.c, but if people like the concept it probably
+ should also be called from diff -r, update -r, get -r, and log -r. */
+
+void
+check_numeric (rev, argc, argv)
+ const char *rev;
+ int argc;
+ char **argv;
+{
+ if (rev == NULL || !isdigit ((unsigned char) *rev))
+ return;
+
+ /* Note that the check for whether we are processing more than one
+ file is (basically) syntactic; that is, we don't behave differently
+ depending on whether a directory happens to contain only a single
+ file or whether it contains more than one. I strongly suspect this
+ is the least confusing behavior. */
+ if (argc != 1
+ || (!wrap_name_has (argv[0], WRAP_TOCVS) && isdir (argv[0])))
+ {
+ error (0, 0, "while processing more than one file:");
+ error (1, 0, "attempt to specify a numeric revision");
+ }
+}
+
+/*
+ * Sanity checks and any required fix-up on message passed to RCS via '-m'.
+ * RCS 5.7 requires that a non-total-whitespace, non-null message be provided
+ * with '-m'. Returns a newly allocated, non-empty buffer with whitespace
+ * stripped from end of lines and end of buffer.
+ *
+ * TODO: We no longer use RCS to manage repository files, so maybe this
+ * nonsense about non-empty log fields can be dropped.
+ */
+char *
+make_message_rcslegal (message)
+ char *message;
+{
+ char *dst, *dp, *mp;
+
+ if (message == NULL) message = "";
+
+ /* Strip whitespace from end of lines and end of string. */
+ dp = dst = (char *) xmalloc (strlen (message) + 1);
+ for (mp = message; *mp != '\0'; ++mp)
+ {
+ if (*mp == '\n')
+ {
+ /* At end-of-line; backtrack to last non-space. */
+ while (dp > dst && (dp[-1] == ' ' || dp[-1] == '\t'))
+ --dp;
+ }
+ *dp++ = *mp;
+ }
+
+ /* Backtrack to last non-space at end of string, and truncate. */
+ while (dp > dst && isspace ((unsigned char) dp[-1]))
+ --dp;
+ *dp = '\0';
+
+ /* After all that, if there was no non-space in the string,
+ substitute a non-empty message. */
+ if (*dst == '\0')
+ {
+ free (dst);
+ dst = xstrdup ("*** empty log message ***");
+ }
+
+ return dst;
+}
+
+/* Does the file FINFO contain conflict markers? The whole concept
+ of looking at the contents of the file to figure out whether there are
+ unresolved conflicts is kind of bogus (people do want to manage files
+ which contain those patterns not as conflict markers), but for now it
+ is what we do. */
+int
+file_has_markers (finfo)
+ const struct file_info *finfo;
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t line_allocated = 0;
+ int result;
+
+ result = 0;
+ fp = CVS_FOPEN (finfo->file, "r");
+ if (fp == NULL)
+ error (1, errno, "cannot open %s", finfo->fullname);
+ while (getline (&line, &line_allocated, fp) > 0)
+ {
+ if (strncmp (line, RCS_MERGE_PAT, sizeof RCS_MERGE_PAT - 1) == 0)
+ {
+ result = 1;
+ goto out;
+ }
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", finfo->fullname);
+out:
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", finfo->fullname);
+ if (line != NULL)
+ free (line);
+ return result;
+}
+
+/* Read the entire contents of the file NAME into *BUF.
+ If NAME is NULL, read from stdin. *BUF
+ is a pointer returned from malloc (or NULL), pointing to *BUFSIZE
+ bytes of space. The actual size is returned in *LEN. On error,
+ give a fatal error. The name of the file to use in error messages
+ (typically will include a directory if we have changed directory)
+ is FULLNAME. MODE is "r" for text or "rb" for binary. */
+
+void
+get_file (name, fullname, mode, buf, bufsize, len)
+ const char *name;
+ const char *fullname;
+ const char *mode;
+ char **buf;
+ size_t *bufsize;
+ size_t *len;
+{
+ struct stat s;
+ size_t nread;
+ char *tobuf;
+ FILE *e;
+ size_t filesize;
+
+ if (name == NULL)
+ {
+ e = stdin;
+ filesize = 100; /* force allocation of minimum buffer */
+ }
+ else
+ {
+ /* Although it would be cleaner in some ways to just read
+ until end of file, reallocating the buffer, this function
+ does get called on files in the working directory which can
+ be of arbitrary size, so I think we better do all that
+ extra allocation. */
+
+ if (CVS_STAT (name, &s) < 0)
+ error (1, errno, "can't stat %s", fullname);
+
+ /* Convert from signed to unsigned. */
+ filesize = s.st_size;
+
+ e = open_file (name, mode);
+ }
+
+ if (*bufsize < filesize)
+ {
+ *bufsize = filesize;
+ *buf = xrealloc (*buf, *bufsize);
+ }
+
+ tobuf = *buf;
+ nread = 0;
+ while (1)
+ {
+ size_t got;
+
+ got = fread (tobuf, 1, *bufsize - (tobuf - *buf), e);
+ if (ferror (e))
+ error (1, errno, "can't read %s", fullname);
+ nread += got;
+ tobuf += got;
+
+ if (feof (e))
+ break;
+
+ /* Allocate more space if needed. */
+ if (tobuf == *buf + *bufsize)
+ {
+ int c;
+ long off;
+
+ c = getc (e);
+ if (c == EOF)
+ break;
+ off = tobuf - *buf;
+ expand_string (buf, bufsize, *bufsize + 100);
+ tobuf = *buf + off;
+ *tobuf++ = c;
+ ++nread;
+ }
+ }
+
+ if (e != stdin && fclose (e) < 0)
+ error (0, errno, "cannot close %s", fullname);
+
+ *len = nread;
+
+ /* Force *BUF to be large enough to hold a null terminator. */
+ if (*buf != NULL)
+ {
+ if (nread == *bufsize)
+ expand_string (buf, bufsize, *bufsize + 1);
+ (*buf)[nread] = '\0';
+ }
+}
+
+
+/* Follow a chain of symbolic links to its destination. FILENAME
+ should be a handle to a malloc'd block of memory which contains the
+ beginning of the chain. This routine will replace the contents of
+ FILENAME with the destination (a real file). */
+
+void
+resolve_symlink (filename)
+ char **filename;
+{
+ if ((! filename) || (! *filename))
+ return;
+
+ while (islink (*filename))
+ {
+ char *newname;
+#ifdef HAVE_READLINK
+ /* The clean thing to do is probably to have each filesubr.c
+ implement this (with an error if not supported by the
+ platform, in which case islink would presumably return 0).
+ But that would require editing each filesubr.c and so the
+ expedient hack seems to be looking at HAVE_READLINK. */
+ newname = xreadlink (*filename);
+#else
+ error (1, 0, "internal error: islink doesn't like readlink");
+#endif
+
+ if (isabsolute (newname))
+ {
+ free (*filename);
+ *filename = newname;
+ }
+ else
+ {
+ char *oldname = last_component (*filename);
+ int dirlen = oldname - *filename;
+ char *fullnewname = xmalloc (dirlen + strlen (newname) + 1);
+ strncpy (fullnewname, *filename, dirlen);
+ strcpy (fullnewname + dirlen, newname);
+ free (newname);
+ free (*filename);
+ *filename = fullnewname;
+ }
+ }
+}
+
+/*
+ * Rename a file to an appropriate backup name based on BAKPREFIX.
+ * If suffix non-null, then ".<suffix>" is appended to the new name.
+ *
+ * Returns the new name, which caller may free() if desired.
+ */
+char *
+backup_file (filename, suffix)
+ const char *filename;
+ const char *suffix;
+{
+ char *backup_name;
+
+ if (suffix == NULL)
+ {
+ backup_name = xmalloc (sizeof (BAKPREFIX) + strlen (filename) + 1);
+ sprintf (backup_name, "%s%s", BAKPREFIX, filename);
+ }
+ else
+ {
+ backup_name = xmalloc (sizeof (BAKPREFIX)
+ + strlen (filename)
+ + strlen (suffix)
+ + 2); /* one for dot, one for trailing '\0' */
+ sprintf (backup_name, "%s%s.%s", BAKPREFIX, filename, suffix);
+ }
+
+ if (isfile (filename))
+ copy_file (filename, backup_name);
+
+ return backup_name;
+}
+
diff --git a/gnu/dist/cvs/src/tag.c b/gnu/dist/cvs/src/tag.c
new file mode 100644
index 00000000000..fb19cbae78a
--- /dev/null
+++ b/gnu/dist/cvs/src/tag.c
@@ -0,0 +1,929 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * Tag
+ *
+ * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
+ * Uses the checked out revision in the current directory.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+static int check_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static int check_filesdoneproc PROTO ((void *callerdat, int err,
+ char *repos, char *update_dir,
+ List *entries));
+static int pretag_proc PROTO((char *repository, char *filter));
+static void masterlist_delproc PROTO((Node *p));
+static void tag_delproc PROTO((Node *p));
+static int pretag_list_proc PROTO((Node *p, void *closure));
+
+static Dtype tag_dirproc PROTO ((void *callerdat, char *dir,
+ char *repos, char *update_dir,
+ List *entries));
+static int tag_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+static int tag_filesdoneproc PROTO ((void *callerdat, int err,
+ char *repos, char *update_dir,
+ List *entries));
+
+static char *numtag;
+static char *date = NULL;
+static char *symtag;
+static int delete_flag; /* adding a tag by default */
+static int branch_mode; /* make an automagic "branch" tag */
+static int local; /* recursive by default */
+static int force_tag_match = 1; /* force tag to match by default */
+static int force_tag_move; /* don't force tag to move by default */
+static int check_uptodate; /* no uptodate-check by default */
+
+struct tag_info
+{
+ Ctype status;
+ char *rev;
+ char *tag;
+ char *options;
+};
+
+struct master_lists
+{
+ List *tlist;
+};
+
+static List *mtlist;
+static List *tlist;
+
+static const char *const tag_usage[] =
+{
+ "Usage: %s %s [-lRF] [-b] [-d] [-c] [-r rev|-D date] tag [files...]\n",
+ "\t-l\tLocal directory only, not recursive.\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-d\tDelete the given tag.\n",
+ "\t-r rev\tExisting revision/tag.\n",
+ "\t-D\tExisting date.\n",
+ "\t-f\tForce a head revision if specified tag not found.\n",
+ "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
+ "\t-F\tMove tag if it already exists.\n",
+ "\t-c\tCheck that working files are unmodified.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+int
+cvstag (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+
+ if (argc == -1)
+ usage (tag_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+FQqlRcdr:D:bf")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'd':
+ delete_flag = 1;
+ break;
+ case 'c':
+ check_uptodate = 1;
+ break;
+ case 'r':
+ numtag = optarg;
+ break;
+ case 'D':
+ if (date)
+ free (date);
+ date = Make_Date (optarg);
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'b':
+ branch_mode = 1;
+ break;
+ case 'F':
+ force_tag_move = 1;
+ break;
+ case '?':
+ default:
+ usage (tag_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage (tag_usage);
+ symtag = argv[0];
+ argc--;
+ argv++;
+
+ if (date && numtag)
+ error (1, 0, "-r and -D options are mutually exclusive");
+ if (delete_flag && branch_mode)
+ error (0, 0, "warning: -b ignored with -d options");
+ RCS_check_tag (symtag);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (!force_tag_match)
+ send_arg ("-f");
+ if (local)
+ send_arg("-l");
+ if (delete_flag)
+ send_arg("-d");
+ if (check_uptodate)
+ send_arg("-c");
+ if (branch_mode)
+ send_arg("-b");
+ if (force_tag_move)
+ send_arg("-F");
+
+ if (numtag)
+ option_with_arg ("-r", numtag);
+ if (date)
+ client_senddate (date);
+
+ send_arg (symtag);
+
+ send_files (argc, argv, local, 0,
+
+ /* I think the -c case is like "cvs status", in
+ which we really better be correct rather than
+ being fast; it is just too confusing otherwise. */
+ check_uptodate ? 0 : SEND_NO_CONTENTS);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_to_server ("tag\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ if (numtag != NULL)
+ tag_check_valid (numtag, argc, argv, local, 0, "");
+
+ /* check to make sure they are authorized to tag all the
+ specified files in the repository */
+
+ mtlist = getlist();
+ err = start_recursion (check_fileproc, check_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 1,
+ (char *) NULL, 1);
+
+ if (err)
+ {
+ error (1, 0, "correct the above errors first!");
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (tag_fileproc, tag_filesdoneproc, tag_dirproc,
+ (DIRLEAVEPROC) NULL, NULL, argc, argv, local,
+ W_LOCAL, 0, 0, (char *) NULL, 1);
+ dellist(&mtlist);
+ return (err);
+}
+
+/* check file that is to be tagged */
+/* All we do here is add it to our list */
+
+static int
+check_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *xdir;
+ Node *p;
+ Vers_TS *vers;
+
+ if (check_uptodate)
+ {
+ Ctype status = Classify_File (finfo, (char *) NULL, (char *) NULL,
+ (char *) NULL, 1, 0, &vers, 0);
+ if ((status != T_UPTODATE) && (status != T_CHECKOUT))
+ {
+ error (0, 0, "%s is locally modified", finfo->fullname);
+ return (1);
+ }
+ }
+
+ if (finfo->update_dir[0] == '\0')
+ xdir = ".";
+ else
+ xdir = finfo->update_dir;
+ if ((p = findnode (mtlist, xdir)) != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ struct master_lists *ml;
+
+ tlist = getlist ();
+ p = getnode ();
+ p->key = xstrdup (xdir);
+ p->type = UPDATE;
+ ml = (struct master_lists *)
+ xmalloc (sizeof (struct master_lists));
+ ml->tlist = tlist;
+ p->data = (char *) ml;
+ p->delproc = masterlist_delproc;
+ (void) addnode (mtlist, p);
+ }
+ /* do tlist */
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = tag_delproc;
+ vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+ if (vers->srcfile == NULL)
+ {
+ if (!really_quiet)
+ error (0, 0, "nothing known about %s", finfo->file);
+ return (1);
+ }
+
+ /* Here we duplicate the calculation in tag_fileproc about which
+ version we are going to tag. There probably are some subtle races
+ (e.g. numtag is "foo" which gets moved between here and
+ tag_fileproc). */
+ if (numtag == NULL && date == NULL)
+ p->data = xstrdup (vers->vn_user);
+ else
+ p->data = RCS_getversion (vers->srcfile, numtag, date,
+ force_tag_match, NULL);
+
+ if (p->data != NULL)
+ {
+ int addit = 1;
+ char *oversion;
+
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
+ (int *) NULL);
+ if (oversion == NULL)
+ {
+ if (delete_flag)
+ {
+ /* Deleting a tag which did not exist is a noop and
+ should not be logged. */
+ addit = 0;
+ }
+ }
+ else if (delete_flag)
+ {
+ free (p->data);
+ p->data = xstrdup (oversion);
+ }
+ else if (strcmp(oversion, p->data) == 0)
+ {
+ addit = 0;
+ }
+ else if (!force_tag_move)
+ {
+ addit = 0;
+ }
+ if (oversion != NULL)
+ {
+ free(oversion);
+ }
+ if (!addit)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ }
+ freevers_ts(&vers);
+ (void) addnode (tlist, p);
+ return (0);
+}
+
+static int
+check_filesdoneproc (callerdat, err, repos, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ int n;
+ Node *p;
+
+ p = findnode(mtlist, update_dir);
+ if (p != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ tlist = (List *) NULL;
+ }
+ if ((tlist == NULL) || (tlist->list->next == tlist->list))
+ {
+ return (err);
+ }
+ if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
+ {
+ error (0, 0, "Pre-tag check failed");
+ err += n;
+ }
+ return (err);
+}
+
+static int
+pretag_proc(repository, filter)
+ char *repository;
+ char *filter;
+{
+ if (filter[0] == '/')
+ {
+ char *s, *cp;
+
+ s = xstrdup(filter);
+ for (cp=s; *cp; cp++)
+ {
+ if (isspace ((unsigned char) *cp))
+ {
+ *cp = '\0';
+ break;
+ }
+ }
+ if (!isfile(s))
+ {
+ error (0, errno, "cannot find pre-tag filter '%s'", s);
+ free(s);
+ return (1);
+ }
+ free(s);
+ }
+ run_setup (filter);
+ run_arg (symtag);
+ run_arg (delete_flag ? "del" : force_tag_move ? "mov" : "add");
+ run_arg (repository);
+ walklist(tlist, pretag_list_proc, NULL);
+ return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL));
+}
+
+static void
+masterlist_delproc(p)
+ Node *p;
+{
+ struct master_lists *ml;
+
+ ml = (struct master_lists *)p->data;
+ dellist(&ml->tlist);
+ free(ml);
+ return;
+}
+
+static void
+tag_delproc(p)
+ Node *p;
+{
+ if (p->data != NULL)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ return;
+}
+
+static int
+pretag_list_proc(p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data != NULL)
+ {
+ run_arg(p->key);
+ run_arg(p->data);
+ }
+ return (0);
+}
+
+
+/*
+ * Called to tag a particular file (the currently checked out version is
+ * tagged with the specified tag - or the specified tag is deleted).
+ */
+/* ARGSUSED */
+static int
+tag_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *version, *oversion;
+ char *nversion = NULL;
+ char *rev;
+ Vers_TS *vers;
+ int retcode = 0;
+
+ /* Lock the directory if it is not already locked. We can't rely
+ on tag_dirproc because it won't handle the case where the user
+ specifies a list of files on the command line. */
+ /* We do not need to acquire a full write lock for the tag operation:
+ the revisions are obtained from the working directory, so we do not
+ require consistency across the entire repository. However, we do
+ need to prevent simultaneous tag operations from interfering with
+ each other. Therefore, we write lock each directory as we enter
+ it, and unlock it as we leave it. */
+ lock_dir_for_write (finfo->repository);
+
+ vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
+
+ if ((numtag != NULL) || (date != NULL))
+ {
+ nversion = RCS_getversion(vers->srcfile,
+ numtag,
+ date,
+ force_tag_match,
+ (int *) NULL);
+ if (nversion == NULL)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ }
+ if (delete_flag)
+ {
+
+ /*
+ * If -d is specified, "force_tag_match" is set, so that this call to
+ * RCS_getversion() will return a NULL version string if the symbolic
+ * tag does not exist in the RCS file.
+ *
+ * This is done here because it's MUCH faster than just blindly calling
+ * "rcs" to remove the tag... trust me.
+ */
+
+ version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
+ (int *) NULL);
+ if (version == NULL || vers->srcfile == NULL)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ free (version);
+
+ if ((retcode = RCS_deltag(vers->srcfile, symtag)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag %s from %s", symtag,
+ vers->srcfile->path);
+ freevers_ts (&vers);
+ return (1);
+ }
+ RCS_rewrite (vers->srcfile, NULL, NULL);
+
+ /* warm fuzzies */
+ if (!really_quiet)
+ {
+ cvs_output ("D ", 2);
+ cvs_output (finfo->fullname, 0);
+ cvs_output ("\n", 1);
+ }
+
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ /*
+ * If we are adding a tag, we need to know which version we have checked
+ * out and we'll tag that version.
+ */
+ if (nversion == NULL)
+ {
+ version = vers->vn_user;
+ }
+ else
+ {
+ version = nversion;
+ }
+ if (version == NULL)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ else if (strcmp (version, "0") == 0)
+ {
+ if (!quiet)
+ error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
+ freevers_ts (&vers);
+ return (0);
+ }
+ else if (version[0] == '-')
+ {
+ if (!quiet)
+ error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
+ freevers_ts (&vers);
+ return (0);
+ }
+ else if (vers->srcfile == NULL)
+ {
+ if (!quiet)
+ error (0, 0, "cannot find revision control file for `%s'", finfo->file);
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ /*
+ * As an enhancement for the case where a tag is being re-applied to a
+ * large number of files, make one extra call to RCS_getversion to see
+ * if the tag is already set in the RCS file. If so, check to see if it
+ * needs to be moved. If not, do nothing. This will likely save a lot of
+ * time when simply moving the tag to the "current" head revisions of a
+ * module -- which I have found to be a typical tagging operation.
+ */
+ rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1,
+ (int *) NULL);
+ if (oversion != NULL)
+ {
+ int isbranch = RCS_nodeisbranch (finfo->rcs, symtag);
+
+ /*
+ * if versions the same and neither old or new are branches don't have
+ * to do anything
+ */
+ if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
+ {
+ free (oversion);
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ if (!force_tag_move)
+ {
+ /* we're NOT going to move the tag */
+ cvs_output ("W ", 2);
+ cvs_output (finfo->fullname, 0);
+ cvs_output (" : ", 0);
+ cvs_output (symtag, 0);
+ cvs_output (" already exists on ", 0);
+ cvs_output (isbranch ? "branch" : "version", 0);
+ cvs_output (" ", 0);
+ cvs_output (oversion, 0);
+ cvs_output (" : NOT MOVING tag to ", 0);
+ cvs_output (branch_mode ? "branch" : "version", 0);
+ cvs_output (" ", 0);
+ cvs_output (rev, 0);
+ cvs_output ("\n", 1);
+ free (oversion);
+ freevers_ts (&vers);
+ return (0);
+ }
+ free (oversion);
+ }
+
+ if ((retcode = RCS_settag(vers->srcfile, symtag, rev)) != 0)
+ {
+ error (1, retcode == -1 ? errno : 0,
+ "failed to set tag %s to revision %s in %s",
+ symtag, rev, vers->srcfile->path);
+ freevers_ts (&vers);
+ return (1);
+ }
+ RCS_rewrite (vers->srcfile, NULL, NULL);
+
+ /* more warm fuzzies */
+ if (!really_quiet)
+ {
+ cvs_output ("T ", 2);
+ cvs_output (finfo->fullname, 0);
+ cvs_output ("\n", 1);
+ }
+
+ if (nversion != NULL)
+ {
+ free (nversion);
+ }
+ freevers_ts (&vers);
+ return (0);
+}
+
+/* Clear any lock we may hold on the current directory. */
+
+static int
+tag_filesdoneproc (callerdat, err, repos, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ Lock_Cleanup ();
+
+ return (err);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+tag_dirproc (callerdat, dir, repos, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repos;
+ char *update_dir;
+ List *entries;
+{
+ if (!quiet)
+ error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
+ return (R_PROCESS);
+}
+
+/* Code relating to the val-tags file. Note that this file has no way
+ of knowing when a tag has been deleted. The problem is that there
+ is no way of knowing whether a tag still exists somewhere, when we
+ delete it some places. Using per-directory val-tags files (in
+ CVSREP) might be better, but that might slow down the process of
+ verifying that a tag is correct (maybe not, for the likely cases,
+ if carefully done), and/or be harder to implement correctly. */
+
+struct val_args {
+ char *name;
+ int found;
+};
+
+static int val_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+static int
+val_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ RCSNode *rcsdata;
+ struct val_args *args = (struct val_args *)callerdat;
+ char *tag;
+
+ if ((rcsdata = finfo->rcs) == NULL)
+ /* Not sure this can happen, after all we passed only
+ W_REPOS | W_ATTIC. */
+ return 0;
+
+ tag = RCS_gettag (rcsdata, args->name, 1, (int *) NULL);
+ if (tag != NULL)
+ {
+ /* FIXME: should find out a way to stop the search at this point. */
+ args->found = 1;
+ free (tag);
+ }
+ return 0;
+}
+
+static Dtype val_direntproc PROTO ((void *, char *, char *, char *, List *));
+
+static Dtype
+val_direntproc (callerdat, dir, repository, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repository;
+ char *update_dir;
+ List *entries;
+{
+ /* This is not quite right--it doesn't get right the case of "cvs
+ update -d -r foobar" where foobar is a tag which exists only in
+ files in a directory which does not exist yet, but which is
+ about to be created. */
+ if (isdir (dir))
+ return 0;
+ return R_SKIP_ALL;
+}
+
+/* Check to see whether NAME is a valid tag. If so, return. If not
+ print an error message and exit. ARGC, ARGV, LOCAL, and AFLAG specify
+ which files we will be operating on.
+
+ REPOSITORY is the repository if we need to cd into it, or NULL if
+ we are already there, or "" if we should do a W_LOCAL recursion.
+ Sorry for three cases, but the "" case is needed in case the
+ working directories come from diverse parts of the repository, the
+ NULL case avoids an unneccesary chdir, and the non-NULL, non-""
+ case is needed for checkout, where we don't want to chdir if the
+ tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
+ local directory. */
+void
+tag_check_valid (name, argc, argv, local, aflag, repository)
+ char *name;
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+ char *repository;
+{
+ DBM *db;
+ char *valtags_filename;
+ int err;
+ datum mytag;
+ struct val_args the_val_args;
+ struct saved_cwd cwd;
+ int which;
+
+ /* Numeric tags require only a syntactic check. */
+ if (isdigit ((unsigned char) name[0]))
+ {
+ char *p;
+ for (p = name; *p != '\0'; ++p)
+ {
+ if (!(isdigit ((unsigned char) *p) || *p == '.'))
+ error (1, 0, "\
+Numeric tag %s contains characters other than digits and '.'", name);
+ }
+ return;
+ }
+
+ /* Special tags are always valid. */
+ if (strcmp (name, TAG_BASE) == 0
+ || strcmp (name, TAG_HEAD) == 0)
+ return;
+
+ /* FIXME: This routine doesn't seem to do any locking whatsoever
+ (and it is called from places which don't have locks in place).
+ If two processes try to write val-tags at the same time, it would
+ seem like we are in trouble. */
+
+ mytag.dptr = name;
+ mytag.dsize = strlen (name);
+
+ valtags_filename = xmalloc (strlen (CVSroot_directory)
+ + sizeof CVSROOTADM
+ + sizeof CVSROOTADM_VALTAGS + 20);
+ strcpy (valtags_filename, CVSroot_directory);
+ strcat (valtags_filename, "/");
+ strcat (valtags_filename, CVSROOTADM);
+ strcat (valtags_filename, "/");
+ strcat (valtags_filename, CVSROOTADM_VALTAGS);
+ db = dbm_open (valtags_filename, O_RDWR, 0666);
+ if (db == NULL)
+ {
+ if (!existence_error (errno))
+ error (1, errno, "cannot read %s", valtags_filename);
+
+ /* If the file merely fails to exist, we just keep going and create
+ it later if need be. */
+ }
+ else
+ {
+ datum val;
+
+ val = dbm_fetch (db, mytag);
+ if (val.dptr != NULL)
+ {
+ /* Found. The tag is valid. */
+ dbm_close (db);
+ free (valtags_filename);
+ return;
+ }
+ /* FIXME: should check errors somehow (add dbm_error to myndbm.c?). */
+ }
+
+ /* We didn't find the tag in val-tags, so look through all the RCS files
+ to see whether it exists there. Yes, this is expensive, but there
+ is no other way to cope with a tag which might have been created
+ by an old version of CVS, from before val-tags was invented.
+
+ Since we need this code anyway, we also use it to create
+ entries in val-tags in general (that is, the val-tags entry
+ will get created the first time the tag is used, not when the
+ tag is created). */
+
+ the_val_args.name = name;
+ the_val_args.found = 0;
+
+ which = W_REPOS | W_ATTIC;
+
+ if (repository != NULL)
+ {
+ if (repository[0] == '\0')
+ which |= W_LOCAL;
+ else
+ {
+ if (save_cwd (&cwd))
+ error_exit ();
+ if ( CVS_CHDIR (repository) < 0)
+ error (1, errno, "cannot change to %s directory", repository);
+ }
+ }
+
+ err = start_recursion (val_fileproc, (FILESDONEPROC) NULL,
+ val_direntproc, (DIRLEAVEPROC) NULL,
+ (void *)&the_val_args,
+ argc, argv, local, which, aflag,
+ 1, NULL, 1);
+ if (repository != NULL && repository[0] != '\0')
+ {
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ }
+
+ if (!the_val_args.found)
+ error (1, 0, "no such tag %s", name);
+ else
+ {
+ /* The tags is valid but not mentioned in val-tags. Add it. */
+ datum value;
+
+ if (noexec)
+ {
+ if (db != NULL)
+ dbm_close (db);
+ free (valtags_filename);
+ return;
+ }
+
+ if (db == NULL)
+ {
+ mode_t omask;
+ omask = umask (cvsumask);
+ db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
+ (void) umask (omask);
+
+ if (db == NULL)
+ {
+ error (0, errno, "cannot create %s", valtags_filename);
+ free (valtags_filename);
+ return;
+ }
+ }
+ value.dptr = "y";
+ value.dsize = 1;
+ if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
+ error (0, errno, "cannot store %s into %s", name,
+ valtags_filename);
+ dbm_close (db);
+ }
+ free (valtags_filename);
+}
+
+/*
+ * Check whether a join tag is valid. This is just like
+ * tag_check_valid, but we must stop before the colon if there is one.
+ */
+
+void
+tag_check_valid_join (join_tag, argc, argv, local, aflag, repository)
+ char *join_tag;
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+ char *repository;
+{
+ char *c, *s;
+
+ c = xstrdup (join_tag);
+ s = strchr (c, ':');
+ if (s != NULL)
+ {
+ if (isdigit ((unsigned char) join_tag[0]))
+ error (1, 0,
+ "Numeric join tag %s may not contain a date specifier",
+ join_tag);
+
+ *s = '\0';
+ /* hmmm... I think it makes sense to allow -j:<date>, but
+ * for now this fixes a bug where CVS just spins and spins (I
+ * think in the RCS code) looking for a zero length tag.
+ */
+ if (!*c)
+ error (1, 0,
+ "argument to join may not contain a date specifier without a tag");
+ }
+
+ tag_check_valid (c, argc, argv, local, aflag, repository);
+
+ free (c);
+}
diff --git a/gnu/dist/cvs/src/update.c b/gnu/dist/cvs/src/update.c
new file mode 100644
index 00000000000..61d84d43737
--- /dev/null
+++ b/gnu/dist/cvs/src/update.c
@@ -0,0 +1,2861 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * "update" updates the version in the present directory with respect to the RCS
+ * repository. The present version must have been created by "checkout". The
+ * user can keep up-to-date by calling "update" whenever he feels like it.
+ *
+ * The present version can be committed by "commit", but this keeps the version
+ * in tact.
+ *
+ * Arguments following the options are taken to be file names to be updated,
+ * rather than updating the entire directory.
+ *
+ * Modified or non-existent RCS files are checked out and reported as U
+ * <user_file>
+ *
+ * Modified user files are reported as M <user_file>. If both the RCS file and
+ * the user file have been modified, the user file is replaced by the result
+ * of rcsmerge, and a backup file is written for the user in .#file.version.
+ * If this throws up irreconcilable differences, the file is reported as C
+ * <user_file>, and as M <user_file> otherwise.
+ *
+ * Files added but not yet committed are reported as A <user_file>. Files
+ * removed but not yet committed are reported as R <user_file>.
+ *
+ * If the current directory contains subdirectories that hold concurrent
+ * versions, these are updated too. If the -d option was specified, new
+ * directories added to the repository are automatically created and updated
+ * as well.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+#ifdef SERVER_SUPPORT
+#include "md5.h"
+#endif
+#include "watch.h"
+#include "fileattr.h"
+#include "edit.h"
+#include "getline.h"
+#include "buffer.h"
+#include "hardlink.h"
+
+static int checkout_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts,
+ int adding, int merging, int update_server));
+#ifdef SERVER_SUPPORT
+static void checkout_to_buffer PROTO ((void *, const char *, size_t));
+#endif
+#ifdef SERVER_SUPPORT
+static int patch_file PROTO ((struct file_info *finfo,
+ Vers_TS *vers_ts,
+ int *docheckout, struct stat *file_info,
+ unsigned char *checksum));
+static void patch_file_write PROTO ((void *, const char *, size_t));
+#endif
+static int merge_file PROTO ((struct file_info *finfo, Vers_TS *vers));
+static int scratch_file PROTO((struct file_info *finfo));
+static Dtype update_dirent_proc PROTO ((void *callerdat, char *dir,
+ char *repository, char *update_dir,
+ List *entries));
+static int update_dirleave_proc PROTO ((void *callerdat, char *dir,
+ int err, char *update_dir,
+ List *entries));
+static int update_fileproc PROTO ((void *callerdat, struct file_info *));
+static int update_filesdone_proc PROTO ((void *callerdat, int err,
+ char *repository, char *update_dir,
+ List *entries));
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+static int get_linkinfo_proc PROTO ((void *callerdat, struct file_info *));
+#endif
+static void write_letter PROTO ((struct file_info *finfo, int letter));
+static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts));
+
+static char *options = NULL;
+static char *tag = NULL;
+static char *date = NULL;
+/* This is a bit of a kludge. We call WriteTag at the beginning
+ before we know whether nonbranch is set or not. And then at the
+ end, once we have the right value for nonbranch, we call WriteTag
+ again. I don't know whether the first call is necessary or not.
+ rewrite_tag is nonzero if we are going to have to make that second
+ call. */
+static int rewrite_tag;
+static int nonbranch;
+
+/* If we set the tag or date for a subdirectory, we use this to undo
+ the setting. See update_dirent_proc. */
+static char *tag_update_dir;
+
+static char *join_rev1, *date_rev1;
+static char *join_rev2, *date_rev2;
+static int aflag = 0;
+static int toss_local_changes = 0;
+static int force_tag_match = 1;
+static int update_build_dirs = 0;
+static int update_prune_dirs = 0;
+static int pipeout = 0;
+#ifdef SERVER_SUPPORT
+static int patches = 0;
+static int rcs_diff_patches = 0;
+#endif
+static List *ignlist = (List *) NULL;
+static time_t last_register_time;
+static const char *const update_usage[] =
+{
+ "Usage: %s %s [-APdflRp] [-k kopt] [-r rev|-D date] [-j rev]\n",
+ " [-I ign] [-W spec] [files...]\n",
+ "\t-A\tReset any sticky tags/date/kopts.\n",
+ "\t-P\tPrune empty directories.\n",
+ "\t-C\tOverwrite locally modified files with clean repository copies.\n",
+ "\t-d\tBuild directories, like checkout does.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-p\tSend updates to standard output (avoids stickiness).\n",
+ "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+ "\t-r rev\tUpdate using specified revision/tag (is sticky).\n",
+ "\t-D date\tSet date to update from (is sticky).\n",
+ "\t-j rev\tMerge in changes made between current revision and rev.\n",
+ "\t-I ign\tMore files to ignore (! to reset).\n",
+ "\t-W spec\tWrappers specification line.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+/*
+ * update is the argv,argc based front end for arg parsing
+ */
+int
+update (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c, err;
+ int local = 0; /* recursive by default */
+ int which; /* where to look for files and dirs */
+
+ if (argc == -1)
+ usage (update_usage);
+
+ ign_setup ();
+ wrap_setup ();
+
+ /* parse the args */
+ optind = 0;
+ while ((c = getopt (argc, argv, "+ApCPflRQqduk:r:D:j:I:W:")) != -1)
+ {
+ switch (c)
+ {
+ case 'A':
+ aflag = 1;
+ break;
+ case 'C':
+ toss_local_changes = 1;
+ break;
+ case 'I':
+ ign_add (optarg, 0);
+ break;
+ case 'W':
+ wrap_add (optarg, 0);
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'd':
+ update_build_dirs = 1;
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'r':
+ tag = optarg;
+ break;
+ case 'D':
+ date = Make_Date (optarg);
+ break;
+ case 'P':
+ update_prune_dirs = 1;
+ break;
+ case 'p':
+ pipeout = 1;
+ noexec = 1; /* so no locks will be created */
+ break;
+ case 'j':
+ if (join_rev2)
+ error (1, 0, "only two -j options can be specified");
+ if (join_rev1)
+ join_rev2 = optarg;
+ else
+ join_rev1 = optarg;
+ break;
+ case 'u':
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ patches = 1;
+ rcs_diff_patches = server_use_rcs_diff ();
+ }
+ else
+#endif
+ usage (update_usage);
+ break;
+ case '?':
+ default:
+ usage (update_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int pass;
+
+ /* The first pass does the regular update. If we receive at least
+ one patch which failed, we do a second pass and just fetch
+ those files whose patches failed. */
+ pass = 1;
+ do
+ {
+ int status;
+
+ start_server ();
+
+ if (local)
+ send_arg("-l");
+ if (update_build_dirs)
+ send_arg("-d");
+ if (pipeout)
+ send_arg("-p");
+ if (!force_tag_match)
+ send_arg("-f");
+ if (aflag)
+ send_arg("-A");
+ if (toss_local_changes)
+ send_arg("-C");
+ if (update_prune_dirs)
+ send_arg("-P");
+ client_prune_dirs = update_prune_dirs;
+ option_with_arg ("-r", tag);
+ if (options && options[0] != '\0')
+ send_arg (options);
+ if (date)
+ client_senddate (date);
+ if (join_rev1)
+ option_with_arg ("-j", join_rev1);
+ if (join_rev2)
+ option_with_arg ("-j", join_rev2);
+ wrap_send ();
+
+ /* If the server supports the command "update-patches", that means
+ that it knows how to handle the -u argument to update, which
+ means to send patches instead of complete files.
+
+ We don't send -u if failed_patches != NULL, so that the
+ server doesn't try to send patches which will just fail
+ again. At least currently, the client also clobbers the
+ file and tells the server it is lost, which also will get
+ a full file instead of a patch, but it seems clean to omit
+ -u. */
+ if (failed_patches == NULL)
+ {
+ if (supported_request ("update-patches"))
+ send_arg ("-u");
+ }
+
+ if (failed_patches == NULL)
+ {
+ unsigned int flags = 0;
+
+ if (update_build_dirs)
+ flags |= SEND_BUILD_DIRS;
+
+ if (toss_local_changes) {
+ flags |= SEND_NO_CONTENTS;
+ flags |= BACKUP_MODIFIED_FILES;
+ }
+
+ /* If noexec, probably could be setting SEND_NO_CONTENTS.
+ Same caveats as for "cvs status" apply. */
+
+ send_files (argc, argv, local, aflag, flags);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ }
+ else
+ {
+ int i;
+
+ (void) printf ("%s client: refetching unpatchable files\n",
+ program_name);
+
+ if (toplevel_wd != NULL
+ && CVS_CHDIR (toplevel_wd) < 0)
+ {
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+
+ for (i = 0; i < failed_patches_count; i++)
+ if (unlink_file (failed_patches[i]) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s",
+ failed_patches[i]);
+ send_files (failed_patches_count, failed_patches, local,
+ aflag, update_build_dirs ? SEND_BUILD_DIRS : 0);
+ send_file_names (failed_patches_count, failed_patches, 0);
+ }
+
+ failed_patches = NULL;
+ failed_patches_count = 0;
+
+ send_to_server ("update\012", 0);
+
+ status = get_responses_and_close ();
+
+ /* If there are any conflicts, the server will return a
+ non-zero exit status. If any patches failed, we still
+ want to run the update again. We use a pass count to
+ avoid an endless loop. */
+
+ /* Notes: (1) assuming that status != 0 implies a
+ potential conflict is the best we can cleanly do given
+ the current protocol. I suppose that trying to
+ re-fetch in cases where there was a more serious error
+ is probably more or less harmless, but it isn't really
+ ideal. (2) it would be nice to have a testsuite case for the
+ conflict-and-patch-failed case. */
+
+ if (status != 0
+ && (failed_patches == NULL || pass > 1))
+ {
+ return status;
+ }
+
+ ++pass;
+ } while (failed_patches != NULL);
+
+ return 0;
+ }
+#endif
+
+ if (tag != NULL)
+ tag_check_valid (tag, argc, argv, local, aflag, "");
+ if (join_rev1 != NULL)
+ tag_check_valid_join (join_rev1, argc, argv, local, aflag, "");
+ if (join_rev2 != NULL)
+ tag_check_valid_join (join_rev2, argc, argv, local, aflag, "");
+
+ /*
+ * If we are updating the entire directory (for real) and building dirs
+ * as we go, we make sure there is no static entries file and write the
+ * tag file as appropriate
+ */
+ if (argc <= 0 && !pipeout)
+ {
+ if (update_build_dirs)
+ {
+ if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_clear_entstat (".", Name_Repository (NULL, NULL));
+#endif
+ }
+
+ /* keep the CVS/Tag file current with the specified arguments */
+ if (aflag || tag || date)
+ {
+ WriteTag ((char *) NULL, tag, date, 0,
+ ".", Name_Repository (NULL, NULL));
+ rewrite_tag = 1;
+ nonbranch = 0;
+ }
+ }
+
+ /* look for files/dirs locally and in the repository */
+ which = W_LOCAL | W_REPOS;
+
+ /* look in the attic too if a tag or date is specified */
+ if (tag != NULL || date != NULL || joining())
+ which |= W_ATTIC;
+
+ /* call the command line interface */
+ err = do_update (argc, argv, options, tag, date, force_tag_match,
+ local, update_build_dirs, aflag, update_prune_dirs,
+ pipeout, which, join_rev1, join_rev2, (char *) NULL);
+
+ /* free the space Make_Date allocated if necessary */
+ if (date != NULL)
+ free (date);
+
+ return (err);
+}
+
+/*
+ * Command line interface to update (used by checkout)
+ */
+int
+do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
+ xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir)
+ int argc;
+ char **argv;
+ char *xoptions;
+ char *xtag;
+ char *xdate;
+ int xforce;
+ int local;
+ int xbuild;
+ int xaflag;
+ int xprune;
+ int xpipeout;
+ int which;
+ char *xjoin_rev1;
+ char *xjoin_rev2;
+ char *preload_update_dir;
+{
+ int err = 0;
+ char *cp;
+
+ /* fill in the statics */
+ options = xoptions;
+ tag = xtag;
+ date = xdate;
+ force_tag_match = xforce;
+ update_build_dirs = xbuild;
+ aflag = xaflag;
+ update_prune_dirs = xprune;
+ pipeout = xpipeout;
+
+ /* setup the join support */
+ join_rev1 = xjoin_rev1;
+ join_rev2 = xjoin_rev2;
+ if (join_rev1 && (cp = strchr (join_rev1, ':')) != NULL)
+ {
+ *cp++ = '\0';
+ date_rev1 = Make_Date (cp);
+ }
+ else
+ date_rev1 = (char *) NULL;
+ if (join_rev2 && (cp = strchr (join_rev2, ':')) != NULL)
+ {
+ *cp++ = '\0';
+ date_rev2 = Make_Date (cp);
+ }
+ else
+ date_rev2 = (char *) NULL;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ if (preserve_perms)
+ {
+ /* We need to do an extra recursion, bleah. It's to make sure
+ that we know as much as possible about file linkage. */
+ hardlist = getlist();
+ working_dir = xgetwd(); /* save top-level working dir */
+
+ /* FIXME-twp: the arguments to start_recursion make me dizzy. This
+ function call was copied from the update_fileproc call that
+ follows it; someone should make sure that I did it right. */
+ err = start_recursion (get_linkinfo_proc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, which, aflag, 1,
+ preload_update_dir, 1);
+ if (err)
+ return (err);
+
+ /* FIXME-twp: at this point we should walk the hardlist
+ and update the `links' field of each hardlink_info struct
+ to list the files that are linked on dist. That would make
+ it easier & more efficient to compare the disk linkage with
+ the repository linkage (a simple strcmp). */
+ }
+#endif
+
+ /* call the recursion processor */
+ err = start_recursion (update_fileproc, update_filesdone_proc,
+ update_dirent_proc, update_dirleave_proc, NULL,
+ argc, argv, local, which, aflag, 1,
+ preload_update_dir, 1);
+
+ /* see if we need to sleep before returning to avoid time-stamp races */
+ if (last_register_time)
+ {
+ while (time ((time_t *) NULL) == last_register_time)
+ sleep (1);
+ }
+
+ return (err);
+}
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+/*
+ * The get_linkinfo_proc callback adds each file to the hardlist
+ * (see hardlink.c).
+ */
+
+static int
+get_linkinfo_proc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *fullpath;
+ Node *linkp;
+ struct hardlink_info *hlinfo;
+
+ /* Get the full pathname of the current file. */
+ fullpath = xmalloc (strlen(working_dir) +
+ strlen(finfo->fullname) + 2);
+ sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
+
+ /* To permit recursing into subdirectories, files
+ are keyed on the full pathname and not on the basename. */
+ linkp = lookup_file_by_inode (fullpath);
+ if (linkp == NULL)
+ {
+ /* The file isn't on disk; we are probably restoring
+ a file that was removed. */
+ return 0;
+ }
+
+ /* Create a new, empty hardlink_info node. */
+ hlinfo = (struct hardlink_info *)
+ xmalloc (sizeof (struct hardlink_info));
+
+ hlinfo->status = (Ctype) 0; /* is this dumb? */
+ hlinfo->checked_out = 0;
+
+ linkp->data = (char *) hlinfo;
+
+ return 0;
+}
+#endif
+
+/*
+ * This is the callback proc for update. It is called for each file in each
+ * directory by the recursion code. The current directory is the local
+ * instantiation. file is the file name we are to operate on. update_dir is
+ * set to the path relative to where we started (for pretty printing).
+ * repository is the repository. entries and srcfiles are the pre-parsed
+ * entries and source control files.
+ *
+ * This routine decides what needs to be done for each file and does the
+ * appropriate magic for checkout
+ */
+static int
+update_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ int retval;
+ Ctype status;
+ Vers_TS *vers;
+ int resurrecting;
+
+ resurrecting = 0;
+
+ status = Classify_File (finfo, tag, date, options, force_tag_match,
+ aflag, &vers, pipeout);
+
+ /* Keep track of whether TAG is a branch tag.
+ Note that if it is a branch tag in some files and a nonbranch tag
+ in others, treat it as a nonbranch tag. It is possible that case
+ should elicit a warning or an error. */
+ if (rewrite_tag
+ && tag != NULL
+ && finfo->rcs != NULL)
+ {
+ char *rev = RCS_getversion (finfo->rcs, tag, NULL, 1, NULL);
+ if (rev != NULL
+ && !RCS_nodeisbranch (finfo->rcs, tag))
+ nonbranch = 1;
+ if (rev != NULL)
+ free (rev);
+ }
+
+ if (pipeout)
+ {
+ /*
+ * We just return success without doing anything if any of the really
+ * funky cases occur
+ *
+ * If there is still a valid RCS file, do a regular checkout type
+ * operation
+ */
+ switch (status)
+ {
+ case T_UNKNOWN: /* unknown file was explicitly asked
+ * about */
+ case T_REMOVE_ENTRY: /* needs to be un-registered */
+ case T_ADDED: /* added but not committed */
+ retval = 0;
+ break;
+ case T_CONFLICT: /* old punt-type errors */
+ retval = 1;
+ break;
+ case T_UPTODATE: /* file was already up-to-date */
+ case T_NEEDS_MERGE: /* needs merging */
+ case T_MODIFIED: /* locally modified */
+ case T_REMOVED: /* removed but not committed */
+ case T_CHECKOUT: /* needs checkout */
+#ifdef SERVER_SUPPORT
+ case T_PATCH: /* needs patch */
+#endif
+ retval = checkout_file (finfo, vers, 0, 0, 0);
+ break;
+
+ default: /* can't ever happen :-) */
+ error (0, 0,
+ "unknown file status %d for file %s", status, finfo->file);
+ retval = 0;
+ break;
+ }
+ }
+ else
+ {
+ switch (status)
+ {
+ case T_UNKNOWN: /* unknown file was explicitly asked
+ * about */
+ case T_UPTODATE: /* file was already up-to-date */
+ retval = 0;
+ break;
+ case T_CONFLICT: /* old punt-type errors */
+ retval = 1;
+ write_letter (finfo, 'C');
+ break;
+ case T_NEEDS_MERGE: /* needs merging */
+ retval = merge_file (finfo, vers);
+ break;
+ case T_MODIFIED: /* locally modified */
+ retval = 0;
+ if (toss_local_changes)
+ {
+ char *bakname;
+ bakname = backup_file (finfo->file, vers->vn_user);
+ /* This behavior is sufficiently unexpected to
+ justify overinformativeness, I think. */
+#ifdef SERVER_SUPPORT
+ if ((! really_quiet) && (! server_active))
+#else /* ! SERVER_SUPPORT */
+ if (! really_quiet)
+#endif /* SERVER_SUPPORT */
+ (void) printf ("(Locally modified %s moved to %s)\n",
+ finfo->file, bakname);
+ free (bakname);
+
+ /* The locally modified file is still present, but
+ it will be overwritten by the repository copy
+ after this. */
+ status = T_CHECKOUT;
+ retval = checkout_file (finfo, vers, 0, 0, 1);
+ }
+ else
+ {
+ if (vers->ts_conflict)
+ {
+ char *filestamp;
+ int retcode;
+
+ /*
+ * If the timestamp has changed and no
+ * conflict indicators are found, it isn't a
+ * 'C' any more.
+ */
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ retcode = vers->ts_conflict[0] != '=';
+ else
+ {
+ filestamp = time_stamp (finfo->file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+ }
+#else
+ filestamp = time_stamp (finfo->file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+#endif
+
+ if (retcode)
+ {
+ /* The timestamps differ. But if there
+ are conflict markers print 'C' anyway. */
+ retcode = !file_has_markers (finfo);
+ }
+
+ if (!retcode)
+ {
+ write_letter (finfo, 'C');
+ retval = 1;
+ }
+ else
+ {
+ /* Reregister to clear conflict flag. */
+ Register (finfo->entries, finfo->file,
+ vers->vn_rcs, vers->ts_rcs,
+ vers->options, vers->tag,
+ vers->date, (char *)0);
+ }
+ }
+ if (!retval)
+ {
+ write_letter (finfo, 'M');
+ retval = 0;
+ }
+ }
+ break;
+#ifdef SERVER_SUPPORT
+ case T_PATCH: /* needs patch */
+ if (patches)
+ {
+ int docheckout;
+ struct stat file_info;
+ unsigned char checksum[16];
+
+ retval = patch_file (finfo,
+ vers, &docheckout,
+ &file_info, checksum);
+ if (! docheckout)
+ {
+ if (server_active && retval == 0)
+ server_updated (finfo, vers,
+ (rcs_diff_patches
+ ? SERVER_RCS_DIFF
+ : SERVER_PATCHED),
+ file_info.st_mode, checksum,
+ (struct buffer *) NULL);
+ break;
+ }
+ }
+ /* If we're not running as a server, just check the
+ file out. It's simpler and faster than producing
+ and applying patches. */
+ /* Fall through. */
+#endif
+ case T_CHECKOUT: /* needs checkout */
+ retval = checkout_file (finfo, vers, 0, 0, 1);
+ break;
+ case T_ADDED: /* added but not committed */
+ write_letter (finfo, 'A');
+ retval = 0;
+ break;
+ case T_REMOVED: /* removed but not committed */
+ write_letter (finfo, 'R');
+ retval = 0;
+ break;
+ case T_REMOVE_ENTRY: /* needs to be un-registered */
+ retval = scratch_file (finfo);
+#ifdef SERVER_SUPPORT
+ if (server_active && retval == 0)
+ {
+ if (vers->ts_user == NULL)
+ server_scratch_entry_only ();
+ server_updated (finfo, vers,
+ SERVER_UPDATED, (mode_t) -1,
+ (unsigned char *) NULL,
+ (struct buffer *) NULL);
+ }
+#endif
+ break;
+ default: /* can't ever happen :-) */
+ error (0, 0,
+ "unknown file status %d for file %s", status, finfo->file);
+ retval = 0;
+ break;
+ }
+ }
+
+ /* only try to join if things have gone well thus far */
+ if (retval == 0 && join_rev1)
+ join_file (finfo, vers);
+
+ /* if this directory has an ignore list, add this file to it */
+ if (ignlist)
+ {
+ Node *p;
+
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (finfo->file);
+ if (addnode (ignlist, p) != 0)
+ freenode (p);
+ }
+
+ freevers_ts (&vers);
+ return (retval);
+}
+
+static void update_ignproc PROTO ((char *, char *));
+
+static void
+update_ignproc (file, dir)
+ char *file;
+ char *dir;
+{
+ struct file_info finfo;
+
+ memset (&finfo, 0, sizeof (finfo));
+ finfo.file = file;
+ finfo.update_dir = dir;
+ if (dir[0] == '\0')
+ finfo.fullname = xstrdup (file);
+ else
+ {
+ finfo.fullname = xmalloc (strlen (file) + strlen (dir) + 10);
+ strcpy (finfo.fullname, dir);
+ strcat (finfo.fullname, "/");
+ strcat (finfo.fullname, file);
+ }
+
+ write_letter (&finfo, '?');
+ free (finfo.fullname);
+}
+
+/* ARGSUSED */
+static int
+update_filesdone_proc (callerdat, err, repository, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repository;
+ char *update_dir;
+ List *entries;
+{
+ if (rewrite_tag)
+ {
+ WriteTag (NULL, tag, date, nonbranch, update_dir, repository);
+ rewrite_tag = 0;
+ }
+
+ /* if this directory has an ignore list, process it then free it */
+ if (ignlist)
+ {
+ ignore_files (ignlist, entries, update_dir, update_ignproc);
+ dellist (&ignlist);
+ }
+
+ /* Clean up CVS admin dirs if we are export */
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* I'm not sure the existence_error is actually possible (except
+ in cases where we really should print a message), but since
+ this code used to ignore all errors, I'll play it safe. */
+ if (unlink_file_dir (CVSADM) < 0 && !existence_error (errno))
+ error (0, errno, "cannot remove %s directory", CVSADM);
+ }
+#ifdef SERVER_SUPPORT
+ else if (!server_active && !pipeout)
+#else
+ else if (!pipeout)
+#endif /* SERVER_SUPPORT */
+ {
+ /* If there is no CVS/Root file, add one */
+ if (!isfile (CVSADM_ROOT))
+ Create_Root ((char *) NULL, CVSroot_original);
+ }
+
+ return (err);
+}
+
+/*
+ * update_dirent_proc () is called back by the recursion processor before a
+ * sub-directory is processed for update. In this case, update_dirent proc
+ * will probably create the directory unless -d isn't specified and this is a
+ * new directory. A return code of 0 indicates the directory should be
+ * processed by the recursion code. A return of non-zero indicates the
+ * recursion code should skip this directory.
+ */
+static Dtype
+update_dirent_proc (callerdat, dir, repository, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ char *repository;
+ char *update_dir;
+ List *entries;
+{
+ if (ignore_directory (update_dir))
+ {
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Ignoring %s", update_dir);
+ return R_SKIP_ALL;
+ }
+
+ if (!isdir (dir))
+ {
+ /* if we aren't building dirs, blow it off */
+ if (!update_build_dirs)
+ return (R_SKIP_ALL);
+
+ /* Various CVS administrators are in the habit of removing
+ the repository directory for things they don't want any
+ more. I've even been known to do it myself (on rare
+ occasions). Not the usual recommended practice, but we
+ want to try to come up with some kind of
+ reasonable/documented/sensible behavior. Generally
+ the behavior is to just skip over that directory (see
+ dirs test in sanity.sh; the case which reaches here
+ is when update -d is specified, and the working directory
+ is gone but the subdirectory is still mentioned in
+ CVS/Entries). */
+ if (1
+#ifdef SERVER_SUPPORT
+ /* In the remote case, the client should refrain from
+ sending us the directory in the first place. So we
+ want to continue to give an error, so clients make
+ sure to do this. */
+ && !server_active
+#endif
+ && !isdir (repository))
+ return R_SKIP_ALL;
+
+ if (noexec)
+ {
+ /* ignore the missing dir if -n is specified */
+ error (0, 0, "New directory `%s' -- ignored", update_dir);
+ return (R_SKIP_ALL);
+ }
+ else
+ {
+ /* otherwise, create the dir and appropriate adm files */
+
+ /* If no tag or date were specified on the command line,
+ and we're not using -A, we want the subdirectory to use
+ the tag and date, if any, of the current directory.
+ That way, update -d will work correctly when working on
+ a branch.
+
+ We use TAG_UPDATE_DIR to undo the tag setting in
+ update_dirleave_proc. If we did not do this, we would
+ not correctly handle a working directory with multiple
+ tags (and maybe we should prohibit such working
+ directories, but they work now and we shouldn't make
+ them stop working without more thought). */
+ if ((tag == NULL && date == NULL) && ! aflag)
+ {
+ ParseTag (&tag, &date, &nonbranch);
+ if (tag != NULL || date != NULL)
+ tag_update_dir = xstrdup (update_dir);
+ }
+
+ make_directory (dir);
+ Create_Admin (dir, update_dir, repository, tag, date,
+ /* This is a guess. We will rewrite it later
+ via WriteTag. */
+ 0,
+ 0);
+ rewrite_tag = 1;
+ nonbranch = 0;
+ Subdir_Register (entries, (char *) NULL, dir);
+ }
+ }
+ /* Do we need to check noexec here? */
+ else if (!pipeout)
+ {
+ char *cvsadmdir;
+
+ /* The directory exists. Check to see if it has a CVS
+ subdirectory. */
+
+ cvsadmdir = xmalloc (strlen (dir) + 80);
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM);
+
+ if (!isdir (cvsadmdir))
+ {
+ /* We cannot successfully recurse into a directory without a CVS
+ subdirectory. Generally we will have already printed
+ "? foo". */
+ free (cvsadmdir);
+ return R_SKIP_ALL;
+ }
+ free (cvsadmdir);
+ }
+
+ /*
+ * If we are building dirs and not going to stdout, we make sure there is
+ * no static entries file and write the tag file as appropriate
+ */
+ if (!pipeout)
+ {
+ if (update_build_dirs)
+ {
+ char *tmp;
+
+ tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ENTSTAT) + 10);
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT);
+ if (unlink_file (tmp) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove file %s", tmp);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_clear_entstat (update_dir, repository);
+#endif
+ free (tmp);
+ }
+
+ /* keep the CVS/Tag file current with the specified arguments */
+ if (aflag || tag || date)
+ {
+ WriteTag (dir, tag, date, 0, update_dir, repository);
+ rewrite_tag = 1;
+ nonbranch = 0;
+ }
+
+ /* initialize the ignore list for this directory */
+ ignlist = getlist ();
+ }
+
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Updating %s", update_dir);
+
+ return (R_PROCESS);
+}
+
+/*
+ * update_dirleave_proc () is called back by the recursion code upon leaving
+ * a directory. It will prune empty directories if needed and will execute
+ * any appropriate update programs.
+ */
+/* ARGSUSED */
+static int
+update_dirleave_proc (callerdat, dir, err, update_dir, entries)
+ void *callerdat;
+ char *dir;
+ int err;
+ char *update_dir;
+ List *entries;
+{
+ FILE *fp;
+
+ /* If we set the tag or date for a new subdirectory in
+ update_dirent_proc, and we're now done with that subdirectory,
+ undo the tag/date setting. Note that we know that the tag and
+ date were both originally NULL in this case. */
+ if (tag_update_dir != NULL && strcmp (update_dir, tag_update_dir) == 0)
+ {
+ if (tag != NULL)
+ {
+ free (tag);
+ tag = NULL;
+ }
+ if (date != NULL)
+ {
+ free (date);
+ date = NULL;
+ }
+ nonbranch = 0;
+ free (tag_update_dir);
+ tag_update_dir = NULL;
+ }
+
+ /* run the update_prog if there is one */
+ /* FIXME: should be checking for errors from CVS_FOPEN and printing
+ them if not existence_error. */
+ if (err == 0 && !pipeout && !noexec &&
+ (fp = CVS_FOPEN (CVSADM_UPROG, "r")) != NULL)
+ {
+ char *cp;
+ char *repository;
+ char *line = NULL;
+ size_t line_allocated = 0;
+
+ repository = Name_Repository ((char *) NULL, update_dir);
+ if (getline (&line, &line_allocated, fp) >= 0)
+ {
+ if ((cp = strrchr (line, '\n')) != NULL)
+ *cp = '\0';
+ run_setup (line);
+ run_arg (repository);
+ cvs_output (program_name, 0);
+ cvs_output (" ", 1);
+ cvs_output (command_name, 0);
+ cvs_output (": Executing '", 0);
+ run_print (stdout);
+ cvs_output ("'\n", 0);
+ (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ }
+ else if (ferror (fp))
+ error (0, errno, "cannot read %s", CVSADM_UPROG);
+ else
+ error (0, 0, "unexpected end of file on %s", CVSADM_UPROG);
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_UPROG);
+ if (line != NULL)
+ free (line);
+ free (repository);
+ }
+
+ if (strchr (dir, '/') == NULL)
+ {
+ /* FIXME: chdir ("..") loses with symlinks. */
+ /* Prune empty dirs on the way out - if necessary */
+ (void) CVS_CHDIR ("..");
+ if (update_prune_dirs && isemptydir (dir, 0))
+ {
+ /* I'm not sure the existence_error is actually possible (except
+ in cases where we really should print a message), but since
+ this code used to ignore all errors, I'll play it safe. */
+ if (unlink_file_dir (dir) < 0 && !existence_error (errno))
+ error (0, errno, "cannot remove %s directory", dir);
+ Subdir_Deregister (entries, (char *) NULL, dir);
+ }
+ }
+
+ return (err);
+}
+
+static int isremoved PROTO ((Node *, void *));
+
+/* Returns 1 if the file indicated by node has been removed. */
+static int
+isremoved (node, closure)
+ Node *node;
+ void *closure;
+{
+ Entnode *entdata = (Entnode*) node->data;
+
+ /* If the first character of the version is a '-', the file has been
+ removed. */
+ return (entdata->version && entdata->version[0] == '-') ? 1 : 0;
+}
+
+/* Returns 1 if the argument directory is completely empty, other than the
+ existence of the CVS directory entry. Zero otherwise. If MIGHT_NOT_EXIST
+ and the directory doesn't exist, then just return 0. */
+int
+isemptydir (dir, might_not_exist)
+ char *dir;
+ int might_not_exist;
+{
+ DIR *dirp;
+ struct dirent *dp;
+
+ if ((dirp = CVS_OPENDIR (dir)) == NULL)
+ {
+ if (might_not_exist && existence_error (errno))
+ return 0;
+ error (0, errno, "cannot open directory %s for empty check", dir);
+ return (0);
+ }
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") != 0
+ && strcmp (dp->d_name, "..") != 0)
+ {
+ if (strcmp (dp->d_name, CVSADM) != 0)
+ {
+ /* An entry other than the CVS directory. The directory
+ is certainly not empty. */
+ (void) closedir (dirp);
+ return (0);
+ }
+ else
+ {
+ /* The CVS directory entry. We don't have to worry about
+ this unless the Entries file indicates that files have
+ been removed, but not committed, in this directory.
+ (Removing the directory would prevent people from
+ comitting the fact that they removed the files!) */
+ List *l;
+ int files_removed;
+ struct saved_cwd cwd;
+
+ if (save_cwd (&cwd))
+ error_exit ();
+
+ if (CVS_CHDIR (dir) < 0)
+ error (1, errno, "cannot change directory to %s", dir);
+ l = Entries_Open (0, NULL);
+ files_removed = walklist (l, isremoved, 0);
+ Entries_Close (l);
+
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+
+ if (files_removed != 0)
+ {
+ /* There are files that have been removed, but not
+ committed! Do not consider the directory empty. */
+ (void) closedir (dirp);
+ return (0);
+ }
+ }
+ }
+ errno = 0;
+ }
+ if (errno != 0)
+ {
+ error (0, errno, "cannot read directory %s", dir);
+ (void) closedir (dirp);
+ return (0);
+ }
+ (void) closedir (dirp);
+ return (1);
+}
+
+/*
+ * scratch the Entries file entry associated with a file
+ */
+static int
+scratch_file (finfo)
+ struct file_info *finfo;
+{
+ history_write ('W', finfo->update_dir, "", finfo->file, finfo->repository);
+ Scratch_Entry (finfo->entries, finfo->file);
+ if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+ error (0, errno, "unable to remove %s", finfo->fullname);
+ return (0);
+}
+
+/*
+ * Check out a file.
+ */
+static int
+checkout_file (finfo, vers_ts, adding, merging, update_server)
+ struct file_info *finfo;
+ Vers_TS *vers_ts;
+ int adding;
+ int merging;
+ int update_server;
+{
+ char *backup;
+ int set_time, retval = 0;
+ int status;
+ int file_is_dead;
+ struct buffer *revbuf;
+
+ backup = NULL;
+ revbuf = NULL;
+
+ /* Don't screw with backup files if we're going to stdout, or if
+ we are the server. */
+ if (!pipeout
+#ifdef SERVER_SUPPORT
+ && ! server_active
+#endif
+ )
+ {
+ backup = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
+ if (isfile (finfo->file))
+ rename_file (finfo->file, backup);
+ else
+ {
+ /* If -f/-t wrappers are being used to wrap up a directory,
+ then backup might be a directory instead of just a file. */
+ if (unlink_file_dir (backup) < 0)
+ {
+ /* Not sure if the existence_error check is needed here. */
+ if (!existence_error (errno))
+ /* FIXME: should include update_dir in message. */
+ error (0, errno, "error removing %s", backup);
+ }
+ free (backup);
+ backup = NULL;
+ }
+ }
+
+ file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
+
+ if (!file_is_dead)
+ {
+ /*
+ * if we are checking out to stdout, print a nice message to
+ * stderr, and add the -p flag to the command */
+ if (pipeout)
+ {
+ if (!quiet)
+ {
+ cvs_outerr ("\
+===================================================================\n\
+Checking out ", 0);
+ cvs_outerr (finfo->fullname, 0);
+ cvs_outerr ("\n\
+RCS: ", 0);
+ cvs_outerr (vers_ts->srcfile->path, 0);
+ cvs_outerr ("\n\
+VERS: ", 0);
+ cvs_outerr (vers_ts->vn_rcs, 0);
+ cvs_outerr ("\n***************\n", 0);
+ }
+ }
+
+#ifdef SERVER_SUPPORT
+ if (update_server
+ && server_active
+ && ! pipeout
+ && ! file_gzip_level
+ && ! joining ()
+ && ! wrap_name_has (finfo->file, WRAP_FROMCVS))
+ {
+ revbuf = buf_nonio_initialize ((BUFMEMERRPROC) NULL);
+ status = RCS_checkout (vers_ts->srcfile, (char *) NULL,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ checkout_to_buffer, revbuf);
+ }
+ else
+#endif
+ status = RCS_checkout (vers_ts->srcfile,
+ pipeout ? NULL : finfo->file,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
+ }
+ if (file_is_dead || status == 0)
+ {
+ mode_t mode;
+
+ mode = (mode_t) -1;
+
+ if (!pipeout)
+ {
+ Vers_TS *xvers_ts;
+
+ if (revbuf != NULL && !noexec)
+ {
+ struct stat sb;
+
+ /* FIXME: We should have RCS_checkout return the mode.
+ That would also fix the kludge with noexec, above, which
+ is here only because noexec doesn't write srcfile->path
+ for us to stat. */
+ if (stat (vers_ts->srcfile->path, &sb) < 0)
+ error (1, errno, "cannot stat %s",
+ vers_ts->srcfile->path);
+ mode = sb.st_mode &~ (S_IWRITE | S_IWGRP | S_IWOTH);
+ }
+
+ if (cvswrite
+ && !file_is_dead
+ && !fileattr_get (finfo->file, "_watched"))
+ {
+ if (revbuf == NULL)
+ xchmod (finfo->file, 1);
+ else
+ {
+ /* We know that we are the server here, so
+ although xchmod checks umask, we don't bother. */
+ mode |= (((mode & S_IRUSR) ? S_IWUSR : 0)
+ | ((mode & S_IRGRP) ? S_IWGRP : 0)
+ | ((mode & S_IROTH) ? S_IWOTH : 0));
+ }
+ }
+
+ {
+ /* A newly checked out file is never under the spell
+ of "cvs edit". If we think we were editing it
+ from a previous life, clean up. Would be better to
+ check for same the working directory instead of
+ same user, but that is hairy. */
+
+ struct addremove_args args;
+
+ editor_set (finfo->file, getcaller (), NULL);
+
+ memset (&args, 0, sizeof args);
+ args.remove_temp = 1;
+ watch_modify_watchers (finfo->file, &args);
+ }
+
+ /* set the time from the RCS file iff it was unknown before */
+ set_time =
+ (!noexec
+ && (vers_ts->vn_user == NULL ||
+ strncmp (vers_ts->ts_rcs, "Initial", 7) == 0)
+ && !file_is_dead);
+
+ wrap_fromcvs_process_file (finfo->file);
+
+ xvers_ts = Version_TS (finfo, options, tag, date,
+ force_tag_match, set_time);
+ if (strcmp (xvers_ts->options, "-V4") == 0)
+ xvers_ts->options[0] = '\0';
+
+ if (revbuf != NULL)
+ {
+ /* If we stored the file data into a buffer, then we
+ didn't create a file at all, so xvers_ts->ts_user
+ is wrong. The correct value is to have it be the
+ same as xvers_ts->ts_rcs, meaning that the working
+ file is unchanged from the RCS file.
+
+ FIXME: We should tell Version_TS not to waste time
+ statting the nonexistent file.
+
+ FIXME: Actually, I don't think the ts_user value
+ matters at all here. The only use I know of is
+ that it is printed in a trace message by
+ Server_Register. */
+
+ if (xvers_ts->ts_user != NULL)
+ free (xvers_ts->ts_user);
+ xvers_ts->ts_user = xstrdup (xvers_ts->ts_rcs);
+ }
+
+ (void) time (&last_register_time);
+
+ if (file_is_dead)
+ {
+ if (xvers_ts->vn_user != NULL)
+ {
+ error (0, 0,
+ "warning: %s is not (any longer) pertinent",
+ finfo->fullname);
+ }
+ Scratch_Entry (finfo->entries, finfo->file);
+#ifdef SERVER_SUPPORT
+ if (server_active && xvers_ts->ts_user == NULL)
+ server_scratch_entry_only ();
+#endif
+ /* FIXME: Rather than always unlink'ing, and ignoring the
+ existence_error, we should do the unlink only if
+ vers_ts->ts_user is non-NULL. Then there would be no
+ need to ignore an existence_error (for example, if the
+ user removes the file while we are running). */
+ if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+ {
+ error (0, errno, "cannot remove %s", finfo->fullname);
+ }
+ }
+ else
+ Register (finfo->entries, finfo->file,
+ adding ? "0" : xvers_ts->vn_rcs,
+ xvers_ts->ts_user, xvers_ts->options,
+ xvers_ts->tag, xvers_ts->date,
+ (char *)0); /* Clear conflict flag on fresh checkout */
+
+ /* fix up the vers structure, in case it is used by join */
+ if (join_rev1)
+ {
+ if (vers_ts->vn_user != NULL)
+ free (vers_ts->vn_user);
+ if (vers_ts->vn_rcs != NULL)
+ free (vers_ts->vn_rcs);
+ vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs);
+ vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs);
+ }
+
+ /* If this is really Update and not Checkout, recode history */
+ if (strcmp (command_name, "update") == 0)
+ history_write ('U', finfo->update_dir, xvers_ts->vn_rcs, finfo->file,
+ finfo->repository);
+
+ freevers_ts (&xvers_ts);
+
+ if (!really_quiet && !file_is_dead)
+ {
+ write_letter (finfo, 'U');
+ }
+ }
+
+#ifdef SERVER_SUPPORT
+ if (update_server && server_active)
+ server_updated (finfo, vers_ts,
+ merging ? SERVER_MERGED : SERVER_UPDATED,
+ mode, (unsigned char *) NULL, revbuf);
+#endif
+ }
+ else
+ {
+ if (backup != NULL)
+ {
+ rename_file (backup, finfo->file);
+ free (backup);
+ backup = NULL;
+ }
+
+ error (0, 0, "could not check out %s", finfo->fullname);
+
+ retval = status;
+ }
+
+ if (backup != NULL)
+ {
+ /* If -f/-t wrappers are being used to wrap up a directory,
+ then backup might be a directory instead of just a file. */
+ if (unlink_file_dir (backup) < 0)
+ {
+ /* Not sure if the existence_error check is needed here. */
+ if (!existence_error (errno))
+ /* FIXME: should include update_dir in message. */
+ error (0, errno, "error removing %s", backup);
+ }
+ free (backup);
+ }
+
+ return (retval);
+}
+
+#ifdef SERVER_SUPPORT
+
+/* This function is used to write data from a file being checked out
+ into a buffer. */
+
+static void
+checkout_to_buffer (callerdat, data, len)
+ void *callerdat;
+ const char *data;
+ size_t len;
+{
+ struct buffer *buf = (struct buffer *) callerdat;
+
+ buf_output (buf, data, len);
+}
+
+#endif /* SERVER_SUPPORT */
+
+#ifdef SERVER_SUPPORT
+
+/* This structure is used to pass information between patch_file and
+ patch_file_write. */
+
+struct patch_file_data
+{
+ /* File name, for error messages. */
+ const char *filename;
+ /* File to which to write. */
+ FILE *fp;
+ /* Whether to compute the MD5 checksum. */
+ int compute_checksum;
+ /* Data structure for computing the MD5 checksum. */
+ struct cvs_MD5Context context;
+ /* Set if the file has a final newline. */
+ int final_nl;
+};
+
+/* Patch a file. Runs diff. This is only done when running as the
+ * server. The hope is that the diff will be smaller than the file
+ * itself.
+ */
+static int
+patch_file (finfo, vers_ts, docheckout, file_info, checksum)
+ struct file_info *finfo;
+ Vers_TS *vers_ts;
+ int *docheckout;
+ struct stat *file_info;
+ unsigned char *checksum;
+{
+ char *backup;
+ char *file1;
+ char *file2;
+ int retval = 0;
+ int retcode = 0;
+ int fail;
+ FILE *e;
+ struct patch_file_data data;
+
+ *docheckout = 0;
+
+ if (noexec || pipeout || joining ())
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
+ /* If this file has been marked as being binary, then never send a
+ patch. */
+ if (strcmp (vers_ts->options, "-kb") == 0)
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
+ /* First check that the first revision exists. If it has been nuked
+ by cvs admin -o, then just fall back to checking out entire
+ revisions. In some sense maybe we don't have to do this; after
+ all cvs.texinfo says "Make sure that no-one has checked out a
+ copy of the revision you outdate" but then again, that advice
+ doesn't really make complete sense, because "cvs admin" operates
+ on a working directory and so _someone_ will almost always have
+ _some_ revision checked out. */
+ {
+ char *rev;
+
+ rev = RCS_gettag (finfo->rcs, vers_ts->vn_user, 1, NULL);
+ if (rev == NULL)
+ {
+ *docheckout = 1;
+ return 0;
+ }
+ else
+ free (rev);
+ }
+
+ /* If the revision is dead, let checkout_file handle it rather
+ than duplicating the processing here. */
+ if (RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs))
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
+ backup = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
+ if (isfile (finfo->file))
+ rename_file (finfo->file, backup);
+ else
+ {
+ if (unlink_file (backup) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", backup);
+ }
+
+ file1 = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, finfo->file);
+ file2 = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, finfo->file);
+
+ fail = 0;
+
+ /* We need to check out both revisions first, to see if either one
+ has a trailing newline. Because of this, we don't use rcsdiff,
+ but just use diff. */
+
+ e = CVS_FOPEN (file1, "w");
+ if (e == NULL)
+ error (1, errno, "cannot open %s", file1);
+
+ data.filename = file1;
+ data.fp = e;
+ data.final_nl = 0;
+ data.compute_checksum = 0;
+
+ retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
+ vers_ts->vn_user, (char *) NULL,
+ vers_ts->options, RUN_TTY,
+ patch_file_write, (void *) &data);
+
+ if (fclose (e) < 0)
+ error (1, errno, "cannot close %s", file1);
+
+ if (retcode != 0 || ! data.final_nl)
+ fail = 1;
+
+ if (! fail)
+ {
+ e = CVS_FOPEN (file2, "w");
+ if (e == NULL)
+ error (1, errno, "cannot open %s", file2);
+
+ data.filename = file2;
+ data.fp = e;
+ data.final_nl = 0;
+ data.compute_checksum = 1;
+ cvs_MD5Init (&data.context);
+
+ retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ patch_file_write, (void *) &data);
+
+ if (fclose (e) < 0)
+ error (1, errno, "cannot close %s", file2);
+
+ if (retcode != 0 || ! data.final_nl)
+ fail = 1;
+ else
+ cvs_MD5Final (checksum, &data.context);
+ }
+
+ retcode = 0;
+ if (! fail)
+ {
+ char *diff_options;
+
+ /* If the client does not support the Rcs-diff command, we
+ send a context diff, and the client must invoke patch.
+ That approach was problematical for various reasons. The
+ new approach only requires running diff in the server; the
+ client can handle everything without invoking an external
+ program. */
+ if (! rcs_diff_patches)
+ {
+ /* We use -c, not -u, because that is what CVS has
+ traditionally used. Kind of a moot point, now that
+ Rcs-diff is preferred, so there is no point in making
+ the compatibility issues worse. */
+ diff_options = "-c";
+ }
+ else
+ {
+ /* Now that diff is librarified, we could be passing -a if
+ we wanted to. However, it is unclear to me whether we
+ would want to. Does diff -a, in any significant
+ percentage of cases, produce patches which are smaller
+ than the files it is patching? I guess maybe text
+ files with character sets which diff regards as
+ 'binary'. Conversely, do they tend to be much larger
+ in the bad cases? This needs some more
+ thought/investigation, I suspect. */
+
+ diff_options = "-n";
+ }
+ retcode = diff_exec (file1, file2, diff_options, finfo->file);
+
+ /* A retcode of 0 means no differences. 1 means some differences. */
+ if (retcode != 0
+ && retcode != 1)
+ {
+ fail = 1;
+ }
+ else
+ {
+#define BINARY "Binary"
+ char buf[sizeof BINARY];
+ unsigned int c;
+
+ /* Stat the original RCS file, and then adjust it the way
+ that RCS_checkout would. FIXME: This is an abstraction
+ violation. */
+ if (CVS_STAT (vers_ts->srcfile->path, file_info) < 0)
+ error (1, errno, "could not stat %s", vers_ts->srcfile->path);
+ if (chmod (finfo->file,
+ file_info->st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH))
+ < 0)
+ error (0, errno, "cannot change mode of file %s", finfo->file);
+ if (cvswrite
+ && !fileattr_get (finfo->file, "_watched"))
+ xchmod (finfo->file, 1);
+
+ /* Check the diff output to make sure patch will be handle it. */
+ e = CVS_FOPEN (finfo->file, "r");
+ if (e == NULL)
+ error (1, errno, "could not open diff output file %s",
+ finfo->fullname);
+ c = fread (buf, 1, sizeof BINARY - 1, e);
+ buf[c] = '\0';
+ if (strcmp (buf, BINARY) == 0)
+ {
+ /* These are binary files. We could use diff -a, but
+ patch can't handle that. */
+ fail = 1;
+ }
+ fclose (e);
+ }
+ }
+
+ if (! fail)
+ {
+ Vers_TS *xvers_ts;
+
+ /* This stuff is just copied blindly from checkout_file. I
+ don't really know what it does. */
+ xvers_ts = Version_TS (finfo, options, tag, date,
+ force_tag_match, 0);
+ if (strcmp (xvers_ts->options, "-V4") == 0)
+ xvers_ts->options[0] = '\0';
+
+ Register (finfo->entries, finfo->file, xvers_ts->vn_rcs,
+ xvers_ts->ts_user, xvers_ts->options,
+ xvers_ts->tag, xvers_ts->date, NULL);
+
+ if (CVS_STAT (finfo->file, file_info) < 0)
+ error (1, errno, "could not stat %s", finfo->file);
+
+ /* If this is really Update and not Checkout, recode history */
+ if (strcmp (command_name, "update") == 0)
+ history_write ('P', finfo->update_dir, xvers_ts->vn_rcs, finfo->file,
+ finfo->repository);
+
+ freevers_ts (&xvers_ts);
+
+ if (!really_quiet)
+ {
+ write_letter (finfo, 'P');
+ }
+ }
+ else
+ {
+ int old_errno = errno; /* save errno value over the rename */
+
+ if (isfile (backup))
+ rename_file (backup, finfo->file);
+
+ if (retcode != 0 && retcode != 1)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+ "could not diff %s", finfo->fullname);
+
+ *docheckout = 1;
+ retval = retcode;
+ }
+
+ if (unlink_file (backup) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", backup);
+ if (unlink_file (file1) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", file1);
+ if (unlink_file (file2) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", file2);
+
+ free (backup);
+ free (file1);
+ free (file2);
+ return (retval);
+}
+
+/* Write data to a file. Record whether the last byte written was a
+ newline. Optionally compute a checksum. This is called by
+ patch_file via RCS_checkout. */
+
+static void
+patch_file_write (callerdat, buffer, len)
+ void *callerdat;
+ const char *buffer;
+ size_t len;
+{
+ struct patch_file_data *data = (struct patch_file_data *) callerdat;
+
+ if (fwrite (buffer, 1, len, data->fp) != len)
+ error (1, errno, "cannot write %s", data->filename);
+
+ data->final_nl = (buffer[len - 1] == '\n');
+
+ if (data->compute_checksum)
+ cvs_MD5Update (&data->context, (unsigned char *) buffer, len);
+}
+
+#endif /* SERVER_SUPPORT */
+
+/*
+ * Several of the types we process only print a bit of information consisting
+ * of a single letter and the name.
+ */
+static void
+write_letter (finfo, letter)
+ struct file_info *finfo;
+ int letter;
+{
+ if (!really_quiet)
+ {
+ char *tag = NULL;
+ /* Big enough for "+updated" or any of its ilk. */
+ char buf[80];
+
+ switch (letter)
+ {
+ case 'U':
+ tag = "updated";
+ break;
+ default:
+ /* We don't yet support tagged output except for "U". */
+ break;
+ }
+
+ if (tag != NULL)
+ {
+ sprintf (buf, "+%s", tag);
+ cvs_output_tagged (buf, NULL);
+ }
+ buf[0] = letter;
+ buf[1] = ' ';
+ buf[2] = '\0';
+ cvs_output_tagged ("text", buf);
+ cvs_output_tagged ("fname", finfo->fullname);
+ cvs_output_tagged ("newline", NULL);
+ if (tag != NULL)
+ {
+ sprintf (buf, "-%s", tag);
+ cvs_output_tagged (buf, NULL);
+ }
+ }
+ return;
+}
+
+/*
+ * Do all the magic associated with a file which needs to be merged
+ */
+static int
+merge_file (finfo, vers)
+ struct file_info *finfo;
+ Vers_TS *vers;
+{
+ char *backup;
+ int status;
+ int retcode = 0;
+ int retval;
+
+ /*
+ * The users currently modified file is moved to a backup file name
+ * ".#filename.version", so that it will stay around for a few days
+ * before being automatically removed by some cron daemon. The "version"
+ * is the version of the file that the user was most up-to-date with
+ * before the merge.
+ */
+ backup = xmalloc (strlen (finfo->file)
+ + strlen (vers->vn_user)
+ + sizeof (BAKPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
+
+ if (unlink_file (backup) && !existence_error (errno))
+ error (0, errno, "unable to remove %s", backup);
+ copy_file (finfo->file, backup);
+ xchmod (finfo->file, 1);
+
+ if (strcmp (vers->options, "-kb") == 0
+ || wrap_merge_is_copy (finfo->file)
+ || special_file_mismatch (finfo, NULL, vers->vn_rcs))
+ {
+ /* For binary files, a merge is always a conflict. Same for
+ files whose permissions or linkage do not match. We give the
+ user the two files, and let them resolve it. It is possible
+ that we should require a "touch foo" or similar step before
+ we allow a checkin. */
+
+ /* TODO: it may not always be necessary to regard a permission
+ mismatch as a conflict. The working file and the RCS file
+ have a common ancestor `A'; if the working file's permissions
+ match A's, then it's probably safe to overwrite them with the
+ RCS permissions. Only if the working file, the RCS file, and
+ A all disagree should this be considered a conflict. But more
+ thought needs to go into this, and in the meantime it is safe
+ to treat any such mismatch as an automatic conflict. -twp */
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_copy_file (finfo->file, finfo->update_dir,
+ finfo->repository, backup);
+#endif
+
+ status = checkout_file (finfo, vers, 0, 1, 1);
+
+ /* Is there a better term than "nonmergeable file"? What we
+ really mean is, not something that CVS cannot or does not
+ want to merge (there might be an external manual or
+ automatic merge process). */
+ error (0, 0, "nonmergeable file needs merge");
+ error (0, 0, "revision %s from repository is now in %s",
+ vers->vn_rcs, finfo->fullname);
+ error (0, 0, "file from working directory is now in %s", backup);
+ write_letter (finfo, 'C');
+
+ history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file,
+ finfo->repository);
+ retval = 0;
+ goto out;
+ }
+
+ status = RCS_merge(finfo->rcs, vers->srcfile->path, finfo->file,
+ vers->options, vers->vn_user, vers->vn_rcs);
+ if (status != 0 && status != 1)
+ {
+ error (0, status == -1 ? errno : 0,
+ "could not merge revision %s of %s", vers->vn_user, finfo->fullname);
+ error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+ finfo->fullname, backup);
+ rename_file (backup, finfo->file);
+ retval = 1;
+ goto out;
+ }
+
+ if (strcmp (vers->options, "-V4") == 0)
+ vers->options[0] = '\0';
+
+ /* This file is the result of a merge, which means that it has
+ been modified. We use a special timestamp string which will
+ not compare equal to any actual timestamp. */
+ {
+ char *cp = 0;
+
+ if (status)
+ {
+ (void) time (&last_register_time);
+ cp = time_stamp (finfo->file);
+ }
+ Register (finfo->entries, finfo->file, vers->vn_rcs,
+ "Result of merge", vers->options, vers->tag,
+ vers->date, cp);
+ if (cp)
+ free (cp);
+ }
+
+ /* fix up the vers structure, in case it is used by join */
+ if (join_rev1)
+ {
+ if (vers->vn_user != NULL)
+ free (vers->vn_user);
+ vers->vn_user = xstrdup (vers->vn_rcs);
+ }
+
+#ifdef SERVER_SUPPORT
+ /* Send the new contents of the file before the message. If we
+ wanted to be totally correct, we would have the client write
+ the message only after the file has safely been written. */
+ if (server_active)
+ {
+ server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
+ backup);
+ server_updated (finfo, vers, SERVER_MERGED,
+ (mode_t) -1, (unsigned char *) NULL,
+ (struct buffer *) NULL);
+ }
+#endif
+
+ /* FIXME: the noexec case is broken. RCS_merge could be doing the
+ xcmp on the temporary files without much hassle, I think. */
+ if (!noexec && !xcmp (backup, finfo->file))
+ {
+ cvs_output (finfo->fullname, 0);
+ cvs_output (" already contains the differences between ", 0);
+ cvs_output (vers->vn_user, 0);
+ cvs_output (" and ", 0);
+ cvs_output (vers->vn_rcs, 0);
+ cvs_output ("\n", 1);
+
+ history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
+ finfo->repository);
+ retval = 0;
+ goto out;
+ }
+
+ if (status == 1)
+ {
+ error (0, 0, "conflicts found in %s", finfo->fullname);
+
+ write_letter (finfo, 'C');
+
+ history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file, finfo->repository);
+
+ }
+ else if (retcode == -1)
+ {
+ error (1, errno, "fork failed while examining update of %s",
+ finfo->fullname);
+ }
+ else
+ {
+ write_letter (finfo, 'M');
+ history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
+ finfo->repository);
+ }
+ retval = 0;
+ out:
+ free (backup);
+ return retval;
+}
+
+/*
+ * Do all the magic associated with a file which needs to be joined
+ * (-j option)
+ */
+static void
+join_file (finfo, vers)
+ struct file_info *finfo;
+ Vers_TS *vers;
+{
+ char *backup;
+ char *options;
+ int status;
+
+ char *rev1;
+ char *rev2;
+ char *jrev1;
+ char *jrev2;
+ char *jdate1;
+ char *jdate2;
+
+ jrev1 = join_rev1;
+ jrev2 = join_rev2;
+ jdate1 = date_rev1;
+ jdate2 = date_rev2;
+
+ /* Determine if we need to do anything at all. */
+ if (vers->srcfile == NULL ||
+ vers->srcfile->path == NULL)
+ {
+ return;
+ }
+
+ /* If only one join revision is specified, it becomes the second
+ revision. */
+ if (jrev2 == NULL)
+ {
+ jrev2 = jrev1;
+ jrev1 = NULL;
+ jdate2 = jdate1;
+ jdate1 = NULL;
+ }
+
+ /* Convert the second revision, walking branches and dates. */
+ rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, (int *) NULL);
+
+ /* If this is a merge of two revisions, get the first revision.
+ If only one join tag was specified, then the first revision is
+ the greatest common ancestor of the second revision and the
+ working file. */
+ if (jrev1 != NULL)
+ rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, (int *) NULL);
+ else
+ {
+ /* Note that we use vn_rcs here, since vn_user may contain a
+ special string such as "-nn". */
+ if (vers->vn_rcs == NULL)
+ rev1 = NULL;
+ else if (rev2 == NULL)
+ {
+ /* This means that the file never existed on the branch.
+ It does not mean that the file was removed on the
+ branch: that case is represented by a dead rev2. If
+ the file never existed on the branch, then we have
+ nothing to merge, so we just return. */
+ return;
+ }
+ else
+ rev1 = gca (vers->vn_rcs, rev2);
+ }
+
+ /* Handle a nonexistent or dead merge target. */
+ if (rev2 == NULL || RCS_isdead (vers->srcfile, rev2))
+ {
+ char *mrev;
+
+ if (rev2 != NULL)
+ free (rev2);
+
+ /* If the first revision doesn't exist either, then there is
+ no change between the two revisions, so we don't do
+ anything. */
+ if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
+ {
+ if (rev1 != NULL)
+ free (rev1);
+ return;
+ }
+
+ /* If we are merging two revisions, then the file was removed
+ between the first revision and the second one. In this
+ case we want to mark the file for removal.
+
+ If we are merging one revision, then the file has been
+ removed between the greatest common ancestor and the merge
+ revision. From the perspective of the branch on to which
+ we ar emerging, which may be the trunk, either 1) the file
+ does not currently exist on the target, or 2) the file has
+ not been modified on the target branch since the greatest
+ common ancestor, or 3) the file has been modified on the
+ target branch since the greatest common ancestor. In case
+ 1 there is nothing to do. In case 2 we mark the file for
+ removal. In case 3 we have a conflict.
+
+ Note that the handling is slightly different depending upon
+ whether one or two join targets were specified. If two
+ join targets were specified, we don't check whether the
+ file was modified since a given point. My reasoning is
+ that if you ask for an explicit merge between two tags,
+ then you want to merge in whatever was changed between
+ those two tags. If a file was removed between the two
+ tags, then you want it to be removed. However, if you ask
+ for a merge of a branch, then you want to merge in all
+ changes which were made on the branch. If a file was
+ removed on the branch, that is a change to the file. If
+ the file was also changed on the main line, then that is
+ also a change. These two changes--the file removal and the
+ modification--must be merged. This is a conflict. */
+
+ /* If the user file is dead, or does not exist, or has been
+ marked for removal, then there is nothing to do. */
+ if (vers->vn_user == NULL
+ || vers->vn_user[0] == '-'
+ || RCS_isdead (vers->srcfile, vers->vn_user))
+ {
+ if (rev1 != NULL)
+ free (rev1);
+ return;
+ }
+
+ /* If the user file has been marked for addition, or has been
+ locally modified, then we have a conflict which we can not
+ resolve. No_Difference will already have been called in
+ this case, so comparing the timestamps is sufficient to
+ determine whether the file is locally modified. */
+ if (strcmp (vers->vn_user, "0") == 0
+ || (vers->ts_user != NULL
+ && strcmp (vers->ts_user, vers->ts_rcs) != 0))
+ {
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s is locally modified, but has been removed in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s is locally modified, but has been removed in revision %s",
+ finfo->fullname, jrev2);
+
+ /* FIXME: Should we arrange to return a non-zero exit
+ status? */
+
+ if (rev1 != NULL)
+ free (rev1);
+
+ return;
+ }
+
+ /* If only one join tag was specified, and the user file has
+ been changed since the greatest common ancestor (rev1),
+ then there is a conflict we can not resolve. See above for
+ the rationale. */
+ if (join_rev2 == NULL
+ && strcmp (rev1, vers->vn_user) != 0)
+ {
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s has been modified, but has been removed in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s has been modified, but has been removed in revision %s",
+ finfo->fullname, jrev2);
+
+ /* FIXME: Should we arrange to return a non-zero exit
+ status? */
+
+ if (rev1 != NULL)
+ free (rev1);
+
+ return;
+ }
+
+ if (rev1 != NULL)
+ free (rev1);
+
+ /* The user file exists and has not been modified. Mark it
+ for removal. FIXME: If we are doing a checkout, this has
+ the effect of first checking out the file, and then
+ removing it. It would be better to just register the
+ removal. */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_scratch (finfo->file);
+ server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1,
+ (unsigned char *) NULL, (struct buffer *) NULL);
+ }
+#endif
+ mrev = xmalloc (strlen (vers->vn_user) + 2);
+ sprintf (mrev, "-%s", vers->vn_user);
+ Register (finfo->entries, finfo->file, mrev, vers->ts_rcs,
+ vers->options, vers->tag, vers->date, vers->ts_conflict);
+ free (mrev);
+ /* We need to check existence_error here because if we are
+ running as the server, and the file is up to date in the
+ working directory, the client will not have sent us a copy. */
+ if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+ error (0, errno, "cannot remove file %s", finfo->fullname);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_checked_in (finfo->file, finfo->update_dir,
+ finfo->repository);
+#endif
+ if (! really_quiet)
+ error (0, 0, "scheduling %s for removal", finfo->fullname);
+
+ return;
+ }
+
+ /* If the target of the merge is the same as the working file
+ revision, then there is nothing to do. */
+ if (vers->vn_user != NULL && strcmp (rev2, vers->vn_user) == 0)
+ {
+ if (rev1 != NULL)
+ free (rev1);
+ free (rev2);
+ return;
+ }
+
+ /* If rev1 is dead or does not exist, then the file was added
+ between rev1 and rev2. */
+ if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
+ {
+ if (rev1 != NULL)
+ free (rev1);
+ free (rev2);
+
+ /* If the file does not exist in the working directory, then
+ we can just check out the new revision and mark it for
+ addition. */
+ if (vers->vn_user == NULL)
+ {
+ Vers_TS *xvers;
+
+ xvers = Version_TS (finfo, vers->options, jrev2, jdate2, 1, 0);
+
+ /* FIXME: If checkout_file fails, we should arrange to
+ return a non-zero exit status. */
+ status = checkout_file (finfo, xvers, 1, 0, 1);
+
+ freevers_ts (&xvers);
+
+ return;
+ }
+
+ /* The file currently exists in the working directory, so we
+ have a conflict which we can not resolve. Note that this
+ is true even if the file is marked for addition or removal. */
+
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s exists, but has been added in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s exists, but has been added in revision %s",
+ finfo->fullname, jrev2);
+
+ return;
+ }
+
+ /* If the two merge revisions are the same, then there is nothing
+ to do. */
+ if (strcmp (rev1, rev2) == 0)
+ {
+ free (rev1);
+ free (rev2);
+ return;
+ }
+
+ /* If there is no working file, then we can't do the merge. */
+ if (vers->vn_user == NULL)
+ {
+ free (rev1);
+ free (rev2);
+
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s is present in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s is present in revision %s",
+ finfo->fullname, jrev2);
+
+ /* FIXME: Should we arrange to return a non-zero exit status? */
+
+ return;
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active && !isreadable (finfo->file))
+ {
+ int retcode;
+ /* The file is up to date. Need to check out the current contents. */
+ retcode = RCS_checkout (vers->srcfile, finfo->file,
+ vers->vn_user, (char *) NULL,
+ (char *) NULL, RUN_TTY,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
+ if (retcode != 0)
+ error (1, 0,
+ "failed to check out %s file", finfo->fullname);
+ }
+#endif
+
+ /*
+ * The users currently modified file is moved to a backup file name
+ * ".#filename.version", so that it will stay around for a few days
+ * before being automatically removed by some cron daemon. The "version"
+ * is the version of the file that the user was most up-to-date with
+ * before the merge.
+ */
+ backup = xmalloc (strlen (finfo->file)
+ + strlen (vers->vn_user)
+ + sizeof (BAKPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
+
+ if (unlink_file (backup) < 0
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", backup);
+ copy_file (finfo->file, backup);
+ xchmod (finfo->file, 1);
+
+ options = vers->options;
+#if 0
+ if (*options == '\0')
+ options = "-kk"; /* to ignore keyword expansions */
+#endif
+
+ /* If the source of the merge is the same as the working file
+ revision, then we can just RCS_checkout the target (no merging
+ as such). In the text file case, this is probably quite
+ similar to the RCS_merge, but in the binary file case,
+ RCS_merge gives all kinds of trouble. */
+ if (vers->vn_user != NULL
+ && strcmp (rev1, vers->vn_user) == 0
+ /* See comments above about how No_Difference has already been
+ called. */
+ && vers->ts_user != NULL
+ && strcmp (vers->ts_user, vers->ts_rcs) == 0
+
+ /* This is because of the worry below about $Name. If that
+ isn't a problem, I suspect this code probably works for
+ text files too. */
+ && (strcmp (options, "-kb") == 0
+ || wrap_merge_is_copy (finfo->file)))
+ {
+ /* FIXME: what about nametag? What does RCS_merge do with
+ $Name? */
+ if (RCS_checkout (finfo->rcs, finfo->file, rev2, NULL, options,
+ RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0)
+ status = 2;
+ else
+ status = 0;
+
+ /* OK, this is really stupid. RCS_checkout carefully removes
+ write permissions, and we carefully put them back. But
+ until someone gets around to fixing it, that seems like the
+ easiest way to get what would seem to be the right mode.
+ I don't check CVSWRITE or _watched; I haven't thought about
+ that in great detail, but it seems like a watched file should
+ be checked out (writable) after a merge. */
+ xchmod (finfo->file, 1);
+
+ /* Traditionally, the text file case prints a whole bunch of
+ scary looking and verbose output which fails to tell the user
+ what is really going on (it gives them rev1 and rev2 but doesn't
+ indicate in any way that rev1 == vn_user). I think just a
+ simple "U foo" is good here; it seems analogous to the case in
+ which the file was added on the branch in terms of what to
+ print. */
+ write_letter (finfo, 'U');
+ }
+ else if (strcmp (options, "-kb") == 0
+ || wrap_merge_is_copy (finfo->file)
+ || special_file_mismatch (finfo, rev1, rev2))
+ {
+ /* We are dealing with binary files, or files with a
+ permission/linkage mismatch, and real merging would
+ need to take place. This is a conflict. We give the user
+ the two files, and let them resolve it. It is possible
+ that we should require a "touch foo" or similar step before
+ we allow a checkin. */
+ if (RCS_checkout (finfo->rcs, finfo->file, rev2, NULL, options,
+ RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0)
+ status = 2;
+ else
+ status = 0;
+
+ /* OK, this is really stupid. RCS_checkout carefully removes
+ write permissions, and we carefully put them back. But
+ until someone gets around to fixing it, that seems like the
+ easiest way to get what would seem to be the right mode.
+ I don't check CVSWRITE or _watched; I haven't thought about
+ that in great detail, but it seems like a watched file should
+ be checked out (writable) after a merge. */
+ xchmod (finfo->file, 1);
+
+ /* Hmm. We don't give them REV1 anywhere. I guess most people
+ probably don't have a 3-way merge tool for the file type in
+ question, and might just get confused if we tried to either
+ provide them with a copy of the file from REV1, or even just
+ told them what REV1 is so they can get it themself, but it
+ might be worth thinking about. */
+ /* See comment in merge_file about the "nonmergeable file"
+ terminology. */
+ error (0, 0, "nonmergeable file needs merge");
+ error (0, 0, "revision %s from repository is now in %s",
+ rev2, finfo->fullname);
+ error (0, 0, "file from working directory is now in %s", backup);
+ write_letter (finfo, 'C');
+ }
+ else
+ status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file,
+ options, rev1, rev2);
+
+ if (status != 0 && status != 1)
+ {
+ error (0, status == -1 ? errno : 0,
+ "could not merge revision %s of %s", rev2, finfo->fullname);
+ error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+ finfo->fullname, backup);
+ rename_file (backup, finfo->file);
+ }
+ free (rev1);
+ free (rev2);
+
+ /* The file has changed, but if we just checked it out it may
+ still have the same timestamp it did when it was first
+ registered above in checkout_file. We register it again with a
+ dummy timestamp to make sure that later runs of CVS will
+ recognize that it has changed.
+
+ We don't actually need to register again if we called
+ RCS_checkout above, and we aren't running as the server.
+ However, that is not the normal case, and calling Register
+ again won't cost much in that case. */
+ {
+ char *cp = 0;
+
+ if (status)
+ {
+ (void) time (&last_register_time);
+ cp = time_stamp (finfo->file);
+ }
+ Register (finfo->entries, finfo->file, vers->vn_rcs,
+ "Result of merge", vers->options, vers->tag,
+ vers->date, cp);
+ if (cp)
+ free(cp);
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
+ backup);
+ server_updated (finfo, vers, SERVER_MERGED,
+ (mode_t) -1, (unsigned char *) NULL,
+ (struct buffer *) NULL);
+ }
+#endif
+ free (backup);
+}
+
+/*
+ * Report whether revisions REV1 and REV2 of FINFO agree on:
+ * . file ownership
+ * . permissions
+ * . major and minor device numbers
+ * . symbolic links
+ * . hard links
+ *
+ * If either REV1 or REV2 is NULL, the working copy is used instead.
+ *
+ * Return 1 if the files differ on these data.
+ */
+
+int
+special_file_mismatch (finfo, rev1, rev2)
+ struct file_info *finfo;
+ char *rev1;
+ char *rev2;
+{
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+ struct stat sb;
+ RCSVers *vp;
+ Node *n;
+ uid_t rev1_uid, rev2_uid;
+ gid_t rev1_gid, rev2_gid;
+ mode_t rev1_mode, rev2_mode;
+ unsigned long dev_long;
+ dev_t rev1_dev, rev2_dev;
+ char *rev1_symlink = NULL;
+ char *rev2_symlink = NULL;
+ List *rev1_hardlinks = NULL;
+ List *rev2_hardlinks = NULL;
+ int check_uids, check_gids, check_modes;
+ int result;
+
+ /* If we don't care about special file info, then
+ don't report a mismatch in any case. */
+ if (!preserve_perms)
+ return 0;
+
+ /* When special_file_mismatch is called from No_Difference, the
+ RCS file has been only partially parsed. We must read the
+ delta tree in order to compare special file info recorded in
+ the delta nodes. (I think this is safe. -twp) */
+ if (finfo->rcs->flags & PARTIAL)
+ RCS_reparsercsfile (finfo->rcs, NULL, NULL);
+
+ check_uids = check_gids = check_modes = 1;
+
+ /* Obtain file information for REV1. If this is null, then stat
+ finfo->file and use that info. */
+ /* If a revision does not know anything about its status,
+ then presumably it doesn't matter, and indicates no conflict. */
+
+ if (rev1 == NULL)
+ {
+ if (islink (finfo->file))
+ rev1_symlink = xreadlink (finfo->file);
+ else
+ {
+ if (CVS_LSTAT (finfo->file, &sb) < 0)
+ error (1, errno, "could not get file information for %s",
+ finfo->file);
+ rev1_uid = sb.st_uid;
+ rev1_gid = sb.st_gid;
+ rev1_mode = sb.st_mode;
+ if (S_ISBLK (rev1_mode) || S_ISCHR (rev1_mode))
+ rev1_dev = sb.st_rdev;
+ }
+ rev1_hardlinks = list_linked_files_on_disk (finfo->file);
+ }
+ else
+ {
+ n = findnode (finfo->rcs->versions, rev1);
+ vp = (RCSVers *) n->data;
+
+ n = findnode (vp->other_delta, "symlink");
+ if (n != NULL)
+ rev1_symlink = xstrdup (n->data);
+ else
+ {
+ n = findnode (vp->other_delta, "owner");
+ if (n == NULL)
+ check_uids = 0; /* don't care */
+ else
+ rev1_uid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "group");
+ if (n == NULL)
+ check_gids = 0; /* don't care */
+ else
+ rev1_gid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "permissions");
+ if (n == NULL)
+ check_modes = 0; /* don't care */
+ else
+ rev1_mode = strtoul (n->data, NULL, 8);
+
+ n = findnode (vp->other_delta, "special");
+ if (n == NULL)
+ rev1_mode |= S_IFREG;
+ else
+ {
+ /* If the size of `ftype' changes, fix the sscanf call also */
+ char ftype[16];
+ if (sscanf (n->data, "%16s %lu", ftype,
+ &dev_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ finfo->file, rev1, n->data);
+ rev1_dev = dev_long;
+ if (strcmp (ftype, "character") == 0)
+ rev1_mode |= S_IFCHR;
+ else if (strcmp (ftype, "block") == 0)
+ rev1_mode |= S_IFBLK;
+ else
+ error (0, 0, "%s:%s unknown file type `%s'",
+ finfo->file, rev1, ftype);
+ }
+
+ rev1_hardlinks = vp->hardlinks;
+ if (rev1_hardlinks == NULL)
+ rev1_hardlinks = getlist();
+ }
+ }
+
+ /* Obtain file information for REV2. */
+ if (rev2 == NULL)
+ {
+ if (islink (finfo->file))
+ rev2_symlink = xreadlink (finfo->file);
+ else
+ {
+ if (CVS_LSTAT (finfo->file, &sb) < 0)
+ error (1, errno, "could not get file information for %s",
+ finfo->file);
+ rev2_uid = sb.st_uid;
+ rev2_gid = sb.st_gid;
+ rev2_mode = sb.st_mode;
+ if (S_ISBLK (rev2_mode) || S_ISCHR (rev2_mode))
+ rev2_dev = sb.st_rdev;
+ }
+ rev2_hardlinks = list_linked_files_on_disk (finfo->file);
+ }
+ else
+ {
+ n = findnode (finfo->rcs->versions, rev2);
+ vp = (RCSVers *) n->data;
+
+ n = findnode (vp->other_delta, "symlink");
+ if (n != NULL)
+ rev2_symlink = xstrdup (n->data);
+ else
+ {
+ n = findnode (vp->other_delta, "owner");
+ if (n == NULL)
+ check_uids = 0; /* don't care */
+ else
+ rev2_uid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "group");
+ if (n == NULL)
+ check_gids = 0; /* don't care */
+ else
+ rev2_gid = strtoul (n->data, NULL, 10);
+
+ n = findnode (vp->other_delta, "permissions");
+ if (n == NULL)
+ check_modes = 0; /* don't care */
+ else
+ rev2_mode = strtoul (n->data, NULL, 8);
+
+ n = findnode (vp->other_delta, "special");
+ if (n == NULL)
+ rev2_mode |= S_IFREG;
+ else
+ {
+ /* If the size of `ftype' changes, fix the sscanf call also */
+ char ftype[16];
+ if (sscanf (n->data, "%16s %lu", ftype,
+ &dev_long) < 2)
+ error (1, 0, "%s:%s has bad `special' newphrase %s",
+ finfo->file, rev2, n->data);
+ rev2_dev = dev_long;
+ if (strcmp (ftype, "character") == 0)
+ rev2_mode |= S_IFCHR;
+ else if (strcmp (ftype, "block") == 0)
+ rev2_mode |= S_IFBLK;
+ else
+ error (0, 0, "%s:%s unknown file type `%s'",
+ finfo->file, rev2, ftype);
+ }
+
+ rev2_hardlinks = vp->hardlinks;
+ if (rev2_hardlinks == NULL)
+ rev2_hardlinks = getlist();
+ }
+ }
+
+ /* Check the user/group ownerships and file permissions, printing
+ an error for each mismatch found. Return 0 if all characteristics
+ matched, and 1 otherwise. */
+
+ result = 0;
+
+ /* Compare symlinks first, since symlinks are simpler (don't have
+ any other characteristics). */
+ if (rev1_symlink != NULL && rev2_symlink == NULL)
+ {
+ error (0, 0, "%s is a symbolic link",
+ (rev1 == NULL ? "working file" : rev1));
+ result = 1;
+ }
+ else if (rev1_symlink == NULL && rev2_symlink != NULL)
+ {
+ error (0, 0, "%s is a symbolic link",
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ else if (rev1_symlink != NULL)
+ result = (strcmp (rev1_symlink, rev2_symlink) == 0);
+ else
+ {
+ /* Compare user ownership. */
+ if (check_uids && rev1_uid != rev2_uid)
+ {
+ error (0, 0, "%s: owner mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare group ownership. */
+ if (check_gids && rev1_gid != rev2_gid)
+ {
+ error (0, 0, "%s: group mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare permissions. */
+ if (check_modes &&
+ (rev1_mode & 07777) != (rev2_mode & 07777))
+ {
+ error (0, 0, "%s: permission mismatch between %s and %s",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+
+ /* Compare device file characteristics. */
+ if ((rev1_mode & S_IFMT) != (rev2_mode & S_IFMT))
+ {
+ error (0, 0, "%s: %s and %s are different file types",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ else if (S_ISBLK (rev1_mode))
+ {
+ if (rev1_dev != rev2_dev)
+ {
+ error (0, 0, "%s: device numbers of %s and %s do not match",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ }
+
+ /* Compare hard links. */
+ if (compare_linkage_lists (rev1_hardlinks, rev2_hardlinks) == 0)
+ {
+ error (0, 0, "%s: hard linkage of %s and %s do not match",
+ finfo->file,
+ (rev1 == NULL ? "working file" : rev1),
+ (rev2 == NULL ? "working file" : rev2));
+ result = 1;
+ }
+ }
+
+ if (rev1_symlink != NULL)
+ free (rev1_symlink);
+ if (rev2_symlink != NULL)
+ free (rev2_symlink);
+ if (rev1_hardlinks != NULL)
+ dellist (&rev1_hardlinks);
+ if (rev2_hardlinks != NULL)
+ dellist (&rev2_hardlinks);
+
+ return result;
+#else
+ return 0;
+#endif
+}
+
+int
+joining ()
+{
+ return (join_rev1 != NULL);
+}
diff --git a/gnu/dist/cvs/src/vers_ts.c b/gnu/dist/cvs/src/vers_ts.c
new file mode 100644
index 00000000000..e1ba32d2c4c
--- /dev/null
+++ b/gnu/dist/cvs/src/vers_ts.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ */
+
+#include "cvs.h"
+
+#ifdef SERVER_SUPPORT
+static void time_stamp_server PROTO((char *, Vers_TS *, Entnode *));
+#endif
+
+/* Fill in and return a Vers_TS structure for the file FINFO. TAG and
+ DATE are from the command line. */
+
+Vers_TS *
+Version_TS (finfo, options, tag, date, force_tag_match, set_time)
+ struct file_info *finfo;
+
+ /* Keyword expansion options, I think generally from the command
+ line. Can be either NULL or "" to indicate none are specified
+ here. */
+ char *options;
+ char *tag;
+ char *date;
+ int force_tag_match;
+ int set_time;
+{
+ Node *p;
+ RCSNode *rcsdata;
+ Vers_TS *vers_ts;
+ struct stickydirtag *sdtp;
+ Entnode *entdata;
+
+#ifdef UTIME_EXPECTS_WRITABLE
+ int change_it_back = 0;
+#endif
+
+ /* get a new Vers_TS struct */
+ vers_ts = (Vers_TS *) xmalloc (sizeof (Vers_TS));
+ memset ((char *) vers_ts, 0, sizeof (*vers_ts));
+
+ /*
+ * look up the entries file entry and fill in the version and timestamp
+ * if entries is NULL, there is no entries file so don't bother trying to
+ * look it up (used by checkout -P)
+ */
+ if (finfo->entries == NULL)
+ {
+ sdtp = NULL;
+ p = NULL;
+ }
+ else
+ {
+ p = findnode_fn (finfo->entries, finfo->file);
+ sdtp = (struct stickydirtag *) finfo->entries->list->data; /* list-private */
+ }
+
+ entdata = NULL;
+ if (p != NULL)
+ {
+ entdata = (Entnode *) p->data;
+
+ if (entdata->type == ENT_SUBDIR)
+ {
+ /* According to cvs.texinfo, the various fields in the Entries
+ file for a directory (other than the name) do not have a
+ defined meaning. We need to pass them along without getting
+ confused based on what is in them. Therefore we make sure
+ not to set vn_user and the like from Entries, add.c and
+ perhaps other code will expect these fields to be NULL for
+ a directory. */
+ vers_ts->entdata = entdata;
+ }
+ else
+#ifdef SERVER_SUPPORT
+ /* An entries line with "D" in the timestamp indicates that the
+ client sent Is-modified without sending Entry. So we want to
+ use the entries line for the sole purpose of telling
+ time_stamp_server what is up; we don't want the rest of CVS
+ to think there is an entries line. */
+ if (strcmp (entdata->timestamp, "D") != 0)
+#endif
+ {
+ vers_ts->vn_user = xstrdup (entdata->version);
+ vers_ts->ts_rcs = xstrdup (entdata->timestamp);
+ vers_ts->ts_conflict = xstrdup (entdata->conflict);
+ if (!tag)
+ {
+ if (!(sdtp && sdtp->aflag))
+ vers_ts->tag = xstrdup (entdata->tag);
+ }
+ if (!date)
+ {
+ if (!(sdtp && sdtp->aflag))
+ vers_ts->date = xstrdup (entdata->date);
+ }
+ vers_ts->entdata = entdata;
+ }
+ /* Even if we don't have an "entries line" as such
+ (vers_ts->entdata), we want to pick up options which could
+ have been from a Kopt protocol request. */
+ if (!options || (options && *options == '\0'))
+ {
+ if (!(sdtp && sdtp->aflag))
+ vers_ts->options = xstrdup (entdata->options);
+ }
+ }
+
+ /*
+ * -k options specified on the command line override (and overwrite)
+ * options stored in the entries file
+ */
+ if (options && *options != '\0')
+ vers_ts->options = xstrdup (options);
+ else if (!vers_ts->options || *vers_ts->options == '\0')
+ {
+ if (finfo->rcs != NULL)
+ {
+ /* If no keyword expansion was specified on command line,
+ use whatever was in the rcs file (if there is one). This
+ is how we, if we are the server, tell the client whether
+ a file is binary. */
+ char *rcsexpand = RCS_getexpand (finfo->rcs);
+ if (rcsexpand != NULL)
+ {
+ vers_ts->options = xmalloc (strlen (rcsexpand) + 3);
+ strcpy (vers_ts->options, "-k");
+ strcat (vers_ts->options, rcsexpand);
+ }
+ }
+ }
+ if (!vers_ts->options)
+ vers_ts->options = xstrdup ("");
+
+ /*
+ * if tags were specified on the command line, they override what is in
+ * the Entries file
+ */
+ if (tag || date)
+ {
+ vers_ts->tag = xstrdup (tag);
+ vers_ts->date = xstrdup (date);
+ }
+ else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0))
+ {
+ if (!vers_ts->tag)
+ {
+ vers_ts->tag = xstrdup (sdtp->tag);
+ vers_ts->nonbranch = sdtp->nonbranch;
+ }
+ if (!vers_ts->date)
+ vers_ts->date = xstrdup (sdtp->date);
+ }
+
+ /* Now look up the info on the source controlled file */
+ if (finfo->rcs != NULL)
+ {
+ rcsdata = finfo->rcs;
+ rcsdata->refcount++;
+ }
+ else if (finfo->repository != NULL)
+ rcsdata = RCS_parse (finfo->file, finfo->repository);
+ else
+ rcsdata = NULL;
+
+ if (rcsdata != NULL)
+ {
+ /* squirrel away the rcsdata pointer for others */
+ vers_ts->srcfile = rcsdata;
+
+ if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0)
+ {
+ vers_ts->vn_rcs = xstrdup (vers_ts->vn_user);
+ vers_ts->vn_tag = xstrdup (vers_ts->vn_user);
+ }
+ else
+ {
+ int simple;
+
+ vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag,
+ vers_ts->date, force_tag_match,
+ &simple);
+ if (vers_ts->vn_rcs == NULL)
+ vers_ts->vn_tag = NULL;
+ else if (simple)
+ vers_ts->vn_tag = xstrdup (vers_ts->tag);
+ else
+ vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs);
+ }
+
+ /*
+ * If the source control file exists and has the requested revision,
+ * get the Date the revision was checked in. If "user" exists, set
+ * its mtime.
+ */
+ if (set_time && vers_ts->vn_rcs != NULL)
+ {
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_modtime (finfo, vers_ts);
+ else
+#endif
+ {
+ struct utimbuf t;
+
+ memset (&t, 0, sizeof (t));
+ t.modtime =
+ RCS_getrevtime (rcsdata, vers_ts->vn_rcs, 0, 0);
+ if (t.modtime != (time_t) -1)
+ {
+ t.actime = t.modtime;
+
+#ifdef UTIME_EXPECTS_WRITABLE
+ if (!iswritable (finfo->file))
+ {
+ xchmod (finfo->file, 1);
+ change_it_back = 1;
+ }
+#endif /* UTIME_EXPECTS_WRITABLE */
+
+ /* This used to need to ignore existence_errors
+ (for cases like where update.c now clears
+ set_time if noexec, but didn't used to). I
+ think maybe now it doesn't (server_modtime does
+ not like those kinds of cases). */
+ (void) utime (finfo->file, &t);
+
+#ifdef UTIME_EXPECTS_WRITABLE
+ if (change_it_back == 1)
+ {
+ xchmod (finfo->file, 0);
+ change_it_back = 0;
+ }
+#endif /* UTIME_EXPECTS_WRITABLE */
+ }
+ }
+ }
+ }
+
+ /* get user file time-stamp in ts_user */
+ if (finfo->entries != (List *) NULL)
+ {
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ time_stamp_server (finfo->file, vers_ts, entdata);
+ else
+#endif
+ vers_ts->ts_user = time_stamp (finfo->file);
+ }
+
+ return (vers_ts);
+}
+
+#ifdef SERVER_SUPPORT
+
+/* Set VERS_TS->TS_USER to time stamp for FILE. */
+
+/* Separate these out to keep the logic below clearer. */
+#define mark_lost(V) ((V)->ts_user = 0)
+#define mark_unchanged(V) ((V)->ts_user = xstrdup ((V)->ts_rcs))
+
+static void
+time_stamp_server (file, vers_ts, entdata)
+ char *file;
+ Vers_TS *vers_ts;
+ Entnode *entdata;
+{
+ struct stat sb;
+ char *cp;
+
+ if (CVS_LSTAT (file, &sb) < 0)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "cannot stat temp file");
+
+ /* Missing file means lost or unmodified; check entries
+ file to see which.
+
+ XXX FIXME - If there's no entries file line, we
+ wouldn't be getting the file at all, so consider it
+ lost. I don't know that that's right, but it's not
+ clear to me that either choice is. Besides, would we
+ have an RCS string in that case anyways? */
+ if (entdata == NULL)
+ mark_lost (vers_ts);
+ else if (entdata->timestamp
+ && entdata->timestamp[0] == '=')
+ mark_unchanged (vers_ts);
+ else if (entdata->timestamp != NULL
+ && (entdata->timestamp[0] == 'M'
+ || entdata->timestamp[0] == 'D')
+ && entdata->timestamp[1] == '\0')
+ vers_ts->ts_user = xstrdup ("Is-modified");
+ else
+ mark_lost (vers_ts);
+ }
+ else if (sb.st_mtime == 0)
+ {
+ /* We shouldn't reach this case any more! */
+ abort ();
+ }
+ else
+ {
+ struct tm *tm_p;
+ struct tm local_tm;
+
+ vers_ts->ts_user = xmalloc (25);
+ /* We want to use the same timestamp format as is stored in the
+ st_mtime. For unix (and NT I think) this *must* be universal
+ time (UT), so that files don't appear to be modified merely
+ because the timezone has changed. For VMS, or hopefully other
+ systems where gmtime returns NULL, the modification time is
+ stored in local time, and therefore it is not possible to cause
+ st_mtime to be out of sync by changing the timezone. */
+ tm_p = gmtime (&sb.st_mtime);
+ if (tm_p)
+ {
+ memcpy (&local_tm, tm_p, sizeof (local_tm));
+ cp = asctime (&local_tm); /* copy in the modify time */
+ }
+ else
+ cp = ctime (&sb.st_mtime);
+
+ cp[24] = 0;
+ (void) strcpy (vers_ts->ts_user, cp);
+ }
+}
+
+#endif /* SERVER_SUPPORT */
+/*
+ * Gets the time-stamp for the file "file" and returns it in space it
+ * allocates
+ */
+char *
+time_stamp (file)
+ char *file;
+{
+ struct stat sb;
+ char *cp;
+ char *ts;
+
+ if (CVS_LSTAT (file, &sb) < 0)
+ {
+ ts = NULL;
+ }
+ else
+ {
+ struct tm *tm_p;
+ struct tm local_tm;
+ ts = xmalloc (25);
+ /* We want to use the same timestamp format as is stored in the
+ st_mtime. For unix (and NT I think) this *must* be universal
+ time (UT), so that files don't appear to be modified merely
+ because the timezone has changed. For VMS, or hopefully other
+ systems where gmtime returns NULL, the modification time is
+ stored in local time, and therefore it is not possible to cause
+ st_mtime to be out of sync by changing the timezone. */
+ tm_p = gmtime (&sb.st_mtime);
+ if (tm_p)
+ {
+ memcpy (&local_tm, tm_p, sizeof (local_tm));
+ cp = asctime (&local_tm); /* copy in the modify time */
+ }
+ else
+ cp = ctime(&sb.st_mtime);
+
+ cp[24] = 0;
+ (void) strcpy (ts, cp);
+ }
+
+ return (ts);
+}
+
+/*
+ * free up a Vers_TS struct
+ */
+void
+freevers_ts (versp)
+ Vers_TS **versp;
+{
+ if ((*versp)->srcfile)
+ freercsnode (&((*versp)->srcfile));
+ if ((*versp)->vn_user)
+ free ((*versp)->vn_user);
+ if ((*versp)->vn_rcs)
+ free ((*versp)->vn_rcs);
+ if ((*versp)->vn_tag)
+ free ((*versp)->vn_tag);
+ if ((*versp)->ts_user)
+ free ((*versp)->ts_user);
+ if ((*versp)->ts_rcs)
+ free ((*versp)->ts_rcs);
+ if ((*versp)->options)
+ free ((*versp)->options);
+ if ((*versp)->tag)
+ free ((*versp)->tag);
+ if ((*versp)->date)
+ free ((*versp)->date);
+ if ((*versp)->ts_conflict)
+ free ((*versp)->ts_conflict);
+ free ((char *) *versp);
+ *versp = (Vers_TS *) NULL;
+}
diff --git a/gnu/dist/cvs/src/version.c b/gnu/dist/cvs/src/version.c
new file mode 100644
index 00000000000..a4401dad4a0
--- /dev/null
+++ b/gnu/dist/cvs/src/version.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1994 david d `zoo' zuhn
+ * Copyright (c) 1994 Free Software Foundation, Inc.
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with this CVS source distribution.
+ *
+ * version.c - the CVS version number
+ */
+
+#include "cvs.h"
+
+char *version_string = "\nConcurrent Versions System (CVS) 1.10.8";
+
+#ifdef CLIENT_SUPPORT
+#ifdef SERVER_SUPPORT
+char *config_string = " (client/server)\n";
+#else
+char *config_string = " (client)\n";
+#endif
+#else
+#ifdef SERVER_SUPPORT
+char *config_string = " (server)\n";
+#else
+char *config_string = "\n";
+#endif
+#endif
diff --git a/gnu/dist/cvs/src/watch.c b/gnu/dist/cvs/src/watch.c
new file mode 100644
index 00000000000..b2935ac3dd0
--- /dev/null
+++ b/gnu/dist/cvs/src/watch.c
@@ -0,0 +1,529 @@
+/* Implementation for "cvs watch add", "cvs watchers", and related commands
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+#include "cvs.h"
+#include "edit.h"
+#include "fileattr.h"
+#include "watch.h"
+
+const char *const watch_usage[] =
+{
+ "Usage: %s %s [on|off|add|remove] [-lR] [-a action] [files...]\n",
+ "on/off: turn on/off read-only checkouts of files\n",
+ "add/remove: add or remove notification on actions\n",
+ "-l (on/off/add/remove): Local directory only, not recursive\n",
+ "-R (on/off/add/remove): Process directories recursively\n",
+ "-a (add/remove): Specify what actions, one of\n",
+ " edit,unedit,commit,all,none\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+static struct addremove_args the_args;
+
+void
+watch_modify_watchers (file, what)
+ char *file;
+ struct addremove_args *what;
+{
+ char *curattr = fileattr_get0 (file, "_watchers");
+ char *p;
+ char *pend;
+ char *nextp;
+ char *who;
+ int who_len;
+ char *mycurattr;
+ char *mynewattr;
+ size_t mynewattr_size;
+
+ int add_edit_pending;
+ int add_unedit_pending;
+ int add_commit_pending;
+ int remove_edit_pending;
+ int remove_unedit_pending;
+ int remove_commit_pending;
+ int add_tedit_pending;
+ int add_tunedit_pending;
+ int add_tcommit_pending;
+
+ who = getcaller ();
+ who_len = strlen (who);
+
+ /* Look for current watcher types for this user. */
+ mycurattr = NULL;
+ if (curattr != NULL)
+ {
+ p = curattr;
+ while (1) {
+ if (strncmp (who, p, who_len) == 0
+ && p[who_len] == '>')
+ {
+ /* Found this user. */
+ mycurattr = p + who_len + 1;
+ }
+ p = strchr (p, ',');
+ if (p == NULL)
+ break;
+ ++p;
+ }
+ }
+ if (mycurattr != NULL)
+ {
+ mycurattr = xstrdup (mycurattr);
+ p = strchr (mycurattr, ',');
+ if (p != NULL)
+ *p = '\0';
+ }
+
+ /* Now copy mycurattr to mynewattr, making the requisite modifications.
+ Note that we add a dummy '+' to the start of mynewattr, to reduce
+ special cases (but then we strip it off when we are done). */
+
+ mynewattr_size = sizeof "+edit+unedit+commit+tedit+tunedit+tcommit";
+ if (mycurattr != NULL)
+ mynewattr_size += strlen (mycurattr);
+ mynewattr = xmalloc (mynewattr_size);
+ mynewattr[0] = '\0';
+
+ add_edit_pending = what->adding && what->edit;
+ add_unedit_pending = what->adding && what->unedit;
+ add_commit_pending = what->adding && what->commit;
+ remove_edit_pending = !what->adding && what->edit;
+ remove_unedit_pending = !what->adding && what->unedit;
+ remove_commit_pending = !what->adding && what->commit;
+ add_tedit_pending = what->add_tedit;
+ add_tunedit_pending = what->add_tunedit;
+ add_tcommit_pending = what->add_tcommit;
+
+ /* Copy over existing watch types, except those to be removed. */
+ p = mycurattr;
+ while (p != NULL)
+ {
+ pend = strchr (p, '+');
+ if (pend == NULL)
+ {
+ pend = p + strlen (p);
+ nextp = NULL;
+ }
+ else
+ nextp = pend + 1;
+
+ /* Process this item. */
+ if (pend - p == 4 && strncmp ("edit", p, 4) == 0)
+ {
+ if (!remove_edit_pending)
+ strcat (mynewattr, "+edit");
+ add_edit_pending = 0;
+ }
+ else if (pend - p == 6 && strncmp ("unedit", p, 6) == 0)
+ {
+ if (!remove_unedit_pending)
+ strcat (mynewattr, "+unedit");
+ add_unedit_pending = 0;
+ }
+ else if (pend - p == 6 && strncmp ("commit", p, 6) == 0)
+ {
+ if (!remove_commit_pending)
+ strcat (mynewattr, "+commit");
+ add_commit_pending = 0;
+ }
+ else if (pend - p == 5 && strncmp ("tedit", p, 5) == 0)
+ {
+ if (!what->remove_temp)
+ strcat (mynewattr, "+tedit");
+ add_tedit_pending = 0;
+ }
+ else if (pend - p == 7 && strncmp ("tunedit", p, 7) == 0)
+ {
+ if (!what->remove_temp)
+ strcat (mynewattr, "+tunedit");
+ add_tunedit_pending = 0;
+ }
+ else if (pend - p == 7 && strncmp ("tcommit", p, 7) == 0)
+ {
+ if (!what->remove_temp)
+ strcat (mynewattr, "+tcommit");
+ add_tcommit_pending = 0;
+ }
+ else
+ {
+ char *mp;
+
+ /* Copy over any unrecognized watch types, for future
+ expansion. */
+ mp = mynewattr + strlen (mynewattr);
+ *mp++ = '+';
+ strncpy (mp, p, pend - p);
+ *(mp + (pend - p)) = '\0';
+ }
+
+ /* Set up for next item. */
+ p = nextp;
+ }
+
+ /* Add in new watch types. */
+ if (add_edit_pending)
+ strcat (mynewattr, "+edit");
+ if (add_unedit_pending)
+ strcat (mynewattr, "+unedit");
+ if (add_commit_pending)
+ strcat (mynewattr, "+commit");
+ if (add_tedit_pending)
+ strcat (mynewattr, "+tedit");
+ if (add_tunedit_pending)
+ strcat (mynewattr, "+tunedit");
+ if (add_tcommit_pending)
+ strcat (mynewattr, "+tcommit");
+
+ {
+ char *curattr_new;
+
+ curattr_new =
+ fileattr_modify (curattr,
+ who,
+ mynewattr[0] == '\0' ? NULL : mynewattr + 1,
+ '>',
+ ',');
+ /* If the attribute is unchanged, don't rewrite the attribute file. */
+ if (!((curattr_new == NULL && curattr == NULL)
+ || (curattr_new != NULL
+ && curattr != NULL
+ && strcmp (curattr_new, curattr) == 0)))
+ fileattr_set (file,
+ "_watchers",
+ curattr_new);
+ if (curattr_new != NULL)
+ free (curattr_new);
+ }
+
+ if (curattr != NULL)
+ free (curattr);
+ if (mycurattr != NULL)
+ free (mycurattr);
+ if (mynewattr != NULL)
+ free (mynewattr);
+}
+
+static int addremove_fileproc PROTO ((void *callerdat,
+ struct file_info *finfo));
+
+static int
+addremove_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ watch_modify_watchers (finfo->file, &the_args);
+ return 0;
+}
+
+static int addremove_filesdoneproc PROTO ((void *, int, char *, char *,
+ List *));
+
+static int
+addremove_filesdoneproc (callerdat, err, repository, update_dir, entries)
+ void *callerdat;
+ int err;
+ char *repository;
+ char *update_dir;
+ List *entries;
+{
+ if (the_args.setting_default)
+ watch_modify_watchers (NULL, &the_args);
+ return err;
+}
+
+static int watch_addremove PROTO ((int argc, char **argv));
+
+static int
+watch_addremove (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int local = 0;
+ int err;
+ int a_omitted;
+
+ a_omitted = 1;
+ the_args.commit = 0;
+ the_args.edit = 0;
+ the_args.unedit = 0;
+ optind = 0;
+ while ((c = getopt (argc, argv, "+lRa:")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'a':
+ a_omitted = 0;
+ if (strcmp (optarg, "edit") == 0)
+ the_args.edit = 1;
+ else if (strcmp (optarg, "unedit") == 0)
+ the_args.unedit = 1;
+ else if (strcmp (optarg, "commit") == 0)
+ the_args.commit = 1;
+ else if (strcmp (optarg, "all") == 0)
+ {
+ the_args.edit = 1;
+ the_args.unedit = 1;
+ the_args.commit = 1;
+ }
+ else if (strcmp (optarg, "none") == 0)
+ {
+ the_args.edit = 0;
+ the_args.unedit = 0;
+ the_args.commit = 0;
+ }
+ else
+ usage (watch_usage);
+ break;
+ case '?':
+ default:
+ usage (watch_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (a_omitted)
+ {
+ the_args.edit = 1;
+ the_args.unedit = 1;
+ the_args.commit = 1;
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ /* FIXME: copes poorly with "all" if server is extended to have
+ new watch types and client is still running an old version. */
+ if (the_args.edit)
+ {
+ send_arg ("-a");
+ send_arg ("edit");
+ }
+ if (the_args.unedit)
+ {
+ send_arg ("-a");
+ send_arg ("unedit");
+ }
+ if (the_args.commit)
+ {
+ send_arg ("-a");
+ send_arg ("commit");
+ }
+ if (!the_args.edit && !the_args.unedit && !the_args.commit)
+ {
+ send_arg ("-a");
+ send_arg ("none");
+ }
+ send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_to_server (the_args.adding ?
+ "watch-add\012" : "watch-remove\012",
+ 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ the_args.setting_default = (argc <= 0);
+
+ lock_tree_for_write (argc, argv, local, 0);
+
+ err = start_recursion (addremove_fileproc, addremove_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 1);
+
+ Lock_Cleanup ();
+ return err;
+}
+
+int
+watch_add (argc, argv)
+ int argc;
+ char **argv;
+{
+ the_args.adding = 1;
+ return watch_addremove (argc, argv);
+}
+
+int
+watch_remove (argc, argv)
+ int argc;
+ char **argv;
+{
+ the_args.adding = 0;
+ return watch_addremove (argc, argv);
+}
+
+int
+watch (argc, argv)
+ int argc;
+ char **argv;
+{
+ if (argc <= 1)
+ usage (watch_usage);
+ if (strcmp (argv[1], "on") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_on (argc, argv);
+ }
+ else if (strcmp (argv[1], "off") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_off (argc, argv);
+ }
+ else if (strcmp (argv[1], "add") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_add (argc, argv);
+ }
+ else if (strcmp (argv[1], "remove") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_remove (argc, argv);
+ }
+ else
+ usage (watch_usage);
+ return 0;
+}
+
+static const char *const watchers_usage[] =
+{
+ "Usage: %s %s [-lR] [files...]\n",
+ "\t-l\tProcess this directory only (not recursive).\n",
+ "\t-R\tProcess directories recursively.\n",
+ "(Specify the --help global option for a list of other help options)\n",
+ NULL
+};
+
+static int watchers_fileproc PROTO ((void *callerdat,
+ struct file_info *finfo));
+
+static int
+watchers_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ char *them;
+ char *p;
+
+ them = fileattr_get0 (finfo->file, "_watchers");
+ if (them == NULL)
+ return 0;
+
+ cvs_output (finfo->fullname, 0);
+
+ p = them;
+ while (1)
+ {
+ cvs_output ("\t", 1);
+ while (*p != '>' && *p != '\0')
+ cvs_output (p++, 1);
+ if (*p == '\0')
+ {
+ /* Only happens if attribute is misformed. */
+ cvs_output ("\n", 1);
+ break;
+ }
+ ++p;
+ cvs_output ("\t", 1);
+ while (1)
+ {
+ while (*p != '+' && *p != ',' && *p != '\0')
+ cvs_output (p++, 1);
+ if (*p == '\0')
+ {
+ cvs_output ("\n", 1);
+ goto out;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ break;
+ }
+ ++p;
+ cvs_output ("\t", 1);
+ }
+ cvs_output ("\n", 1);
+ }
+ out:;
+ return 0;
+}
+
+int
+watchers (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (watchers_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+lR")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (watchers_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_to_server ("watchers\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ return start_recursion (watchers_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 1);
+}
diff --git a/gnu/dist/cvs/src/wrapper.c b/gnu/dist/cvs/src/wrapper.c
new file mode 100644
index 00000000000..2d53c7a981d
--- /dev/null
+++ b/gnu/dist/cvs/src/wrapper.c
@@ -0,0 +1,614 @@
+/* This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+#include "cvs.h"
+#include "getline.h"
+
+/*
+ Original Author: athan@morgan.com <Andrew C. Athan> 2/1/94
+ Modified By: vdemarco@bou.shl.com
+
+ This package was written to support the NEXTSTEP concept of
+ "wrappers." These are essentially directories that are to be
+ treated as "files." This package allows such wrappers to be
+ "processed" on the way in and out of CVS. The intended use is to
+ wrap up a wrapper into a single tar, such that that tar can be
+ treated as a single binary file in CVS. To solve the problem
+ effectively, it was also necessary to be able to prevent rcsmerge
+ application at appropriate times.
+
+ ------------------
+ Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
+
+ wildcard [option value][option value]...
+
+ where option is one of
+ -f from cvs filter value: path to filter
+ -t to cvs filter value: path to filter
+ -m update methodology value: MERGE or COPY
+ -k default -k rcs option to use on import or add
+
+ and value is a single-quote delimited value.
+
+ E.g:
+ *.nib -f 'gunzipuntar' -t 'targzip' -m 'COPY'
+*/
+
+
+typedef struct {
+ char *wildCard;
+ char *tocvsFilter;
+ char *fromcvsFilter;
+ char *rcsOption;
+ WrapMergeMethod mergeMethod;
+} WrapperEntry;
+
+static WrapperEntry **wrap_list=NULL;
+static WrapperEntry **wrap_saved_list=NULL;
+
+static int wrap_size=0;
+static int wrap_count=0;
+static int wrap_tempcount=0;
+
+/* FIXME: the relationship between wrap_count, wrap_tempcount,
+ * wrap_saved_count, and wrap_saved_tempcount is not entirely clear;
+ * it is certainly suspicious that wrap_saved_count is never set to a
+ * value other than zero! If the variable isn't being used, it should
+ * be removed. And in general, we should describe how temporary
+ * vs. permanent wrappers are implemented, and then make sure the
+ * implementation is actually doing that.
+ *
+ * Right now things seem to be working, but that's no guarantee there
+ * isn't a bug lurking somewhere in the murk.
+ */
+
+static int wrap_saved_count=0;
+
+static int wrap_saved_tempcount=0;
+
+#define WRAPPER_GROW 8
+
+void wrap_add_entry PROTO((WrapperEntry *e,int temp));
+void wrap_kill PROTO((void));
+void wrap_kill_temp PROTO((void));
+void wrap_free_entry PROTO((WrapperEntry *e));
+void wrap_free_entry_internal PROTO((WrapperEntry *e));
+void wrap_restore_saved PROTO((void));
+
+void wrap_setup()
+{
+ /* FIXME-reentrancy: if we do a multithreaded server, will need to
+ move this to a per-connection data structure, or better yet
+ think about a cleaner solution. */
+ static int wrap_setup_already_done = 0;
+ char *homedir;
+
+ if (wrap_setup_already_done != 0)
+ return;
+ else
+ wrap_setup_already_done = 1;
+
+#ifdef CLIENT_SUPPORT
+ if (!client_active)
+#endif
+ {
+ char *file;
+
+ file = xmalloc (strlen (CVSroot_directory)
+ + sizeof (CVSROOTADM)
+ + sizeof (CVSROOTADM_WRAPPER)
+ + 10);
+ /* Then add entries found in repository, if it exists. */
+ (void) sprintf (file, "%s/%s/%s", CVSroot_directory, CVSROOTADM,
+ CVSROOTADM_WRAPPER);
+ if (isfile (file))
+ {
+ wrap_add_file(file,0);
+ }
+ free (file);
+ }
+
+ /* Then add entries found in home dir, (if user has one) and file
+ exists. */
+ homedir = get_homedir ();
+ /* If we can't find a home directory, ignore ~/.cvswrappers. This may
+ make tracking down problems a bit of a pain, but on the other
+ hand it might be obnoxious to complain when CVS will function
+ just fine without .cvswrappers (and many users won't even know what
+ .cvswrappers is). */
+ if (homedir != NULL)
+ {
+ char *file;
+
+ file = xmalloc (strlen (homedir) + sizeof (CVSDOTWRAPPER) + 10);
+ (void) sprintf (file, "%s/%s", homedir, CVSDOTWRAPPER);
+ if (isfile (file))
+ {
+ wrap_add_file (file, 0);
+ }
+ free (file);
+ }
+
+ /* FIXME: calling wrap_add() below implies that the CVSWRAPPERS
+ * environment variable contains exactly one "wrapper" -- a line
+ * of the form
+ *
+ * FILENAME_PATTERN FLAG OPTS [ FLAG OPTS ...]
+ *
+ * This may disagree with the documentation, which states:
+ *
+ * `$CVSWRAPPERS'
+ * A whitespace-separated list of file name patterns that CVS
+ * should treat as wrappers. *Note Wrappers::.
+ *
+ * Does this mean the environment variable can hold multiple
+ * wrappers lines? If so, a single call to wrap_add() is
+ * insufficient.
+ */
+
+ /* Then add entries found in CVSWRAPPERS environment variable. */
+ wrap_add (getenv (WRAPPER_ENV), 0);
+}
+
+#ifdef CLIENT_SUPPORT
+/* Send -W arguments for the wrappers to the server. The command must
+ be one that accepts them (e.g. update, import). */
+void
+wrap_send ()
+{
+ int i;
+
+ for (i = 0; i < wrap_count + wrap_tempcount; ++i)
+ {
+ if (wrap_list[i]->tocvsFilter != NULL
+ || wrap_list[i]->fromcvsFilter != NULL)
+ /* For greater studliness we would print the offending option
+ and (more importantly) where we found it. */
+ error (0, 0, "\
+-t and -f wrapper options are not supported remotely; ignored");
+ if (wrap_list[i]->mergeMethod == WRAP_COPY)
+ /* For greater studliness we would print the offending option
+ and (more importantly) where we found it. */
+ error (0, 0, "\
+-m wrapper option is not supported remotely; ignored");
+ if (wrap_list[i]->rcsOption != NULL)
+ {
+ send_to_server ("Argument -W\012Argument ", 0);
+ send_to_server (wrap_list[i]->wildCard, 0);
+ send_to_server (" -k '", 0);
+ send_to_server (wrap_list[i]->rcsOption, 0);
+ send_to_server ("'\012", 0);
+ }
+ }
+}
+#endif /* CLIENT_SUPPORT */
+
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+/* Output wrapper entries in the format of cvswrappers lines.
+ *
+ * This is useful when one side of a client/server connection wants to
+ * send its wrappers to the other; since the receiving side would like
+ * to use wrap_add() to incorporate the wrapper, it's best if the
+ * entry arrives in this format.
+ *
+ * The entries are stored in `line', which is allocated here. Caller
+ * can free() it.
+ *
+ * If first_call_p is nonzero, then start afresh. */
+void
+wrap_unparse_rcs_options (line, first_call_p)
+ char **line;
+ int first_call_p;
+{
+ /* FIXME-reentrancy: we should design a reentrant interface, like
+ a callback which gets handed each wrapper (a multithreaded
+ server being the most concrete reason for this, but the
+ non-reentrant interface is fairly unnecessary/ugly). */
+ static int i;
+
+ if (first_call_p)
+ i = 0;
+
+ for (; i < wrap_count + wrap_tempcount; ++i)
+ {
+ if (wrap_list[i]->rcsOption != NULL)
+ {
+ *line = xmalloc (strlen (wrap_list[i]->wildCard)
+ + strlen ("\t")
+ + strlen (" -k '")
+ + strlen (wrap_list[i]->rcsOption)
+ + strlen ("'")
+ + 1); /* leave room for '\0' */
+
+ strcpy (*line, wrap_list[i]->wildCard);
+ strcat (*line, " -k '");
+ strcat (*line, wrap_list[i]->rcsOption);
+ strcat (*line, "'");
+
+ /* We're going to miss the increment because we return, so
+ do it by hand. */
+ ++i;
+
+ return;
+ }
+ }
+
+ *line = NULL;
+ return;
+}
+#endif /* SERVER_SUPPORT || CLIENT_SUPPORT */
+
+/*
+ * Open a file and read lines, feeding each line to a line parser. Arrange
+ * for keeping a temporary list of wrappers at the end, if the "temp"
+ * argument is set.
+ */
+void
+wrap_add_file (file, temp)
+ const char *file;
+ int temp;
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t line_allocated = 0;
+
+ wrap_restore_saved ();
+ wrap_kill_temp ();
+
+ /* Load the file. */
+ fp = CVS_FOPEN (file, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", file);
+ return;
+ }
+ while (getline (&line, &line_allocated, fp) >= 0)
+ wrap_add (line, temp);
+ if (line)
+ free (line);
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", file);
+ if (fclose (fp) == EOF)
+ error (0, errno, "cannot close %s", file);
+}
+
+void
+wrap_kill()
+{
+ wrap_kill_temp();
+ while(wrap_count)
+ wrap_free_entry(wrap_list[--wrap_count]);
+}
+
+void
+wrap_kill_temp()
+{
+ WrapperEntry **temps=wrap_list+wrap_count;
+
+ while(wrap_tempcount)
+ wrap_free_entry(temps[--wrap_tempcount]);
+}
+
+void
+wrap_free_entry(e)
+ WrapperEntry *e;
+{
+ wrap_free_entry_internal(e);
+ free(e);
+}
+
+void
+wrap_free_entry_internal(e)
+ WrapperEntry *e;
+{
+ free (e->wildCard);
+ if (e->tocvsFilter)
+ free (e->tocvsFilter);
+ if (e->fromcvsFilter)
+ free (e->fromcvsFilter);
+ if (e->rcsOption)
+ free (e->rcsOption);
+}
+
+void
+wrap_restore_saved()
+{
+ if(!wrap_saved_list)
+ return;
+
+ wrap_kill();
+
+ free(wrap_list);
+
+ wrap_list=wrap_saved_list;
+ wrap_count=wrap_saved_count;
+ wrap_tempcount=wrap_saved_tempcount;
+
+ wrap_saved_list=NULL;
+ wrap_saved_count=0;
+ wrap_saved_tempcount=0;
+}
+
+void
+wrap_add (line, isTemp)
+ char *line;
+ int isTemp;
+{
+ char *temp;
+ char ctemp;
+ WrapperEntry e;
+ char opt;
+
+ if (!line || line[0] == '#')
+ return;
+
+ memset (&e, 0, sizeof(e));
+
+ /* Search for the wild card */
+ while (*line && isspace ((unsigned char) *line))
+ ++line;
+ for (temp = line;
+ *line && !isspace ((unsigned char) *line);
+ ++line)
+ ;
+ if(temp==line)
+ return;
+
+ ctemp=*line;
+ *line='\0';
+
+ e.wildCard=xstrdup(temp);
+ *line=ctemp;
+
+ while(*line){
+ /* Search for the option */
+ while(*line && *line!='-')
+ ++line;
+ if(!*line)
+ break;
+ ++line;
+ if(!*line)
+ break;
+ opt=*line;
+
+ /* Search for the filter commandline */
+ for(++line;*line && *line!='\'';++line);
+ if(!*line)
+ break;
+
+ for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
+ ;
+
+ /* This used to "break;" (ignore the option) if there was a
+ single character between the single quotes (I'm guessing
+ that was accidental). Now it "break;"s if there are no
+ characters. I'm not sure either behavior is particularly
+ necessary--the current options might not require ''
+ arguments, but surely some future option legitimately
+ might. Also I'm not sure that ignoring the option is a
+ swift way to handle syntax errors in general. */
+ if (line==temp)
+ break;
+
+ ctemp=*line;
+ *line='\0';
+ switch(opt){
+ case 'f':
+ /* Before this is reenabled, need to address the problem in
+ commit.c (see http://www.cyclic.com/cvs/dev-wrap.txt). */
+ error (1, 0,
+ "-t/-f wrappers not supported by this version of CVS");
+
+ if(e.fromcvsFilter)
+ free(e.fromcvsFilter);
+ /* FIXME: error message should say where the bad value
+ came from. */
+ e.fromcvsFilter=expand_path (temp, "<wrapper>", 0);
+ if (!e.fromcvsFilter)
+ error (1, 0, "Correct above errors first");
+ break;
+ case 't':
+ /* Before this is reenabled, need to address the problem in
+ commit.c (see http://www.cyclic.com/cvs/dev-wrap.txt). */
+ error (1, 0,
+ "-t/-f wrappers not supported by this version of CVS");
+
+ if(e.tocvsFilter)
+ free(e.tocvsFilter);
+ /* FIXME: error message should say where the bad value
+ came from. */
+ e.tocvsFilter=expand_path (temp, "<wrapper>", 0);
+ if (!e.tocvsFilter)
+ error (1, 0, "Correct above errors first");
+ break;
+ case 'm':
+ if(*temp=='C' || *temp=='c')
+ e.mergeMethod=WRAP_COPY;
+ else
+ e.mergeMethod=WRAP_MERGE;
+ break;
+ case 'k':
+ if (e.rcsOption)
+ free (e.rcsOption);
+ e.rcsOption = xstrdup (temp);
+ break;
+ default:
+ break;
+ }
+ *line=ctemp;
+ if(!*line)break;
+ ++line;
+ }
+
+ wrap_add_entry(&e, isTemp);
+}
+
+void
+wrap_add_entry(e, temp)
+ WrapperEntry *e;
+ int temp;
+{
+ int x;
+ if(wrap_count+wrap_tempcount>=wrap_size){
+ wrap_size += WRAPPER_GROW;
+ wrap_list = (WrapperEntry **) xrealloc ((char *) wrap_list,
+ wrap_size *
+ sizeof (WrapperEntry *));
+ }
+
+ if(!temp && wrap_tempcount){
+ for(x=wrap_count+wrap_tempcount-1;x>=wrap_count;--x)
+ wrap_list[x+1]=wrap_list[x];
+ }
+
+ x=(temp ? wrap_count+(wrap_tempcount++):(wrap_count++));
+ wrap_list[x]=(WrapperEntry *)xmalloc(sizeof(WrapperEntry));
+ wrap_list[x]->wildCard=e->wildCard;
+ wrap_list[x]->fromcvsFilter=e->fromcvsFilter;
+ wrap_list[x]->tocvsFilter=e->tocvsFilter;
+ wrap_list[x]->mergeMethod=e->mergeMethod;
+ wrap_list[x]->rcsOption = e->rcsOption;
+}
+
+/* Return 1 if the given filename is a wrapper filename */
+int
+wrap_name_has (name,has)
+ const char *name;
+ WrapMergeHas has;
+{
+ int x,count=wrap_count+wrap_tempcount;
+ char *temp;
+
+ for(x=0;x<count;++x)
+ if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0){
+ switch(has){
+ case WRAP_TOCVS:
+ temp=wrap_list[x]->tocvsFilter;
+ break;
+ case WRAP_FROMCVS:
+ temp=wrap_list[x]->fromcvsFilter;
+ break;
+ case WRAP_RCSOPTION:
+ temp = wrap_list[x]->rcsOption;
+ break;
+ default:
+ abort ();
+ }
+ if(temp==NULL)
+ return (0);
+ else
+ return (1);
+ }
+ return (0);
+}
+
+static WrapperEntry *wrap_matching_entry PROTO ((const char *));
+
+static WrapperEntry *
+wrap_matching_entry (name)
+ const char *name;
+{
+ int x,count=wrap_count+wrap_tempcount;
+
+ for(x=0;x<count;++x)
+ if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0)
+ return wrap_list[x];
+ return (WrapperEntry *)NULL;
+}
+
+/* Return the RCS options for FILENAME in a newly malloc'd string. If
+ ASFLAG, then include "-k" at the beginning (e.g. "-kb"), otherwise
+ just give the option itself (e.g. "b"). */
+char *
+wrap_rcsoption (filename, asflag)
+ const char *filename;
+ int asflag;
+{
+ WrapperEntry *e = wrap_matching_entry (filename);
+ char *buf;
+
+ if (e == NULL || e->rcsOption == NULL || (*e->rcsOption == '\0'))
+ return NULL;
+
+ buf = xmalloc (strlen (e->rcsOption) + 3);
+ if (asflag)
+ {
+ strcpy (buf, "-k");
+ strcat (buf, e->rcsOption);
+ }
+ else
+ {
+ strcpy (buf, e->rcsOption);
+ }
+ return buf;
+}
+
+char *
+wrap_tocvs_process_file(fileName)
+ const char *fileName;
+{
+ WrapperEntry *e=wrap_matching_entry(fileName);
+ static char *buf = NULL;
+ char *args;
+
+ if(e==NULL || e->tocvsFilter==NULL)
+ return NULL;
+
+ if (buf != NULL)
+ free (buf);
+ buf = cvs_temp_name ();
+
+ args = xmalloc (strlen (e->tocvsFilter)
+ + strlen (fileName)
+ + strlen (buf));
+ /* FIXME: sprintf will blow up if the format string contains items other
+ than %s, or contains too many %s's. We should instead be parsing
+ e->tocvsFilter ourselves and giving a real error. */
+ sprintf (args, e->tocvsFilter, fileName, buf);
+ run_setup (args);
+ run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY );
+ free (args);
+
+ return buf;
+}
+
+int
+wrap_merge_is_copy (fileName)
+ const char *fileName;
+{
+ WrapperEntry *e=wrap_matching_entry(fileName);
+ if(e==NULL || e->mergeMethod==WRAP_MERGE)
+ return 0;
+
+ return 1;
+}
+
+void
+wrap_fromcvs_process_file(fileName)
+ const char *fileName;
+{
+ char *args;
+ WrapperEntry *e=wrap_matching_entry(fileName);
+
+ if(e==NULL || e->fromcvsFilter==NULL)
+ return;
+
+ args = xmalloc (strlen (e->fromcvsFilter)
+ + strlen (fileName));
+ /* FIXME: sprintf will blow up if the format string contains items other
+ than %s, or contains too many %s's. We should instead be parsing
+ e->fromcvsFilter ourselves and giving a real error. */
+ sprintf (args, e->fromcvsFilter, fileName);
+ run_setup (args);
+ run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL );
+ free (args);
+ return;
+}
diff --git a/gnu/dist/cvs/src/zlib.c b/gnu/dist/cvs/src/zlib.c
new file mode 100644
index 00000000000..fa0c2ad40d0
--- /dev/null
+++ b/gnu/dist/cvs/src/zlib.c
@@ -0,0 +1,662 @@
+/* zlib.c --- interface to the zlib compression library
+ Ian Lance Taylor <ian@cygnus.com>
+
+ This file is part of GNU CVS.
+
+ GNU CVS is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+/* The routines in this file are the interface between the CVS
+ client/server support and the zlib compression library. */
+
+#include <assert.h>
+#include "cvs.h"
+#include "buffer.h"
+
+#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
+
+#include "zlib.h"
+
+/* OS/2 doesn't have EIO. FIXME: this whole notion of turning
+ a different error into EIO strikes me as pretty dubious. */
+#if !defined (EIO)
+#define EIO EBADPOS
+#endif
+
+/* The compression interface is built upon the buffer data structure.
+ We provide a buffer type which compresses or decompresses the data
+ which passes through it. An input buffer decompresses the data
+ read from an underlying buffer, and an output buffer compresses the
+ data before writing it to an underlying buffer. */
+
+/* This structure is the closure field of the buffer. */
+
+struct compress_buffer
+{
+ /* The underlying buffer. */
+ struct buffer *buf;
+ /* The compression information. */
+ z_stream zstr;
+};
+
+static void compress_error PROTO((int, int, z_stream *, const char *));
+static int compress_buffer_input PROTO((void *, char *, int, int, int *));
+static int compress_buffer_output PROTO((void *, const char *, int, int *));
+static int compress_buffer_flush PROTO((void *));
+static int compress_buffer_block PROTO((void *, int));
+static int compress_buffer_shutdown_input PROTO((void *));
+static int compress_buffer_shutdown_output PROTO((void *));
+
+/* Report an error from one of the zlib functions. */
+
+static void
+compress_error (status, zstatus, zstr, msg)
+ int status;
+ int zstatus;
+ z_stream *zstr;
+ const char *msg;
+{
+ int hold_errno;
+ const char *zmsg;
+ char buf[100];
+
+ hold_errno = errno;
+
+ zmsg = zstr->msg;
+ if (zmsg == NULL)
+ {
+ sprintf (buf, "error %d", zstatus);
+ zmsg = buf;
+ }
+
+ error (status,
+ zstatus == Z_ERRNO ? hold_errno : 0,
+ "%s: %s", msg, zmsg);
+}
+
+/* Create a compression buffer. */
+
+struct buffer *
+compress_buffer_initialize (buf, input, level, memory)
+ struct buffer *buf;
+ int input;
+ int level;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct compress_buffer *n;
+ int zstatus;
+
+ n = (struct compress_buffer *) xmalloc (sizeof *n);
+ memset (n, 0, sizeof *n);
+
+ n->buf = buf;
+
+ if (input)
+ zstatus = inflateInit (&n->zstr);
+ else
+ zstatus = deflateInit (&n->zstr, level);
+ if (zstatus != Z_OK)
+ compress_error (1, zstatus, &n->zstr, "compression initialization");
+
+ /* There may already be data buffered on BUF. For an output
+ buffer, this is OK, because these routines will just use the
+ buffer routines to append data to the (uncompressed) data
+ already on BUF. An input buffer expects to handle a single
+ buffer_data of buffered input to be uncompressed, so that is OK
+ provided there is only one buffer. At present that is all
+ there ever will be; if this changes, compress_buffer_input must
+ be modified to handle multiple input buffers. */
+ assert (! input || buf->data == NULL || buf->data->next == NULL);
+
+ return buf_initialize (input ? compress_buffer_input : NULL,
+ input ? NULL : compress_buffer_output,
+ input ? NULL : compress_buffer_flush,
+ compress_buffer_block,
+ (input
+ ? compress_buffer_shutdown_input
+ : compress_buffer_shutdown_output),
+ memory,
+ n);
+}
+
+/* Input data from a compression buffer. */
+
+static int
+compress_buffer_input (closure, data, need, size, got)
+ void *closure;
+ char *data;
+ int need;
+ int size;
+ int *got;
+{
+ struct compress_buffer *cb = (struct compress_buffer *) closure;
+ struct buffer_data *bd;
+
+ if (cb->buf->input == NULL)
+ abort ();
+
+ /* We use a single buffer_data structure to buffer up data which
+ the z_stream structure won't use yet. We can safely store this
+ on cb->buf->data, because we never call the buffer routines on
+ cb->buf; we only call the buffer input routine, since that
+ gives us the semantics we want. As noted in
+ compress_buffer_initialize, the buffer_data structure may
+ already exist, and hold data which was already read and
+ buffered before the decompression began. */
+ bd = cb->buf->data;
+ if (bd == NULL)
+ {
+ bd = ((struct buffer_data *) malloc (sizeof (struct buffer_data)));
+ if (bd == NULL)
+ return -2;
+ bd->text = (char *) malloc (BUFFER_DATA_SIZE);
+ if (bd->text == NULL)
+ {
+ free (bd);
+ return -2;
+ }
+ bd->bufp = bd->text;
+ bd->size = 0;
+ cb->buf->data = bd;
+ }
+
+ cb->zstr.avail_out = size;
+ cb->zstr.next_out = (Bytef *) data;
+
+ while (1)
+ {
+ int zstatus, sofar, status, nread;
+
+ /* First try to inflate any data we already have buffered up.
+ This is useful even if we don't have any buffered data,
+ because there may be data buffered inside the z_stream
+ structure. */
+
+ cb->zstr.avail_in = bd->size;
+ cb->zstr.next_in = (Bytef *) bd->bufp;
+
+ do
+ {
+ zstatus = inflate (&cb->zstr, Z_NO_FLUSH);
+ if (zstatus == Z_STREAM_END)
+ break;
+ if (zstatus != Z_OK && zstatus != Z_BUF_ERROR)
+ {
+ compress_error (0, zstatus, &cb->zstr, "inflate");
+ return EIO;
+ }
+ } while (cb->zstr.avail_in > 0
+ && cb->zstr.avail_out > 0);
+
+ bd->size = cb->zstr.avail_in;
+ bd->bufp = (char *) cb->zstr.next_in;
+
+ if (zstatus == Z_STREAM_END)
+ return -1;
+
+ /* If we have obtained NEED bytes, then return, unless NEED is
+ zero and we haven't obtained anything at all. If NEED is
+ zero, we will keep reading from the underlying buffer until
+ we either can't read anything, or we have managed to
+ inflate at least one byte. */
+ sofar = size - cb->zstr.avail_out;
+ if (sofar > 0 && sofar >= need)
+ break;
+
+ /* All our buffered data should have been processed at this
+ point. */
+ assert (bd->size == 0);
+
+ /* This will work well in the server, because this call will
+ do an unblocked read and fetch all the available data. In
+ the client, this will read a single byte from the stdio
+ stream, which will cause us to call inflate once per byte.
+ It would be more efficient if we could make a call which
+ would fetch all the available bytes, and at least one byte. */
+
+ status = (*cb->buf->input) (cb->buf->closure, bd->text,
+ need > 0 ? 1 : 0,
+ BUFFER_DATA_SIZE, &nread);
+ if (status != 0)
+ return status;
+
+ /* If we didn't read anything, then presumably the buffer is
+ in nonblocking mode, and we should just get out now with
+ whatever we've inflated. */
+ if (nread == 0)
+ {
+ assert (need == 0);
+ break;
+ }
+
+ bd->bufp = bd->text;
+ bd->size = nread;
+ }
+
+ *got = size - cb->zstr.avail_out;
+
+ return 0;
+}
+
+/* Output data to a compression buffer. */
+
+static int
+compress_buffer_output (closure, data, have, wrote)
+ void *closure;
+ const char *data;
+ int have;
+ int *wrote;
+{
+ struct compress_buffer *cb = (struct compress_buffer *) closure;
+
+ cb->zstr.avail_in = have;
+ cb->zstr.next_in = (unsigned char *) data;
+
+ while (cb->zstr.avail_in > 0)
+ {
+ char buffer[BUFFER_DATA_SIZE];
+ int zstatus;
+
+ cb->zstr.avail_out = BUFFER_DATA_SIZE;
+ cb->zstr.next_out = (unsigned char *) buffer;
+
+ zstatus = deflate (&cb->zstr, Z_NO_FLUSH);
+ if (zstatus != Z_OK)
+ {
+ compress_error (0, zstatus, &cb->zstr, "deflate");
+ return EIO;
+ }
+
+ if (cb->zstr.avail_out != BUFFER_DATA_SIZE)
+ buf_output (cb->buf, buffer,
+ BUFFER_DATA_SIZE - cb->zstr.avail_out);
+ }
+
+ *wrote = have;
+
+ /* We will only be here because buf_send_output was called on the
+ compression buffer. That means that we should now call
+ buf_send_output on the underlying buffer. */
+ return buf_send_output (cb->buf);
+}
+
+/* Flush a compression buffer. */
+
+static int
+compress_buffer_flush (closure)
+ void *closure;
+{
+ struct compress_buffer *cb = (struct compress_buffer *) closure;
+
+ cb->zstr.avail_in = 0;
+ cb->zstr.next_in = NULL;
+
+ while (1)
+ {
+ char buffer[BUFFER_DATA_SIZE];
+ int zstatus;
+
+ cb->zstr.avail_out = BUFFER_DATA_SIZE;
+ cb->zstr.next_out = (unsigned char *) buffer;
+
+ zstatus = deflate (&cb->zstr, Z_SYNC_FLUSH);
+
+ /* The deflate function will return Z_BUF_ERROR if it can't do
+ anything, which in this case means that all data has been
+ flushed. */
+ if (zstatus == Z_BUF_ERROR)
+ break;
+
+ if (zstatus != Z_OK)
+ {
+ compress_error (0, zstatus, &cb->zstr, "deflate flush");
+ return EIO;
+ }
+
+ if (cb->zstr.avail_out != BUFFER_DATA_SIZE)
+ buf_output (cb->buf, buffer,
+ BUFFER_DATA_SIZE - cb->zstr.avail_out);
+
+ /* If the deflate function did not fill the output buffer,
+ then all data has been flushed. */
+ if (cb->zstr.avail_out > 0)
+ break;
+ }
+
+ /* Now flush the underlying buffer. Note that if the original
+ call to buf_flush passed 1 for the BLOCK argument, then the
+ buffer will already have been set into blocking mode, so we
+ should always pass 0 here. */
+ return buf_flush (cb->buf, 0);
+}
+
+/* The block routine for a compression buffer. */
+
+static int
+compress_buffer_block (closure, block)
+ void *closure;
+ int block;
+{
+ struct compress_buffer *cb = (struct compress_buffer *) closure;
+
+ if (block)
+ return set_block (cb->buf);
+ else
+ return set_nonblock (cb->buf);
+}
+
+/* Shut down an input buffer. */
+
+static int
+compress_buffer_shutdown_input (closure)
+ void *closure;
+{
+ struct compress_buffer *cb = (struct compress_buffer *) closure;
+ int zstatus;
+
+ /* Pick up any trailing data, such as the checksum. */
+ while (1)
+ {
+ int status, nread;
+ char buf[100];
+
+ status = compress_buffer_input (cb, buf, 0, sizeof buf, &nread);
+ if (status == -1)
+ break;
+ if (status != 0)
+ return status;
+ }
+
+ zstatus = inflateEnd (&cb->zstr);
+ if (zstatus != Z_OK)
+ {
+ compress_error (0, zstatus, &cb->zstr, "inflateEnd");
+ return EIO;
+ }
+
+ return buf_shutdown (cb->buf);
+}
+
+/* Shut down an output buffer. */
+
+static int
+compress_buffer_shutdown_output (closure)
+ void *closure;
+{
+ struct compress_buffer *cb = (struct compress_buffer *) closure;
+ int zstatus, status;
+
+ do
+ {
+ char buffer[BUFFER_DATA_SIZE];
+
+ cb->zstr.avail_out = BUFFER_DATA_SIZE;
+ cb->zstr.next_out = (unsigned char *) buffer;
+
+ zstatus = deflate (&cb->zstr, Z_FINISH);
+ if (zstatus != Z_OK && zstatus != Z_STREAM_END)
+ {
+ compress_error (0, zstatus, &cb->zstr, "deflate finish");
+ return EIO;
+ }
+
+ if (cb->zstr.avail_out != BUFFER_DATA_SIZE)
+ buf_output (cb->buf, buffer,
+ BUFFER_DATA_SIZE - cb->zstr.avail_out);
+ } while (zstatus != Z_STREAM_END);
+
+ zstatus = deflateEnd (&cb->zstr);
+ if (zstatus != Z_OK)
+ {
+ compress_error (0, zstatus, &cb->zstr, "deflateEnd");
+ return EIO;
+ }
+
+ status = buf_flush (cb->buf, 1);
+ if (status != 0)
+ return status;
+
+ return buf_shutdown (cb->buf);
+}
+
+
+
+/* Here is our librarified gzip implementation. It is very minimal
+ but attempts to be RFC1952 compliant. */
+
+/* BUF should contain SIZE bytes of gzipped data (RFC1952/RFC1951).
+ We are to uncompress the data and write the result to the file
+ descriptor FD. If something goes wrong, give a nonfatal error message
+ mentioning FULLNAME as the name of the file for FD. Return 1 if
+ it is an error we can't recover from. */
+
+int
+gunzip_and_write (fd, fullname, buf, size)
+ int fd;
+ char *fullname;
+ unsigned char *buf;
+ size_t size;
+{
+ size_t pos;
+ z_stream zstr;
+ int zstatus;
+ unsigned char outbuf[32768];
+ unsigned long crc;
+
+ if (buf[0] != 31 || buf[1] != 139)
+ {
+ error (0, 0, "gzipped data does not start with gzip identification");
+ return 1;
+ }
+ if (buf[2] != 8)
+ {
+ error (0, 0, "only the deflate compression method is supported");
+ return 1;
+ }
+
+ /* Skip over the fixed header, and then skip any of the variable-length
+ fields. */
+ pos = 10;
+ if (buf[3] & 4)
+ pos += buf[pos] + (buf[pos + 1] << 8) + 2;
+ if (buf[3] & 8)
+ pos += strlen (buf + pos) + 1;
+ if (buf[3] & 16)
+ pos += strlen (buf + pos) + 1;
+ if (buf[3] & 2)
+ pos += 2;
+
+ memset (&zstr, 0, sizeof zstr);
+ /* Passing a negative argument tells zlib not to look for a zlib
+ (RFC1950) header. This is an undocumented feature; I suppose if
+ we wanted to be anal we could synthesize a header instead,
+ but why bother? */
+ zstatus = inflateInit2 (&zstr, -15);
+
+ if (zstatus != Z_OK)
+ compress_error (1, zstatus, &zstr, fullname);
+
+ /* I don't see why we should have to include the 8 byte trailer in
+ avail_in. But I see that zlib/gzio.c does, and it seemed to fix
+ a fairly rare bug in which we'd get a Z_BUF_ERROR for no obvious
+ reason. */
+ zstr.avail_in = size - pos;
+ zstr.next_in = buf + pos;
+
+ crc = crc32 (0, NULL, 0);
+
+ do
+ {
+ zstr.avail_out = sizeof (outbuf);
+ zstr.next_out = outbuf;
+ zstatus = inflate (&zstr, Z_NO_FLUSH);
+ if (zstatus != Z_STREAM_END && zstatus != Z_OK)
+ {
+ compress_error (0, zstatus, &zstr, fullname);
+ return 1;
+ }
+ if (write (fd, outbuf, sizeof (outbuf) - zstr.avail_out) < 0)
+ {
+ error (0, errno, "writing decompressed file %s", fullname);
+ return 1;
+ }
+ crc = crc32 (crc, outbuf, sizeof (outbuf) - zstr.avail_out);
+ } while (zstatus != Z_STREAM_END);
+ zstatus = inflateEnd (&zstr);
+ if (zstatus != Z_OK)
+ compress_error (0, zstatus, &zstr, fullname);
+
+ if (crc != (buf[zstr.total_in + 10]
+ + (buf[zstr.total_in + 11] << 8)
+ + (buf[zstr.total_in + 12] << 16)
+ + (buf[zstr.total_in + 13] << 24)))
+ {
+ error (0, 0, "CRC error uncompressing %s", fullname);
+ return 1;
+ }
+
+ if (zstr.total_out != (buf[zstr.total_in + 14]
+ + (buf[zstr.total_in + 15] << 8)
+ + (buf[zstr.total_in + 16] << 16)
+ + (buf[zstr.total_in + 17] << 24)))
+ {
+ error (0, 0, "invalid length uncompressing %s", fullname);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Read all of FD and put the gzipped data (RFC1952/RFC1951) into *BUF,
+ replacing previous contents of *BUF. *BUF is malloc'd and *SIZE is
+ its allocated size. Put the actual number of bytes of data in
+ *LEN. If something goes wrong, give a nonfatal error mentioning
+ FULLNAME as the name of the file for FD, and return 1 if we can't
+ recover from it). LEVEL is the compression level (1-9). */
+
+int
+read_and_gzip (fd, fullname, buf, size, len, level)
+ int fd;
+ char *fullname;
+ unsigned char **buf;
+ size_t *size;
+ size_t *len;
+ int level;
+{
+ z_stream zstr;
+ int zstatus;
+ unsigned char inbuf[8192];
+ int nread;
+ unsigned long crc;
+
+ if (*size < 1024)
+ {
+ unsigned char *newbuf;
+
+ *size = 1024;
+ newbuf = realloc (*buf, *size);
+ if (newbuf == NULL)
+ {
+ error (0, 0, "out of memory");
+ return 1;
+ }
+ *buf = newbuf;
+ }
+ (*buf)[0] = 31;
+ (*buf)[1] = 139;
+ (*buf)[2] = 8;
+ (*buf)[3] = 0;
+ (*buf)[4] = (*buf)[5] = (*buf)[6] = (*buf)[7] = 0;
+ /* Could set this based on level, but why bother? */
+ (*buf)[8] = 0;
+ (*buf)[9] = 255;
+
+ memset (&zstr, 0, sizeof zstr);
+ zstatus = deflateInit2 (&zstr, level, Z_DEFLATED, -15, 8,
+ Z_DEFAULT_STRATEGY);
+ crc = crc32 (0, NULL, 0);
+ if (zstatus != Z_OK)
+ {
+ compress_error (0, zstatus, &zstr, fullname);
+ return 1;
+ }
+ zstr.avail_out = *size;
+ zstr.next_out = *buf + 10;
+
+ while (1)
+ {
+ int finish = 0;
+
+ nread = read (fd, inbuf, sizeof inbuf);
+ if (nread < 0)
+ {
+ error (0, errno, "cannot read %s", fullname);
+ return 1;
+ }
+ else if (nread == 0)
+ /* End of file. */
+ finish = 1;
+ crc = crc32 (crc, inbuf, nread);
+ zstr.next_in = inbuf;
+ zstr.avail_in = nread;
+
+ do
+ {
+ size_t offset;
+
+ /* I don't see this documented anywhere, but deflate seems
+ to tend to dump core sometimes if we pass it Z_FINISH and
+ a small (e.g. 2147 byte) avail_out. So we insist on at
+ least 4096 bytes (that is what zlib/gzio.c uses). */
+
+ if (zstr.avail_out < 4096)
+ {
+ unsigned char *newbuf;
+
+ offset = zstr.next_out - *buf;
+ *size *= 2;
+ newbuf = realloc (*buf, *size);
+ if (newbuf == NULL)
+ {
+ error (0, 0, "out of memory");
+ return 1;
+ }
+ *buf = newbuf;
+ zstr.next_out = *buf + offset;
+ zstr.avail_out = *size - offset;
+ }
+
+ zstatus = deflate (&zstr, finish ? Z_FINISH : 0);
+ if (zstatus == Z_STREAM_END)
+ goto done;
+ else if (zstatus != Z_OK)
+ compress_error (0, zstatus, &zstr, fullname);
+ } while (zstr.avail_out == 0);
+ }
+ done:
+ *(*buf + zstr.total_out + 10) = crc & 0xff;
+ *(*buf + zstr.total_out + 11) = (crc >> 8) & 0xff;
+ *(*buf + zstr.total_out + 12) = (crc >> 16) & 0xff;
+ *(*buf + zstr.total_out + 13) = (crc >> 24) & 0xff;
+
+ *(*buf + zstr.total_out + 14) = zstr.total_in & 0xff;
+ *(*buf + zstr.total_out + 15) = (zstr.total_in >> 8) & 0xff;
+ *(*buf + zstr.total_out + 16) = (zstr.total_in >> 16) & 0xff;
+ *(*buf + zstr.total_out + 17) = (zstr.total_in >> 24) & 0xff;
+
+ *len = zstr.total_out + 18;
+
+ zstatus = deflateEnd (&zstr);
+ if (zstatus != Z_OK)
+ compress_error (0, zstatus, &zstr, fullname);
+
+ return 0;
+}
+#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */