summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--contrib/cvs/BUGS262
-rw-r--r--contrib/cvs/COPYING249
-rw-r--r--contrib/cvs/COPYING.LIB481
-rw-r--r--contrib/cvs/ChangeLog1162
-rw-r--r--contrib/cvs/ChangeLog.zoo700
-rw-r--r--contrib/cvs/FAQ14
-rw-r--r--contrib/cvs/HACKING102
-rw-r--r--contrib/cvs/INSTALL344
-rw-r--r--contrib/cvs/MINOR-BUGS60
-rw-r--r--contrib/cvs/Makefile.in264
-rw-r--r--contrib/cvs/NEWS907
-rw-r--r--contrib/cvs/PROJECTS59
-rw-r--r--contrib/cvs/README222
-rw-r--r--contrib/cvs/TODO423
-rw-r--r--contrib/cvs/acconfig.h12
-rw-r--r--contrib/cvs/config.h.in210
-rwxr-xr-xcontrib/cvs/configure3186
-rw-r--r--contrib/cvs/configure.in191
-rw-r--r--contrib/cvs/contrib/ChangeLog193
-rw-r--r--contrib/cvs/contrib/Makefile.in134
-rw-r--r--contrib/cvs/contrib/README106
-rw-r--r--contrib/cvs/contrib/clmerge.pl152
-rw-r--r--contrib/cvs/contrib/cln_hist.pl92
-rw-r--r--contrib/cvs/contrib/commit_prep.pl216
-rw-r--r--contrib/cvs/contrib/cvs_acls.pl143
-rw-r--r--contrib/cvs/contrib/cvscheck.man53
-rw-r--r--contrib/cvs/contrib/cvscheck.sh84
-rw-r--r--contrib/cvs/contrib/cvshelp.man562
-rw-r--r--contrib/cvs/contrib/descend.man115
-rw-r--r--contrib/cvs/contrib/descend.sh116
-rw-r--r--contrib/cvs/contrib/dirfns.shar481
-rw-r--r--contrib/cvs/contrib/intro.doc112
-rw-r--r--contrib/cvs/contrib/log.pl169
-rw-r--r--contrib/cvs/contrib/log_accum.pl560
-rw-r--r--contrib/cvs/contrib/mfpipe.pl88
-rw-r--r--contrib/cvs/contrib/rcs-to-cvs.sh185
-rw-r--r--contrib/cvs/contrib/rcs2log.sh592
-rw-r--r--contrib/cvs/contrib/rcs2sccs.sh143
-rw-r--r--contrib/cvs/contrib/rcslock.pl235
-rw-r--r--contrib/cvs/contrib/sccs2rcs.csh277
-rw-r--r--contrib/cvs/cvs-format.el81
-rw-r--r--contrib/cvs/doc/ChangeLog569
-rw-r--r--contrib/cvs/doc/ChangeLog.fsf38
-rw-r--r--contrib/cvs/doc/Makefile.in203
-rw-r--r--contrib/cvs/doc/cvs-paper.ms1073
-rw-r--r--contrib/cvs/doc/cvs.texinfo7738
-rw-r--r--contrib/cvs/doc/cvsclient.texi824
-rwxr-xr-xcontrib/cvs/install-sh238
-rw-r--r--contrib/cvs/lib/ChangeLog448
-rw-r--r--contrib/cvs/lib/ChangeLog.fsf90
-rw-r--r--contrib/cvs/lib/Makefile.in164
-rw-r--r--contrib/cvs/lib/argmatch.c89
-rw-r--r--contrib/cvs/lib/dup2.c40
-rw-r--r--contrib/cvs/lib/fnmatch.c193
-rw-r--r--contrib/cvs/lib/fnmatch.h45
-rw-r--r--contrib/cvs/lib/ftruncate.c76
-rw-r--r--contrib/cvs/lib/getdate.y1001
-rw-r--r--contrib/cvs/lib/getline.c125
-rw-r--r--contrib/cvs/lib/getline.h15
-rw-r--r--contrib/cvs/lib/getopt.c759
-rw-r--r--contrib/cvs/lib/getopt.h131
-rw-r--r--contrib/cvs/lib/getopt1.c187
-rw-r--r--contrib/cvs/lib/getwd.c35
-rw-r--r--contrib/cvs/lib/hostname.c49
-rw-r--r--contrib/cvs/lib/md5.c277
-rw-r--r--contrib/cvs/lib/md5.h31
-rw-r--r--contrib/cvs/lib/memmove.c57
-rw-r--r--contrib/cvs/lib/mkdir.c129
-rw-r--r--contrib/cvs/lib/regex.c4952
-rw-r--r--contrib/cvs/lib/regex.h490
-rw-r--r--contrib/cvs/lib/rename.c84
-rw-r--r--contrib/cvs/lib/savecwd.c141
-rw-r--r--contrib/cvs/lib/savecwd.h20
-rw-r--r--contrib/cvs/lib/sighandle.c414
-rw-r--r--contrib/cvs/lib/strdup.c43
-rw-r--r--contrib/cvs/lib/strerror.c813
-rw-r--r--contrib/cvs/lib/strippath.c80
-rw-r--r--contrib/cvs/lib/stripslash.c44
-rw-r--r--contrib/cvs/lib/strstr.c40
-rw-r--r--contrib/cvs/lib/strtoul.c100
-rw-r--r--contrib/cvs/lib/system.h468
-rw-r--r--contrib/cvs/lib/valloc.c25
-rw-r--r--contrib/cvs/lib/vasprintf.c171
-rw-r--r--contrib/cvs/lib/wait.h32
-rw-r--r--contrib/cvs/lib/waitpid.c76
-rw-r--r--contrib/cvs/lib/xgetwd.c79
-rw-r--r--contrib/cvs/lib/yesno.c42
-rw-r--r--contrib/cvs/man/ChangeLog141
-rw-r--r--contrib/cvs/man/Makefile.in96
-rw-r--r--contrib/cvs/man/cvs.12181
-rw-r--r--contrib/cvs/man/cvs.5325
-rw-r--r--contrib/cvs/man/cvsbug.8269
-rwxr-xr-xcontrib/cvs/mkinstalldirs32
-rw-r--r--contrib/cvs/src/ChangeLog1373
-rw-r--r--contrib/cvs/src/ChangeLog-9194524
-rw-r--r--contrib/cvs/src/ChangeLog-93953731
-rw-r--r--contrib/cvs/src/Makefile.in198
-rw-r--r--contrib/cvs/src/NOTES60
-rw-r--r--contrib/cvs/src/README-rm-add31
-rw-r--r--contrib/cvs/src/add.c514
-rw-r--r--contrib/cvs/src/admin.c168
-rw-r--r--contrib/cvs/src/checkin.c188
-rw-r--r--contrib/cvs/src/checkout.c889
-rw-r--r--contrib/cvs/src/classify.c493
-rw-r--r--contrib/cvs/src/client.c4490
-rw-r--r--contrib/cvs/src/client.h191
-rw-r--r--contrib/cvs/src/commit.c1824
-rw-r--r--contrib/cvs/src/create_adm.c140
-rw-r--r--contrib/cvs/src/cvs.h690
-rwxr-xr-xcontrib/cvs/src/cvsbug.sh528
-rw-r--r--contrib/cvs/src/cvsrc.c150
-rw-r--r--contrib/cvs/src/diff.c623
-rw-r--r--contrib/cvs/src/edit.c1020
-rw-r--r--contrib/cvs/src/edit.h42
-rw-r--r--contrib/cvs/src/entries.c548
-rw-r--r--contrib/cvs/src/error.c256
-rw-r--r--contrib/cvs/src/error.h47
-rw-r--r--contrib/cvs/src/expand_path.c241
-rw-r--r--contrib/cvs/src/fileattr.c517
-rw-r--r--contrib/cvs/src/fileattr.h125
-rw-r--r--contrib/cvs/src/filesubr.c662
-rw-r--r--contrib/cvs/src/find_names.c271
-rw-r--r--contrib/cvs/src/hash.c442
-rw-r--r--contrib/cvs/src/hash.h58
-rw-r--r--contrib/cvs/src/history.c1484
-rw-r--r--contrib/cvs/src/ignore.c405
-rw-r--r--contrib/cvs/src/import.c1207
-rw-r--r--contrib/cvs/src/lock.c639
-rw-r--r--contrib/cvs/src/log.c163
-rw-r--r--contrib/cvs/src/login.c392
-rw-r--r--contrib/cvs/src/logmsg.c521
-rw-r--r--contrib/cvs/src/main.c814
-rw-r--r--contrib/cvs/src/mkmodules.c742
-rw-r--r--contrib/cvs/src/modules.c900
-rw-r--r--contrib/cvs/src/myndbm.c288
-rw-r--r--contrib/cvs/src/myndbm.h47
-rw-r--r--contrib/cvs/src/no_diff.c129
-rw-r--r--contrib/cvs/src/options.h.in275
-rw-r--r--contrib/cvs/src/parseinfo.c162
-rw-r--r--contrib/cvs/src/patch.c604
-rw-r--r--contrib/cvs/src/rcs.c2262
-rw-r--r--contrib/cvs/src/rcs.h104
-rw-r--r--contrib/cvs/src/rcscmds.c188
-rw-r--r--contrib/cvs/src/recurse.c714
-rw-r--r--contrib/cvs/src/release.c286
-rw-r--r--contrib/cvs/src/remove.c200
-rw-r--r--contrib/cvs/src/repos.c147
-rw-r--r--contrib/cvs/src/root.c177
-rw-r--r--contrib/cvs/src/rtag.c682
-rw-r--r--contrib/cvs/src/run.c541
-rwxr-xr-xcontrib/cvs/src/sanity.sh2869
-rw-r--r--contrib/cvs/src/scramble.c246
-rw-r--r--contrib/cvs/src/server.c4642
-rw-r--r--contrib/cvs/src/server.h138
-rw-r--r--contrib/cvs/src/status.c279
-rw-r--r--contrib/cvs/src/subr.c318
-rw-r--r--contrib/cvs/src/tag.c781
-rw-r--r--contrib/cvs/src/update.c1830
-rw-r--r--contrib/cvs/src/update.h21
-rw-r--r--contrib/cvs/src/vers_ts.c354
-rw-r--r--contrib/cvs/src/version.c29
-rw-r--r--contrib/cvs/src/watch.c521
-rw-r--r--contrib/cvs/src/watch.h56
-rw-r--r--contrib/cvs/src/wrapper.c374
-rw-r--r--contrib/cvs/stamp-h.in1
-rw-r--r--contrib/cvs/tools/ChangeLog7
-rw-r--r--contrib/cvs/tools/Makefile.in76
-rw-r--r--contrib/cvs/tools/README5
-rw-r--r--contrib/cvs/tools/pcl-cvs/ChangeLog891
-rw-r--r--contrib/cvs/tools/pcl-cvs/INSTALL94
-rw-r--r--contrib/cvs/tools/pcl-cvs/Makefile.in238
-rw-r--r--contrib/cvs/tools/pcl-cvs/NEWS149
-rw-r--r--contrib/cvs/tools/pcl-cvs/README25
-rw-r--r--contrib/cvs/tools/pcl-cvs/pcl-cvs-lucid.el134
-rw-r--r--contrib/cvs/tools/pcl-cvs/pcl-cvs-startup.el17
-rw-r--r--contrib/cvs/tools/pcl-cvs/pcl-cvs.el3450
-rw-r--r--contrib/cvs/tools/pcl-cvs/pcl-cvs.texinfo1565
177 files changed, 93736 insertions, 0 deletions
diff --git a/contrib/cvs/BUGS b/contrib/cvs/BUGS
new file mode 100644
index 0000000..fc6af32
--- /dev/null
+++ b/contrib/cvs/BUGS
@@ -0,0 +1,262 @@
+To report bugs send mail to bug-cvs@prep.ai.mit.edu, or run the "cvsbug"
+program and fill out the template:
+
+ $ cvsbug
+
+The "cvsbug" program is installed in the same location as the "cvs"
+program. If your installation failed, you may need to run "cvsbug"
+directly out of the "src" directory as "src/cvsbug.sh". This is also
+the procedure for submitting suggested changes to CVS--note that all
+submitted changes may be distributed under the terms of the GNU Public
+License, so if you don't like this, don't submit them.
+
+
+
+* `cvs checkout -d nested/dir/path <module>' just doesn't work. The
+ simpler version -- `cvs checkout -d single-dir <module>' works,
+ however.
+
+
+* CVS leaves .#mumble files around when a conflict occurs. (Note:
+ this is intentional and is documented in doc/cvs.texinfo. Of course
+ whether it is a good idea is a separate question).
+
+
+* pcl-cvs doesn't like it when you try to check in a file which isn't
+ up-to-date. The messages produced by the server perhaps don't match
+ what pcl-cvs is looking for.
+
+
+* From: Roland McGrath <roland@gnu.ai.mit.edu>
+ To: Cyclic CVS Hackers <info-cvs@prep.ai.mit.edu>
+ Subject: weird bug
+ Date: Sat, 25 Mar 1995 16:41:41 -0500
+ X-Windows: Even your dog won't like it.
+
+ I just noticed some droppings on my disk from what must be a pretty weird
+ bug in remote CVS.
+
+ In my home directory on a repository machine I use, I find:
+
+ drwxr-xr-x 4 roland staff 512 Mar 7 14:08 cvs-serv28962
+ drwxr-xr-x 4 roland staff 512 Mar 7 14:11 cvs-serv28978
+ drwxr-xr-x 4 roland staff 512 Mar 7 15:13 cvs-serv29141
+
+ OK, so these are leftover cruft from some cvs run that got aborted.
+ Well, it should clean up after itself, but so what.
+
+ The last one is pretty dull; the real weirdness is the contents of the
+ first two directories.
+
+ duality 77 # ls -RF cvs-serv28978/
+ CVS/ cvs-serv28978/
+
+ cvs-serv28978/CVS:
+ Entries Repository
+
+ cvs-serv28978/cvs-serv28978:
+ arpa/
+
+ cvs-serv28978/cvs-serv28978/arpa:
+ CVS/ cvs-serv28978/
+
+ cvs-serv28978/cvs-serv28978/arpa/CVS:
+ Entries Repository
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978:
+ assert/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert:
+ CVS/ cvs-serv28978/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/CVS:
+ Entries Repository
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978:
+ bare/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare:
+ CVS/ cvs-serv28978/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/CVS:
+ Entries Repository
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978:
+ conf/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf:
+ CVS/ cvs-serv28978/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/CVS:
+ Entries Repository
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978:
+ crypt/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt:
+ CVS/ cvs-serv28978/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/CVS:
+ Entries Repository
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978:
+ csu/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu:
+ CVS/ cvs-serv28978/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/CVS:
+ Entries Repository
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978:
+ ctype/
+
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype:
+ CVS/ cvs-serv28978/
+
+ [...]
+
+ ls: cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype/cvs-serv28978/dirent/cvs-serv28978/elf/cvs-serv28978/gnu/cvs-serv28978/gnulib/cvs-serv28978/grp/cvs-serv28978/hurd/cvs-serv28978/hurd/hurd/cvs-serv28978/inet/cvs-serv28978/inet/arpa/cvs-serv28978/inet/netinet[...]/cvs-serv28978/posix/cvs-serv28978/posix/glob/cvs-serv28978/posix/gnu/cvs-serv28978/posix/sys/cvs-serv28978/protocols/cvs-serv28978/pwd/cvs-serv28978/resolv/cvs-serv28978/resolv/arpa/cvs-serv28978/resolv/sys/cvs-serv28978/resource/cvs-serv28978/resource/sys/cvs-serv28978/rpc/cvs-serv28978/setjmp/cvs-serv28978/signal/cvs-serv28978/signal/sys/cvs-serv28978/socket/cvs-serv28978/socket: File name too long
+ cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype/cvs-serv28978/dirent/cvs-serv28978/elf/cvs-serv28978/gnu/cvs-serv28978/gnulib/cvs-serv28978/grp/cvs-serv28978/hurd/cvs-serv28978/hurd/hurd/cvs-serv28978/inet/cvs-serv28978/inet/arpa/cvs-serv28978/inet/netinet[...]/cvs-serv28978/posix/glob/cvs-serv28978/posix/gnu/cvs-serv28978/posix/sys/cvs-serv28978/protocols/cvs-serv28978/pwd/cvs-serv28978/resolv/cvs-serv28978/resolv/arpa/cvs-serv28978/resolv/sys/cvs-serv28978/resource/cvs-serv28978/resource/sys/cvs-serv28978/rpc/cvs-serv28978/setjmp/cvs-serv28978/signal/cvs-serv28978/signal/sys/cvs-serv28978/socket/cvs-serv28978:
+
+
+* From: billr@mpd.tandem.com (Bill Robertson)
+ Subject: Problem with rtag and the -D option
+ Date: Fri, 17 Mar 1995 10:53:29 -0600 (CST)
+
+ I have been trying to use the -D option to specify a date for tagging, but
+ rtag does not recognize the -D option. It is documented to do so and I've
+ tested the use of -D with cvs update and cvs diff and it works fine there.
+
+
+* We need some version numbers really badly. Are there some
+ (and Charles Hannum is just not including them in his reports), or do
+ we simply have no reliable way to distinguish between the various
+ versions of rCVS people on the list are running?
+
+ Now that I think of it, version numbers present a problem when
+ people can update their sources anytime and rebuild. I think the
+ solution is to increment a minor version number *every* time a bug is
+ fixed, so we can identify uniquely what someone is running when they
+ submit a report. This implies recording the version increments in the
+ ChangeLog; that way we can just look to see where a particular version
+ lies in relation to the flow of changing code.
+
+ Should we be doing same with Guppy? I guess not -- it's only
+ important when you have people who are updating directly from your
+ development tree, which is the case with the remote-cvs folks.
+
+ Thoughts?
+
+
+* (Charles Hannum <mycroft@ai.mit.edu>) has these bugs:
+
+ I just tossed remote CVS at a fairly large source tree that I already
+ had, and noticed a few problems:
+
+ 1) server.c assumes that /usr/tmp is a valid default for the place to
+ store files uploaded from the client. There are a number of systems
+ that now use /var/tmp. These should probably be detected by autoconf.
+
+ 2) The server deals rather ungracefully with the tmp directory
+ becoming full.
+
+ 3) There's some oddness with relative paths in Repository files that
+ causes the directory prefix to be added twice; e.g. if I have CVSROOT
+ set to `machine:/this/dir', and I try to update in a directory whose
+ Repository file says `src/bin', the server looks in
+ `/this/dir/machine:/this/dir/src/bin'.
+
+* From: "Charles M. Hannum" <mycroft@ai.mit.edu>
+ To: jimb@duality.gnu.ai.mit.edu, roland@duality.gnu.ai.mit.edu
+ Subject: Serious flaw in remote CVS
+ Date: Wed, 22 Feb 1995 20:54:36 -0500
+
+ I just found a major flaw in the current implementation. Because the
+ sockets are not changed to non-blocking mode, write(2)s can hang. In
+ some cases, this happens on both sides at the same time, with the
+ socket buffers full in both directions. This causes a deadlock,
+ because both processes are stuck in I/O wait and thus never drain
+ their input queues.
+
+ Until this is fixed, I can't use it. I'll look at the problem myself
+ at some point, but I don't know when.
+
+
+ From: "Charles M. Hannum" <mycroft@ai.mit.edu>
+ To: info-cvs@prep.ai.mit.edu
+ Cc: jimb@totoro.bio.indiana.edu
+ Subject: Re: forwarded message from Charles M. Hannum
+ Date: Wed, 22 Feb 1995 22:07:07 -0500
+
+ FYI, this happened because the tmp directory on the server became
+ full. Somehow the server started interpreting the files the client
+ was sending as commands, and started spewing tons of errors.
+ Apparently the errors are sent with blocking I/O, or something, and
+ thus allowed the deadlock to happen.
+
+
+* From: "Charles M. Hannum" <mycroft@ai.mit.edu>
+ To: info-cvs@prep.ai.mit.edu
+ Subject: Regarding that relative path problem
+ Date: Thu, 23 Feb 1995 02:41:51 -0500
+
+ This is actually more serious. If you have `bar.com:/foo' as your CVS
+ root directory, then:
+
+ 1) When you check things out, the Repository files will contain
+ `/foo/...' (i.e. without the machine name), which makes little sense.
+
+ 2) If you instead have a relative path, when the Repository file is
+ read, `bar.com:/foo' is prepended. This is sent to the server, but
+ confuses it, because it's not expecting the machine name to be
+ prepended.
+
+ A slightly klugy fix would be to have the client prepend the machine
+ name when writing a new Repository file, and strip it off before
+ sending one to the server. This would be backward-compatible with the
+ current arrangement.
+
+
+* From: "Charles M. Hannum" <mycroft@ai.mit.edu>
+ To: info-cvs@prep.ai.mit.edu
+ Subject: Still one more bug
+ Date: Sat, 25 Feb 1995 17:01:15 -0500
+
+ mycroft@duality [1]; cd /usr/src/lib/libc
+ mycroft@duality [1]; cvs diff -c2 '-D1 day ago' -Dnow
+ cvs server: Diffing .
+ cvs server: Diffing DB
+ cvs [server aborted]: could not chdir to DB: No such file or directory
+ mycroft@duality [1];
+
+ `DB' is an old directory, which no longer has files in it, and is
+ removed automatically when I use the `-P' option to checkout.
+
+ This error doesn't occur when run locally.
+
+ P.S. Is anyone working on fixing these bugs?
+
+
+* From: Roland McGrath <roland@gnu.ai.mit.edu>
+ To: Cyclic CVS Hackers <info-cvs@prep.ai.mit.edu>
+ Subject: bizarre failure mode
+ Date: Tue, 7 Mar 95 14:17:28 -0500
+
+ This is pretty weird:
+
+ CVS_SERVER='TMPDIR=. /usr/local/bin/cvs' ../cvs-build/src/cvs update -q
+ cvs [server aborted]: could not get working directory: Result too large
+ [Exit 1]
+ asylum 29 % grep 'Result too large' /usr/include/sys/errno.h
+ #define ERANGE 34 /* Result too large */
+
+ Now, getcwd fails with ERANGE when the buffer is too small. But I don't
+ know why that would be the case; I don't think there are exceptionally long
+ directory names involved. It would be robust to notice ERANGE and use a
+ bigger buffer. But I suspect something weirder is going on.
+
+ The repository in question in duality.gnu.ai.mit.edu:/gd4/gnu/cvsroot/libc.
+
+ Send me a PGP-signed message if you want the password to use the machine
+ where the problem showed up.
diff --git a/contrib/cvs/COPYING b/contrib/cvs/COPYING
new file mode 100644
index 0000000..9a17037
--- /dev/null
+++ b/contrib/cvs/COPYING
@@ -0,0 +1,249 @@
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 1, February 1989
+
+ Copyright (C) 1989 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The license agreements of most software companies try to keep users
+at the mercy of those companies. By contrast, our General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. The
+General Public License applies to the Free Software Foundation's
+software and to any other program whose authors commit to using it.
+You can use it for your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Specifically, the General Public License is designed to make
+sure that you have the freedom to give away or sell copies of free
+software, that you receive source code or can get it if you want it,
+that you can change the software or use pieces of it in new free
+programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of a such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must tell them their rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any program or other work which
+contains a notice placed by the copyright holder saying it may be
+distributed under the terms of this General Public License. The
+"Program", below, refers to any such program or work, and a "work based
+on the Program" means either the Program or any work containing the
+Program or a portion of it, either verbatim or with modifications. Each
+licensee is addressed as "you".
+
+ 1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this
+General Public License and to the absence of any warranty; and give any
+other recipients of the Program a copy of this General Public License
+along with the Program. You may charge a fee for the physical act of
+transferring a copy.
+
+ 2. You may modify your copy or copies of the Program or any portion of
+it, and copy and distribute such modifications under the terms of Paragraph
+1 above, provided that you also do the following:
+
+ a) cause the modified files to carry prominent notices stating that
+ you changed the files and the date of any change; and
+
+ b) cause the whole of any work that you distribute or publish, that
+ in whole or in part contains the Program or any part thereof, either
+ with or without modifications, to be licensed at no charge to all
+ third parties under the terms of this General Public License (except
+ that you may choose to grant warranty protection to some or all
+ third parties, at your option).
+
+ c) If the modified program normally reads commands interactively when
+ run, you must cause it, when started running for such interactive use
+ in the simplest and most usual way, to print or display an
+ announcement including an appropriate copyright notice and a notice
+ that there is no warranty (or else, saying that you provide a
+ warranty) and that users may redistribute the program under these
+ conditions, and telling the user how to view a copy of this General
+ Public License.
+
+ d) You may charge a fee for the physical act of transferring a
+ copy, and you may at your option offer warranty protection in
+ exchange for a fee.
+
+Mere aggregation of another independent work with the Program (or its
+derivative) on a volume of a storage or distribution medium does not bring
+the other work under the scope of these terms.
+
+ 3. You may copy and distribute the Program (or a portion or derivative of
+it, under Paragraph 2) in object code or executable form under the terms of
+Paragraphs 1 and 2 above provided that you also do one of the following:
+
+ a) accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of
+ Paragraphs 1 and 2 above; or,
+
+ b) accompany it with a written offer, valid for at least three
+ years, to give any third party free (except for a nominal charge
+ for the cost of distribution) a complete machine-readable copy of the
+ corresponding source code, to be distributed under the terms of
+ Paragraphs 1 and 2 above; or,
+
+ c) accompany it with the information you received as to where the
+ corresponding source code may be obtained. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form alone.)
+
+Source code for a work means the preferred form of the work for making
+modifications to it. For an executable file, complete source code means
+all the source code for all modules it contains; but, as a special
+exception, it need not include source code for modules which are standard
+libraries that accompany the operating system on which the executable
+file runs, or for standard header files or definitions files that
+accompany that operating system.
+
+ 4. You may not copy, modify, sublicense, distribute or transfer the
+Program except as expressly provided under this General Public License.
+Any attempt otherwise to copy, modify, sublicense, distribute or transfer
+the Program is void, and will automatically terminate your rights to use
+the Program under this License. However, parties who have received
+copies, or rights to use copies, from you under this General Public
+License will not have their licenses terminated so long as such parties
+remain in full compliance.
+
+ 5. By copying, distributing or modifying the Program (or any work based
+on the Program) you indicate your acceptance of this license to do so,
+and all its terms and conditions.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the original
+licensor to copy, distribute or modify the Program subject to these
+terms and conditions. You may not impose any further restrictions on the
+recipients' exercise of the rights granted herein.
+
+ 7. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of the license which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+the license, you may choose any version ever published by the Free Software
+Foundation.
+
+ 8. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to humanity, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+ To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19xx name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the
+appropriate parts of the General Public License. Of course, the
+commands you use may be called something other than `show w' and `show
+c'; they could even be mouse-clicks or menu items--whatever suits your
+program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ program `Gnomovision' (a program to direct compilers to make passes
+ at assemblers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/contrib/cvs/COPYING.LIB b/contrib/cvs/COPYING.LIB
new file mode 100644
index 0000000..eb685a5
--- /dev/null
+++ b/contrib/cvs/COPYING.LIB
@@ -0,0 +1,481 @@
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/contrib/cvs/ChangeLog b/contrib/cvs/ChangeLog
new file mode 100644
index 0000000..ad187ad
--- /dev/null
+++ b/contrib/cvs/ChangeLog
@@ -0,0 +1,1162 @@
+Sun May 5 17:38:21 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ Integrated changes submitted by Ian Taylor <ian@cygnus.com>
+
+ * update.c (update_dirent_proc): cvs co -p doesn't print
+ anything when run from an empty directory.
+
+ * import.c (import_descend_dir): Check for a file in the
+ repository which will be checked out to the same name as the
+ directory.
+
+Sun May 5 15:49:00 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * configure.in: autoconf 2.9 handles AC_CHECK_LIB in a
+ way that it can not be used to check for main(). Check
+ for printf() instead. (Reported by ian@cygnus.com)
+
+ * configure: Regenerated.
+
+Thu May 2 13:34:37 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * Version 1.7.88
+
+Thu May 2 10:42:13 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Clarify what happened to examples directory.
+
+Thu May 2 02:06:49 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * INSTALL: Updated for NeXTSTEP 3.3 (1.7)
+
+Thu May 2 01:40:55 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * Compatibility fixes affecting QNX, NetBSD, and SCO
+
+ * configure.in (AC_CHECK_FUNCS): Added check for initgroups(),
+ (ac_cv_func_crypt) Added check for crypt() in -lcrypt;
+ define AUTH_SERVER_SUPPORT only if crypt() is found.
+
+ * configure: Regenerated.
+
+ * src/server.c (HAVE_INITGROUPS): Use initgroups() only if
+ located by configure.
+
+Wed May 1 15:38:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Remove item about reserving all-uppercase tag names.
+
+Fri Apr 19 11:22:35 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * Version 1.7.86
+
+Sun Apr 14 11:06:44 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * configure.in (AC_OUTPUT): generate contrib/elib/Makefile,
+ tools/Makefile, and tools/pcl-cvs/Makefile. Do not any longer
+ generate contrib/pcl-cvs/Makefile.
+
+ * Makefile.in: deal w/ above changes.
+
+ * configure: regenerated.
+
+ * Added `tools' subdir (pcl-cvs will live there, as will other
+ things maintained along with the CVS distribution).
+
+Wed Apr 10 17:15:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Mention documentation and A4 paper in particular.
+
+Thu Mar 28 12:31:38 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Add "cvs annotate".
+
+Tue Mar 26 10:46:59 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: In example, change tag name to avoid using a tag name
+ reserved to CVS.
+
+ * NEWS: Document reservation of some tag names.
+
+Fri Mar 22 10:45:23 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Clarify that RCS is only for server or local.
+
+Mon Mar 18 10:15:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Mention info@cyclic.com where we mention support
+ contracts, not at the end where people might be tempted to view it
+ as a generic help line.
+
+Thu Mar 14 16:34:26 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (stamp-h): Don't run ./config.status --recheck.
+
+Thu Mar 14 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * cvsnt.mak: Regenerate dependencies.
+
+Thu Mar 14 13:45:11 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * configure.in (AC_OUTPUT): Don't create examples/Makefile; we're
+ not using the examples directory any more.
+
+Wed Mar 13 17:02:00 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Refer to cvs.texinfo rather than out-of-date cvsinit
+ instructions. Instead of telling everyone to update modules
+ whenever adding directories (which is optional), refer to the
+ manual regarding all administrative files. Revise "make check"
+ instructions to be even less encouraging about submitting bug
+ reports.
+
+ * examples/*: Removed.
+ * Makefile.in (SUBDIRS): Remove examples.
+ * cvsinit.sh: Removed.
+ * Makefile.in: Remove all cvsinit and PROGS stuff.
+ * NEWS: Mention cvsinit -> cvs init change.
+
+Mon Mar 11 13:12:35 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * BUGS: removed previous description from Greg Woods (3/6/96)
+ since the bug seems to be corrected
+
+Wed Mar 6 10:35:32 1996 Greg A. Woods <woods@most.weird.com>
+
+ * BUGS: describe a weird core-dump with 'cvs co -c'. Now I can't
+ even get a stack backtrace again -- dbx dumps core!
+
+Fri Mar 1 09:21:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README.VMS: Remove distribution information (since it is no
+ longer different for VMS). Various wording fixes to reflect the
+ fact that using rsh is just one of several ways to connect to a
+ cvs server, not "the official" one. Say that the unsuitable rsh
+ is the UCX one. Clarify what rsh uses privileged ports for.
+
+Fri Mar 1 01:26:28 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * README.VMS, build.com: Added for VMS.
+
+Thu Feb 29 10:04:20 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Mention change to default ignore list.
+
+Thu Feb 29 00:28:08 1996 Peter Wemm <peter@jhome.DIALix.COM>
+
+ * configure.in: correctly spell FNM_PATHNAME in fnmatch() test,
+ the supplied test fails on proposed POSIX.2, lib/fnmatch.*, Linux,
+ FreeBSD, etc.
+ * configure: Regenerated.
+
+Tue Feb 27 10:43:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Change submission address to bug-cvs from info-cvs.
+ Encourage submissions to be in the form of diffs to INSTALL.
+
+Sun Feb 25 15:23:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * HACKING: Fix typo.
+
+Fri Feb 23 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * cvsnt.mak: Add login.c and scramble.c.
+
+Fri Feb 23 16:36:11 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Mention comp.software.config-mgmt. Don't mention old
+ cyclic-cvs mailing list.
+
+ * acconfig.h: Add AUTH_SERVER_SUPPORT. Remove DIFF and GREP (no
+ longer used).
+ * configure.in: Define AUTH_SERVER_SUPPORT.
+ * config.h.in, configure: Regenerated.
+
+Thu Feb 22 22:32:09 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in: Remove AC_FUNC_ALLOCA.
+ * configure: Regenerated.
+
+Mon Feb 19 09:39:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * HACKING: Add comments about portability and assert().
+
+Thu Feb 15 16:40:13 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Mention $USER internal variable.
+
+Thu Feb 15 14:00:00 1996 Gary Oberbrunner <garyo@avs.com>
+ and Jim Kingdon <kingdon@cyclic.com>
+
+ * cvsnt.mak: Add vasprintf.c and mkmodules.c
+
+Tue Feb 13 20:05:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in (AC_REPLACE_FUNCS): Add strtoul.
+ * configure: Regenerated.
+
+Mon Feb 12 10:06:27 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * TODO: Remove mkmodules stuff.
+ * NEWS: Add item concerning mkmodules.
+
+ * configure.in (AC_REPLACE_FUNCS): Add vasprintf.
+ * configure: Regenerated.
+
+Sun Feb 11 16:43:38 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * Makefile.in (DISTFILES): added HACKING.
+
+Sun Feb 11 12:38:51 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Revise *info files feature (now user vars, not env vars).
+
+Fri Feb 9 23:51:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Mention env var in *info files feature.
+
+Fri Feb 9 02:41:50 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (DISTFILES): Remove config.sub and config.guess from
+ the list; they're not distributed any more.
+
+Thu Feb 1 19:47:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Remove RM; no longer used.
+
+Thu Feb 1 14:38:04 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * configure: re-ran autoconf.
+
+ * Makefile.in (USOURCE_SUBDIRS, SUBDIRS): abstract unix source
+ subdirs to new var USOURCE_SUBDIRS, for lint's sake and possibly
+ etags's someday.
+ (lint): run in USOURCE_SUBDIRS only.
+
+Thu Feb 1 13:06:47 1996 Roland McGrath <roland@baalperazim.frob.com>
+
+ * configure.in (WITH_KRB4): Escape $ in help text.
+
+Wed Jan 31 19:03:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * HACKING: Add info about NEWS file and release process.
+
+Tue Jan 30 16:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * cvsnt.mak: Change save-cwd.c to savecwd.c and regenerate
+ dependencies to take care of save-cwd.h.
+ * windows-NT/README: Update information about Visual C++ 4.0.
+
+Tue Jan 30 16:09:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Rename lib/save-cwd.c to lib/savecwd.c. Avoiding a hyphen
+ seems to be the only way to get Visual C++ 2.1 to generate a
+ cvsnt.mak which Visual C++ 4.0 will accept.
+ * Rename lib/save-cwd.h to lib/savecwd.h for consistency.
+ * os2/Makefile.in, lib/Makefile.in, lib/savecwd.c, src/add.c,
+ src/import.c, src/modules.c, src/recurse.c, src/tag.c: Update
+ accordingly.
+
+ * INSTALL, os2/options.h, windows-NT/options.h,
+ macintosh/options.h, src/options.h.in: Remove SORT; it is no
+ longer used.
+
+Mon Jan 29 15:16:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Mention -b. Don't talk about RCS 5.6.[5-7] beta
+ releases; this will be an issue for few if any people. Remove
+ stuff about diff and --with-diffutils which is no longer true.
+
+ * README: Refer to HACKING file. Refer to cvs.texinfo not
+ manpage. Rewrite section about compatibility between CVS versions.
+ * HACKING: New file.
+ * INSTALL: Move -Wall section to HACKING; refer to HACKING.
+
+Wed Jan 24 20:26:55 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in: Remove diff stuff. Also remove AC_CANONICAL_HOST
+ and bindir crud as that was the only place they were used.
+ * config.h.in, configure: Regenerated.
+ * config.sub, config.guess: Removed.
+ * src/options.h.in (DIFF): Change to "diff" and change comment to tell
+ people not to use -a.
+ * src/sanity.sh: New test binfiles tests for above-fixed bug (see
+ comments in patch_file in update.c--passing -a to diff generates a
+ patch which patch cannot apply).
+
+ * NEWS: Adjust to reflect existence of 1.7.
+
+Tue Jan 23 14:20:39 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * devel-cvs: New file, not to be included in the distribution.
+
+Thu Jan 18 21:46:56 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * BUGS: Remove all mention of the outdated cyclic-cvs@cyclic.com
+ and remote-cvs@cyclic.com addresses. It turns out that people see
+ these addresses and use them. Mention the proper way to report
+ bugs.
+
+Wed Jan 17 16:40:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Fix typo (info-cvs-requests -> info-cvs-request).
+
+Fri Jan 12 13:38:12 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in, configure: Revert "Checking user's gender" change.
+ Sure, you only live once, but I want mine to be a *long* life, not
+ one interrupted by a CVS user who is not amused coming after me
+ with an axe.
+
+Fri Jan 12 12:46:23 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * configure: regenerated.
+
+ * configure.in: print "Checking user's gender... ok". I mean,
+ what the heck, you only live once.
+
+Thu Jan 11 14:00:00 1996 Jim Kingdon <peary.cyclic.com>
+
+ * cvsnt.mak: Update dependencies.
+
+Thu Jan 11 12:03:10 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * NEWS: document loss of CVS_NOADMIN. Also, mention the
+ possibility to use "cvs" in .cvsrc.
+
+Wed Jan 10 20:40:23 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * configure: regenerated.
+
+ * configure.in (AC_OUTPUT): added `macintosh/Makefile'.
+
+ * Makefile.in (SUBDIRS): added `macintosh'.
+
+Wed Jan 10 01:17:18 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Remove URL of obsolete David Zuhn web page.
+
+ * FAQ: Replace entire file with short paragraph explaining the FAQ
+ is dead.
+
+ * configure.in: Don't set exec_prefix. Set bindir from prefix if
+ exec_prefix isn't set.
+ * configure: Regenerated.
+
+ * INSTALL: Update list of machines for 1.6.85 (further changes to
+ the list of machines will not receive ChangeLog entries).
+
+Tue Jan 9 09:02:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Mention changes in default ignore list.
+
+ * INSTALL: check.log is not in /tmp/cvs-sanity. Mention
+ submitting bug reports as a possibility, not a request from us.
+ Separate out "make check" a bit to make clear it is optional.
+
+Mon Jan 8 11:42:40 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Remove grep stuff; no longer necessary.
+ Don't say that patch must understand unidiffs; no longer true.
+ Suggest configuring with -Wall (here until we have a "how to hack
+ CVS document").
+
+Wed Jan 3 19:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * .cvsignore: Add cvsnt.vcp.
+
+Mon Jan 1 22:45:50 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * os2/Makefile.in (Makefile), windows-NT/Makefile.in (Makefile):
+ New rules.
+
+Sun Dec 31 16:52:49 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * NEWS: add a blurb about password authentication.
+
+Sun Dec 31 16:16:38 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Add "submissions will be distributed under the GPL"
+ language (like the newspapers have for letters to the editor).
+
+Thu Dec 21 16:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * cvsnt.mak: Revert to an old version, then add in recent changes
+ to lists of files (using Visual C++; not by hand editing--this way
+ it can be used as an internal project not just an external one).
+
+Tue Dec 19 17:13:14 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Mention -kb (strictly speaking a bugfix, not a new
+ feature, I guess, but it seems worth mentioning anyway).
+
+Tue Dec 19 17:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * TODO: Remove "regular TODO list:" line which accidentally got
+ checked in.
+
+Mon Dec 18 18:59:30 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (TAR_VERBOSE): Default to empty, not "v". I don't
+ want that whole long list of files any more than jimb's daily
+ update script does.
+
+Sun Dec 17 23:59:11 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in (AC_REPLACE_FUNCS): Remove vasprintf.
+ * configure: Regenerated.
+
+Sat Dec 16 17:19:45 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in (AC_REPLACE_FUNCS): Add vasprintf.
+ * configure: Regenerated.
+
+Mon Nov 20 14:19:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * TODO: Remove items about developer communications; they are done.
+ * NEWS: Mention developer communication features.
+ * cvsinit.sh: Also add notify file.
+
+Mon Dec 11 22:44:58 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * New subdir "macintosh", for Mike Ladwig's
+ <mike@twinpeaks.prc.com> port-in-progress.
+
+Thu Dec 7 14:32:49 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (check): Make sure library is built before running
+ make in src.
+ (remotecheck): Likewise.
+ (installcheck): Likewise.
+
+Wed Dec 6 11:40:37 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * configure.in: Remove leading -l from first argument of
+ AC_CHECK_LIB for -lkrb and -ldes checks.
+
+Mon Dec 4 08:06:31 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * config.h.in: Regenerated.
+
+Sun Dec 3 20:05:10 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in: Remove grep stuff.
+ * configure: Regenerated.
+
+Fri Dec 1 11:16:18 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * configure, config.h.in: re-ran autoconf
+
+ * configure.in (AC_CHECK_HEADERS): add sys/resource.h to list of
+ tested headers
+
+ * Makefile.in (DISTFILES): add config.sub and config.guess
+
+Thu Nov 23 09:01:53 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * TODO: Remove item about doc describing undoing a change; it
+ already does.
+
+Sun Nov 19 18:12:36 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (dist): Pull out the 'v' in the tar command to a
+ variable, so I can disable it in my daily update script.
+
+Tue Nov 14 18:31:36 1995 Greg A. Woods <woods@most.weird.com>
+
+ * cvsinit.sh:
+ - new rcs id
+ - new opening comment
+ - read only one "word" for CVSROOT
+ - add checkoutlist, cvswrappers, taginfo, wrap, & unwrap to
+ examples install loop, special handling for latter....
+ - don't do any special stuff for loginfo -- always comment out
+ everything in the newly installed examples
+ - add a wee message to suggest editing newly installed examples
+ - tweak some more comments, esp. regarding install of contrib
+ scripts....
+ - make $CVSROOT/CVROOT/history group writable if it didn't exist
+ as it's not very useful otherwise
+
+Tue Nov 14 15:22:25 1995 Greg A. Woods <woods@most.weird.com>
+
+ * cvsinit.sh: woops! wasn't installing contrib/log!
+
+Tue Nov 14 12:09:11 1995 Greg A. Woods <woods@most.weird.com>
+
+ * INSTALL: oops, missed a couple of things about "configure"
+
+ * configure: re-ran autoconf
+
+Tue Nov 14 11:06:25 1995 Greg A. Woods <woods@most.weird.com>
+
+ * config.guess, config.sub: first time in (from autoconf-2.4)
+
+ * configure.in:
+ - updated to work with autoconf-2.4
+ - call AC_CANONICAL_HOST to get host OS type right (needs
+ config.sub and config.guess)
+ - added full support for --with-diffutils and --with-gnugrep
+ - fixed the diff search to work almost like the one for RCS-5.7
+ - fixed some quoting problems
+
+ * README: mention optional 'make check' step
+
+ * INSTALL:
+ - updated notes about working SunOS versions
+ - re-wrote notes about RCS, diffutils, etc.
+ - added notes about configuring with GNU diffutils and GNU grep
+ - added notes about using 'make check'
+ - changed bug reporiting instructions to mention cvsbug
+ - re-wrote notes about setting CVSROOT in shell startups
+
+Fri Nov 3 11:11:16 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README: Fix typo in URL of molli's web site.
+
+Tue Oct 31 19:28:16 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * testing something, please ignore.
+
+Mon Oct 23 18:37:27 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * configure: re-ran autoconf.
+
+ * configure.in (AC_OUTPUT): os2/Makefile.
+
+ * Makefile.in (SUBDIRS): added os2 subdir.
+
+Mon Oct 23 12:02:51 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvsnt.mak: added lib/getline.c
+
+Fri Oct 20 17:04:55 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvsnt.mak: added src/expand_path.c, error.[ch] now in src
+
+Thu Oct 19 16:26:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Remove note about RCS 5.7 and log messages
+ consisting only of whitespace; fixed in CVS on 11 Jul 95.
+
+Tue Oct 17 17:57:23 1995 Warren Jones <wjones@tc.fluke.com>
+
+ * man/cvs.5, examples/modules: Document -e.
+
+Tue Oct 10 16:34:25 1995 Thorsten Lockert <tholo@sigmasoft.com>
+
+ * configure.in: More crud looking for kerberos, this time for 4.4BSD.
+ * configure: Regenerated.
+
+Sun Oct 8 12:22:19 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * configure.in: check for POSIX and BSD style reliable signals
+ * configure: regenerated by autoconf
+ * config.h.in: regenerated by autoheader
+
+Fri Oct 6 21:50:48 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ There is little point in trying to share a file as trivial as
+ lib/error.c between programs. So just admit it is CVS specific:
+ * lib/error.c: Move from here...
+ * src/error.c: ...to here, and remove CVS_SUPPORT ifdefs.
+ * lib/error.h: Move from here...
+ * src/error.h: ...to here. Remove CVS_SUPPORT
+ ifdefs; remove unused variable error_message_count.
+ * src/Makefile.in (OBJECTS): Add error.o.
+ (SOURCES): Add error.c.
+ (HEADERS): Add error.h.
+ * lib/Makefile.in (OBJECTS): Remove error.o.
+ (SOURCES): Remove error.c.
+ (HEADERS): Remove error.h.
+ * acconfig.h, configure.in: Remove CVS_SUPPORT.
+ * configure, config.h.in: Rebuilt using autoconf and autoheader.
+ * windows-NT/config.h: Remove CVS_SUPPORT.
+
+Thu Oct 5 17:26:38 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Mention Siemens-Nixdorf RM600.
+
+Tue Oct 3 09:32:19 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * NEWS: Remove item about -f global option; it is old news already
+ mentioned elsewhere in the file.
+
+Mon Oct 2 18:12:15 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * FAQ: Updated for CVS 1.5. And now 1.6 is almost out. The FAQ
+ always lags the package, sigh...
+
+Mon Oct 2 18:10:35 1995 Larry Jones <larry.jones@sdrc.com>
+
+ * configure, config.h.in: Rebuilt using autoconf and autoheader.
+
+ * configure.in: check for <sys/bsdtypes.h>; used by src/server.c.
+ (ISC keeps all the stuff that BSD has in <sys/types.h> here, so
+ we need it for the FD_SET stuff for select().)
+ Moved check for gethostname() after check for connect() since if
+ connect() is not found, we may add librariesd and gethostname()
+ may well be in one of those libraries.
+ If connect() isn't found, look in -linet (ISC) in addition to
+ -lsocket and -lnsl. Also, ignore the cache since we need to
+ update LIBS reguardless of whether it was found before or not and
+ the answer may well be different afterwards.
+ Define CLIENT_SUPPORT and SERVER_SUPPORT only if connect() is
+ found.
+
+ * INSTALL: update info for ISC 4.0.1; renumber footnotes.
+
+Mon Oct 2 17:01:07 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Indicate CVS version tested with Solaris 2.4.
+
+Mon Oct 2 10:42:37 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * (configure): Re-ran autoconf.
+
+Mon Oct 2 10:33:58 1995 Michael Finken <finken@conware.de>
+
+ * configure.in: AC_REPLACE `strstr'.
+
+Sun Oct 1 23:22:28 1995 Bryan O'Sullivan <bos@serpentine.com>
+
+ * (INSTALL): noted that CVS works fine on Solaris 2.4 with both
+ gcc and SPARCworks cc.
+
+Sun Oct 1 18:48:19 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * (configure): re-ran autoconf following Peter Wemm's change
+ below.
+
+Sun Oct 1 22:24:56 1995 Peter Wemm <peter@haywire.dialix.com>
+
+ * configure.in: more extensive searching for -lsocket and -lnsl
+ as done in Taylor-UUCP 1.06
+
+Sun Oct 1 15:32:01 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * (configure): re-ran autoconf.
+
+Sun Oct 1 11:35:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * TODO: Remove item about setting comment leader automatically;
+ RCS 5.7 does this.
+
+Wed Sep 27 15:34:04 1995 Peter Wemm <peter@haywire.dialix.com>
+
+ * configure.in: correct detection of GNU diff's -a option for
+ src/options.h
+ * configure: regenerate with autoconf
+
+Fri Sep 22 14:29:31 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * TODO: Remove item about reindenting on the way in and out.
+ wrappers provide this functionality.
+
+Wed Sep 20 14:27:28 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * configure.in: #define the symbols DIFF and GREP to be the paths
+ to the DIFF and GREP programs; their values will be edited into
+ src/options.h (and config.h, coincidentally).
+ * acconfig.h (DIFF, GREP): Add these.
+ * configure, config.h.in: Rebuilt using autoconf and autoheader.
+
+Sun Sep 10 21:38:05 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * TODO: CVS can already undo a change, suggest documenting how.
+ Expand slightly on mode stuff.
+ Remove item about not letting people check out into repository (it
+ is done).
+ Redo item about expanding env vars in *info to reflect current
+ thinking.
+ Remove item about making it hard to accidentally move tags; it is
+ done.
+ Add client/server note to suggestion regarding interactive merging.
+
+Fri Sep 1 12:07:02 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * BUGS: Remove items about refetching unpatchable files and options.h.
+
+Fri Sep 1 09:20:09 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (DISTFILES): Remove cvsnt.vcp; it's been deleted.
+
+Thu Aug 31 13:47:35 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (stamp-h): Rebuild config.status before trying to
+ use it to build config.h.
+
+ * Makefile.in: Change "cd foo; make" to "cd foo && make";
+ otherwise we get into an infinite loop if an objdir doesn't exist.
+
+Thu Aug 31 11:07:06 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * configure.in: Arrange not to touch options.h if we haven't
+ modified it. AC_CONFIG_HEADER checks if the file is unmodified,
+ whereas AC_OUTPUT doesn't, and they're otherwise identical, so...
+ (AC_CONFIG_HEADER): ... mention src/options.h here...
+ (AC_OUTPUT): ... not here.
+ Copy src/options.h to src/options.h-SAVED, don't move it.
+ Otherwise, configure will create it again every time.
+ Remove the code to compare the new src/options.h with
+ src/options.h-SAVED and move it back if it's unchanged; autoconf
+ writes that for us now.
+
+Wed Aug 30 18:45:28 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * .cvsignore: Ignore WinDebug and WinRel directories, used by
+ Microsoft Visual C++ to store object files and executables.
+
+ * acconfig.h (CVS_SUPPORT, CLIENT_SUPPORT, SERVER_SUPPORT): New
+ symbols, which autoheader will use to build config.h.in from
+ configure.in.
+ * configure.in (SERVER_SUPPORT, CLIENT_SUPPORT): Remove spaces
+ between AC_DEFINEs and opening parens of argument lists. Oops.
+ * configure: Rebuild using autoconf.
+ * config.h.in: Rebuild using autoheader.
+
+ * Makefile.in (SUBDIRS): Uncomment windows-NT.
+
+ * INSTALL: Added Windows NT to list of supported platforms.
+ Added Windows NT installation instructions.
+
+Tue Aug 29 16:08:01 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * cvsnt.mak: Completed Windows NT port.
+
+ * configure.in (SERVER_SUPPORT, CLIENT_SUPPORT): Arrange for these
+ to get #defined. In the config.h file for the Windows NT port, we
+ only #define CLIENT_SUPPORT.
+ * config.h.in (SERVER_SUPPORT, CLIENT_SUPPORT): Add #undefs for
+ these.
+
+ * configure.in (AC_OUTPUT): Build the Makefile for the windows-NT
+ subdirectory too.
+
+ * cvsnt.vcp: Removed. This doesn't store any information needed
+ to compile CVS; it seems to be mostly programmer preference stuff.
+ There's no need to distribute it.
+
+ * INSTALL: Added info about Harris Nighthawk from Steve Allen ---
+ thanks!
+
+Mon Aug 21 16:08:37 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ Bring the saga to a close:
+ * configure.in: Use AC_PROG_MAKE_SET here, to decide whether we
+ need to set the MAKE variable in Makefile.
+ * Makefile.in: Use @SET_MAKE@ here, to set MAKE when appropriate.
+
+Mon Aug 21 15:26:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in: Add comment regarding AC_SET_MAKE.
+
+Sat Aug 19 21:57:51 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * configure.in: Define CVS_SUPPORT, to tell certain library
+ functions that they're part of CVS.
+ * config.h.in: Add #undef for CVS_SUPPORT, for configure to chew
+ on.
+
+Fri Aug 18 22:35:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in: Don't set MAKE; apparently all makes set it and GNU
+ make, at least, will set it to what make was invoked as (perhaps gmake
+ or some such), not just "make" (which might not support VPATH, for
+ example).
+
+Sun Aug 13 23:35:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * INSTALL: Convert Data General entry to same format as other entries.
+
+Sun Aug 13 13:11:36 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * cvs-format.el: Add note about set-c-style.
+
+Thu Aug 3 16:13:29 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * INSTALL: Fixed mail address for updates.
+
+ * INSTALL: Noted that 1.5 runs on SunOS 4.1.1 -- 4.1.3.
+
+Sun Jul 30 20:12:26 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsinit.sh: Unify code for modules and loginfo with code for
+ other files which have checked-out and ,v files in CVSROOT.
+ Don't add "#" to start of lines in rcstemplate.
+
+Sat Jul 29 16:48:05 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvsinit.sh: If arguments are given, give version number and
+ usage message. Make printed messages much more concise.
+
+ * cvsinit.sh: Rename log.pl to log. Don't install log twice.
+
+ * Makefile.in (install-local), contrib/Makefile.in (install):
+ Remove "reminder" to run cvsinit; running cvsinit is not required.
+
+Fri Jul 28 16:46:10 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (SUBDIRS): Comment out windows-NT.
+
+Fri Jul 28 02:27:54 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (DISTFILES): Add cvsnt.mak and cvsnt.vcp.
+ (SUBDIRS): Add windows-NT.
+
+ * config.h.in: Regenerated from configure.in by autoheader.
+
+Wed Jul 19 18:00:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * configure.in (AC_CHECK_HEADERS): Check for <io.h> and <direct.h>.
+
+Tue Jul 18 21:18:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * configure.in (AC_CHECK_HEADERS): Check for sys/param.h; Windows NT
+ doesn't have it.
+
+ * configure.in (AC_CHECK_HEADERS): Check for sys/time.h. If you're
+ using AC_HEADER_TIME, it's best to check for this too.
+
+ * cvsnt.mak: New file --- makefile equivalent for Microsoft Visual C++.
+ Choose this as your project when working with CVS under MSVC++.
+ * cvsnt.vcp: New file --- configuration info for Microsoft Visual C++.
+ * windows-NT: New subdirectory, containing files to be used to
+ build under Microsoft Windows NT.
+
+Wed Jul 12 23:26:24 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in: Remove duplicate install-info rule.
+
+Wed Jul 12 16:00:27 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * Makefile.in (install-local): added rule for install-info, made
+ `install' depend on it.
+
+ * README: correct mailing list addresses.
+ * INSTALL: same.
+
+Wed Jul 12 09:15:02 1995 Jim Meyering (meyering@comco.com)
+
+ * configure.in (gdiff_path): Remove gdiff from the list of programs.
+ SGI's Irix includes a program named gdiff that is an X-based GUI to
+ diff.
+
+ * configure.in: Add check for working fnmatch functions so that
+ systems providing it don't incur the space overhead of linking
+ with the version in lib. Cross compiling builds always use the
+ version in lib.
+
+Tue Jul 11 15:47:20 1995 Greg A. Woods <woods@most.weird.com>
+
+ * configure.in: add some FIXME comments
+ - add a hack to restore src/options.h if AC_OUTPUT() didn't modify
+ it. Note that this does *not* work for config.status, thus one
+ FIXME comment.
+ - add test for #! (to warn about possible failure of perl scripts
+ - add test for diff and grep paths (for src/options.h.in)
+ - fix up handling of src/options.h.in
+ - add checks for PERL_PATH and CSH_PATH (from previous local changes)
+
+Tue Jul 11 14:31:18 1995 Michael Shields <shields@tembel.org>
+
+ * Makefile.in (LDFLAGS): Pick up from configure.
+
+Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * configure: re-ran autoconf-2.4
+
+ * cvsinit.sh: make use of xVERSIONx from the Makefile
+ - get rid of stuff duplicated in examples/* and use that instead
+
+ * Makefile.in: $(VERSION) for cvsinit.sh wasn't set, so get it
+ from src/version.c instead.
+
+ * cvsinit.sh: install two more example CVSROOT control/config
+ files: rcstemplate checkoutlist
+ - install useful scripts from $CVSLIB/contrib too...
+ (from previous local changes)
+
+ * Makefile.in: add another reminder to run 'cvsinit' to update
+ repository(ies) (from previous local changes)
+
+Thu Jul 6 17:53:55 1995 Paul Eggert <eggert@twinsun.com>
+
+ * Makefile.in (mostlyclean-local): Remove $(PROGS).
+
+Sat Jul 1 13:11:41 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.5.1.
+
+Thu Jun 29 01:02:09 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in, configure: cross_compiling gets set to "no", not
+ empty--change test accordingly.
+
+ * Version 1.4.93.
+
+Wed Jun 28 22:33:54 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * lib/Makefile.in, man/Makefile.in, doc/Makefile.in: Comment out
+ rules for configure and config.status, just like in Makefile.in or
+ src/Makefile.in.
+
+Tue Jun 27 19:53:05 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in (AC_REPLACE_FUNCS), configure: Remove fnmatch.
+ * lib/Makefile.in (OBJECTS): Add fnmatch.
+ Avoids buggy Solaris 2.4 libc fnmatch.
+
+ * FAQ: Updated with new version from ftp.odi.com.
+
+Mon Jun 26 15:17:46 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.4.92.
+
+Thu Jun 22 12:45:24 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.4.91.
+
+Wed Jun 21 16:33:04 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * PROJECTS: New file.
+ * Makefile.in (DISTFILES): Add it.
+
+Wed Jun 21 16:12:14 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (FLAGS_TO_PASS): Don't pass INSTALL to sub-makes.
+ The reason for passing it is gone now that we are using autoconf
+ 2.x which will set INSTALL in the sub-makefiles correctly.
+
+Tue Jun 20 18:14:54 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * configure.in, configure: Make sure src directory exists before
+ trying to copy options.h to it.
+
+Mon Jun 19 13:47:20 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in: Add a "remotecheck" target here, for consistency;
+ people shouldn't have to switch to src before running the tests.
+
+Mon Jun 19 10:08:03 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * INSTALL: Update list of machines tested. Remove note about
+ systems missing opendir--this is an autoconf issue, not something
+ installers should have to worry about. Refer to NEWS instead of
+ ChangeLog. No longer "strongly recommend" putting diff -a in
+ options.h.
+
+Fri Jun 16 22:30:03 1995 Jim Kingdon (kingdon@cyclic.com)
+
+ * Version 1.4.90.
+
+ * configure, configure.in (AC_OUTPUT): Add config/pcl-cvs/Makefile.
+
+ * Makefile.in (dist): Rename dist from ccvs-<version> to cvs-<version>.
+
+ * Makefile.in (dist, dist-dir), src/Makefile.in, doc/Makefile.in,
+ examples/Makefile.in, contrib/Makefile.in,
+ contrib/pcl-cvs/Makefile, man/Makefile.in, lib/Makefile.in
+ (dist-dir): Use srcdir where appropriate.
+
+Thu Jun 15 14:33:37 1995 Jim Kingdon (kingdon@cyclic.com)
+
+ * CYCLIC-CVS-FAQ: Removed.
+ * Rename ChangeLog.fsf to NEWS. Add information about changes
+ since 1.4A2.
+ * Makefile.in (DISTFILES): Adjust accordingly.
+ * README: Revise to reflect current status of releases.
+
+Thu Jun 15 12:22:42 1995 Jim Kingdon (kingdon@cyclic.com)
+
+ * TODO: Remove various items already fixed. Revise others.
+
+Thu Jun 15 12:24:45 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * configure.in: Use AC_C_INLINE to handle inline.
+ Reorganized to put compiler and OS checks first so that any
+ special defines they might provide are used in subsequent tests.
+
+ * configure, config.h.in: regenerated with autoconf and
+ autoheader version 2.3.
+
+Thu Jun 8 16:33:51 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * INSTALL (Installation): Disrecommend RCS 5.6.[5-7].
+
+Tue May 30 00:07:15 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (distclean-local): Don't delete config.status here.
+ (distclean): Delete config.status here instead, but only after
+ recursive make invocations. Otherwise, the new dependencies
+ in */Makefile.in on ../config.status led to failure in each sub-make
+ because there is no rule there to make ../config.status.
+ Reported by Jeff Johnson <jbj@brewster.jbj.org>.
+ (realclean): Likewise.
+
+Mon May 29 22:24:28 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * configure.in: Use AC_HEADER_DIRENT instead of AC_DIR_HEADER.
+ Use AC_HEADER_STAT to determine if S_FOO() macros work.
+ Use AC_HEADER_TIME to determine if both <sys/time.h> and <time.h>
+ can be included as recommend by autoconf manual.
+ Remove AC_STRUCT_TM test, as above test is better.
+
+ * configure, config.h.in: regenerated with autoconf and
+ autoheader version 2.3.
+
+Fri Apr 28 14:36:49 1995 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * Makefile.in: Set "all" as default target instead of ".PHONY".
+ Some versions of make will otherwise try building all of the phony
+ targets, in order.
+
+Mon May 1 14:02:42 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * configure.in: Set up src/options.h for the user. Its defaults are
+ usually right.
+ * README, INSTALL: Adjust installation instructions appropriately.
+
+Fri Apr 28 22:31:26 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (DISTFILES): Brought up-to-date.
+ (dist): Rewritten to use dist-dir targets, passing DISTDIR variable.
+ (GZIP, GZIP_EXT): New variables.
+ (dist-dir): New target.
+
+ We don't want to include a file the user has to edit in the
+ distribution.
+ * src/options.h: No longer distributed.
+ * src/options.h.in: Distribute this instead.
+ * INSTALL, README: Installation instructions updated.
+
+Sat Apr 8 19:02:21 1995 Roland McGrath <roland@baalperazim.frob.com>
+
+ * configure.in: Check for fchdir.
+ (connect check): Use AC_CHECK_LIB instead of (obsolete)
+ AC_HAVE_LIBRARY.
+
+Sat Apr 8 14:52:46 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (CFLAGS): Let configure set the default for CFLAGS.
+ Under GCC, we want -g -O.
+
+Wed Feb 8 06:49:49 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Makefile.in (stamp-h): Pass CONFIG_FILES=$@ to config.status so
+ the target is created.
+ * configure.in: Applied `autoupdate' from Autoconf 2.1 to
+ modernize macro usage.
+ (AC_RSH): Call removed. It was obsolete and not doing anything useful.
+ (AC_OUTPUT): Write stamp-h as the Makefile rules expect we will.
+ (AC_TYPE_PID_T): Add this check.
+
+Tue Nov 8 06:26:54 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Add stamp-h.in. Remove it from .cvsignore.
+
+Fri Oct 28 11:50:51 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Makefile.in: Comment out autoconf and autoheader rules.
+
+Tue Oct 25 17:44:13 1994 Ken Raeburn <raeburn@cujo.cygnus.com>
+
+ * Makefile.in (all, install, uninstall): Fail if make in
+ subdirectory fails.
+
+Tue Oct 18 13:26:15 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Makefile.in (FLAGS_TO_PASS): Pass INSTALL*. Add comment about
+ why we need to.
+
+Tue Sep 27 08:27:06 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Makefile.in (SUBDIRS): Reinstate "contrib".
+ * configure.in (AC_OUTPUT): Add contrib/Makefile.
+ * configure: Regenerated.
+
+Tue Sep 27 01:03:59 1994 John Gilmore (gnu@cygnus.com)
+
+ * Makefile.in (SUBDIRS): Comment out "contrib". Since we don't
+ bother to configure it, we shouldn't make it either.
+
+Wed Aug 10 14:52:57 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * Makefile.in (FLAGS_TO_PASS): Don't include LIBS or CFLAGS twice.
+
+ * configure.in: Include waitpid and memmove in AC_REPLACE_FUNCS
+ list. Don't check for memmove separately.
+ * configure: Regenerated.
+ * config.h.in: Regenerated for Mark's change.
+
+Wed Aug 10 14:32:24 1994 Mark Eichin (eichin@cygnus.com)
+
+ * configure.in (KRB4): recognize --with-krb4=path. Also test for
+ krb_get_err_text so src/main.c and src/client.c can deal
+ appropriately.
+
+Tue Aug 9 15:49:07 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * configure.in: Check sizes of `long' and `int', needed for md5
+ code.
+ * acconfig.h: New file. Mention HAVE_KERBEROS, to keep autoheader
+ happy.
+ * configure, config.h.in: Regenerated.
+
+Tue Jul 19 11:23:21 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * configure.in: Check not only that krb.h exists, but that it will
+ actually compile correctly.
+ * configure: Regenerated.
+
+Mon Jul 11 07:04:36 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * configure.in: Add comment re autoheader.
+
+Tue Jun 28 22:09:23 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * configure.in: Only look for -lsocket and -lnsl if we don't
+ already have connect.
+ * configure: Regenerated.
+
+Mon Jun 27 17:21:48 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * configure.in: Correct "krb_libdir" to "${krb_libdir}".
+ * configure: Regenerated.
+
+Fri Jun 3 10:15:24 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * configure.in: Check for -lsocket and -lnsl.
+ * configure: Regenerated.
+
+Fri May 27 18:12:43 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * configure.in: Add valloc to AC_REPLACE_FUNCS. Add getpagesize
+ to AC_HAVE_FUNCS. Check for krb.h and -lkrb. If not found, look
+ in /usr/kerberos if native. If found somewhere, define
+ HAVE_KERBEROS and also look for -ldes. Substitute includeopt.
+ * configure: Regenerated.
+
+Fri Mar 11 13:11:51 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * configure.in: Check for <sys/select.h>; used by src/server.c.
+ * configure: Regenerated.
+
+Sun Jan 9 12:04:15 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * configure.in: Check for timezone function, for NetBSD support.
+ * configure: Regenerated.
+
+Wed Dec 15 18:05:21 1993 david d `zoo' zuhn (zoo@andros.cygnus.com)
+
+ * Makefile.in: add MAKEINFO to MDEFINES, pass down MDEFINES on all
+ recursive make invocations that require it; define
+ INSTALL_PROGRAM and use it; reorganize MDEFINES; set infodir and
+ add to MDEFINES; use YACC instead of BISON
+
+
+Mon Dec 6 17:02:18 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * src/diff.c (diff_fileproc): add support for "cvs diff -N" which
+ allows for adding or removing files via patches.
+
diff --git a/contrib/cvs/ChangeLog.zoo b/contrib/cvs/ChangeLog.zoo
new file mode 100644
index 0000000..a1e1d0e
--- /dev/null
+++ b/contrib/cvs/ChangeLog.zoo
@@ -0,0 +1,700 @@
+Thu Sep 15 14:19:21 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * Makefile.in: define TEXI2DVI, add it to FLAGS_TO_PASS; remove
+ old comments about parameters for DEFS
+
+Wed Jul 13 21:54:46 1994 david d `zoo' zuhn (zoo@monad.armadillo.com)
+
+ * contrib/rcs-to-cvs: rewritten for Bourne shell (thanks to David
+ MacKenzie <djm@cygnus.com>)
+
+Wed Jul 13 21:48:38 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * Makefile.in: Deleted line consisting of only whitespace; it
+ confuses some versions of make.
+
+Mon Jan 24 12:26:47 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * configure.in: check for <sys/select.h> and <ndbm.h>
+
+ * Makefile.in: define YACC and not BISON
+
+Sat Dec 18 00:52:04 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * config.h.in: handle HAVE_SYS_WAIT_H, HAVE_ERRNO_H
+
+ * configure.in: check for memmove, <errno.h>
+
+ * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead
+
+ * configure.in (AC_HAVE_HEADERS): check for <sys/wait.h>
+
+Mon Nov 29 15:05:43 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * lib/Makefile.in, src/Makefile.in (CFLAGS): default to -g.
+
+ * src/log.c (log_fileproc): if a file has been added, but not
+ committed, then say so rather than reporting that nothing is
+ known.
+
+ * src/sanity.el: update for emacs-19.
+
+ * src/RCS-patches, src/README-rm-add: update for rcs-5.6.6.
+
+ * src/Makefile.in: removed some gratuitous diffs from cvs-1.3.
+
+ * src/cvsrc.c: strdup -> xstrdup, malloc -> xmalloc, comment about
+ fgets lossage.
+
+ * configure, configure.in, Makefile.in: support man and doc
+ directories and info and dvi targets.
+
+ * doc/cvs.texinfo: comment out include of gpl.texinfo.
+
+ * doc/Makefile.in: added dvi & info targets.
+
+ * doc/cvsclient.texi: added @setfilename.
+
+ * lib/Makefile.in: remove some extraneous diffs against the
+ patched cvs-1.3.
+
+ * doc/Makefile.in, man/Makefile.in: update for autoconf.
+
+Fri Nov 19 12:56:34 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * Many files: added configure.in, updated configure based on
+ autoconf.
+
+Tue Jun 1 17:02:41 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * configure: add support for alloca and sys/select.h
+
+Wed May 19 19:34:48 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvs-format.el: Don't set c-tab-always-indent.
+
+Mon Mar 22 23:25:33 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * Makefile.in: installcheck: recurse into src directory to run tests
+
+Mon Jan 18 17:21:16 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * Makefile.in (check): recur into src directory in order to pick
+ up the sanity check.
+
+Thu Dec 17 19:41:22 1992 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * Makefile.in: added blank 'dvi' target
+
+Tue Apr 7 15:55:25 1992 Brian Berliner (berliner at sun.com)
+
+ * Changes between CVS 1.3 Beta-3 and official CVS 1.3!
+
+ * A new shell script is provided, "./cvsinit", which can be run at
+ install time to help setup your $CVSROOT area. This can greatly
+ ease your entry into CVS usage.
+
+ * The INSTALL file has been updated to include the machines on
+ which CVS has compiled successfully. I think CVS 1.3 is finally
+ portable. Thanks to all the Beta testers!
+
+ * Support for the "editinfo" file was contributed. This file
+ (located in $CVSROOT/CVSROOT) can be used to specify a special
+ "editor" to run on a per-directory basis within the repository,
+ instead of the usual user's editor. As such, it can verify that
+ the log message entered by the user is of the appropriate form
+ (contains a bugid and test validation, for example).
+
+ * The manual pages cvs(1) and cvs(5) have been updated.
+
+ * The "mkmodules" command now informs you when your modules file
+ has duplicate entries.
+
+ * The "add" command now preserves any per-directory sticky tag when
+ you add a new directory to your checked-out sources.
+
+ * The "admin" command is now a fully recursive interface to the
+ "rcs" program which operates on your checked-out sources. It no
+ longer requires you to specify the full path to the RCS file.
+
+ * The per-file sticky tags can now be effectively removed with
+ "cvs update -A file", even if you had checked out the whole
+ directory with a per-directory sticky tag. This allows a great
+ deal of flexibility in managing the revisions that your checked-out
+ sources are based upon (both per-directory and per-file sticky
+ tags).
+
+ * The "cvs -n commit" command now works, to show which files are
+ out-of-date and will cause the real commit to fail, or which files
+ will fail any pre-commit checks. Also, the "cvs -n import ..."
+ command will now show you what it would've done without actually
+ doing it.
+
+ * Doing "cvs commit modules" to checkin the modules file will no
+ properly run the "mkmodules" program (assuming you have setup your
+ $CVSROOT/CVSROOT/modules file to do so).
+
+ * The -t option in the modules file (which specifies a program to
+ run when you do a "cvs rtag" operation on a module) now gets the
+ symbolic tag as the second argument when invoked.
+
+ * When the source repository is locked by another user, that user's
+ login name will be displayed as the holder of the lock.
+
+ * Doing "cvs checkout module/file.c" now works even if
+ module/file.c is in the Attic (has been removed from main-line
+ development).
+
+ * Doing "cvs commit */Makefile" now works as one would expect.
+ Rather than trying to commit everything recursively, it will now
+ commit just the files specified.
+
+ * The "cvs remove" command is now fully recursive. To schedule a
+ file for removal, all you have to do is "rm file" and "cvs rm".
+ With no arguments, "cvs rm" will schedule all files that have been
+ physically removed for removal from the source repository at the
+ next "cvs commit".
+
+ * The "cvs tag" command now prints "T file" for each file that was
+ tagged by this invocation and "D file" for each file that had the
+ tag removed (as with "cvs tag -d").
+
+ * The -a option has been added to "cvs rtag" to force it to clean
+ up any old, matching tags for files that have been removed (in the
+ Attic) that may not have been touched by this tag operation. This
+ can help keep a consistent view with your tag, even if you re-use
+ it frequently.
+
+Sat Feb 29 16:02:05 1992 Brian Berliner (berliner at sun.com)
+
+ * Changes between CVS 1.3 Beta-2 and CVS 1.3 Beta-3
+
+ * Many portability fixes, thanks to all the Beta testers! With any
+ luck, this Beta release will compile correctly on most anything.
+ Hey, what are we without our dreams.
+
+ * CVS finally has support for doing isolated development on a
+ branch off the current (or previous!) revisions. This is also
+ extremely nice for generating patches for previously released
+ software while development is progressing on the next release.
+ Here's an example of creating a branch to fix a patch with the 2.0
+ version of the "foo" module, even though we are already well into
+ the 3.0 release. Do:
+
+ % cvs rtag -b -rFOO_2_0 FOO_2_0_Patch foo
+ % cvs checkout -rFOO_2_0_Patch foo
+ % cd foo
+ [[ hack away ]]
+ % cvs commit
+
+ A physical branch will be created in the RCS file only when you
+ actually commit the change. As such, forking development at some
+ random point in time is extremely light-weight -- requiring just a
+ symbolic tag in each file until a commit is done. To fork
+ development at the currently checked out sources, do:
+
+ % cvs tag -b Personal_Hack
+ % cvs update -rPersonal_Hack
+ [[ hack away ]]
+ % cvs commit
+
+ Now, if you decide you want the changes made in the Personal_Hack
+ branch to be merged in with other changes made in the main-line
+ development, you could do:
+
+ % cvs commit # to make Personal_Hack complete
+ % cvs update -A # to update sources to main-line
+ % cvs update -jPersonal_Hack # to merge Personal_Hack
+
+ to update your checked-out sources, or:
+
+ % cvs checkout -jPersonal_Hack module
+
+ to checkout a fresh copy.
+
+ To support this notion of forked development, CVS reserves
+ all even-numbered branches for its own use. In addition, CVS
+ reserves the ".0" and ".1" branches. So, if you intend to do your
+ own branches by hand with RCS, you should use odd-numbered branches
+ starting with ".3", as in "1.1.3", "1.1.5", 1.2.9", ....
+
+ * The "cvs commit" command now supports a fully functional -r
+ option, allowing you to commit your changes to a specific numeric
+ revision or symbolic tag with full consistency checks. Numeric
+ tags are useful for bringing your sources all up to some revision
+ level:
+
+ % cvs commit -r2.0
+
+ For symbolic tags, you can only commit to a tag that references a
+ branch in the RCS file. One created by "cvs rtag -b" or from
+ "cvs tag -b" is appropriate (see below).
+
+ * Roland Pesch <pesch@cygnus.com> and K. Richard Pixley
+ <rich@cygnus.com> were kind enough to contribute two new manual
+ pages for CVS: cvs(1) and cvs(5). Most of the new CVS 1.3 features
+ are now documented, with the exception of the new branch support
+ added to commit/rtag/tag/checkout/update.
+
+ * The -j options of checkout/update have been added. The "cvs join"
+ command has been removed.
+
+ With one -j option, CVS will merge the changes made between the
+ resulting revision and the revision that it is based on (e.g., if
+ the tag refers to a branch, CVS will merge all changes made in
+ that branch into your working file).
+
+ With two -j options, 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:
+
+ % cvs update -j1.5 -j1.3 foo.c # note the order...
+
+ In addition, each -j 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, as in:
+
+ -jSymbolic_Tag:Date_Specifier
+
+ An example might be what "cvs import" tells you to do when you have
+ just imported sources that have conflicts with local changes:
+
+ % cvs checkout -jTAG:yesterday -jTAG module
+
+ which tells CVS to merge in the changes made to the branch
+ specified by TAG in the last 24 hours. If this is not what is
+ intended, substitute "yesterday" for whatever format of date that
+ is appropriate, like:
+
+ % cvs checkout -jTAG:'1 week ago' -jTAG module
+
+ * "cvs diff" now supports the special tags "BASE" and "HEAD". So,
+ the command:
+
+ % cvs diff -u -rBASE -rHEAD
+
+ will effectively show the changes made by others (in unidiff
+ format) that will be merged into your working sources with your
+ next "cvs update" command. "-rBASE" resolves to the revision that
+ your working file is based on. "-rHEAD" resolves to the current
+ head of the branch or trunk that you are working on.
+
+ * The -P option of "cvs checkout" now means to Prune empty
+ directories, as with "update". The default is to not remove empty
+ directories. However, if you do "checkout" with any -r options, -P
+ will be implied. I.e., checking out with a tag will cause empty
+ directories to be pruned automatically.
+
+ * The new file INSTALL describes how to install CVS, including
+ detailed descriptions of interfaces to "configure".
+
+ * The example loginfo file in examples/loginfo has been updated to
+ use the perl script included in contrib/log.pl. The nice thing
+ about this log program is that it records the revision numbers of
+ your change in the log message.
+
+ Example files for commitinfo and rcsinfo are now included in the
+ examples directory.
+
+ * All "#if defined(__STDC__) && __STDC__ == 1" lines have been
+ changed to be "#if __STDC__" to fix some problems with the former.
+
+ * The lib/regex.[ch] files have been updated to the 1.3 release of
+ the GNU regex package.
+
+ * The ndbm emulation routines included with CVS 1.3 Beta-2 in the
+ src/ndbm.[ch] files has been moved into the src/myndbm.[ch] files
+ to avoid any conflict with the system <ndbm.h> header file. If
+ you had a previous CVS 1.3 Beta release, you will want to "cvs
+ remove ndbm.[ch]" form your copy of CVS as well.
+
+ * "cvs add" and "cvs remove" are a bit more verbose, telling you
+ what to do to add/remove your file permanently.
+
+ * We no longer mess with /dev/tty in "commit" and "add".
+
+ * More things are quiet with the -Q option set.
+
+ * New src/config.h option: If CVS_BADROOT is set, CVS will not
+ allow people really logged in as "root" to commit changes.
+
+ * "cvs diff" exits with a status of 0 if there were no diffs, 1 if
+ there were diffs, and 2 if there were errors.
+
+ * "cvs -n diff" is now supported so that you can still run diffs
+ even while in the middle of committing files.
+
+ * Handling of the CVS/Entries file is now much more robust.
+
+ * The default file ignore list now includes "*.so".
+
+ * "cvs import" did not expand '@' in the log message correctly. It
+ does now. Also, import now uses the ignore file facility
+ correctly.
+
+ Import will now tell you whether there were conflicts that need to
+ be resolved, and how to resolve them.
+
+ * "cvs log" has been changed so that you can "log" things that are
+ not a part of the current release (in the Attic).
+
+ * If you don't change the editor message on commit, CVS now prompts
+ you with the choice:
+
+ !)reuse this message unchanged for remaining dirs
+
+ which allows you to tell CVS that you have no intention of changing
+ the log message for the remainder of the commit.
+
+ * It is no longer necessary to have CVSROOT set if you are using
+ the -H option to get Usage information on the commands.
+
+ * Command argument changes:
+ checkout: -P handling changed as described above.
+ New -j option (up to 2 can be specified)
+ for doing rcsmerge kind of things on
+ checkout.
+ commit: -r option now supports committing to a
+ numeric or symbolic tags, with some
+ restrictions. Full consistency checks will
+ be done.
+ Added "-f logfile" option, which tells
+ commit to glean the log message from the
+ specified file, rather than invoking the
+ editor.
+ rtag: Added -b option to create a branch tag,
+ useful for creating a patch for a previous
+ release, or for forking development.
+ tag: Added -b option to create a branch tag,
+ useful for creating a patch for a previous
+ release, or for forking development.
+ update: New -j option (up to 2 can be specified)
+ for doing rcsmerge kind of things on
+ update.
+
+Thu Jan 9 10:51:35 MST 1992 Jeff Polk (polk at BSDI.COM)
+
+ * Changes between CVS 1.3 Beta-1 and CVS 1.3 Beta-2
+
+ * Thanks to K. Richard Pixley at Cygnus we now have function
+ prototypes in all the files
+
+ * Some small changes to configure for portability. There have
+ been other portability problems submitted that have not been fixed
+ (Brian will be working on those). Additionally all __STDC__
+ tests have been modified to check __STDC__ against the constant 1
+ (this is what the Second edition of K&R says must be true).
+
+ * Lots of additional error checking for forked processes (run_exec)
+ (thanks again to K. Richard Pixley)
+
+ * Lots of miscellaneous bug fixes - including but certainly not
+ limited to:
+ various commit core dumps
+ various update core dumps
+ bogus results from status with numeric sticky tags
+ commitprog used freed memory
+ Entries file corruption caused by No_Difference
+ commit to revision broken (now works if branch exists)
+ ignore file processing broken for * and !
+ ignore processing didn't handle memory reasonably
+ miscellaneous bugs in the recursion processor
+ file descriptor leak in ParseInfo
+ CVSROOT.adm->CVSROOT rename bug
+ lots of lint fixes
+
+ * Reformatted all the code in src (with GNU indent) and then
+ went back and fixed prototypes, etc since indent gets confused. The
+ rationale is that it is better to do it sooner than later and now
+ everything is consistent and will hopefully stay that way.
+ The basic options to indent were: "-bad -bbb -bap -cdb -d0 -bl -bli0
+ -nce -pcs -cs -cli4 -di1 -nbc -psl -lp -i4 -ip4 -c41" and then
+ miscellaneous formatting fixes were applied. Note also that the
+ "-nfc1" or "-nfca" may be appropriate in files where comments have
+ been carefully formatted (e.g, modules.c).
+
+Sat Dec 14 20:35:22 1991 Brian Berliner (berliner at sun.com)
+
+ * Changes between CVS 1.2 and CVS 1.3 Beta are described here.
+
+ * Lots of portability work. CVS now uses the GNU "configure"
+ script to dynamically determine the features provided by your
+ system. It probably is not foolproof, but it is better than
+ nothing. Please let me know of any portability problems. Some
+ file names were changed to fit within 14-characters.
+
+ * CVS has a new RCS parser that is much more flexible and
+ extensible. It should read all known RCS ",v" format files.
+
+ * Most of the commands now are fully recursive, rather than just
+ operating on the current directory alone. This includes "commit",
+ which makes it real easy to do an "atomic" commit of all the
+ changes made to a CVS hierarchy of sources. Most of the commands
+ also correctly handle file names that are in directories other than
+ ".", including absolute path names. Commands now accept the "-R"
+ option to force recursion on (though it is always the default now)
+ and the "-l" option to force recursion off, doing just "." and not
+ any sub-directories.
+
+ * CVS supports many of the features provided with the RCS 5.x
+ distribution - including the new "-k" keyword expansion options. I
+ recommend using RCS 5.x (5.6 is the current official RCS version)
+ and GNU diff 1.15 (or later) distributions with CVS.
+
+ * Checking out files with symbolic tags/dates is now "sticky", in
+ that CVS remembers the tag/date used for each file (and directory)
+ and will use that tag/date automatically on the next "update" call.
+ This stickyness also holds for files checked out with the the new
+ RCS 5.x "-k" options.
+
+ * The "cvs diff" command now recognizes all of the rcsdiff 5.x
+ options. Unidiff format is available by installing the GNU
+ diff 1.15 distribution.
+
+ * The old "CVS.adm" directories created on checkout are now called
+ "CVS" directories, to look more like "RCS" and "SCCS". Old CVS.adm
+ directories are automagically converted to CVS directories. The
+ old "CVSROOT.adm" directory within the source repository is
+ automagically changed into a "CVSROOT" directory as well.
+
+ * Symbolic links in the source repository are fully supported ONLY
+ if you use RCS 5.6 or later and (of course) your system supports
+ symlinks.
+
+ * A history database has been contributed which maintains the
+ history of certain CVS operations, as well as providing a wide array
+ of querying options.
+
+ * The "cvs" program has a "-n" option which can be used with the
+ "update" command to show what would be updated without actually
+ doing the update, like: "cvs -n update". All usage statements
+ have been cleaned up and made more verbose.
+
+ * The module database parsing has been rewritten. The new format
+ is compatible with the old format, but with much more
+ functionality. It allows modules to be created that grab pieces or
+ whole directories from various different parts of your source
+ repository. Module-relative specifications are also correctly
+ recognized now, like "cvs checkout module/file.c".
+
+ * A configurable template can be specified such that on a "commit",
+ certain directories can supply a template that the user must fill
+ before completing the commit operation.
+
+ * A configurable pre-commit checking program can be specified which
+ will run to verify that a "commit" can happen. This feature can be
+ used to restrict certain users from changing certain pieces of the
+ source repository, or denying commits to the entire source
+ repository.
+
+ * The new "cvs export" command is much like "checkout", but
+ establishes defaults suitable for exporting code to others (expands
+ out keywords, forces the use of a symbolic tag, and does not create
+ "CVS" directories within the checked out sources.
+
+ * The new "cvs import" command replaces the deprecated "checkin"
+ shell script and is used to import sources into CVS control. It is
+ also much faster for the first-time import. Some algorithmic
+ improvements have also been made to reduce the number of
+ conflicting files on next-time imports.
+
+ * The new "cvs admin" command is basically an interface to the
+ "rcs" program. (Not yet implemented very well).
+
+ * Signal handling (on systems with BSD or POSIX signals) is much
+ improved. Interrupting CVS now works with a single interrupt!
+
+ * CVS now invokes RCS commands by direct fork/exec rather than
+ calling system(3). This improves performance by removing a call to
+ the shell to parse the arguments.
+
+ * Support for the .cvsignore file has been contributed. CVS will
+ now show "unknown" files as "? filename" as the result of an "update"
+ command. The .cvsignore file can be used to add files to the
+ current list of ignored files so that they won't show up as unknown.
+
+ * Command argument changes:
+ cvs: Added -l to turn off history logging.
+ Added -n to show what would be done without actually
+ doing anything.
+ Added -q/-Q for quiet and really quiet settings.
+ Added -t to show debugging trace.
+ add: Added -k to allow RCS 5.x -k options to be specified.
+ admin: New command; an interface to rcs(1).
+ checkout: Added -A to reset sticky tags/date/options.
+ Added -N to not shorten module paths.
+ Added -R option to force recursion.
+ Changed -p (prune empty directories) to -P option.
+ Changed -f option; forcing tags match is now default.
+ Added -p option to checkout module to standard output.
+ Added -s option to cat the modules db with status.
+ Added -d option to checkout in the specified directory.
+ Added -k option to use RCS 5.x -k support.
+ commit: Removed -a option; use -l instead.
+ Removed -f option.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ If no files specified, commit is recursive.
+ diff: Now recognizes all RCS 5.x rcsdiff options.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ history: New command; displays info about CVS usage.
+ import: Replaces "checkin" shell script; imports sources
+ under CVS control. Ignores files on the ignore
+ list (see -I option or .cvsignore description above).
+ export: New command; like "checkout", but w/special options
+ turned on by default to facilitate exporting sources.
+ join: Added -B option to join from base of the branch;
+ join now defaults to only joining with the top two
+ revisions on the branch.
+ Added -k option for RCS 5.x -k support.
+ log: Supports all RCS 5.x options.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ patch: Changed -f option; forcing tags match is now default.
+ Added -c option to force context-style diffs.
+ Added -u option to support unidiff-style diffs.
+ Added -V option to support RCS specific-version
+ keyword expansion formats.
+ Added -R option to force recursion.
+ remove: No option changes. It's a bit more verbose.
+ rtag: Equivalent to the old "cvs tag" command.
+ No option changes. It's a lot faster for re-tag.
+ status: New output formats with more information.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ Added -v option to show symbolic tags for files.
+ tag: Functionality changed to tag checked out files
+ rather than modules; use "rtag" command to get the
+ old "cvs tag" behaviour.
+ update: Added -A to reset sticky tags/date/options.
+ Changed -p (prune empty directories) to -P option.
+ Changed -f option; forcing tags match is now default.
+ Added -p option to checkout module to standard output.
+ Added -I option to add files to the ignore list.
+ Added -R option to force recursion.
+
+ Major Contributors:
+
+ * Jeff Polk <polk@bsdi.com> rewrote most of the grody code of CVS
+ 1.2. He made just about everything dynamic (by using malloc),
+ added a generic hashed list manager, re-wrote the modules database
+ parsing in a compatible - but extended way, generalized directory
+ hierarchy recursion for virtually all the commands (including
+ commit!), generalized the loginfo file to be used for pre-commit
+ checks and commit templates, wrote a new and flexible RCS parser,
+ fixed an uncountable number of bugs, and helped in the design of
+ future CVS features. If there's anything gross left in CVS, it's
+ probably my fault!
+
+ * David G. Grubbs <dgg@ksr.com> contributed the CVS "history" and
+ "release" commands. As well as the ever-so-useful "-n" option of
+ CVS which tells CVS to show what it would do, without actually
+ doing it. He also contributed support for the .cvsignore file.
+
+ * Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> wrote and
+ contributed the code in lib/sighandle.c. I added support for
+ POSIX, BSD, and non-POSIX/non-BSD systems.
+
+ * Free Software Foundation contributed the "configure" script and
+ other compatibility support in the "lib" directory, which will help
+ make CVS much more portable.
+
+ * Many others have contributed bug reports and enhancement requests.
+ Some have even submitted actual code which I have not had time yet
+ to integrate into CVS. Maybe for the next release.
+
+ * Thanks to you all!
+
+Wed Feb 6 10:10:58 1991 Brian Berliner (berliner at sun.com)
+
+ * Changes from CVS 1.0 Patchlevel 1 to CVS 1.0 Patchlevel 2; also
+ known as "Changes from CVS 1.1 to CVS 1.2".
+
+ * Major new support with this release is the ability to use the
+ recently-posted RCS 5.5 distribution with CVS 1.2. See below for
+ other assorted bug-fixes that have been thrown in.
+
+ * ChangeLog (new): Added Emacs-style change-log file to CVS 1.2
+ release. Chronological description of changes between release.
+
+ * README: Small fixes to installation instructions. My email
+ address is now "berliner@sun.com".
+
+ * src/Makefile: Removed "rcstime.h". Removed "depend" rule.
+
+ * src/partime.c: Updated to RCS 5.5 version with hooks for CVS.
+ * src/maketime.c: Updated to RCS 5.5 version with hooks for CVS.
+ * src/rcstime.h: Removed from the CVS 1.2 distribution.
+ Thanks to Paul Eggert <eggert@twinsun.com> for these changes.
+
+ * src/checkin.csh: Support for RCS 5.5 parsing.
+ Thanks to Paul Eggert <eggert@twinsun.com> for this change.
+
+ * src/collect_sets.c (Collect_Sets): Be quieter if "-f" option is
+ specified. When checking out files on-top-of other files that CVS
+ doesn't know about, run a diff in the hopes that they are really
+ the same file before aborting.
+
+ * src/commit.c (branch_number): Fix for RCS 5.5 parsing.
+ Thanks to Paul Eggert <eggert@twinsun.com> for this change.
+
+ * src/commit.c (do_editor): Bug fix - fprintf missing argument
+ which sometimes caused core dumps.
+
+ * src/modules.c (process_module): Properly NULL-terminate
+ update_dir[] in all cases.
+
+ * src/no_difference.c (No_Difference): The wrong RCS revision was
+ being registered in certain (strange) cases.
+
+ * src/patch.c (get_rcsdate): New algorithm. No need to call
+ maketime() any longer.
+ Thanks to Paul Eggert <eggert@twinsun.com> for this change.
+
+ * src/patchlevel.h: Increased patch level to "2".
+
+ * src/subr.c (isdir, islink): Changed to compare stat mode bits
+ correctly.
+
+ * src/tag.c (tag_file): Added support for following symbolic links
+ that are in the master source repository when tagging. Made tag
+ somewhat quieter in certain cases.
+
+ * src/update.c (update_process_lists): Unlink the user's file if it
+ was put on the Wlist, meaning that the user's file is not modified
+ and its RCS file has been removed by someone else.
+
+ * src/update.c (update): Support for "cvs update dir" to correctly
+ just update the argument directory "dir".
+
+ * src/cvs.h: Fixes for RCS 5.5 parsing.
+ * src/version_number.c (Version_Number): Fixes for parsing RCS 5.5
+ and older RCS-format files.
+ Thanks to Paul Eggert <eggert@twinsun.com> for these changes.
+
+ * src/version_number.c (Version_Number): Bug fixes for "-f" option.
+ Bug fixes for parsing with certain branch numbers. RCS
+ revision/symbol parsing is much more solid now.
+
+Wed Feb 14 10:01:33 1990 Brian Berliner (berliner at sun.com)
+
+ * Changes from CVS 1.0 Patchlevel 0 to CVS 1.0 Patchlevel 1; also
+ known as "Changes from CVS 1.0 to CVS 1.1".
+
+ * src/patch.c (get_rcsdate): Portability fix. Replaced call to
+ timelocal() with call to maketime().
+
+Mon Nov 19 23:15:11 1990 Brian Berliner (berliner at prisma.com)
+
+ * Sent CVS 1.0 release to comp.sources.unix moderator and FSF.
+
+ * Special thanks to Dick Grune <dick@cs.vu.nl> for his work on the
+ 1986 version of CVS and making it available to the world. Dick's
+ version is available on uunet.uu.net in the
+ comp.sources.unix/volume6/cvs directory.
+
+@(#)ChangeLog 1.17 92/04/10
diff --git a/contrib/cvs/FAQ b/contrib/cvs/FAQ
new file mode 100644
index 0000000..450fa32
--- /dev/null
+++ b/contrib/cvs/FAQ
@@ -0,0 +1,14 @@
+This file formerly contained a CVS FAQ, maintained by David Grubbs.
+However, it has been out of date for a long time, he officially gave up
+maintaining it in the fall of 1995, and noone else has taken it over.
+Most of the information which used to be in it can be found in the CVS
+manual, doc/cvs.texinfo in this distribution. For information on
+questions like "what is the latest version of CVS?" and "what about GUIs
+for CVS?", see the CVS web site,
+http://www.loria.fr/~molli/cvs-index.html. There are many web sites on
+Configuration Management packages (of which CVS is an example); for
+example see
+
+http://www.yahoo.com/Computers_and_Internet/Software/Software_Engineering/Configuration_Management/index.html
+
+or http://www.iac.honeywell.com/Pub/Tech/CM/index.html.
diff --git a/contrib/cvs/HACKING b/contrib/cvs/HACKING
new file mode 100644
index 0000000..ac4cdd1
--- /dev/null
+++ b/contrib/cvs/HACKING
@@ -0,0 +1,102 @@
+How to write code for CVS
+
+* Compiler options
+
+If you are using GCC, you'll want to configure with -Wall, which can
+detect many programming errors. This is not the default because it
+might cause spurious warnings, but at least on some machines, there
+should be no spurious warnings. For example:
+
+ $ CFLAGS="-g -Wall" ./configure
+
+Configure is not very good at remembering this setting; it will get
+wiped out whenever you do a ./config.status --recheck, so you'll need
+to use:
+
+ $ CFLAGS="-g -Wall" ./config.status --recheck
+
+* Indentation style
+
+CVS mostly uses a consistent indentation style which looks like this:
+
+void
+foo (arg)
+ char *arg;
+{
+ if (arg != NULL)
+ {
+ bar (arg);
+ baz (arg);
+ }
+}
+
+The file cvs-format.el contains settings for emacs and the NEWS file
+contains a set of options for the indent program which I haven't tried
+but which are correct as far as I know. You will find some code which
+does not conform to this indentation style; the plan is to reindent it
+as those sections of the code are changed (one function at a time,
+perhaps).
+
+In a submitted patch it is acceptable to refrain from changing the
+indentation of large blocks of code to minimize the size of the patch;
+the person checking in such a patch should reindent it.
+
+* Portability
+
+If it is in ANSI C and it is in SunOS4 (using /bin/cc), generally it
+is OK to use it without ifdefs (for example, assert() and void * as
+long as you add more casts to and from void * than ANSI requires. But
+not function prototypes). Such constructs are generally portable
+enough, including to NT, OS/2, VMS, etc.
+
+* Run-time behaviors
+
+Use assert() to check "can't happen" conditions internal to CVS. We
+realize that there are functions in CVS which instead return NULL or
+some such value (thus confusing the meaning of such a returned value),
+but we want to fix that code. Of course, bad input data, a corrupt
+repository, bad options, etc., should always print a real error
+message instead.
+
+* Coding standards in general
+
+Generally speaking the GNU coding standards are mostly used by CVS
+(but see the exceptions mentioned above, such as indentation style,
+and perhaps an exception or two we haven't mentioned). This is the
+file standards.text at the GNU FTP sites.
+
+* Submitting patches
+
+Please include a ChangeLog entry (see the GNU coding standards for
+information on writing one) with patches. Include a description of
+what the patch does (sometimes the ChangeLog entry and/or comments in
+the code are appropriate for this, but not always)--patches should not
+be checked in unless there is some reason for them, and the
+description may be helpful if there is a better way to solve the
+problem. In addition to the ChangeLog entry, there should be a change
+to the NEWS file in the case of a new feature.
+
+If you solve several unrelated problems, submit a separate
+patch for each one. Patches should be tested before submission. Use
+context diffs or unidiffs for patches.
+
+Note that all submitted changes may be distributed under the terms of
+the GNU Public License, so if you don't like this, don't submit them.
+Submit changes to bug-cvs@prep.ai.mit.edu.
+
+Generally speaking if you follow the guidelines in this file you can
+expect a yes or no answer about whether your patch is accepted. But
+even in this case there is no guarantee because wading through a bunch
+of submissions can be time consuming, and noone has volunteered to
+offer any such guarantee. If you don't receive an answer one way or
+another within a month, feel free to ask what the status is. You can,
+if you wish, distribute your patch on mailing lists or newsgroups, if
+you want to make it available before it gets merged.
+
+* What is the schedule for the next release?
+
+There isn't one. That is, upcoming releases are not announced (or
+even hinted at, really) until the feature freeze which is
+approximately 2 weeks before the final release (at this time test
+releases start appearing and are announced on info-cvs). This is
+intentional, to avoid a last minute rush to get new features in.
diff --git a/contrib/cvs/INSTALL b/contrib/cvs/INSTALL
new file mode 100644
index 0000000..4907134
--- /dev/null
+++ b/contrib/cvs/INSTALL
@@ -0,0 +1,344 @@
+#ident "$CVSid$"
+
+First, read the README file. If you're still happy...
+
+CVS has been tested on the following platforms. The most recent
+version of CVS reported to have been tested is indicated, but more
+recent versions of CVS probably will work too. Please send updates to
+this list to bug-cvs@prep.ai.mit.edu (doing so in the form of a diff
+to this file is encouraged).
+
+Alpha:
+ DEC Alpha running OSF/1 version 1.3 using cc (about 1.4A2)
+ DEC Alpha running OSF/1 version 2.0 (1.4.90)
+ DEC Alpha running OSF/1 version 2.1 (about 1.4A2)
+ DEC Alpha running OSF/1 version 3.0 (1.5.95) (footnote 7)
+ DEC Alpha running OSF/1 version 3.2 (1.7+obvious patch)
+HPPA:
+ HP 9000/710 running HP-UX 8.07A using gcc (about 1.4A2)
+ HP 9000/715 running HP-UX 9.01 (1.6)
+ HPPA running HP-UX 10.01 (1.7)
+ HPPA 1.1 running HP-UX A.09.03 (1.5.95) (footnote 8)
+ HPPA 1.1 running HP-UX A.09.04 (1.7.1)
+ NextSTEP 3.3 (1.6.86)
+i386 family:
+ Solaris 2.4 using gcc (about 1.4A2)
+ UnixWare v1.1.1 using gcc (about 1.4A2)
+ ISC 4.0.1 (1.5.94)
+ Linux (kernel 1.2.x) (1.7.1)
+ BSDI 2.0 (1.4.93) (footnote 5)
+ FreeBSD 2.0.5, i486, gcc (1.5.95)
+ NextSTEP 3.3 (1.6.86)
+ NeXTSTEP 3.3 (1.7), (footnote 10)
+ SCO Unix 3.2.4.2 (1.4.93) (footnote 4)
+ SCO OpenServer 5.0.0, "CC='cc -b elf' configure"
+ Lynx 2.3.0 080695 (1.6.86) (footnote 9)
+ Windows NT 3.51 (1.7.87 client-only)
+ QNX 4 (1.7 + obvious patches)
+ OS/2 Version 3 using IBM C/C++ Tools 2.01 (1.7.86 with patches)
+m68k:
+ Sun 3 running SunOS 4.1.1_U1 w/ bundled K&R /usr/5bin/cc (1.6)
+ NextSTEP 3.3 (1.6.86)
+ NeXTSTEP 3.3 (1.7), (footnote 10)
+ Lynx 2.3.0 062695 (1.6.86) (footnote 9)
+m88k:
+ Data General AViiON running dgux 5.4R2.10 (1.5)
+ Data General AViiON running dgux 5.4R3.10 (1.7.1)
+ Harris Nighthawk 5800 running CX/UX 7.1 (1.5) (footnote 6)
+MIPS:
+ DECstation running Ultrix 4.2a (1.4.90)
+ DECstation running Ultrix 4.3 (1.6.86)
+ SGI running Irix 4.0.5H using gcc and cc (about 1.4A2) (footnote 2)
+ SGI running Irix 5.3 (1.7)
+ SGI running Irix-6 (about 1.4.90) (footnote 3)
+ Siemens-Nixdorf RM600 running SINIX-Y (1.6)
+PowerPC or RS/6000:
+ IBM RS/6000 running AIX 3.1 using gcc and cc (1.6.86)
+ IBM RS/6000 running AIX 3.2.5 (1.7.87)
+ IBM RS/6000 running AIX 4.1 using gcc and cc (about 1.4A2) (footnote 1)
+ Lynx 2.3.1 120495 (1.6.86) (footnote 9)
+SPARC:
+ Sun SPARC running SunOS 4.1.x (1.6.86)
+ Sun SPARCstation 10 running Solaris 2.3 using gcc and cc (about 1.4A2)
+ Sun SPARCstation running Solaris 2.4 using gcc and cc (about 1.5.91)
+ Sun SPARC running Solaris 2.5 (2.5 beta?) (1.6.4)
+ NextSTEP 3.3 (1.6.86)
+ NeXTSTEP 3.3 (1.7), (footnote 10)
+
+(footnote 1)
+ AIX 4.1 systems fail to run "configure" due to bugs in their
+ "/bin/sh" implementation. You might want to try feeding the
+ configure script to "bash" ported to AIX 4.1. (about 1.4A2).
+
+(footnote 2)
+ Some Irix 4.0 systems may core dump in malloc while running
+ CVS. We believe this is a bug in the Irix malloc. You can
+ workaround this bug by linking with "-lmalloc" if necessary.
+ (about 1.4A2).
+
+(footnote 3)
+ There are some warnings about pointer casts which can safely be
+ ignored. (about 1.4.90).
+
+(footnote 4) Comment out the include of sys/time.h in src/server.c. (1.4.93)
+ You also may have to make sure TIME_WITH_SYS_TIME is undef'ed.
+
+(footnote 5) Change /usr/tmp to /var/tmp in src/server.c (2 places) (1.4.93).
+
+(footnote 6) Build in ucb universe with COFF compiler tools. Put
+ /usr/local/bin first in PATH while doing a configure, make
+ and install of GNU diffutils-2.7, rcs-5.7, then cvs-1.5.
+
+(footnote 7) Manoj Srivastava <srivasta@pilgrim.umass.edu> reports
+ success with this configure command:
+ CC=cc CFLAGS='-O2 -Olimit 2000 -std1' ./configure --verbose alpha-dec-osf
+
+(footnote 8) Manoj Srivastava <srivasta@pilgrim.umass.edu> reports
+ success with this configure command:
+ CC=cc CFLAGS='+O2 -Aa -D_HPUX_SOURCE' ./configure --verbose hppa1.1-hp-hpux
+
+(footnote 9)
+ Had to configure with ./configure --host=<arch>-lynx.
+
+ In src/cvs.h, protected the waitpid prototype with ifdef _POSIX_SOURCE.
+ (I might try building with gcc -mposix -D_POSIX_SOURCE.)
+
+ LynxOS has <dirent.h>, but you don't want to use it.
+ You want to use <sys/dir.h> instead.
+ So after running configure I had to undef HAVE_DIRENT_H and
+ define HAVE_SYS_DIR_H.
+
+(footnote 10) Ralf E. Stranzenbach <ralf@reswi.ruhr.de>
+ I've made some modifications to "filesubr.c" to deal with NFS
+ mounted directories (and those funny .nfs* files). This patch
+ should be used whenever the programmers "sandbox" is located on
+ a NFS mounted device --- at least on NeXTSTEP.
+
+-------------------------------------------------------------------------------
+
+Installation under Unix:
+
+1) Run "configure":
+
+ $ ./configure
+
+ You can specify an alternate destination to override the default with
+ the --prefix option:
+
+ $ ./configure --prefix=/usr/local/gnu
+
+ or some path that is more appropriate for your site. The default prefix
+ value is "/usr/local", with binaries in sub-directory "bin", manual
+ pages in sub-directory "man", and libraries in sub-directory "lib".
+
+ If you are using server or local CVS, RCS needs to be installed in
+ the user's PATH (or a path you have configured in src/options.h,
+ or a path specified with the -b option). If you don't have RCS,
+ you will need to get it from GNU as well. It is best to get the
+ version 5.7 (or later) version of RCS, available from
+ prep.ai.mit.edu in the file pub/gnu/rcs-5.7.tar.gz.
+
+ If you want version control of files with binary data, make sure
+ that the RCS configure script finds GNU diff 1.15 or later and
+ notices that diff supports the -a option. CVS itself is much less
+ picky about which version of diff it uses, and you shouldn't need
+ to worry about that.
+
+ NOTE: The configure program will cache the results of the previous
+ configure execution. If you need to re-run configure from scratch, you
+ may need to run "make distclean" first to remove the cached
+ configuration information.
+
+ If you are using gcc and are planning to modify CVS, you may want to
+ configure with -Wall; see the file HACKING for details.
+
+ Try './configure --help' for further information on its usage.
+
+ NOTE ON CVS's USE OF NDBM:
+
+ By default, CVS uses some built-in ndbm emulation code to allow
+ CVS to work in a heterogeneous environment. However, if you have
+ a very large modules database, this may not work well. You will
+ need to edit src/options.h to turn off the MY_NDBM #define and
+ re-run configure. If you do this, the following comments apply.
+ If not, you may safely skip these comments.
+
+ If you configure CVS to use the real ndbm(3) libraries and
+ you do not have them installed in a "normal" place, you will
+ probably want to get the GNU version of ndbm (gdbm) and install
+ that before running the CVS configure script. Be aware that the
+ GDBM 1.5 release does NOT install the <ndbm.h> header file included
+ with the release automatically. You may have to install it by hand.
+
+ If you configure CVS to use the ndbm(3) libraries, you cannot
+ compile CVS with GNU cc (gcc) on Sun-4 SPARC systems. However, gcc
+ 2.0 may have fixed this limitation if -fpcc-struct-return is
+ defined. When using gcc on other systems to compile CVS, you *may*
+ need to specify the -fpcc-struct-return option to gcc (you will
+ *know* you have to if "cvs checkout" core dumps in some ndbm
+ function). You can do this as follows:
+
+ $ CC='gcc -fpcc-struct-return' ./configure
+
+ for sh, bash, and ksh users and:
+
+ % setenv CC 'gcc -fpcc-struct-return'
+ % ./configure
+
+ for csh and tcsh users.
+
+ END OF NOTE FOR NDBM GUNK.
+
+2) Edit src/options.h. Appropriate things to look at may be the
+ invocation locations of programs like DIFF and GREP.
+ Also glance at the default values for the environment variables
+ that CVS uses, in particular, the RCSBIN variable, which holds the
+ path to where the RCS programs live on your system.
+
+3) Try to build it:
+
+ $ make
+
+ This will (hopefully) make the needed CVS binaries within the
+ "src" directory. If something fails for your system, and you want
+ to submit a bug report, you may wish to include your
+ "config.status" file, your host type, operating system and
+ compiler information, make output, and anything else you think
+ will be helpful.
+
+3a) Run the regression tests (optional).
+
+ You may also wish to validate the correctness of the new binary by
+ running the regression tests:
+
+ $ make check
+
+ Note that if your /bin/sh doesn't support shell functions, you'll
+ have to try something like this, where "/bin/sh5" is replaced by the
+ pathname of a shell which handles normal shell functions:
+
+ $ make SHELL=/bin/sh5 check
+
+ WARNING: This test can take quite a while to run, esp. if your
+ disks are slow or over-loaded.
+
+ If you receive any un-expected output from the regression tests,
+ it may indicate a bug in CVS (or might just indicate a problem
+ running the tests). If you choose to submit a bug report,
+ be aware that, as with all bug reports, you may or may not get a
+ response, and your odds might be better if you include enough information
+ to reproduce the bug, an analysis of what is going wrong (if you have
+ the time and ability to provide one), etc. The check.log file is the
+ first place to look.
+
+4) Install the binaries/documentation:
+
+ $ make install
+
+ Depending on your installation's configuration, you may need to be
+ root to do this.
+
+5) Take a look at the CVS documentation.
+
+ $ man cvs
+
+ and
+
+ $ info cvs
+
+ See what it can do for you, and if it fits your environment (or can
+ possibly be made to fit your environment). If things look good,
+ continue on...
+
+6) Set up the master source repository. See the "Setting up the repository"
+ section of cvs.texinfo for details; the one-line summary is (if you
+ are putting the repository in /src/master):
+ $ cvs -d /src/master init
+
+7) Have all users of the CVS system set the 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 user's ~/.profile, ~/.bash_profile file; or in the
+ site-wide /etc/profile:
+
+ CVSROOT=/src/master; export CVSROOT
+
+ for sh/bash/ksh users, or place the following commands in the user's
+ ~/.cshrc, ~/.login, or /etc/chsrc file:
+
+ setenv CVSROOT /src/master
+
+ for csh/tcsh users. If these environment variables are not already set
+ in your current shell, set them now (or source the login script you
+ just edited). You will need to have the CVSROOT environment variable
+ set to continue on to the next step.
+
+8) It might be a good idea to jump right in and put the CVS distribution
+ directly under CVS control. From within the top-level directory of the
+ CVS distribution (the one that contains this README file) do the
+ following commands:
+
+ $ make distclean
+ $ cvs import -m 'CVS 1.6 distribution' cvs CVS_DIST CVS-1_6
+
+9) Having done step 8, one should be able to checkout a fresh copy of the
+ CVS distribution and hack away at the sources with the following command:
+
+ $ cd
+ $ cvs checkout cvs
+
+ This will make the directory "cvs" in your current directory and
+ populate it with the appropriate CVS files and directories.
+
+10) You may wish to customize the various administrative files, in particular
+ modules. See cvs.texinfo for details.
+
+11) Read the NEWS file to see what's new.
+
+12) Hack away.
+
+-------------------------------------------------------------------------------
+
+Detailed information about your interaction with "configure":
+
+The "configure" script and its interaction with its options and the
+environment is described here. For more detailed documentation about
+"configure", please refer to the GNU Autoconf documentation.
+
+Supported options are:
+
+ --srcdir=DIR Useful for compiling on many different
+ machines sharing one source tree.
+ --prefix=DIR The root of where to install the
+ various pieces of CVS (/usr/local).
+ --exec_prefix=DIR If you want executables in a
+ host-dependent place and shared
+ things in a host-independent place.
+
+The following environment variables override configure's default
+behaviour:
+
+ CC If not set, tries to use gcc first,
+ then cc. Also tries to use "-g -O"
+ as options, backing down to -g
+ alone if that doesn't work.
+ INSTALL If not set, tries to use "install", then
+ "./install-sh" as a final choice.
+ RANLIB If not set, tries to determine if "ranlib"
+ is available, choosing "echo" if it doesn't
+ appear to be.
+ YACC If not set, tries to determine if "bison"
+ is available, choosing "yacc" if it doesn't
+ appear to be.
+
+-------------------------------------------------------------------------------
+Installation under Windows NT:
+
+You may find interesting information in windows-NT/README.
+
+1) Using Microsoft Visual C++ version 2.1, open the project `cvsnt.mak',
+ in the top directory of the CVS distribution.
+2) Choose "Build cvs.exe" from the "Project" menu.
+3) MSVC will place the executable file cvs.exe in WinDebug, or whatever
+ your target directory is.
+-------------------------------------------------------------------------------
diff --git a/contrib/cvs/MINOR-BUGS b/contrib/cvs/MINOR-BUGS
new file mode 100644
index 0000000..7b85719
--- /dev/null
+++ b/contrib/cvs/MINOR-BUGS
@@ -0,0 +1,60 @@
+Low-priority bugs go here. We don't have many yet -- everything is
+high-priority at the moment. :-)
+
+
+* From: Jeff Johnson <jbj@brewster.JBJ.ORG>
+ To: cyclic-cvs@cyclic.com
+ Subject: Named_Root assumes . on server
+ Date: Wed, 17 May 1995 11:04:53 -0400 (EDT)
+
+ Problem:
+ On server, Name_Root() attempts (aggressively) to set CVSADM_Root.
+ If ~/CVS/Root exists (wrto rsh login), then CVSADM_Root will be
+ initialized from that file. The sanity check between the root
+ repository and the invocation will fail if the two values are not
+ coincidentally the same.
+
+ Workaround:
+ There's a zillion ways to fix this bugture/featurelet. My current
+ workaround is to remove ~/CVS/Root on the server. I shall attempt
+ a better fix as soon as I can determine what appears politically
+ correct. IMHO, the CVS/Root stuff (and getenv("CVSROOT") also) is
+ a bit fragile and tedious in an rcmd() driven CCVS environment.
+
+
+* (Jeff Johnson <jbj@jbj.org>)
+ I tried a "cvs status -v" and received the following:
+
+ ? CVS
+ ? programs/CVS
+ ? tests/CVS
+ cvs server: Examining .
+ ===================================================================
+ File: Install.dec Status: Up-to-date
+ ...
+
+ I claim that CVS dirs should be ignored.
+
+
+* I sometimes get this message:
+
+ Could not look up address for your host. Permission denied.
+ cvs [update aborted]: premature end of file from server
+
+ The client's response should be cleaned up.
+
+* In the gb-grep module, update-ChangeLog (and therefore, I assume,
+ rcs2log) truncates file names --- I get entries for things called
+ ring/lenstring.h instead of lenstring/lenstring.h.
+
+* On remote checkout, files don't have the right time/date stamps in
+ the CVS/Entries files. Doesn't look like the C/S protocol has any
+ way to send this information along (according to cvsclient.texi).
+ Perhaps we can spiff it up a bit by using the conflict field for the
+ stamp on the checkout/update command. Please note that this really
+ doesn't do very much for us even if we get it done.
+
+* Does the function that lists the available modules in the repository
+ belong under the "checkout" function? Perhaps it is more logically
+ grouped with the "history" function or we should create a new "info"
+ function?
diff --git a/contrib/cvs/Makefile.in b/contrib/cvs/Makefile.in
new file mode 100644
index 0000000..636a0d7
--- /dev/null
+++ b/contrib/cvs/Makefile.in
@@ -0,0 +1,264 @@
+# Master Makefile for the GNU Concurrent Versions System.
+# Copyright (C) 1986, 1988-1992, 1994 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# $CVSid: @(#)Makefile.in 1.30 94/10/22 $
+
+SHELL = /bin/sh
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+# If you use gcc, you should either run the fixincludes script that
+# comes with it or else use gcc with the -traditional option. Otherwise
+# ioctl calls will be compiled incorrectly on some systems.
+CC = @CC@
+AR = ar
+
+@SET_MAKE@
+
+# Set RANLIB = echo if your system doesn't have or need ranlib.
+RANLIB = @RANLIB@
+# Set YACC = bison or yacc, depending on which you have on your system
+YACC = @YACC@
+# Use cp if you don't have install.
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+
+DEFS = @DEFS@
+LIBS = @LIBS@
+
+INCLUDES = -I. -I../lib @includeopt@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+MAKEINFO = makeinfo
+TEXI2DVI = texi2dvi
+
+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 Info files
+infodir = $(prefix)/info
+
+# Where to put the manual pages.
+mandir = $(prefix)/man
+
+#### End of system configuration section. ####
+
+FLAGS_TO_PASS = \
+ AR='$(AR)' \
+ CC='$(CC)' \
+ CFLAGS='$(CFLAGS)' \
+ LDFLAGS='$(LDFLAGS)' \
+ LIBPROGS='$(LIBPROGS)' \
+ LIBS='$(LIBS)' \
+ MAKE='$(MAKE)' \
+ MAKEINFO='$(MAKEINFO)' \
+ RANLIB='$(RANLIB)' \
+ TEXI2DVI='$(TEXI2DVI)' \
+ YACC='$(YACC)' \
+ bindir='$(bindir)' \
+ infodir='$(infodir)' \
+ libdir='$(libdir)' \
+ mandir='$(mandir)' \
+ prefix='$(prefix)' \
+ exec_prefix='$(exec_prefix)'
+
+DISTFILES = \
+ COPYING COPYING.LIB INSTALL README TODO PROJECTS \
+ BUGS MINOR-BUGS FAQ HACKING \
+ ChangeLog NEWS ChangeLog.zoo \
+ configure configure.in stamp-h.in config.h.in Makefile.in acconfig.h \
+ cvs-format.el mkinstalldirs install-sh \
+ cvsnt.mak \
+ .cvsignore
+
+### Subdirectories to run make in for the primary targets.
+# Unix source subdirs, where we'll want to run lint and etags:
+USOURCE_SUBDIRS = lib src
+# All other subdirs:
+SUBDIRS = ${USOURCE_SUBDIRS} man doc contrib tools windows-NT os2 macintosh
+# Only make TAGS/tags files in these directories, in this order
+# [Why in this order? If we didn't have to stick to this order, we
+# could make "TSUBDIRS = ${USOURCE_SUBDIRS}". -Karl]
+TSUBDIRS= src lib
+
+# Set default target.
+all:
+
+.PHONY: all install uninstall
+all install uninstall: config.h Makefile all-local
+ @for subdir in $(SUBDIRS); do \
+ echo "making $@ in $$subdir"; \
+ ( cd $$subdir && $(MAKE) $(FLAGS_TO_PASS) $@ ) || exit 1; \
+ done
+
+install: all install-local install-info
+
+.PHONY: all-local
+all-local:
+
+.PHONY: info dvi clean-info install-info
+info dvi clean-info install-info:
+ cd doc && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1
+
+.PHONY: install-local
+install-local: all-local
+ @: nothing to do locally
+
+.PHONY: tags
+tags:
+ @for dir in $(TSUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done
+ @echo making $@ in .
+ @ctags `for i in \`$(MAKE) SUBDIRS="$(TSUBDIRS)" ls\` ; do echo $(srcdir)/$$i ; done`
+
+.PHONY: TAGS
+TAGS:
+ @for dir in $(TSUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done
+ @echo making $@ in .
+ @etags `for i in \`$(MAKE) SUBDIRS="$(TSUBDIRS)" ls | grep -v 'make\[[0-9]\]'\` ; do echo $(srcdir)/$$i ; done`
+
+.PHONY: ls
+ls:
+ @echo $(DISTFILES)
+ @for dir in $(SUBDIRS); do \
+ for i in `cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ | grep -v 'make\[[0-9]\]'` ; do \
+ echo $$dir/$$i ; \
+ done ; \
+ done
+
+.PHONY: clean
+clean: clean-local
+ @for dir in $(SUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done
+
+.PHONY: distclean
+distclean: distclean-local
+ @for dir in $(SUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done
+ rm -f config.status
+
+.PHONY: realclean
+realclean: realclean-local
+ @for dir in $(SUBDIRS); do echo making $@ in $$dir; cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done
+ rm -f config.status
+
+.PHONY: mostlyclean-local
+mostlyclean-local:
+ rm -f *~
+
+.PHONY: clean-local
+clean-local: mostlyclean-local
+
+.PHONY: distclean-local
+distclean-local: clean-local
+ rm -f Makefile config.cache config.h config.log stamp-h
+ rm -f tags TAGS
+
+.PHONY: realclean-local
+realclean-local: distclean-local
+
+.PHONY: saber
+saber:
+ @for dir in $(SUBDIRS); do cd $$dir && $(MAKE) $(FLAGS_TO_PASS) $@ || exit 1; cd ..; done
+
+.PHONY: check
+check:
+ cd lib ; $(MAKE) $(FLAGS_TO_PASS)
+ cd src ; $(MAKE) $(FLAGS_TO_PASS) check
+
+.PHONY: remotecheck
+remotecheck:
+ cd lib ; $(MAKE) $(FLAGS_TO_PASS)
+ cd src ; $(MAKE) $(FLAGS_TO_PASS) remotecheck
+
+.PHONY: installcheck
+installcheck:
+ cd lib ; $(MAKE) $(FLAGS_TO_PASS)
+ cd src ; $(MAKE) $(FLAGS_TO_PASS) installcheck
+
+.PHONY: lint
+lint:
+ @for dir in $(USOURCE_SUBDIRS); do cd $$dir && $(MAKE) $(FLAGS_TO_PASS) xlint || exit 1; cd ..; done
+
+.PHONY: dist
+GZIP=gzip --best
+GZIP_EXT=.gz
+TAR_VERBOSE=
+dist:
+ echo > .fname \
+ cvs-`sed < $(srcdir)/src/version.c \
+ -e '/version_string/!d' \
+ -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \
+ -e q`
+ rm -rf `cat .fname`
+ ${MAKE} dist-dir DISTDIR="`cat .fname`"
+ for dir in ${SUBDIRS}; do \
+ ( DISTDIR="../`cat .fname`/$${dir}"; \
+ cd $${dir} && \
+ ${MAKE} dist-dir DISTDIR="$${DISTDIR}" \
+ ); \
+ done
+ tar chf${TAR_VERBOSE} - `cat .fname` | ${GZIP} > "`cat .fname`.tar${GZIP_EXT}"
+ rm -rf `cat .fname` .fname
+
+.PHONY: dist-dir
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+
+# For the justification of the following Makefile rules, see node
+# `Automatic Remaking' in GNU Autoconf documentation.
+Makefile: Makefile.in config.status
+ CONFIG_FILES=$@ CONFIG_HEADERS= ./config.status
+config.status: configure
+ ./config.status --recheck
+# The rules to run autoconf and autoheader are commented out. This is because
+# when the user unpacks a tarfile, configure.in might end up newer than
+# configure, but the user might not have (and does not need to have) autoconf
+# installed.
+#configure: configure.in #aclocal.m4
+# cd $(srcdir); autoconf
+
+config.h: stamp-h
+
+# This used to do a ./config.status --recheck, to update config.status with
+# any new #defines from config.h.in. The problem was that the rule itself
+# depends on config.status, so that the --recheck would get run several
+# times, which is bad if the user was trying to specify CFLAGS manually.
+# It was a big pain in the neck.
+stamp-h: config.h.in config.status
+ CONFIG_FILES=$@ CONFIG_HEADERS=config.h ./config.status
+
+#config.h.in: stamp-h.in
+#stamp-h.in: configure.in #aclocal.m4 acconfig.h
+# cd $(srcdir); autoheader
+# date > $(srcdir)/stamp-h.in
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/contrib/cvs/NEWS b/contrib/cvs/NEWS
new file mode 100644
index 0000000..81d82ac
--- /dev/null
+++ b/contrib/cvs/NEWS
@@ -0,0 +1,907 @@
+Changes since 1.7:
+
+* New "cvs annotate" command to display the last modification for each
+line of a file, with the revision number, user checking in the
+modification, and date of the modification. For more information see
+the `annotate' node in cvs.texinfo.
+
+* The cvsinit shell script has been replaced by a cvs init command.
+The cvs init command creates some example administrative files which
+are similar to the files found in the examples directory (and copied
+by cvsinit) in previous releases.
+
+* Added the patterns *.olb *.exe _$* *$ to default ignore list.
+
+* There is now a $USER internal variable for *info files.
+
+* There is no longer a separate `mkmodules' program; the functionality
+is now built into `cvs'. If upgrading an old repository, it is OK to
+leave in the lines in the modules file which run mkmodules (the
+mkmodules actions will get done twice, but that is harmless); you will
+probably want to remove them once you are no longer using the old CVS.
+
+* One can now specify user variables in *info files via the
+${=varname} syntax; there is a -s global option to set them. See the
+Variables node in cvs.texinfo for details.
+
+Changes from 1.6 to 1.7:
+
+* The default ignore list has changed slightly: *.obj has been added
+and CVS* has been changed to CVS CVS.adm.
+
+* CVS now supports password authentication when accessing remote
+repositories; this is useful for sites that can't use rsh (because of
+a firewall, for example), and also don't have kerberos. See node
+"Password authenticated" (in "Remote repositories", in
+doc/cvs.texinfo) for more details. Note: This feature requires both
+the client and server to be upgraded.
+
+* Using the -kb option to specify binary files now works--most cases
+did not work before. See the "Binary files" section of
+doc/cvs.texinfo for details.
+
+* New developer communication features. See the "Watches" section of
+doc/cvs.texinfo for details.
+
+* RCS keyword "Name" supported for "cvs update -r <tag>" and "cvs
+checkout -r <tag>".
+
+* If there is a group whose name matches a compiled in value which
+defaults to "cvsadmin", only members of that group can use "cvs
+admin". This replaces the CVS_NOADMIN option.
+
+* CVS now sets the modes of files in the repository based on the
+CVSUMASK environment variable or a compiled in value defaulting to
+002. This way other developers will be able to access the files in
+the repository regardless of the umask of the developer creating them.
+
+* The command names in .cvsrc now match the official name of the
+command, not the one (possibly an alias) by which it was invoked. If
+you had previously relied on "cvs di" and "cvs diff" using different
+options, instead use a shell function or alias (for example "alias
+cvsdi='cvs diff -u'"). You also can specify global CVS options (like
+"-z") using the command name "cvs".
+
+Changes from 1.5 to 1.6:
+
+* Del updated the man page to include all of the new features
+of CVS 1.6.
+
+* "cvs tag" now supports a "-r | -D" option for tagging an already
+tagged revision / specific revision of a file.
+
+* There is a "taginfo" file in CVSROOT that supports filtering and
+recording of tag operations.
+
+* Long options support added, including --help and --version options.
+
+* "cvs release" no longer cares whether or not the directory being
+released has an entry in the `modules' file.
+
+* The modules file now takes a -e option which is used instead of -o
+for "cvs export". If your modules file has a -o option which you want
+to be used for "cvs export", change it to specify -e as well as -o.
+
+* "cvs export" now takes a -k option to set RCS keyword expansion.
+This way you can export binary files. If you want the old behavior,
+you need to specify -kv.
+
+* "cvs update", "cvs rdiff", "cvs checkout", "cvs import", "cvs
+release", "cvs rtag", and "cvs tag" used to take -q and -Q options
+after the command name (e.g. "cvs update -q"). This was confusing
+because other commands, such as "cvs ci", did not. So the options
+after the command name have been removed and you must now specify, for
+example, "cvs -q update", which has been supported since CVS 1.3.
+
+* New "wrappers" feature. This allows you to set a hook which
+transforms files on their way in and out of cvs (apparently on the
+NeXT there is some particular usefulness in tarring things up in the
+repository). It also allows you to declare files as merge-by-copy
+which means that instead of trying to merge the file, CVS will merely
+copy the new version. There is a CVSROOT/cvswrappers file and an
+optionsl ~/.cvswrappers file to support this feature.
+
+* You can set CVSROOT to user@host:dir, not just host:dir, if your
+username on the server host is different than on the client host.
+
+* VISUAL is accepted as well as EDITOR.
+
+* $CVSROOT is expanded in *info files.
+
+Changes from 1.4A2 to 1.5:
+
+* Remote implementation. This is very helpful when collaborating on a
+project with someone across a wide-area network. This release can
+also be used locally, like other CVS versions, if you have no need for
+remote access.
+
+Here are some of the features of the remote implementation:
+- It uses reliable transport protocols (TCP/IP) for remote repository
+ access, not NFS. NFS is unusable over long distances (and sometimes
+ over short distances)
+- It transfers only those files that have changed in the repository or
+ the working directory. To save transmission time, it will transfer
+ patches when appropriate, and can compress data for transmission.
+- The server never holds CVS locks while waiting for a reply from the client;
+ this makes the system robust when used over flaky networks.
+
+The remote features are documented in doc/cvsclient.texi in the CVS
+distribution, but the main doc file, cvs.texinfo, has not yet been
+updated to include the remote features.
+
+* Death support. See src/README-rm-add for more information on this.
+
+* Many speedups, especially from jtc@cygnus.com.
+
+* CVS 1.2 compatibility code has been removed as a speedup. If you
+have working directories checked out by CVS 1.2, CVS 1.3 or 1.4A2 will
+try to convert them, but CVS 1.5 and later will not (if the working
+directory is up to date and contains no extraneous files, you can just
+remove it, and then check out a new working directory). Likewise if
+your repository contains a CVSROOT.adm directory instead of a CVSROOT
+directory, you need to rename it.
+
+Fri Oct 21 20:58:54 1994 Brian Berliner <berliner@sun.com>
+
+ * Changes between CVS 1.3 and CVS 1.4 Alpha-2
+
+ * A new program, "cvsbug", is provided to let you send bug reports
+ directly to the CVS maintainers. Please use it instead of sending
+ mail to the info-cvs mailing list. If your build fails, you may
+ have to invoke "cvsbug" directly from the "src" directory as
+ "src/cvsbug.sh".
+
+ * A new User's Guide and Tutorial, written by Per Cederqvist
+ <ceder@signum.se> of Signum Support. See the "doc" directory. A
+ PostScript version is included as "doc/cvs.ps".
+
+ * The Frequesntly Asked Questions file, FAQ, has been added to the
+ release. Unfortunately, its contents are likely out-of-date.
+
+ * The "cvsinit" shell script is now installed in the $prefix/bin
+ directory like the other programs. You can now create new
+ CVS repositories with great ease.
+
+ * Index: lines are now printed on output from 'diff' and 'rdiff',
+ in order to facilitate application of patches to multiple subdirs.
+
+ * Support for a ~/.cvsrc file, which allows you to specify options
+ that are always supposed to be given to a specific command. This
+ feature shows the non-orthogonality of the option set, since while
+ there may be an option to turn something on, the option to turn
+ that same thing off may not exist.
+
+ * You can now list subdirectories that you wish to ignore in a
+ modules listing, such as:
+
+ gcc -a gnu/gcc, !gnu/gcc/testsuites
+
+ which will check out everything underneath gnu/gcc, except
+ everything underneath gnu/gcc/testsuites.
+
+ * It is now much harder to accidentally overwrite an existing tag
+ name, since attempting to move a tag name will result in a error,
+ unless the -F (force) flag is given to the tag subcommands.
+
+ * Better error checking on matching of the repository used to
+ check code out from against the repository the current cvs
+ commnands would use. (Thanks to Mark Baushke <mdb@cisco.com>)
+
+ * Better support for sites with multiple CVSROOT repositories has
+ been contributed. The file "CVS/Root" in your working directory
+ is created to hold the full path to the CVS repository and a
+ simple check is made against your current CVSROOT setting.
+
+ * You can now specify an RCS keyword substitution value when you
+ import files into the repository.
+
+ * Uses a much newer version of Autoconf, and conforms to the GNU
+ coding standards much more closely. No, it still doesn't have
+ long option names.
+
+ * Code cleanup. Many passes through gcc -Wall helped to identify
+ a number of questionable constructs. Most arbitrary length limits
+ were removed.
+
+ * Profiling to determine bottlenecks helped to identify the best
+ places to spend time speeding up the code, which was then done. A
+ number of performance enhancements in filename matching have sped
+ up checkouts.
+
+ * Many more contributions have been added to the "contrib"
+ directory. See the README file in that directory for more
+ information.
+
+ * "cvs commit" will try harder to not change the file's
+ modification time after the commit. If the file does not change
+ as a result of the commit operation, CVS will preserve the
+ original modification time, thus speeding up future make-type
+ builds.
+
+ * "cvs commit" now includes any removed files in the (optional)
+ pre-commit checking program that may be invoked. Previously, only
+ added and modified files were included.
+
+ * It is now possible to commit a file directly onto the trunk at a
+ specific revision level by doing "cvs commit -r3.0 file.c", where
+ "3.0" specifies the revision you wish to create. The file must be
+ up-to-date with the current head of the trunk for this to succeed.
+
+ * "cvs commit" will now function with a pre-commit program that
+ has arguments specified in the "commitinfo" file.
+
+ * The "mkmodules" program will now look within the
+ $CVSROOT/CVSROOT/checkoutlist" file for any additional files that
+ should be automatically checked out within CVSROOT; mkmodules also
+ tries harder to preserve any execute bits the files may have
+ originally had.
+
+ * "cvs diff" is much more accurate about its exit status now. It
+ now returns the maximum exit status of any invoked diff.
+
+ * The "-I !" option is now supported for the import and update
+ commands correctly. It will properly clear the ignore list now.
+
+ * Some problems with "cvs import" handling of .cvsignore have been
+ fixed; as well, some rampant recursion problems with import have
+ also been fixed.
+
+ * "cvs rdiff" (aka "cvs patch") now tries to set the modify time
+ of any temporary files it uses to match those specified for the
+ particular revision. This allows a more accurate patch image to
+ be created.
+
+ * "cvs status" has improved revision descriptions. "Working
+ revision" is used for the revision of the working file that you
+ edit directly; "Repository revision" is the revision of the file
+ with the $CVSROOT source repository. Also, the output is clearer
+ with regard to sticky and branch revisions.
+
+ * CVS no longer dumps core when given a mixture of directories and
+ files in sub-directories (as in "cvs ci file1 dir1/file2").
+ Instead, arguments are now clumped into their respective directory
+ and operated on in chunks, together.
+
+ * If the CVSEDITOR environment variable is set, that editor is
+ used for log messages instead of the EDITOR environment variable.
+ This makes it easy to substitute intelligent programs to make more
+ elaborate log messages. Contributed by Mark D Baushke
+ (mdb@cisco.com).
+
+ * Command argument changes:
+ cvs: The "-f" option has been added to ignore
+ the ~/.cvsrc file.
+ commit: Renamed the "-f logfile" option to the
+ "-F logfile" option. Added the "-f"
+ option to force a commit of the specified
+ files (this disables recursion).
+ history: Added "-t timezone" option to force any
+ date-specific output into the specified
+ timezone.
+ import: Added "-d" option to use the file's
+ modification time as the time of the
+ import. Added "-k sub" option to set the
+ default RCS keyword substitution mode for
+ newly-created files.
+ remove: Added "-f" option to force the file's
+ automatic removal if it still exists in
+ the working directory (use with caution).
+ rtag: Added "-F" option to move the tag if it
+ already exists -- new default is to NOT
+ move tags automatically.
+ tag: Added "-F" option to move the tag if it
+ already exists -- new default is to NOT
+ move tags automatically.
+
+Tue Apr 7 15:55:25 1992 Brian Berliner (berliner at sun.com)
+
+ * Changes between CVS 1.3 Beta-3 and official CVS 1.3!
+
+ * A new shell script is provided, "./cvsinit", which can be run at
+ install time to help setup your $CVSROOT area. This can greatly
+ ease your entry into CVS usage.
+
+ * The INSTALL file has been updated to include the machines on
+ which CVS has compiled successfully. I think CVS 1.3 is finally
+ portable. Thanks to all the Beta testers!
+
+ * Support for the "editinfo" file was contributed. This file
+ (located in $CVSROOT/CVSROOT) can be used to specify a special
+ "editor" to run on a per-directory basis within the repository,
+ instead of the usual user's editor. As such, it can verify that
+ the log message entered by the user is of the appropriate form
+ (contains a bugid and test validation, for example).
+
+ * The manual pages cvs(1) and cvs(5) have been updated.
+
+ * The "mkmodules" command now informs you when your modules file
+ has duplicate entries.
+
+ * The "add" command now preserves any per-directory sticky tag when
+ you add a new directory to your checked-out sources.
+
+ * The "admin" command is now a fully recursive interface to the
+ "rcs" program which operates on your checked-out sources. It no
+ longer requires you to specify the full path to the RCS file.
+
+ * The per-file sticky tags can now be effectively removed with
+ "cvs update -A file", even if you had checked out the whole
+ directory with a per-directory sticky tag. This allows a great
+ deal of flexibility in managing the revisions that your checked-out
+ sources are based upon (both per-directory and per-file sticky
+ tags).
+
+ * The "cvs -n commit" command now works, to show which files are
+ out-of-date and will cause the real commit to fail, or which files
+ will fail any pre-commit checks. Also, the "cvs -n import ..."
+ command will now show you what it would've done without actually
+ doing it.
+
+ * Doing "cvs commit modules" to checkin the modules file will no
+ properly run the "mkmodules" program (assuming you have setup your
+ $CVSROOT/CVSROOT/modules file to do so).
+
+ * The -t option in the modules file (which specifies a program to
+ run when you do a "cvs rtag" operation on a module) now gets the
+ symbolic tag as the second argument when invoked.
+
+ * When the source repository is locked by another user, that user's
+ login name will be displayed as the holder of the lock.
+
+ * Doing "cvs checkout module/file.c" now works even if
+ module/file.c is in the Attic (has been removed from main-line
+ development).
+
+ * Doing "cvs commit */Makefile" now works as one would expect.
+ Rather than trying to commit everything recursively, it will now
+ commit just the files specified.
+
+ * The "cvs remove" command is now fully recursive. To schedule a
+ file for removal, all you have to do is "rm file" and "cvs rm".
+ With no arguments, "cvs rm" will schedule all files that have been
+ physically removed for removal from the source repository at the
+ next "cvs commit".
+
+ * The "cvs tag" command now prints "T file" for each file that was
+ tagged by this invocation and "D file" for each file that had the
+ tag removed (as with "cvs tag -d").
+
+ * The -a option has been added to "cvs rtag" to force it to clean
+ up any old, matching tags for files that have been removed (in the
+ Attic) that may not have been touched by this tag operation. This
+ can help keep a consistent view with your tag, even if you re-use
+ it frequently.
+
+Sat Feb 29 16:02:05 1992 Brian Berliner (berliner at sun.com)
+
+ * Changes between CVS 1.3 Beta-2 and CVS 1.3 Beta-3
+
+ * Many portability fixes, thanks to all the Beta testers! With any
+ luck, this Beta release will compile correctly on most anything.
+ Hey, what are we without our dreams.
+
+ * CVS finally has support for doing isolated development on a
+ branch off the current (or previous!) revisions. This is also
+ extremely nice for generating patches for previously released
+ software while development is progressing on the next release.
+ Here's an example of creating a branch to fix a patch with the 2.0
+ version of the "foo" module, even though we are already well into
+ the 3.0 release. Do:
+
+ % cvs rtag -b -rFOO_2_0 FOO_2_0_Patch foo
+ % cvs checkout -rFOO_2_0_Patch foo
+ % cd foo
+ [[ hack away ]]
+ % cvs commit
+
+ A physical branch will be created in the RCS file only when you
+ actually commit the change. As such, forking development at some
+ random point in time is extremely light-weight -- requiring just a
+ symbolic tag in each file until a commit is done. To fork
+ development at the currently checked out sources, do:
+
+ % cvs tag -b Personal_Hack
+ % cvs update -rPersonal_Hack
+ [[ hack away ]]
+ % cvs commit
+
+ Now, if you decide you want the changes made in the Personal_Hack
+ branch to be merged in with other changes made in the main-line
+ development, you could do:
+
+ % cvs commit # to make Personal_Hack complete
+ % cvs update -A # to update sources to main-line
+ % cvs update -jPersonal_Hack # to merge Personal_Hack
+
+ to update your checked-out sources, or:
+
+ % cvs checkout -jPersonal_Hack module
+
+ to checkout a fresh copy.
+
+ To support this notion of forked development, CVS reserves
+ all even-numbered branches for its own use. In addition, CVS
+ reserves the ".0" and ".1" branches. So, if you intend to do your
+ own branches by hand with RCS, you should use odd-numbered branches
+ starting with ".3", as in "1.1.3", "1.1.5", 1.2.9", ....
+
+ * The "cvs commit" command now supports a fully functional -r
+ option, allowing you to commit your changes to a specific numeric
+ revision or symbolic tag with full consistency checks. Numeric
+ tags are useful for bringing your sources all up to some revision
+ level:
+
+ % cvs commit -r2.0
+
+ For symbolic tags, you can only commit to a tag that references a
+ branch in the RCS file. One created by "cvs rtag -b" or from
+ "cvs tag -b" is appropriate (see below).
+
+ * Roland Pesch <pesch@cygnus.com> and K. Richard Pixley
+ <rich@cygnus.com> were kind enough to contribute two new manual
+ pages for CVS: cvs(1) and cvs(5). Most of the new CVS 1.3 features
+ are now documented, with the exception of the new branch support
+ added to commit/rtag/tag/checkout/update.
+
+ * The -j options of checkout/update have been added. The "cvs join"
+ command has been removed.
+
+ With one -j option, CVS will merge the changes made between the
+ resulting revision and the revision that it is based on (e.g., if
+ the tag refers to a branch, CVS will merge all changes made in
+ that branch into your working file).
+
+ With two -j options, 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:
+
+ % cvs update -j1.5 -j1.3 foo.c # note the order...
+
+ In addition, each -j 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, as in:
+
+ -jSymbolic_Tag:Date_Specifier
+
+ An example might be what "cvs import" tells you to do when you have
+ just imported sources that have conflicts with local changes:
+
+ % cvs checkout -jTAG:yesterday -jTAG module
+
+ which tells CVS to merge in the changes made to the branch
+ specified by TAG in the last 24 hours. If this is not what is
+ intended, substitute "yesterday" for whatever format of date that
+ is appropriate, like:
+
+ % cvs checkout -jTAG:'1 week ago' -jTAG module
+
+ * "cvs diff" now supports the special tags "BASE" and "HEAD". So,
+ the command:
+
+ % cvs diff -u -rBASE -rHEAD
+
+ will effectively show the changes made by others (in unidiff
+ format) that will be merged into your working sources with your
+ next "cvs update" command. "-rBASE" resolves to the revision that
+ your working file is based on. "-rHEAD" resolves to the current
+ head of the branch or trunk that you are working on.
+
+ * The -P option of "cvs checkout" now means to Prune empty
+ directories, as with "update". The default is to not remove empty
+ directories. However, if you do "checkout" with any -r options, -P
+ will be implied. I.e., checking out with a tag will cause empty
+ directories to be pruned automatically.
+
+ * The new file INSTALL describes how to install CVS, including
+ detailed descriptions of interfaces to "configure".
+
+ * The example loginfo file in examples/loginfo has been updated to
+ use the perl script included in contrib/log.pl. The nice thing
+ about this log program is that it records the revision numbers of
+ your change in the log message.
+
+ Example files for commitinfo and rcsinfo are now included in the
+ examples directory.
+
+ * All "#if defined(__STDC__) && __STDC__ == 1" lines have been
+ changed to be "#if __STDC__" to fix some problems with the former.
+
+ * The lib/regex.[ch] files have been updated to the 1.3 release of
+ the GNU regex package.
+
+ * The ndbm emulation routines included with CVS 1.3 Beta-2 in the
+ src/ndbm.[ch] files has been moved into the src/myndbm.[ch] files
+ to avoid any conflict with the system <ndbm.h> header file. If
+ you had a previous CVS 1.3 Beta release, you will want to "cvs
+ remove ndbm.[ch]" form your copy of CVS as well.
+
+ * "cvs add" and "cvs remove" are a bit more verbose, telling you
+ what to do to add/remove your file permanently.
+
+ * We no longer mess with /dev/tty in "commit" and "add".
+
+ * More things are quiet with the -Q option set.
+
+ * New src/config.h option: If CVS_BADROOT is set, CVS will not
+ allow people really logged in as "root" to commit changes.
+
+ * "cvs diff" exits with a status of 0 if there were no diffs, 1 if
+ there were diffs, and 2 if there were errors.
+
+ * "cvs -n diff" is now supported so that you can still run diffs
+ even while in the middle of committing files.
+
+ * Handling of the CVS/Entries file is now much more robust.
+
+ * The default file ignore list now includes "*.so".
+
+ * "cvs import" did not expand '@' in the log message correctly. It
+ does now. Also, import now uses the ignore file facility
+ correctly.
+
+ Import will now tell you whether there were conflicts that need to
+ be resolved, and how to resolve them.
+
+ * "cvs log" has been changed so that you can "log" things that are
+ not a part of the current release (in the Attic).
+
+ * If you don't change the editor message on commit, CVS now prompts
+ you with the choice:
+
+ !)reuse this message unchanged for remaining dirs
+
+ which allows you to tell CVS that you have no intention of changing
+ the log message for the remainder of the commit.
+
+ * It is no longer necessary to have CVSROOT set if you are using
+ the -H option to get Usage information on the commands.
+
+ * Command argument changes:
+ checkout: -P handling changed as described above.
+ New -j option (up to 2 can be specified)
+ for doing rcsmerge kind of things on
+ checkout.
+ commit: -r option now supports committing to a
+ numeric or symbolic tags, with some
+ restrictions. Full consistency checks will
+ be done.
+ Added "-f logfile" option, which tells
+ commit to glean the log message from the
+ specified file, rather than invoking the
+ editor.
+ rtag: Added -b option to create a branch tag,
+ useful for creating a patch for a previous
+ release, or for forking development.
+ tag: Added -b option to create a branch tag,
+ useful for creating a patch for a previous
+ release, or for forking development.
+ update: New -j option (up to 2 can be specified)
+ for doing rcsmerge kind of things on
+ update.
+
+Thu Jan 9 10:51:35 MST 1992 Jeff Polk (polk at BSDI.COM)
+
+ * Changes between CVS 1.3 Beta-1 and CVS 1.3 Beta-2
+
+ * Thanks to K. Richard Pixley at Cygnus we now have function
+ prototypes in all the files
+
+ * Some small changes to configure for portability. There have
+ been other portability problems submitted that have not been fixed
+ (Brian will be working on those). Additionally all __STDC__
+ tests have been modified to check __STDC__ against the constant 1
+ (this is what the Second edition of K&R says must be true).
+
+ * Lots of additional error checking for forked processes (run_exec)
+ (thanks again to K. Richard Pixley)
+
+ * Lots of miscellaneous bug fixes - including but certainly not
+ limited to:
+ various commit core dumps
+ various update core dumps
+ bogus results from status with numeric sticky tags
+ commitprog used freed memory
+ Entries file corruption caused by No_Difference
+ commit to revision broken (now works if branch exists)
+ ignore file processing broken for * and !
+ ignore processing didn't handle memory reasonably
+ miscellaneous bugs in the recursion processor
+ file descriptor leak in ParseInfo
+ CVSROOT.adm->CVSROOT rename bug
+ lots of lint fixes
+
+ * Reformatted all the code in src (with GNU indent) and then
+ went back and fixed prototypes, etc since indent gets confused. The
+ rationale is that it is better to do it sooner than later and now
+ everything is consistent and will hopefully stay that way.
+ The basic options to indent were: "-bad -bbb -bap -cdb -d0 -bl -bli0
+ -nce -pcs -cs -cli4 -di1 -nbc -psl -lp -i4 -ip4 -c41" and then
+ miscellaneous formatting fixes were applied. Note also that the
+ "-nfc1" or "-nfca" may be appropriate in files where comments have
+ been carefully formatted (e.g, modules.c).
+
+Sat Dec 14 20:35:22 1991 Brian Berliner (berliner at sun.com)
+
+ * Changes between CVS 1.2 and CVS 1.3 Beta are described here.
+
+ * Lots of portability work. CVS now uses the GNU "configure"
+ script to dynamically determine the features provided by your
+ system. It probably is not foolproof, but it is better than
+ nothing. Please let me know of any portability problems. Some
+ file names were changed to fit within 14-characters.
+
+ * CVS has a new RCS parser that is much more flexible and
+ extensible. It should read all known RCS ",v" format files.
+
+ * Most of the commands now are fully recursive, rather than just
+ operating on the current directory alone. This includes "commit",
+ which makes it real easy to do an "atomic" commit of all the
+ changes made to a CVS hierarchy of sources. Most of the commands
+ also correctly handle file names that are in directories other than
+ ".", including absolute path names. Commands now accept the "-R"
+ option to force recursion on (though it is always the default now)
+ and the "-l" option to force recursion off, doing just "." and not
+ any sub-directories.
+
+ * CVS supports many of the features provided with the RCS 5.x
+ distribution - including the new "-k" keyword expansion options. I
+ recommend using RCS 5.x (5.6 is the current official RCS version)
+ and GNU diff 1.15 (or later) distributions with CVS.
+
+ * Checking out files with symbolic tags/dates is now "sticky", in
+ that CVS remembers the tag/date used for each file (and directory)
+ and will use that tag/date automatically on the next "update" call.
+ This stickyness also holds for files checked out with the the new
+ RCS 5.x "-k" options.
+
+ * The "cvs diff" command now recognizes all of the rcsdiff 5.x
+ options. Unidiff format is available by installing the GNU
+ diff 1.15 distribution.
+
+ * The old "CVS.adm" directories created on checkout are now called
+ "CVS" directories, to look more like "RCS" and "SCCS". Old CVS.adm
+ directories are automagically converted to CVS directories. The
+ old "CVSROOT.adm" directory within the source repository is
+ automagically changed into a "CVSROOT" directory as well.
+
+ * Symbolic links in the source repository are fully supported ONLY
+ if you use RCS 5.6 or later and (of course) your system supports
+ symlinks.
+
+ * A history database has been contributed which maintains the
+ history of certain CVS operations, as well as providing a wide array
+ of querying options.
+
+ * The "cvs" program has a "-n" option which can be used with the
+ "update" command to show what would be updated without actually
+ doing the update, like: "cvs -n update". All usage statements
+ have been cleaned up and made more verbose.
+
+ * The module database parsing has been rewritten. The new format
+ is compatible with the old format, but with much more
+ functionality. It allows modules to be created that grab pieces or
+ whole directories from various different parts of your source
+ repository. Module-relative specifications are also correctly
+ recognized now, like "cvs checkout module/file.c".
+
+ * A configurable template can be specified such that on a "commit",
+ certain directories can supply a template that the user must fill
+ before completing the commit operation.
+
+ * A configurable pre-commit checking program can be specified which
+ will run to verify that a "commit" can happen. This feature can be
+ used to restrict certain users from changing certain pieces of the
+ source repository, or denying commits to the entire source
+ repository.
+
+ * The new "cvs export" command is much like "checkout", but
+ establishes defaults suitable for exporting code to others (expands
+ out keywords, forces the use of a symbolic tag, and does not create
+ "CVS" directories within the checked out sources.
+
+ * The new "cvs import" command replaces the deprecated "checkin"
+ shell script and is used to import sources into CVS control. It is
+ also much faster for the first-time import. Some algorithmic
+ improvements have also been made to reduce the number of
+ conflicting files on next-time imports.
+
+ * The new "cvs admin" command is basically an interface to the
+ "rcs" program. (Not yet implemented very well).
+
+ * Signal handling (on systems with BSD or POSIX signals) is much
+ improved. Interrupting CVS now works with a single interrupt!
+
+ * CVS now invokes RCS commands by direct fork/exec rather than
+ calling system(3). This improves performance by removing a call to
+ the shell to parse the arguments.
+
+ * Support for the .cvsignore file has been contributed. CVS will
+ now show "unknown" files as "? filename" as the result of an "update"
+ command. The .cvsignore file can be used to add files to the
+ current list of ignored files so that they won't show up as unknown.
+
+ * Command argument changes:
+ cvs: Added -l to turn off history logging.
+ Added -n to show what would be done without actually
+ doing anything.
+ Added -q/-Q for quiet and really quiet settings.
+ Added -t to show debugging trace.
+ add: Added -k to allow RCS 5.x -k options to be specified.
+ admin: New command; an interface to rcs(1).
+ checkout: Added -A to reset sticky tags/date/options.
+ Added -N to not shorten module paths.
+ Added -R option to force recursion.
+ Changed -p (prune empty directories) to -P option.
+ Changed -f option; forcing tags match is now default.
+ Added -p option to checkout module to standard output.
+ Added -s option to cat the modules db with status.
+ Added -d option to checkout in the specified directory.
+ Added -k option to use RCS 5.x -k support.
+ commit: Removed -a option; use -l instead.
+ Removed -f option.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ If no files specified, commit is recursive.
+ diff: Now recognizes all RCS 5.x rcsdiff options.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ history: New command; displays info about CVS usage.
+ import: Replaces "checkin" shell script; imports sources
+ under CVS control. Ignores files on the ignore
+ list (see -I option or .cvsignore description above).
+ export: New command; like "checkout", but w/special options
+ turned on by default to facilitate exporting sources.
+ join: Added -B option to join from base of the branch;
+ join now defaults to only joining with the top two
+ revisions on the branch.
+ Added -k option for RCS 5.x -k support.
+ log: Supports all RCS 5.x options.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ patch: Changed -f option; forcing tags match is now default.
+ Added -c option to force context-style diffs.
+ Added -u option to support unidiff-style diffs.
+ Added -V option to support RCS specific-version
+ keyword expansion formats.
+ Added -R option to force recursion.
+ remove: No option changes. It's a bit more verbose.
+ rtag: Equivalent to the old "cvs tag" command.
+ No option changes. It's a lot faster for re-tag.
+ status: New output formats with more information.
+ Added -l option to disable recursion.
+ Added -R option to force recursion.
+ Added -v option to show symbolic tags for files.
+ tag: Functionality changed to tag checked out files
+ rather than modules; use "rtag" command to get the
+ old "cvs tag" behaviour.
+ update: Added -A to reset sticky tags/date/options.
+ Changed -p (prune empty directories) to -P option.
+ Changed -f option; forcing tags match is now default.
+ Added -p option to checkout module to standard output.
+ Added -I option to add files to the ignore list.
+ Added -R option to force recursion.
+
+ Major Contributors:
+
+ * Jeff Polk <polk@bsdi.com> rewrote most of the grody code of CVS
+ 1.2. He made just about everything dynamic (by using malloc),
+ added a generic hashed list manager, re-wrote the modules database
+ parsing in a compatible - but extended way, generalized directory
+ hierarchy recursion for virtually all the commands (including
+ commit!), generalized the loginfo file to be used for pre-commit
+ checks and commit templates, wrote a new and flexible RCS parser,
+ fixed an uncountable number of bugs, and helped in the design of
+ future CVS features. If there's anything gross left in CVS, it's
+ probably my fault!
+
+ * David G. Grubbs <dgg@odi.com> contributed the CVS "history" and
+ "release" commands. As well as the ever-so-useful "-n" option of
+ CVS which tells CVS to show what it would do, without actually
+ doing it. He also contributed support for the .cvsignore file.
+
+ * Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> wrote and
+ contributed the code in lib/sighandle.c. I added support for
+ POSIX, BSD, and non-POSIX/non-BSD systems.
+
+ * Free Software Foundation contributed the "configure" script and
+ other compatibility support in the "lib" directory, which will help
+ make CVS much more portable.
+
+ * Many others have contributed bug reports and enhancement requests.
+ Some have even submitted actual code which I have not had time yet
+ to integrate into CVS. Maybe for the next release.
+
+ * Thanks to you all!
+
+Wed Feb 6 10:10:58 1991 Brian Berliner (berliner at sun.com)
+
+ * Changes from CVS 1.0 Patchlevel 1 to CVS 1.0 Patchlevel 2; also
+ known as "Changes from CVS 1.1 to CVS 1.2".
+
+ * Major new support with this release is the ability to use the
+ recently-posted RCS 5.5 distribution with CVS 1.2. See below for
+ other assorted bug-fixes that have been thrown in.
+
+ * ChangeLog (new): Added Emacs-style change-log file to CVS 1.2
+ release. Chronological description of changes between release.
+
+ * README: Small fixes to installation instructions. My email
+ address is now "berliner@sun.com".
+
+ * src/Makefile: Removed "rcstime.h". Removed "depend" rule.
+
+ * src/partime.c: Updated to RCS 5.5 version with hooks for CVS.
+ * src/maketime.c: Updated to RCS 5.5 version with hooks for CVS.
+ * src/rcstime.h: Removed from the CVS 1.2 distribution.
+ Thanks to Paul Eggert <eggert@twinsun.com> for these changes.
+
+ * src/checkin.csh: Support for RCS 5.5 parsing.
+ Thanks to Paul Eggert <eggert@twinsun.com> for this change.
+
+ * src/collect_sets.c (Collect_Sets): Be quieter if "-f" option is
+ specified. When checking out files on-top-of other files that CVS
+ doesn't know about, run a diff in the hopes that they are really
+ the same file before aborting.
+
+ * src/commit.c (branch_number): Fix for RCS 5.5 parsing.
+ Thanks to Paul Eggert <eggert@twinsun.com> for this change.
+
+ * src/commit.c (do_editor): Bug fix - fprintf missing argument
+ which sometimes caused core dumps.
+
+ * src/modules.c (process_module): Properly NULL-terminate
+ update_dir[] in all cases.
+
+ * src/no_difference.c (No_Difference): The wrong RCS revision was
+ being registered in certain (strange) cases.
+
+ * src/patch.c (get_rcsdate): New algorithm. No need to call
+ maketime() any longer.
+ Thanks to Paul Eggert <eggert@twinsun.com> for this change.
+
+ * src/patchlevel.h: Increased patch level to "2".
+
+ * src/subr.c (isdir, islink): Changed to compare stat mode bits
+ correctly.
+
+ * src/tag.c (tag_file): Added support for following symbolic links
+ that are in the master source repository when tagging. Made tag
+ somewhat quieter in certain cases.
+
+ * src/update.c (update_process_lists): Unlink the user's file if it
+ was put on the Wlist, meaning that the user's file is not modified
+ and its RCS file has been removed by someone else.
+
+ * src/update.c (update): Support for "cvs update dir" to correctly
+ just update the argument directory "dir".
+
+ * src/cvs.h: Fixes for RCS 5.5 parsing.
+ * src/version_number.c (Version_Number): Fixes for parsing RCS 5.5
+ and older RCS-format files.
+ Thanks to Paul Eggert <eggert@twinsun.com> for these changes.
+
+ * src/version_number.c (Version_Number): Bug fixes for "-f" option.
+ Bug fixes for parsing with certain branch numbers. RCS
+ revision/symbol parsing is much more solid now.
+
+Wed Feb 14 10:01:33 1990 Brian Berliner (berliner at sun.com)
+
+ * Changes from CVS 1.0 Patchlevel 0 to CVS 1.0 Patchlevel 1; also
+ known as "Changes from CVS 1.0 to CVS 1.1".
+
+ * src/patch.c (get_rcsdate): Portability fix. Replaced call to
+ timelocal() with call to maketime().
+
+Mon Nov 19 23:15:11 1990 Brian Berliner (berliner at prisma.com)
+
+ * Sent CVS 1.0 release to comp.sources.unix moderator and FSF.
+
+ * Special thanks to Dick Grune <dick@cs.vu.nl> for his work on the
+ 1986 version of CVS and making it available to the world. Dick's
+ version is available on uunet.uu.net in the
+ comp.sources.unix/volume6/cvs directory.
+
+$CVSid: @(#)ChangeLog 1.35 94/10/22 $
diff --git a/contrib/cvs/PROJECTS b/contrib/cvs/PROJECTS
new file mode 100644
index 0000000..de76576
--- /dev/null
+++ b/contrib/cvs/PROJECTS
@@ -0,0 +1,59 @@
+This is a list of projects for CVS. In general, unlike the things in
+the TODO file, these need more analysis to determine if and how
+worthwhile each task is.
+
+I haven't gone through TODO, but it's likely that it has entries that
+are actually more appropriate for this list.
+
+0. Improved Efficency
+
+* CVS uses a single doubly linked list/hash table data structure for
+ all of its lists. Since the back links are only used for deleting
+ list nodes it might be beneficial to use singly linked lists or a
+ tree structure. Most likely, a single list implementation will not
+ be appropriate for all uses.
+
+ One easy change would be to remove the "type" field out of the list
+ and node structures. I have found it to be of very little use when
+ debugging, and each instance eats up a word of memory. This can add
+ up and be a problem on memory-starved machines.
+
+ Profiles have shown that on fast machines like the Alpha, fsortcmp()
+ is one of the hot spots.
+
+* Dynamically allocated character strings are created, copied, and
+ destroyed throughout CVS. The overhead of malloc()/strcpy()/free()
+ needs to be measured. If significant, it could be minimized by using a
+ reference counted string "class".
+
+* File modification time is stored as a character string. It might be
+ worthwile to use a time_t internally if the time to convert a time_t
+ (from struct stat) to a string is greater that the time to convert a
+ ctime style string (from the entries file) to a time_t. time_t is
+ an machine-dependant type (although it's pretty standard on UN*X
+ systems), so we would have to have different conversion routines.
+ Profiles show that both operations are called about the same number
+ of times.
+
+* stat() is one of the largest performance bottlenecks on systems
+ without the 4.4BSD filesystem. By spliting information out of
+ the filesystem (perhaps the "rename database") we should be
+ able to improve performance.
+
+* Parsing RCS files is very expensive. This might be unnecessary if
+ RCS files are only used as containers for revisions, and tag,
+ revision, and date information was available in easy to read
+ (and modify) indexes. This becomes very apparent with files
+ with several hundred revisions.
+
+* A RCS "library", so CVS could operate on RCS files directly.
+
+ CVS parses RCS files in order to determine if work needs to be done,
+ and then RCS parses the files again when it is performing the work.
+ This would be much faster if CVS could do whatever is necessary
+ by itself.
+
+1. Improved testsuite/sanity check script
+
+* Need to use a code coverage tool to determine how much the sanity
+ script tests, and fill in the holes.
diff --git a/contrib/cvs/README b/contrib/cvs/README
new file mode 100644
index 0000000..5c4c9b6
--- /dev/null
+++ b/contrib/cvs/README
@@ -0,0 +1,222 @@
+$CVSid: @(#)README 1.32 94/10/22 $
+
+ CVS Kit
+
+ Copyright (c) 1993-1994 Brian Berliner
+ Copyright (c) 1992 Brian Berliner and Jeff Polk
+ Copyright (c) 1989-1992, Brian Berliner
+ All Rights Reserved
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+-------------------------------------------------------------------------------
+
+Welcome to CVS!
+
+Bug reports are accepted, however note that someone may or may not
+feel like taking care of your bug report. Support contracts are
+available from Cyclic Software (http://www.cyclic.com or
+info@cyclic.com).
+
+To report bugs send mail to bug-cvs@prep.ai.mit.edu, or run the "cvsbug"
+program and fill out the template:
+
+ $ cvsbug
+
+The "cvsbug" program is installed in the same location as the "cvs"
+program. If your installation failed, you may need to run "cvsbug"
+directly out of the "src" directory as "src/cvsbug.sh". This is also
+the procedure for submitting suggested changes to CVS (see the file
+HACKING for more details). Note that all submitted changes may be
+distributed under the terms of the GNU Public License, so if you don't
+like this, don't submit them.
+
+Please consult the INSTALL file for information on tested
+configurations. If you have a comment about an already tested
+configuration, or have tried CVS on a new configuration, please write
+to the above address and let us know! Free software only works if we
+all help out.
+
+Finally, we cannot guarantee that this release will not completely wipe out
+all of your work from your system. We do some simple testing before each
+release, but you are completely on your own. We recommend testing this
+release on a source repository that is not critical to your work. THIS
+SOFTWARE IS SUPPLIED COMPLETELY "AS IS". NO WARRANTY....
+
+Thanks for your support!
+
+ -The CVS Team
+
+-------------------------------------------------------------------------------
+
+CVS is a freely available collection of programs that provide for software
+release and revision control functions in a UNIX environment. It is
+designed to work on top of the RCS distribution, V4 and later. CVS does
+understand how to parse older RCS formats, but cannot do any of the fancier
+features (like vendor branch support) without RCS branch support.
+
+Short blurb from the manual page (larger blurb is included there):
+ cvs is a front end to the rcs(1) revision control system
+ which extends the notion of revision control from a collec-
+ tion of files in a single directory to a hierarchical col-
+ lection of directories consisting of revision controlled
+ files. These directories and files can be combined together
+ to form a software release. cvs provides the functions
+ necessary to manage these software releases and to control
+ the concurrent editing of source files among multiple
+ software developers.
+
+And a whole lot more. See the doc/cvs.texinfo file for more information.
+
+-------------------------------------------------------------------------------
+
+Notes to people upgrading from a previous release of CVS:
+
+See the NEWS file for a description of features new in this version.
+
+The repository format is compatible going back to CVS 1.3. But see
+the "Watches compatibility" section of doc/cvs.texinfo if you have
+copies of CVS 1.6 or older and you want to use the optional developer
+communication features.
+
+The working directory format is compatible going back to CVS 1.5. It
+did change between CVS 1.3 and CVS 1.5. If you run CVS 1.5 or newer
+on a working directory checked out with CVS 1.3, CVS will convert it,
+but to go back to CVS 1.3 you need to check out a new working
+directory with CVS 1.3.
+
+The remote protocol is interoperable going back to CVS 1.5. Using a
+client or server older than 1.5 is deprecated and may fail to work at
+some point in the future (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.
+
+-------------------------------------------------------------------------------
+
+Installation:
+
+Please read the INSTALL file for installation instructions. Brief summary:
+
+ $ ./configure
+ $ make
+ $ make check # optional, long-running, step
+ $ make install
+ $ cvsinit
+
+The documentation is in the doc subdirectory. cvs.texinfo is the main
+manual; cvs.info* and cvs.ps are the info and postscript versions,
+respectively, generated from cvs.texinfo. The postscript version is
+for A4 paper; if you want US letter size, you need to remove the line
+@afourpaper from cvs.texinfo and re-generate cvs.ps using TeX.
+
+-------------------------------------------------------------------------------
+
+* How do I get up-to-date information and information about other
+versions of CVS?
+
+On the web, http://www.loria.fr/~molli/cvs-index.html.
+
+The mailing list for CVS is info-cvs@prep.ai.mit.edu. Send
+subscription and removal requests for that list to
+info-cvs-request@prep.ai.mit.edu.
+
+The newsgroup for CVS (and other configuration management systems) is
+comp.software.config-mgmt. There is not yet a CVS-specific newsgroup,
+but perhaps if comp.software.config-mgmt gets enough CVS discussion,
+then it will be possible to create one.
+
+-------------------------------------------------------------------------------
+
+Credits:
+
+The conflict-resolution algorithms and much of the administrative file
+definitions of CVS were based on the original package written by Dick Grune
+at Vrije Universiteit in Amsterdam <dick@cs.vu.nl>, and posted to
+comp.sources.unix in the volume 6 release sometime in 1986. This original
+version was a collection of shell scripts. I am thankful that Dick made
+his work available.
+
+Brian Berliner from Prisma, Inc. (now at Sun Microsystems, Inc.)
+<berliner@sun.com> converted the original CVS shell scripts into reasonably
+fast C and added many, many features to support software release control
+functions. See the manual page in the "man" directory. A copy of the
+USENIX article presented at the Winter 1990 USENIX Conference, Washington
+D.C., is included in the "doc" directory.
+
+Jeff Polk from BSDI <polk@bsdi.com> converted the CVS 1.2
+sources into much more readable and maintainable C code. He also added a
+whole lot of functionality and modularity to the code in the process.
+See the ChangeLog file.
+
+david d `zoo' zuhn <zoo@armadillo.com> contributed the working base code
+for CVS 1.4 Alpha. His work carries on from work done by K. Richard Pixley
+and others at Cygnus Support. The CVS 1.4 upgrade is due in large part to
+Zoo's efforts.
+
+David G. Grubbs <dgg@odi.com> contributed the CVS "history" and "release"
+commands. As well as the ever-so-useful "-n" option of CVS which tells CVS
+to show what it would do, without actually doing it. He also contributed
+support for the .cvsignore file.
+
+The Free Software Foundation (GNU) contributed most of the portability
+framework that CVS now uses. This can be found in the "configure" script,
+the Makefile's, and basically most of the "lib" directory.
+
+K. Richard Pixley, Cygnus Support <rich@cygnus.com> contributed many bug
+fixes/enhancement as well as completing early reviews of the CVS 1.3 manual
+pages.
+
+Roland Pesch, then of Cygnus Support <roland@wrs.com> contributed brand new
+cvs(1) and cvs(5) manual pages. We should all thank him for saving us from
+my poor use of our language!
+
+Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> wrote and
+contributed the code in lib/sighandle.c. I added support for POSIX, BSD,
+and non-POSIX/non-BSD systems.
+
+Jim Kingdon and others at Cygnus Support <info@cygnus.com> wrote the
+remote repository access code.
+
+In addition to the above contributors, the following Beta testers deserve
+special mention for their support. If I have left off your name, I
+apologize. Just write to me and let me know!
+
+ Mark D. Baushke <mdb@cisco.com>
+ Per Cederqvist <ceder@signum.se>
+ J.T. Conklin (jtc@cygnus.com>
+ Vince DeMarco <vdemarco@fdcsrvr.cs.mci.com>
+ Paul Eggert <eggert@twinsun.com>
+ Lal George <george@research.att.com>
+ Dean E. Hardi <Dean.E.Hardi@ccmail.jpl.nasa.gov>
+ Mike Heath <mike@pencom.com>
+ Jim Kingdon <kingdon@cygnus.com>
+ Bernd Leibing <bernd.leibing@rz.uni-ulm.de>
+ Benedict Lofstedt <benedict@tusc.com.au>
+ Dave Love <d.love@dl.ac.uk>
+ Robert Lupton the Good <rhl@astro.princeton.edu>
+ Tom McAliney <tom@hilco.com>
+ Eberhard Mattes <mattes@azu.informatik.uni-stuttgart.de>
+ Jim Meyering <meyering@comco.com>
+ Thomas Mohr <mohr@lts.sel.alcatel.de>
+ Thomas Nilsson <thoni@softlab.se>
+ Raye Raskin <raye.raskin@lia.com>
+ Harlan Stenn <harlan@landmark.com>
+ Gunnar Tornblom <gunnar.tornblom@senet.abb.se>
+ Greg A. Woods <woods@planix.com>
+
+Many contributors have added code to the "contrib" directory. See the
+README file there for a list of what is available. There is also a
+contributed GNU Emacs CVS-mode in contrib/pcl-cvs.
diff --git a/contrib/cvs/TODO b/contrib/cvs/TODO
new file mode 100644
index 0000000..ef9306c
--- /dev/null
+++ b/contrib/cvs/TODO
@@ -0,0 +1,423 @@
+$CVSid: @(#)TODO 1.26 94/09/21 $
+
+14. Pathname stripper, for checkout, as well as for writing the
+ Repository file.
+ [[ I have a simple one, but need to make sure to call it at all the
+ appropriate points ]]
+ (I'm not sure what this means -kingdon, Jun 1995).
+
+22. Catch signals for cleanup when "add"ing files.
+
+24. Insist on a log message.
+ (This should be configurable via commitinfo or some new config file
+ -kingdon, Jun 1995).
+
+30. Add "patch" program option to the modules database.
+
+31. Think hard about ^C recovery.
+
+35. Add "admin" command as an interface to "rcs".
+ [[ a cheesy version is there, but it should be re-done ]]
+
+38. Think hard about using RCS state information to allow one to checkin
+ a new vendor release without having it be accessed until it has been
+ integrated into the local changes.
+
+39. Think about allowing parallel source trees that can easily track
+ each other.
+ [[ sort of solved with the automagic branch support, but I want more ]]
+
+45. Consider enhancing the "patch" and "tag" command support in the module
+ database -- they seem hard to use since these commands deal directly
+ with the RCS ,v files.
+
+46. Perhaps checkout/checkin/tag/patch commands should be imbedded in the
+ file system directly, using special known command names?
+
+49. cvs xxx commands should be able to deal with files in other
+ directories. I want to do a cvs add foo/bar.c.
+ [[ most commands now use the generic recursion processor, but not all;
+ this note is left here to remind me to fix the others ]]
+
+52. SCCS has a feature that I would *love* to see in CVS, as it is very
+ useful. One may make a private copy of SCCS suid to a particular user,
+ so other users in the authentication list may check files in and out of
+ a project directory without mucking about with groups. Is there any
+ plan to provide a similar functionality to CVS? Our site (and, I'd
+ imagine, many other sites with large user bases) has decided against
+ having the user-groups feature of unix available to the users, due to
+ perceived administrative, technical and performance headaches. A tool
+ such as CVS with features that provide group-like functionality would
+ be a huge help.
+
+62. Consider using revision controlled files and directories to handle the
+ new module format -- consider a cvs command front-end to
+ add/delete/modify module contents, maybe.
+
+63. The "import" and vendor support commands (co -j) need to be documented
+ better.
+
+64. Need to greatly increase the performance of an initial checkout.
+ [[ it got better, then we added functionality, making it worse again ]]
+
+66. Length of the CVS temporary files must be limited to 14 characters for
+ System-V stupid support. As well as the length on the CVS.adm files.
+
+67. cvs import should populate the vendor sources with CVS.adm files so
+ that one could use the vendor sources directly without having the check
+ them out.
+
+69. Consider enhacing import to add a module automatically to the module
+ database. Perhaps with a new option, or perhaps with an editor.
+
+72. Consider re-design of the module -o, -i, -t options to use the file
+ system more intuitively.
+
+73. Consider an option (in .cvsrc?) to automatically add files that are new
+ and specified to commit.
+
+76. Consider adding a layer of abstraction so that CVS can work with both
+ RCS and SCCS files. Larry says this should be #ifdef'ed.
+
+79. Might be nice to have some sort of interface to TFS and tagged
+ revisions.
+
+82. Maybe the import stuff should allow an arbitrary revision to be
+ specified.
+
+84. Improve the documentation about administration of the repository and
+ how to add/remove files and the use of symbolic links.
+
+85. Add revision controlled symbolic links to CVS using one of the tag
+ fields in the RCS file.
+
+91. Better document the format of the source repository and how one might
+ convert their current SCCS or RCS files into CVS format.
+
+92. Look into this:
+ After a bit of soul searching via dbx, I realized my sin was that I'd
+ specified "echo" as the program to call from loginfo. The commit
+ procedure worked fine till it hit my echo, then silently aborted
+ leaving the lockfiles intact. Since I needn't use the loginfo
+ facility, I simply removed those commands and it all works.
+
+93. Need to think hard about release and development environments. Think
+ about execsets as well.
+
+98. If diff3 bombs out (too many differences) cvs then thinks that the file
+ has been updated and is OK to be commited even though the file
+ has not yet been merged.
+
+100. Checked out files should have revision control support. Maybe.
+
+102. Perhaps directory modes should be propagated on all import check-ins.
+ Not necessarily uid/gid changes.
+
+103. setuid/setgid on files is suspect.
+
+104. cvs should recover nicely on unreadable files/directories.
+
+105. cvs should have administrative tools to allow for changing permissions
+ and modes and what not. In particular, this would make cvs a
+ more attractive alternative to rdist.
+
+107. It should be possible to specify a list of symbolic revisions to
+ checkout such that the list is processed in reverse order looking for
+ matches within the RCS file for the symbolic revision. If there is
+ not a match, the next symbolic rev on the list is checked, and so on,
+ until all symbolic revs are exhausted. This would allow one to, say,
+ checkout "4.0" + "4.0.3" + "4.0.3Patch1" + "4.0.3Patch2" to get the
+ most recent 4.x stuff. This is usually handled by just specifying the
+ right release_tag, but most people forget to do this.
+
+108. If someone creates a whole new directory (i.e. adds it to the cvs
+ repository) and you happen to have a directory in your source farm by
+ the same name, when you do your cvs update -d it SILENTLY does
+ *nothing* to that directory. At least, I think it was silent;
+ certainly, it did *not* abort my cvs update, as it would have if the
+ same thing had happened with a file instead of a directory.
+
+109. I had gotten pieces of the sys directory in the past but not a
+ complete tree. I just did something like:
+
+ cvs get *
+
+ Where sys was in * and got the message
+
+ cvs get: Executing 'sys/tools/make_links sys'
+ sh: sys/tools/make_links: not found
+
+ I suspect this is because I didn't have the file in question,
+ but I do not understand how I could fool it into getting an
+ error. I think a later cvs get sys seemed to work so perhaps
+ something is amiss in handling multiple arguments to cvs get?
+
+113. The "cvs update" command should tee its output to a log file in ".".
+ (why? What is wrong with piping stdout to "tee"? -kingdon, Jun 1995)
+
+119. Consider an option to have import checkout the RCS or SCCS files
+ if necessary.
+
+122. If Name_Repository fails, it currently causes CVS to die completely. It
+ should instead return NULL and have the caller do something reasonable.
+
+123. Add a flag to import to not build vendor branches for local code.
+
+124. Anyway, I thought you might want to add something like the following
+ to the cvs man pages:
+
+ BUGS
+ The sum of the sizes of a module key and its contents are
+ limited. See ndbm(3).
+
+126. Do an analysis to see if CVS is forgetting to close file descriptors.
+ Especially when committing many files (more than the open file limit
+ for the particular UNIX).
+
+127. Look at *info files; they should all be quiet if the files are not
+ there. Should be able to point at a RCS directory and go.
+
+128. When I tag a file, the message tells me that I'm tagging a directory.
+
+129. Something strange seems to have happened here. When I check this out,
+ the update lines (U CFTS/...) seem to report a bogus leading CFTS
+ (e.g. U CFTS/Medusa_TS/...) when the later files are checked out.
+
+ The directory structure doesn't seem to be botched, just the
+ messages. I don't recall seeing this before.
+
+130. cvs diff with no -r arguments does not need to look up the current RCS
+ version number since it only cares about what's in the Entries file.
+ This should make it much faster.
+
+ It should ParseEntries itself and access the entries list much like
+ Version_TS does (sticky tags and sticky options may need to be
+ supported here as well). Then it should only diff the things that
+ have the wrong time stamp (the ones that look modified).
+
+134. Make a statement about using hard NFS mounts to your source
+ repository. Look into checking NULL fgets() returns with ferror() to
+ see if an error had occurred.
+
+135. The email CVS sends with each checkin, should include the version
+ number of each file it is checking in.
+ [[ Sort of solved by contrib/log.pl, which does a good job of this ]]
+
+137. Some sites might want CVS to fsync() the RCS ,v file to protect
+ against nasty hardware errors. There is a slight performance hit with
+ doing so, though, so it should be configurable in the .cvsrc file.
+ Also, along with this, we should look at the places where CVS itself
+ could be a little more synchronous so as not to lose data.
+ [[ I've done some of this, but it could use much more ]]
+
+138. Some people have suggested that CVS use a VPATH-like environment
+ variable to limit the amount of sources that need to be duplicated for
+ sites with giant source trees and no disk space.
+
+141. Import should accept modules as its directory argument.
+
+143. Update the documentation to show that the source repository is
+ something far away from the files that you work on.
+
+144. Have cvs checkout look for the environment variable CVSPREFIX
+ (or CVSMODPREFIX or some such). If it's set, then when looking
+ up an alias in the modules database, first look it up with the
+ value of CVSPREFIX attached, and then look for the alias itself.
+ This would be useful when you have several projects in a single
+ repository. You could have aliases abc_src and xyz_src and
+ tell people working on project abc to put "setenv CVSPREFIX abc_"
+ in their .cshrc file (or equivalent for other shells).
+ Then they could do "cvs co src" to get a copy of their src
+ directory, not xyz's. (This should create a directory called
+ src, not abc_src.)
+
+145. After you create revision 1.1.1.1 in the previous scenario, if
+ you do "cvs update -r1 filename" you get revision 1.1, not
+ 1.1.1.1. It would be nice to get the later revision. Again,
+ this restriction comes from RCS and is probably hard to
+ change in CVS. Sigh.
+
+ |"cvs update -r1 filename" does not tell RCS to follow any branches. CVS
+ |tries to be consistent with RCS in this fashion, so I would not change
+ |this. Within CVS we do have the flexibility of extending things, like
+ |making a revision of the form "-r1HEAD" find the most recent revision
+ |(branch or not) with a "1." prefix in the RCS file. This would get what
+ |you want maybe.
+
+ This would be very useful. Though I would prefer an option
+ such as "-v1" rather than "-r1HEAD". This option might be
+ used quite often.
+
+146. The merging of files should be controlled via a hook so that programs
+ other than "rcsmerge" can be used, like Sun's filemerge or emacs's
+ emerge.el. (but be careful in making this work client/server--it means
+ doing the interactive merging at the end after the server is done).
+
+149. On Sun, 2 Feb 92 22:01:38 EST, rouilj@dl5000.bc.edu (John P. Rouillard)
+ said:
+ Maybe there should be an option to cvs admin that allows a user to
+ change the Repository file with some degree of error checking?
+ Something like "cvs admin reposmv /old/path /new/pretty/path". Before
+ it does the replace it check to see that the files
+ /new/pretty/path/<dir>/<files> exist.
+
+150. I have a customer request for a way to specify log message per
+ file, non-interactively before the commit, such that a single, fully
+ recursive commit prompts for one commit message, and concatenates the
+ per file messages for each file. In short, one commit, one editor
+ session, log messages allowed to vary across files within the commit.
+ Also, the per file messages should be allowed to be written when the
+ files are changed, which may predate the commit considerably.
+
+ A new command seems appropriate for this. The state can be saved in the
+ CVS directory. I.e.,
+
+ % cvs msg foo.c
+ Enter log message for foo.c
+ >> fixed an uninitialized variable
+ >> ^D
+
+ The text is saved as CVS/foo.c,m (or some such name) and commit is
+ modified to append (prepend?) the text (if found) to the log message
+ specified at commit time. Easy enough.
+
+151. Also, is there a flag I am missing that allows replacing Ulrtx_Build
+ by Ultrix_build? I.E. I would like a tag replacement to be a one step
+ operation rather than a two step "cvs rtag -r Ulrtx_Build Ultrix_Build"
+ followed by "cvs trag -d Ulrtx_Build"
+
+152. The "cvs -n" option does not work as one would expect for all the
+ commands. In particular, for "commit" and "import", where one would
+ also like to see what it would do, without actually doing anything.
+
+153. There should be some command (maybe I just haven't figured
+ out which one...) to import a source directory which is already
+ RCS-administered without losing all prior RCS gathered data. Thus, it
+ would have to examine the RCS files and choose a starting version and
+ branch higher than previous ones used.
+
+154. When committing the modules file, a pre-commit check should be done to
+ verify the validity of the new modules file before allowing it to be
+ committed.
+
+155. The options for "cvs history" are mutually exclusive, even though
+ useful queries can be done if they are not, as in specifying both a
+ module and a tag. A workaround is to specify the module, then run the
+ output through grep to only display lines that begin with T, which are
+ tag lines.
+
+156. Also, how hard would it be to allow continuation lines in the
+ {commit,rcs,log}info files? It would probably be useful with all of
+ the various flags that are now available, or if somebody has a lot of
+ files to put into a module.
+
+157. The "cvs release" command does not understand about module names with
+ the same flexibility that the "checkout" and "rdiff" commands do.
+ It should, though, since it's confusing right now.
+
+158. If I do a recursive commit and find that the same RCS file is checked
+ out (and modified!) in two different places within my checked-out
+ files (but within the realm of a single "commit"), CVS will commit the
+ first change, then overwrite that change with the second change. We
+ should catch this (typically unusual) case and issue an appropriate
+ diagnostic and die.
+
+159. On "update", when a merge is done, CVS should remember that your file
+ was merged into and should keep reminding you of this fact until you
+ actually look at the file (change its access time). Once you do this,
+ it should go back to being a normal, unmodified file. This way, after
+ a big update, you can run update again to see which files just got
+ merged and may need attention.
+
+160. The checks that the commit command does should be extended to make
+ sure that the revision that we will lock is not already locked by
+ someone else. Maybe it should also lock the new revision if the old
+ revision was already locked by the user as well, thus moving the lock
+ forward after the commit.
+
+161. The date parser included with CVS (lib/getdate.y) does not support
+ such RCS-supported dates as "1992/03/07". It probably should.
+
+163. The rtag/tag commands should have an option that removes the specified
+ tag from any file that is in the attic. This allows one to re-use a
+ tag (like "Mon", "Tue", ...) all the time and still have it tag the
+ real main-line code.
+
+164. The *info files should allow multiple ocurrences of $CVSROOT and/or
+ other cvs variables. They probably should *not* expand environment
+ variables, as their behavior probably should not depend on who is
+ running CVS.
+
+165. The "import" command will create RCS files automatically, but will
+ screw-up when trying to create long file names on short file name
+ file systems. Perhaps import should be a bit more cautious.
+
+166. There really needs to be a "Getting Started" document which describes
+ some of the new CVS philosophies. Folks coming straight from SCCS or
+ RCS might be confused by "cvs import". Also need to explain:
+ - How one might setup their $CVSROOT
+ - What all the tags mean in an "import" command
+ - Tags are important; revision numbers are not
+
+167. "cvs log" doesn't understand about CVS magic branch numbers. As such,
+ the command:
+
+ cvs log -r1.63.2
+ cvs log -rC2
+
+ where "C2" is a magic branch that resolves to 1.63.2 do not print the
+ same things. Sigh.
+
+169. We are using CVS as the configuration control for a software reuse library.
+ What we do is do system calls passing the needed arguments. In the next
+ release, it would be nice to see an option to put cvs .o files into a
+ archive library with an API. This enhancement would go nicely with the
+ notion of being able to integrate tools into a large software engineering
+ environment.
+
+170. Is there an "info" file that can be invoked when a file is checked out, or
+ updated ? What I want to do is to advise users, particularly novices, of
+ the state of their working source whenever they check something out, as
+ a sanity check.
+
+ For example, I've written a perl script which tells you what branch you're
+ on, if any. Hopefully this will help guard against mistaken checkins to
+ the trunk, or to the wrong branch. I suppose I can do this in
+ "commitinfo", but it'd be nice to advise people before they edit their
+ files.
+
+ It would also be nice if there was some sort of "verboseness" switch to
+ the checkout and update commands that could turn this invocation of the
+ script off, for mature users.
+
+173. We have a tagged branch in CVS. How do we get the version of that branch
+ (for an entire directory) that corresponds to the files on that branch on a
+ certain day? I'd like to specify BOTH -r and -D to 'cvs checkout', but I
+ can't. It looks like I can only specify the date for the main line (as
+ opposed to any branches). True? Any workarounds to get what I need?
+
+174. I would like to see "cvs release" modified so that it only removes files
+ which are known to CVS - all the files in the repository, plus those which
+ are listed in .cvsignore. This way, if you do leave something valuable in
+ a source tree you can "cvs release -d" the tree and your non-CVS goodies
+ are still there. If a user is going to leave non-CVS files in their source
+ trees, they really should have to clean them up by hand.
+
+175. And, in the feature request department, I'd dearly love a command-line
+ interface to adding a new module to the CVSROOT/modules file.
+
+176. If you use the -i flag in the modules file, you can control access
+ to source code; this is a Good Thing under certain circumstances. I
+ just had a nasty thought, and on experiment discovered that the
+ filter specified by -i is _not_ run before a cvs admin command; as
+ this allows a user to go behind cvs's back and delete information
+ (cvs admin -o1.4 file) this seems like a serious problem.
+
+177. We've got some external vendor source that sits under a source code
+ hierarchy, and when we do a cvs update, it gets wiped out because
+ its tag is different from the "main" distribution. I've tried to
+ use "-I" to ignore the directory, as well as .cvsignore, but this
+ doesn't work.
+
+179. "cvs admin" does not log its actions with loginfo, nor does it check
+ whether the action is allowed with commitinfo. It should.
diff --git a/contrib/cvs/acconfig.h b/contrib/cvs/acconfig.h
new file mode 100644
index 0000000..551a8aa
--- /dev/null
+++ b/contrib/cvs/acconfig.h
@@ -0,0 +1,12 @@
+/* Define if you have MIT Kerberos version 4 available. */
+#undef HAVE_KERBEROS
+
+/* Define if you want CVS to be able to be a remote repository client. */
+#undef CLIENT_SUPPORT
+
+/* Define if you want CVS to be able to serve repositories to remote
+ clients. */
+#undef SERVER_SUPPORT
+
+/* Define if you want to use the password authenticated server. */
+#undef AUTH_SERVER_SUPPORT
diff --git a/contrib/cvs/config.h.in b/contrib/cvs/config.h.in
new file mode 100644
index 0000000..9181936
--- /dev/null
+++ b/contrib/cvs/config.h.in
@@ -0,0 +1,210 @@
+/* config.h.in. Generated automatically from configure.in by autoheader. */
+
+/* Define if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+#undef _ALL_SOURCE
+#endif
+
+/* Define if type char is unsigned and you are not using gcc. */
+#ifndef __CHAR_UNSIGNED__
+#undef __CHAR_UNSIGNED__
+#endif
+
+/* Define to empty if the keyword does not work. */
+#undef const
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef gid_t
+
+/* Define if you support file names longer than 14 characters. */
+#undef HAVE_LONG_FILE_NAMES
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */
+#undef HAVE_SYS_WAIT_H
+
+/* Define if utime(file, NULL) sets file's timestamp to the present. */
+#undef HAVE_UTIME_NULL
+
+/* Define as __inline if that's what the C compiler calls it. */
+#undef inline
+
+/* Define if on MINIX. */
+#undef _MINIX
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef mode_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef pid_t
+
+/* Define if the system does not provide POSIX.1 features except
+ with this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define if you need to in order for stat and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Define as the return type of signal handlers (int or void). */
+#undef RETSIGTYPE
+
+/* Define to `unsigned' if <sys/types.h> doesn't define. */
+#undef size_t
+
+/* Define if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+#undef STAT_MACROS_BROKEN
+
+/* Define if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Define if you can safely include both <sys/time.h> and <time.h>. */
+#undef TIME_WITH_SYS_TIME
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef uid_t
+
+/* Define if you have MIT Kerberos version 4 available. */
+#undef HAVE_KERBEROS
+
+/* Define if you want CVS to be able to be a remote repository client. */
+#undef CLIENT_SUPPORT
+
+/* Define if you want CVS to be able to serve repositories to remote
+ clients. */
+#undef SERVER_SUPPORT
+
+/* Define if you want to use the password authenticated server. */
+#undef AUTH_SERVER_SUPPORT
+
+/* The number of bytes in a int. */
+#undef SIZEOF_INT
+
+/* The number of bytes in a long. */
+#undef SIZEOF_LONG
+
+/* Define if you have the connect function. */
+#undef HAVE_CONNECT
+
+/* Define if you have the fchdir function. */
+#undef HAVE_FCHDIR
+
+/* Define if you have the fchmod function. */
+#undef HAVE_FCHMOD
+
+/* Define if you have the fsync function. */
+#undef HAVE_FSYNC
+
+/* Define if you have the ftime function. */
+#undef HAVE_FTIME
+
+/* Define if you have the ftruncate function. */
+#undef HAVE_FTRUNCATE
+
+/* Define if you have the getpagesize function. */
+#undef HAVE_GETPAGESIZE
+
+/* Define if you have the krb_get_err_text function. */
+#undef HAVE_KRB_GET_ERR_TEXT
+
+/* Define if you have the mkfifo function. */
+#undef HAVE_MKFIFO
+
+/* Define if you have the putenv function. */
+#undef HAVE_PUTENV
+
+/* Define if you have the setvbuf function. */
+#undef HAVE_SETVBUF
+
+/* Define if you have the sigaction function. */
+#undef HAVE_SIGACTION
+
+/* Define if you have the sigblock function. */
+#undef HAVE_SIGBLOCK
+
+/* Define if you have the sigprocmask function. */
+#undef HAVE_SIGPROCMASK
+
+/* Define if you have the sigsetmask function. */
+#undef HAVE_SIGSETMASK
+
+/* Define if you have the sigvec function. */
+#undef HAVE_SIGVEC
+
+/* Define if you have the timezone function. */
+#undef HAVE_TIMEZONE
+
+/* Define if you have the vfork function. */
+#undef HAVE_VFORK
+
+/* Define if you have the vprintf function. */
+#undef HAVE_VPRINTF
+
+/* Define if you have the <direct.h> header file. */
+#undef HAVE_DIRECT_H
+
+/* Define if you have the <dirent.h> header file. */
+#undef HAVE_DIRENT_H
+
+/* Define if you have the <errno.h> header file. */
+#undef HAVE_ERRNO_H
+
+/* Define if you have the <fcntl.h> header file. */
+#undef HAVE_FCNTL_H
+
+/* Define if you have the <io.h> header file. */
+#undef HAVE_IO_H
+
+/* Define if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define if you have the <ndbm.h> header file. */
+#undef HAVE_NDBM_H
+
+/* Define if you have the <ndir.h> header file. */
+#undef HAVE_NDIR_H
+
+/* Define if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define if you have the <sys/bsdtypes.h> header file. */
+#undef HAVE_SYS_BSDTYPES_H
+
+/* Define if you have the <sys/dir.h> header file. */
+#undef HAVE_SYS_DIR_H
+
+/* Define if you have the <sys/ndir.h> header file. */
+#undef HAVE_SYS_NDIR_H
+
+/* Define if you have the <sys/param.h> header file. */
+#undef HAVE_SYS_PARAM_H
+
+/* Define if you have the <sys/resource.h> header file. */
+#undef HAVE_SYS_RESOURCE_H
+
+/* Define if you have the <sys/select.h> header file. */
+#undef HAVE_SYS_SELECT_H
+
+/* Define if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define if you have the <sys/timeb.h> header file. */
+#undef HAVE_SYS_TIMEB_H
+
+/* Define if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define if you have the <utime.h> header file. */
+#undef HAVE_UTIME_H
+
+/* Define if you have the inet library (-linet). */
+#undef HAVE_LIBINET
+
+/* Define if you have the nsl library (-lnsl). */
+#undef HAVE_LIBNSL
+
+/* Define if you have the nsl_s library (-lnsl_s). */
+#undef HAVE_LIBNSL_S
+
+/* Define if you have the socket library (-lsocket). */
+#undef HAVE_LIBSOCKET
diff --git a/contrib/cvs/configure b/contrib/cvs/configure
new file mode 100755
index 0000000..3025eef
--- /dev/null
+++ b/contrib/cvs/configure
@@ -0,0 +1,3186 @@
+#! /bin/sh
+
+# Guess values for system-dependent variables and create Makefiles.
+# Generated automatically using autoconf version 2.9
+# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+
+# Defaults:
+ac_help=
+ac_default_prefix=/usr/local
+# Any additions from configure.in:
+ac_help="$ac_help
+ --with-krb4=value set default \$(KRB4) from value"
+
+# Initialize some variables set by options.
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+build=NONE
+cache_file=./config.cache
+exec_prefix=NONE
+host=NONE
+no_create=
+nonopt=NONE
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+target=NONE
+verbose=
+x_includes=NONE
+x_libraries=NONE
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+# Initialize some other variables.
+subdirs=
+MFLAGS= MAKEFLAGS=
+
+ac_prev=
+for ac_option
+do
+
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval "$ac_prev=\$ac_option"
+ ac_prev=
+ continue
+ fi
+
+ case "$ac_option" in
+ -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+ *) ac_optarg= ;;
+ esac
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case "$ac_option" in
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir="$ac_optarg" ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build="$ac_optarg" ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file="$ac_optarg" ;;
+
+ -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+ | --da=*)
+ datadir="$ac_optarg" ;;
+
+ -disable-* | --disable-*)
+ ac_feature=`echo $ac_option|sed -e 's/-*disable-//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then
+ { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+ fi
+ ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+ eval "enable_${ac_feature}=no" ;;
+
+ -enable-* | --enable-*)
+ ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then
+ { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+ fi
+ ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+ case "$ac_option" in
+ *=*) ;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "enable_${ac_feature}='$ac_optarg'" ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix="$ac_optarg" ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he)
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat << EOF
+Usage: configure [options] [host]
+Options: [defaults in brackets after descriptions]
+Configuration:
+ --cache-file=FILE cache test results in FILE
+ --help print this message
+ --no-create do not create output files
+ --quiet, --silent do not print \`checking...' messages
+ --version print the version of autoconf that created configure
+Directory and file names:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [same as prefix]
+ --bindir=DIR user executables in DIR [EPREFIX/bin]
+ --sbindir=DIR system admin executables in DIR [EPREFIX/sbin]
+ --libexecdir=DIR program executables in DIR [EPREFIX/libexec]
+ --datadir=DIR read-only architecture-independent data in DIR
+ [PREFIX/share]
+ --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data in DIR
+ [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var]
+ --libdir=DIR object code libraries in DIR [EPREFIX/lib]
+ --includedir=DIR C header files in DIR [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include]
+ --infodir=DIR info documentation in DIR [PREFIX/info]
+ --mandir=DIR man documentation in DIR [PREFIX/man]
+ --srcdir=DIR find the sources in DIR [configure dir or ..]
+ --program-prefix=PREFIX prepend PREFIX to installed program names
+ --program-suffix=SUFFIX append SUFFIX to installed program names
+ --program-transform-name=PROGRAM
+ run sed PROGRAM on installed program names
+EOF
+ cat << EOF
+Host type:
+ --build=BUILD configure for building on BUILD [BUILD=HOST]
+ --host=HOST configure for HOST [guessed]
+ --target=TARGET configure for TARGET [TARGET=HOST]
+Features and packages:
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --x-includes=DIR X include files are in DIR
+ --x-libraries=DIR X library files are in DIR
+EOF
+ if test -n "$ac_help"; then
+ echo "--enable and --with options recognized:$ac_help"
+ fi
+ exit 0 ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host="$ac_optarg" ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir="$ac_optarg" ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir="$ac_optarg" ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir="$ac_optarg" ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir="$ac_optarg" ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst \
+ | --locals | --local | --loca | --loc | --lo)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+ localstatedir="$ac_optarg" ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir="$ac_optarg" ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir="$ac_optarg" ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix="$ac_optarg" ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix="$ac_optarg" ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix="$ac_optarg" ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name="$ac_optarg" ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir="$ac_optarg" ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir="$ac_optarg" ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site="$ac_optarg" ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir="$ac_optarg" ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir="$ac_optarg" ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target="$ac_optarg" ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers)
+ echo "configure generated by autoconf version 2.9"
+ exit 0 ;;
+
+ -with-* | --with-*)
+ ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then
+ { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+ fi
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ case "$ac_option" in
+ *=*) ;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "with_${ac_package}='$ac_optarg'" ;;
+
+ -without-* | --without-*)
+ ac_package=`echo $ac_option|sed -e 's/-*without-//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then
+ { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+ fi
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ eval "with_${ac_package}=no" ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes="$ac_optarg" ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries="$ac_optarg" ;;
+
+ -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; }
+ ;;
+
+ *)
+ if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then
+ echo "configure: warning: $ac_option: invalid host type" 1>&2
+ fi
+ if test "x$nonopt" != xNONE; then
+ { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; }
+ fi
+ nonopt="$ac_option"
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; }
+fi
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+# File descriptor usage:
+# 0 standard input
+# 1 file creation
+# 2 errors and warnings
+# 3 some systems may open it to /dev/tty
+# 4 used on the Kubota Titan
+# 6 checking for... messages and results
+# 5 compiler messages saved in config.log
+if test "$silent" = yes; then
+ exec 6>/dev/null
+else
+ exec 6>&1
+fi
+exec 5>./config.log
+
+echo "\
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+" 1>&5
+
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell metacharacters.
+ac_configure_args=
+for ac_arg
+do
+ case "$ac_arg" in
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c) ;;
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;;
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*)
+ ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+ *) ac_configure_args="$ac_configure_args $ac_arg" ;;
+ esac
+done
+
+# NLS nuisances.
+# Only set LANG and LC_ALL to C if already set.
+# These must not be set unconditionally because not all systems understand
+# e.g. LANG=C (notably SCO).
+if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi
+if test "${LANG+set}" = set; then LANG=C; export LANG; fi
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo > confdefs.h
+
+# A filename unique to this package, relative to the directory that
+# configure is in, which we can look for to find out if srcdir is correct.
+ac_unique_file=src/cvs.h
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then its parent.
+ ac_prog=$0
+ ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'`
+ test "x$ac_confdir" = "x$ac_prog" && ac_confdir=.
+ srcdir=$ac_confdir
+ if test ! -r $srcdir/$ac_unique_file; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+ if test "$ac_srcdir_defaulted" = yes; then
+ { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; }
+ else
+ { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; }
+ fi
+fi
+srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
+
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+ if test -r "$ac_site_file"; then
+ echo "loading site script $ac_site_file"
+ . "$ac_site_file"
+ fi
+done
+
+if test -r "$cache_file"; then
+ echo "loading cache $cache_file"
+ . $cache_file
+else
+ echo "creating cache $cache_file"
+ > $cache_file
+fi
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+
+if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then
+ # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu.
+ if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then
+ ac_n= ac_c='
+' ac_t=' '
+ else
+ ac_n=-n ac_c= ac_t=
+ fi
+else
+ ac_n= ac_c='\c' ac_t=
+fi
+
+
+
+
+# Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_CC="gcc"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ ac_prog_rejected=no
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# -gt 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ set dummy "$ac_dir/$ac_word" "$@"
+ shift
+ ac_cv_prog_CC="$@"
+ fi
+fi
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.c <<EOF
+#ifdef __GNUC__
+ yes;
+#endif
+EOF
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:606: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+ ac_cv_prog_gcc=yes
+else
+ ac_cv_prog_gcc=no
+fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc" 1>&6
+if test $ac_cv_prog_gcc = yes; then
+ GCC=yes
+ if test "${CFLAGS+set}" != set; then
+ echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_prog_gcc_g'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ echo 'void f(){}' > conftest.c
+if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then
+ ac_cv_prog_gcc_g=yes
+else
+ ac_cv_prog_gcc_g=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc_g" 1>&6
+ if test $ac_cv_prog_gcc_g = yes; then
+ CFLAGS="-g -O"
+ else
+ CFLAGS="-O"
+ fi
+ fi
+else
+ GCC=
+ test "${CFLAGS+set}" = set || CFLAGS="-g"
+fi
+
+
+echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ # This must be in double quotes, not single quotes, because CPP may get
+ # substituted into the Makefile and "${CC-cc}" will confuse make.
+ CPP="${CC-cc} -E"
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp.
+ cat > conftest.$ac_ext <<EOF
+#line 659 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:665: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ rm -rf conftest*
+ CPP="${CC-cc} -E -traditional-cpp"
+ cat > conftest.$ac_ext <<EOF
+#line 674 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:680: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ rm -rf conftest*
+ CPP=/lib/cpp
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+ ac_cv_prog_CPP="$CPP"
+fi
+ CPP="$ac_cv_prog_CPP"
+else
+ ac_cv_prog_CPP="$CPP"
+fi
+echo "$ac_t""$CPP" 1>&6
+
+echo $ac_n "checking for AIX""... $ac_c" 1>&6
+cat > conftest.$ac_ext <<EOF
+#line 702 "configure"
+#include "confdefs.h"
+#ifdef _AIX
+ yes
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "yes" >/dev/null 2>&1; then
+ rm -rf conftest*
+ echo "$ac_t""yes" 1>&6; cat >> confdefs.h <<\EOF
+#define _ALL_SOURCE 1
+EOF
+
+else
+ rm -rf conftest*
+ echo "$ac_t""no" 1>&6
+fi
+rm -f conftest*
+
+
+ac_safe=`echo "minix/config.h" | tr './\055' '___'`
+echo $ac_n "checking for minix/config.h""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 729 "configure"
+#include "confdefs.h"
+#include <minix/config.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:734: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ MINIX=yes
+else
+ echo "$ac_t""no" 1>&6
+MINIX=
+fi
+
+if test "$MINIX" = yes; then
+ cat >> confdefs.h <<\EOF
+#define _POSIX_SOURCE 1
+EOF
+
+ cat >> confdefs.h <<\EOF
+#define _POSIX_1_SOURCE 2
+EOF
+
+ cat >> confdefs.h <<\EOF
+#define _MINIX 1
+EOF
+
+fi
+
+echo $ac_n "checking for POSIXized ISC""... $ac_c" 1>&6
+if test -d /etc/conf/kconfig.d &&
+ grep _POSIX_VERSION /usr/include/sys/unistd.h >/dev/null 2>&1
+then
+ echo "$ac_t""yes" 1>&6
+ ISC=yes # If later tests want to check for ISC.
+ cat >> confdefs.h <<\EOF
+#define _POSIX_SOURCE 1
+EOF
+
+ if test "$GCC" = yes; then
+ CC="$CC -posix"
+ else
+ CC="$CC -Xp"
+ fi
+else
+ echo "$ac_t""no" 1>&6
+ ISC=
+fi
+
+if test "$ISC" = yes; then
+CFLAGS="$CFLAGS -D_SYSV3"
+fi
+
+if test "x$prefix" = xNONE; then
+echo $ac_n "checking for prefix by ""... $ac_c" 1>&6
+# Extract the first word of "cvs", so it can be a program name with args.
+set dummy cvs; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_path_CVS'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$CVS" in
+ /*)
+ ac_cv_path_CVS="$CVS" # Let the user override the test with a path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_CVS="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+CVS="$ac_cv_path_CVS"
+if test -n "$CVS"; then
+ echo "$ac_t""$CVS" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ if test -n "$ac_cv_path_CVS"; then
+ prefix=`echo $ac_cv_path_CVS|sed 's%/[^/][^/]*//*[^/][^/]*$%%'`
+ fi
+fi
+
+
+# If we cannot run a trivial program, we must be cross compiling.
+echo $ac_n "checking whether cross-compiling""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_c_cross'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ ac_cv_c_cross=yes
+else
+cat > conftest.$ac_ext <<EOF
+#line 840 "configure"
+#include "confdefs.h"
+main(){return(0);}
+EOF
+{ (eval echo configure:844: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }
+if test -s conftest && (./conftest; exit) 2>/dev/null; then
+ ac_cv_c_cross=no
+else
+ ac_cv_c_cross=yes
+fi
+fi
+rm -fr conftest*
+fi
+
+echo "$ac_t""$ac_cv_c_cross" 1>&6
+cross_compiling=$ac_cv_c_cross
+
+
+echo $ac_n "checking for working const""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_c_const'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 863 "configure"
+#include "confdefs.h"
+
+int main() { return 0; }
+int t() {
+
+/* Ultrix mips cc rejects this. */
+typedef int charset[2]; const charset x;
+/* SunOS 4.1.1 cc rejects this. */
+char const *const *ccp;
+char **p;
+/* NEC SVR4.0.2 mips cc rejects this. */
+struct point {int x, y;};
+static struct point const zero = {0,0};
+/* AIX XL C 1.02.0.0 rejects this.
+ It does not let you subtract one const X* pointer from another in an arm
+ of an if-expression whose if-part is not a constant expression */
+const char *g = "string";
+ccp = &g + (g ? g-g : 0);
+/* HPUX 7.0 cc rejects these. */
+++ccp;
+p = (char**) ccp;
+ccp = (char const *const *) p;
+{ /* SCO 3.2v4 cc rejects this. */
+ char *t;
+ char const *s = 0 ? (char *) 0 : (char const *) 0;
+
+ *t++ = 0;
+}
+{ /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */
+ int x[] = {25, 17};
+ const int *foo = &x[0];
+ ++foo;
+}
+{ /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */
+ typedef const int *iptr;
+ iptr p = 0;
+ ++p;
+}
+{ /* AIX XL C 1.02.0.0 rejects this saying
+ "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */
+ struct s { int j; const int *ap[3]; };
+ struct s *b; b->j = 5;
+}
+{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */
+ const int foo = 10;
+}
+
+; return 0; }
+EOF
+if { (eval echo configure:913: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_c_const=yes
+else
+ rm -rf conftest*
+ ac_cv_c_const=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_c_const" 1>&6
+if test $ac_cv_c_const = no; then
+ cat >> confdefs.h <<\EOF
+#define const
+EOF
+
+fi
+
+echo $ac_n "checking whether char is unsigned""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_c_char_unsigned'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$GCC" = yes; then
+ # GCC predefines this symbol on systems where it applies.
+cat > conftest.$ac_ext <<EOF
+#line 939 "configure"
+#include "confdefs.h"
+#ifdef __CHAR_UNSIGNED__
+ yes
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "yes" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_c_char_unsigned=yes
+else
+ rm -rf conftest*
+ ac_cv_c_char_unsigned=no
+fi
+rm -f conftest*
+
+else
+if test "$cross_compiling" = yes; then
+ { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; }
+else
+cat > conftest.$ac_ext <<EOF
+#line 961 "configure"
+#include "confdefs.h"
+/* volatile prevents gcc2 from optimizing the test away on sparcs. */
+#if !defined(__STDC__) || __STDC__ != 1
+#define volatile
+#endif
+main() {
+ volatile char c = 255; exit(c < 0);
+}
+EOF
+{ (eval echo configure:971: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }
+if test -s conftest && (./conftest; exit) 2>/dev/null; then
+ ac_cv_c_char_unsigned=yes
+else
+ ac_cv_c_char_unsigned=no
+fi
+fi
+rm -fr conftest*
+fi
+fi
+
+echo "$ac_t""$ac_cv_c_char_unsigned" 1>&6
+if test $ac_cv_c_char_unsigned = yes && test "$GCC" != yes; then
+ cat >> confdefs.h <<\EOF
+#define __CHAR_UNSIGNED__ 1
+EOF
+
+fi
+
+echo $ac_n "checking for inline""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_c_inline'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_cv_c_inline=no
+for ac_kw in inline __inline__ __inline; do
+ cat > conftest.$ac_ext <<EOF
+#line 997 "configure"
+#include "confdefs.h"
+
+int main() { return 0; }
+int t() {
+} $ac_kw foo() {
+; return 0; }
+EOF
+if { (eval echo configure:1005: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_c_inline=$ac_kw; break
+fi
+rm -f conftest*
+
+done
+
+fi
+
+echo "$ac_t""$ac_cv_c_inline" 1>&6
+case "$ac_cv_c_inline" in
+ inline | yes) ;;
+ no) cat >> confdefs.h <<\EOF
+#define inline
+EOF
+ ;;
+ *) cat >> confdefs.h <<EOF
+#define inline $ac_cv_c_inline
+EOF
+ ;;
+esac
+
+
+ac_aux_dir=
+for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do
+ if test -f $ac_dir/install-sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f $ac_dir/install.sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; }
+fi
+ac_config_guess=$ac_aux_dir/config.guess
+ac_config_sub=$ac_aux_dir/config.sub
+ac_configure=$ac_aux_dir/configure # This should be Cygnus configure.
+
+# Find a good install program. We prefer a C program (faster),
+# so one script is as good as another. But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# ./install, which can be erroneously created by make from ./install.sh.
+echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6
+if test -z "$INSTALL"; then
+if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ for ac_dir in $PATH; do
+ # Account for people who put trailing slashes in PATH elements.
+ case "$ac_dir/" in
+ /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;;
+ *)
+ # OSF1 and SCO ODT 3.0 have their own names for install.
+ for ac_prog in ginstall installbsd scoinst install; do
+ if test -f $ac_dir/$ac_prog; then
+ if test $ac_prog = install &&
+ grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then
+ # AIX install. It has an incompatible calling convention.
+ # OSF/1 installbsd also uses dspmsg, but is usable.
+ :
+ else
+ ac_cv_path_install="$ac_dir/$ac_prog -c"
+ break 2
+ fi
+ fi
+ done
+ ;;
+ esac
+ done
+ IFS="$ac_save_ifs"
+
+fi
+ if test "${ac_cv_path_install+set}" = set; then
+ INSTALL="$ac_cv_path_install"
+ else
+ # As a last resort, use the slow shell script. We don't cache a
+ # path for INSTALL within a source directory, because that will
+ # break other packages using the cache if that directory is
+ # removed, or if the path is relative.
+ INSTALL="$ac_install_sh"
+ fi
+fi
+echo "$ac_t""$INSTALL" 1>&6
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+# Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$RANLIB"; then
+ ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_RANLIB="ranlib"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":"
+fi
+fi
+RANLIB="$ac_cv_prog_RANLIB"
+if test -n "$RANLIB"; then
+ echo "$ac_t""$RANLIB" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+for ac_prog in 'bison -y' byacc
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_prog_YACC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$YACC"; then
+ ac_cv_prog_YACC="$YACC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_YACC="$ac_prog"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+YACC="$ac_cv_prog_YACC"
+if test -n "$YACC"; then
+ echo "$ac_t""$YACC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+test -n "$YACC" && break
+done
+test -n "$YACC" || YACC="yacc"
+
+echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6
+set dummy ${MAKE-make}; ac_make=$2
+if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftestmake <<\EOF
+all:
+ @echo 'ac_maketemp="${MAKE}"'
+EOF
+# GNU make sometimes prints "make[1]: Entering...", which would confuse us.
+eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=`
+if test -n "$ac_maketemp"; then
+ eval ac_cv_prog_make_${ac_make}_set=yes
+else
+ eval ac_cv_prog_make_${ac_make}_set=no
+fi
+rm -f conftestmake
+fi
+if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SET_MAKE=
+else
+ echo "$ac_t""no" 1>&6
+ SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+
+# Extract the first word of "perl", so it can be a program name with args.
+set dummy perl; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_path_perl_path'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$perl_path" in
+ /*)
+ ac_cv_path_perl_path="$perl_path" # Let the user override the test with a path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_perl_path="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_path_perl_path" && ac_cv_path_perl_path="no"
+ ;;
+esac
+fi
+perl_path="$ac_cv_path_perl_path"
+if test -n "$perl_path"; then
+ echo "$ac_t""$perl_path" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+# Extract the first word of "csh", so it can be a program name with args.
+set dummy csh; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_path_csh_path'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$csh_path" in
+ /*)
+ ac_cv_path_csh_path="$csh_path" # Let the user override the test with a path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_csh_path="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_path_csh_path" && ac_cv_path_csh_path="no"
+ ;;
+esac
+fi
+csh_path="$ac_cv_path_csh_path"
+if test -n "$csh_path"; then
+ echo "$ac_t""$csh_path" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+
+# Pull the hash mark out of the macro call to avoid m4 problems.
+ac_msg="whether #! works in shell scripts"
+echo $ac_n "checking $ac_msg""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_sys_interpreter'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ echo '#! /bin/cat
+exit 69
+' > conftest
+chmod u+x conftest
+(SHELL=/bin/sh; export SHELL; ./conftest >/dev/null)
+if test $? -ne 69; then
+ ac_cv_sys_interpreter=yes
+else
+ ac_cv_sys_interpreter=no
+fi
+rm -f conftest
+fi
+
+echo "$ac_t""$ac_cv_sys_interpreter" 1>&6
+
+if test X"$ac_cv_sys_interpreter" != X"yes" ; then
+ # silly trick to avoid problems in AC macros...
+ ac_msg='perl scripts using #! may not be invoked properly'
+ echo "configure: warning: $ac_msg" 1>&2
+fi
+
+echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1290 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1298: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ ac_cv_header_stdc=yes
+else
+ echo "$ac_err" >&5
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 1313 "configure"
+#include "confdefs.h"
+#include <string.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "memchr" >/dev/null 2>&1; then
+ :
+else
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 1331 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "free" >/dev/null 2>&1; then
+ :
+else
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+if test "$cross_compiling" = yes; then
+ :
+else
+cat > conftest.$ac_ext <<EOF
+#line 1352 "configure"
+#include "confdefs.h"
+#include <ctype.h>
+#define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int main () { int i; for (i = 0; i < 256; i++)
+if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2);
+exit (0); }
+
+EOF
+{ (eval echo configure:1363: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }
+if test -s conftest && (./conftest; exit) 2>/dev/null; then
+ :
+else
+ ac_cv_header_stdc=no
+fi
+fi
+rm -fr conftest*
+fi
+fi
+
+echo "$ac_t""$ac_cv_header_stdc" 1>&6
+if test $ac_cv_header_stdc = yes; then
+ cat >> confdefs.h <<\EOF
+#define STDC_HEADERS 1
+EOF
+
+fi
+
+for ac_hdr in errno.h unistd.h string.h memory.h utime.h fcntl.h ndbm.h \
+ sys/param.h sys/select.h sys/time.h sys/timeb.h \
+ io.h direct.h sys/bsdtypes.h sys/resource.h
+do
+ac_safe=`echo "$ac_hdr" | tr './\055' '___'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1392 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1397: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | tr 'abcdefghijklmnopqrstuvwxyz./\055' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ___'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+echo $ac_n "checking for sys/wait.h that is POSIX.1 compatible""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_header_sys_wait_h'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1426 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
+#endif
+#ifndef WIFEXITED
+#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
+#endif
+int main() { return 0; }
+int t() {
+int s;
+wait (&s);
+s = WIFEXITED (s) ? WEXITSTATUS (s) : 1;
+; return 0; }
+EOF
+if { (eval echo configure:1443: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_header_sys_wait_h=yes
+else
+ rm -rf conftest*
+ ac_cv_header_sys_wait_h=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_header_sys_wait_h" 1>&6
+if test $ac_cv_header_sys_wait_h = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_SYS_WAIT_H 1
+EOF
+
+fi
+
+echo $ac_n "checking whether stat file-mode macros are broken""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_header_stat_broken'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1467 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(S_ISBLK) && defined(S_IFDIR)
+# if S_ISBLK (S_IFDIR)
+You lose.
+# endif
+#endif
+
+#if defined(S_ISBLK) && defined(S_IFCHR)
+# if S_ISBLK (S_IFCHR)
+You lose.
+# endif
+#endif
+
+#if defined(S_ISLNK) && defined(S_IFREG)
+# if S_ISLNK (S_IFREG)
+You lose.
+# endif
+#endif
+
+#if defined(S_ISSOCK) && defined(S_IFREG)
+# if S_ISSOCK (S_IFREG)
+You lose.
+# endif
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "You lose" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_header_stat_broken=yes
+else
+ rm -rf conftest*
+ ac_cv_header_stat_broken=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_header_stat_broken" 1>&6
+if test $ac_cv_header_stat_broken = yes; then
+ cat >> confdefs.h <<\EOF
+#define STAT_MACROS_BROKEN 1
+EOF
+
+fi
+
+echo $ac_n "checking whether time.h and sys/time.h may both be included""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_header_time'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1522 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+int main() { return 0; }
+int t() {
+struct tm *tp;
+; return 0; }
+EOF
+if { (eval echo configure:1532: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_header_time=yes
+else
+ rm -rf conftest*
+ ac_cv_header_time=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_header_time" 1>&6
+if test $ac_cv_header_time = yes; then
+ cat >> confdefs.h <<\EOF
+#define TIME_WITH_SYS_TIME 1
+EOF
+
+fi
+
+ac_header_dirent=no
+for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h
+do
+ac_safe=`echo "$ac_hdr" | tr './\055' '___'`
+echo $ac_n "checking for $ac_hdr that defines DIR""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_header_dirent_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1560 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <$ac_hdr>
+int main() { return 0; }
+int t() {
+DIR *dirp = 0;
+; return 0; }
+EOF
+if { (eval echo configure:1569: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_header_dirent_$ac_safe=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_header_dirent_$ac_safe=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_header_dirent_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | tr 'abcdedfghijklmnopqrstuvwxyz./\055' 'ABCDEDFGHIJKLMNOPQRSTUVWXYZ___'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+ ac_header_dirent=$ac_hdr; break
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix.
+if test $ac_header_dirent = dirent.h; then
+echo $ac_n "checking for -ldir""... $ac_c" 1>&6
+ac_lib_var=`echo dir_opendir | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldir $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1600 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char opendir();
+
+int main() { return 0; }
+int t() {
+opendir()
+; return 0; }
+EOF
+if { (eval echo configure:1610: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="$LIBS -ldir"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+else
+echo $ac_n "checking for -lx""... $ac_c" 1>&6
+ac_lib_var=`echo x_opendir | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lx $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 1637 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char opendir();
+
+int main() { return 0; }
+int t() {
+opendir()
+; return 0; }
+EOF
+if { (eval echo configure:1647: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="$LIBS -lx"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+fi
+
+echo $ac_n "checking return type of signal handlers""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_type_signal'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1672 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <signal.h>
+#ifdef signal
+#undef signal
+#endif
+#ifdef __cplusplus
+extern "C" void (*signal (int, void (*)(int)))(int);
+#else
+void (*signal ()) ();
+#endif
+
+int main() { return 0; }
+int t() {
+int i;
+; return 0; }
+EOF
+if { (eval echo configure:1690: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_type_signal=void
+else
+ rm -rf conftest*
+ ac_cv_type_signal=int
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_type_signal" 1>&6
+cat >> confdefs.h <<EOF
+#define RETSIGTYPE $ac_cv_type_signal
+EOF
+
+
+echo $ac_n "checking for uid_t in sys/types.h""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_type_uid_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1712 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "uid_t" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_uid_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_uid_t=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_type_uid_t" 1>&6
+if test $ac_cv_type_uid_t = no; then
+ cat >> confdefs.h <<\EOF
+#define uid_t int
+EOF
+
+ cat >> confdefs.h <<\EOF
+#define gid_t int
+EOF
+
+fi
+
+echo $ac_n "checking for mode_t""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_type_mode_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1745 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "mode_t" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_mode_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_mode_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_mode_t" 1>&6
+if test $ac_cv_type_mode_t = no; then
+ cat >> confdefs.h <<\EOF
+#define mode_t int
+EOF
+
+fi
+
+echo $ac_n "checking for size_t""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_type_size_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1776 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "size_t" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_size_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_size_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_size_t" 1>&6
+if test $ac_cv_type_size_t = no; then
+ cat >> confdefs.h <<\EOF
+#define size_t unsigned
+EOF
+
+fi
+
+echo $ac_n "checking for pid_t""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_type_pid_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1807 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "pid_t" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_pid_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_pid_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_pid_t" 1>&6
+if test $ac_cv_type_pid_t = no; then
+ cat >> confdefs.h <<\EOF
+#define pid_t int
+EOF
+
+fi
+
+for ac_func in getwd mkdir rename strdup strstr dup2 strerror valloc waitpid memmove vasprintf strtoul
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1840 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char $ac_func();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:1862: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ :
+else
+ echo "$ac_t""no" 1>&6
+LIBOBJS="$LIBOBJS ${ac_func}.o"
+fi
+
+done
+
+for ac_func in fchmod fsync ftime mkfifo putenv setvbuf vfork vprintf ftruncate timezone getpagesize initgroups fchdir sigaction sigprocmask sigvec sigsetmask sigblock
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1889 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char $ac_func();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:1911: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+echo $ac_n "checking for re_exec""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_re_exec'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1938 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char re_exec(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char re_exec();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_re_exec) || defined (__stub___re_exec)
+choke me
+#else
+re_exec();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:1960: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_re_exec=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_re_exec=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'re_exec`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ :
+else
+ echo "$ac_t""no" 1>&6
+LIBOBJS="$LIBOBJS regex.o"
+fi
+
+echo $ac_n "checking whether utime accepts a null argument""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_utime_null'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ rm -f conftestdata; > conftestdata
+# Sequent interprets utime(file, 0) to mean use start of epoch. Wrong.
+if test "$cross_compiling" = yes; then
+ ac_cv_func_utime_null=no
+else
+cat > conftest.$ac_ext <<EOF
+#line 1988 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+main() {
+struct stat s, t;
+exit(!(stat ("conftestdata", &s) == 0 && utime("conftestdata", (long *)0) == 0
+&& stat("conftestdata", &t) == 0 && t.st_mtime >= s.st_mtime
+&& t.st_mtime - s.st_mtime < 120));
+}
+EOF
+{ (eval echo configure:1999: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }
+if test -s conftest && (./conftest; exit) 2>/dev/null; then
+ ac_cv_func_utime_null=yes
+else
+ ac_cv_func_utime_null=no
+fi
+fi
+rm -fr conftest*
+rm -f core core.* *.core
+fi
+
+echo "$ac_t""$ac_cv_func_utime_null" 1>&6
+if test $ac_cv_func_utime_null = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_UTIME_NULL 1
+EOF
+
+fi
+
+echo $ac_n "checking for long file names""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_sys_long_file_names'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_cv_sys_long_file_names=yes
+# Test for long file names in all the places we know might matter:
+# . the current directory, where building will happen
+# /tmp where it might want to write temporary files
+# /var/tmp likewise
+# /usr/tmp likewise
+# $prefix/lib where we will be installing things
+# $exec_prefix/lib likewise
+# eval it to expand exec_prefix.
+for ac_dir in `eval echo . /tmp /var/tmp /usr/tmp $prefix/lib $exec_prefix/lib` ; do
+ test -d $ac_dir || continue
+ test -w $ac_dir || continue # It is less confusing to not echo anything here.
+ (echo 1 > $ac_dir/conftest9012345) 2>/dev/null
+ (echo 2 > $ac_dir/conftest9012346) 2>/dev/null
+ val=`cat $ac_dir/conftest9012345 2>/dev/null`
+ if test ! -f $ac_dir/conftest9012345 || test "$val" != 1; then
+ ac_cv_sys_long_file_names=no
+ rm -f $ac_dir/conftest9012345 $ac_dir/conftest9012346 2>/dev/null
+ break
+ fi
+ rm -f $ac_dir/conftest9012345 $ac_dir/conftest9012346 2>/dev/null
+done
+fi
+
+echo "$ac_t""$ac_cv_sys_long_file_names" 1>&6
+if test $ac_cv_sys_long_file_names = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_LONG_FILE_NAMES 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for working fnmatch function""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ccvs_cv_sys_working_fnmatch'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ ccvs_cv_sys_working_fnmatch=no
+else
+cat > conftest.$ac_ext <<EOF
+#line 2063 "configure"
+#include "confdefs.h"
+
+#include <fnmatch.h>
+int
+main ()
+{
+ exit ((fnmatch ("a", "a", FNM_PATHNAME) == 0
+ && fnmatch ("a", "b", FNM_PATHNAME) == FNM_NOMATCH)
+ ? 0 : 1);
+}
+EOF
+{ (eval echo configure:2075: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }
+if test -s conftest && (./conftest; exit) 2>/dev/null; then
+ ccvs_cv_sys_working_fnmatch=yes
+else
+ ccvs_cv_sys_working_fnmatch=no
+fi
+fi
+rm -fr conftest*
+fi
+
+if test $ccvs_cv_sys_working_fnmatch = no; then
+ LIBOBJS="$LIBOBJS fnmatch.o"
+fi
+echo "$ac_t""$ccvs_cv_sys_working_fnmatch" 1>&6
+
+KRB4=/usr/kerberos
+
+# Check whether --with-krb4 or --without-krb4 was given.
+if test "${with_krb4+set}" = set; then
+ withval="$with_krb4"
+ KRB4=$withval
+fi
+echo "default place for krb4 is $KRB4"
+
+
+echo $ac_n "checking size of long""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_sizeof_long'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; }
+else
+cat > conftest.$ac_ext <<EOF
+#line 2108 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+main()
+{
+ FILE *f=fopen("conftestval", "w");
+ if (!f) exit(1);
+ fprintf(f, "%d\n", sizeof(long));
+ exit(0);
+}
+EOF
+{ (eval echo configure:2119: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }
+if test -s conftest && (./conftest; exit) 2>/dev/null; then
+ ac_cv_sizeof_long=`cat conftestval`
+else
+ ac_cv_sizeof_long=0
+fi
+fi
+rm -fr conftest*
+fi
+echo "$ac_t""$ac_cv_sizeof_long" 1>&6
+cat >> confdefs.h <<EOF
+#define SIZEOF_LONG $ac_cv_sizeof_long
+EOF
+
+
+echo $ac_n "checking size of int""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_sizeof_int'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; }
+else
+cat > conftest.$ac_ext <<EOF
+#line 2142 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+main()
+{
+ FILE *f=fopen("conftestval", "w");
+ if (!f) exit(1);
+ fprintf(f, "%d\n", sizeof(int));
+ exit(0);
+}
+EOF
+{ (eval echo configure:2153: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }
+if test -s conftest && (./conftest; exit) 2>/dev/null; then
+ ac_cv_sizeof_int=`cat conftestval`
+else
+ ac_cv_sizeof_int=0
+fi
+fi
+rm -fr conftest*
+fi
+echo "$ac_t""$ac_cv_sizeof_int" 1>&6
+cat >> confdefs.h <<EOF
+#define SIZEOF_INT $ac_cv_sizeof_int
+EOF
+
+
+
+krb_h=
+echo $ac_n "checking for krb.h""... $ac_c" 1>&6
+cat > conftest.$ac_ext <<EOF
+#line 2172 "configure"
+#include "confdefs.h"
+#include <krb.h>
+int main() { return 0; }
+int t() {
+int i;
+; return 0; }
+EOF
+if { (eval echo configure:2180: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ krb_h=yes krb_incdir=
+else
+ rm -rf conftest*
+ if test "$cross_compiling" != yes && test -r $KRB4/include/krb.h; then
+ hold_cflags=$CFLAGS
+ CFLAGS="$CFLAGS -I$KRB4/include"
+ cat > conftest.$ac_ext <<EOF
+#line 2189 "configure"
+#include "confdefs.h"
+#include <krb.h>
+int main() { return 0; }
+int t() {
+int i;
+; return 0; }
+EOF
+if { (eval echo configure:2197: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ krb_h=yes krb_incdir=$KRB4/include
+fi
+rm -f conftest*
+
+ CFLAGS=$hold_cflags
+ fi
+fi
+rm -f conftest*
+
+if test -z "$krb_h"; then
+ cat > conftest.$ac_ext <<EOF
+#line 2210 "configure"
+#include "confdefs.h"
+#include <krb.h>
+int main() { return 0; }
+int t() {
+int i;
+; return 0; }
+EOF
+if { (eval echo configure:2218: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ krb_h=yes krb_incdir=
+else
+ rm -rf conftest*
+ if test "$cross_compiling" != yes && test -r $KRB4/include/kerberosIV/krb.h; then
+ hold_cflags=$CFLAGS
+ CFLAGS="$CFLAGS -I$KRB4/include/kerberosIV"
+ cat > conftest.$ac_ext <<EOF
+#line 2227 "configure"
+#include "confdefs.h"
+#include <krb.h>
+int main() { return 0; }
+int t() {
+int i;
+; return 0; }
+EOF
+if { (eval echo configure:2235: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ krb_h=yes krb_incdir=$KRB4/include/kerberosIV
+fi
+rm -f conftest*
+
+ CFLAGS=$hold_cflags
+ fi
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$krb_h" 1>&6
+
+if test -n "$krb_h"; then
+ krb_lib=
+ echo $ac_n "checking for -lkrb""... $ac_c" 1>&6
+ac_lib_var=`echo krb_printf | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lkrb $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2259 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char printf();
+
+int main() { return 0; }
+int t() {
+printf()
+; return 0; }
+EOF
+if { (eval echo configure:2269: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ krb_lib=yes krb_libdir=
+else
+ echo "$ac_t""no" 1>&6
+if test "$cross_compiling" != yes && test -r $KRB4/lib/libkrb.a; then
+ krb_lib=yes krb_libdir=$KRB4/lib
+ fi
+fi
+
+ if test -n "$krb_lib"; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_KERBEROS 1
+EOF
+
+ test -n "${krb_libdir}" && LIBS="${LIBS} -L${krb_libdir}"
+ LIBS="${LIBS} -lkrb"
+ echo $ac_n "checking for -ldes""... $ac_c" 1>&6
+ac_lib_var=`echo des_printf | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldes $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2305 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char printf();
+
+int main() { return 0; }
+int t() {
+printf()
+; return 0; }
+EOF
+if { (eval echo configure:2315: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="${LIBS} -ldes"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ if test -n "$krb_incdir"; then
+ includeopt="${includeopt} -I$krb_incdir"
+
+ fi
+ fi
+fi
+for ac_func in krb_get_err_text
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2346 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char $ac_func();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2368: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+# If we can't find connect, try looking in -lsocket, -lnsl, and -linet.
+# The Irix 5 libc.so has connect and gethostbyname, but Irix 5 also has
+# libsocket.so which has a bad implementation of gethostbyname (it
+# only looks in /etc/hosts), so we only look for -lsocket if we need
+# it.
+unset ac_cv_func_connect
+echo $ac_n "checking for connect""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_connect'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2401 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char connect(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char connect();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_connect) || defined (__stub___connect)
+choke me
+#else
+connect();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2423: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_connect=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_connect=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'connect`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ :
+else
+ echo "$ac_t""no" 1>&6
+case "$LIBS" in
+*-lnsl*) ;;
+*) echo $ac_n "checking for -lnsl_s""... $ac_c" 1>&6
+ac_lib_var=`echo nsl_s_printf | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lnsl_s $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2448 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char printf();
+
+int main() { return 0; }
+int t() {
+printf()
+; return 0; }
+EOF
+if { (eval echo configure:2458: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo nsl_s | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-lnsl_s $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+ ;;
+esac
+case "$LIBS" in
+*-lnsl*) ;;
+*) echo $ac_n "checking for -lnsl""... $ac_c" 1>&6
+ac_lib_var=`echo nsl_printf | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lnsl $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2493 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char printf();
+
+int main() { return 0; }
+int t() {
+printf()
+; return 0; }
+EOF
+if { (eval echo configure:2503: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo nsl | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-lnsl $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+ ;;
+esac
+case "$LIBS" in
+*-lsocket*) ;;
+*) echo $ac_n "checking for -lsocket""... $ac_c" 1>&6
+ac_lib_var=`echo socket_connect | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lsocket $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2538 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char connect();
+
+int main() { return 0; }
+int t() {
+connect()
+; return 0; }
+EOF
+if { (eval echo configure:2548: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo socket | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-lsocket $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+ ;;
+esac
+case "$LIBS" in
+*-linet*) ;;
+*) echo $ac_n "checking for -linet""... $ac_c" 1>&6
+ac_lib_var=`echo inet_connect | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-linet $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2583 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char connect();
+
+int main() { return 0; }
+int t() {
+connect()
+; return 0; }
+EOF
+if { (eval echo configure:2593: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo inet | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-linet $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+ ;;
+esac
+unset ac_cv_func_connect
+for ac_func in connect
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2626 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char $ac_func();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2648: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+
+echo $ac_n "checking for gethostname""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_gethostname'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2678 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char gethostname(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char gethostname();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_gethostname) || defined (__stub___gethostname)
+choke me
+#else
+gethostname();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2700: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_gethostname=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_gethostname=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'gethostname`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ :
+else
+ echo "$ac_t""no" 1>&6
+LIBOBJS="$LIBOBJS hostname.o"
+fi
+
+
+# If we have connect(), we want the full client & server arrangement.
+if test "$ac_cv_func_connect" = yes; then
+cat >> confdefs.h <<\EOF
+#define CLIENT_SUPPORT 1
+EOF
+
+cat >> confdefs.h <<\EOF
+#define SERVER_SUPPORT 1
+EOF
+
+# Define AUTH_SERVER_SUPPORT only if we could locate the crypt() function
+unset ac_cv_func_crypt
+echo $ac_n "checking for crypt""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_crypt'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2736 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char crypt(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char crypt();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_crypt) || defined (__stub___crypt)
+choke me
+#else
+crypt();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2758: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_crypt=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_crypt=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'crypt`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ :
+else
+ echo "$ac_t""no" 1>&6
+case "$LIBS" in
+*-lcrypt*) ;;
+*) echo $ac_n "checking for -lcrypt""... $ac_c" 1>&6
+ac_lib_var=`echo crypt_crypt | tr '.-/+' '___p'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lcrypt $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2783 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+char crypt();
+
+int main() { return 0; }
+int t() {
+crypt()
+; return 0; }
+EOF
+if { (eval echo configure:2793: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_lib=HAVE_LIB`echo crypt | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+ LIBS="-lcrypt $LIBS"
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+ ;;
+esac
+unset ac_cv_func_crypt
+for ac_func in crypt
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2826 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+char $ac_func();
+
+int main() { return 0; }
+int t() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:2848: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+
+fi
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+ if test "$ac_cv_func_crypt" = yes; then
+ cat >> confdefs.h <<\EOF
+#define AUTH_SERVER_SUPPORT 1
+EOF
+
+ fi
+fi
+
+test -f src/options.h && (
+ echo "configure: warning: saving ./src/options.h in ./src/options.h-SAVED" 1>&2
+ echo "configure: warning: You may wish to check that local options have not been lost." 1>&2
+ echo "configure: warning: Do not re-run ./configure or ./config.status until you have...." 1>&2
+ cp ./src/options.h ./src/options.h-SAVED
+)
+
+trap '' 1 2 15
+cat > confcache <<\EOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs. It is not useful on other systems.
+# If it contains results you don't want to keep, you may remove or edit it.
+#
+# By default, configure uses ./config.cache as the cache file,
+# creating it if it does not exist already. You can give configure
+# the --cache-file=FILE option to use a different cache file; that is
+# what configure does when it calls configure scripts in
+# subdirectories, so they share the cache.
+# Giving --cache-file=/dev/null disables caching, for debugging configure.
+# config.status only pays attention to the cache file if you give it the
+# --recheck option to rerun configure.
+#
+EOF
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(set) 2>&1 |
+ sed -n "s/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=\${\1='\2'}/p" \
+ >> confcache
+if cmp -s $cache_file confcache; then
+ :
+else
+ if test -w $cache_file; then
+ echo "updating cache $cache_file"
+ cat confcache > $cache_file
+ else
+ echo "not updating unwritable cache $cache_file"
+ fi
+fi
+rm -f confcache
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Any assignment to VPATH causes Sun make to only execute
+# the first set of double-colon rules, so remove it if not needed.
+# If there is a colon in the path, we need to keep it.
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d'
+fi
+
+trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
+
+DEFS=-DHAVE_CONFIG_H
+
+# Without the "./", some shells look in PATH for config.status.
+: ${CONFIG_STATUS=./config.status}
+
+echo creating $CONFIG_STATUS
+rm -f $CONFIG_STATUS
+cat > $CONFIG_STATUS <<EOF
+#! /bin/sh
+# Generated automatically by configure.
+# Run this file to recreate the current configuration.
+# This directory was configured as follows,
+# on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+#
+# $0 $ac_configure_args
+#
+# Compiler output produced by configure, useful for debugging
+# configure, is in ./config.log if it exists.
+
+ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]"
+for ac_option
+do
+ case "\$ac_option" in
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion"
+ exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;;
+ -version | --version | --versio | --versi | --vers | --ver | --ve | --v)
+ echo "$CONFIG_STATUS generated by autoconf version 2.9"
+ exit 0 ;;
+ -help | --help | --hel | --he | --h)
+ echo "\$ac_cs_usage"; exit 0 ;;
+ *) echo "\$ac_cs_usage"; exit 1 ;;
+ esac
+done
+
+ac_given_srcdir=$srcdir
+ac_given_INSTALL="$INSTALL"
+
+trap 'rm -fr `echo "Makefile lib/Makefile src/Makefile doc/Makefile \
+ man/Makefile tools/Makefile tools/pcl-cvs/Makefile \
+ contrib/Makefile contrib/elib/Makefile \
+ windows-NT/Makefile os2/Makefile macintosh/Makefile stamp-h config.h src/options.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+# Protect against being on the right side of a sed subst in config.status.
+sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g;
+ s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF
+$ac_vpsub
+$extrasub
+s%@CFLAGS@%$CFLAGS%g
+s%@CPPFLAGS@%$CPPFLAGS%g
+s%@CXXFLAGS@%$CXXFLAGS%g
+s%@DEFS@%$DEFS%g
+s%@LDFLAGS@%$LDFLAGS%g
+s%@LIBS@%$LIBS%g
+s%@exec_prefix@%$exec_prefix%g
+s%@prefix@%$prefix%g
+s%@program_transform_name@%$program_transform_name%g
+s%@bindir@%$bindir%g
+s%@sbindir@%$sbindir%g
+s%@libexecdir@%$libexecdir%g
+s%@datadir@%$datadir%g
+s%@sysconfdir@%$sysconfdir%g
+s%@sharedstatedir@%$sharedstatedir%g
+s%@localstatedir@%$localstatedir%g
+s%@libdir@%$libdir%g
+s%@includedir@%$includedir%g
+s%@oldincludedir@%$oldincludedir%g
+s%@infodir@%$infodir%g
+s%@mandir@%$mandir%g
+s%@CC@%$CC%g
+s%@CPP@%$CPP%g
+s%@CVS@%$CVS%g
+s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g
+s%@INSTALL_DATA@%$INSTALL_DATA%g
+s%@RANLIB@%$RANLIB%g
+s%@YACC@%$YACC%g
+s%@SET_MAKE@%$SET_MAKE%g
+s%@perl_path@%$perl_path%g
+s%@csh_path@%$csh_path%g
+s%@LIBOBJS@%$LIBOBJS%g
+s%@KRB4@%$KRB4%g
+s%@includeopt@%$includeopt%g
+
+CEOF
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+CONFIG_FILES=\${CONFIG_FILES-"Makefile lib/Makefile src/Makefile doc/Makefile \
+ man/Makefile tools/Makefile tools/pcl-cvs/Makefile \
+ contrib/Makefile contrib/elib/Makefile \
+ windows-NT/Makefile os2/Makefile macintosh/Makefile stamp-h"}
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then
+ # Support "outfile[:infile]", defaulting infile="outfile.in".
+ case "$ac_file" in
+ *:*) ac_file_in=`echo "$ac_file"|sed 's%.*:%%'`
+ ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+ *) ac_file_in="${ac_file}.in" ;;
+ esac
+
+ # Adjust relative srcdir, etc. for subdirectories.
+
+ # Remove last slash and all that follows it. Not all systems have dirname.
+ ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+ if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+ # The file is in a subdirectory.
+ test ! -d "$ac_dir" && mkdir "$ac_dir"
+ ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`"
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'`
+ else
+ ac_dir_suffix= ac_dots=
+ fi
+
+ case "$ac_given_srcdir" in
+ .) srcdir=.
+ if test -z "$ac_dots"; then top_srcdir=.
+ else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;;
+ /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;;
+ *) # Relative path.
+ srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix"
+ top_srcdir="$ac_dots$ac_given_srcdir" ;;
+ esac
+
+ case "$ac_given_INSTALL" in
+ [/$]*) INSTALL="$ac_given_INSTALL" ;;
+ *) INSTALL="$ac_dots$ac_given_INSTALL" ;;
+ esac
+ echo creating "$ac_file"
+ rm -f "$ac_file"
+ configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure."
+ case "$ac_file" in
+ *Makefile*) ac_comsub="1i\\
+# $configure_input" ;;
+ *) ac_comsub= ;;
+ esac
+ sed -e "$ac_comsub
+s%@configure_input@%$configure_input%g
+s%@srcdir@%$srcdir%g
+s%@top_srcdir@%$top_srcdir%g
+s%@INSTALL@%$INSTALL%g
+" -f conftest.subs $ac_given_srcdir/$ac_file_in > $ac_file
+fi; done
+rm -f conftest.subs
+
+# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where
+# NAME is the cpp macro being defined and VALUE is the value it is being given.
+#
+# ac_d sets the value in "#define NAME VALUE" lines.
+ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)'
+ac_dB='\([ ][ ]*\)[^ ]*%\1#\2'
+ac_dC='\3'
+ac_dD='%g'
+# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE".
+ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)'
+ac_uB='\([ ]\)%\1#\2define\3'
+ac_uC=' '
+ac_uD='\4%g'
+# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE".
+ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)'
+ac_eB='$%\1#\2define\3'
+ac_eC=' '
+ac_eD='%g'
+
+CONFIG_HEADERS=${CONFIG_HEADERS-"config.h src/options.h"}
+for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then
+ # Support "outfile[:infile]", defaulting infile="outfile.in".
+ case "$ac_file" in
+ *:*) ac_file_in=`echo "$ac_file"|sed 's%.*:%%'`
+ ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+ *) ac_file_in="${ac_file}.in" ;;
+ esac
+
+ echo creating $ac_file
+
+ rm -f conftest.frag conftest.in conftest.out
+ cp $ac_given_srcdir/$ac_file_in conftest.in
+
+EOF
+
+# Transform confdefs.h into a sed script conftest.vals that substitutes
+# the proper values into config.h.in to produce config.h. And first:
+# Protect against being on the right side of a sed subst in config.status.
+# Protect against being in an unquoted here document in config.status.
+rm -f conftest.vals
+cat > conftest.hdr <<\EOF
+s/[\\&%]/\\&/g
+s%[\\$`]%\\&%g
+s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp
+s%ac_d%ac_u%gp
+s%ac_u%ac_e%gp
+EOF
+sed -n -f conftest.hdr confdefs.h > conftest.vals
+rm -f conftest.hdr
+
+# This sed command replaces #undef with comments. This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+cat >> conftest.vals <<\EOF
+s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */%
+EOF
+
+# Break up conftest.vals because some shells have a limit on
+# the size of here documents, and old seds have small limits too.
+# Maximum number of lines to put in a single here document.
+ac_max_here_lines=12
+
+rm -f conftest.tail
+while :
+do
+ ac_lines=`grep -c . conftest.vals`
+ # grep -c gives empty output for an empty file on some AIX systems.
+ if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi
+ # Write a limited-size here document to conftest.frag.
+ echo ' cat > conftest.frag <<CEOF' >> $CONFIG_STATUS
+ sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS
+ echo 'CEOF
+ sed -f conftest.frag conftest.in > conftest.out
+ rm -f conftest.in
+ mv conftest.out conftest.in
+' >> $CONFIG_STATUS
+ sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail
+ rm -f conftest.vals
+ mv conftest.tail conftest.vals
+done
+rm -f conftest.vals
+
+cat >> $CONFIG_STATUS <<\EOF
+ rm -f conftest.frag conftest.h
+ echo "/* $ac_file. Generated automatically by configure. */" > conftest.h
+ cat conftest.in >> conftest.h
+ rm -f conftest.in
+ if cmp -s $ac_file conftest.h 2>/dev/null; then
+ echo "$ac_file is unchanged"
+ rm -f conftest.h
+ else
+ rm -f $ac_file
+ mv conftest.h $ac_file
+ fi
+fi; done
+
+
+
+exit 0
+EOF
+chmod +x $CONFIG_STATUS
+rm -fr confdefs* $ac_clean_files
+test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1
+
diff --git a/contrib/cvs/configure.in b/contrib/cvs/configure.in
new file mode 100644
index 0000000..554a401
--- /dev/null
+++ b/contrib/cvs/configure.in
@@ -0,0 +1,191 @@
+dnl configure.in for cvs
+dnl "$CVSid$"
+AC_INIT(src/cvs.h)
+AC_PREREQ(2.4)dnl Required Autoconf version.
+AC_CONFIG_HEADER(config.h src/options.h)
+
+AC_PROG_CC
+
+AC_AIX
+AC_MINIX
+AC_ISC_POSIX
+if test "$ISC" = yes; then
+CFLAGS="$CFLAGS -D_SYSV3"
+fi
+
+AC_PREFIX_PROGRAM(cvs)
+
+AC_C_CROSS
+
+AC_C_CONST
+AC_C_CHAR_UNSIGNED
+AC_C_INLINE
+
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+AC_PROG_YACC
+AC_PROG_MAKE_SET
+
+AC_PATH_PROG(perl_path, perl, no)
+AC_PATH_PROG(csh_path, csh, no)
+
+AC_SYS_INTERPRETER
+if test X"$ac_cv_sys_interpreter" != X"yes" ; then
+ # silly trick to avoid problems in AC macros...
+ ac_msg='perl scripts using #! may not be invoked properly'
+ AC_MSG_WARN($ac_msg)
+fi
+
+AC_HEADER_STDC
+AC_CHECK_HEADERS(errno.h unistd.h string.h memory.h utime.h fcntl.h ndbm.h \
+ sys/param.h sys/select.h sys/time.h sys/timeb.h \
+ io.h direct.h sys/bsdtypes.h sys/resource.h)
+AC_HEADER_SYS_WAIT
+AC_HEADER_STAT
+AC_HEADER_TIME
+AC_HEADER_DIRENT
+AC_TYPE_SIGNAL
+AC_TYPE_UID_T
+AC_TYPE_MODE_T
+AC_TYPE_SIZE_T
+AC_TYPE_PID_T
+AC_REPLACE_FUNCS(getwd mkdir rename strdup strstr dup2 strerror valloc waitpid memmove vasprintf strtoul)
+AC_CHECK_FUNCS(fchmod fsync ftime mkfifo putenv setvbuf vfork vprintf ftruncate timezone getpagesize initgroups fchdir sigaction sigprocmask sigvec sigsetmask sigblock)
+AC_CHECK_FUNC(re_exec, :, LIBOBJS="$LIBOBJS regex.o")
+AC_FUNC_UTIME_NULL
+AC_SYS_LONG_FILE_NAMES
+
+AC_MSG_CHECKING([for working fnmatch function])
+AC_CACHE_VAL(ccvs_cv_sys_working_fnmatch,
+[AC_TRY_RUN([
+#include <fnmatch.h>
+int
+main ()
+{
+ exit ((fnmatch ("a", "a", FNM_PATHNAME) == 0
+ && fnmatch ("a", "b", FNM_PATHNAME) == FNM_NOMATCH)
+ ? 0 : 1);
+}],
+ccvs_cv_sys_working_fnmatch=yes,
+ccvs_cv_sys_working_fnmatch=no,
+ccvs_cv_sys_working_fnmatch=no)])
+if test $ccvs_cv_sys_working_fnmatch = no; then
+ LIBOBJS="$LIBOBJS fnmatch.o"
+fi
+AC_MSG_RESULT($ccvs_cv_sys_working_fnmatch)
+
+dnl
+dnl set $(KRB4) from --with-krb4=value -- WITH_KRB4
+dnl
+KRB4=/usr/kerberos
+define(WITH_KRB4,[
+AC_ARG_WITH([krb4],
+ [ --with-krb4=value set default \$(KRB4) from value],
+ [KRB4=$withval],
+)dnl
+echo "default place for krb4 is $KRB4"
+AC_SUBST(KRB4)])dnl
+WITH_KRB4
+
+AC_CHECK_SIZEOF(long)
+AC_CHECK_SIZEOF(int)
+
+krb_h=
+AC_MSG_CHECKING([for krb.h])
+AC_TRY_LINK([#include <krb.h>],[int i;],
+ [krb_h=yes krb_incdir=],
+ [if test "$cross_compiling" != yes && test -r $KRB4/include/krb.h; then
+ hold_cflags=$CFLAGS
+ CFLAGS="$CFLAGS -I$KRB4/include"
+ AC_TRY_LINK([#include <krb.h>],[int i;],
+ [krb_h=yes krb_incdir=$KRB4/include])
+ CFLAGS=$hold_cflags
+ fi])
+if test -z "$krb_h"; then
+ AC_TRY_LINK([#include <krb.h>],[int i;],
+ [krb_h=yes krb_incdir=],
+ [if test "$cross_compiling" != yes && test -r $KRB4/include/kerberosIV/krb.h; then
+ hold_cflags=$CFLAGS
+ CFLAGS="$CFLAGS -I$KRB4/include/kerberosIV"
+ AC_TRY_LINK([#include <krb.h>],[int i;],
+ [krb_h=yes krb_incdir=$KRB4/include/kerberosIV])
+ CFLAGS=$hold_cflags
+ fi])
+fi
+AC_MSG_RESULT($krb_h)
+
+if test -n "$krb_h"; then
+ krb_lib=
+ AC_CHECK_LIB(krb,printf,[krb_lib=yes krb_libdir=],
+ [if test "$cross_compiling" != yes && test -r $KRB4/lib/libkrb.a; then
+ krb_lib=yes krb_libdir=$KRB4/lib
+ fi])
+ if test -n "$krb_lib"; then
+ AC_DEFINE(HAVE_KERBEROS)
+ test -n "${krb_libdir}" && LIBS="${LIBS} -L${krb_libdir}"
+ LIBS="${LIBS} -lkrb"
+ AC_CHECK_LIB(des,printf,[LIBS="${LIBS} -ldes"])
+ if test -n "$krb_incdir"; then
+ includeopt="${includeopt} -I$krb_incdir"
+ AC_SUBST(includeopt)
+ fi
+ fi
+fi
+AC_CHECK_FUNCS(krb_get_err_text)
+# If we can't find connect, try looking in -lsocket, -lnsl, and -linet.
+# The Irix 5 libc.so has connect and gethostbyname, but Irix 5 also has
+# libsocket.so which has a bad implementation of gethostbyname (it
+# only looks in /etc/hosts), so we only look for -lsocket if we need
+# it.
+unset ac_cv_func_connect
+AC_CHECK_FUNC(connect, :,
+[case "$LIBS" in
+*-lnsl*) ;;
+*) AC_CHECK_LIB(nsl_s, printf) ;;
+esac
+case "$LIBS" in
+*-lnsl*) ;;
+*) AC_CHECK_LIB(nsl, printf) ;;
+esac
+case "$LIBS" in
+*-lsocket*) ;;
+*) AC_CHECK_LIB(socket, connect) ;;
+esac
+case "$LIBS" in
+*-linet*) ;;
+*) AC_CHECK_LIB(inet, connect) ;;
+esac
+unset ac_cv_func_connect
+AC_CHECK_FUNCS(connect)])
+
+AC_CHECK_FUNC(gethostname, :, LIBOBJS="$LIBOBJS hostname.o")
+
+# If we have connect(), we want the full client & server arrangement.
+if test "$ac_cv_func_connect" = yes; then
+AC_DEFINE(CLIENT_SUPPORT)
+AC_DEFINE(SERVER_SUPPORT)
+# Define AUTH_SERVER_SUPPORT only if we could locate the crypt() function
+unset ac_cv_func_crypt
+AC_CHECK_FUNC(crypt, :,
+[case "$LIBS" in
+*-lcrypt*) ;;
+*) AC_CHECK_LIB(crypt, crypt) ;;
+esac
+unset ac_cv_func_crypt
+AC_CHECK_FUNCS(crypt)])
+ if test "$ac_cv_func_crypt" = yes; then
+ AC_DEFINE(AUTH_SERVER_SUPPORT)
+ fi
+fi
+
+test -f src/options.h && (
+ AC_MSG_WARN(saving ./src/options.h in ./src/options.h-SAVED)
+ AC_MSG_WARN(You may wish to check that local options have not been lost.)
+ AC_MSG_WARN(Do not re-run ./configure or ./config.status until you have....)
+ cp ./src/options.h ./src/options.h-SAVED
+)
+
+AC_OUTPUT(Makefile lib/Makefile src/Makefile doc/Makefile \
+ man/Makefile tools/Makefile tools/pcl-cvs/Makefile \
+ contrib/Makefile contrib/elib/Makefile \
+ windows-NT/Makefile os2/Makefile macintosh/Makefile stamp-h)
diff --git a/contrib/cvs/contrib/ChangeLog b/contrib/cvs/contrib/ChangeLog
new file mode 100644
index 0000000..80db5b8
--- /dev/null
+++ b/contrib/cvs/contrib/ChangeLog
@@ -0,0 +1,193 @@
+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/contrib/cvs/contrib/Makefile.in b/contrib/cvs/contrib/Makefile.in
new file mode 100644
index 0000000..a29dec0
--- /dev/null
+++ b/contrib/cvs/contrib/Makefile.in
@@ -0,0 +1,134 @@
+# 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.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# $CVSid: @(#)Makefile.in 1.6 94/10/22 $
+
+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 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 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 $(libdir)/cvs/contrib
+ 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
+ @echo "You might consider running 'cvsinit' to upgrade your repository(s)...."
+.PHONY: install
+
+$(libdir)/cvs/contrib:
+ $(top_srcdir)/mkinstalldirs $(libdir)/cvs/contrib
+
+tags:
+.PHONY: tags
+
+TAGS:
+.PHONY: TAGS
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+clean:
+ /bin/rm -f *.o core
+.PHONY: clean
+
+distclean: clean
+ rm -f Makefile elib/Makefile $(PROGS) $(CONTRIB_PROGS)
+.PHONY: distclean
+
+realclean: distclean
+.PHONY: realclean
+
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+ cd elib; ${MAKE} dist-dir DISTDIR="../${DISTDIR}/elib"
+.PHONY: dist-dir
+
+subdir = contrib
+Makefile: ../config.status Makefile.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
diff --git a/contrib/cvs/contrib/README b/contrib/cvs/contrib/README
new file mode 100644
index 0000000..d453f8d
--- /dev/null
+++ b/contrib/cvs/contrib/README
@@ -0,0 +1,106 @@
+$CVSid: @(#)README 1.12 94/09/25 $
+
+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, I must point out that these
+contributions are really, REALLY UNSUPPORTED. In fact, I probably
+don't even know what they do. Nor do I guarantee to have tried them,
+or ported them to work with this CVS distribution. If you have questions,
+you might contact the author, but you should not necessarily expect
+a reply. USE AT YOUR OWN RISK -- and all that stuff.
+
+"Unsupported" also means that noone 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.
+The exception is pcl-cvs; that is more actively maintained (see
+pcl-cvs/README). 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 also
+a web page of software related to CVS at
+http://www.loria.fr/~molli/cvs-index.html which would presumably be
+willing to list your software.
+
+Contents of 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>.
+ pcl-cvs A directory that contains GNU Emacs lisp code which
+ implements a CVS-mode for emacs.
+ Contributed by Per Cederqvist <ceder@lysator.liu.se>.
+ commit_prep 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>.
+ ccvs-rsh A Perl script which allows "rsh pipelines" to
+ be built in order to use Cyclic CVS from
+ behind some varieties of firewall.
+ 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>.
+ listener A program which listens to a TCP port, authenticates
+ by hostname, then runs a subprocess whose input/output
+ is redirected through the port.
+ Contributed by Benjamin J. Lee <benjamin@cyclic.com>
diff --git a/contrib/cvs/contrib/clmerge.pl b/contrib/cvs/contrib/clmerge.pl
new file mode 100644
index 0000000..ac81371
--- /dev/null
+++ b/contrib/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/contrib/cvs/contrib/cln_hist.pl b/contrib/cvs/contrib/cln_hist.pl
new file mode 100644
index 0000000..ff49d0a
--- /dev/null
+++ b/contrib/cvs/contrib/cln_hist.pl
@@ -0,0 +1,92 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+# $Id: cln_hist.pl,v 1.2 1995/07/10 02:01:26 kfogel Exp $
+# Contributed by David G. Grubbs <dgg@ksr.com>
+#
+# Clean up the history file. 10 Record types: MAR OFT WUCG
+#
+# WUCG records are thrown out.
+# MAR records are retained.
+# T records: retain only last tag with same combined tag/module.
+#
+# Two passes: Walk through the first time and remember the
+# 1. Last Tag record with same "tag" and "module" names.
+# 2. Last O record with unique user/module/directory, unless followed
+# by a matching F record.
+#
+
+$r = $ENV{"CVSROOT"};
+$c = "$r/CVSROOT";
+$h = "$c/history";
+
+eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
+ while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
+exit 255 if $die; # process any variable=value switches
+
+%tags = ();
+%outs = ();
+
+#
+# Move history file to safe place and re-initialize a new one.
+#
+rename($h, "$h.bak");
+open(XX, ">$h");
+close(XX);
+
+#
+# Pass1 -- remember last tag and checkout.
+#
+open(HIST, "$h.bak");
+while (<HIST>) {
+ next if /^[MARWUCG]/;
+
+ # Save whole line keyed by tag|module
+ if (/^T/) {
+ @tmp = split(/\|/, $_);
+ $tags{$tmp[4] . '|' . $tmp[5]} = $_;
+ }
+ # Save whole line
+ if (/^[OF]/) {
+ @tmp = split(/\|/, $_);
+ $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} = $_;
+ }
+}
+
+#
+# Pass2 -- print out what we want to save.
+#
+open(SAVE, ">$h.work");
+open(HIST, "$h.bak");
+while (<HIST>) {
+ next if /^[FWUCG]/;
+
+ # If whole line matches saved (i.e. "last") one, print it.
+ if (/^T/) {
+ @tmp = split(/\|/, $_);
+ next if $tags{$tmp[4] . '|' . $tmp[5]} ne $_;
+ }
+ # Save whole line
+ if (/^O/) {
+ @tmp = split(/\|/, $_);
+ next if $outs{$tmp[1] . '|' . $tmp[2] . '|' . $tmp[5]} ne $_;
+ }
+
+ print SAVE $_;
+}
+
+#
+# Put back the saved stuff
+#
+system "cat $h >> $h.work";
+
+if (-s $h) {
+ rename ($h, "$h.interim");
+ print "history.interim has non-zero size.\n";
+} else {
+ unlink($h);
+}
+
+rename ("$h.work", $h);
+
+exit(0);
diff --git a/contrib/cvs/contrib/commit_prep.pl b/contrib/cvs/contrib/commit_prep.pl
new file mode 100644
index 0000000..5272c04
--- /dev/null
+++ b/contrib/cvs/contrib/commit_prep.pl
@@ -0,0 +1,216 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+#ident "@(#)cvs/contrib:$Name: $:$Id: commit_prep.pl,v 1.2 1995/07/10 02:01:29 kfogel Exp $"
+#
+# 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/contrib/cvs/contrib/cvs_acls.pl b/contrib/cvs/contrib/cvs_acls.pl
new file mode 100644
index 0000000..bcb544d
--- /dev/null
+++ b/contrib/cvs/contrib/cvs_acls.pl
@@ -0,0 +1,143 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+# $Id: cvs_acls.pl,v 1.2 1995/07/10 02:01:33 kfogel Exp $
+#
+# Access control lists for CVS. dgg@ksr.com (David G. Grubbs)
+#
+# CVS "commitinfo" for matching repository names, running the program it finds
+# on the same line. More information is available in the CVS man pages.
+#
+# ==== INSTALLATION:
+#
+# To use this program as I intended, do the following four things:
+#
+# 0. Install PERL. :-)
+#
+# 1. Put one line, as the *only* non-comment line, in your commitinfo file:
+#
+# DEFAULT /usr/local/bin/cvs_acls
+#
+# 2. Install this file as /usr/local/bin/cvs_acls and make it executable.
+#
+# 3. Create a file named $CVSROOT/CVSROOT/avail.
+#
+# ==== FORMAT OF THE avail FILE:
+#
+# The avail file determines whether you may commit files. It contains lines
+# read from top to bottom, keeping track of a single "bit". The "bit"
+# defaults to "on". It can be turned "off" by "unavail" lines and "on" by
+# "avail" lines. ==> Last one counts.
+#
+# Any line not beginning with "avail" or "unavail" is ignored.
+#
+# Lines beginning with "avail" or "unavail" are assumed to be '|'-separated
+# triples: (All spaces and tabs are ignored in a line.)
+#
+# {avail.*,unavail.*} [| user,user,... [| repos,repos,...]]
+#
+# 1. String starting with "avail" or "unavail".
+# 2. Optional, comma-separated list of usernames.
+# 3. Optional, comma-separated list of repository pathnames.
+# These are pathnames relative to $CVSROOT. They can be directories or
+# filenames. A directory name allows access to all files and
+# directories below it.
+#
+# Example: (Text from the ';;' rightward may not appear in the file.)
+#
+# unavail ;; Make whole repository unavailable.
+# avail|dgg ;; Except for user "dgg".
+# avail|fred, john|bin/ls ;; Except when "fred" or "john" commit to
+# ;; the module whose repository is "bin/ls"
+#
+# PROGRAM LOGIC:
+#
+# CVS passes to @ARGV an absolute directory pathname (the repository
+# appended to your $CVSROOT variable), followed by a list of filenames
+# within that directory.
+#
+# We walk through the avail file looking for a line that matches both
+# the username and repository.
+#
+# A username match is simply the user's name appearing in the second
+# column of the avail line in a space-or-comma separate list.
+#
+# A repository match is either:
+# - One element of the third column matches $ARGV[0], or some
+# parent directory of $ARGV[0].
+# - Otherwise *all* file arguments ($ARGV[1..$#ARGV]) must be
+# in the file list in one avail line.
+# - In other words, using directory names in the third column of
+# the avail file allows committing of any file (or group of
+# files in a single commit) in the tree below that directory.
+# - If individual file names are used in the third column of
+# the avail file, then files must be committed individually or
+# all files specified in a single commit must all appear in
+# third column of a single avail line.
+#
+
+$debug = 0;
+$cvsroot = $ENV{'CVSROOT'};
+$availfile = $cvsroot . "/CVSROOT/avail";
+$myname = $ENV{"USER"} if !($myname = $ENV{"LOGNAME"});
+
+eval "print STDERR \$die='Unknown parameter $1\n' if !defined \$$1; \$$1=\$';"
+ while ($ARGV[0] =~ /^(\w+)=/ && shift(@ARGV));
+exit 255 if $die; # process any variable=value switches
+
+die "Must set CVSROOT\n" if !$cvsroot;
+($repos = shift) =~ s:^$cvsroot/::;
+grep($_ = $repos . '/' . $_, @ARGV);
+
+print "$$ Repos: $repos\n","$$ ==== ",join("\n$$ ==== ",@ARGV),"\n" if $debug;
+
+$exit_val = 0; # Good Exit value
+
+$universal_off = 0;
+open (AVAIL, $availfile) || exit(0); # It is ok for avail file not to exist
+while (<AVAIL>) {
+ chop;
+ next if /^\s*\#/;
+ next if /^\s*$/;
+ ($flagstr, $u, $m) = split(/[\s,]*\|[\s,]*/, $_);
+
+ # Skip anything not starting with "avail" or "unavail" and complain.
+ (print "Bad avail line: $_\n"), next
+ if ($flagstr !~ /^avail/ && $flagstr !~ /^unavail/);
+
+ # Set which bit we are playing with. ('0' is OK == Available).
+ $flag = (($& eq "avail") ? 0 : 1);
+
+ # If we find a "universal off" flag (i.e. a simple "unavail") remember it
+ $universal_off = 1 if ($flag && !$u && !$m);
+
+ # $myname considered "in user list" if actually in list or is NULL
+ $in_user = (!$u || grep ($_ eq $myname, split(/[\s,]+/,$u)));
+ print "$$ \$myname($myname) in user list: $_\n" if $debug && $in_user;
+
+ # Module matches if it is a NULL module list in the avail line. If module
+ # list is not null, we check every argument combination.
+ if (!($in_repo = !$m)) {
+ @tmp = split(/[\s,]+/,$m);
+ for $j (@tmp) {
+ # If the repos from avail is a parent(or equal) dir of $repos, OK
+ $in_repo = 1, last if ($repos eq $j || $repos =~ /^$j\//);
+ }
+ if (!$in_repo) {
+ $in_repo = 1;
+ for $j (@ARGV) {
+ last if !($in_repo = grep ($_ eq $j, @tmp));
+ }
+ }
+ }
+ print "$$ \$repos($repos) in repository list: $_\n" if $debug && $in_repo;
+
+ $exit_val = $flag if ($in_user && $in_repo);
+ print "$$ ==== \$exit_val = $exit_val\n$$ ==== \$flag = $flag\n" if $debug;
+}
+close(AVAIL);
+print "$$ ==== \$exit_val = $exit_val\n" if $debug;
+print "**** Access denied: Insufficient Karma ($myname|$repos)\n" if $exit_val;
+print "**** Access allowed: Personal Karma exceeds Environmental Karma.\n"
+ if $universal_off && !$exit_val;
+exit($exit_val);
diff --git a/contrib/cvs/contrib/cvscheck.man b/contrib/cvs/contrib/cvscheck.man
new file mode 100644
index 0000000..61a064a
--- /dev/null
+++ b/contrib/cvs/contrib/cvscheck.man
@@ -0,0 +1,53 @@
+.\" $Id: cvscheck.man,v 1.1.1.3 1995/08/28 16:20:24 jimb Exp $
+.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+.TH CVSCHECK LOCAL "4 March 1991" FLUKE
+.SH NAME
+cvscheck \- identify files added, changed, or removed in a CVS working
+directory
+.SH SYNOPSIS
+.B cvscheck
+.SH DESCRIPTION
+This command is a housekeeping aid. It should be run in a working
+directory that has been checked out using CVS. It identifies files
+that have been added, changed, or removed in the working directory, but
+not CVS
+.BR commit ted.
+It also determines whether the files have been CVS
+.BR add ed
+or CVS
+.BR remove d.
+For directories, this command determines only whether they have been
+.BR add ed.
+It operates in the current directory only.
+.LP
+This command provides information that is available using CVS
+.B status
+and CVS
+.BR diff .
+The advantage of
+.B cvscheck
+is that its output is very concise. It saves you the strain (and
+potential error) of interpreting the output of CVS
+.B status
+and
+.BR diff .
+.LP
+See
+.BR cvs (local)
+or
+.BR cvshelp (local)
+for instructions on how to add or remove a file or directory in a
+CVS-controlled package.
+.SH DIAGNOSTICS
+The exit status is 0 if no files have been added, changed, or removed
+from the current directory. Otherwise, the command returns a count of
+the adds, changes, and deletes.
+.SH SEE ALSO
+.BR cvs (local),
+.BR cvshelp (local)
+.SH AUTHOR
+Lowell Skoog
+.br
+Software Technology Group
+.br
+Technical Computing
diff --git a/contrib/cvs/contrib/cvscheck.sh b/contrib/cvs/contrib/cvscheck.sh
new file mode 100644
index 0000000..96dba6e
--- /dev/null
+++ b/contrib/cvs/contrib/cvscheck.sh
@@ -0,0 +1,84 @@
+#! /bin/sh
+# $Id: cvscheck.sh,v 1.1 1995/07/10 02:26:29 kfogel Exp $
+#
+# cvscheck - identify files added, changed, or removed
+# in CVS working directory
+#
+# Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+#
+# This program should be run in a working directory that has been
+# checked out using CVS. It identifies files that have been added,
+# changed, or removed in the working directory, but not "cvs
+# committed". It also determines whether the files have been "cvs
+# added" or "cvs removed". For directories, it is only practical to
+# determine whether they have been added.
+
+name=cvscheck
+changes=0
+
+# If we can't run CVS commands in this directory
+cvs status . > /dev/null 2>&1
+if [ $? != 0 ] ; then
+
+ # Bail out
+ echo "$name: there is no version here; bailing out" 1>&2
+ exit 1
+fi
+
+# Identify files added to working directory
+for file in .* * ; do
+
+ # Skip '.' and '..'
+ if [ $file = '.' -o $file = '..' ] ; then
+ continue
+ fi
+
+ # If a regular file
+ if [ -f $file ] ; then
+ if cvs status $file | grep -s '^From:[ ]*New file' ; then
+ echo "file added: $file - not CVS committed"
+ changes=`expr $changes + 1`
+ elif cvs status $file | grep -s '^From:[ ]*no entry for' ; then
+ echo "file added: $file - not CVS added, not CVS committed"
+ changes=`expr $changes + 1`
+ fi
+
+ # Else if a directory
+ elif [ -d $file -a $file != CVS.adm ] ; then
+
+ # Move into it
+ cd $file
+
+ # If CVS commands don't work inside
+ cvs status . > /dev/null 2>&1
+ if [ $? != 0 ] ; then
+ echo "directory added: $file - not CVS added"
+ changes=`expr $changes + 1`
+ fi
+
+ # Move back up
+ cd ..
+ fi
+done
+
+# Identify changed files
+changedfiles=`cvs diff | egrep '^diff' | awk '{print $3}'`
+for file in $changedfiles ; do
+ echo "file changed: $file - not CVS committed"
+ changes=`expr $changes + 1`
+done
+
+# Identify files removed from working directory
+removedfiles=`cvs status | egrep '^File:[ ]*no file' | awk '{print $4}'`
+
+# Determine whether each file has been cvs removed
+for file in $removedfiles ; do
+ if cvs status $file | grep -s '^From:[ ]*-' ; then
+ echo "file removed: $file - not CVS committed"
+ else
+ echo "file removed: $file - not CVS removed, not CVS committed"
+ fi
+ changes=`expr $changes + 1`
+done
+
+exit $changes
diff --git a/contrib/cvs/contrib/cvshelp.man b/contrib/cvs/contrib/cvshelp.man
new file mode 100644
index 0000000..2cfae1f
--- /dev/null
+++ b/contrib/cvs/contrib/cvshelp.man
@@ -0,0 +1,562 @@
+.\" $Id: cvshelp.man,v 1.1.1.3 1995/08/28 16:20:28 jimb Exp $
+.\" Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net>
+.\" Full space in nroff; half space in troff
+.de SP
+.if n .sp
+.if t .sp .5
+..
+.\" Start a command example
+.de XS
+.SP
+.in +.5i
+.ft B
+.nf
+..
+.\" End a command example
+.de XE
+.fi
+.ft P
+.in -.5i
+.SP
+..
+.TH CVSHELP LOCAL "17 March 1991" FLUKE
+.SH NAME
+cvshelp \- advice on using the Concurrent Versions System
+.SH DESCRIPTION
+This man page is based on experience using CVS.
+It is bound to change as we gain more experience.
+If you come up with better advice than is found here,
+contact the Software Technology
+Group and we will add it to this page.
+.SS "Getting Started"
+Use the following steps to prepare to use CVS:
+.TP
+\(bu
+Take a look at the CVS manual page to see what it can do for you, and
+if it fits your environment (or can possibly be made to fit your
+environment).
+.XS
+man cvs
+.XE
+If things look good, continue on...
+.TP
+\(bu
+Setup the master source repository. Choose a directory with
+ample disk space available for source files. This is where the RCS
+`,v' files will be stored. Say you choose
+.B /src/master
+as the root
+of your source repository. Make the
+.SB CVSROOT.adm
+directory in the root of the source repository:
+.XS
+mkdir /src/master/CVSROOT.adm
+.XE
+.TP
+\(bu
+Populate this directory with the
+.I loginfo
+and
+.I modules
+files from the
+.B "/usr/doc/local/cvs"
+directory. Edit these files to reflect your local source repository
+environment \- they may be quite small initially, but will grow as
+sources are added to your source repository. Turn these files into
+RCS controlled files:
+.XS
+cd /src/master/CVSROOT.adm
+ci \-m'Initial loginfo file' loginfo
+ci \-m'Initial modules file' modules
+.XE
+.TP
+\(bu
+Run the command:
+.XS
+mkmodules /src/master/CVSROOT.adm
+.XE
+This will build the
+.BR ndbm (3)
+file for the modules database.
+.TP
+\(bu
+Remember to edit the
+.I modules
+file manually when sources are checked
+in with
+.B checkin
+or CVS
+.BR add .
+A copy of the
+.I modules
+file for editing can be retrieved with the command:
+.XS
+cvs checkout CVSROOT.adm
+.XE
+.TP
+\(bu
+Have all users of the CVS system set the
+.SM CVSROOT
+environment variable appropriately to reflect the placement of your
+source repository. If the above example is used, the following
+commands can be placed in a
+.I .login
+or
+.I .profile
+file:
+.XS
+setenv CVSROOT /src/master
+.XE
+for csh users, and
+.XS
+CVSROOT=/src/master; export CVSROOT
+.XE
+for sh users.
+.SS "Placing Locally Written Sources Under CVS Control"
+Say you want to place the `whizbang' sources under
+CVS control. Say further that the sources have never
+been under revision control before.
+.TP
+\(bu
+Move the source hierarchy (lock, stock, and barrel)
+into the master source repository:
+.XS
+mv ~/whizbang $CVSROOT
+.XE
+.TP
+\(bu
+Clean out unwanted object files:
+.XS
+cd $CVSROOT/whizbang
+make clean
+.XE
+.TP
+\(bu
+Turn every file in the hierarchy into an RCS controlled file:
+.XS
+descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-nV\fR\fIx\fR\fB_\fR\fIy\fR\fB *'
+.XE
+In this example, the initial release tag is \fBV\fIx\fB_\fIy\fR,
+representing version \fIx\fR.\fIy\fR.
+.LP
+You can use CVS on sources that are already under RCS control.
+The following example shows how.
+In this example, the source package is called `skunkworks'.
+.TP
+\(bu
+Move the source hierarchy into the master source
+repository:
+.XS
+mv ~/skunkworks $CVSROOT
+.XE
+.TP
+\(bu
+Clean out unwanted object files:
+.XS
+cd $CVSROOT/skunkworks
+make clean
+.XE
+.TP
+\(bu
+Clean out unwanted working files, leaving only the RCS `,v' files:
+.XS
+descend \-r rcsclean
+.XE
+Note: If any working files have been checked out and changed,
+.B rcsclean
+will fail. Check in the modified working files
+and run the command again.
+.TP
+\(bu
+Get rid of
+.SB RCS
+subdirectories. CVS does not use them.
+.XS
+descend \-r \-f 'mv RCS/*,v .'
+descend \-r \-f 'rmdir RCS'
+.XE
+.TP
+\(bu
+Delete any unwanted files that remain in the source hierarchy. Then
+make sure all files are under RCS control:
+.XS
+descend \-f 'ci \-t/dev/null \-m"Placed under CVS control" \-n\fR\fItag\fR\fB *'
+.XE
+.I tag
+is the latest symbolic revision tag that you applied to your package
+(if any). Note: This command will probably generate lots of error
+messages (for directories and existing RCS files) that you can
+ignore.
+.SS "Placing a Third-Party Source Distribution Under CVS Control"
+The
+.B checkin
+command checks third-party sources into CVS. The
+difference between third-party sources and locally
+written sources is that third-party sources must be checked into a
+separate branch (called the
+.IR "vendor branch" )
+of the RCS tree. This makes it possible to merge local changes to
+the sources with later releases from the vendor.
+.TP
+\(bu
+Save the original distribution kit somewhere. For example, if the
+master source repository is
+.B /src/master
+the distribution kit could be saved in
+.BR /src/dist .
+Organize the distribution directory so that each release
+is clearly identifiable.
+.TP
+\(bu
+Unpack the package in a scratch directory, for example
+.BR ~/scratch .
+.TP
+\(bu
+Create a repository for the package.
+In this example, the package is called `Bugs-R-Us 4.3'.
+.XS
+mkdir $CVSROOT/bugs
+.XE
+.TP
+\(bu
+Check in the unpacked files:
+.XS
+cd ~/scratch
+checkin \-m 'Bugs-R-Us 4.3 distribution' bugs VENDOR V4_3
+.XE
+There is nothing magic about the tag `VENDOR', which is applied to
+the vendor branch. You can use whatever tag you want. `VENDOR' is a
+useful convention.
+.TP
+\(bu
+Never modify vendor files before checking them in.
+Check in the files
+.I exactly
+as you unpacked them.
+If you check in locally modified files, future vendor releases may
+wipe out your local changes.
+.SS "Working With CVS-Controlled Sources"
+To use or edit the sources, you must check out a private copy.
+For the following examples, the master files are assumed to reside in
+.BR "$CVSROOT/behemoth" .
+The working directory is
+.BR "~/work" .
+See
+.BR cvs (local)
+for more details on the commands mentioned below.
+.TP
+.I "To Check Out Working Files
+Use CVS
+.BR checkout :
+.XS
+cd ~/work
+cvs checkout behemoth
+.XE
+There is nothing magic about the working directory. CVS will check
+out sources anywhere you like. Once you have a working copy of the
+sources, you can compile or edit them as desired.
+.TP
+.I "To Display Changes You Have Made"
+Use CVS
+.BR diff
+to display detailed changes, equivalent to
+.BR rcsdiff (local).
+You can also use
+.BR cvscheck (local)
+to list files added, changed, and removed in
+the directory, but not yet
+.BR commit ted.
+You must be in a directory containing working files.
+.TP
+.I "To Display Revision Information"
+Use CVS
+.BR log ,
+which is equivalent to
+.BR rlog (local).
+You must be in a directory containing working files.
+.TP
+.I "To Update Working Files"
+Use CVS
+.BR update
+in a directory containing working files.
+This command brings your working files up
+to date with changes checked into the
+master repository since you last checked out or updated
+your files.
+.TP
+.I "To Check In Your Changes"
+Use CVS
+.BR commit
+in a directory containing working files.
+This command checks your changes into the master repository.
+You can specify files by name or use
+.XS
+cvs commit \-a
+.XE
+to
+.B commit
+all the files you have changed.
+.TP
+.I "To Add a File"
+Add the file to the working directory.
+Use CVS
+.B add
+to mark the file as added.
+Use CVS
+.B commit
+to add the file to the master repository.
+.TP
+.I "To Remove a File"
+Remove the file from the working directory.
+Use CVS
+.B remove
+to mark the file as removed.
+Use CVS
+.B commit
+to move the file from its current location in the master repository
+to the CVS
+.IR Attic
+directory.
+.TP
+.I "To Add a Directory"
+Add the directory to the working directory.
+Use CVS
+.B add
+to add the directory to the master repository.
+.TP
+.I "To Remove a Directory"
+.br
+You shouldn't remove directories under CVS. You should instead remove
+their contents and then prune them (using the
+.B \-f
+and
+.B \-p
+options) when you
+.B checkout
+or
+.B update
+your working files.
+.TP
+.I "To Tag a Release"
+Use CVS
+.B tag
+to apply a symbolic tag to the latest revision of each file in the
+master repository. For example:
+.XS
+cvs tag V2_1 behemoth
+.XE
+.TP
+.I "To Retrieve an Exact Copy of a Previous Release"
+During a CVS
+.B checkout
+or
+.BR update ,
+use the
+.B \-r
+option to retrieve revisions associated with a symbolic tag.
+Use the
+.B \-f
+option to ignore all RCS files that do not contain the
+tag.
+Use the
+.B \-p
+option to prune directories that wind up empty because none
+of their files matched the tag. Example:
+.XS
+cd ~/work
+cvs checkout \-r V2_1 \-f \-p behemoth
+.XE
+.SS "Logging Changes"
+It is a good idea to keep a change log together with the
+sources. As a minimum, the change log should name and describe each
+tagged release. The change log should also be under CVS control and
+should be tagged along with the sources.
+.LP
+.BR cvslog (local)
+can help. This command logs
+changes reported during CVS
+.B commit
+operations. It automatically
+updates a change log file in your working directory. When you are
+finished making changes, you (optionally) edit the change log file and
+then commit it to the master repository.
+.LP
+Note: You must edit the change log to describe a new release
+and
+.B commit
+it to the master repository
+.I before
+.BR tag ging
+the release using CVS. Otherwise, the release description will not be
+included in the tagged package.
+.LP
+See
+.BR cvslog (local)
+for more information.
+.SS "Merging a Subsequent Third-Party Distribution"
+The initial steps in this process are identical to placing a
+third-party distribution under CVS for the first time: save the
+distribution kit and unpack the package in a scratch directory. From
+that point the steps diverge.
+The following example considers release 5.0 of the
+Bugs-R-Us package.
+.TP
+\(bu
+Check in the sources after unpacking them:
+.XS
+cd ~/scratch
+checkin \-m 'Bugs-R-Us 5.0 distribution' bugs VENDOR V5_0 \\
+ | tee ~/WARNINGS
+.XE
+It is important to save the output of
+.B checkin
+in a file
+because it lists the sources that have been locally modified.
+It is best to save the file in a different directory (for example,
+your home directory). Otherwise,
+.B checkin
+will try to check it into the master repository.
+.TP
+\(bu
+In your usual working directory, check out a fresh copy of the
+distribution that you just checked in.
+.XS
+cd ~/work
+cvs checkout \-r VENDOR bugs
+.XE
+The
+.B checkout
+command shown above retrieves the latest revision on the vendor branch.
+.TP
+\(bu
+See the `WARNINGS' file for a list of all locally modified
+sources.
+For each locally modified source,
+look at the differences between
+the new distribution and the latest local revision:
+.XS
+cvs diff \-r \fR\fILocalRev file\fR\fB
+.XE
+In this command,
+.I LocalRev
+is the latest
+numeric or symbolic revision
+on the RCS trunk of
+.IR file .
+You can use CVS
+.B log
+to get the revision history.
+.TP
+\(bu
+If your local modifications to a file have been incorporated into
+the vendor's distribution, then you should reset the default RCS
+branch for that file to the vendor branch. CVS doesn't provide a
+mechanism to do this. You have to do it by hand in the master
+repository:
+.XS
+rcs \-bVENDOR \fR\fIfile\fR\fB,v
+.XE
+.TP
+\(bu
+If your local modifications need to be merged with the
+new distribution, use CVS
+.B join
+to do it:
+.XS
+cvs join \-r VENDOR \fR\fIfile\fR\fB
+.XE
+The resulting file will be placed in your working directory.
+Edit it to resolve any overlaps.
+.TP
+\(bu
+Test the merged package.
+.TP
+\(bu
+Commit all modified files to the repository:
+.XS
+cvs commit \-a
+.XE
+.TP
+\(bu
+Tag the repository with a new local tag.
+.SS "Applying Patches to Third-Party Sources"
+Patches are handled in a manner very similar to complete
+third-party distributions. This example considers patches applied to
+Bugs-R-Us release 5.0.
+.TP
+\(bu
+Save the patch files together with the distribution kit
+to which they apply.
+The patch file names should clearly indicate the patch
+level.
+.TP
+\(bu
+In a scratch directory, check out the last `clean' vendor copy \- the
+highest revision on the vendor branch with
+.IR "no local changes" :
+.XS
+cd ~/scratch
+cvs checkout \-r VENDOR bugs
+.XE
+.TP
+\(bu
+Use
+.BR patch (local)
+to apply the patches. You should now have an image of the
+vendor's software just as though you had received a complete,
+new release.
+.TP
+\(bu
+Proceed with the steps described for merging a subsequent third-party
+distribution.
+.TP
+\(bu
+Note: When you get to the step that requires you
+to check out the new distribution after you have
+checked it into the vendor branch, you should move to a different
+directory. Do not attempt to
+.B checkout
+files in the directory in
+which you applied the patches. If you do, CVS will try to merge the
+changes that you made during patching with the version being checked
+out and things will get very confusing. Instead,
+go to a different directory (like your working directory) and
+check out the files there.
+.SS "Advice to Third-Party Source Hackers"
+As you can see from the preceding sections, merging local changes
+into third-party distributions remains difficult, and probably
+always will. This fact suggests some guidelines:
+.TP
+\(bu
+Minimize local changes.
+.I Never
+make stylistic changes.
+Change makefiles only as much as needed for installation. Avoid
+overhauling anything. Pray that the vendor does the same.
+.TP
+\(bu
+Avoid renaming files or moving them around.
+.TP
+\(bu
+Put independent, locally written files like help documents, local
+tools, or man pages in a sub-directory called `local-additions'.
+Locally written files that are linked into an existing executable
+should be added right in with the vendor's sources (not in a
+`local-additions' directory).
+If, in the future,
+the vendor distributes something
+equivalent to your locally written files
+you can CVS
+.B remove
+the files from the `local-additions' directory at that time.
+.SH SEE ALSO
+.BR cvs (local),
+.BR checkin (local),
+.BR cvslog (local),
+.BR cvscheck (local)
+.SH AUTHOR
+Lowell Skoog
+.br
+Software Technology Group
+.br
+Technical Computing
diff --git a/contrib/cvs/contrib/descend.man b/contrib/cvs/contrib/descend.man
new file mode 100644
index 0000000..5ac46f4
--- /dev/null
+++ b/contrib/cvs/contrib/descend.man
@@ -0,0 +1,115 @@
+.\" $Id: descend.man,v 1.1.1.3 1995/08/28 16:20:31 jimb Exp $
+.TH DESCEND 1 "31 March 1992"
+.SH NAME
+descend \- walk directory tree and execute a command at each node
+.SH SYNOPSIS
+.B descend
+[
+.B \-afqrv
+]
+.I command
+[
+.I directory
+\&.\|.\|.
+]
+.SH DESCRIPTION
+.B descend
+walks down a directory tree and executes a command at each node. It
+is not as versatile as
+.BR find (1),
+but it has a simpler syntax. If no
+.I directory
+is specified,
+.B descend
+starts at the current one.
+.LP
+Unlike
+.BR find ,
+.B descend
+can be told to skip the special directories associated with RCS,
+CVS, and SCCS. This makes
+.B descend
+especially handy for use with these packages. It can be used with
+other commands too, of course.
+.LP
+.B descend
+is a poor man's way to make any command recursive. Note:
+.B descend
+does not follow symbolic links to directories unless they are
+specified on the command line.
+.SH OPTIONS
+.TP 15
+.B \-a
+.I All.
+Descend into directories that begin with '.'.
+.TP
+.B \-f
+.I Force.
+Ignore errors during descent. Normally,
+.B descend
+quits when an error occurs.
+.TP
+.B \-q
+.I Quiet.
+Suppress the message `In directory
+.IR directory '
+that is normally printed during the descent.
+.TP
+.B \-r
+.I Restricted.
+Don't descend into the special directories
+.SB RCS,
+.SB CVS,
+.SB CVS.adm,
+and
+.SB SCCS.
+.TP
+.B \-v
+.I Verbose.
+Print
+.I command
+before executing it.
+.SH EXAMPLES
+.TP 15
+.B "descend ls"
+Cheap substitute for `ls -R'.
+.TP 15
+.B "descend -f 'rm *' tree"
+Strip `tree' of its leaves. This command descends the `tree'
+directory, removing all regular files. Since
+.BR rm (1)
+does not remove directories, this command leaves the directory
+structure of `tree' intact, but denuded. The
+.B \-f
+option is required to keep
+.B descend
+from quitting. You could use `rm \-f' instead.
+.TP
+.B "descend -r 'co RCS/*'" /project/src/
+Check out every RCS file under the directory
+.BR "/project/src" .
+.TP
+.B "descend -r 'cvs diff'"
+Perform CVS `diff' operation on every directory below (and including)
+the current one.
+.SH DIAGNOSTICS
+Returns 1 if errors occur (and the
+.B \-f
+option is not used). Otherwise returns 0.
+.SH SEE ALSO
+.BR find (1),
+.BR rcsintro (1),
+.BR cvs (1),
+.BR sccs (1)
+.SH AUTHOR
+Lowell Skoog
+.br
+Software Technology Group
+.br
+John Fluke Mfg. Co., Inc.
+.SH BUGS
+Shell metacharacters in
+.I command
+may have bizarre effects. In particular, compound commands
+(containing ';', '[', and ']' characters) will not work. It is best
+to enclose complicated commands in single quotes \(aa\ \(aa.
diff --git a/contrib/cvs/contrib/descend.sh b/contrib/cvs/contrib/descend.sh
new file mode 100644
index 0000000..e6a7880
--- /dev/null
+++ b/contrib/cvs/contrib/descend.sh
@@ -0,0 +1,116 @@
+#! /bin/sh
+# $Id: descend.sh,v 1.1 1995/07/10 02:26:32 kfogel Exp $
+#
+# descend - walk down a directory tree and execute a command at each node
+
+fullname=$0
+name=descend
+usage="Usage: $name [-afqrv] command [directory ...]\n
+\040\040-a\040\040All: descend into directories starting with '.'\n
+\040\040-f\040\040Force: ignore errors during descent\n
+\040\040-q\040\040Quiet: don't print directory names\n
+\040\040-r\040\040Restricted: don't descend into RCS, CVS.adm, SCCS directories\n
+\040\040-v\040\040Verbose: print command before executing it"
+
+# Scan for options
+while getopts afqrv option; do
+ case $option in
+ a)
+ alldirs=$option
+ options=$options" "-$option
+ ;;
+ f)
+ force=$option
+ options=$options" "-$option
+ ;;
+ q)
+ verbose=
+ quiet=$option
+ options=$options" "-$option
+ ;;
+ r)
+ restricted=$option
+ options=$options" "-$option
+ ;;
+ v)
+ verbose=$option
+ quiet=
+ options=$options" "-$option
+ ;;
+ \?)
+ /usr/5bin/echo $usage 1>&2
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+# Get command to execute
+if [ $# -lt 1 ] ; then
+ /usr/5bin/echo $usage 1>&2
+ exit 1
+else
+ command=$1
+ shift
+fi
+
+# If no directory specified, use '.'
+if [ $# -lt 1 ] ; then
+ default_dir=.
+fi
+
+# For each directory specified
+for dir in $default_dir "$@" ; do
+
+ # Spawn sub-shell so we return to starting directory afterward
+ (cd $dir
+
+ # Execute specified command
+ if [ -z "$quiet" ] ; then
+ echo In directory `hostname`:`pwd`
+ fi
+ if [ -n "$verbose" ] ; then
+ echo $command
+ fi
+ eval "$command" || if [ -z "$force" ] ; then exit 1; fi
+
+ # Collect dot file names if necessary
+ if [ -n "$alldirs" ] ; then
+ dotfiles=.*
+ else
+ dotfiles=
+ fi
+
+ # For each file in current directory
+ for file in $dotfiles * ; do
+
+ # Skip '.' and '..'
+ if [ "$file" = "." -o "$file" = ".." ] ; then
+ continue
+ fi
+
+ # If a directory but not a symbolic link
+ if [ -d "$file" -a ! -h "$file" ] ; then
+
+ # If not skipping this type of directory
+ if [ \( "$file" != "RCS" -a \
+ "$file" != "SCCS" -a \
+ "$file" != "CVS" -a \
+ "$file" != "CVS.adm" \) \
+ -o -z "$restricted" ] ; then
+
+ # Recursively descend into it
+ $fullname $options "$command" "$file" \
+ || if [ -z "$force" ] ; then exit 1; fi
+ fi
+
+ # Else if a directory AND a symbolic link
+ elif [ -d "$file" -a -h "$file" ] ; then
+
+ if [ -z "$quiet" ] ; then
+ echo In directory `hostname`:`pwd`/$file: symbolic link: skipping
+ fi
+ fi
+ done
+ ) || if [ -z "$force" ] ; then exit 1; fi
+done
diff --git a/contrib/cvs/contrib/dirfns.shar b/contrib/cvs/contrib/dirfns.shar
new file mode 100644
index 0000000..8324c41
--- /dev/null
+++ b/contrib/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/contrib/cvs/contrib/intro.doc b/contrib/cvs/contrib/intro.doc
new file mode 100644
index 0000000..a6d4ec1
--- /dev/null
+++ b/contrib/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/contrib/cvs/contrib/log.pl b/contrib/cvs/contrib/log.pl
new file mode 100644
index 0000000..5e3bf48
--- /dev/null
+++ b/contrib/cvs/contrib/log.pl
@@ -0,0 +1,169 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+#ident "$CVSid$"
+#
+# 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;
+
+# 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, 19$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, 19$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;
+ }
+ open(RCS, "-|") || exec 'cvs', '-nQq', 'status', '-v', $file;
+ 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/contrib/cvs/contrib/log_accum.pl b/contrib/cvs/contrib/log_accum.pl
new file mode 100644
index 0000000..d5c6783
--- /dev/null
+++ b/contrib/cvs/contrib/log_accum.pl
@@ -0,0 +1,560 @@
+#! xPERL_PATHx
+# -*-Perl-*-
+#
+#ident "@(#)ccvs/contrib:$Name: $:$Id: log_accum.pl,v 1.4 1996/03/06 15:27:09 woods Exp $"
+#
+# 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
+
+#
+# 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\nChanges by:\t%s@%s\t%02d/%02d/%02d %02d:%02d:%02d",
+ $cvsroot,
+ $modulename,
+ $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 "From: " . $login . "@" . $hostdomain . "\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`);
+$hostdomain = $hostname . $domainname;
+$cvsroot = $ENV{'CVSROOT'};
+$do_status = 1;
+$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 '-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.
+#
+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/) {
+ 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/contrib/cvs/contrib/mfpipe.pl b/contrib/cvs/contrib/mfpipe.pl
new file mode 100644
index 0000000..bae7a72
--- /dev/null
+++ b/contrib/cvs/contrib/mfpipe.pl
@@ -0,0 +1,88 @@
+#! 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.
+#
+# $Id: mfpipe.pl,v 1.2 1995/07/10 02:01:57 kfogel Exp $
+#
+#
+# File: mfpipe
+#
+# Author: John Clyne
+# National Center for Atmospheric Research
+# PO 3000, Boulder, Colorado
+#
+# Date: Wed Feb 26 18:34:53 MST 1992
+#
+# Description: Tee standard input to mail a list of users and to
+# a file. Used by CVS logging.
+#
+# Usage: mfpipe [-f file] [user@host...]
+#
+# Environment: CVSROOT
+# Path to CVS root.
+#
+# Files:
+#
+#
+# Options: -f file
+# Capture output to 'file'
+#
+
+$header = "Log Message:\n";
+
+$mailcmd = "| mail -s 'CVS update notice'";
+$whoami = `whoami`;
+chop $whoami;
+$date = `date`;
+chop $date;
+
+$cvsroot = $ENV{'CVSROOT'};
+
+while (@ARGV) {
+ $arg = shift @ARGV;
+
+ if ($arg eq '-f') {
+ $file = shift @ARGV;
+ }
+ else {
+ $users = "$users $arg";
+ }
+}
+
+if ($users) {
+ $mailcmd = "$mailcmd $users";
+ open(MAIL, $mailcmd) || die "Execing $mail: $!\n";
+}
+
+if ($file) {
+ $logfile = "$cvsroot/LOG/$file";
+ open(FILE, ">> $logfile") || die "Opening $logfile: $!\n";
+}
+
+print FILE "$whoami $date--------BEGIN LOG ENTRY-------------\n" if ($logfile);
+
+while (<>) {
+ print FILE $log if ($log && $logfile);
+
+ print FILE $_ if ($logfile);
+ print MAIL $_ if ($users);
+
+ $log = "log: " if ($_ eq $header);
+}
+
+close FILE;
+die "Write failed" if $?;
+close MAIL;
+die "Mail failed" if $?;
+
+exit 0;
diff --git a/contrib/cvs/contrib/rcs-to-cvs.sh b/contrib/cvs/contrib/rcs-to-cvs.sh
new file mode 100644
index 0000000..3af83d7
--- /dev/null
+++ b/contrib/cvs/contrib/rcs-to-cvs.sh
@@ -0,0 +1,185 @@
+#! /bin/sh
+#
+# $Id: rcs-to-cvs.sh,v 1.2 1995/07/15 03:40:34 jimb Exp $
+# 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=""
+message_file=/usr/tmp/checkin.$$
+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-/usr/ucb/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/contrib/cvs/contrib/rcs2log.sh b/contrib/cvs/contrib/rcs2log.sh
new file mode 100644
index 0000000..ccea907
--- /dev/null
+++ b/contrib/cvs/contrib/rcs2log.sh
@@ -0,0 +1,592 @@
+#! /bin/sh
+
+# RCS to ChangeLog generator
+
+# Generate a change log prefix from RCS files 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 `}'.
+
+# Author: Paul Eggert <eggert@twinsun.com>
+
+# $Id: rcs2log.sh,v 1.2 1995/07/28 19:48:45 eggert Exp $
+
+# Copyright 1992, 1993, 1994, 1995 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+tab=' '
+nl='
+'
+
+# Parse options.
+
+# defaults
+: ${AWK=awk}
+: ${TMPDIR=/tmp}
+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
+recursive= # t if we want recursive rlog
+rlog_options= # options to pass to rlog
+tabwidth=8 # width of horizontal tab
+
+while :
+do
+ case $1 in
+ -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
+ loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4
+ 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
+ loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2
+ shift
+ esac
+ logins=$logins$nl$login
+ ;;
+ -r) rlog_options=$rlog_options$nl${2?}; shift;;
+ -R) recursive=t;;
+ -t) tabwidth=${2?}; shift;;
+ -*) echo >&2 "$0: usage: $0 [options] [file ...]
+Options:
+ [-h hostname] [-i indent] [-l length] [-R] [-r rlog_option]
+ [-t tabwidth] [-u 'login<TAB>fullname<TAB>mailaddr']..."
+ exit 1;;
+ *) 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"
+
+ # days in non-leap year thus far, indexed by month (0-12)
+ mo[0]=0; mo[1]=31; mo[2]=59; mo[3]=90
+ mo[4]=120; mo[5]=151; mo[6]=181; mo[7]=212
+ mo[8]=243; mo[9]=273; mo[10]=304; mo[11]=334
+ mo[12]=365
+'
+
+
+# Put rlog output into $rlogout.
+
+# If no rlog options are given,
+# log the revisions checked in since the first ChangeLog entry.
+case $rlog_options in
+'')
+ date=1970
+ if test -s ChangeLog
+ then
+ # Add 1 to seconds to avoid duplicating most recent log.
+ e='
+ /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
+ '"$month_data"'
+ year = $5
+ for (i=0; i<=11; i++) if (m[i] == $2) break
+ dd = $3
+ hh = substr($0,12,2)
+ mm = substr($0,15,2)
+ ss = substr($0,18,2)
+ ss++
+ if (ss == 60) {
+ ss = 0
+ mm++
+ if (mm == 60) {
+ mm = 0
+ hh++
+ if (hh == 24) {
+ hh = 0
+ dd++
+ monthdays = mo[i+1] - mo[i]
+ if (i == 1 && year%4 == 0 && (year%100 != 0 || year%400 == 0)) monthdays++
+ if (dd == monthdays + 1) {
+ dd = 1
+ i++
+ if (i == 12) {
+ i = 0
+ year++
+ }
+ }
+ }
+ }
+ }
+ # Output comma instead of space to avoid CVS 1.5 bug.
+ printf "%d/%02d/%02d,%02d:%02d:%02d\n", year,i+1,dd,hh,mm,ss
+ exit
+ }
+ '
+ d=`$AWK "$e" <ChangeLog` || exit
+ case $d in
+ ?*) date=$d
+ esac
+ fi
+ datearg="-d>$date"
+esac
+
+# 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 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
+
+# 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 -print
+ esac
+ find . -name '*,v' -print
+ } |
+ sort -u |
+ sed "$filesFromRCSfiles"
+ `;;
+ *)
+ files=
+ for file in RCS/.* RCS/* .*,v *,v
+ do
+ case $file in
+ RCS/. | RCS/..) continue;;
+ RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue
+ esac
+ files=$files$nl$file
+ 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 $rlog_options in
+?*) $rlog $rlog_options ${1+"$@"} >$rlogout;;
+'') $rlog "$datearg" ${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=`
+ (cat /etc/passwd; 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 \r).
+# `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, CR)) sep = "\n" indent_string
+
+ # Print the label.
+ printf "%s*%s:", indent_string, files
+
+ # Print each line of the log, transliterating \r to \n.
+ while ((i = index(Log, CR)) != 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)
+ }
+}'
+
+case $hostname in
+'')
+ hostname=`(
+ hostname || uname -n || uuname -l || cat /etc/whoami
+ ) 2>/dev/null` || {
+ echo >&2 "$0: cannot deduce hostname"
+ exit 1
+ }
+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 \r 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)
+ }
+ }
+ }
+ /^Working file:/ { if (repository == "") filename = $3 }
+ /^date: /, /^(-----------*|===========*)$/ {
+ if ($0 ~ /^branches: /) { next }
+ if ($0 ~ /^date: [0-9][- +\/0-9:]*;/) {
+ date = $2
+ if (date ~ /-/) {
+ # An ISO format date. Replace all "-"s with "/"s.
+ newdate = ""
+ while ((i = index(date, "-")) != 0) {
+ newdate = newdate substr(date, 1, i-1) "/"
+ date = substr(date, i+1)
+ }
+ date = newdate date
+ }
+ # Ignore any time zone; ChangeLog has no room for it.
+ time = substr($3, 1, 8)
+ author = substr($5, 1, length($5)-1)
+ printf "%s %s %s %s %c", filename, date, time, author, 13
+ next
+ }
+ if ($0 ~ /^(-----------*|===========*)$/) { print ""; next }
+ printf "%s%c", $0, 13
+ }
+' |
+
+# Now each line is of the form
+# FILENAME YYYY/MM/DD HH:MM:SS AUTHOR \rLOG
+# where \r stands for a carriage return,
+# and each line of the log is terminated by \r 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 (just in case).
+sort +1 -3r +3 +0 |
+
+# Finally, reformat the sorted log entries.
+$AWK '
+ BEGIN {
+ # Some awk variants do not understand "\r" or "\013", so we have to
+ # put a carriage return directly in the file.
+ CR=" " # <-- There is a single CR between the " chars 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 " "
+
+ # Set up date conversion tables.
+ # RCS uses a nice, clean, sortable format,
+ # but ChangeLog wants the traditional, ugly ctime format.
+
+ # January 1, 0 AD (Gregorian) was Saturday = 6
+ EPOCH_WEEKDAY = 6
+ # Of course, there was no 0 AD, but the algorithm works anyway.
+
+ w[0]="Sun"; w[1]="Mon"; w[2]="Tue"; w[3]="Wed"
+ w[4]="Thu"; w[5]="Fri"; w[6]="Sat"
+
+ '"$month_data"'
+ }
+
+ {
+ newlog = substr($0, 1 + index($0, CR))
+
+ # Ignore log entries prefixed by "#".
+ if (newlog ~ /^#/) { next }
+
+ if (Log != newlog || date != $2 || author != $4) {
+
+ # 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 != $2 || author != $4) {
+ # The previous date+author and this date+author differ.
+ # Print the new one.
+ date = $2
+ author = $4
+
+ # Convert nice RCS date like "1992/01/03 00:03:44"
+ # into ugly ctime date like "Fri Jan 3 00:03:44 1992".
+ # Calculate day of week from Gregorian calendar.
+ i = index($2, "/")
+ year = substr($2, 1, i-1) + 0
+ monthday = substr($2, i+1)
+ i = index(monthday, "/")
+ month = substr(monthday, 1, i-1) + 0
+ day = substr(monthday, i+1) + 0
+ leap = 0
+ if (2 < month && year%4 == 0 && (year%100 != 0 || year%400 == 0)) leap = 1
+ days_since_Sunday_before_epoch = EPOCH_WEEKDAY + year * 365 + int((year + 3) / 4) - int((year + 99) / 100) + int((year + 399) / 400) + mo[month-1] + leap + day - 1
+
+ # Print "date 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 %2d %s %d %s ", w[days_since_Sunday_before_epoch%7], m[month-1], day, $3, year, 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
+ }
+ }
+ END {
+ # Print the last log.
+ if (date != "") {
+ '"$printlogline"'
+ printf "\n"
+ }
+ }
+' &&
+
+
+# Exit successfully.
+
+exec rm -f $llogout $rlogout
diff --git a/contrib/cvs/contrib/rcs2sccs.sh b/contrib/cvs/contrib/rcs2sccs.sh
new file mode 100644
index 0000000..af70138
--- /dev/null
+++ b/contrib/cvs/contrib/rcs2sccs.sh
@@ -0,0 +1,143 @@
+#! /bin/sh
+#
+#
+# OrigId: rcs2sccs,v 1.12 90/10/04 20:52:23 kenc Exp Locker: kenc
+# $Id: rcs2sccs.sh,v 1.1 1995/07/10 02:26:45 kfogel Exp $
+
+############################################################
+# 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/contrib/cvs/contrib/rcslock.pl b/contrib/cvs/contrib/rcslock.pl
new file mode 100644
index 0000000..01e349f
--- /dev/null
+++ b/contrib/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/contrib/cvs/contrib/sccs2rcs.csh b/contrib/cvs/contrib/sccs2rcs.csh
new file mode 100644
index 0000000..0f31893
--- /dev/null
+++ b/contrib/cvs/contrib/sccs2rcs.csh
@@ -0,0 +1,277 @@
+#! 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.
+#
+# $Id: sccs2rcs.csh,v 1.1 1995/07/10 02:26:48 kfogel Exp $
+
+
+#we'll assume the user set up the path correctly
+# for the Pmax, /usr/ucb/sccs is suid sccs, what a pain
+# /usr/local/bin/sccs should override /usr/ucb/sccs there
+set path = (/usr/local/bin $path)
+
+
+############################################################
+# Error checking
+#
+if (! -w .) then
+ echo "Error: ./ not writeable by you."
+ exit 1
+endif
+if (! -d SCCS) then
+ echo "Error: ./SCCS directory not found."
+ exit 1
+endif
+set edits = (`sccs tell`)
+if ($#edits) then
+ echo "Error: $#edits file(s) out for edit...clean up before converting."
+ exit 1
+endif
+if (-d RCS) then
+ echo "Warning: RCS directory exists"
+ if (`ls -a RCS | wc -l` > 2) then
+ echo "Error: RCS directory not empty
+ exit 1
+ endif
+else
+ mkdir RCS
+endif
+
+sccs clean
+
+set logfile = /tmp/sccs2rcs_$$_log
+rm -f $logfile
+set tmpfile = /tmp/sccs2rcs_$$_tmp
+rm -f $tmpfile
+set emptyfile = /tmp/sccs2rcs_$$_empty
+echo -n "" > $emptyfile
+set initialfile = /tmp/sccs2rcs_$$_init
+echo "Initial revision" > $initialfile
+set sedfile = /tmp/sccs2rcs_$$_sed
+rm -f $sedfile
+set revfile = /tmp/sccs2rcs_$$_rev
+rm -f $revfile
+
+# the quotes surround the dollar signs to fool RCS when I check in this script
+set sccs_keywords = (\
+ '%W%[ ]*%G%'\
+ '%W%[ ]*%E%'\
+ '%W%'\
+ '%Z%%M%[ ]*%I%[ ]*%G%'\
+ '%Z%%M%[ ]*%I%[ ]*%E%'\
+ '%M%[ ]*%I%[ ]*%G%'\
+ '%M%[ ]*%I%[ ]*%E%'\
+ '%M%'\
+ '%I%'\
+ '%G%'\
+ '%E%'\
+ '%U%')
+set rcs_keywords = (\
+ '$'Id'$'\
+ '$'Id'$'\
+ '$'Id'$'\
+ '$'SunId'$'\
+ '$'SunId'$'\
+ '$'Id'$'\
+ '$'Id'$'\
+ '$'RCSfile'$'\
+ '$'Revision'$'\
+ '$'Date'$'\
+ '$'Date'$'\
+ '')
+
+
+############################################################
+# Get some answers from user
+#
+echo ""
+echo "Do you want to be prompted for a description of each"
+echo "file as it is checked in to RCS initially?"
+echo -n "(y=prompt for description, n=null description) [y] ?"
+set ans = $<
+if ((_$ans == _) || (_$ans == _y) || (_$ans == _Y)) then
+ set nodesc = 0
+else
+ set nodesc = 1
+endif
+echo ""
+echo "The default keyword substitutions are as follows and are"
+echo "applied in the order specified:"
+set i = 1
+while ($i <= $#sccs_keywords)
+# echo ' '\"$sccs_keywords[$i]\"' ==> '\"$rcs_keywords[$i]\"
+ echo " $sccs_keywords[$i] ==> $rcs_keywords[$i]"
+ @ i = $i + 1
+end
+echo ""
+echo -n "Do you want to change them [n] ?"
+set ans = $<
+if ((_$ans != _) && (_$ans != _n) && (_$ans != _N)) then
+ echo "You can't always get what you want."
+ echo "Edit this script file and change the variables:"
+ echo ' $sccs_keywords'
+ echo ' $rcs_keywords'
+else
+ echo "good idea."
+endif
+
+# create the sed script
+set i = 1
+while ($i <= $#sccs_keywords)
+ echo "s,$sccs_keywords[$i],$rcs_keywords[$i],g" >> $sedfile
+ @ i = $i + 1
+end
+
+onintr ERROR
+
+############################################################
+# Loop over every s-file in SCCS dir
+#
+foreach sfile (SCCS/s.*)
+ # get rid of the "s." at the beginning of the name
+ set file = `echo $sfile:t | sed -e "s/^..//"`
+
+ # work on each rev of that file in ascending order
+ set firsttime = 1
+ sccs prs $file | grep "^D " | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile
+ foreach rev (`cat $revfile`)
+ if ($status != 0) goto ERROR
+
+ # get file into current dir and get stats
+ set date = `sccs prs -r$rev $file | grep "^D " | awk '{printf("19%s %s", $3, $4); exit}'`
+ set author = `sccs prs -r$rev $file | grep "^D " | awk '{print $5; exit}'`
+ echo ""
+ echo "==> file $file, rev=$rev, date=$date, author=$author"
+ sccs edit -r$rev $file >>& $logfile
+ if ($status != 0) goto ERROR
+ echo checked out of SCCS
+
+ # add RCS keywords in place of SCCS keywords
+ sed -f $sedfile $file > $tmpfile
+ if ($status != 0) goto ERROR
+ echo performed keyword substitutions
+ cp $tmpfile $file
+
+ # check file into RCS
+ if ($firsttime) then
+ set firsttime = 0
+ if ($nodesc) then
+ echo about to do ci
+ echo ci -f -r$rev -d"$date" -w$author -t$emptyfile $file
+ ci -f -r$rev -d"$date" -w$author -t$emptyfile $file < $initialfile >>& $logfile
+ if ($status != 0) goto ERROR
+ echo initial rev checked into RCS without description
+ else
+ echo ""
+ echo Enter a brief description of the file $file \(end w/ Ctrl-D\):
+ cat > $tmpfile
+ ci -f -r$rev -d"$date" -w$author -t$tmpfile $file < $initialfile >>& $logfile
+ if ($status != 0) goto ERROR
+ echo initial rev checked into RCS
+ endif
+ else
+ # get RCS lock
+ set lckrev = `echo $rev | sed -e 's/\.[0-9]*$//'`
+ if ("$lckrev" =~ [0-9]*.*) then
+ # need to lock the brach -- it is OK if the lock fails
+ rcs -l$lckrev $file >>& $logfile
+ else
+ # need to lock the trunk -- must succeed
+ rcs -l $file >>& $logfile
+ if ($status != 0) goto ERROR
+ endif
+ echo got lock
+ sccs prs -r$rev $file | grep "." > $tmpfile
+ # it's OK if grep fails here and gives status == 1
+ # put the delta message in $tmpfile
+ ed $tmpfile >>& $logfile <<EOF
+/COMMENTS
+1,.d
+w
+q
+EOF
+ ci -f -r$rev -d"$date" -w$author $file < $tmpfile >>& $logfile
+ if ($status != 0) goto ERROR
+ echo checked into RCS
+ endif
+ sccs unedit $file >>& $logfile
+ if ($status != 0) goto ERROR
+ end
+ rm -f $file
+end
+
+
+############################################################
+# Clean up
+#
+echo cleaning up...
+mv SCCS old-SCCS
+rm -f $tmpfile $emptyfile $initialfile $sedfile
+echo ===================================================
+echo " Conversion Completed Successfully"
+echo ""
+echo " SCCS history now in old-SCCS/"
+echo ===================================================
+set exitval = 0
+goto cleanup
+
+ERROR:
+foreach f (`sccs tell`)
+ sccs unedit $f
+end
+echo ""
+echo ""
+echo Danger\! Danger\!
+echo Some command exited with a non-zero exit status.
+echo Log file exists in $logfile.
+echo ""
+echo Incomplete history in ./RCS -- remove it
+echo Original unchanged history in ./SCCS
+set exitval = 1
+
+cleanup:
+# leave log file
+rm -f $tmpfile $emptyfile $initialfile $sedfile $revfile
+
+exit $exitval
diff --git a/contrib/cvs/cvs-format.el b/contrib/cvs/cvs-format.el
new file mode 100644
index 0000000..cdbd842
--- /dev/null
+++ b/contrib/cvs/cvs-format.el
@@ -0,0 +1,81 @@
+;; -*- lisp-interaction -*-
+;; -*- emacs-lisp -*-
+;;
+;;
+;; originally from...
+;; Rich's personal .emacs file. feel free to copy.
+;;
+;; Last Mod Wed Feb 5 16:11:47 PST 1992, by rich@cygnus.com
+;;
+
+;;
+;;
+;; This section sets constants used by c-mode for formating
+;;
+;;
+
+;; If `c-auto-newline' is non-`nil', newlines are inserted both
+;;before and after braces that you insert, and after colons and semicolons.
+;;Correct C indentation is done on all the lines that are made this way.
+
+(setq c-auto-newline nil)
+
+
+;;*Non-nil means TAB in C mode should always reindent the current line,
+;;regardless of where in the line point is when the TAB command is used.
+;;It might be desirable to set this to nil for CVS, since unlike GNU
+;; CVS often uses comments over to the right separated by TABs.
+;; Depends some on whether you're in the habit of using TAB to
+;; reindent.
+;(setq c-tab-always-indent nil)
+
+;;; It seems to me that
+;;; `M-x set-c-style BSD RET'
+;;; or
+;;; (set-c-style "BSD")
+;;; takes care of the indentation parameters correctly.
+
+
+;; C does not have anything analogous to particular function names for which
+;;special forms of indentation are desirable. However, it has a different
+;;need for customization facilities: many different styles of C indentation
+;;are in common use.
+;;
+;; There are six variables you can set to control the style that Emacs C
+;;mode will use.
+;;
+;;`c-indent-level'
+;; Indentation of C statements within surrounding block. The surrounding
+;; block's indentation is the indentation of the line on which the
+;; open-brace appears.
+
+(setq c-indent-level 4)
+
+;;`c-continued-statement-offset'
+;; Extra indentation given to a substatement, such as the then-clause of
+;; an if or body of a while.
+
+(setq c-continued-statement-offset 4)
+
+;;`c-brace-offset'
+;; Extra indentation for line if it starts with an open brace.
+
+(setq c-brace-offset -4)
+
+;;`c-brace-imaginary-offset'
+;; An open brace following other text is treated as if it were this far
+;; to the right of the start of its line.
+
+(setq c-brace-imaginary-offset 0)
+
+;;`c-argdecl-indent'
+;; Indentation level of declarations of C function arguments.
+
+(setq c-argdecl-indent 4)
+
+;;`c-label-offset'
+;; Extra indentation for line that is a label, or case or default.
+
+(setq c-label-offset -4)
+
+;;;; eof
diff --git a/contrib/cvs/doc/ChangeLog b/contrib/cvs/doc/ChangeLog
new file mode 100644
index 0000000..01efb1a
--- /dev/null
+++ b/contrib/cvs/doc/ChangeLog
@@ -0,0 +1,569 @@
+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/contrib/cvs/doc/ChangeLog.fsf b/contrib/cvs/doc/ChangeLog.fsf
new file mode 100644
index 0000000..2f14099
--- /dev/null
+++ b/contrib/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/contrib/cvs/doc/Makefile.in b/contrib/cvs/doc/Makefile.in
new file mode 100644
index 0000000..11c7051
--- /dev/null
+++ b/contrib/cvs/doc/Makefile.in
@@ -0,0 +1,203 @@
+# 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.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# $CVSid: @(#)Makefile.in 1.8 94/10/22 $
+
+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 \
+ 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.
+install-info: info
+ test -f cvs.info || cd $(srcdir); \
+ for i in *.info* ; do \
+ $(INSTALL_DATA) $$i $(infodir)/$$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
+ $(SET_TEXINPUTS) $(TEX) cvsclient.texi
+ $(SET_TEXINPUTS) $(TEX) cvsclient.texi
+ $(TEXINDEX) cvsclient.??
+ $(SET_TEXINPUTS) $(TEX) 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/contrib/cvs/doc/cvs-paper.ms b/contrib/cvs/doc/cvs-paper.ms
new file mode 100644
index 0000000..567179b
--- /dev/null
+++ b/contrib/cvs/doc/cvs-paper.ms
@@ -0,0 +1,1073 @@
+.\" soelim cvs.ms | pic | tbl | troff -ms
+.\" @(#)cvs.ms 1.2 92/01/30
+.\"
+.\" troff source to the cvs USENIX article, Winter 1990, Washington, D.C.
+.\" Copyright (c) 1989, Brian Berliner
+.\"
+.\" This program is free software; you can redistribute it and/or modify
+.\" it under the terms of the GNU General Public License as published by
+.\" the Free Software Foundation; either version 1, or (at your option)
+.\" any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+.\" GNU General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with this program; if not, write to the Free Software
+.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+.\"
+.\" The author can be reached at: berliner@prisma.com
+.\"
+.de SP
+.if n .sp
+.if t .sp .5
+..
+.de hl
+.br
+.in +0.5i
+\l'\\n(LLu-1i'
+.in -0.5i
+.sp
+..
+.OH ""
+.nr PS 11
+.nr PO 1.25i
+.pl -0.2i
+.TL
+.ps 14
+.ft B
+.nf
+CVS II:
+Parallelizing Software Development
+.fi
+.ft
+.ps
+.AU
+.ps 12
+.ft I
+Brian Berliner
+.ft
+.ps
+.AI
+.ps 12
+.ft I
+Prisma, Inc.
+5465 Mark Dabling Blvd.
+Colorado Springs, CO 80918
+berliner@prisma.com
+.ft
+.ps
+.AB
+The program described in this paper fills a need in the UNIX
+community for a freely available tool to manage software revision and
+release control in a multi-developer, multi-directory, multi-group
+environment.
+This tool also addresses the increasing need for tracking third-party vendor
+source distributions while trying to maintain local modifications to
+earlier releases.
+.AE
+.NH
+Background
+.PP
+In large software development projects, it is usually necessary for more
+than one software developer to be modifying (usually different) modules of the
+code at the same time.
+Some of these code modifications are done in an
+experimental sense, at least until the code functions correctly, and some
+testing of the entire program is usually necessary.
+Then, the modifications are returned to a master source repository
+so that others in the project can
+enjoy the new bug-fix or functionality.
+In order to manage such a project, some sort of revision control system is
+necessary.
+.PP
+Specifically, UNIX\**
+.FS
+UNIX is a registered trademark of AT&T.
+.FE
+kernel development is an excellent example of the
+problems that an adequate revision control system must address.
+The SunOS\**
+.FS
+SunOS is a trademark of Sun Microsystems, Inc.
+.FE
+kernel is composed of over a thousand files spread across a
+hierarchy of dozens of directories.\**
+.FS
+Yes, the SunOS 4.0 kernel is composed of over a \fIthousand\fP files!
+.FE
+Pieces of the kernel must be edited
+by many software developers within an organization.
+While undesirable in
+theory, it is not uncommon to have two or more people making
+modifications to the same file within the kernel sources in
+order to facilitate a desired change.
+Existing revision control systems like
+.SM
+RCS
+.LG
+[Tichy] or
+.SM
+SCCS
+.LG
+[Bell] serialize file modifications by
+allowing only one developer to have a writable copy of a particular file at
+any one point in time.
+That developer is said to
+have \*Qlocked\*U the file for his exclusive use, and no other developer is
+allowed to check out a writable copy of the file until the locking
+developer has finished impeding others' productivity.
+Development pressures of productivity and deadlines
+often force organizations to require that multiple developers be able to
+simultaneously edit
+copies of the same revision controlled file.
+.PP
+The necessity for multiple developers to modify the same file concurrently
+questions the value of serialization-based policies in traditional revision
+control.
+This paper discusses the approach that
+Prisma took in adapting a standard revision control system,
+.SM
+RCS\c
+.LG
+, along with an existing public-domain collection of shell scripts that sits
+atop
+.SM
+RCS
+.LG
+and provides the basic conflict-resolution algorithms.
+The resulting
+program, \fBcvs\fP, addresses not only the issue of conflict-resolution in
+a multi-developer open-editing environment, but also the issues of
+software release control and vendor source support and integration.
+.NH
+The CVS Program
+.PP
+\fBcvs\fP
+(Concurrent Versions System)
+is a front end to the
+.SM
+RCS
+.LG
+revision control system which extends
+the notion of revision control from a collection of files in a single
+directory to a hierarchical collection of directories each containing
+revision controlled files.
+Directories and files in the \fBcvs\fP system can be combined together in
+many ways to form a software release.
+\fBcvs\fP
+provides the functions necessary to manage these software releases and to
+control the concurrent editing of source files among multiple software
+developers.
+.PP
+The six major features of \fBcvs\fP are listed below, and will be
+described in more detail in the following sections:
+.RS
+.IP 1.
+Concurrent access and conflict-resolution algorithms to guarantee that
+source changes are not \*Qlost.\*U
+.IP 2.
+Support for tracking third-party vendor source distributions while
+maintaining the local modifications made to those sources.
+.IP 3.
+A flexible module database that provides a symbolic mapping of names to
+components of a larger software distribution.
+This symbolic mapping provides for location independence within the software
+release and, for example, allows one to check out a copy of the \*Qdiff\*U
+program without ever knowing that the sources to \*Qdiff\*U actually reside
+in the \*Qbin/diff\*U directory.
+.IP 4.
+Configurable logging support allows all \*Qcommitted\*U source file changes
+to be logged using an arbitrary program to save the log messages in a file,
+notesfile, or news database.
+.IP 5.
+A software release can be symbolically tagged and checked out at any time
+based on that tag.
+An exact copy of a previous software release can be checked out at
+any time, \fIregardless\fP of whether files or directories have been
+added/removed from the \*Qcurrent\*U software release.
+As well,
+a \*Qdate\*U can be used to check out the \fIexact\fP version of the software
+release as of the specified date.
+.IP 6.
+A \*Qpatch\*U format file [Wall] can be produced between two software
+releases, even if the releases span multiple directories.
+.RE
+.PP
+The sources maintained by \fBcvs\fP are kept within a single directory
+hierarchy known as the \*Qsource repository.\*U
+This \*Qsource repository\*U holds the actual
+.SM
+RCS
+.LG
+\*Q,v\*U files directly, as well as a special per-repository directory
+(\c
+.SM
+CVSROOT.adm\c
+.LG
+) which contains a small number of administrative files that describe the
+repository and how it can be accessed.
+See Figure 1 for a picture of the \fBcvs\fP tree.
+.KF
+.hl
+.DS B
+.PS
+line from 4.112,9.200 to 5.550,8.887
+line from 5.447,8.884 to 5.550,8.887 to 5.458,8.933
+line from 4.112,9.200 to 4.550,8.950
+line from 4.451,8.978 to 4.550,8.950 to 4.476,9.021
+line from 4.112,9.200 to 3.737,8.887
+line from 3.798,8.971 to 3.737,8.887 to 3.830,8.932
+line from 3.612,8.762 to 4.737,8.137
+line from 4.638,8.164 to 4.737,8.137 to 4.662,8.208
+line from 3.612,8.762 to 3.737,8.137
+line from 3.693,8.231 to 3.737,8.137 to 3.742,8.240
+line from 3.612,8.762 to 2.612,8.200
+line from 2.687,8.271 to 2.612,8.200 to 2.712,8.227
+line from 2.362,9.262 to 2.737,8.950
+line from 2.645,8.995 to 2.737,8.950 to 2.677,9.033
+line from 2.362,9.262 to 1.925,8.950
+line from 1.992,9.028 to 1.925,8.950 to 2.021,8.988
+line from 3.362,9.762 to 4.050,9.387
+line from 3.950,9.413 to 4.050,9.387 to 3.974,9.457
+line from 3.362,9.762 to 2.487,9.387
+line from 2.570,9.450 to 2.487,9.387 to 2.589,9.404
+.ps 11
+"newfs.c,v" at 4.487,8.043 ljust
+.ps 11
+"mkfs.c,v" at 3.487,8.043 ljust
+.ps 11
+"Makefile,v" at 2.237,8.043 ljust
+.ps 11
+"newfs" at 3.487,8.793 ljust
+.ps 11
+"halt.c,v" at 5.487,8.793 ljust
+.ps 11
+"Makefile,v" at 4.237,8.793 ljust
+.ps 11
+"modules,v" at 2.487,8.793 ljust
+.ps 11
+"loginfo,v" at 1.488,8.793 ljust
+.ps 11
+"etc" at 3.987,9.293 ljust
+.ps 11
+"CVSROOT.adm" at 1.988,9.293 ljust
+.ps 11
+"/src/master" at 2.987,9.793 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 1.\fP
+.SM
+\fBcvs\fP Source Repository
+.ce 0
+.sp
+.KE
+.NH 2
+Software Conflict Resolution\**
+.FS
+The basic conflict-resolution algorithms
+used in the \fBcvs\fP program find their roots
+in the original work done by Dick Grune at Vrije Universiteit in Amsterdam
+and posted to \fBcomp.sources.unix\fP in the volume 6 release sometime in 1986.
+This original version of \fBcvs\fP was a collection of shell scripts that
+combined to form a front end to the
+.SM
+RCS
+.LG
+programs.
+.FE
+.PP
+\fBcvs\fP allows several software developers to edit personal copies of a
+revision controlled file concurrently.
+The revision number of each checked out file is maintained independently
+for each user, and \fBcvs\fP forces the checked out file to be current with
+the \*Qhead\*U revision before it can be \*Qcommitted\*U as a permanent change.
+A checked out file is brought up-to-date with the \*Qhead\*U revision using
+the \*Qupdate\*U command of \fBcvs\fP.
+This command compares the \*Qhead\*U revision number with that of the user's
+file and performs an
+.SM
+RCS
+.LG
+merge operation if they are not the same.
+The result of the merge is a file that contains the user's modifications
+and those modifications that were \*Qcommitted\*U after the user
+checked out his version of the file (as well as a backup copy of the
+user's original file).
+\fBcvs\fP points out any conflicts during the merge.
+It is the user's responsibility to resolve these conflicts
+and to \*Qcommit\*U his/her changes when ready.
+.PP
+Although the \fBcvs\fP conflict-resolution algorithm was defined in 1986,
+it is remarkably similar to the \*QCopy-Modify-Merge\*U scenario included
+with NSE\**
+.FS
+NSE is the Network Software Environment, a product of Sun Microsystems, Inc.
+.FE
+and described in [Honda] and [Courington].
+The following explanation from [Honda] also applies to \fBcvs\fP:
+.QP
+Simply stated, a developer copies an object without locking it, modifies
+the copy, and then merges the modified copy with the original.
+This paradigm allows developers to work in isolation from one another since
+changes are made to copies of objects.
+Because locks are not used, development is not serialized and can proceed
+in parallel.
+Developers, however, must merge objects after the changes have been made.
+In particular, a developer must resolve conflicts when the same object has
+been modified by someone else.
+.PP
+In practice, Prisma has found that conflicts that occur when the same
+object has been modified by someone else are quite rare.
+When they do happen, the changes made by the other developer are usually
+easily resolved.
+This practical use has shown that the \*QCopy-Modify-Merge\*U paradigm is a
+correct and useful one.
+.NH 2
+Tracking Third-Party Source Distributions
+.PP
+Currently, a large amount of software is based on source
+distributions from a third-party distributor.
+It is often the case that local modifications are to be made to this
+distribution, \fIand\fP that the vendor's future releases should be
+tracked.
+Rolling your local modifications forward into the new vendor release is a
+time-consuming task, but \fBcvs\fP can ease this burden somewhat.
+The \fBcheckin\fP program of \fBcvs\fP initially sets up a source
+repository by integrating the source modules directly from the vendor's
+release, preserving the directory hierarchy of the vendor's distribution.
+The branch support of
+.SM
+RCS
+.LG
+is used to build this vendor release as a branch of the main
+.SM
+RCS
+.LG
+trunk.
+Figure 2 shows how the \*Qhead\*U tracks a sample vendor
+branch when no local modifications have been made to the file.
+.KF
+.hl
+.DS B
+.PS
+ellipse at 3.237,6.763 wid 1.000 ht 0.500
+dashwid = 0.050i
+line dashed from 3.237,7.513 to 3.737,7.513 to 3.737,9.762 to 4.237,9.762
+line from 4.138,9.737 to 4.237,9.762 to 4.138,9.787
+line dashed from 2.237,8.262 to 3.237,8.262 to 3.237,7.013
+line from 3.212,7.112 to 3.237,7.013 to 3.262,7.112
+line from 3.737,6.763 to 4.237,6.763
+line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788
+line from 2.237,6.763 to 2.737,6.763
+line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788
+line from 1.738,6.013 to 1.738,6.513
+line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413
+line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013
+line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012
+line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012
+line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013
+line from 4.737,7.013 to 4.737,7.513
+line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413
+line from 4.737,8.012 to 4.737,8.512
+line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412
+line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012
+line from 4.737,9.012 to 4.737,9.512
+line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412
+line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013
+.ps 11
+"\"HEAD\"" at 1.550,8.231 ljust
+.ps 11
+"'SunOS'" at 2.987,6.293 ljust
+.ps 11
+"1.1.1" at 3.050,6.793 ljust
+.ps 11
+"1.1" at 1.613,6.793 ljust
+.ps 11
+"1.1.1.1" at 4.487,6.793 ljust
+.ps 11
+"1.1.1.2" at 4.487,7.793 ljust
+.ps 11
+"1.1.1.3" at 4.487,8.793 ljust
+.ps 11
+"1.1.1.4" at 4.487,9.793 ljust
+.ps 11
+"'SunOS_4_0'" at 5.487,6.793 ljust
+.ps 11
+"'SunOS_4_0_1'" at 5.487,7.793 ljust
+.ps 11
+"'YAPT_5_5C'" at 5.487,8.793 ljust
+.ps 11
+"'SunOS_4_0_3'" at 5.487,9.793 ljust
+.ps 11
+"rcsfile.c,v" at 2.987,5.543 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 2.\fP
+.SM
+\fBcvs\fP Vendor Branch Example
+.ce 0
+.sp .3
+.KE
+Once this is done, developers can check out files and make local changes to
+the vendor's source distribution.
+These local changes form a new branch to the tree which is then used as the
+source for future check outs.
+Figure 3 shows how the \*Qhead\*U moves to the main
+.SM
+RCS
+.LG
+trunk when a local modification is made.
+.KF
+.hl
+.DS B
+.PS
+ellipse at 3.237,6.763 wid 1.000 ht 0.500
+dashwid = 0.050i
+line dashed from 2.800,9.075 to 1.738,9.075 to 1.738,8.012
+line from 1.713,8.112 to 1.738,8.012 to 1.762,8.112
+line from 1.738,7.013 to 1.738,7.513
+line from 1.762,7.413 to 1.738,7.513 to 1.713,7.413
+line from 1.238,8.012 to 2.237,8.012 to 2.237,7.513 to 1.238,7.513 to 1.238,8.012
+line from 3.737,6.763 to 4.237,6.763
+line from 4.138,6.737 to 4.237,6.763 to 4.138,6.788
+line from 2.237,6.763 to 2.737,6.763
+line from 2.637,6.737 to 2.737,6.763 to 2.637,6.788
+line from 1.738,6.013 to 1.738,6.513
+line from 1.762,6.413 to 1.738,6.513 to 1.713,6.413
+line from 1.238,7.013 to 2.237,7.013 to 2.237,6.513 to 1.238,6.513 to 1.238,7.013
+line from 4.237,9.012 to 5.237,9.012 to 5.237,8.512 to 4.237,8.512 to 4.237,9.012
+line from 4.237,8.012 to 5.237,8.012 to 5.237,7.513 to 4.237,7.513 to 4.237,8.012
+line from 4.237,7.013 to 5.237,7.013 to 5.237,6.513 to 4.237,6.513 to 4.237,7.013
+line from 4.737,7.013 to 4.737,7.513
+line from 4.763,7.413 to 4.737,7.513 to 4.712,7.413
+line from 4.737,8.012 to 4.737,8.512
+line from 4.763,8.412 to 4.737,8.512 to 4.712,8.412
+line from 4.237,10.012 to 5.237,10.012 to 5.237,9.512 to 4.237,9.512 to 4.237,10.012
+line from 4.737,9.012 to 4.737,9.512
+line from 4.763,9.412 to 4.737,9.512 to 4.712,9.412
+line from 5.987,5.013 to 5.987,6.013 to 0.988,6.013 to 0.988,5.013 to 5.987,5.013
+.ps 11
+"1.2" at 1.613,7.793 ljust
+.ps 11
+"\"HEAD\"" at 2.862,9.043 ljust
+.ps 11
+"'SunOS'" at 2.987,6.293 ljust
+.ps 11
+"1.1.1" at 3.050,6.793 ljust
+.ps 11
+"1.1" at 1.613,6.793 ljust
+.ps 11
+"1.1.1.1" at 4.487,6.793 ljust
+.ps 11
+"1.1.1.2" at 4.487,7.793 ljust
+.ps 11
+"1.1.1.3" at 4.487,8.793 ljust
+.ps 11
+"1.1.1.4" at 4.487,9.793 ljust
+.ps 11
+"'SunOS_4_0'" at 5.487,6.793 ljust
+.ps 11
+"'SunOS_4_0_1'" at 5.487,7.793 ljust
+.ps 11
+"'YAPT_5_5C'" at 5.487,8.793 ljust
+.ps 11
+"'SunOS_4_0_3'" at 5.487,9.793 ljust
+.ps 11
+"rcsfile.c,v" at 2.987,5.543 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 3.\fP
+.SM
+\fBcvs\fP Local Modification to Vendor Branch
+.ce 0
+.sp
+.KE
+.PP
+When a new version of the vendor's source distribution arrives, the
+\fBcheckin\fP program adds the new and changed vendor's files to the
+already existing source repository.
+For files that have not been changed locally, the new file from the
+vendor becomes the current \*Qhead\*U revision.
+For files that have been modified locally, \fBcheckin\fP warns that the
+file must be merged with the new vendor release.
+The \fBcvs\fP \*Qjoin\*U command is a useful tool that aids this process by
+performing the necessary
+.SM
+RCS
+.LG
+merge, as is done above when performing an \*Qupdate.\*U
+.PP
+There is also limited support for \*Qdual\*U derivations for source files.
+See Figure 4 for a sample dual-derived file.
+.KF
+.hl
+.DS B
+.PS
+ellipse at 2.337,8.575 wid 0.700 ht 0.375
+ellipse at 2.312,9.137 wid 0.700 ht 0.375
+line from 1.225,9.012 to 1.225,9.363
+line from 1.250,9.263 to 1.225,9.363 to 1.200,9.263
+line from 0.875,9.725 to 1.600,9.725 to 1.600,9.363 to 0.875,9.363 to 0.875,9.725
+line from 0.875,9.012 to 1.600,9.012 to 1.600,8.650 to 0.875,8.650 to 0.875,9.012
+line from 4.050,10.200 to 4.775,10.200 to 4.775,9.850 to 4.050,9.850 to 4.050,10.200
+line from 4.050,9.475 to 4.775,9.475 to 4.775,9.113 to 4.050,9.113 to 4.050,9.475
+line from 4.050,8.762 to 4.775,8.762 to 4.775,8.400 to 4.050,8.400 to 4.050,8.762
+line from 4.425,8.762 to 4.425,9.113
+line from 4.450,9.013 to 4.425,9.113 to 4.400,9.013
+line from 4.425,9.475 to 4.425,9.850
+line from 4.450,9.750 to 4.425,9.850 to 4.400,9.750
+line from 3.050,10.000 to 3.775,10.000 to 3.775,9.637 to 3.050,9.637 to 3.050,10.000
+line from 3.050,9.312 to 3.775,9.312 to 3.775,8.950 to 3.050,8.950 to 3.050,9.312
+line from 0.713,7.325 to 0.713,8.075 to 4.925,8.075 to 4.925,7.325 to 0.713,7.325
+line from 1.238,8.075 to 1.238,8.637
+line from 1.262,8.537 to 1.238,8.637 to 1.213,8.537
+line from 1.613,8.825 to 1.975,8.575
+line from 1.878,8.611 to 1.975,8.575 to 1.907,8.652
+line from 2.675,8.575 to 4.050,8.575
+line from 3.950,8.550 to 4.050,8.575 to 3.950,8.600
+line from 2.675,9.137 to 3.050,9.137
+line from 2.950,9.112 to 3.050,9.137 to 2.950,9.162
+line from 3.425,9.325 to 3.425,9.637
+line from 3.450,9.537 to 3.425,9.637 to 3.400,9.537
+line from 1.613,8.825 to 1.925,9.137
+line from 1.872,9.049 to 1.925,9.137 to 1.837,9.084
+.ps 11
+"'BSD'" at 2.138,9.481 ljust
+.ps 11
+"1.2" at 1.113,9.543 ljust
+.ps 11
+"1.1" at 1.125,8.831 ljust
+.ps 11
+"1.1.1.1" at 4.175,8.543 ljust
+.ps 11
+"1.1.1.2" at 4.175,9.281 ljust
+.ps 11
+"1.1.1.3" at 4.175,9.993 ljust
+.ps 11
+"1.1.2.2" at 3.175,9.793 ljust
+.ps 11
+"1.1.2.1" at 3.175,9.106 ljust
+.ps 11
+"rcsfile.c,v" at 2.425,7.706 ljust
+.ps 11
+"1.1.1" at 2.175,8.568 ljust
+.ps 11
+"'SunOS'" at 2.125,8.243 ljust
+.ps 11
+"1.1.2" at 2.163,9.131 ljust
+.PE
+.DE
+.hl
+.ce 100
+.LG
+\fBFigure 4.\fP
+.SM
+\fBcvs\fP Support For \*QDual\*U Derivations
+.ce 0
+.sp
+.KE
+This example tracks the SunOS distribution but includes major changes from
+Berkeley.
+These BSD files are saved directly in the
+.SM
+RCS
+.LG
+file off a new branch.
+.NH 2
+Location Independent Module Database
+.PP
+\fBcvs\fP contains support for a simple, yet powerful, \*Qmodule\*U database.
+For reasons of efficiency, this database is stored in \fBndbm\fP\|(3) format.
+The module database is used to apply names to collections of directories
+and files as a matter of convenience for checking out pieces of a large
+software distribution.
+The database records the physical location of the sources as a form of
+information hiding, allowing one to check out whole directory hierarchies
+or individual files without regard for their actual location within the
+global source distribution.
+.PP
+Consider the following small sample of a module database, which must be
+tailored manually to each specific source repository environment:
+.DS
+\f(CW #key [-option argument] directory [files...]
+ diff bin/diff
+ libc lib/libc
+ sys -o sys/tools/make_links sys
+ modules -i mkmodules CVSROOT.adm modules
+ kernel -a sys lang/adb
+ ps bin Makefile ps.c\fP
+.DE
+.PP
+The \*Qdiff\*U and \*Qlibc\*U modules refer to whole directory hierarchies that
+are extracted on check out.
+The \*Qsys\*U module extracts the \*Qsys\*U hierarchy, and runs the
+\*Qmake_links\*U program at the end of the check out process (the \fI-o\fP
+option specifies a program to run on check\fIo\fPut).
+The \*Qmodules\*U module allows one to edit the module database file and
+runs the \*Qmkmodules\*U program on check\fIi\fPn to regenerate the
+\fBndbm\fP database that \fBcvs\fP uses.
+The \*Qkernel\*U module is an alias (as the \fI-a\fP option specifies)
+which causes the remaining arguments after the \fI-a\fP to be interpreted
+exactly as if they had been specified on the command line.
+This is useful for objects that require shared pieces of code from far away
+places to be compiled (as is the case with the kernel debugger, \fBkadb\fP,
+which shares code with the standard \fBadb\fP debugger).
+The \*Qps\*U module shows that the source for \*Qps\*U lives in the \*Qbin\*U
+directory, but only \fIMakefile\fP and \fIps.c\fP are required to build the
+object.
+.PP
+The module database at Prisma is now populated for the entire UNIX
+distribution and thereby allows us to issue the
+following convenient commands to check out components of the UNIX
+distribution without regard for their actual location within the master source
+repository:
+.DS
+\f(CW example% cvs checkout diff
+ example% cvs checkout libc ps
+ example% cd diff; make\fP
+.DE
+.PP
+In building the module database file, it is quite possible to have name
+conflicts within a global software distribution.
+For example, SunOS provides two \fBcat\fP programs:
+one for the standard environment, \fI/bin/cat\fP, and one for the System V
+environment, \fI/usr/5bin/cat\fP.
+We resolved this conflict by naming the standard \fBcat\fP module
+\*Qcat\*U, and the System V \fBcat\fP module \*Q5cat\*U.
+Similar name modifications must be applied to other conflicting names, as
+might be found between a utility program and a library function, though
+Prisma chose not to include individual library functions within the module
+database at this time.
+.NH 2
+Configurable Logging Support
+.PP
+The \fBcvs\fP \*Qcommit\*U command is used to make a permanent change to the
+master source repository (where the
+.SM
+RCS
+.LG
+\*Q,v\*U files live).
+Whenever a \*Qcommit\*U is done, the log message for the change is carefully
+logged by an arbitrary program (in a file, notesfile, news database, or
+mail).
+For example, a collection of these updates can be used to produce release
+notices.
+\fBcvs\fP can be configured to send log updates through one or more filter
+programs, based on a regular expression match on the directory that is
+being changed.
+This allows multiple related or unrelated projects to exist within a single
+\fBcvs\fP source repository tree, with each different project sending its
+\*Qcommit\*U reports to a unique log device.
+.PP
+A sample logging configuration file might look as follows:
+.DS
+\f(CW #regex filter-program
+ DEFAULT /usr/local/bin/nfpipe -t %s utils.updates
+ ^diag /usr/local/bin/nfpipe -t %s diag.updates
+ ^local /usr/local/bin/nfpipe -t %s local.updates
+ ^perf /usr/local/bin/nfpipe -t %s perf.updates
+ ^sys /usr/local/bin/nfpipe -t %s kernel.updates\fP
+.DE
+.PP
+This sample allows the diagnostics and performance groups to
+share the same source repository with the kernel and utilities groups.
+Changes that they make are sent directly to their own notesfile [Essick]
+through the \*Qnfpipe\*U program.
+A sufficiently simple title is substituted for the \*Q%s\*U argument before
+the filter program is executed.
+This logging configuration file is tailored manually to each specific
+source repository environment.
+.NH 2
+Tagged Releases and Dates
+.PP
+Any release can be given a symbolic tag name that is stored directly in the
+.SM
+RCS
+.LG
+files.
+This tag can be used at any time to get an exact copy of any previous
+release.
+With equal ease, one can also extract an exact copy of the source files as
+of any arbitrary date in the past as well.
+Thus, all that's required to tag the current kernel, and to tag the kernel
+as of the Fourth of July is:
+.DS
+\f(CW example% cvs tag TEST_KERNEL kernel
+ example% cvs tag -D 'July 4' PATRIOTIC_KERNEL kernel\fP
+.DE
+The following command would retrieve an exact copy of the test kernel at
+some later date:
+.DS
+\f(CW example% cvs checkout -fp -rTEST_KERNEL kernel\fP
+.DE
+The \fI-f\fP option causes only files that match the specified tag to be
+extracted, while the \fI-p\fP option automatically prunes empty directories.
+Consequently, directories added to the kernel after the test kernel was
+tagged are not included in the newly extracted copy of the test kernel.
+.PP
+The \fBcvs\fP date support has exactly the same interface as that provided
+with
+.SM
+RCS\c
+.LG
+, however \fBcvs\fP must process the \*Q,v\*U files directly due to the
+special handling required by the vendor branch support.
+The standard
+.SM
+RCS
+.LG
+date handling only processes one branch (or the main trunk) when checking
+out based on a date specification.
+\fBcvs\fP must instead process the current \*Qhead\*U branch and, if a
+match is not found, proceed to look for a match on the vendor branch.
+This, combined with reasons of performance, is why \fBcvs\fP processes
+revision (symbolic and numeric) and date specifications directly from the
+\*Q,v\*U files.
+.NH 2
+Building \*Qpatch\*U Source Distributions
+.PP
+\fBcvs\fP can produce a \*Qpatch\*U format [Wall] output file which can be
+used to bring a previously released software distribution current with the
+newest release.
+This patch file supports an entire directory hierarchy within a single
+patch, as well as being able to add whole new files to the previous
+release.
+One can combine symbolic revisions and dates together to display changes in
+a very generic way:
+.DS
+\f(CW example% cvs patch -D 'December 1, 1988' \e
+ -D 'January 1, 1989' sys\fP
+.DE
+This example displays the kernel changes made in the month of December,
+1988.
+To release a patch file, for example, to take the \fBcvs\fP distribution
+from version 1.0 to version 1.4 might be done as follows:
+.DS
+\f(CW example% cvs patch -rCVS_1_0 -rCVS_1_4 cvs\fP
+.DE
+.NH
+CVS Experience
+.NH 2
+Statistics
+.PP
+A quick summary of the scale that \fBcvs\fP is addressing today
+can be found in Table 1.
+.KF
+.TS
+box center tab(:);
+c s
+c s
+c | c
+l | n .
+\fB\s+2Revision Control Statistics at Prisma
+as of 11/11/89\fP\s-2
+_
+How Many...:Total
+=
+Files:17243
+Directories:1005
+Lines of code:3927255
+Removed files:131
+Software developers:14
+Software groups:6
+Megabytes of source:128
+.TE
+.ce 100
+.LG
+\fBTable 1.\fP
+.SM
+\fBcvs\fP Statistics
+.ce 0
+.sp .3
+.KE
+Table 2 shows the history of files changed or added and the number
+of source lines affected by the change at Prisma.
+Only changes made to the kernel sources are included.
+.KF
+.TS
+box center tab(:);
+c s s s s
+c s s s s
+c || c | c || c | c
+c || c | c || c | c
+l || n | n || n | n.
+\fB\s+2Prisma Kernel Source File Changes
+By Month, 1988-1989\fP\s-2
+_
+Month:# Changed:# Lines:# Added:# Lines
+\^:Files:Changed:Files:Added
+=
+Dec:87:3619:68:9266
+Jan:39:4324:0:0
+Feb:73:1578:5:3550
+Mar:99:5301:18:11461
+Apr:112:7333:11:5759
+May:138:5371:17:13986
+Jun:65:2261:27:12875
+Jul:34:2000:1:58
+Aug:65:6378:8:4724
+Sep:266:23410:113:39965
+Oct:22:621:1:155
+Total:1000:62196:269:101799
+.TE
+.ce 100
+.LG
+\fBTable 2.\fP
+.SM
+\fBcvs\fP Usage History for the Kernel
+.ce 0
+.sp
+.KE
+The large number of source file changes made in September are the result of
+merging the SunOS 4.0.3 sources into the kernel.
+This merge process is described in section 3.3.
+.NH 2
+Performance
+.PP
+The performance of \fBcvs\fP is currently quite reasonable.
+Little effort has been expended on tuning \fBcvs\fP, although performance
+related decisions were made during the \fBcvs\fP design.
+For example, \fBcvs\fP parses the
+.SM
+RCS
+.LG
+\*Q,v\*U files directly instead of running an
+.SM
+RCS
+.LG
+process.
+This includes following branches as well as integrating with the vendor
+source branches and the main trunk when checking out files based on a date.
+.PP
+Checking out the entire kernel source tree (1223 files/59 directories)
+currently takes 16 wall clock minutes on a Sun-4/280.
+However, bringing the tree up-to-date with the current kernel sources, once
+it has been checked out, takes only 1.5 wall clock minutes.
+Updating the \fIcomplete\fP 128 MByte source tree under \fBcvs\fP control
+(17243 files/1005 directories) takes roughly 28 wall clock minutes and
+utilizes one-third of the machine.
+For now this is entirely acceptable; improvements on these numbers will
+possibly be made in the future.
+.NH 2
+The SunOS 4.0.3 Merge
+.PP
+The true test of the \fBcvs\fP vendor branch support came with the arrival
+of the SunOS 4.0.3 source upgrade tape.
+As described above, the \fBcheckin\fP program was used to install the new
+sources and the resulting output file listed the files that had been
+locally modified, needing to be merged manually.
+For the kernel, there were 94 files in conflict.
+The \fBcvs\fP \*Qjoin\*U command was used on each of the 94 conflicting
+files, and the remaining conflicts were resolved.
+.PP
+The \*Qjoin\*U command performs an \fBrcsmerge\fP operation.
+This in turn uses \fI/usr/lib/diff3\fP to produce a three-way diff file.
+As it happens, the \fBdiff3\fP program has a hard-coded limit of 200
+source-file changes maximum.
+This proved to be too small for a few of the kernel files that needed
+merging by hand, due to the large number of local changes that Prisma had
+made.
+The \fBdiff3\fP problem was solved by increasing the hard-coded limit by an
+order of magnitude.
+.PP
+The SunOS 4.0.3 kernel source upgrade distribution contained
+346 files, 233 of which were modifications to previously released files,
+and 113 of which were newly added files.
+\fBcheckin\fP added the 113 new files to the source repository
+without intervention.
+Of the 233 modified files, 139 dropped in cleanly by \fBcheckin\fP, since
+Prisma had not made any local changes to them, and 94 required manual
+merging due to local modifications.
+The 233 modified files consisted of 20,766 lines of differences.
+It took one developer two days to manually merge the 94 files using the
+\*Qjoin\*U command and resolving conflicts manually.
+An additional day was required for kernel debugging.
+The entire process of merging over 20,000 lines of differences was
+completed in less than a week.
+This one time-savings alone was justification enough for the \fBcvs\fP
+development effort; we expect to gain even more when tracking future SunOS
+releases.
+.NH
+Future Enhancements and Current Bugs
+.PP
+Since \fBcvs\fP was designed to be incomplete, for reasons of design
+simplicity, there are naturally a good
+number of enhancements that can be made to make it more useful.
+As well, some nuisances exist in the current implementation.
+.RS
+.IP \(bu 3
+\fBcvs\fP does not currently \*Qremember\*U who has a checked out a copy of a
+module.
+As a result, it is impossible to know who might be working on the same
+module that you are.
+A simple-minded database that is updated nightly would likely suffice.
+.IP \(bu 3
+Signal processing, keyboard interrupt handling in particular, is currently
+somewhat weak.
+This is due to the heavy use of the \fBsystem\fP\|(3) library
+function to execute
+.SM
+RCS
+.LG
+programs like \fBco\fP and \fBci\fP.
+It sometimes takes multiple interrupts to make \fBcvs\fP quit.
+This can be fixed by using a home-grown \fBsystem\fP\|() replacement.
+.IP \(bu 3
+Security of the source repository is currently not dealt with directly.
+The usual UNIX approach of user-group-other security permissions through
+the file system is utilized, but nothing else.
+\fBcvs\fP could likely be a set-group-id executable that checks a
+protected database to verify user access permissions for particular objects
+before allowing any operations to affect those objects.
+.IP \(bu 3
+With every checked-out directory, \fBcvs\fP maintains some administrative
+files that record the current revision numbers of the checked-out files as
+well as the location of the respective source repository.
+\fBcvs\fP does not recover nicely at all if these administrative files are
+removed.
+.IP \(bu 3
+The source code for \fBcvs\fP has been tested extensively on Sun-3 and
+Sun-4 systems, all running SunOS 4.0 or later versions of the operating
+system.
+Since the code has not yet been compiled under other platforms, the overall
+portability of the code is still questionable.
+.IP \(bu 3
+As witnessed in the previous section, the \fBcvs\fP method for tracking
+third party vendor source distributions can work quite nicely.
+However, if the vendor changes the directory structure or the file names
+within the source distribution, \fBcvs\fP has no way of matching the old
+release with the new one.
+It is currently unclear as to how to solve this, though it is certain to
+happen in practice.
+.RE
+.NH
+Availability
+.PP
+The \fBcvs\fP program sources can be found in a recent posting to the
+\fBcomp.sources.unix\fP newsgroup.
+It is also currently available via anonymous ftp from \*Qprisma.com\*U.
+Copying rights for \fBcvs\fP will be covered by the GNU General Public
+License.
+.NH
+Summary
+.PP
+Prisma has used \fBcvs\fP since December, 1988.
+It has evolved to meet our specific needs of revision and release control.
+We will make our code freely available so that others can
+benefit from our work, and can enhance \fBcvs\fP to meet broader needs yet.
+.PP
+Many of the other software release and revision control systems, like the
+one described in [Glew], appear to use a collection of tools that are
+geared toward specific environments \(em one set of tools for the kernel,
+one set for \*Qgeneric\*U software, one set for utilities, and one set for
+kernel and utilities.
+Each of these tool sets apparently handle some specific aspect of the
+problem uniquely.
+\fBcvs\fP took a somewhat different approach.
+File sharing through symbolic or hard links is not addressed; instead, the
+disk space is simply burned since it is \*Qcheap.\*U
+Support for producing objects for multiple architectures is not addressed;
+instead, a parallel checked-out source tree must be used for each
+architecture, again wasting disk space to simplify complexity and ease of
+use \(em punting on this issue allowed \fIMakefile\fPs to remain
+unchanged, unlike the approach taken in [Mahler], thereby maintaining closer
+compatibility with the third-party vendor sources.
+\fBcvs\fP is essentially a source-file server, making no assumptions or
+special handling of the sources that it controls.
+To \fBcvs\fP:
+.QP
+A source is a source, of course, of course, unless of course the source is
+Mr. Ed.\**
+.FS
+\fBcvs\fP, of course, does not really discriminate against Mr. Ed.\**
+.FE
+.FS
+Yet.
+.FE
+.LP
+Sources are maintained, saved, and retrievable at any time based on
+symbolic or numeric revision or date in the past.
+It is entirely up to \fBcvs\fP wrapper programs to provide for release
+environments and such.
+.PP
+The major advantage of \fBcvs\fP over the
+many other similar systems that have already been designed is the
+simplicity of \fBcvs\fP.
+\fBcvs\fP contains only three programs that do all the work of release
+and revision control, and two manually-maintained administrative
+files for each source repository.
+Of course, the deciding factor of any tool is whether people use it, and if
+they even \fIlike\fP to use it.
+At Prisma, \fBcvs\fP prevented members of the kernel
+group from killing each other.
+.NH
+Acknowledgements
+.PP
+Many thanks to Dick Grune at Vrije Universiteit in Amsterdam for his work
+on the original version of \fBcvs\fP and for making it available to the
+world.
+Thanks to Jeff Polk of Prisma for helping with the design of the module
+database, vendor branch support, and for writing the \fBcheckin\fP shell
+script.
+Thanks also to the entire software group at Prisma for taking the
+time to review the paper and correct my grammar.
+.NH
+References
+.IP [Bell] 12
+Bell Telephone Laboratories.
+\*QSource Code Control System User's Guide.\*U
+\fIUNIX System III Programmer's Manual\fP, October 1981.
+.IP [Courington] 12
+Courington, W.
+\fIThe Network Software Environment\fP,
+Sun Technical Report FE197-0, Sun Microsystems Inc, February 1989.
+.IP [Essick] 12
+Essick, Raymond B. and Robert Bruce Kolstad.
+\fINotesfile Reference Manual\fP,
+Department of Computer Science Technical Report #1081,
+University of Illinois at Urbana-Champaign, Urbana, Illinois,
+1982, p. 26.
+.IP [Glew] 12
+Glew, Andy.
+\*QBoxes, Links, and Parallel Trees:
+Elements of a Configuration Management System.\*U
+\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX,
+New Orleans, April 1989.
+.IP [Grune] 12
+Grune, Dick.
+Distributed the original shell script version of \fBcvs\fP in the
+\fBcomp.sources.unix\fP volume 6 release in 1986.
+.IP [Honda] 12
+Honda, Masahiro and Terrence Miller.
+\*QSoftware Management Using a CASE Environment.\*U
+\fIWorkshop Proceedings of the Software Management Conference\fP, USENIX,
+New Orleans, April 1989.
+.IP [Mahler] 12
+Mahler, Alex and Andreas Lampen.
+\*QAn Integrated Toolset for Engineering Software Configurations.\*U
+\fIProceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on
+Practical Software Development Environments\fP, ACM, Boston, November 1988.
+Described is the \fBshape\fP toolkit posted to the
+\fBcomp.sources.unix\fP newsgroup in the volume 19 release.
+.IP [Tichy] 12
+Tichy, Walter F.
+\*QDesign, Implementation, and Evaluation of a Revision Control System.\*U
+\fIProceedings of the 6th International Conference on Software
+Engineering\fP, IEEE, Tokyo, September 1982.
+.IP [Wall] 12
+Wall, Larry.
+The \fBpatch\fP program is an indispensable tool for applying a diff file
+to an original.
+Can be found on uunet.uu.net in ~ftp/pub/patch.tar.
diff --git a/contrib/cvs/doc/cvs.texinfo b/contrib/cvs/doc/cvs.texinfo
new file mode 100644
index 0000000..29bc5f3
--- /dev/null
+++ b/contrib/cvs/doc/cvs.texinfo
@@ -0,0 +1,7738 @@
+\input texinfo @c -*-texinfo-*-
+@comment cvs.texinfo,v 1.6 1995/10/12 23:39:26 kfogel Exp
+@comment Documentation for CVS.
+@comment Copyright (C) 1992, 1993 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 1, 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.
+
+@comment You should have received a copy of the GNU General Public License
+@comment along with CVS; see the file COPYING. If not, write to
+@comment the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+@afourpaper
+@setfilename cvs.info
+@include CVSvn.texi
+@settitle CVS---Concurrent Versions System
+@setchapternewpage odd
+
+@c -- TODO list:
+@c -- Fix all lines that match "^@c -- "
+@c -- Document how CVS finds the binaries it executes.
+@c Things to include in the index:
+@c Finding RCS binaries
+@c Path to RCS binaries
+@c RCS, how CVS finds them
+@c s/RCS/diff/
+@c -- More on binary files
+
+@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
+section entitled ``GNU General Public License'' is included exactly as
+in the original, and provided that the entire resulting derived work is
+distributed under the terms of a permission notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that the section entitled ``GNU General Public License'' and
+this permission notice may be included in translations approved by the
+Free Software Foundation instead of in the original English.
+@end ifinfo
+
+@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
+section entitled ``GNU General Public License'' is included exactly as
+in the original, and provided that the entire resulting derived work is
+distributed under the terms of a permission notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that the section entitled ``GNU General Public License'' and
+this permission notice may be included in translations approved by the
+Free Software Foundation instead of in the original English.
+@end titlepage
+
+@comment ================================================================
+@comment The real text starts here
+@comment ================================================================
+
+@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
+
+@menu
+* Preface:: About this manual
+* What is CVS?:: What is CVS?
+* Basic concepts:: Basic concepts of revision management
+* A sample session:: A tour of basic CVS usage
+* Repository:: Where all your sources are stored
+* Starting a new project:: Starting a project with CVS
+* Multiple developers:: How CVS helps a group of developers
+* Branches:: Parallel development explained
+* Merging:: How to move changes between branches
+* Recursive behavior:: CVS descends directories
+* Adding files:: Adding files to a module
+* Removing files:: Removing files from a module
+* Tracking sources:: Tracking third-party sources
+* Moving files:: Moving and renaming files
+* Moving directories:: Moving and renaming directories
+* History browsing:: Viewing the history of files in various ways
+* Keyword substitution:: CVS can include the revision inside the file
+* Binary files:: CVS can handle binary files
+* Revision management:: Policy questions for revision management
+* Invoking CVS:: Reference manual for CVS commands
+* Administrative files:: Reference manual for the Administrative files
+* Environment variables:: All environment variables which affect CVS
+* Troubleshooting:: Some tips when nothing works
+* Copying:: GNU GENERAL PUBLIC LICENSE
+* Index:: Index
+@end menu
+
+@c ---------------------------------------------------------------------
+@node Preface
+@unnumbered About this manual
+@cindex Preface
+@cindex About this manual
+
+Up to this point, one of the weakest parts of @sc{cvs}
+has been the documentation. @sc{cvs} is a complex
+program. Previous versions of the manual were written
+in the manual page format, which is not really well
+suited for such a complex program.
+
+When writing this manual, I had several goals in mind:
+
+@itemize @bullet
+@item
+No knowledge of @sc{rcs} should be necessary.
+
+@item
+No previous knowledge of revision control software
+should be necessary. All terms, such as @dfn{revision
+numbers}, @dfn{revision trees} and @dfn{merging} are
+explained as they are introduced.
+
+@item
+The manual should concentrate on the things @sc{cvs} users
+want to do, instead of what the @sc{cvs} commands can do.
+The first part of this manual leads you through things
+you might want to do while doing development, and
+introduces the relevant @sc{cvs} commands as they are
+needed.
+
+@item
+Information should be easy to find. In the reference
+manual in the appendices almost all information about
+every @sc{cvs} command is gathered together. There is also
+an extensive index, and a lot of cross references.
+@end itemize
+
+@cindex Signum Support
+@cindex Support, getting CVS support
+This manual was contributed by Signum Support AB in
+Sweden. Signum is yet another in the growing list of
+companies that support free software. You are free to
+copy both this manual and the @sc{cvs} program.
+@xref{Copying}, for the details. Signum Support offers
+@c -- Check this reference! It has been bogus in the past.
+support contracts and binary distribution for many
+programs, such as @sc{cvs}, @sc{gnu} Emacs, the
+@sc{gnu} C compiler and others. Write to us for
+more information.
+
+@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
+@end example
+
+Another company selling support for @sc{cvs} is Cyclic
+Software, web: @code{http://www.cyclic.com/}, email:
+@code{info@@cyclic.com}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@menu
+* Checklist::
+* Credits::
+* BUGS::
+@end menu
+
+@node Checklist
+@unnumberedsec Checklist for the impatient reader
+
+@sc{cvs} is a complex system. You will need to read
+the manual to be able to use all of its capabilities.
+There are dangers that can easily be avoided if you
+know about them, and this manual tries to warn you
+about them. This checklist is intended to help you
+avoid the dangers without reading the entire manual.
+If you intend to read the entire manual you can skip
+this table.
+
+@table @asis
+@item Binary files
+@sc{cvs} can handle binary files, but
+you must have @sc{rcs} release 5.5 or later and
+a release of @sc{gnu} diff that supports the @samp{-a}
+flag (release 1.15 and later are OK). You must also
+configure both @sc{rcs} and @sc{cvs} to handle binary
+files when you install them.
+
+Keword substitution can be a source of trouble with
+binary files. @xref{Keyword substitution}, for
+solutions.
+
+@item The @code{admin} command
+Uncareful use of the @code{admin} command can cause
+@sc{cvs} to cease working. @xref{admin}, before trying
+to use it.
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Credits
+@unnumberedsec Credits
+
+@cindex Contributors (manual)
+@cindex Credits (manual)
+Roland Pesch, Cygnus Support <@t{pesch@@cygnus.com}>
+wrote the manual pages which were distributed with
+@sc{cvs} 1.3. Appendix A and B contain much text that
+was extracted from them. 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} (@pxref{What is CVS?}) by David
+G. Grubbs has been used as a check-list to make sure
+that this manual is as complete as possible. (This
+manual does however not include all of the material in
+the @sc{faq}). The @sc{faq} contains a lot of useful
+information.
+
+In addition, the following persons have helped by
+telling me about mistakes I've made:
+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}>.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node BUGS
+@unnumberedsec BUGS
+
+@cindex Bugs, known in this manual
+@cindex Known bugs in this manual
+This manual is known to have room for improvement.
+Here is a list of known deficiencies:
+
+@itemize @bullet
+@item
+In the examples, the output from @sc{cvs} is sometimes
+displayed, sometimes not.
+
+@item
+The input that you are supposed to type in the examples
+should have a different font than the output from the
+computer.
+
+@item
+This manual should be clearer about what file
+permissions you should set up in the repository, and
+about setuid/setgid.
+
+@item
+Some of the chapters are not yet complete. They are
+noted by comments in the @file{cvs.texinfo} file.
+
+@item
+@cindex Reporting bugs (manual)
+@cindex Bugs, reporting (manual)
+@cindex Errors, reporting (manual)
+This list is not complete. If you notice any error,
+omission, or something that is unclear, please send
+mail to @t{bug-cvs@@prep.ai.mit.edu}.
+@end itemize
+
+I hope that you will find this manual useful, despite
+the above-mentioned shortcomings.
+
+@flushright
+
+Linkoping, October 1993
+Per Cederqvist
+@end flushright
+
+@c ---------------------------------------------------------------------
+@node What is CVS?
+@chapter 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 @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} via anonymous ftp from a number of
+sites, for instance @t{prep.ai.mit.edu} in
+@file{pub/gnu}.
+
+@cindex Mailing list
+@cindex List, mailing list
+There is a mailing list for @sc{cvs} where bug reports
+can be sent, questions can be asked, an FAQ is posted,
+and discussion about future enhancements to @sc{cvs}
+take place. To submit a message to the list, write to
+<@t{info-cvs@@prep.ai.mit.edu}>. To subscribe or
+unsubscribe, write to
+<@t{info-cvs-request@@prep.ai.mit.edu}>. Please be
+specific about your email address.
+
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@unnumberedsec CVS is not@dots{}
+
+@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. That's simply bad planning.
+
+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. You must use your
+brain and a collection of other tools to provide a
+build scheme to match your plans.
+
+Of course, you should place the tools created to
+support such a build system (scripts, @file{Makefile}s,
+etc) under @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} is not a configuration management system.
+
+@sc{cvs} is a source control system. The phrase
+``configuration management'' is a marketing term, not
+an industry-recognized set of functions.
+
+A true ``configuration management system'' would contain
+elements of the following:
+
+@itemize @bullet
+@item Source control.
+@item Dependency tracking.
+@item Build systems (i.e. What to build and how to find
+things during a build. What is shared? What is local?)
+@item Bug tracking.
+@item Automated Testing procedures.
+@item Release Engineering documentation and procedures.
+@item Tape Construction.
+@item Customer Installation.
+@item A way for users to run different versions of the same
+software on the same host at the same time.
+@end itemize
+
+@sc{cvs} provides only the first.
+@end table
+
+This section is taken from release 2.3 of the @sc{cvs}
+@sc{faq}.
+
+@c ---------------------------------------------------------------------
+@node Basic concepts
+@chapter Basic concepts
+@cindex Modules (intro)
+@cindex Repository (intro)
+
+@sc{cvs} stores all files in a centralized
+@dfn{repository}: a directory (such as
+@file{/usr/local/cvsroot} or
+@file{user@@remotehost:/usr/local/cvsroot}) which is
+populated with a hierarchy of files and directories.
+(@pxref{Remote repositories} for information about
+keeping the repository on a remote machine.)
+
+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, 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 files in the repository are organized in
+@dfn{modules}. Each module is made up of one or more
+files, and can include files from several directories.
+A typical usage is to define one module per project.
+
+@menu
+* Revision numbers:: The meaning of a revision number
+* Versions revisions releases:: Terminology used in this manual
+@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 Main trunk (intro)
+@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
+
+@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
+@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.2.2.4 !
+ / +---------+ +---------+ +---------+ +---------+
+ /
+ /
++-----+ +-----+ +-----+ +-----+ +-----+
+! 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 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.
+
+@c ---------------------------------------------------------------------
+@node A sample session
+@chapter A sample session
+@cindex 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)
+
+This section describes a typical work-session using
+@sc{cvs}. It assumes that a repository is set up
+(@pxref{Repository}).
+
+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
+@section 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 tc
+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
+@section 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}.
+
+@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 the editor defaults to
+@code{vi}. If you want to avoid the overhead of
+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
+@section 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) module `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
+@section 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.
+
+@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) module `tc': y
+@end example
+
+@c ---------------------------------------------------------------------
+@node Repository
+@chapter The Repository
+@cindex Repository, example
+@cindex Layout of repository
+@cindex Typical repository
+@cindex CVSROOT, environment variable
+@cindex .profile
+@cindex .cshrc
+@cindex .tcshrc
+@cindex .bashrc
+@cindex /usr/local/cvsroot
+@cindex cvsroot
+
+Figure 3 below shows a typical setup of a repository.
+Only directories are shown below.
+
+@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
+
+
+There are a couple of different 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
+
+ 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}, all @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
+
+ 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;
+however, @sc{CVS} will complain if the @file{-d}
+argument and the @file{CVS/Root} file disagree.
+
+There is nothing magical about the name
+@file{/usr/local/cvsroot}. You can choose to place the
+repository anywhere you like.
+@xref{Remote repositories} to learn how the repository can be on a
+different machine than your working copy of the sources.
+
+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
+* User modules:: The structure of the repository
+* Intro administrative files:: Defining modules
+* Multiple repositories:: Multiple repositories
+* Creating a repository:: Creating a repository
+* Remote repositories:: Accessing repositories on remote machines
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node User modules
+@section User modules
+@cindex User modules
+@cindex Repository, user parts
+
+@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
+@cindex RCS, CVS uses RCS
+The figure above shows the contents of the @samp{tc}
+module inside the repository. As you can see all file
+names end in @samp{,v}. The files are @dfn{history
+files}. They 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. @sc{cvs} uses the
+facilities of @sc{rcs}, a simpler version control
+system, to maintain these files. For a full
+description of the file format, see the @code{man} page
+@cite{rcsfile(5)}.
+@c -- Use this format for all references to man pages,
+@c -- or use something better!
+
+@menu
+* File permissions:: File permissions
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node File permissions
+@subsection File permissions
+@c -- Move this to @node Setting up
+@cindex Security
+@cindex File permissions
+@cindex Group
+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.
+
+This means that you can only control access to files on
+a per-directory basis.
+
+@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.
+
+@cindex setuid
+@cindex setgid
+Since @sc{cvs} was not written to be run setuid, it is
+unsafe to try to run it setuid. You cannot use the
+setuid features of @sc{rcs} together with @sc{cvs}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Intro administrative files
+@section The administrative files
+@cindex Administrative files (intro)
+@cindex Modules file
+@cindex CVSROOT, module name
+@cindex Defining modules (intro)
+
+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 for 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.
+
+@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 directories) by
+simply allowing @sc{cvs} to use the repository that was
+used to check out the working directory (@pxref{Repository}).
+
+Notwithstanding, it can be confusing to have two or
+more repositories.
+
+None of the examples in this manual show multiple
+repositories.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Creating a repository
+@section Creating a repository
+@c -- Well, how do you do?
+
+See the instructions in the @file{INSTALL} file in the
+@sc{cvs} distribution.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Remote repositories
+@section Remote repositories
+@cindex Repositories, remote
+@cindex Remote repositories
+@cindex Client/Server Operation
+
+ Your working copy of the sources can be on a
+different machine than the repository. Generally,
+using a remote repository is just like using a local
+one, except that the format of the repository name is:
+
+@example
+ user@@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.
+
+@menu
+* Connecting via rsh:: Using the @code{rsh} program to connect
+* Password authenticated:: Direct connections using passwords
+* Kerberos authenticated:: Direct connections with kerberos
+@end menu
+
+@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{anklet.grunge.com}, and the
+server machine is @file{chainsaw.brickyard.com}. On
+chainsaw, put the following line into the file
+@file{.rhosts} in @file{bach}'s home directory:
+
+@example
+anklet.grunge.com mozart
+@end example
+
+Then test that @code{rsh} is working with
+
+@example
+rsh -l bach chainsaw.brickyard.com echo $PATH
+@end example
+
+@cindex CVS_SERVER
+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}.
+
+There is no need to edit @code{inetd.conf} or start a
+@sc{cvs} server daemon.
+
+Continuing our example, supposing you want to access
+the module @file{foo} in the repository
+@file{/usr/local/cvsroot/}, on machine
+@file{chainsaw.brickyard.com}, you are ready to go:
+
+@example
+cvs -d bach@@chainsaw.brickyard.com:/user/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.)
+
+@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
+
+@cindex Pserver (subcommand)
+@cindex password server, setting up
+@cindex authenticating server, setting up
+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 -b /usr/local/bin pserver
+@end example
+
+The @samp{-b} option specifies the directory which contains
+the @sc{rcs} binaries on the server.
+
+ 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.
+
+@cindex CVS passwd file
+@cindex passwd file
+Because the client stores and transmits passwords in
+cleartext (almost---see @ref{Password authentication
+security} for details), a separate @sc{cvs} password
+file may be 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}). Its format is
+similar to @file{/etc/passwd}, except that it only has
+two fields, username and password. For example:
+
+@example
+bach:ULtgRLXo7NRxs
+cwang:1sOp854gDF3DY
+@end example
+
+The password is encrypted according to the standard
+Unix @code{crypt()} function, so it is possible to
+paste in passwords directly from regular Unix
+@file{passwd} files.
+
+When authenticating a password, the server first checks
+for the user in the @sc{cvs} @file{passwd} file. If it
+finds the user, it compares against that password. If
+it does not find the user, or if the @sc{cvs}
+@file{passwd} file does not exist, then the server
+tries to match the password using the system's
+user-lookup routine. When using the @sc{cvs}
+@file{passwd} file, the server runs under as the
+username specified in the the third argument in the
+entry, or as the first argument if there is no third
+argument (in this way @sc{cvs} allows imaginary
+usernames provided the @sc{cvs} @file{passwd} file
+indicates corresponding valid system usernames). In
+any case, @sc{cvs} will have no privileges which the
+(valid) user would not have.
+
+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.
+
+@node Password authentication client
+@subsubsection Using the client with password authentication
+@cindex Login (subcommand)
+@cindex password client, using
+@cindex authenticated client, using
+Before connecting to the server, the client must @dfn{log
+in} with the command @code{cvs login}. Logging in
+verifies a password with the server, and also records
+the password for later transactions with the server.
+The @code{cvs login} command needs to know the
+username, server hostname, and full repository path,
+and it gets this information from the repository
+argument or the @code{CVSROOT} environment variable.
+
+@code{cvs login} is interactive --- it prompts for a
+password:
+
+@example
+cvs -d bach@@chainsaw.brickyard.com:/usr/local/cvsroot login
+CVS password:
+@end example
+
+The password is checked with the server; if it is
+correct, the @code{login} succeeds, else it fails,
+complaining that the password was incorrect.
+
+Once you have logged in, you can force @sc{cvs} to
+connect directly to the server and authenticate with
+the stored password by prefixing the repository with
+@samp{:pserver:}:
+
+@example
+cvs -d :pserver:bach@@chainsaw.brickyard.com:/usr/local/cvsroot checkout foo
+@end example
+
+The @samp{:pserver:} is necessary because without it,
+@sc{cvs} will assume it should use @code{rsh} to
+connect with the server (@pxref{Connecting via rsh}).
+(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} records it in the working copy's
+@file{CVS} subdirectory.)
+
+@cindex CVS_PASSFILE, environment variable
+Passwords are stored by default in the file
+@file{$HOME/.cvspass}. Its format is human-readable,
+but don't edit it unless you know what you are doing.
+The passwords are not stored in cleartext, but are
+trivially encoded to protect them from "innocent"
+compromise (i.e., inadvertently being seen by a system
+administrator who happens to look at that file).
+
+The @code{CVS_PASSFILE} environment variable overrides
+this default. 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.
+
+@cindex CVS_PASSWORD, environment variable
+The @code{CVS_PASSWORD} environment variable overrides
+@emph{all} stored passwords. If it is set, @sc{cvs}
+will use it for all password-authenticated
+connections.
+
+@node Password authentication security
+@subsubsection Security considerations with password authentication
+
+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.
+
+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
+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.
+Furthermore, there may be other ways in which having
+access to @sc{cvs} allows people to gain more general
+access to the system; noone has done a careful audit.
+
+In summary, anyone who gets the password gets
+repository access, and 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 Kerberos authenticated
+@subsection Direct connection with kerberos
+
+@cindex kerberos
+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 (note that the data
+transmitted is @emph{not} encrypted).
+
+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.
+
+@cindex CVS_CLIENT_PORT
+You need to edit @code{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. Set @code{CVS_CLIENT_PORT} to
+@samp{-1} to force an rsh connection.
+
+@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 chainsaw.brickyard.com:/user/local/cvsroot checkout foo
+@end example
+
+If @sc{cvs} fails to connect, it will fall back to
+trying rsh.
+
+@c ---------------------------------------------------------------------
+@node Starting a new project
+@chapter Starting a project with CVS
+@cindex Starting a project with CVS
+@cindex Creating a project
+
+@comment --moduledb--
+Since @sc{cvs} 1.x is bad at renaming files and moving
+them between directories, the first thing you do when
+you start a new project should be to think through your
+file organization. It is not impossible---just
+awkward---to rename or move files.
+@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 module from scratch.
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node From files
+@subsection Creating a module 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{dir}}, and you want them to appear in the
+repository as @file{$CVSROOT/yoyodyne/@var{dir}}, you can do this:
+
+@example
+$ cd @var{dir}
+$ cvs import -m "Imported sources" yoyodyne/@var{dir} 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.
+
+@example
+$ cd ..
+$ mv @var{dir} @var{dir}.orig
+$ cvs checkout yoyodyne/@var{dir} # @r{Explanation below}
+$ ls -R yoyodyne
+$ 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 @samp{$CVSROOT}
+are reasonable, and that they belong to the proper
+groups. @xref{File permissions}.
+
+@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 systesm
+
+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 appopriate 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.
+
+@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.
+
+@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).
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node From scratch
+@subsection Creating a module from scratch
+
+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 @samp{$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 modules
+$ cd modules
+@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 modules
+@end example
+@end enumerate
+
+@c ---------------------------------------------------------------------
+@node Multiple developers
+@chapter Multiple developers
+@cindex Multiple developers
+@cindex Team of developers
+@cindex File locking
+@cindex Locking files
+@cindex Working copy
+
+When more than one person works on a software project
+things often get complicated. Often, two people try to
+edit the same file simultaneously. Some other version
+control systems (including @sc{rcs} and @sc{sccs})
+try to solve that particular problem by introducing
+@dfn{file locking}, so that only one person can edit
+each file at a time. Unfortunately, file locking 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.
+
+@sc{cvs} does not use file locking. Instead, it allows many
+people to 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, and explained in this chapter.
+
+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. It is often useful to inform the group of
+commits you have done. @sc{cvs} has several ways of
+automating that process. @xref{Informing others}.
+@xref{Revision management}, for more tips on how to use
+@sc{cvs}.
+
+@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
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node File status
+@section File status
+@cindex File status
+@cindex Status of a file
+@cindex Four states of a file
+
+After you have checked out a file out from @sc{cvs}, it is in
+one of these four states:
+
+@table @asis
+@cindex Up-to-date
+@item Up-to-date
+The file is identical with the latest revision in the
+repository.
+@c -- The above is not always true if branching is used.
+
+@item Locally modified
+@cindex Locally modified
+You have edited the file, and not yet committed your changes.
+
+@item Needing update
+@cindex Needing update
+Someone else has committed a newer revision to the repository.
+
+@item Needing merge
+@cindex Needing merge
+Someone else have committed a newer revision to the repository, and you
+have also made modifications to the file.
+@c -- What about "added" "removed" and so on?
+@end table
+
+You can use the @code{status} command to find out the status of a given
+file. @xref{status}.
+
+@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 copy of the
+module.
+
+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");
+<<<<<<< driver.c
+ exit(nerr == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+=======
+ exit(!!nerr);
+>>>>>>> 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
+
+@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
+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
+
+@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 for and remove files starting with
+@file{#cvs.tfl}, @file{#cvs.rfl}, or @file{#cvs.wfl}
+from the repository.
+
+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}--a
+way to prevent other developers from working on a
+particular file.
+
+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
+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.
+
+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{-l}] files @dots{}
+
+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.
+
+If @var{files} is omitted, it defaults to the current directory.
+
+@cindex watch off (subcommand)
+@end deffn
+
+@deffn Command {cvs watch off} [@code{-l}] files @dots{}
+
+Do not provide notification about work on @var{files}. CVS will create
+working copies of @var{files} read-write.
+
+The @var{files} and @code{-l} arguments 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{-l}] 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 @code{-l} option are processed as for the
+@code{cvs watch} commands.
+
+@end deffn
+
+
+@cindex watch remove (subcommand)
+@deffn Command {cvs watch remove} [@code{-a} action] [@code{-l}] 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
+
+When the conditions exist for notification, @sc{cvs}
+calls the @file{notify} administrative file, passing it
+the user to receive the notification and the user who
+is taking the action which results in notification.
+Normally @file{notify} will just send an email message.
+
+@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).
+
+@node Editing files
+@subsection How to edit a file which is being watched
+
+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.
+
+@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 @code{-l} option are processed as for the @code{cvs
+watch} commands.
+
+@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)
+@deffn Command {cvs unedit} [@code{-l}] 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 @code{-l} option are processed as for the
+@code{cvs watch} commands.
+
+@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 succesfully 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{-l}] 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 @code{-l} arguments are processed as for the
+@code{cvs watch} commands.
+
+@end deffn
+
+
+@cindex editors (subcommand)
+@deffn Command {cvs editors} [@code{-l}] 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 @code{-l} arguments 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
+
+@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.
+
+@c ---------------------------------------------------------------------
+@node Branches
+@chapter Branches
+@cindex Branches
+@cindex Main trunk and branches
+@cindex Revision tree, making branches
+
+So far, all revisions shown in this manual have been on
+the @dfn{main trunk}
+of the revision tree, i.e., all revision numbers
+have been of the form @var{x}.@var{y}. One useful
+feature, especially when maintaining several releases
+of a software product at once, is the ability to make
+branches on the revision tree. @dfn{Tags}, symbolic
+names for revisions, will also be
+introduced in this chapter.
+
+@menu
+* Tags:: Tags--Symbolic revisions
+* Branches motivation:: What branches are good for
+* Creating a branch:: Creating a branch
+* Sticky tags:: Sticky tags
+@end menu
+
+@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)
+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 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 contain characters such as @samp{%} or
+@samp{=}, rather than being named analogously to
+@code{BASE} and @code{HEAD}, to avoid conflicts with
+actual tag names.
+@c FIXME: is the above list of valid characters in tag
+@c names complete?
+
+@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
+copy of the module. That is, you should issue the
+command in the directory where @file{backend.c}
+resides.
+
+@example
+$ cvs tag release-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 /usr/local/cvsroot/yoyodyne/tc/backend.c,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+ Existing Tags:
+ release-0-4 (revision: 1.4)
+
+@end example
+
+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 release-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 release-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 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
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Branches motivation
+@section What branches are good for
+@cindex Branches motivation
+@cindex What branches are good for
+@cindex Motivation for branches
+
+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 select 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 rtag, creating a branch using
+
+@c FIXME: should be more explicit about the value of
+@c having a tag on the branchpoint. Also should talk
+@c about creating a branch with tag not rtag.
+The @code{rtag} command can be used to create a branch.
+The @code{rtag} command is much like @code{tag}, but it
+does not require that you have a working copy of the
+module. @xref{rtag}. (You can also use the @code{tag}
+command; @pxref{tag}).
+
+@example
+$ cvs rtag -b -r release-1-0 release-1-0-patches tc
+@end example
+
+The @samp{-b} flag makes @code{rtag} create a branch
+(rather than just a symbolic revision name). @samp{-r
+release-1-0} says that this branch should be rooted at the node (in
+the revision tree) that corresponds to the tag
+@samp{release-1-0}. Note that the numeric revision number that matches
+@samp{release-1-0} will probably be different from file to file. The
+name of the new branch is @samp{release-1-0-patches}, and the
+module affected is @samp{tc}.
+
+To fix the problem in release 1.0, you need a working
+copy of the branch you just created.
+
+@example
+$ cvs checkout -r release-1-0-patches tc
+$ 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 /usr/local/cvsroot/yoyodyne/tc/driver.c,v
+ Sticky Tag: release-1-0-patches (branch: 1.7.2)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+ Existing Tags:
+ release-1-0-patches (branch: 1.7.2)
+ release-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 /usr/local/cvsroot/yoyodyne/tc/backend.c,v
+ Sticky Tag: release-1-0-patches (branch: 1.4.2)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+ Existing Tags:
+ release-1-0-patches (branch: 1.4.2)
+ release-1-0 (revision: 1.4)
+ release-0-4 (revision: 1.4)
+
+@end example
+
+@cindex Branch numbers
+As the output from the @code{status} command shows the branch
+number is created by adding a digit at the tail of the revision number
+it is based on. (If @samp{release-1-0} corresponds to revision 1.4, the
+branch's revision number will be 1.4.2. For obscure reasons @sc{cvs} always
+gives branches even numbers, starting at 2.
+@xref{Revision numbers}).
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Sticky tags
+@section Sticky tags
+@cindex Sticky tags
+@cindex Tags, sticky
+@cindex Branches, sticky
+
+@c FIXME: make this stand alone better; many places
+@c @xref to this node.
+The @samp{-r release-1-0-patches} flag that was given
+to @code{checkout} in the previous example
+is @dfn{sticky}, that is, it will apply to subsequent commands
+in this directory. If you commit any modifications, they are
+committed on the branch. You can later merge the modifications into
+the main trunk. @xref{Merging}.
+
+You can use the @code{status} command to see what
+sticky tags or dates are set:
+
+@c FIXME: This example needs to stand alone better and it
+@c would also better if it didn't use -v which only
+@c clutters the output in this context.
+@example
+$ vi driver.c # @r{Fix the bugs}
+$ cvs commit -m "Fixed initialization bug" driver.c
+Checking in driver.c;
+/usr/local/cvsroot/yoyodyne/tc/driver.c,v <-- driver.c
+new revision: 1.7.2.1; previous revision: 1.7
+done
+$ cvs status -v 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 /usr/local/cvsroot/yoyodyne/tc/driver.c,v
+ Sticky Tag: release-1-0-patches (branch: 1.7.2)
+ Sticky Date: (none)
+ Sticky Options: (none)
+
+ Existing Tags:
+ release-1-0-patches (branch: 1.7.2)
+ release-1-0 (revision: 1.7)
+
+@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.
+
+@c Is the fact that CVS works this way a bug or a
+@c feature? If a feature, describe how you would use
+@c it to do something useful.
+Sticky tags are not just for branches. If you check
+out a certain revision (such as 1.4) it will also
+become sticky. Subsequent @samp{cvs update} will not
+retrieve the latest revision until you reset the tag
+with @samp{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.
+
+@cindex Restoring old version of removed file
+@cindex Resurrecting old version of dead file
+Many times you will want to retrieve an old version of
+a file without setting a sticky tag. The way to do
+that is with the @samp{-p} option to @code{checkout} or
+@code{update}, which sends the contents of the file to
+standard output. 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 -p -r 1.1 file1 >file1
+===================================================================
+Checking out file1
+RCS: /tmp/cvs-sanity/cvsroot/first-dir/Attic/file1,v
+VERS: 1.1
+***************
+$ cvs add file1
+cvs add: version 1.2 of `file1' will be resurrected
+cvs add: use 'cvs commit' to add this file permanently
+$ 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
+
+@c ---------------------------------------------------------------------
+@node Merging
+@chapter Merging
+@cindex Merging
+@cindex Copying changes
+@cindex Branches, copying changes between
+@cindex Changes, copying between branches
+@cindex Modifications, copying between branches
+
+You can include the changes made between any two
+revisions into your working copy, by @dfn{merging}.
+You can then commit that revision, and thus effectively
+copy the changes onto another branch.
+
+@menu
+* 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
+@end menu
+
+@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}.
+
+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 @emph{remove} 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
+that make up a module. You almost always use symbolic
+tags rather than revision numbers when operating on
+multiple files.
+
+@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 @samp{cvs
+update testing/testpgm.t testing/test2.t}
+
+@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} module
+@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.
+
+@example
+$ cvs update -l # @r{Don't update files in subdirectories}
+@end example
+
+@c ---------------------------------------------------------------------
+@node Adding files
+@chapter Adding files to a module
+@cindex Adding files
+
+To add a new file to a module, follow these steps.
+
+@itemize @bullet
+@item
+You must have a working copy of the module.
+@xref{Getting the source}.
+
+@item
+Create the new file inside your working copy of the module.
+
+@item
+Use @samp{cvs add @var{filename}} to tell @sc{cvs} that you
+want to version control the file.
+
+@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.
+
+@item
+If the file contains binary data it might be necessary
+to change the default keyword substitution.
+@xref{Keyword substitution}. @xref{admin examples}.
+@end itemize
+
+You can also use the @code{add} command to add a new
+directory inside a module.
+
+Unlike most other commands, the @code{add} command is
+not recursive. You cannot even type @samp{cvs add
+foo/bar}! Instead, you have to
+
+@example
+$ cd foo
+$ cvs add bar
+@end example
+
+@xref{add}, for a more complete description of the @code{add}
+command.
+
+@c ---------------------------------------------------------------------
+@node Removing files
+@chapter Removing files from a module
+@cindex Removing files
+@cindex Deleting files
+
+Modules change. New files are added, and old files
+disappear. Still, you want to be able to retrieve an
+exact copy of old releases of the module.
+
+Here is what you can do to remove a file from a module,
+but remain able to retrieve old revisions:
+
+@itemize @bullet
+@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 module.
+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
+
+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}.
+
+@cindex Remove (subcommand)
+@deffn Command {cvs remove} [@code{-lR}] 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. The @samp{-R} option
+(the default) specifies that it will recurse into
+subdirectories; @samp{-l} specifies that it will not.
+@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
+
+If you change your mind you can easily resurrect the
+file before you commit it, using the @code{add}
+command.
+
+@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
+
+@c ---------------------------------------------------------------------
+@node Tracking sources
+@chapter Tracking third-party sources
+@cindex Third-party sources
+@cindex Tracking sources
+
+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. After a successful @code{import}
+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 a module for the first time
+* Update imports:: Updating a module with the import command
+@end menu
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node First import
+@section Importing a module for the first time
+@cindex Importing modules
+
+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{import options}). The
+@dfn{release tags} are symbolic names for a particular
+release, such as @samp{FSF_0_04}.
+
+@cindex Wdiff (import example)
+Suppose you use @code{wdiff} (a variant of @code{diff}
+that ignores changes that only involve whitespace), 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
+$ tar xfz wdiff-0.04.tar.gz
+$ 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Update imports
+@section Updating a module 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.
+
+@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.
+
+@c ---------------------------------------------------------------------
+@node Moving files
+@chapter 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
+@section The Normal way to Rename
+
+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. (Both @var{old} and @var{new} could
+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 at 1.0 again, so if that bothers you, use the
+@samp{-r rev} option to commit (@pxref{commit options})
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Inside
+@section 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{module}
+$ 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 of the module 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
+@section 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{module}
+$ cp @var{old},v @var{new},v
+# @r{Remove the old file}
+$ cd ~/@var{module}
+$ 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 tag names}
+$ cvs tag -d @var{tag1}
+$ cvs tag -d @var{tag2}
+@dots{}
+@end example
+
+By removing the tags you will be able to check out old
+revisions of the module.
+
+@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.
+
+@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 itemize
+
+@c ---------------------------------------------------------------------
+@node Moving directories
+@chapter Moving and renaming directories
+@cindex Moving directories
+@cindex Renaming directories
+@cindex Directories, moving
+
+If you want to be able to retrieve old versions of the
+module, you must move each file in the directory
+with the @sc{cvs} commands. @xref{Outside}. The old, empty
+directory will remain inside the repository, but it
+will not appear in your workspace when you check out
+the module in the future.
+@c -- rephrase
+
+If you really want to rename or delete a directory, you
+can do it like this:
+
+@enumerate
+@item
+Inform everyone who has a copy of the module that the
+directory will be renamed. They should commit all
+their changes, and remove their working copies of the
+module, before you take the steps below.
+
+@item
+Rename the directory inside the repository.
+
+@example
+$ cd $CVSROOT/@var{module}
+$ 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 the module and continue
+working.
+
+@end enumerate
+
+If someone had a working copy of the module 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.
+
+@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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@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
+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.
+
+@node annotate
+@section Annotate command
+@cindex annotate (subcommand)
+
+@deffn Command {cvs annotate} [@code{-l}] 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. The @code{-l} option means
+to process the local directory only, not to recurse
+(@pxref{Common options}). 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
+
+@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 your working
+copy of a module 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.
+
+@sc{Rcs} uses 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:: RCS 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 RCS Keywords
+@cindex RCS keywords
+
+This is a list of the keywords that @sc{rcs} currently
+(in release 5.6.0.1) supports:
+
+@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 Locker keyword
+@item $@asis{Locker}$
+The login name of the user who locked the revision
+(empty if not locked, and thus almost always useless
+when you are using @sc{cvs}).
+
+@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 a @dfn{comment leader}
+which @sc{rcs} guesses from the file name extension.
+It can be changed with @code{cvs admin -c}.
+@xref{admin options}. This 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}---@xref{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.
+
+@need 800
+It is common to embed @code{$@asis{}Id$} string in the
+C source code. This example shows the first few lines
+of a typical file, after keyword substitution has been
+performed:
+
+@example
+static char *rcsid="$@asis{}Id: samp.c,v 1.5 1993/10/19 14:57:32 ceder Exp $";
+/* @r{The following lines will prevent @code{gcc} version 2.@var{x}}
+ @r{from issuing an "unused variable" warning}. */
+#if __GNUC__ == 2
+#define USE(var) static void * use_##var = (&use_##var, (void *) &var)
+USE (rcsid);
+#endif
+@end example
+
+Even though a clever optimizing compiler could remove
+the unused variable @code{rcsid}, most compilers tend
+to include the string in the binary. Some compilers
+have a @code{#pragma} directive to include literal text
+in the binary.
+
+@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 @sc{rcs} 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{rcs} 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 @sc{rcs} 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 -k (RCS kflags)
+@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 -k or -A options to @code{cvs
+checkout} or @code{cvs update}. @code{cvs diff} also
+has a @samp{-k} option. For some examples,
+@xref{Binary files}.
+
+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.
+This option is normally not useful when @sc{cvs} is used.
+
+@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.
+
+@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{rcs} 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.
+
+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 Binary files
+@chapter Handling binary files
+@cindex Binary files
+
+There are two issues with using @sc{cvs} to store
+binary files. The first is that @sc{cvs} by default
+convert 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.
+
+The @samp{-kb} option available with some @sc{cvs}
+commands insures that neither line ending conversion
+nor keyword expansion will be done. If you are using
+an old version of @sc{rcs} without this option, and you
+are using an operating system, such as unix, which
+terminates lines with linefeeds only, you can use
+@samp{-ko} instead; if you are on another operating
+system, upgrade to a version of @sc{rcs}, such as 5.7
+or later, which supports @samp{-kb}.
+
+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
+@end example
+
+When you check in the file @file{kotest} the keywords
+are expanded. (Try the above example, and do a
+@code{cat kotest} after every command). 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. The
+easiest way to get the unexpanded version of
+@file{kotest} is @code{cvs update -A}.
+
+@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 Invoking CVS
+@appendix Reference manual for CVS commands
+@cindex Command reference
+@cindex Reference, commands
+@cindex Invoking CVS
+
+This appendix describes how to invoke @sc{cvs}, and
+describes in detail those subcommands of @sc{cvs} which
+are not fully described elsewhere. To look up a
+particular subcommand, see @ref{Index}.
+
+@menu
+* Structure:: Overall structure of CVS commands
+* ~/.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
+* add:: Add a new file/directory to the repository
+* admin:: Administration front end for rcs
+* checkout:: Checkout sources for editing
+* commit:: Check files into the repository
+* diff:: Run diffs 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:: Print out 'rlog' information for files
+* rdiff:: 'patch' format diffs between releases
+* release:: Indicate that a Module is no longer in use
+* rtag:: Add a tag to a module
+* status:: Status info on the revisions
+* tag:: Add a tag to checked out version
+* 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 first release of @sc{cvs} consisted of a number of shell-scripts.
+Today @sc{cvs} is implemented as a single program that is a front-end
+to @sc{rcs} and @code{diff}. 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 program that is a front-end to @sc{rcs}.
+
+@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.
+
+@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
+.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
+co -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
+@cindex RCSBIN, overriding
+@cindex Overriding RCSBIN
+@item -b @var{bindir}
+Use @var{bindir} as the directory where @sc{rcs} programs are
+located. Overrides the setting of the @code{$RCSBIN} 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.
+
+@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{diff},
+you may need to use @samp{-f} to show the tag names.
+@footnote{Yes, this really should be fixed, and it's
+being worked on}
+
+@item -H
+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 a summary of all the
+commands available.
+
+@item -l
+Do not log the 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.
+
+@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
+@item -r
+Make new working files 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
+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.
+
+@item -z @var{gzip-level}
+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}).
+
+A wide variety of date formats are supported by the underlying
+@sc{rcs} facilities, similar to those described in co(1), but not
+exactly the same. The @var{date_spec} is interpreted as being
+in the local timezone, unless a specific timezone is specified.
+Examples of valid date specifications include:
+
+@example
+ 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
+@end example
+
+@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}).
+
+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).
+
+@need 800
+@samp{-f} is available with these commands: @code{checkout},
+@code{export}, @code{rdiff}, @code{rtag}, and @code{update}.
+
+@strong{Warning:} The @code{commit} command also has a
+@samp{-f} option, but it has a different behavior for
+that command. @xref{commit options}.
+
+@item -H
+Help; describe the options available for this command. This is
+the only option supported for all @sc{cvs} commands.
+
+@item -k @var{kflag}
+Alter the default @sc{rcs} 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} 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{checkout},
+@code{commit}, @code{diff}, @code{export}, @code{log},
+@code{remove}, @code{rdiff}, @code{rtag},
+@code{status}, @code{tag}, and @code{update}.
+
+@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 (remove) directories that are empty after being updated, on
+@code{checkout}, or @code{update}. Normally, an empty directory
+(one that is void of revision-controlled files) is left alone.
+Specifying @samp{-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 @samp{-r} or @samp{-D}
+options of @code{checkout} and @code{export}.
+@c -- implied--
+
+@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 -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.
+Avaliable with the following commands: @code{import},
+and @code{update}.
+
+@item -r @var{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.
+
+The tag specification is sticky when you use this 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.
+@xref{Tags}.
+
+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} history file
+does not contain the specified tag.
+
+@strong{Warning:} this is not the same as the overall `cvs -r' option,
+which you can specify to the left of a 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.
+
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node add
+@appendixsec add---Add a new file/directory to the repository
+@cindex Add (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: add [-k kflag] [-m 'message'] files@dots{}
+@item
+Requires: repository, working directory.
+@item
+Changes: working directory.
+@item
+Synonym: new
+@end itemize
+
+Use the @code{add} command to create a new file or directory in the
+source repository. The files or directories specified with @code{add}
+must already exist in the current directory (which must have been
+created with the @code{checkout} command). 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}.
+
+If the argument to @code{add} refers to an immediate
+sub-directory, the directory is created at the correct place in
+the source repository, and the necessary @sc{cvs} administration
+files are created in your working directory. If the directory
+already exists in the source repository, @code{add} still creates
+the administration files in your version of the directory.
+This allows you to use @code{add} to add a particular directory
+to your private sources even if someone else created that
+directory after your checkout of the sources. You can do the
+following:
+
+@example
+$ mkdir new_directory
+$ cvs add new_directory
+$ cvs update new_directory
+@end example
+
+An alternate approach using @code{update} might be:
+
+@example
+$ cvs update -d new_directory
+@end example
+
+(To add any available new directories to your working directory,
+it's probably simpler to use @code{checkout} (@pxref{checkout})
+or @samp{update -d} (@pxref{update})).
+
+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 resurrect the file, unless a @code{commit} command
+intervened.
+@xref{Removing files}, for an example.
+
+
+Unlike most other commands @code{add} never recurses down
+directories. It cannot yet handle relative paths. Instead of
+
+@example
+$ cvs add foo/bar.c
+@end example
+
+you have to do
+
+@example
+$ cd foo
+$ cvs add bar.c
+@end example
+
+@menu
+* add options:: add options
+* add examples:: add examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node add options
+@appendixsubsec add options
+@cindex Add options
+
+There are only two options you can give to @samp{add}:
+
+@table @code
+@item -k @var{kflag}
+This option specifies the default way that this file
+will be checked out. The @var{kflag} argument
+(@pxref{Substitution modes}) is stored in the @sc{rcs}
+file and can be changed with @code{admin -k}
+(@pxref{admin options}). See @ref{Binary files}, for
+information on using this option for binary files.
+
+@item -m @var{description}
+Using this option, you can give 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 @sc{rcs} history
+file 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 table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node add examples
+@appendixsubsec add examples
+
+To add the file @file{backend.c} to the repository, with a
+description, the following can be used.
+
+@example
+$ cvs add -m "Optimizer and code generation passes." backend.c
+$ cvs commit -m "Early version. Not yet compilable." backend.c
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node admin
+@appendixsec admin---Administration front end for rcs
+@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 @sc{rcs}
+facilities, documented in rcs(1). @code{admin} simply passes
+all its options and arguments to the @code{rcs} command; it does
+no filtering or other processing. This command @emph{does} work
+recursively, however, so extreme care should be used.
+
+If there is a group whose name matches a compiled in
+value which defaults to @code{cvsadmin}, only members
+of that group can use @code{cvs admin}. To disallow
+@code{cvs admin} for all users, create a group with no
+users in it.
+
+@menu
+* admin options:: admin options
+* admin examples:: admin examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node admin options
+@appendixsubsec admin options
+
+Not all valid @code{rcs} options are useful together
+with @sc{cvs}. Some even makes it impossible to use
+@sc{cvs} until you undo the effect!
+
+This description of the available options is based on
+the @samp{rcs(1)} man page, but modified to suit
+readers that are more interrested in @sc{cvs} than
+@sc{rcs}.
+
+@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}]
+When used with bare @sc{rcs}, this
+option sets the default branch to @var{rev}; in
+@sc{cvs} sticky tags (@pxref{Sticky tags}) are a better
+way to decide which branch you want to work on. With
+@sc{cvs}, this option can be used to control behavior
+with respect to the vendor branch.
+@c FIXME: document how you use it with the vendor
+@c branch (or fix cvs so that there is a more graceful
+@c way to handle the case).
+
+@item -c@var{string}
+Useful with @sc{cvs}. Sets the comment leader to
+@var{string}. The comment leader is printed before
+every log message line generated by the keyword
+@code{$@asis{}Log$} (@pxref{Keyword substitution}).
+This is useful for programming languages without
+multi-line comments. @sc{Rcs} initially guesses the
+value of the comment leader from the file name
+extension when the file is first committed.
+
+@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.
+
+@item -I
+Run interactively, even if the standard input is not a
+terminal.
+
+@item -i
+Useless with @sc{cvs}. When using bare @sc{rcs}, this
+is used to create and initialize a new @sc{rcs} file,
+without depositing a revision.
+
+@item -k@var{subst}
+Useful with @sc{cvs}. 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.
+
+@cindex Reserved checkouts
+@cindex RCS-style locking
+@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.
+
+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}.
+
+@item -N@var{name}[:[@var{rev}]]
+Act like @samp{-n}, except override any previous
+assignment of @var{name}.
+
+@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{rcs -n@var{name}: RCS/*} associates @var{name} with the
+current latest revision of all the named RCS files;
+this contrasts with @samp{rcs -n@var{name}:$ RCS/*} 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}
+Potentially useful, but dangerous, with @sc{cvs} (see below).
+Deletes (@dfn{outdates}) the revisions given by
+@var{range}. A range consisting of a single revision
+number means that revision. A range consisting of a
+branch number means the latest revision on that branch.
+A range of the form @samp{@var{rev1}:@var{rev2}} means
+revisions @var{rev1} to @var{rev2} on the same branch,
+@samp{:@var{rev}} means from the beginning of the
+branch containing @var{rev} up to and including
+@var{rev}, and @samp{@var{rev}:} means from revision
+@var{rev} to the end of the branch containing
+@var{rev}. None of the outdated revisions may have
+branches or locks.
+
+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.
+
+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{-}. 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}. The descriptive text can be seen in the
+output from @samp{cvs log} (@pxref{log}).
+
+@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.
+
+@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.
+
+@item -V@var{n}
+Emulate @sc{rcs} version @var{n}. Use -V@var{n} to make
+an @sc{rcs} file acceptable to @sc{rcs} version @var{n}
+by discarding information that would confuse version
+@var{n}.
+
+@item -x@var{suffixes}
+Useless with @sc{cvs}. Use @var{suffixes} to
+characterize RCS files.
+@end table
+
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node admin examples
+@appendixsubsec admin examples
+
+@appendixsubsubsec Outdating is dangerous
+
+First, an example of how @emph{not} to use the
+@code{admin} command. It is included to stress the
+fact that this command can be quite dangerous unless
+you know @emph{exactly} what you are doing.
+
+The @samp{-o} option can be used to @dfn{outdate} old revisions
+from the history file. 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!
+
+The next line is an example of a command that you would
+@emph{not} like to execute.
+
+@example
+$ cvs admin -o:R_1_02 .
+@end example
+
+The above command will delete all revisions up to, and
+including, the revision that corresponds to 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!
+
+@appendixsubsubsec Comment leaders
+@cindex Comment leader
+@cindex Log keyword, selecting comment leader
+@cindex Nroff (selecting comment leader)
+
+If you use the @code{$@asis{}Log$} keyword and you do
+not agree with the guess for comment leader that
+@sc{cvs} has done, you can enforce your will with
+@code{cvs admin -c}. This might be suitable for
+@code{nroff} source:
+
+@example
+$ cvs admin -c'.\" ' *.man
+$ rm *.man
+$ cvs update
+@end example
+
+The two last steps are to make sure that you get the
+versions with correct comment leaders in your working
+files.
+
+@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
+
+Make 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} part of the command 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}.
+
+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}).
+
+@c FIXME: misleading--checkout takes a module as
+@c argument, and update does not--so -d behavior is not the only
+@c difference.
+Running @code{checkout} on a directory that was already
+built by a prior @code{checkout} is also permitted, and
+has the same effect as specifying the @samp{-d} option
+to the @code{update} command, that is, any new
+directories that have been created in the repository
+will appear in your work area. @xref{update}.
+
+@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 @sc{rcs} keywords according to @var{kflag}. See
+co(1). 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. @xref{status}.
+
+@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.
+
+@item -p
+Pipe files to the standard output.
+
+@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. Unless you
+also use @samp{-N}, the paths created under @var{dir}
+will be as short as possible.
+
+@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{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. (Normally, @sc{cvs} shortens paths as
+much as possible when you specify an explicit target
+directory).
+
+@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
+Version 1.3 Synopsis: commit [-lnR] [-m 'log_message' |
+-f file] [-r revision] [files@dots{}]
+@item
+Version 1.3.1 Synopsis: commit [-lnRf] [-m 'log_message' |
+-F file] [-r revision] [files@dots{}]
+@c -- rename-f-F--
+@item
+Requires: working directory, repository.
+@item
+Changes: repository.
+@item
+Synonym: ci
+@end itemize
+
+@strong{Warning:} The @samp{-f @var{file}} option will
+probably be renamed to @samp{-F @var{file}}, and @samp{-f}
+will be given a new behavior in future releases of @sc{cvs}.
+@c -- rename-f-F--
+
+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} history file inside the
+repository. This log message can be retrieved with the
+@code{log} command; @xref{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
+@c -- rename-f-F--
+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. You
+cannot commit to a specific revision on a branch.
+@end table
+
+@code{commit} also supports these options:
+
+@table @code
+@item -F @var{file}
+This option is present in @sc{cvs} releases 1.3-s3 and
+later. Read the log message from @var{file}, instead
+of invoking an editor.
+
+@item -f
+@c -- rename-f-F--
+This option is present in @sc{cvs} 1.3-s3 and later releases
+of @sc{cvs}. Note that this is not the standard behavior of
+the @samp{-f} option as defined in @xref{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
+
+@item -f @var{file}
+@c -- rename-f-F--
+This option is present in @sc{cvs} releases 1.3, 1.3-s1 and
+1.3-s2. Note that this is not the standard behavior of
+the @samp{-f} option as defined in @xref{Common options}.
+
+Read the log message from @var{file}, instead
+of invoking an editor.
+
+@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
+
+@appendixsubsubsec New major release number
+
+When you make a major release of your product, you
+might want the revision numbers to track your major
+release number. You should normally not care about
+the revision numbers, but this is a thing that many
+people want to do, and it can be done without doing any
+harm.
+
+To bring all your files up to the @sc{rcs} revision 3.0
+(including those that haven't changed), you might do:
+
+@example
+$ cvs commit -r 3.0
+@end example
+
+Note that it is generally a bad idea to try to make the
+@sc{rcs} revision number equal to the current release number
+of your product. You should think of the revision
+number as an internal number that the @sc{cvs} package
+maintains, and that you generally never need to care
+much about. Using the @code{tag} and @code{rtag}
+commands you can give symbolic names to the releases
+instead. @xref{tag} and @xref{rtag}.
+
+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}.
+
+@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{tag}
+or @pxref{rtag}). 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---Run diffs between revisions
+@cindex Diff (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: diff [-l] [rcsdiff_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 will be 0 if no differences were found,
+1 if some differences were found, and 2 if any error
+occurred.
+
+@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.
+
+@sc{cvs} can be configured to pass the @samp{-D} option
+through to @code{rcsdiff} (which in turn passes it on
+to @code{diff}. @sc{Gnu} diff uses @samp{-D} as a way to
+put @code{cpp}-style @samp{#define} statements around the output
+differences. There is no way short of testing to
+figure out how @sc{cvs} was configured. In the default
+configuration @sc{cvs} will use the @samp{-D @var{date}} option.
+
+@item -k @var{kflag}
+Process @sc{rcs} keywords according to @var{kflag}. See
+co(1).
+
+@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).
+
+One or both @samp{-r} options can be replaced by a
+@samp{-D @var{date}} option, described above.
+@end table
+
+Any other options that are found are passed through to
+@code{rcsdiff}, which in turn passes them to
+@code{diff}. The exact meaning of the options depends
+on which @code{diff} you are using. The long options
+introduced in @sc{gnu} diff 2.0 are not yet supported in
+@sc{cvs}. See the documentation for your @code{diff} to see
+which options are supported.
+
+@c -- Document some common useful diff options, such as
+@c -u and -c.
+@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 [-flNn] [-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 @sc{rcs} 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 @sc{rcs} 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. Unless you
+also use @samp{-N}, the paths created under @var{dir}
+will be as short as possible.
+
+@item -k @var{subst}
+Set keyword expansion mode (@pxref{Substitution modes}).
+
+@item -N
+Only useful together with @samp{-d @var{dir}}. With this
+option, @sc{cvs} will not shorten module paths in your
+working directory. (Normally, @sc{cvs} shortens paths as
+much as possible when you specify an explicit target
+directory.)
+@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{-xMACFROGWUT}.
+
+@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 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 -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 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}.
+@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)
+
+@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
+
+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 @sc{rcs}
+branch, by default 1.1.1. Updates are leaves of this
+branch; for example, files from the first imported
+collection of source will be revision 1.1.1.1, then
+files from the first imported update will be revision
+1.1.1.2, and so on.
+
+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}.
+
+@menu
+* import options:: import options
+* 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 three additional special options.
+
+@table @code
+@item -b @var{branch}
+Specify a first-level branch other than 1.1.1. Unless
+the @samp{-b @var{branch}} flag is given, revisions will
+@emph{always} be made to the branch 1.1.1---even if a
+@var{vendortag} that matches another branch is given!
+What happens in that case, is that the tag will be
+reset to 1.1.1. Warning: This behavior might change
+in the future.
+
+@item -k @var{subst}
+Indicate the RCS 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 examples
+@appendixsubsec import examples
+
+@xref{Tracking sources}, and @xref{From files}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node log
+@appendixsec log---Print out 'rlog' information for files
+@cindex Log (subcommand)
+
+@itemize @bullet
+@item
+Synopsis: log [-l] rlog-options [files@dots{}]
+@item
+Requires: repository, working directory.
+@item
+Changes: nothing.
+@item
+Synonym: rlog
+@end itemize
+
+Display log information for files. @code{log} calls
+the @sc{rcs} utility @code{rlog}, which prints all available
+information about the @sc{rcs} history file. This 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 -- timezone--
+
+@menu
+* log options:: log options
+* log examples:: log examples
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node log options
+@appendixsubsec log options
+
+Only one option is interpreted by @sc{cvs} and not passed on to @code{rlog}:
+
+@table @code
+@item -l
+Local; run only in current working directory. (Default
+is to run recursively).
+@end table
+
+By default, @code{rlog} prints all information that is
+available. All other options (including those that
+normally behave differently) are passed through to
+@code{rlog} and restrict the output. See rlog(1) for a
+complete description of options. This incomplete list
+(which is a slightly edited extract from rlog(1)) lists
+all options that are useful in conjunction with @sc{cvs}.
+
+@strong{Please note:} There can be no space between the option
+and its argument, since @code{rlog} parses its options
+in a different way than @sc{cvs}.
+
+@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 following table
+explains the available range formats:
+
+@table @code
+@item @var{d1}<@var{d2}
+@itemx @var{d2}>@var{d1}
+Select the revisions that were deposited between
+@var{d1} and @var{d2} inclusive.
+
+@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 date/time strings @var{d}, @var{d1}, and @var{d2}
+are in the free format explained in co(1). Quoting is
+normally necessary, especially for < and >. Note that
+the separator is a semicolon (;).
+
+@item -h
+Print only the @sc{rcs} pathname, working pathname, head,
+default branch, access list, locks, symbolic names, and
+suffix.
+
+@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} history file.
+
+@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. You can unfortunately not specify a
+symbolic branch here. You must specify the numeric
+branch number. @xref{Magic branch numbers}, for an
+explanation.
+
+@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.
+
+@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.
+@end table
+
+@code{rlog} prints the intersection of the revisions
+selected with the options @samp{-d}, @samp{-l},
+@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 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 patch command when
+patching the old sources, so that 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 @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.
+This option is not available if your diff does not
+support the unidiff format. 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 @sc{rcs} keywords according to the rules current in
+@sc{rcs} version @var{vn} (the expansion format changed with
+@sc{rcs} version 5).
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node rdiff examples
+@appendixsubsec rdiff examples
+
+Suppose you receive mail from @t{foo@@bar.com} 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@@bar.com
+@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 options
+* 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 uses
+@samp{rm -r @file{module}} to delete your file. 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{add}) 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{add}) will be
+silently ignored (and deleted, if @samp{-d} is
+specified), even if they contain files.
+
+@table @code
+@item U @var{file}
+There exists a newer revision of this file in the
+repository, and you have not modified your local copy
+of the file.
+
+@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.
+
+Note that no warning message like this is printed for
+spurious directories that @sc{cvs} encounters. The
+directory, and all its contents, are silently ignored.
+
+@c FIXME -- CVS should be fixed to print "? foo" for
+@c such spurious directories
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node release examples
+@appendixsubsec release examples
+
+Release the module, 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) module `tc': y
+$
+@end example
+
+@node rtag
+@appendixsec rtag---Add a tag to the RCS file
+@cindex Rtag (subcommand)
+
+@itemize @bullet
+@item
+rtag [-falnR] [-b] [-d] [-r tag | -Ddate] symbolic_tag modules@dots{}
+@item
+Requires: repository.
+@item
+Changes: repository.
+@item
+Synonym: rfreeze
+@end itemize
+
+You can use this command to assign symbolic tags to
+particular, explicitly specified source revisions in
+the repository. @code{rtag} works directly on the
+repository contents (and requires no prior checkout).
+Use @code{tag} instead (@pxref{tag}), to base the
+selection of revisions on the contents of your
+working directory.
+
+If you attempt to use a tag name that already exists,
+@sc{cvs} will complain and not overwrite that tag. Use
+the @samp{-F} option to force the new tag value.
+
+@menu
+* rtag options:: rtag options
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node rtag options
+@appendixsubsec rtag options
+
+These standard options are supported by @code{rtag}
+(@pxref{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 -F
+Overwrite an existing tag of the same name on a
+different revision. This option is new in @sc{cvs}
+1.4. The old behavior is matched by @samp{cvs tag -F}.
+
+@item -l
+Local; run only in current working directory.
+
+@item -n
+Do not run any tag program that was specified with the
+@samp{-t} flag inside the @file{modules} file.
+(@pxref{modules}).
+
+@item -R
+Commit directories recursively. This is on by default.
+
+@item -r @var{tag}
+Only tag those files that contain @var{tag}. This can
+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.
+@end table
+
+In addition to the above common options, these options
+are available:
+
+@table @code
+@item -a
+Use the @samp{-a} option to have @code{rtag} look in the
+@file{Attic} (@pxref{Removing files}) 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).
+
+@item -b
+Make the tag a branch tag. @xref{Branches}.
+
+@item -d
+Delete the tag instead of creating it.
+
+In general, tags (often the symbolic names of software
+distributions) should not be removed, but the @samp{-d}
+option is available as a means to remove completely
+obsolete symbolic names if necessary (as might be the
+case for an Alpha release, or if you mistagged a
+module).
+@end table
+
+@ignore
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@c @node rtag examples
+@appendixsubsec rtag examples
+
+@c -- Examples here!
+@end ignore
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node status
+@appendixsec status---Status info on the revisions
+@cindex Status (subcommand)
+
+@itemize @bullet
+@item
+status [-lR] [-v] [files@dots{}]
+@item
+Requires: working directory, repository.
+@item
+Changes: nothing.
+@end itemize
+
+Display a brief report on the current status of files
+with respect to the source repository, including any
+sticky tags, dates, or @samp{-k} options.
+
+You can also use this command to determine the
+potential impact of a @samp{cvs update} on your working
+source directory---but remember that things might
+change in the repository before you run @code{update}.
+
+@menu
+* status options:: status options
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node status options
+@appendixsubsec status options
+
+These standard options are supported by @code{status}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -l
+Local; run only in current working directory.
+
+@item -R
+Commit directories recursively. This is on by default.
+@end table
+
+There is one additional option:
+
+@table @code
+@item -v
+Verbose. In addition to the information normally
+displayed, print all symbolic tags, together with the
+numerical value of the revision or branch they refer
+to.
+@end table
+
+@ignore
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@c @node status examples
+@appendixsubsec status examples
+
+@c -- FIXME
+@end ignore
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node tag
+@appendixsec tag---Add a symbolic tag to checked out version of RCS file
+@c -- //////// - unnecessary. Also
+@c -- in a lot of other
+@c -- places.
+@cindex Tag (subcommand)
+
+@itemize @bullet
+@item
+tag [-lR] [-b] [-d] symbolic_tag [files@dots{}]
+@item
+Requires: working directory, repository.
+@item
+Changes: repository.
+@item
+Synonym: freeze
+@end itemize
+
+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
+@code{rtag}, but the versions are supplied implicitly by the
+@sc{cvs} records of your working files' history rather than
+applied explicitly.
+
+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.
+
+The symbolic tags are meant to permanently record which
+revisions of which files were used in creating a
+software distribution. The @code{checkout} and
+@code{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.
+
+This command can also be used to delete a symbolic tag,
+or to create a branch. See the options section below.
+
+If you attempt to use a tag name that already exists,
+@sc{cvs} will complain and not overwrite that tag. Use
+the @samp{-F} option to force the new tag value.
+
+
+@menu
+* tag options:: tag options
+@end menu
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node tag options
+@appendixsubsec tag options
+
+These standard options are supported by @code{tag}
+(@pxref{Common options}, for a complete description of
+them):
+
+@table @code
+@item -F
+Overwrite an existing tag of the same name on a
+different revision. This option is new in @sc{cvs}
+1.4. The old behavior is matched by @samp{cvs tag -F}.
+
+@item -l
+Local; run only in current working directory.
+
+@item -R
+Commit directories recursively. This is on by default.
+@end table
+
+Two special options are available:
+
+@table @code
+@item -b
+The -b option makes the tag a branch tag
+(@pxref{Branches}), allowing concurrent, isolated
+development. This is most useful for creating a patch
+to a previously released software distribution.
+
+@item -d
+Delete a tag.
+
+If you use @samp{cvs tag -d symbolic_tag}, the symbolic
+tag you specify is deleted instead of being added.
+Warning: Be very certain of your ground before you
+delete a tag; doing this permanently discards some
+historical information, which may later turn out to
+be valuable.
+@end table
+
+@ignore
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@c @node tag examples
+@appendixsubsec tag examples
+
+@c -- FIXME
+@end ignore
+
+@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
+* update examples:: update examples
+@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 @sc{rcs} keywords according to @var{kflag}. See
+co(1). 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. @xref{status}.
+
+@item -l
+Local; run only in current working directory. @xref{Recursive behavior}.
+
+@item -P
+Prune empty directories.
+
+@item -p
+Pipe files to the standard output.
+
+@item -R
+Operate recursively. This is on by default.
+@xref{Recursive behavior}.
+
+@item -r tag
+Retrieve revision @var{tag}. 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 -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 -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{Merging}.
+
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node update output
+@appendixsubsec update output
+
+@code{update} 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 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 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}
+A conflict was detected while trying to merge your
+changes to @var{file} with changes from the source
+repository. @var{file} (the copy in your working
+directory) is now the output of the rcsmerge(1) command
+on 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 @sc{rcs} revision that your modified file started
+from. (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.)
+
+@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}).
+
+Note that no warning message like this is printed for
+spurious directories that @sc{cvs} encounters. The
+directory, and all its contents, are silently ignored.
+@end table
+
+@c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+@node update examples
+@appendixsubsec update examples
+
+The following line will display all files which are not
+up-to-date without actually change anything in your
+working directory. It can be used to check what has
+been going on with the project.
+
+@example
+$ cvs -n -q update
+@end example
+
+@c ---------------------------------------------------------------------
+@node Administrative files
+@appendix Reference manual for the Administrative files
+@cindex Administrative files (reference)
+@cindex Files, reference manual
+@cindex Reference manual (files)
+@cindex CVSROOT (file)
+
+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.
+
+The most important of these files is the @file{modules}
+file, which defines the modules inside the repository.
+
+@menu
+* modules:: Defining modules
+* Wrappers:: Treat directories as files
+* commit files:: The commit support files
+* commitinfo:: Pre-commit checking
+* editinfo:: Specifying how log messages are created
+* loginfo:: Where should log messages be sent?
+* rcsinfo:: Templates for the log messages
+* cvsignore:: Ignoring files via cvsignore
+* history file:: History information
+* Setting up:: Setting up the repository
+* Variables:: Various variables are expanded
+@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.
+
+A module definition is a single line of the
+@file{modules} file, in either of two formats. In both
+cases, @var{mname} represents the symbolic module name,
+and the remainder of the line is its definition.
+
+@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.
+
+@item @var{mname} [ options ] @var{dir} [ @var{files}@dots{} ] [ &@var{module}@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.
+
+By explicitly specifying files in the module definition
+after @var{dir}, you can select particular files from
+directory @var{dir}. The sample definition for
+@samp{modules} is an example of a module defined with a
+single file from a particular directory. Here is
+another example:
+
+@example
+m4test unsupported/gnu/m4 foreach.m4 forloop.m4
+@end example
+
+@noindent
+With this definition, executing @samp{cvs checkout
+m4test} will create a single working directory
+@file{m4test} containing the two files listed, which
+both come from a common directory several levels deep
+in the @sc{cvs} source repository.
+
+A module definition can refer to other modules by
+including @samp{&@var{module}} in its definition.
+@code{checkout} creates a subdirectory for each such
+module, in your working directory.
+@c -- Nope. "in your working directory" is wrong. What
+@c -- is right?
+
+@table @code
+@item -d @var{name}
+Name the working directory something other than the
+module name.
+
+@cindex Export program
+@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.
+
+@cindex Checkin program
+@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{editinfo} files provide other
+ways to call a program on commit.
+
+@cindex Checkout program
+@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.
+
+@cindex Status of a module
+@cindex Module status
+@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
+@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}. There is no way to
+specify a program to run when @code{tag} is executed.
+
+@cindex Update program
+@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.
+@end table
+@end table
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Wrappers
+@appendixsec The cvswrappers file
+@cindex cvswrappers (admin file)
+@cindex CVSWRAPPERS, environment variable
+@cindex Wrappers
+
+Wrappers allow you to set a hook which transforms files on
+their way in and out of @sc{cvs}. Most or all of the
+wrappers features do not work with client/server @sc{cvs}.
+
+The file @file{cvswrappers} defines the script that will be
+run on a file when its name matches a regular
+expresion. 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 @file{cvswrappers} also has a @samp{-m} option to
+specify the merge methodology that should be used when
+the file is updated. @code{MERGE} means the usual
+@sc{cvs} behavior: try to merge the files (this
+generally will not work for binary files). @code{COPY}
+means that @code{cvs update} will merely copy one
+version over the other, and require the user using
+mechanisms outside @sc{cvs}, to insert any necessary
+changes.
+@c FIXME: which version is copied over which version?
+The @samp{-m} wrapper option only affects behavior when
+merging is done on update; it does not affect how files
+are stored. See @xref{Binary files}, for more on
+binary files.
+
+The basic format of the file @file{cvswrappers} is:
+
+@example
+wildcard [option value][option value]...
+
+where option is one of
+-f from cvs filter value: path tofilter
+-t to cvs filter value: path to filter
+-m update methodology value: MERGE or COPY
+
+and value is a single-quote delimited value.
+@end example
+
+@example
+*.nib -f 'unwrap %s' -t 'wrap %s %s' -m 'COPY'
+*.c -t 'indent %s %s'
+@end example
+
+@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).
+
+The last example line says that all files that end with
+a @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.
+
+@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 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}).
+
+@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
+
+The four files @file{commitinfo}, @file{loginfo},
+@file{rcsinfo} and @file{editinfo} all have a common
+format. The purpose of the files are described later
+on. The common syntax is described here.
+
+Each line contains the following:
+@itemize @bullet
+@item
+A regular expression
+
+@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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@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).
+
+The first line with a regular expression matching the
+relative path to the module will be used. If the
+command returns a non-zero exit status the commit will
+be aborted.
+
+@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 occurances 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node editinfo
+@appendixsec Editinfo
+@cindex Editinfo
+@cindex Editor, specifying per module
+@cindex Per-module editor
+@cindex Log messages, editing
+
+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 precompiled default, normally @code{vi}, will be
+used.
+
+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,
+@file{editinfo} will be run on the @emph{remote}
+(i.e., server) side, not the client side (@pxref{Remote
+repositories}).
+
+@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
+@cindex Storing log messages
+@cindex Mailing log messages
+@cindex Distributing log messages
+@cindex Log messages
+
+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.
+
+The filter program may use one and only one % modifier
+(a la printf). If @samp{%s} is specified in the filter
+program, a brief title is included (enclosed in single
+quotes) showing the modified file names.
+
+If the repository name does not match any of the
+regular expressions in this file, the @samp{DEFAULT}
+line is used, if it is specified.
+
+All occurances 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.
+
+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
+@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}.
+@c and 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? This example (with the
+@c mail to ceder put back in) is awkward to write if
+@c only the first matching line is used.
+@example
+ALL /usr/local/bin/cvs-log $CVSROOT/CVSROOT/commitlog
+@c ^CVSROOT Mail -s %s ceder
+^CVSROOT /usr/local/bin/cvs-log /usr/adm/cvsroot-log
+@end example
+
+The shell-script @file{/usr/local/bin/cvs-log} looks
+like this:
+
+@example
+#!/bin/sh
+(echo "-----------------------------------------------------------------";
+ echo -n $USER" ";
+ date;
+ echo;
+ sed '1s+'$@{CVSROOT@}'++') >> $1
+@end example
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node rcsinfo
+@appendixsec Rcsinfo
+@cindex Rcsinfo
+@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{editinfo}, @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 occurances of the name @samp{ALL} appearing as a
+regular expression are used in addition to the first
+matching regular expression or @samp{DEFAULT}.
+
+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{editinfo example}, 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, 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}.
+
+@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{Setting up}).
+
+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}.
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Setting up
+@appendixsec Setting up the repository
+@cindex Repository, setting up
+@cindex Creating a repository
+@cindex Setting up a repository
+
+To set up a @sc{cvs} repository, choose a directory
+with ample disk space available for the revision
+history of the source files. It should be accessable
+(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.
+
+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 Variables
+@appendixsec Expansions in administrative files
+
+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 resonable
+expansion if pserver (@pxref{Password authenticated})
+is in used; 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 alphanumberic 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
+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
+This is the value @sc{cvs} is using for where to find
+@sc{rcs} binaries. @xref{Global options}, for a
+description of how to specify this.
+
+@item CVSEDITOR
+@itemx VISUAL
+@itemx EDITOR
+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
+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 that is running @sc{cvs} can specify,
+use a user variable. 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 @code{cvs -s TESTDIR=/work/local/tests}, 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.
+
+@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
+@item $CVSIGNORE
+A whitespace-separated list of file name patterns that
+@sc{cvs} should ignore. @xref{cvsignore}.
+
+@cindex CVSWRAPPERS
+@item $CVSWRAPPERS
+A whitespace-separated list of file name patterns that
+@sc{cvs} should treat as wrappers. @xref{Wrappers}.
+
+@cindex 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.
+
+@cindex CVSROOT
+@item $CVSROOT
+Should contain the full pathname to the root of the @sc{cvs}
+source repository (where the @sc{rcs} history 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.
+
+@cindex EDITOR
+@cindex CVSEDITOR
+@item $EDITOR
+@itemx $CVSEDITOR
+Specifies the program to use for recording log messages
+during commit. If not set, the default is
+@samp{/usr/ucb/vi}. @code{$CVSEDITOR} overrides
+@code{$EDITOR}. @code{$CVSEDITOR} does not exist in
+@sc{cvs} 1.3, but the next release will probably
+include it.
+
+@cindex PATH
+@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 RCSBIN
+@item $RCSBIN
+Specifies the full pathname of the location of @sc{rcs} programs,
+such as co(1) and ci(1). If not set, a compiled-in
+value is used, or your @code{$PATH} is searched.
+
+@cindex HOME
+@item $HOME
+@cindex HOMEPATH
+@item $HOMEPATH
+Used to locate the directory where the @file{.cvsrc}
+file is searched (@code{$HOMEPATH} is used for Windows-NT).
+@pxref{~/.cvsrc}
+
+@cindex CVS_RSH
+@item $CVS_RSH
+Used in client-server mode when accessing a remote
+repository using @sc{rsh}. The default value is
+@code{rsh}. You can set it to use another program for
+accssing the remote server (e.g. for HP-UX 9, you
+should set it to @code{remsh} because @code{rsh}
+invokes the restricted shell). @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_PASSWORD
+Used in client-server mode when accessing the @code{cvs
+login server}.
+@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
+@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
+@item $CVS_CLIENT_LOG
+Used for debugging only in client-server
+mode. If set, everything send to the server is logged
+into @file{@code{$CVS_CLIENT_LOG}.in} and everything
+send from the server is logged into
+@file{@code{$CVS_CLIENT_LOG}.out}.
+
+@cindex CVS_SERVER_SLEEP
+@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 the specified amount of
+seconds so that you can attach to it with a debugger.
+
+@cindex CVS_IGNORE_REMOTE_ROOT
+@item $CVS_IGNORE_REMOTE_ROOT
+(What is the purpose of this variable?)
+
+@cindex COMSPEC
+@item $COMSPEC
+Used under OS/2 only. It specifies the name of the
+command interpreter and defaults to @sc{cmd.exe}.
+
+
+@end table
+
+@sc{cvs} is a front-end to @sc{rcs}. The following environment
+variables affect @sc{rcs}. Note that if you are using
+the client/server @sc{cvs}, these variables need to be
+set on the server side (which may or not may be
+possible depending on how you are connecting). There
+is probably not any need to set any of them, however.
+
+@table @code
+@cindex LOGNAME
+@item $LOGNAME
+@cindex USER
+@itemx $USER
+If set, they affect who @sc{rcs} thinks you are. If you
+have trouble checking in files it might be because your
+login name differs from the setting of e.g.
+@code{$LOGNAME}.
+
+@cindex RCSINIT
+@item $RCSINIT
+Options prepended to the argument list, separated by
+spaces. A backslash escapes spaces within an option.
+The @code{$RCSINIT} options are prepended to the
+argument lists of most @sc{rcs} commands.
+
+@cindex TMPDIR
+@item $TMPDIR
+@cindex TMP
+@itemx $TMP
+@cindex TEMP
+@itemx $TEMP
+Name of the temporary directory. The environment
+variables are inspected in the order they appear above
+and the first value found is taken; if none of them are
+set, a host-dependent default is used, typically
+@file{/tmp}.
+@end table
+
+@c ---------------------------------------------------------------------
+@node Troubleshooting
+@appendix Troubleshooting
+
+@menu
+* Magic branch numbers:: Magic branch numbers
+@end menu
+
+@ignore
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@c @node Bad administrative files
+@appendixsec Bad administrative files
+
+@c -- Give hints on how to fix them
+@end ignore
+
+@c - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+@node Magic branch numbers
+@appendixsec Magic branch numbers
+
+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.3 becomes
+1.2.0.3, 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 at least four places the
+hiding is incomplete.
+
+@itemize @bullet
+@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.
+
+@item
+The magic branch number appears in the output from
+@code{cvs log}. This is much harder to fix, since
+@code{cvs log} runs @code{rlog} (which is part of the
+@sc{rcs} distribution), and modifying @code{rlog} to
+know about magic branches would probably break someone's
+habits (if they use branch 0 for their own purposes).
+
+@item
+You cannot specify a symbolic branch name to @code{cvs log}.
+
+@item
+You cannot specify a symbolic branch name to @code{cvs
+admin}.
+
+@end itemize
+
+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 Copying
+@appendix GNU GENERAL PUBLIC LICENSE
+@c @include gpl.texinfo
+
+@c ---------------------------------------------------------------------
+@node Index
+@unnumbered Index
+@cindex Index
+
+@printindex cp
+
+@summarycontents
+
+@contents
+
+@bye
+
+Local Variables:
+fill-column: 55
+End:
diff --git a/contrib/cvs/doc/cvsclient.texi b/contrib/cvs/doc/cvsclient.texi
new file mode 100644
index 0000000..0d61ac1
--- /dev/null
+++ b/contrib/cvs/doc/cvsclient.texi
@@ -0,0 +1,824 @@
+\input texinfo @c -*- texinfo -*-
+
+@setfilename cvsclient.info
+@include CVSvn.texi
+
+@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.
+* Notes:: Notes on the current implementation
+* Protocol Notes:: Possible enhancements, limitations, etc. of the protocol
+* Connection and Authentication:: Various ways to connect to the server
+* Protocol:: Complete description 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 @xref{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. It is expected
+that it runs over TCP, UUCP, etc.
+
+@item
+Security and authentication are handled outside this protocol (but see
+below about @samp{cvs kserver}).
+
+@item
+This might be a first step towards adding transactions to CVS (i.e. a
+set of operations is either executed atomically or none of them is
+executed), improving the locking, or other features. The current server
+implementation is a long way from being able to do any of these
+things. The protocol, however, is not known to contain any defects
+which would preclude them.
+
+@item
+The server never has to have any CVS locks in place while it is waiting
+for communication with the client. This makes things robust in the face
+of flaky networks.
+
+@item
+Data is transferred in large chunks, which is necessary for good
+performance. In fact, currently the client uploads all the data
+(without waiting for server responses), and then waits for one server
+response (which consists of a massive download of all the data). There
+may be cases in which it is better to have a richer interraction, but
+the need for the server to release all locks whenever it waits for the
+client makes it complicated.
+@end itemize
+
+@node Notes
+@chapter Notes on the Current Implementation
+
+The client is built in to the normal @code{cvs} program, triggered by a
+@code{CVSROOT} variable containing a colon, for example
+@code{cygnus.com:/rel/cvsfiles}.
+
+The client stores what is stored in checked-out directories (including
+@file{CVS}). The way these are stored is totally compatible with
+standard CVS. The server requires no storage other than the repository,
+which also is totally compatible with standard CVS.
+
+The server is started by @code{cvs server}. There is no particularly
+compelling reason for this rather than making it a separate program
+which shares a lot of sources with cvs.
+
+The server can also be started by @code{cvs kserver}, in which case it
+does an initial Kerberos authentication on stdin. If the authentication
+succeeds, it subsequently runs identically to @code{cvs server}.
+
+The current server implementation can use up huge amounts of memory
+when transmitting a lot of data over a slow link (i.e. the network is
+slower than the server can generate the data). There is some
+experimental code (see @code{SERVER_FLOWCONTROL} in options.h) which
+should help significantly.
+
+@node Protocol Notes
+@chapter Notes on the Protocol
+
+A number of enhancements are possible:
+
+@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, which would double client
+disk space requirements or require coordination with editors (e.g. maybe
+it could use emacs numbered backups). This would also allow local
+operation of @code{cvs diff} without arguments.
+
+@item
+Have the client keep a copy of some part of the repository. This allows
+all of @code{cvs diff} and large parts of @code{cvs update} and
+@code{cvs ci} to be local. The local copy could be made consistent with
+the master copy at night (but if the master copy has been updated since
+the latest nightly re-sync, then it would read what it needs to from the
+master).
+
+@item
+Provide encryption using kerberos.
+
+@item
+The current procedure for @code{cvs update} is highly sub-optimal if
+there are many modified files. One possible alternative would be to
+have the client send a first request without the contents of every
+modified file, then have the server tell it what files it needs. Note
+the server needs to do the what-needs-to-be-updated check twice (or
+more, if changes in the repository mean it has to ask the client for
+more files), because it can't keep locks open while waiting for the
+network. Perhaps this whole thing is irrelevant if client-side
+repositories are implemented, and the rcsmerge is done by the client.
+@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 rsh
+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 the kerberized client and server should be changed to use port
+2401 (see below), and send a different string in place of @samp{BEGIN
+AUTH REQUEST} to identify the authentication method in use. However,
+noone has yet gotten around to implementing this.
+
+@item pserver
+The password authenticated 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, sends the string @samp{BEGIN AUTH REQUEST}, a linefeed, the
+cvs root, a linefeed, the username, a linefeed, the password trivially
+encoded (see scramble.c in the cvs sources), a linefeed, the string
+@samp{END AUTH REQUEST}, and a linefeed. The server responds with
+@samp{I LOVE YOU} and a linefeed if the authentication is successful or
+@samp{I HATE YOU} and a linefeed if the authentication fails. After
+receiving @samp{I LOVE YOU}, the client proceeds with the cvs protocol.
+If the client wishes to merely authenticate without starting the cvs
+protocol, the procedure is the same, except @samp{BEGIN AUTH REQUEST} is
+replaced with @samp{BEGIN VERIFICATION REQUEST}, @samp{END AUTH REQUEST}
+is replaced with @samp{END VERIFICATION REQUEST}, and upon receipt of
+@samp{I LOVE YOU} the connection is closed rather than continuing.
+@end table
+
+@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.
+
+@menu
+* Entries Lines::
+* Modes::
+* Filenames:: Conventions regarding filenames
+* Requests::
+* Responses::
+* Example::
+* Requirements::
+@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.
+
+@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.
+
+@node Modes
+@section 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).
+
+@node Requests
+@section Requests
+
+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
+newline, 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}. 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 newline iff that file (or its
+compressed form) ends with a newline.
+
+@table @code
+@item Root @var{pathname} \n
+Response expected: no. Tell the server which @code{CVSROOT} to use.
+@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.
+
+@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 Repository @var{repository} \n
+Response expected: no. Tell the server what repository to use. This
+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 a
+@code{Repository } for each directory in which there will be an
+@code{Entry } or @code{Modified }, and then a final @code{Repository }
+for the original directory, then the command.
+
+@item Directory @var{local-directory} \n
+Additional data: @var{repository} \n. This is like @code{Repository},
+but the local name of the directory may differ from the repository name.
+If the client uses this request, it affects the way the server returns
+pathnames; see @ref{Responses}. @var{local-directory} is relative to
+the top level at which the command is occurring (i.e. the last
+@code{Directory} or @code{Repository} which is sent before the command).
+
+@item Max-dotdot @var{level} \n
+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{Repository} or @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{Repository} has a sticky tag or date @var{tagspec}.
+The first character of @var{tagspec} is @samp{T} for a tag, or @samp{D}
+for a date. The remainder of @var{tagspec} contains the actual tag or
+date.
+
+@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{Repository}. 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{Unchanged}, or @code{Lost} for that
+file the meaning depends on whether @code{UseUnchanged} has been sent;
+if it has been it means the file is lost, if not it means the file is
+unchanged.
+
+@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
+relative to the most recent repository sent with @code{Repository}. 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 Lost @var{filename} \n
+Response expected: no. Tell the server that @var{filename} no longer
+exists. The name is relative to the most recent repository sent with
+@code{Repository}. This is used for any case in which @code{Entry} is
+being sent but the file no longer exists. If the client has issued the
+@code{UseUnchanged} request, then this request is not used.
+
+@item Unchanged @var{filename} \n
+Response expected: no. Tell the server that @var{filename} has not been
+modified in the checked out directory. The name is relative to the most
+recent repository sent with @code{Repository}. This request can only be
+issued if @code{UseUnchanged} has been sent.
+
+@item UseUnchanged \n
+Response expected: no. Tell the server that the client will be
+indicating unmodified files with @code{Unchanged}, and that files for
+which no information is sent are nonexistent on the client side, not
+unchanged. This is necessary for correct behavior since only the server
+knows what possible files may exist, and thus what files are
+nonexistent.
+
+@item Notify @var{filename} \n
+Tell the server that a @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. Response expected: no. 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 or @samp{U} for
+unedit, @var{time} is the time at which the edit or unedit took place,
+@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 to set; if it is followed by \t then the tab and the
+rest of the line are ignored.
+
+@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.
+
+@item Case \n
+Tell the server that filenames should be matched against ignore patterns
+in a case-insensitive fashion. Note that this does not apply to other
+comparisons---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.
+
+@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
+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 Set @var{variable}=@var{value} \n
+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.
+
+@item co \n
+@itemx ci \n
+@itemx diff \n
+@itemx tag \n
+@itemx status \n
+@itemx log \n
+@itemx add \n
+@itemx remove \n
+@itemx rdiff \n
+@itemx rtag \n
+@itemx admin \n
+@itemx export \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{Repository}, @code{Entry},
+@code{Modified}, or @code{Lost} requests, if they have been sent. The
+last @code{Repository} 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.
+
+@itemx init @var{root-name} \n
+Response expected: yes. If it doesn't already exist, create a @sc{cvs}
+repository @var{root-name}. The @code{Root} request need not have been
+previously sent.
+
+@itemx update \n
+Response expected: yes. Actually do a @code{cvs update} command. This
+uses any previous @code{Argument}, @code{Repository}, @code{Entry},
+@code{Modified}, or @code{Lost} requests, if they have been sent. The
+last @code{Repository} 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{Repository}, @code{Entry},
+@code{Modified}, or @code{Lost} requests, if they have been sent. The
+last @code{Repository} sent specifies the working directory at the time
+of the operation. 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 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{Repository}, @code{Entry}, @code{Modified}, or @code{Lost}
+requests, if they have been sent. The last @code{Repository} 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
+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
+This request asks the server to filter files it sends to the client
+through the @samp{gzip} program, using the specified level of
+compression. If this request is not made, the server must not do any
+compression.
+
+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 @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 command which doesn't expect a response produced an error.
+@end table
+
+When the client is done, it drops the connection.
+
+@node Responses
+@section Responses
+
+After a command which expects a response, the server sends however many
+of the following responses are appropriate. Pathnames are of the actual
+files operated on (i.e. they do not contain @samp{,v} endings), and are
+suitable for use in a subsequent @code{Repository} request. However, if
+the client has used the @code{Directory} request, then it is instead 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
+newline and a repository name (the pathname which is sent if
+@code{Directory} is not used). Then the slash and the filename. 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
+
+Any response always ends with @samp{error} or @samp{ok}. This indicates
+that the response is over.
+
+@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.
+
+@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.
+
+@item Patched @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 a patch produced by @samp{diff -u}. This client must
+apply this patch, using the @samp{patch} program, 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 Mode @var{mode} \n
+This @var{mode} applies to the next file mentioned in
+@code{Checked-in}. It does not apply to any request which follows a
+@code{Checked-in}, @code{New-entry}, @code{Updated}, @code{Merged}, or
+@code{Patched} response.
+
+@item Checksum @var{checksum}\n
+The @var{checksum} applies to the next file sent over via
+@code{Updated}, @code{Merged}, or @code{Patched}. 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. 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}.
+
+@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 first character
+of @var{tagspec} is @samp{T} for a tag, or @samp{D} for a date. 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 M @var{text} \n
+A one-line message for the user.
+
+@item E @var{text} \n
+Same as @code{M} but send to stderr not stdout.
+
+@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.
+
+@item ok \n
+The command completed successfully.
+@end table
+
+@node Example
+@section Example
+
+Lines beginning with @samp{c>} are sent by the client; lines beginning
+with @samp{s>} are sent by the server; lines beginning with @samp{#} are
+not part of the actual exchange.
+
+@example
+c> Root /rel/cvsfiles
+# In actual practice the lists of valid responses and requests would
+# be longer
+c> Valid-responses Updated Checked-in M ok error
+c> valid-requests
+s> Valid-requests Root co Modified Entry Repository ci Argument Argumentx
+s> ok
+# cvs co devo/foo
+c> Argument devo/foo
+c> co
+s> Updated /rel/cvsfiles/devo/foo/foo.c
+s> /foo.c/1.4/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993//
+s> 26
+s> int mein () @{ abort (); @}
+s> Updated /rel/cvsfiles/devo/foo/Makefile
+s> /Makefile/1.2/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993//
+s> 28
+s> foo: foo.c
+s> $(CC) -o foo $<
+s> ok
+# In actual practice the next part would be a separate connection.
+# Here it is shown as part of the same one.
+c> Repository /rel/cvsfiles/devo/foo
+# foo.c relative to devo/foo just set as Repository.
+c> Entry /foo.c/1.4/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993//
+c> Entry /Makefile/1.2/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993//
+c> Modified foo.c
+c> 26
+c> int main () @{ abort (); @}
+# cvs ci -m <log message> foo.c
+c> Argument -m
+c> Argument Well, you see, it took me hours and hours to find this typo and I
+c> Argumentx searched and searched and eventually had to ask John for help.
+c> Argument foo.c
+c> ci
+s> Checked-in /rel/cvsfiles/devo/foo/foo.c
+s> /foo.c/1.5/ Mon Apr 19 15:54:22 CDT 1993//
+s> M Checking in foo.c;
+s> M /cygint/rel/cvsfiles/devo/foo/foo.c,v <-- foo.c
+s> M new revision: 1.5; previous revision: 1.4
+s> M done
+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 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: Root, Valid-responses, valid-requests,
+Repository, Entry, Modified, Argument, Argumentx, ci, co, update.
+Responses: ok, error, Valid-requests, Checked-in, Updated, Merged,
+Removed, M, E.
+
+Failure to support the Directory, UseUnchanged, and Unchanged requests
+is deprecated. CVS 1.5 and later have supported these requests and in
+the future it will be considered reasonable behavior to completely
+fail to work with an implementation which attempts to not support
+them. Support for the Repository and Lost requests is deprecated; CVS
+clients 1.5 and later will not use them if communicating with a server
+which supports Directory and UseUnchanged.
+
+@bye
diff --git a/contrib/cvs/install-sh b/contrib/cvs/install-sh
new file mode 100755
index 0000000..ab74c88
--- /dev/null
+++ b/contrib/cvs/install-sh
@@ -0,0 +1,238 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+#
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+tranformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+ case $1 in
+ -c) instcmd="$cpprog"
+ shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -m) chmodcmd="$chmodprog $2"
+ shift
+ shift
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd="$stripprog"
+ shift
+ continue;;
+
+ -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+ shift
+ continue;;
+
+ -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+ shift
+ continue;;
+
+ *) if [ x"$src" = x ]
+ then
+ src=$1
+ else
+ # this colon is to work around a 386BSD /bin/sh bug
+ :
+ dst=$1
+ fi
+ shift
+ continue;;
+ esac
+done
+
+if [ x"$src" = x ]
+then
+ echo "install: no input file specified"
+ exit 1
+else
+ true
+fi
+
+if [ x"$dir_arg" != x ]; then
+ dst=$src
+ src=""
+
+ if [ -d $dst ]; then
+ instcmd=:
+ else
+ instcmd=mkdir
+ fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+ if [ -f $src -o -d $src ]
+ then
+ true
+ else
+ echo "install: $src does not exist"
+ exit 1
+ fi
+
+ if [ x"$dst" = x ]
+ then
+ echo "install: no destination specified"
+ exit 1
+ else
+ true
+ fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+ if [ -d $dst ]
+ then
+ dst="$dst"/`basename $src`
+ else
+ true
+ fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+# this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+ pathcomp="${pathcomp}${1}"
+ shift
+
+ if [ ! -d "${pathcomp}" ] ;
+ then
+ $mkdirprog "${pathcomp}"
+ else
+ true
+ fi
+
+ pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+ $doit $instcmd $dst &&
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+ if [ x"$transformarg" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ dstfile=`basename $dst $transformbasename |
+ sed $transformarg`$transformbasename
+ fi
+
+# don't allow the sed command to completely eliminate the filename
+
+ if [ x"$dstfile" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ true
+ fi
+
+# Make a temp file name in the proper directory.
+
+ dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+ $doit $instcmd $src $dsttmp &&
+
+ trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing. If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+ $doit $rmcmd -f $dstdir/$dstfile &&
+ $doit $mvcmd $dsttmp $dstdir/$dstfile
+
+fi &&
+
+
+exit 0
diff --git a/contrib/cvs/lib/ChangeLog b/contrib/cvs/lib/ChangeLog
new file mode 100644
index 0000000..900d6d4
--- /dev/null
+++ b/contrib/cvs/lib/ChangeLog
@@ -0,0 +1,448 @@
+Thu Apr 25 18:26:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * getdate.y (get_date): Set Start from nowtime, not now->time,
+ which may not be set.
+ * getdate.c: Regenerated.
+
+Wed Apr 10 17:55:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * getdate.y (get_date): Use a time_t variable rather than a field
+ in a struct timeb. Works around Solaris compiler bug. Sure, it
+ is a compiler bug, but the workaround is completely painless.
+ * getdate.c: Regenerated.
+
+Fri Mar 22 11:17:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * system.h: If EXIT_FAILURE is not defined by stdlib.h, define it
+ ourself.
+
+Thu Mar 14 16:27:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * system.h: Remove alloca cruft.
+
+Wed Feb 28 03:16:48 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * build_lib.com: Changed definition of symbol CC to search
+ for include files in [-.VMS] so VMS config.h can be picked
+ up without copying.
+
+Tue Feb 27 21:26:34 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * build_lib.com: Added. DCL File to build contents of [.lib]
+
+Tue Feb 27 21:18:38 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * system.h: added an existence_error macro check for EVMSERR
+ necessary for happiness under VMS
+
+Thu Feb 22 22:30:04 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (OBJECTS): Remove @ALLOCA@
+ (SOURCES): Remove alloca.c
+ * alloca.c: Removed.
+ * regex.c (REGEX_MALLOC): Define.
+
+Thu Feb 15 14:00:00 Jim Kingdon <kingdon@cyclic.com>
+
+ * vasprintf.c: Declare abs().
+
+Wed Feb 14 14:48:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vasprintf.c (int_vasprintf): Don't cast arguments to memcpy.
+ * vasprintf.c, strtoul.c: Don't include ansidecl.h. Do include
+ config.h if HAVE_CONFIG_H (for const).
+ * strtoul.c: Change CONST to const.
+
+Tue Feb 13 20:04:39 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * strtoul.c: Added (needed by vasprintf.c, and missing on SunOS4).
+ * Makefile.in (SOURCES): Add strtoul.c.
+
+Mon Feb 12 10:04:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vasprintf.c: Added (same contents as before).
+ * Makefile.in (SOURCES): Add vasprintf.c.
+
+Thu Feb 1 14:33:17 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * Makefile.in (xlint): new rule; does nothing, as I'm not sure
+ running lint is actually advisable in here, but the top-level
+ Makefile thinks it can `make xlint' here.
+
+Thu Feb 1 15:07:42 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * getopt.c: Remove rcsid.
+
+Tue Jan 30 18:20:27 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * getline.c: Don't define NDEBUG.
+ (getstr): Rewrite assertions in a way which should stay clear of
+ signed/unsigned problems and compiler warnings thereof.
+
+Thu Jan 25 00:14:06 1996 Jim Kingdon <kingdon@beezley.cyclic.com>
+
+ * yesno.c (yesno): fflush stdout as well as stderr.
+
+Wed Jan 3 18:16:50 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sighandle.c (SIG_register): Use memset not bzero.
+ * system.h: Remove defines for index, rindex, bcmp, and bzero.
+ All the calls to those functions are gone from CVS.
+
+Tue Jan 2 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ Visual C++ lint:
+ * sighandle.c: Prototype SIG_handle and SIG_defaults.
+ Use SIG_ERR where appropriate.
+
+Mon Dec 18 10:15:05 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rename.c: Check ENOENT rather than existence_error. The latter
+ is undefined in this file, and including system.h is said to cause
+ (unspecified) problems.
+
+Sun Dec 17 23:58:06 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vasprintf.c: Removed (it is no longer used).
+ * Makefile.in (SOURCES): Remove vasprintf.c.
+
+Sat Dec 16 17:18:33 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vasprintf.c: Added.
+ * Makefile.in (SOURCES): Add vasprintf.c
+
+Mon Dec 4 10:54:04 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * getdate.c: Remove #line directives. I know, this is a kludge,
+ but Visual C++ 2.1 seems to require it (why, I have no idea. It
+ has no trouble with the #line directives in getdate in CVS 1.6).
+
+Sat Nov 18 16:20:37 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * rename.c: same.
+
+ * mkdir.c: Use new macro `existence_error', instead of comparing
+ errno to ENOENT directly.
+
+ * system.h (existence_error): new macro, tries to portably ask if
+ errno represents a file-not-exist error.
+
+Fri Nov 17 20:08:58 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * system.h (NEED_DECOY_PERMISSIONS): moved this section to where
+ it belongs, duh.
+
+ * getdate.c: if STDC_HEADERS, then just include <stdlib.h> instead
+ of declaring malloc() and realloc() to be char *.
+
+ * system.h: ifdef NEED_DECOY_PERMISSIONS, then define the S_I*
+ permission masks for USR, GRP, and OTH in terms of the simpler
+ OS/2 masks.
+
+Wed Nov 15 15:36:03 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * system.h: ifdef USE_OWN_TCPIP_H, then include "tcpip.h". Only
+ OS/2 does this right now.
+
+Tue Nov 14 18:44:57 1995 Greg A. Woods <woods@most.weird.com>
+
+ * getdate.c: OK, this one is from SunOS-4.1 yacc and may be more
+ portable -- at least it compiles silently here! ;-)
+
+Mon Nov 13 03:53:45 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * fnmatch.c: conform to 80 column standard (yes, I'm a pedant).
+
+Wed Nov 8 11:10:59 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * system.h (STAT_MACROS): ifdef S_IFMT, then use it as before; but
+ if it's not defined, then just do a single mask and assume
+ acceptance any of non-zero result. Norbert, I trust you'll let me
+ know if this is unsatisfactory. :-)
+ Ifdef HAVE_SYS_UTIME_H, then include <sys/utime.h>. Only OS/2
+ defines this right now.
+
+Wed Nov 8 13:18:51 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * valloc.c: omit malloc declaration (it's already in system.h
+ which is included and conflicts with <stdlib.h> on some
+ systems).
+
+Tue Nov 7 19:38:48 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * system.h (STAT_MACROS_BROKEN): undo previous change, because
+ else all regular files will be identified as links (the mask for
+ links is S_IFREG|S_IFCHR).
+
+Mon Nov 6 19:20:56 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * system.h (STAT_MACROS_BROKEN): in defining the S_IF* macros,
+ don't fold to 1 or 0 by first masking with S_IFMT; not all
+ systems have that macro, and anyway it's only necessary that we
+ return non-zero.
+
+Fri Oct 27 13:43:35 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * save-cwd.c: use __PROTO instead of __P (see below).
+
+ * getline.h (__PROTO): same as below.
+
+ * save-cwd.h (__PROTO): replaces __P. New name, so don't ask if
+ already defined. The conflict was that OS/2 w/ IBM C/C++ uses
+ `__P' for something else, in <ctype.h> of all places.
+
+ * system.h: do nothing about alloca ifdef ALLOCA_IN_STDLIB (see
+ ../src/ChangeLog).
+
+Tue Oct 24 13:01:25 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * wait.h: include sys/resource.h if available. This is needed at
+ least under AIX-3.2 where <sys/wait.h> doesn't include it.
+
+Mon Oct 23 17:39:11 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * valloc.c (valloc): change parameter definition
+
+Sun Oct 22 14:15:44 1995 Jim Meyering (meyering@comco.com)
+
+ * getline.c, getline.h: New files.
+ * Makefile.in (SOURCES, OBJECTS, HEADERS): Add getline.c, getline.o,
+ and getline.h, respectively.
+
+Tue Oct 10 18:01:50 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * Makefile.in (cvs_srcdir): define cvs_srcdir to be ../src, then
+ include it with -I so save_cwd.c can find error.h (for example).
+
+Sun Oct 8 12:27:57 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * system.h: define POSIX_SIGNALS or BSD_SIGNALS if configure has
+ located all the necessary functions for each "type".
+ * sighandle.c: detect/use POSIX/BSD reliable signals (especially
+ for blocking signals in critical sections). Helps prevent stray
+ locks on interruption.
+
+Mon Oct 2 18:11:23 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * system.h: Doc fix.
+
+Mon Oct 2 18:10:35 1995 Larry Jones <larry.jones@sdrc.com>
+
+ * regex.c: compile 4.2 BSD compatible functions even when
+ _POSIX_SOURCE is defined since we need them and we wouldn't be
+ compiling this file unless they don't exist.
+
+Mon Oct 2 10:32:20 1995 Michael Finken <finken@conware.de>
+
+ * strstr.c (strstr): new file and func.
+
+ * Makefile.in (SOURCES): added strstr.c.
+
+Sun Oct 1 21:03:40 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * regex.c: reverted below change.
+
+Thu Sep 28 13:37:04 1995 Larry Jones <larry.jones@sdrc.com>
+
+ * regexp.c: check for ISC.
+
+Thu Sep 7 19:18:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * save-cwd.c: #include <direct.h> and <io.h>, on systems that
+ have them.
+
+ * getopt.c (_getopt_internal): Cast the return value of strlen,
+ which is unsigned, before comparing it with the difference between
+ two pointers, which is unsigned.
+
+Thu Aug 31 11:31:42 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * getdate.y [STDC_HEADERS]: #include <stdlib.h>, for abort.
+ [HAVE_ALLOCA_H]: #include <alloca.h>, for alloca on Windows NT.
+
+Wed Aug 30 18:48:44 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * system.h [HAVE_IO_H]: #include <io.h>, for Windows NT.
+ [HAVE_DIRECT_H]: #include <direct.h>, for Windows NT.
+ (CVS_MKDIR, FOLD_FN_CHAR, fnfold, fncmp, ISDIRSEP, OPEN_BINARY,
+ FOPEN_BINARY_READ, FOPEN_BINARY_WRITE): New macros/functions, for
+ use in system-sensitive code.
+
+ * regex.c (re_set_registers): start and end are pointers, not
+ integers. Cast the initializing value appropriately.
+
+ * getopt.c [HAVE_STRING_H]: #include <string.h>, to avoid
+ warnings.
+
+ * fnmatch.c (FOLD_FN_CHAR): Give this a dummy #definition if
+ config.h didn't #define it.
+ (fnmatch): Pass filename characters through FOLD_FN_CHAR before
+ comparing them.
+
+ * argmatch.c: #include <sys/types.h>.
+ (argmatch): Declare arglen to be a size_t, rather than an int,
+ to avoid signed/unsigned comparison "problems".
+
+ * .cvsignore: Remove getdate.c from this file. We want to
+ distribute it, for systems that don't have a Yacc-equivalent
+ installed (like Windows NT).
+
+Sat Aug 19 22:00:51 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * error.c: Don't #define CVS_SUPPORT here. config.h takes care of
+ that for us.
+ [CVS_SUPPORT] (error_use_protocol): New variable, with apology.
+ (error): If error_use_protocol is set, report errors using the
+ client/server protocol.
+ * error.h [CVS_SUPPORT]: Extern decl for error_use_protocol.
+
+Fri Aug 4 00:01:24 1995 Jim Meyering (meyering@comco.com)
+
+ * xgetwd.c: Don't declare free. A K&R style declaration gets
+ a conflict on some Sun systems when compiling with acc.
+
+ * save-cwd.c: New file.
+ * save-cwd.h: New file.
+ * Makefile.in (SOURCES): Add save-cwd.c
+ (OBJECTS): Add save-cwd.o.
+ (HEADERS): Add save-cwd.h.
+
+Thu Aug 3 00:55:54 1995 Jim Meyering (meyering@comco.com)
+
+ * error.h: New file.
+ * Makefile.in (HEADERS): Add error.h.
+
+Sat Jul 29 15:53:55 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (SOURCES): Add getdate.c.
+
+Thu Jul 27 09:11:41 1995 Robert Lipe <robertl@rjlhome.arnet.com>
+
+ * system.h: Check for PATHSIZE before falling back to _POSIX_PATH_MAX.
+
+Thu Jul 20 12:38:03 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * error.c: Instead of calling cvs functions to clean up, allow cvs
+ to register a callback via error_set_cleanup. Avoids hassles with
+ include files and SERVER_SUPPORT and so on.
+
+Tue Jul 18 21:18:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * system.h: Include <sys/param.h> only if HAVE_SYS_PARAM_H
+ is #defined. We've added a test to configure.in to #define this
+ on most systems.
+
+Thu Jul 13 11:22:21 1995 Jim Meyering (meyering@comco.com)
+
+ * xgetwd.c: New file.
+ * Makefile.in (SOURCES): Add xgetwd.c
+ (OBJECTS): Add xgetwd.o.
+
+Wed Jul 12 09:18:49 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (OBJECTS): Remove fnmatch.o. Now configure adds it
+ to LIBOBJS when necessary.
+
+Fri Jun 30 16:27:18 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * rename.c (rename): If MVDIR is not defined, just give an error
+ on attempt to rename a directory.
+
+Thu Jun 29 00:46:31 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * system.h: Check HAVE_SYS_TIMEB_H not non-existent HAVE_TIMEB_H.
+
+ * system.h: Don't define alloca if it is already defined.
+
+Wed Jun 28 15:24:51 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * system.h: If NeXT, define utimbuf ourself.
+
+Mon May 29 22:32:40 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * system.h: Handle time and directory headers as recommended in
+ the autoconf manual.
+ Undefine the S_FOO() macros if STAT_MACROS_BROKEN is set.
+ Don't define mode_t, as it is handled by config.h.
+
+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:49:25 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (SOURCES, OBJECTS): Updated.
+ (HEADERS): New variable.
+ (DISTFILES): Updated.
+ (dist-dir): Renamed from dist; changed to work with DISTDIR
+ variable passed from parent.
+
+Wed Feb 8 06:37:53 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * system.h (S_IRUSR et al): Define if not already defined.
+
+ * waitpid.c [HAVE_CONFIG_H]: Include "config.h".
+ (ualloc): Return OLDPTR rather than running off the end.
+
+Mon Aug 22 22:48:19 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * error.c (strerror): Replaced conditional static definition
+ (always used, since the condition variable was never set) with an
+ extern declaration, since it's provided by libc or strerror.c.
+
+Wed Aug 10 14:54:25 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * Makefile.in (SOURCES): Add waitpid.c.
+ * waitpid.c: New file.
+
+Tue Aug 9 16:00:12 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * md5.h (uint32): If SIZEOF_LONG isn't 4, don't define this to be
+ "unsigned long"; try SIZEOF_INT and "unsigned int", otherwise
+ complain.
+
+ * md5.c: Include config.h.
+ (const): Don't bother defining here, config.h should take care of
+ it.
+
+ * valloc.c (malloc): Declare.
+
+Fri Jul 15 12:57:20 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * getopt.c: Do not include <stdlib.h> unless __GNU_LIBRARY__ is
+ defined. On Irix 5.2, <stdlib.h> includes <getopt.h>, which
+ causes a multiple definition of struct option.
+
+Fri Jul 8 10:04:59 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * md5.h, md5.c: Remove ANSI-isms.
+
+Thu Jul 7 20:24:18 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * md5.h, md5.c: New files.
+ * Makefile.in (SOURCES): Add md5.c.
+ (OBJECTS): Add md5.o.
+ (DISTFILES): Add md5.h.
+ (md5.o): New target; depend upon md5.h.
+
+Fri May 27 18:15:34 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * valloc.c: New file.
+
+Tue May 17 08:18:26 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * error.c (error, fperror): If server_active, call server_cleanup
+ as well as Lock_Cleanup.
+
+Thu Jan 6 13:45:04 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * system.h: Fix Dec 27 change to work correctly. Makes Sep 9
+ change unnecessary, so backed that one out. Never define PATH_MAX
+ in terms of pathconf, because that doesn't produce a constant, and
+ PATH_MAX is used to set array sizes.
+
+Mon Dec 27 14:22:07 1993 Mark Eichin (eichin@cygnus.com)
+
+ * system.h: don't touch PATH_MAX or MAXPATHLEN if *both* of them
+ are already defined, as one may be defined in terms of the other.
diff --git a/contrib/cvs/lib/ChangeLog.fsf b/contrib/cvs/lib/ChangeLog.fsf
new file mode 100644
index 0000000..176d791
--- /dev/null
+++ b/contrib/cvs/lib/ChangeLog.fsf
@@ -0,0 +1,90 @@
+Thu Sep 15 00:18:26 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * system.h: remove a bunch of "extern int " declarations of system
+ functions (could conflict with vendor header files, and didn't
+ do anything *too* useful to begin with).
+
+ * Makefile.in: update getdate.y message (now has 10 s/r conflicts)
+
+Wed Sep 14 22:12:21 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * strerror.c: more complete, from the Cygnus libiberty package
+
+ * error.c (strerror): removed, functionality is in strerror.c
+
+ * cvs.h: remove duplicate prototype for Reader_Lock
+ * history.c: printf argument mismatch
+ (Both fixes thanks to J.T. Conklin (jtc@cygnus.com)
+
+Sat Jul 30 13:50:11 1994 david d `zoo' zuhn (zoo@monad.armadillo.com)
+
+ * getopt1.c, getopt.c, getopt.h, getdate.y: latest versions from FSF
+
+Wed Jul 13 22:11:17 1994 david d `zoo' zuhn (zoo@monad.armadillo.com)
+
+ * system.h: don't set PATH_MAX to pathconf(), since PATH_MAX is
+ used to size arrays. (thanks to kingdon@cygnus.com)
+
+ * getopt1.c: remove #ifdef __STDC__ around const usages (which
+ isn't correct and weren't complete)
+
+Wed Apr 20 14:57:16 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * getopt.h: Prevent multiple inclusion.
+
+Tue Jan 25 17:34:42 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * Makefile.in: make sure that no blank lines are in the $(OBJECTS)
+ list (from Brad Figg)
+
+Mon Jan 24 12:27:13 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * system.h: remove alloca checks (added to src/cvs.h); revamped
+ the MAXPATHLEN and PATH_MAX tests (from Brad Figg
+ <bradf@wv.MENTORG.COM>); handle index,rindex,bcmp,bzero better
+ (don't redefine if already defined); added S_IWRITE, S_IWGRP,
+ S_IWOTH definitions (header file reorganization)
+
+ * strippath.c: use strchr, not index
+
+ * getopt1.c: match prototypes when __STDC__ compiler (lint fixes)
+
+ * getdate.c: alloca checks for when using bison
+
+ * Makefile.in: added CC and YACC definitions; use YACC not BISON;
+ better getdate.c tests (also from Brad Figg)
+
+Sat Dec 18 00:55:43 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead
+
+ * memmove.c: new file, implements memmove in terms of bcopy
+
+ * wait.h: include <sys/wait.h> if HAVE_SYS_WAIT_H, not if POSIX
+
+Thu Sep 9 18:02:11 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ * system.h: only #undef PATH_MAX if not on an Alpha. The #undef
+ causes problems with the Alpha C compiler.
+
+Thu Apr 8 12:39:56 1993 Ian Lance Taylor (ian@cygnus.com)
+
+ * system.h: Removed several incorrect declarations which fail
+ on Solaris.
+
+Wed Jan 20 17:57:24 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * system.h: add externs for sun4 so that gcc -Wall becomes useful
+ again.
+
+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.
+
+Sat Dec 28 02:42:06 1991 K. Richard Pixley (rich at cygnus.com)
+
+ * mkdir.c, rename.c: change fork() to vfork().
+
+
diff --git a/contrib/cvs/lib/Makefile.in b/contrib/cvs/lib/Makefile.in
new file mode 100644
index 0000000..9fb93f3
--- /dev/null
+++ b/contrib/cvs/lib/Makefile.in
@@ -0,0 +1,164 @@
+# Makefile for library files used by GNU CVS.
+# Do not use this makefile directly, but only from `../Makefile'.
+# Copyright (C) 1986, 1988-1994 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# $CVSid: @(#)Makefile.in 1.21 94/09/24 $
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+cvs_srcdir = @top_srcdir@/src
+VPATH = @srcdir@
+
+SHELL = /bin/sh
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+SOURCES = \
+ argmatch.c \
+ dup2.c \
+ fnmatch.c \
+ ftruncate.c \
+ getdate.c \
+ getdate.y \
+ getline.c \
+ getopt.c \
+ getopt1.c \
+ getwd.c \
+ hostname.c \
+ md5.c \
+ memmove.c \
+ mkdir.c \
+ regex.c \
+ rename.c \
+ savecwd.c \
+ sighandle.c \
+ strdup.c \
+ strstr.c \
+ strerror.c \
+ strippath.c \
+ stripslash.c \
+ strtoul.c \
+ valloc.c \
+ vasprintf.c \
+ waitpid.c \
+ xgetwd.c \
+ yesno.c
+
+HEADERS = getline.h getopt.h fnmatch.h regex.h system.h wait.h md5.h savecwd.h
+
+OBJECTS = \
+ @LIBOBJS@ \
+ argmatch.o \
+ getline.o \
+ getopt.o \
+ getopt1.o \
+ md5.o \
+ savecwd.o \
+ sighandle.o \
+ strippath.o \
+ stripslash.o \
+ xgetwd.o \
+ yesno.o \
+ getdate.o
+
+DISTFILES = \
+ .cvsignore ChangeLog ChangeLog.fsf Makefile.in \
+ ${SOURCES} ${HEADERS}
+
+DEFS = @DEFS@
+RANLIB = @RANLIB@
+
+CC = @CC@
+CFLAGS = -g
+CPPFLAGS=
+
+YACC = @YACC@
+
+.c.o:
+ $(CC) $(CPPFLAGS) -I.. -I$(srcdir) -I$(cvs_srcdir) \
+ $(DEFS) $(CFLAGS) -c $<
+
+all: libcvs.a
+.PHONY: all
+
+install: all
+.PHONY: install
+
+tags: $(DISTFILES)
+ ctags `for i in $(DISTFILES); do echo $(srcdir)/$$i; done`
+
+TAGS: $(DISTFILES)
+ etags `for i in $(DISTFILES); do echo $(srcdir)/$$i; done`
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+clean:
+ rm -f *.a *.o
+.PHONY: clean
+
+distclean: clean
+ rm -f tags TAGS Makefile
+.PHONY: distclean
+
+realclean: distclean
+ rm -f *.tab.c getdate.c
+.PHONY: realclean
+
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+.PHONY: dist-dir
+
+libcvs.a: $(OBJECTS)
+ $(AR) cr $@ $(OBJECTS)
+ -$(RANLIB) $@
+
+getdate.c: getdate.y
+ @echo expect 10 shift/reduce conflicts
+ $(YACC) $(srcdir)/getdate.y
+ -@if test -f y.tab.c; then \
+ mv y.tab.c getdate.c ;\
+ else \
+ if test -f getdate.tab.c ; then \
+ mv getdate.tab.c getdate.c ; \
+ else \
+ echo '*** Unable to create getdate.c' ;\
+ fi ;\
+ fi
+
+fnmatch.o: fnmatch.h
+getopt1.o: getopt.h
+regex.o: regex.h
+getwd.o: system.h
+md5.o: md5.h
+
+xlint:
+ @echo xlint does nothing
+
+subdir = lib
+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/contrib/cvs/lib/argmatch.c b/contrib/cvs/lib/argmatch.c
new file mode 100644
index 0000000..cc360ee
--- /dev/null
+++ b/contrib/cvs/lib/argmatch.c
@@ -0,0 +1,89 @@
+/* argmatch.c -- find a match for a string in an array
+ Copyright (C) 1990 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Written by David MacKenzie <djm@ai.mit.edu> */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#ifdef STDC_HEADERS
+#include <string.h>
+#endif
+
+extern char *program_name;
+
+/* If ARG is an unambiguous match for an element of the
+ null-terminated array OPTLIST, return the index in OPTLIST
+ of the matched element, else -1 if it does not match any element
+ or -2 if it is ambiguous (is a prefix of more than one element). */
+
+int
+argmatch (arg, optlist)
+ char *arg;
+ char **optlist;
+{
+ int i; /* Temporary index in OPTLIST. */
+ size_t arglen; /* Length of ARG. */
+ int matchind = -1; /* Index of first nonexact match. */
+ int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */
+
+ arglen = strlen (arg);
+
+ /* Test all elements for either exact match or abbreviated matches. */
+ for (i = 0; optlist[i]; i++)
+ {
+ if (!strncmp (optlist[i], arg, arglen))
+ {
+ if (strlen (optlist[i]) == arglen)
+ /* Exact match found. */
+ return i;
+ else if (matchind == -1)
+ /* First nonexact match found. */
+ matchind = i;
+ else
+ /* Second nonexact match found. */
+ ambiguous = 1;
+ }
+ }
+ if (ambiguous)
+ return -2;
+ else
+ return matchind;
+}
+
+/* Error reporting for argmatch.
+ KIND is a description of the type of entity that was being matched.
+ VALUE is the invalid value that was given.
+ PROBLEM is the return value from argmatch. */
+
+void
+invalid_arg (kind, value, problem)
+ char *kind;
+ char *value;
+ int problem;
+{
+ fprintf (stderr, "%s: ", program_name);
+ if (problem == -1)
+ fprintf (stderr, "invalid");
+ else /* Assume -2. */
+ fprintf (stderr, "ambiguous");
+ fprintf (stderr, " %s `%s'\n", kind, value);
+}
diff --git a/contrib/cvs/lib/dup2.c b/contrib/cvs/lib/dup2.c
new file mode 100644
index 0000000..1974383
--- /dev/null
+++ b/contrib/cvs/lib/dup2.c
@@ -0,0 +1,40 @@
+/*
+ dup2 -- 7th Edition UNIX system call emulation for UNIX System V
+
+ last edit: 11-Feb-1987 D A Gwyn
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+
+extern int close(), fcntl();
+
+int
+dup2( oldfd, newfd )
+ int oldfd; /* already-open file descriptor */
+ int newfd; /* desired duplicate descriptor */
+{
+ register int ret; /* for fcntl() return value */
+ register int save; /* for saving entry errno */
+
+ if ( oldfd == newfd )
+ return oldfd; /* be careful not to close() */
+
+ save = errno; /* save entry errno */
+ (void) close( newfd ); /* in case newfd is open */
+ /* (may have just clobbered the original errno value) */
+
+ ret = fcntl( oldfd, F_DUPFD, newfd ); /* dupe it */
+
+ if ( ret >= 0 )
+ errno = save; /* restore entry errno */
+ else /* fcntl() returned error */
+ if ( errno == EINVAL )
+ errno = EBADF; /* we think of everything */
+
+ return ret; /* return file descriptor */
+}
diff --git a/contrib/cvs/lib/fnmatch.c b/contrib/cvs/lib/fnmatch.c
new file mode 100644
index 0000000..9cb847e
--- /dev/null
+++ b/contrib/cvs/lib/fnmatch.c
@@ -0,0 +1,193 @@
+/* Copyright (C) 1992 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+/* Modified slightly by Brian Berliner <berliner@sun.com> and
+ Jim Blandy <jimb@cyclic.com> for CVS use */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Some file systems are case-insensitive. If FOLD_FN_CHAR is
+ #defined, it maps the character C onto its "canonical" form. In a
+ case-insensitive system, it would map all alphanumeric characters
+ to lower case. Under Windows NT, / and \ are both path component
+ separators, so FOLD_FN_CHAR would map them both to /. */
+#ifndef FOLD_FN_CHAR
+#define FOLD_FN_CHAR(c) (c)
+#endif
+
+/* IGNORE(@ */
+/* #include <ansidecl.h> */
+/* @) */
+#include <errno.h>
+#include <fnmatch.h>
+
+#if !defined(__GNU_LIBRARY__) && !defined(STDC_HEADERS)
+extern int errno;
+#endif
+
+/* Match STRING against the filename pattern PATTERN, returning zero if
+ it matches, nonzero if not. */
+int
+#if __STDC__
+fnmatch (const char *pattern, const char *string, int flags)
+#else
+fnmatch (pattern, string, flags)
+ char *pattern;
+ char *string;
+ int flags;
+#endif
+{
+ register const char *p = pattern, *n = string;
+ register char c;
+
+ if ((flags & ~__FNM_FLAGS) != 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ while ((c = *p++) != '\0')
+ {
+ switch (c)
+ {
+ case '?':
+ if (*n == '\0')
+ return FNM_NOMATCH;
+ else if ((flags & FNM_PATHNAME) && *n == '/')
+ return FNM_NOMATCH;
+ else if ((flags & FNM_PERIOD) && *n == '.' &&
+ (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
+ return FNM_NOMATCH;
+ break;
+
+ case '\\':
+ if (!(flags & FNM_NOESCAPE))
+ c = *p++;
+ if (*n != c)
+ return FNM_NOMATCH;
+ break;
+
+ case '*':
+ if ((flags & FNM_PERIOD) && *n == '.' &&
+ (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
+ return FNM_NOMATCH;
+
+ for (c = *p++; c == '?' || c == '*'; c = *p++, ++n)
+ if (((flags & FNM_PATHNAME) && *n == '/') ||
+ (c == '?' && *n == '\0'))
+ return FNM_NOMATCH;
+
+ if (c == '\0')
+ return 0;
+
+ {
+ char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c;
+ for (--p; *n != '\0'; ++n)
+ if ((c == '[' || *n == c1) &&
+ fnmatch(p, n, flags & ~FNM_PERIOD) == 0)
+ return 0;
+ return FNM_NOMATCH;
+ }
+
+ case '[':
+ {
+ /* Nonzero if the sense of the character class is inverted. */
+ register int not;
+
+ if (*n == '\0')
+ return FNM_NOMATCH;
+
+ if ((flags & FNM_PERIOD) && *n == '.' &&
+ (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
+ return FNM_NOMATCH;
+
+ not = (*p == '!' || *p == '^');
+ if (not)
+ ++p;
+
+ c = *p++;
+ for (;;)
+ {
+ register char cstart = c, cend = c;
+
+ if (!(flags & FNM_NOESCAPE) && c == '\\')
+ cstart = cend = *p++;
+
+ if (c == '\0')
+ /* [ (unterminated) loses. */
+ return FNM_NOMATCH;
+
+ c = *p++;
+
+ if ((flags & FNM_PATHNAME) && c == '/')
+ /* [/] can never match. */
+ return FNM_NOMATCH;
+
+ if (c == '-' && *p != ']')
+ {
+ cend = *p++;
+ if (!(flags & FNM_NOESCAPE) && cend == '\\')
+ cend = *p++;
+ if (cend == '\0')
+ return FNM_NOMATCH;
+ c = *p++;
+ }
+
+ if (*n >= cstart && *n <= cend)
+ goto matched;
+
+ if (c == ']')
+ break;
+ }
+ if (!not)
+ return FNM_NOMATCH;
+ break;
+
+ matched:;
+ /* Skip the rest of the [...] that already matched. */
+ while (c != ']')
+ {
+ if (c == '\0')
+ /* [... (unterminated) loses. */
+ return FNM_NOMATCH;
+
+ c = *p++;
+ if (!(flags & FNM_NOESCAPE) && c == '\\')
+ /* 1003.2d11 is unclear if this is right. %%% */
+ ++p;
+ }
+ if (not)
+ return FNM_NOMATCH;
+ }
+ break;
+
+ default:
+ if (FOLD_FN_CHAR (c) != FOLD_FN_CHAR (*n))
+ return FNM_NOMATCH;
+ }
+
+ ++n;
+ }
+
+ if (*n == '\0')
+ return 0;
+
+ return FNM_NOMATCH;
+}
diff --git a/contrib/cvs/lib/fnmatch.h b/contrib/cvs/lib/fnmatch.h
new file mode 100644
index 0000000..a1e4f87
--- /dev/null
+++ b/contrib/cvs/lib/fnmatch.h
@@ -0,0 +1,45 @@
+/* Copyright (C) 1992 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+#ifndef _FNMATCH_H
+
+#define _FNMATCH_H 1
+
+/* Bits set in the FLAGS argument to `fnmatch'. */
+#undef FNM_PATHNAME
+#define FNM_PATHNAME (1 << 0)/* No wildcard can ever match `/'. */
+#undef FNM_NOESCAPE
+#define FNM_NOESCAPE (1 << 1)/* Backslashes don't quote special chars. */
+#undef FNM_PERIOD
+#define FNM_PERIOD (1 << 2)/* Leading `.' is matched only explicitly. */
+#undef __FNM_FLAGS
+#define __FNM_FLAGS (FNM_PATHNAME|FNM_NOESCAPE|FNM_PERIOD)
+
+/* Value returned by `fnmatch' if STRING does not match PATTERN. */
+#undef FNM_NOMATCH
+#define FNM_NOMATCH 1
+
+/* Match STRING against the filename pattern PATTERN,
+ returning zero if it matches, FNM_NOMATCH if not. */
+#if __STDC__
+extern int fnmatch (const char *pattern, const char *string, int flags);
+#else
+extern int fnmatch ();
+#endif
+
+#endif /* fnmatch.h */
diff --git a/contrib/cvs/lib/ftruncate.c b/contrib/cvs/lib/ftruncate.c
new file mode 100644
index 0000000..13f20a3
--- /dev/null
+++ b/contrib/cvs/lib/ftruncate.c
@@ -0,0 +1,76 @@
+/* ftruncate emulations that work on some System V's.
+ This file is in the public domain. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <fcntl.h>
+
+#ifdef F_CHSIZE
+int
+ftruncate (fd, length)
+ int fd;
+ off_t length;
+{
+ return fcntl (fd, F_CHSIZE, length);
+}
+#else
+#ifdef F_FREESP
+/* The following function was written by
+ kucharsk@Solbourne.com (William Kucharski) */
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+int
+ftruncate (fd, length)
+ int fd;
+ off_t length;
+{
+ struct flock fl;
+ struct stat filebuf;
+
+ if (fstat (fd, &filebuf) < 0)
+ return -1;
+
+ if (filebuf.st_size < length)
+ {
+ /* Extend file length. */
+ if (lseek (fd, (length - 1), SEEK_SET) < 0)
+ return -1;
+
+ /* Write a "0" byte. */
+ if (write (fd, "", 1) != 1)
+ return -1;
+ }
+ else
+ {
+ /* Truncate length. */
+ fl.l_whence = 0;
+ fl.l_len = 0;
+ fl.l_start = length;
+ fl.l_type = F_WRLCK; /* Write lock on file space. */
+
+ /* This relies on the UNDOCUMENTED F_FREESP argument to
+ fcntl, which truncates the file so that it ends at the
+ position indicated by fl.l_start.
+ Will minor miracles never cease? */
+ if (fcntl (fd, F_FREESP, &fl) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+#else
+int
+ftruncate (fd, length)
+ int fd;
+ off_t length;
+{
+ return chsize (fd, length);
+}
+#endif
+#endif
diff --git a/contrib/cvs/lib/getdate.y b/contrib/cvs/lib/getdate.y
new file mode 100644
index 0000000..5787142
--- /dev/null
+++ b/contrib/cvs/lib/getdate.y
@@ -0,0 +1,1001 @@
+%{
+/*
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
+** send any email to Rich.
+**
+** This grammar has 10 shift/reduce conflicts.
+**
+** This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
+/* SUPPRESS 288 on yyerrlab *//* Label unused */
+
+#ifdef HAVE_CONFIG_H
+#if defined (emacs) || defined (CONFIG_BROKETS)
+#include <config.h>
+#else
+#include "config.h"
+#endif
+#endif
+
+/* Since the code of getdate.y is not included in the Emacs executable
+ itself, there is no need to #define static in this file. Even if
+ the code were included in the Emacs executable, it probably
+ wouldn't do any harm to #undef it here; this will only cause
+ problems if we try to write to a static variable, which I don't
+ think this code needs to do. */
+#ifdef emacs
+#undef static
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+/* The code at the top of get_date which figures out the offset of the
+ current time zone checks various CPP symbols to see if special
+ tricks are need, but defaults to using the gettimeofday system call.
+ Include <sys/time.h> if that will be used. */
+
+#if defined(vms)
+
+#include <types.h>
+#include <time.h>
+
+#else
+
+#include <sys/types.h>
+
+#ifdef TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+
+#ifdef timezone
+#undef timezone /* needed for sgi */
+#endif
+
+#if defined(HAVE_SYS_TIMEB_H)
+#include <sys/timeb.h>
+#else
+/*
+** We use the obsolete `struct timeb' as part of our interface!
+** Since the system doesn't have it, we define it here;
+** our callers must do likewise.
+*/
+struct timeb {
+ time_t time; /* Seconds since the epoch */
+ unsigned short millitm; /* Field not used */
+ short timezone; /* Minutes west of GMT */
+ short dstflag; /* Field not used */
+};
+#endif /* defined(HAVE_SYS_TIMEB_H) */
+
+#endif /* defined(vms) */
+
+#if defined (STDC_HEADERS) || defined (USG)
+#include <string.h>
+#endif
+
+/* Some old versions of bison generate parsers that use bcopy.
+ That loses on systems that don't provide the function, so we have
+ to redefine it here. */
+#if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
+#define bcopy(from, to, len) memcpy ((to), (from), (len))
+#endif
+
+#if defined (STDC_HEADERS)
+#include <stdlib.h>
+#endif
+
+#if defined (HAVE_ALLOCA_H)
+#include <alloca.h>
+#endif
+
+extern struct tm *gmtime();
+extern struct tm *localtime();
+
+#define yyparse getdate_yyparse
+#define yylex getdate_yylex
+#define yyerror getdate_yyerror
+
+#if !defined(lint) && !defined(SABER)
+static char RCS[] = "$CVSid: @(#)getdate.y 1.11 94/09/21 $";
+#endif /* !defined(lint) && !defined(SABER) */
+
+static int yylex ();
+static int yyerror ();
+
+#define EPOCH 1970
+#define HOUR(x) ((time_t)(x) * 60)
+#define SECSPERDAY (24L * 60L * 60L)
+
+
+/*
+** An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+ char *name;
+ int type;
+ time_t value;
+} TABLE;
+
+
+/*
+** Daylight-savings mode: on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+ DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+** Global variables. We could get rid of most of these by using a good
+** union as the yacc stack. (This routine was originally written before
+** yacc had the %union construct.) Maybe someday; right now we only use
+** the %union very rarely.
+*/
+static char *yyInput;
+static DSTMODE yyDSTmode;
+static time_t yyDayOrdinal;
+static time_t yyDayNumber;
+static int yyHaveDate;
+static int yyHaveDay;
+static int yyHaveRel;
+static int yyHaveTime;
+static int yyHaveZone;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+%}
+
+%union {
+ time_t Number;
+ enum _MERIDIAN Meridian;
+}
+
+%token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
+%token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
+
+%type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
+%type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
+%type <Meridian> tMERIDIAN o_merid
+
+%%
+
+spec : /* NULL */
+ | spec item
+ ;
+
+item : time {
+ yyHaveTime++;
+ }
+ | zone {
+ yyHaveZone++;
+ }
+ | date {
+ yyHaveDate++;
+ }
+ | day {
+ yyHaveDay++;
+ }
+ | rel {
+ yyHaveRel++;
+ }
+ | number
+ ;
+
+time : tUNUMBER tMERIDIAN {
+ yyHour = $1;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = 0;
+ yyMeridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
+ }
+ ;
+
+zone : tZONE {
+ yyTimezone = $1;
+ yyDSTmode = DSToff;
+ }
+ | tDAYZONE {
+ yyTimezone = $1;
+ yyDSTmode = DSTon;
+ }
+ |
+ tZONE tDST {
+ yyTimezone = $1;
+ yyDSTmode = DSTon;
+ }
+ ;
+
+day : tDAY {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tDAY ',' {
+ yyDayOrdinal = 1;
+ yyDayNumber = $1;
+ }
+ | tUNUMBER tDAY {
+ yyDayOrdinal = $1;
+ yyDayNumber = $2;
+ }
+ ;
+
+date : tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ yyYear = $5;
+ }
+ | tUNUMBER tSNUMBER tSNUMBER {
+ /* ISO 8601 format. yyyy-mm-dd. */
+ yyYear = $1;
+ yyMonth = -$2;
+ yyDay = -$3;
+ }
+ | tUNUMBER tMONTH tSNUMBER {
+ /* e.g. 17-JUN-1992. */
+ yyDay = $1;
+ yyMonth = $2;
+ yyYear = -$3;
+ }
+ | tMONTH tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ yyYear = $4;
+ }
+ | tUNUMBER tMONTH {
+ yyMonth = $2;
+ yyDay = $1;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ yyMonth = $2;
+ yyDay = $1;
+ yyYear = $3;
+ }
+ ;
+
+rel : relunit tAGO {
+ yyRelSeconds = -yyRelSeconds;
+ yyRelMonth = -yyRelMonth;
+ }
+ | relunit
+ ;
+
+relunit : tUNUMBER tMINUTE_UNIT {
+ yyRelSeconds += $1 * $2 * 60L;
+ }
+ | tSNUMBER tMINUTE_UNIT {
+ yyRelSeconds += $1 * $2 * 60L;
+ }
+ | tMINUTE_UNIT {
+ yyRelSeconds += $1 * 60L;
+ }
+ | tSNUMBER tSEC_UNIT {
+ yyRelSeconds += $1;
+ }
+ | tUNUMBER tSEC_UNIT {
+ yyRelSeconds += $1;
+ }
+ | tSEC_UNIT {
+ yyRelSeconds++;
+ }
+ | tSNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tUNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tMONTH_UNIT {
+ yyRelMonth += $1;
+ }
+ ;
+
+number : tUNUMBER {
+ if (yyHaveTime && yyHaveDate && !yyHaveRel)
+ yyYear = $1;
+ else {
+ if($1>10000) {
+ yyHaveDate++;
+ yyDay= ($1)%100;
+ yyMonth= ($1/100)%100;
+ yyYear = $1/10000;
+ }
+ else {
+ yyHaveTime++;
+ if ($1 < 100) {
+ yyHour = $1;
+ yyMinutes = 0;
+ }
+ else {
+ yyHour = $1 / 100;
+ yyMinutes = $1 % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = MER24;
+ }
+ }
+ }
+ ;
+
+o_merid : /* NULL */ {
+ $$ = MER24;
+ }
+ | tMERIDIAN {
+ $$ = $1;
+ }
+ ;
+
+%%
+
+/* Month and day table. */
+static TABLE const MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "sept", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 1 },
+ { "tuesday", tDAY, 2 },
+ { "tues", tDAY, 2 },
+ { "wednesday", tDAY, 3 },
+ { "wednes", tDAY, 3 },
+ { "thursday", tDAY, 4 },
+ { "thur", tDAY, 4 },
+ { "thurs", tDAY, 4 },
+ { "friday", tDAY, 5 },
+ { "saturday", tDAY, 6 },
+ { NULL }
+};
+
+/* Time units table. */
+static TABLE const UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
+ { "week", tMINUTE_UNIT, 7 * 24 * 60 },
+ { "day", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "hour", tMINUTE_UNIT, 60 },
+ { "minute", tMINUTE_UNIT, 1 },
+ { "min", tMINUTE_UNIT, 1 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+ { NULL }
+};
+
+/* Assorted relative-time words. */
+static TABLE const OtherTable[] = {
+ { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
+ { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
+ { "today", tMINUTE_UNIT, 0 },
+ { "now", tMINUTE_UNIT, 0 },
+ { "last", tUNUMBER, -1 },
+ { "this", tMINUTE_UNIT, 0 },
+ { "next", tUNUMBER, 2 },
+ { "first", tUNUMBER, 1 },
+/* { "second", tUNUMBER, 2 }, */
+ { "third", tUNUMBER, 3 },
+ { "fourth", tUNUMBER, 4 },
+ { "fifth", tUNUMBER, 5 },
+ { "sixth", tUNUMBER, 6 },
+ { "seventh", tUNUMBER, 7 },
+ { "eighth", tUNUMBER, 8 },
+ { "ninth", tUNUMBER, 9 },
+ { "tenth", tUNUMBER, 10 },
+ { "eleventh", tUNUMBER, 11 },
+ { "twelfth", tUNUMBER, 12 },
+ { "ago", tAGO, 1 },
+ { NULL }
+};
+
+/* The timezone table. */
+/* Some of these are commented out because a time_t can't store a float. */
+static TABLE const TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
+ { "utc", tZONE, HOUR( 0) },
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "wat", tZONE, HOUR( 1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+#if 0
+ /* For completeness. BST is also British Summer, and GST is
+ * also Guam Standard. */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+#endif
+#if 0
+ { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
+ { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
+#endif
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
+ { "cat", tZONE, HOUR(10) }, /* Central Alaska */
+ { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
+ { "nt", tZONE, HOUR(11) }, /* Nome */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
+#if 0
+ { "it", tZONE, -HOUR(3.5) },/* Iran */
+#endif
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+#if 0
+ { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
+#endif
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+#if 0
+ /* For completeness. NST is also Newfoundland Stanard, and SST is
+ * also Swedish Summer. */
+ { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
+#endif /* 0 */
+ { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
+#if 0
+ { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
+#endif
+ { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
+#if 0
+ { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
+ { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
+#endif
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { NULL }
+};
+
+/* Military timezone table. */
+static TABLE const MilitaryTable[] = {
+ { "a", tZONE, HOUR( 1) },
+ { "b", tZONE, HOUR( 2) },
+ { "c", tZONE, HOUR( 3) },
+ { "d", tZONE, HOUR( 4) },
+ { "e", tZONE, HOUR( 5) },
+ { "f", tZONE, HOUR( 6) },
+ { "g", tZONE, HOUR( 7) },
+ { "h", tZONE, HOUR( 8) },
+ { "i", tZONE, HOUR( 9) },
+ { "k", tZONE, HOUR( 10) },
+ { "l", tZONE, HOUR( 11) },
+ { "m", tZONE, HOUR( 12) },
+ { "n", tZONE, HOUR(- 1) },
+ { "o", tZONE, HOUR(- 2) },
+ { "p", tZONE, HOUR(- 3) },
+ { "q", tZONE, HOUR(- 4) },
+ { "r", tZONE, HOUR(- 5) },
+ { "s", tZONE, HOUR(- 6) },
+ { "t", tZONE, HOUR(- 7) },
+ { "u", tZONE, HOUR(- 8) },
+ { "v", tZONE, HOUR(- 9) },
+ { "w", tZONE, HOUR(-10) },
+ { "x", tZONE, HOUR(-11) },
+ { "y", tZONE, HOUR(-12) },
+ { "z", tZONE, HOUR( 0) },
+ { NULL }
+};
+
+
+
+
+/* ARGSUSED */
+static int
+yyerror(s)
+ char *s;
+{
+ return 0;
+}
+
+
+static time_t
+ToSeconds(Hours, Minutes, Seconds, Meridian)
+ time_t Hours;
+ time_t Minutes;
+ time_t Seconds;
+ MERIDIAN Meridian;
+{
+ if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
+ return -1;
+ switch (Meridian) {
+ case MER24:
+ if (Hours < 0 || Hours > 23)
+ return -1;
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+ case MERam:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+ case MERpm:
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
+ default:
+ abort ();
+ }
+ /* NOTREACHED */
+}
+
+
+static time_t
+Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
+ time_t Month;
+ time_t Day;
+ time_t Year;
+ time_t Hours;
+ time_t Minutes;
+ time_t Seconds;
+ MERIDIAN Meridian;
+ DSTMODE DSTmode;
+{
+ static int DaysInMonth[12] = {
+ 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ time_t tod;
+ time_t Julian;
+ int i;
+
+ if (Year < 0)
+ Year = -Year;
+ if (Year < 100)
+ Year += 1900;
+ DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
+ ? 29 : 28;
+ if (Year < EPOCH || Year > 1999
+ || Month < 1 || Month > 12
+ /* Lint fluff: "conversion from long may lose accuracy" */
+ || Day < 1 || Day > DaysInMonth[(int)--Month])
+ return -1;
+
+ for (Julian = Day - 1, i = 0; i < Month; i++)
+ Julian += DaysInMonth[i];
+ for (i = EPOCH; i < Year; i++)
+ Julian += 365 + (i % 4 == 0);
+ Julian *= SECSPERDAY;
+ Julian += yyTimezone * 60L;
+ if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+ return -1;
+ Julian += tod;
+ if (DSTmode == DSTon
+ || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
+ Julian -= 60 * 60;
+ return Julian;
+}
+
+
+static time_t
+DSTcorrect(Start, Future)
+ time_t Start;
+ time_t Future;
+{
+ time_t StartDay;
+ time_t FutureDay;
+
+ StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+ FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+ return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
+}
+
+
+static time_t
+RelativeDate(Start, DayOrdinal, DayNumber)
+ time_t Start;
+ time_t DayOrdinal;
+ time_t DayNumber;
+{
+ struct tm *tm;
+ time_t now;
+
+ now = Start;
+ tm = localtime(&now);
+ now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
+ now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
+ return DSTcorrect(Start, now);
+}
+
+
+static time_t
+RelativeMonth(Start, RelMonth)
+ time_t Start;
+ time_t RelMonth;
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ if (RelMonth == 0)
+ return 0;
+ tm = localtime(&Start);
+ Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+ Year = Month / 12;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t)tm->tm_mday, Year,
+ (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(buff)
+ char *buff;
+{
+ register char *p;
+ register char *q;
+ register const TABLE *tp;
+ int i;
+ int abbrev;
+
+ /* Make it lowercase. */
+ for (p = buff; *p; p++)
+ if (isupper(*p))
+ *p = tolower(*p);
+
+ if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+
+ /* See if we have an abbreviation for a month. */
+ if (strlen(buff) == 3)
+ abbrev = 1;
+ else if (strlen(buff) == 4 && buff[3] == '.') {
+ abbrev = 1;
+ buff[3] = '\0';
+ }
+ else
+ abbrev = 0;
+
+ for (tp = MonthDayTable; tp->name; tp++) {
+ if (abbrev) {
+ if (strncmp(buff, tp->name, 3) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ else if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ if (strcmp(buff, "dst") == 0)
+ return tDST;
+
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Strip off any plural and try the units table again. */
+ i = strlen(buff) - 1;
+ if (buff[i] == 's') {
+ buff[i] = '\0';
+ for (tp = UnitsTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ buff[i] = 's'; /* Put back for "this" in OtherTable. */
+ }
+
+ for (tp = OtherTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Military timezones. */
+ if (buff[1] == '\0' && isalpha(*buff)) {
+ for (tp = MilitaryTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Drop out any periods and try the timezone table again. */
+ for (i = 0, p = q = buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ else
+ i++;
+ *p = '\0';
+ if (i)
+ for (tp = TimezoneTable; tp->name; tp++)
+ if (strcmp(buff, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ return tID;
+}
+
+
+static int
+yylex()
+{
+ register char c;
+ register char *p;
+ char buff[20];
+ int Count;
+ int sign;
+
+ for ( ; ; ) {
+ while (isspace(*yyInput))
+ yyInput++;
+
+ if (isdigit(c = *yyInput) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ if (!isdigit(*++yyInput))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ for (yylval.Number = 0; isdigit(c = *yyInput++); )
+ yylval.Number = 10 * yylval.Number + c - '0';
+ yyInput--;
+ if (sign < 0)
+ yylval.Number = -yylval.Number;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ if (isalpha(c)) {
+ for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
+ if (p < &buff[sizeof buff - 1])
+ *p++ = c;
+ *p = '\0';
+ yyInput--;
+ return LookupWord(buff);
+ }
+ if (c != '(')
+ return *yyInput++;
+ Count = 0;
+ do {
+ c = *yyInput++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ Count++;
+ else if (c == ')')
+ Count--;
+ } while (Count > 0);
+ }
+}
+
+#define TM_YEAR_ORIGIN 1900
+
+/* Yield A - B, measured in seconds. */
+static long
+difftm (a, b)
+ struct tm *a, *b;
+{
+ int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
+ int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
+ int days = (
+ /* difference in day of year */
+ a->tm_yday - b->tm_yday
+ /* + intervening leap days */
+ + ((ay >> 2) - (by >> 2))
+ - (ay/100 - by/100)
+ + ((ay/100 >> 2) - (by/100 >> 2))
+ /* + difference in years * 365 */
+ + (long)(ay-by) * 365
+ );
+ return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+
+time_t
+get_date(p, now)
+ char *p;
+ struct timeb *now;
+{
+ struct tm *tm, gmt;
+ struct timeb ftz;
+ time_t Start;
+ time_t tod;
+ time_t nowtime;
+
+ yyInput = p;
+ if (now == NULL) {
+ now = &ftz;
+ (void)time (&nowtime);
+
+ if (! (tm = gmtime (&nowtime)))
+ return -1;
+ gmt = *tm; /* Make a copy, in case localtime modifies *tm. */
+
+ if (! (tm = localtime (&nowtime)))
+ return -1;
+
+ ftz.timezone = difftm (&gmt, tm) / 60;
+ if(tm->tm_isdst)
+ ftz.timezone += 60;
+ }
+ else
+ {
+ nowtime = now->time;
+ }
+
+ tm = localtime(&nowtime);
+ yyYear = tm->tm_year;
+ yyMonth = tm->tm_mon + 1;
+ yyDay = tm->tm_mday;
+ yyTimezone = now->timezone;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveDay = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+ yyHaveZone = 0;
+
+ if (yyparse()
+ || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
+ return -1;
+
+ if (yyHaveDate || yyHaveTime || yyHaveDay) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+ yyMeridian, yyDSTmode);
+ if (Start < 0)
+ return -1;
+ }
+ else {
+ Start = nowtime;
+ if (!yyHaveRel)
+ Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
+ }
+
+ Start += yyRelSeconds;
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ if (yyHaveDay && !yyHaveDate) {
+ tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
+ Start += tod;
+ }
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.) */
+ return Start == -1 ? 0 : Start;
+}
+
+
+#if defined(TEST)
+
+/* ARGSUSED */
+int
+main(ac, av)
+ int ac;
+ char *av[];
+{
+ char buff[128];
+ time_t d;
+
+ (void)printf("Enter date, or blank line to exit.\n\t> ");
+ (void)fflush(stdout);
+ while (gets(buff) && buff[0]) {
+ d = get_date(buff, (struct timeb *)NULL);
+ if (d == -1)
+ (void)printf("Bad format - couldn't convert.\n");
+ else
+ (void)printf("%s", ctime(&d));
+ (void)printf("\t> ");
+ (void)fflush(stdout);
+ }
+ exit(0);
+ /* NOTREACHED */
+}
+#endif /* defined(TEST) */
diff --git a/contrib/cvs/lib/getline.c b/contrib/cvs/lib/getline.c
new file mode 100644
index 0000000..a7ab97b
--- /dev/null
+++ b/contrib/cvs/lib/getline.c
@@ -0,0 +1,125 @@
+/* getline.c -- Replacement for GNU C library function getline
+
+Copyright (C) 1993 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 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Written by Jan Brittenson, bson@gnu.ai.mit.edu. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <assert.h>
+
+#if STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc (), *realloc ();
+#endif
+
+/* Always add at least this many bytes when extending the buffer. */
+#define MIN_CHUNK 64
+
+/* Read up to (and including) a TERMINATOR from STREAM into *LINEPTR
+ + OFFSET (and null-terminate it). *LINEPTR is a pointer returned from
+ malloc (or NULL), pointing to *N characters of space. It is realloc'd
+ as necessary. Return the number of characters read (not including the
+ null terminator), or -1 on error or EOF. */
+
+int
+getstr (lineptr, n, stream, terminator, offset)
+ char **lineptr;
+ size_t *n;
+ FILE *stream;
+ char terminator;
+ int offset;
+{
+ int nchars_avail; /* Allocated but unused chars in *LINEPTR. */
+ char *read_pos; /* Where we're reading into *LINEPTR. */
+ int ret;
+
+ if (!lineptr || !n || !stream)
+ return -1;
+
+ if (!*lineptr)
+ {
+ *n = MIN_CHUNK;
+ *lineptr = malloc (*n);
+ if (!*lineptr)
+ return -1;
+ }
+
+ nchars_avail = *n - offset;
+ read_pos = *lineptr + offset;
+
+ for (;;)
+ {
+ register int c = getc (stream);
+
+ /* We always want at least one char left in the buffer, since we
+ always (unless we get an error while reading the first char)
+ NUL-terminate the line buffer. */
+
+ assert((*lineptr + *n) == (read_pos + nchars_avail));
+ if (nchars_avail < 2)
+ {
+ if (*n > MIN_CHUNK)
+ *n *= 2;
+ else
+ *n += MIN_CHUNK;
+
+ nchars_avail = *n + *lineptr - read_pos;
+ *lineptr = realloc (*lineptr, *n);
+ if (!*lineptr)
+ return -1;
+ read_pos = *n - nchars_avail + *lineptr;
+ assert((*lineptr + *n) == (read_pos + nchars_avail));
+ }
+
+ if (c == EOF || ferror (stream))
+ {
+ /* Return partial line, if any. */
+ if (read_pos == *lineptr)
+ return -1;
+ else
+ break;
+ }
+
+ *read_pos++ = c;
+ nchars_avail--;
+
+ if (c == terminator)
+ /* Return the line. */
+ break;
+ }
+
+ /* Done - NUL terminate and return the number of chars read. */
+ *read_pos = '\0';
+
+ ret = read_pos - (*lineptr + offset);
+ return ret;
+}
+
+int
+getline (lineptr, n, stream)
+ char **lineptr;
+ size_t *n;
+ FILE *stream;
+{
+ return getstr (lineptr, n, stream, '\n', 0);
+}
diff --git a/contrib/cvs/lib/getline.h b/contrib/cvs/lib/getline.h
new file mode 100644
index 0000000..30bcc25
--- /dev/null
+++ b/contrib/cvs/lib/getline.h
@@ -0,0 +1,15 @@
+#ifndef _getline_h_
+#define _getline_h_ 1
+
+#include <stdio.h>
+
+#if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
+#define __PROTO(args) args
+#else
+#define __PROTO(args) ()
+#endif /* GCC. */
+
+int
+ getline __PROTO ((char **_lineptr, size_t *_n, FILE *_stream));
+
+#endif /* _getline_h_ */
diff --git a/contrib/cvs/lib/getopt.c b/contrib/cvs/lib/getopt.c
new file mode 100644
index 0000000..137e66b
--- /dev/null
+++ b/contrib/cvs/lib/getopt.c
@@ -0,0 +1,759 @@
+/* Getopt for GNU.
+ NOTE: getopt is now part of the C library, so if you don't know what
+ "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu
+ before changing it!
+
+ Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94
+ Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+#define _NO_PROTO
+#endif
+
+#ifdef HAVE_CONFIG_H
+#if defined (emacs) || defined (CONFIG_BROKETS)
+/* We use <config.h> instead of "config.h" so that a compilation
+ using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
+ (which it would do because it found this file in $srcdir). */
+#include <config.h>
+#else
+#include "config.h"
+#endif
+#endif
+
+#ifndef __STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#if defined (_LIBC) || !defined (__GNU_LIBRARY__)
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+#include <stdlib.h>
+#endif /* GNU C library. */
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt.h"
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns EOF, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* XXX 1003.2 says this must be 1 before any call. */
+int optind = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return EOF with `optind' != ARGC. */
+
+static enum
+{
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+ because there are many ways it can cause trouble.
+ On some systems, it contains special magic macros that don't work
+ in GCC. */
+#include <string.h>
+#define my_index strchr
+#else
+
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+
+char *getenv ();
+
+static char *
+my_index (str, chr)
+ const char *str;
+ int chr;
+{
+ while (*str)
+ {
+ if (*str == chr)
+ return (char *) str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+ If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+ That was relevant to code that was here before. */
+#ifndef __STDC__
+/* gcc with -traditional declares the built-in strlen to return int,
+ and has done so at least since version 2.4.5. -- rms. */
+extern int strlen (const char *);
+#endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments. */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (argv)
+ char **argv;
+{
+ int bottom = first_nonopt;
+ int middle = last_nonopt;
+ int top = optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ first_nonopt += (optind - last_nonopt);
+ last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+static const char *
+_getopt_initialize (optstring)
+ const char *optstring;
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ first_nonopt = last_nonopt = optind = 1;
+
+ nextchar = NULL;
+
+ posixly_correct = getenv ("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (posixly_correct != NULL)
+ ordering = REQUIRE_ORDER;
+ else
+ ordering = PERMUTE;
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns `EOF'.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+int
+_getopt_internal (argc, argv, optstring, longopts, longind, long_only)
+ int argc;
+ char *const *argv;
+ const char *optstring;
+ const struct option *longopts;
+ int *longind;
+ int long_only;
+{
+ optarg = NULL;
+
+ if (optind == 0)
+ optstring = _getopt_initialize (optstring);
+
+ if (nextchar == NULL || *nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ if (ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange ((char **) argv);
+ else if (last_nonopt != optind)
+ first_nonopt = optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (optind < argc
+ && (argv[optind][0] != '-' || argv[optind][1] == '\0'))
+ optind++;
+ last_nonopt = optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (optind != argc && !strcmp (argv[optind], "--"))
+ {
+ optind++;
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange ((char **) argv);
+ else if (first_nonopt == last_nonopt)
+ first_nonopt = optind;
+ last_nonopt = argc;
+
+ optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (optind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (first_nonopt != last_nonopt)
+ optind = first_nonopt;
+ return EOF;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if ((argv[optind][0] != '-' || argv[optind][1] == '\0'))
+ {
+ if (ordering == REQUIRE_ORDER)
+ return EOF;
+ optarg = argv[optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ nextchar = (argv[optind] + 1
+ + (longopts != NULL && argv[optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[optind][1] == '-'
+ || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1])))))
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound;
+ int option_index;
+
+ for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, nextchar, nameend - nextchar))
+ {
+ if (nameend - nextchar == (int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (opterr)
+ fprintf (stderr, "%s: option `%s' is ambiguous\n",
+ argv[0], argv[optind]);
+ nextchar += strlen (nextchar);
+ optind++;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else
+ {
+ if (opterr)
+ {
+ if (argv[optind - 1][1] == '-')
+ /* --option */
+ fprintf (stderr,
+ "%s: option `--%s' doesn't allow an argument\n",
+ argv[0], pfound->name);
+ else
+ /* +option or -option */
+ fprintf (stderr,
+ "%s: option `%c%s' doesn't allow an argument\n",
+ argv[0], argv[optind - 1][0], pfound->name);
+ }
+ nextchar += strlen (nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else
+ {
+ if (opterr)
+ fprintf (stderr, "%s: option `%s' requires an argument\n",
+ argv[0], argv[optind - 1]);
+ nextchar += strlen (nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen (nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[optind][1] == '-'
+ || my_index (optstring, *nextchar) == NULL)
+ {
+ if (opterr)
+ {
+ if (argv[optind][1] == '-')
+ /* --option */
+ fprintf (stderr, "%s: unrecognized option `--%s'\n",
+ argv[0], nextchar);
+ else
+ /* +option or -option */
+ fprintf (stderr, "%s: unrecognized option `%c%s'\n",
+ argv[0], argv[optind][0], nextchar);
+ }
+ nextchar = (char *) "";
+ optind++;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *nextchar++;
+ char *temp = my_index (optstring, c);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*nextchar == '\0')
+ ++optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (opterr)
+ {
+ if (posixly_correct)
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c);
+ else
+ fprintf (stderr, "%s: invalid option -- %c\n", argv[0], c);
+ }
+ optopt = c;
+ return '?';
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ optind++;
+ }
+ else
+ optarg = NULL;
+ nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ }
+ else if (optind == argc)
+ {
+ if (opterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr, "%s: option requires an argument -- %c\n",
+ argv[0], c);
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ optarg = argv[optind++];
+ nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+getopt (argc, argv, optstring)
+ int argc;
+ char *const *argv;
+ const char *optstring;
+{
+ return _getopt_internal (argc, argv, optstring,
+ (const struct option *) 0,
+ (int *) 0,
+ 0);
+}
+
+#endif /* _LIBC or not __GNU_LIBRARY__. */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+
+ c = getopt (argc, argv, "abc:d:0123456789");
+ if (c == EOF)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/contrib/cvs/lib/getopt.h b/contrib/cvs/lib/getopt.h
new file mode 100644
index 0000000..f644aa1
--- /dev/null
+++ b/contrib/cvs/lib/getopt.h
@@ -0,0 +1,131 @@
+/* Declarations for getopt.
+ Copyright (C) 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* $CVSid: @(#)getopt.h 1.7 94/09/21 $ */
+
+#ifndef _GETOPT_H
+#define _GETOPT_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns EOF, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int optopt;
+
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option
+{
+#if __STDC__
+ const char *name;
+#else
+ char *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#if __STDC__
+#if defined(__GNU_LIBRARY__)
+/* Many other libraries have conflicting prototypes for getopt, with
+ differences in the consts, in stdlib.h. To avoid compilation
+ errors, only prototype getopt for the GNU C library. */
+extern int getopt (int argc, char *const *argv, const char *shortopts);
+#else /* not __GNU_LIBRARY__ */
+extern int getopt ();
+#endif /* not __GNU_LIBRARY__ */
+extern int getopt_long (int argc, char *const *argv, const char *shortopts,
+ const struct option *longopts, int *longind);
+extern int getopt_long_only (int argc, char *const *argv,
+ const char *shortopts,
+ const struct option *longopts, int *longind);
+
+/* Internal only. Users should not call this directly. */
+extern int _getopt_internal (int argc, char *const *argv,
+ const char *shortopts,
+ const struct option *longopts, int *longind,
+ int long_only);
+#else /* not __STDC__ */
+extern int getopt ();
+extern int getopt_long ();
+extern int getopt_long_only ();
+
+extern int _getopt_internal ();
+#endif /* not __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GETOPT_H */
diff --git a/contrib/cvs/lib/getopt1.c b/contrib/cvs/lib/getopt1.c
new file mode 100644
index 0000000..f784b57
--- /dev/null
+++ b/contrib/cvs/lib/getopt1.c
@@ -0,0 +1,187 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+ Copyright (C) 1987, 88, 89, 90, 91, 92, 1993
+ Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#if defined (emacs) || defined (CONFIG_BROKETS)
+/* We use <config.h> instead of "config.h" so that a compilation
+ using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
+ (which it would do because it found this file in $srcdir). */
+#include <config.h>
+#else
+#include "config.h"
+#endif
+#endif
+
+#include "getopt.h"
+
+#ifndef __STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#if defined (_LIBC) || !defined (__GNU_LIBRARY__)
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#else
+char *getenv ();
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (argc, argv, options, long_options, opt_index)
+ int argc;
+ char *const *argv;
+ const char *options;
+ const struct option *long_options;
+ int *opt_index;
+{
+ return _getopt_internal (argc, argv, options, long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+getopt_long_only (argc, argv, options, long_options, opt_index)
+ int argc;
+ char *const *argv;
+ const char *options;
+ const struct option *long_options;
+ int *opt_index;
+{
+ return _getopt_internal (argc, argv, options, long_options, opt_index, 1);
+}
+
+
+#endif /* _LIBC or not __GNU_LIBRARY__. */
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ static struct option long_options[] =
+ {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long (argc, argv, "abc:d:0123456789",
+ long_options, &option_index);
+ if (c == EOF)
+ break;
+
+ switch (c)
+ {
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value `%s'\n", optarg);
+ break;
+
+ case 'd':
+ printf ("option d with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/contrib/cvs/lib/getwd.c b/contrib/cvs/lib/getwd.c
new file mode 100644
index 0000000..573a788
--- /dev/null
+++ b/contrib/cvs/lib/getwd.c
@@ -0,0 +1,35 @@
+/* getwd.c -- get current working directory pathname
+ Copyright (C) 1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Some systems which include both getwd() and getcwd() have an implementation
+ of getwd() which is much faster than getcwd(). As a result, we use the
+ system's getwd() if it is available */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "system.h"
+
+/* Get the current working directory into PATHNAME */
+
+char *
+getwd (pathname)
+ char *pathname;
+{
+ return (getcwd(pathname, PATH_MAX));
+}
diff --git a/contrib/cvs/lib/hostname.c b/contrib/cvs/lib/hostname.c
new file mode 100644
index 0000000..34be15e
--- /dev/null
+++ b/contrib/cvs/lib/hostname.c
@@ -0,0 +1,49 @@
+/* hostname.c -- use uname() to get the name of the host
+ Copyright (C) 1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(STDC_HEADERS) || defined(USG)
+#include <string.h>
+#ifndef index
+#define index strchr
+#endif
+#else
+#include <strings.h>
+#endif
+
+#include <sys/utsname.h>
+
+/* Put this host's name into NAME, using at most NAMELEN characters */
+
+int
+gethostname(name, namelen)
+ char *name;
+ int namelen;
+{
+ struct utsname ugnm;
+
+ if (uname(&ugnm) < 0)
+ return (-1);
+
+ (void) strncpy(name, ugnm.nodename, namelen-1);
+ name[namelen-1] = '\0';
+
+ return (0);
+}
diff --git a/contrib/cvs/lib/md5.c b/contrib/cvs/lib/md5.c
new file mode 100644
index 0000000..4ad99cd
--- /dev/null
+++ b/contrib/cvs/lib/md5.c
@@ -0,0 +1,277 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include "config.h"
+
+#if HAVE_STRING_H || STDC_HEADERS
+#include <string.h> /* for memcpy() */
+#endif
+
+/* Add prototype support. */
+#ifndef PROTO
+#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
+#define PROTO(ARGS) ARGS
+#else
+#define PROTO(ARGS) ()
+#endif
+#endif
+
+#include "md5.h"
+
+void byteReverse PROTO ((unsigned char *buf, unsigned longs));
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse (buf, longs)
+ unsigned char *buf;
+ unsigned longs;
+{
+ uint32 t;
+ do {
+ t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
+ ((unsigned)buf[1]<<8 | buf[0]);
+ *(uint32 *)buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(ctx)
+ struct MD5Context *ctx;
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(ctx, buf, len)
+ struct MD5Context *ctx;
+ unsigned char const *buf;
+ unsigned len;
+{
+ uint32 t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32)len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if ( t ) {
+ unsigned char *p = (unsigned char *)ctx->in + t;
+
+ t = 64-t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(digest, ctx)
+ unsigned char digest[16];
+ struct MD5Context *ctx;
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = ctx->in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count-8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0];
+ ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ byteReverse((unsigned char *)ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void
+MD5Transform(buf, in)
+ uint32 buf[4];
+ uint32 const in[16];
+{
+ register uint32 a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+#endif
diff --git a/contrib/cvs/lib/md5.h b/contrib/cvs/lib/md5.h
new file mode 100644
index 0000000..bfe79cc
--- /dev/null
+++ b/contrib/cvs/lib/md5.h
@@ -0,0 +1,31 @@
+#ifndef MD5_H
+#define MD5_H
+
+#if SIZEOF_LONG == 4
+typedef unsigned long uint32;
+#else
+#if SIZEOF_INT == 4
+typedef unsigned int uint32;
+#else
+Congratulations! You get to rewrite this code so that it does not require
+a 32-bit integer type! (Or maybe you just need to reconfigure.)
+#endif
+#endif
+
+struct MD5Context {
+ uint32 buf[4];
+ uint32 bits[2];
+ unsigned char in[64];
+};
+
+void MD5Init PROTO((struct MD5Context *context));
+void MD5Update PROTO((struct MD5Context *context, unsigned char const *buf, unsigned len));
+void MD5Final PROTO((unsigned char digest[16], struct MD5Context *context));
+void MD5Transform PROTO((uint32 buf[4], uint32 const in[16]));
+
+/*
+ * This is needed to make RSAREF happy on some MS-DOS compilers.
+ */
+typedef struct MD5Context MD5_CTX;
+
+#endif /* !MD5_H */
diff --git a/contrib/cvs/lib/memmove.c b/contrib/cvs/lib/memmove.c
new file mode 100644
index 0000000..8818d46
--- /dev/null
+++ b/contrib/cvs/lib/memmove.c
@@ -0,0 +1,57 @@
+/* memmove -- copy memory regions of arbitary length
+ Copyright (C) 1991 Free Software Foundation, Inc.
+
+This file is part of the libiberty library.
+Libiberty is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+Libiberty is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with libiberty; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+
+/*
+
+NAME
+
+ memmove -- copy memory regions of arbitary length
+
+SYNOPSIS
+
+ void memmove (void *out, const void *in, size_t n);
+
+DESCRIPTION
+
+ Copy LENGTH bytes from memory region pointed to by IN to memory
+ region pointed to by OUT.
+
+ Regions can be overlapping.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef __STDC__
+#include <stddef.h>
+#else
+#define size_t unsigned long
+#endif
+
+void *
+memmove (out, in, length)
+ void *out;
+ const void* in;
+ size_t length;
+{
+ bcopy(in, out, length);
+ return out;
+}
diff --git a/contrib/cvs/lib/mkdir.c b/contrib/cvs/lib/mkdir.c
new file mode 100644
index 0000000..89ed4b6
--- /dev/null
+++ b/contrib/cvs/lib/mkdir.c
@@ -0,0 +1,129 @@
+/* mkrmdir.c -- BSD compatible directory functions for System V
+ Copyright (C) 1988, 1990 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#ifndef STDC_HEADERS
+extern int errno;
+#endif
+
+/* mkdir and rmdir adapted from GNU tar. */
+
+/* Make directory DPATH, with permission mode DMODE.
+
+ Written by Robert Rother, Mariah Corporation, August 1985
+ (sdcsvax!rmr or rmr@uscd). If you want it, it's yours.
+
+ Severely hacked over by John Gilmore to make a 4.2BSD compatible
+ subroutine. 11Mar86; hoptoad!gnu
+
+ Modified by rmtodd@uokmax 6-28-87 -- when making an already existing dir,
+ subroutine didn't return EEXIST. It does now. */
+
+int
+mkdir (dpath, dmode)
+ char *dpath;
+ int dmode;
+{
+ int cpid, status;
+ struct stat statbuf;
+
+ if (stat (dpath, &statbuf) == 0)
+ {
+ errno = EEXIST; /* stat worked, so it already exists. */
+ return -1;
+ }
+
+ /* If stat fails for a reason other than non-existence, return error. */
+ if (! existence_error (errno))
+ return -1;
+
+ cpid = fork ();
+ switch (cpid)
+ {
+ case -1: /* Cannot fork. */
+ return -1; /* errno is set already. */
+
+ case 0: /* Child process. */
+ /* Cheap hack to set mode of new directory. Since this child
+ process is going away anyway, we zap its umask.
+ This won't suffice to set SUID, SGID, etc. on this
+ directory, so the parent process calls chmod afterward. */
+ status = umask (0); /* Get current umask. */
+ umask (status | (0777 & ~dmode)); /* Set for mkdir. */
+ execl ("/bin/mkdir", "mkdir", dpath, (char *) 0);
+ _exit (1);
+
+ default: /* Parent process. */
+ while (wait (&status) != cpid) /* Wait for kid to finish. */
+ /* Do nothing. */ ;
+
+ if (status & 0xFFFF)
+ {
+ errno = EIO; /* /bin/mkdir failed. */
+ return -1;
+ }
+ return chmod (dpath, dmode);
+ }
+}
+
+/* Remove directory DPATH.
+ Return 0 if successful, -1 if not. */
+
+int
+rmdir (dpath)
+ char *dpath;
+{
+ int cpid, status;
+ struct stat statbuf;
+
+ if (stat (dpath, &statbuf) != 0)
+ return -1; /* stat set errno. */
+
+ if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+
+ cpid = fork ();
+ switch (cpid)
+ {
+ case -1: /* Cannot fork. */
+ return -1; /* errno is set already. */
+
+ case 0: /* Child process. */
+ execl ("/bin/rmdir", "rmdir", dpath, (char *) 0);
+ _exit (1);
+
+ default: /* Parent process. */
+ while (wait (&status) != cpid) /* Wait for kid to finish. */
+ /* Do nothing. */ ;
+
+ if (status & 0xFFFF)
+ {
+ errno = EIO; /* /bin/rmdir failed. */
+ return -1;
+ }
+ return 0;
+ }
+}
diff --git a/contrib/cvs/lib/regex.c b/contrib/cvs/lib/regex.c
new file mode 100644
index 0000000..03fc721
--- /dev/null
+++ b/contrib/cvs/lib/regex.c
@@ -0,0 +1,4952 @@
+/* Extended regular expression matching and search library,
+ version 0.12.
+ (Implements POSIX draft P10003.2/D11.2, except for
+ internationalization features.)
+
+ Copyright (C) 1993 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Trying to define this in the makefile would get hairy, unless we can
+ more gracefully do it for NT, OS/2, unix, etc. */
+#define REGEX_MALLOC 1
+
+/* AIX requires this to be the first thing in the file. */
+#if defined (_AIX) && !defined (REGEX_MALLOC)
+ #pragma alloca
+#endif
+
+#define _GNU_SOURCE
+
+/* We need this for `regex.h', and perhaps for the Emacs include files. */
+#include <sys/types.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* The `emacs' switch turns on certain matching commands
+ that make sense only in Emacs. */
+#ifdef emacs
+
+#include "lisp.h"
+#include "buffer.h"
+#include "syntax.h"
+
+/* Emacs uses `NULL' as a predicate. */
+#undef NULL
+
+#else /* not emacs */
+
+/* We used to test for `BSTRING' here, but only GCC and Emacs define
+ `BSTRING', as far as I know, and neither of them use this code. */
+#if HAVE_STRING_H || STDC_HEADERS
+#include <string.h>
+#ifndef bcmp
+#define bcmp(s1, s2, n) memcmp ((s1), (s2), (n))
+#endif
+#ifndef bcopy
+#define bcopy(s, d, n) memcpy ((d), (s), (n))
+#endif
+#ifndef bzero
+#define bzero(s, n) memset ((s), 0, (n))
+#endif
+#else
+#include <strings.h>
+#endif
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc ();
+char *realloc ();
+#endif
+
+
+/* Define the syntax stuff for \<, \>, etc. */
+
+/* This must be nonzero for the wordchar and notwordchar pattern
+ commands in re_match_2. */
+#ifndef Sword
+#define Sword 1
+#endif
+
+#ifdef SYNTAX_TABLE
+
+extern char *re_syntax_table;
+
+#else /* not SYNTAX_TABLE */
+
+/* How many characters in the character set. */
+#define CHAR_SET_SIZE 256
+
+static char re_syntax_table[CHAR_SET_SIZE];
+
+static void
+init_syntax_once ()
+{
+ register int c;
+ static int done = 0;
+
+ if (done)
+ return;
+
+ bzero (re_syntax_table, sizeof re_syntax_table);
+
+ for (c = 'a'; c <= 'z'; c++)
+ re_syntax_table[c] = Sword;
+
+ for (c = 'A'; c <= 'Z'; c++)
+ re_syntax_table[c] = Sword;
+
+ for (c = '0'; c <= '9'; c++)
+ re_syntax_table[c] = Sword;
+
+ re_syntax_table['_'] = Sword;
+
+ done = 1;
+}
+
+#endif /* not SYNTAX_TABLE */
+
+#define SYNTAX(c) re_syntax_table[c]
+
+#endif /* not emacs */
+
+/* Get the interface, including the syntax bits. */
+#include "regex.h"
+
+/* isalpha etc. are used for the character classes. */
+#include <ctype.h>
+
+#ifndef isascii
+#define isascii(c) 1
+#endif
+
+#ifdef isblank
+#define ISBLANK(c) (isascii (c) && isblank (c))
+#else
+#define ISBLANK(c) ((c) == ' ' || (c) == '\t')
+#endif
+#ifdef isgraph
+#define ISGRAPH(c) (isascii (c) && isgraph (c))
+#else
+#define ISGRAPH(c) (isascii (c) && isprint (c) && !isspace (c))
+#endif
+
+#define ISPRINT(c) (isascii (c) && isprint (c))
+#define ISDIGIT(c) (isascii (c) && isdigit (c))
+#define ISALNUM(c) (isascii (c) && isalnum (c))
+#define ISALPHA(c) (isascii (c) && isalpha (c))
+#define ISCNTRL(c) (isascii (c) && iscntrl (c))
+#define ISLOWER(c) (isascii (c) && islower (c))
+#define ISPUNCT(c) (isascii (c) && ispunct (c))
+#define ISSPACE(c) (isascii (c) && isspace (c))
+#define ISUPPER(c) (isascii (c) && isupper (c))
+#define ISXDIGIT(c) (isascii (c) && isxdigit (c))
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+/* We remove any previous definition of `SIGN_EXTEND_CHAR',
+ since ours (we hope) works properly with all combinations of
+ machines, compilers, `char' and `unsigned char' argument types.
+ (Per Bothner suggested the basic approach.) */
+#undef SIGN_EXTEND_CHAR
+#if __STDC__
+#define SIGN_EXTEND_CHAR(c) ((signed char) (c))
+#else /* not __STDC__ */
+/* As in Harbison and Steele. */
+#define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128)
+#endif
+
+/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we
+ use `alloca' instead of `malloc'. This is because using malloc in
+ re_search* or re_match* could cause memory leaks when C-g is used in
+ Emacs; also, malloc is slower and causes storage fragmentation. On
+ the other hand, malloc is more portable, and easier to debug.
+
+ Because we sometimes use alloca, some routines have to be macros,
+ not functions -- `alloca'-allocated space disappears at the end of the
+ function it is called in. */
+
+#ifdef REGEX_MALLOC
+
+#define REGEX_ALLOCATE malloc
+#define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize)
+
+#else /* not REGEX_MALLOC */
+
+/* Emacs already defines alloca, sometimes. */
+#ifndef alloca
+
+/* Make alloca work the best possible way. */
+#ifdef __GNUC__
+#define alloca __builtin_alloca
+#else /* not __GNUC__ */
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else /* not __GNUC__ or HAVE_ALLOCA_H */
+#ifndef _AIX /* Already did AIX, up at the top. */
+char *alloca ();
+#endif /* not _AIX */
+#endif /* not HAVE_ALLOCA_H */
+#endif /* not __GNUC__ */
+
+#endif /* not alloca */
+
+#define REGEX_ALLOCATE alloca
+
+/* Assumes a `char *destination' variable. */
+#define REGEX_REALLOCATE(source, osize, nsize) \
+ (destination = (char *) alloca (nsize), \
+ bcopy (source, destination, osize), \
+ destination)
+
+#endif /* not REGEX_MALLOC */
+
+
+/* True if `size1' is non-NULL and PTR is pointing anywhere inside
+ `string1' or just past its end. This works if PTR is NULL, which is
+ a good thing. */
+#define FIRST_STRING_P(ptr) \
+ (size1 && string1 <= (ptr) && (ptr) <= string1 + size1)
+
+/* (Re)Allocate N items of type T using malloc, or fail. */
+#define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t)))
+#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t)))
+#define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t)))
+
+#define BYTEWIDTH 8 /* In bits. */
+
+#define STREQ(s1, s2) ((strcmp (s1, s2) == 0))
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+typedef char boolean;
+#define false 0
+#define true 1
+
+/* These are the command codes that appear in compiled regular
+ expressions. Some opcodes are followed by argument bytes. A
+ command code can specify any interpretation whatsoever for its
+ arguments. Zero bytes may appear in the compiled regular expression.
+
+ The value of `exactn' is needed in search.c (search_buffer) in Emacs.
+ So regex.h defines a symbol `RE_EXACTN_VALUE' to be 1; the value of
+ `exactn' we use here must also be 1. */
+
+typedef enum
+{
+ no_op = 0,
+
+ /* Followed by one byte giving n, then by n literal bytes. */
+ exactn = 1,
+
+ /* Matches any (more or less) character. */
+ anychar,
+
+ /* Matches any one char belonging to specified set. First
+ following byte is number of bitmap bytes. Then come bytes
+ for a bitmap saying which chars are in. Bits in each byte
+ are ordered low-bit-first. A character is in the set if its
+ bit is 1. A character too large to have a bit in the map is
+ automatically not in the set. */
+ charset,
+
+ /* Same parameters as charset, but match any character that is
+ not one of those specified. */
+ charset_not,
+
+ /* Start remembering the text that is matched, for storing in a
+ register. Followed by one byte with the register number, in
+ the range 0 to one less than the pattern buffer's re_nsub
+ field. Then followed by one byte with the number of groups
+ inner to this one. (This last has to be part of the
+ start_memory only because we need it in the on_failure_jump
+ of re_match_2.) */
+ start_memory,
+
+ /* Stop remembering the text that is matched and store it in a
+ memory register. Followed by one byte with the register
+ number, in the range 0 to one less than `re_nsub' in the
+ pattern buffer, and one byte with the number of inner groups,
+ just like `start_memory'. (We need the number of inner
+ groups here because we don't have any easy way of finding the
+ corresponding start_memory when we're at a stop_memory.) */
+ stop_memory,
+
+ /* Match a duplicate of something remembered. Followed by one
+ byte containing the register number. */
+ duplicate,
+
+ /* Fail unless at beginning of line. */
+ begline,
+
+ /* Fail unless at end of line. */
+ endline,
+
+ /* Succeeds if at beginning of buffer (if emacs) or at beginning
+ of string to be matched (if not). */
+ begbuf,
+
+ /* Analogously, for end of buffer/string. */
+ endbuf,
+
+ /* Followed by two byte relative address to which to jump. */
+ jump,
+
+ /* Same as jump, but marks the end of an alternative. */
+ jump_past_alt,
+
+ /* Followed by two-byte relative address of place to resume at
+ in case of failure. */
+ on_failure_jump,
+
+ /* Like on_failure_jump, but pushes a placeholder instead of the
+ current string position when executed. */
+ on_failure_keep_string_jump,
+
+ /* Throw away latest failure point and then jump to following
+ two-byte relative address. */
+ pop_failure_jump,
+
+ /* Change to pop_failure_jump if know won't have to backtrack to
+ match; otherwise change to jump. This is used to jump
+ back to the beginning of a repeat. If what follows this jump
+ clearly won't match what the repeat does, such that we can be
+ sure that there is no use backtracking out of repetitions
+ already matched, then we change it to a pop_failure_jump.
+ Followed by two-byte address. */
+ maybe_pop_jump,
+
+ /* Jump to following two-byte address, and push a dummy failure
+ point. This failure point will be thrown away if an attempt
+ is made to use it for a failure. A `+' construct makes this
+ before the first repeat. Also used as an intermediary kind
+ of jump when compiling an alternative. */
+ dummy_failure_jump,
+
+ /* Push a dummy failure point and continue. Used at the end of
+ alternatives. */
+ push_dummy_failure,
+
+ /* Followed by two-byte relative address and two-byte number n.
+ After matching N times, jump to the address upon failure. */
+ succeed_n,
+
+ /* Followed by two-byte relative address, and two-byte number n.
+ Jump to the address N times, then fail. */
+ jump_n,
+
+ /* Set the following two-byte relative address to the
+ subsequent two-byte number. The address *includes* the two
+ bytes of number. */
+ set_number_at,
+
+ wordchar, /* Matches any word-constituent character. */
+ notwordchar, /* Matches any char that is not a word-constituent. */
+
+ wordbeg, /* Succeeds if at word beginning. */
+ wordend, /* Succeeds if at word end. */
+
+ wordbound, /* Succeeds if at a word boundary. */
+ notwordbound /* Succeeds if not at a word boundary. */
+
+#ifdef emacs
+ ,before_dot, /* Succeeds if before point. */
+ at_dot, /* Succeeds if at point. */
+ after_dot, /* Succeeds if after point. */
+
+ /* Matches any character whose syntax is specified. Followed by
+ a byte which contains a syntax code, e.g., Sword. */
+ syntaxspec,
+
+ /* Matches any character whose syntax is not that specified. */
+ notsyntaxspec
+#endif /* emacs */
+} re_opcode_t;
+
+/* Common operations on the compiled pattern. */
+
+/* Store NUMBER in two contiguous bytes starting at DESTINATION. */
+
+#define STORE_NUMBER(destination, number) \
+ do { \
+ (destination)[0] = (number) & 0377; \
+ (destination)[1] = (number) >> 8; \
+ } while (0)
+
+/* Same as STORE_NUMBER, except increment DESTINATION to
+ the byte after where the number is stored. Therefore, DESTINATION
+ must be an lvalue. */
+
+#define STORE_NUMBER_AND_INCR(destination, number) \
+ do { \
+ STORE_NUMBER (destination, number); \
+ (destination) += 2; \
+ } while (0)
+
+/* Put into DESTINATION a number stored in two contiguous bytes starting
+ at SOURCE. */
+
+#define EXTRACT_NUMBER(destination, source) \
+ do { \
+ (destination) = *(source) & 0377; \
+ (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8; \
+ } while (0)
+
+#ifdef DEBUG
+static void
+extract_number (dest, source)
+ int *dest;
+ unsigned char *source;
+{
+ int temp = SIGN_EXTEND_CHAR (*(source + 1));
+ *dest = *source & 0377;
+ *dest += temp << 8;
+}
+
+#ifndef EXTRACT_MACROS /* To debug the macros. */
+#undef EXTRACT_NUMBER
+#define EXTRACT_NUMBER(dest, src) extract_number (&dest, src)
+#endif /* not EXTRACT_MACROS */
+
+#endif /* DEBUG */
+
+/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number.
+ SOURCE must be an lvalue. */
+
+#define EXTRACT_NUMBER_AND_INCR(destination, source) \
+ do { \
+ EXTRACT_NUMBER (destination, source); \
+ (source) += 2; \
+ } while (0)
+
+#ifdef DEBUG
+static void
+extract_number_and_incr (destination, source)
+ int *destination;
+ unsigned char **source;
+{
+ extract_number (destination, *source);
+ *source += 2;
+}
+
+#ifndef EXTRACT_MACROS
+#undef EXTRACT_NUMBER_AND_INCR
+#define EXTRACT_NUMBER_AND_INCR(dest, src) \
+ extract_number_and_incr (&dest, &src)
+#endif /* not EXTRACT_MACROS */
+
+#endif /* DEBUG */
+
+/* If DEBUG is defined, Regex prints many voluminous messages about what
+ it is doing (if the variable `debug' is nonzero). If linked with the
+ main program in `iregex.c', you can enter patterns and strings
+ interactively. And if linked with the main program in `main.c' and
+ the other test files, you can run the already-written tests. */
+
+#ifdef DEBUG
+
+/* We use standard I/O for debugging. */
+#include <stdio.h>
+
+/* It is useful to test things that ``must'' be true when debugging. */
+#include <assert.h>
+
+static int debug = 0;
+
+#define DEBUG_STATEMENT(e) e
+#define DEBUG_PRINT1(x) if (debug) printf (x)
+#define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2)
+#define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3)
+#define DEBUG_PRINT4(x1, x2, x3, x4) if (debug) printf (x1, x2, x3, x4)
+#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) \
+ if (debug) print_partial_compiled_pattern (s, e)
+#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) \
+ if (debug) print_double_string (w, s1, sz1, s2, sz2)
+
+
+extern void printchar ();
+
+/* Print the fastmap in human-readable form. */
+
+void
+print_fastmap (fastmap)
+ char *fastmap;
+{
+ unsigned was_a_range = 0;
+ unsigned i = 0;
+
+ while (i < (1 << BYTEWIDTH))
+ {
+ if (fastmap[i++])
+ {
+ was_a_range = 0;
+ printchar (i - 1);
+ while (i < (1 << BYTEWIDTH) && fastmap[i])
+ {
+ was_a_range = 1;
+ i++;
+ }
+ if (was_a_range)
+ {
+ printf ("-");
+ printchar (i - 1);
+ }
+ }
+ }
+ putchar ('\n');
+}
+
+
+/* Print a compiled pattern string in human-readable form, starting at
+ the START pointer into it and ending just before the pointer END. */
+
+void
+print_partial_compiled_pattern (start, end)
+ unsigned char *start;
+ unsigned char *end;
+{
+ int mcnt, mcnt2;
+ unsigned char *p = start;
+ unsigned char *pend = end;
+
+ if (start == NULL)
+ {
+ printf ("(null)\n");
+ return;
+ }
+
+ /* Loop over pattern commands. */
+ while (p < pend)
+ {
+ switch ((re_opcode_t) *p++)
+ {
+ case no_op:
+ printf ("/no_op");
+ break;
+
+ case exactn:
+ mcnt = *p++;
+ printf ("/exactn/%d", mcnt);
+ do
+ {
+ putchar ('/');
+ printchar (*p++);
+ }
+ while (--mcnt);
+ break;
+
+ case start_memory:
+ mcnt = *p++;
+ printf ("/start_memory/%d/%d", mcnt, *p++);
+ break;
+
+ case stop_memory:
+ mcnt = *p++;
+ printf ("/stop_memory/%d/%d", mcnt, *p++);
+ break;
+
+ case duplicate:
+ printf ("/duplicate/%d", *p++);
+ break;
+
+ case anychar:
+ printf ("/anychar");
+ break;
+
+ case charset:
+ case charset_not:
+ {
+ register int c;
+
+ printf ("/charset%s",
+ (re_opcode_t) *(p - 1) == charset_not ? "_not" : "");
+
+ assert (p + *p < pend);
+
+ for (c = 0; c < *p; c++)
+ {
+ unsigned bit;
+ unsigned char map_byte = p[1 + c];
+
+ putchar ('/');
+
+ for (bit = 0; bit < BYTEWIDTH; bit++)
+ if (map_byte & (1 << bit))
+ printchar (c * BYTEWIDTH + bit);
+ }
+ p += 1 + *p;
+ break;
+ }
+
+ case begline:
+ printf ("/begline");
+ break;
+
+ case endline:
+ printf ("/endline");
+ break;
+
+ case on_failure_jump:
+ extract_number_and_incr (&mcnt, &p);
+ printf ("/on_failure_jump/0/%d", mcnt);
+ break;
+
+ case on_failure_keep_string_jump:
+ extract_number_and_incr (&mcnt, &p);
+ printf ("/on_failure_keep_string_jump/0/%d", mcnt);
+ break;
+
+ case dummy_failure_jump:
+ extract_number_and_incr (&mcnt, &p);
+ printf ("/dummy_failure_jump/0/%d", mcnt);
+ break;
+
+ case push_dummy_failure:
+ printf ("/push_dummy_failure");
+ break;
+
+ case maybe_pop_jump:
+ extract_number_and_incr (&mcnt, &p);
+ printf ("/maybe_pop_jump/0/%d", mcnt);
+ break;
+
+ case pop_failure_jump:
+ extract_number_and_incr (&mcnt, &p);
+ printf ("/pop_failure_jump/0/%d", mcnt);
+ break;
+
+ case jump_past_alt:
+ extract_number_and_incr (&mcnt, &p);
+ printf ("/jump_past_alt/0/%d", mcnt);
+ break;
+
+ case jump:
+ extract_number_and_incr (&mcnt, &p);
+ printf ("/jump/0/%d", mcnt);
+ break;
+
+ case succeed_n:
+ extract_number_and_incr (&mcnt, &p);
+ extract_number_and_incr (&mcnt2, &p);
+ printf ("/succeed_n/0/%d/0/%d", mcnt, mcnt2);
+ break;
+
+ case jump_n:
+ extract_number_and_incr (&mcnt, &p);
+ extract_number_and_incr (&mcnt2, &p);
+ printf ("/jump_n/0/%d/0/%d", mcnt, mcnt2);
+ break;
+
+ case set_number_at:
+ extract_number_and_incr (&mcnt, &p);
+ extract_number_and_incr (&mcnt2, &p);
+ printf ("/set_number_at/0/%d/0/%d", mcnt, mcnt2);
+ break;
+
+ case wordbound:
+ printf ("/wordbound");
+ break;
+
+ case notwordbound:
+ printf ("/notwordbound");
+ break;
+
+ case wordbeg:
+ printf ("/wordbeg");
+ break;
+
+ case wordend:
+ printf ("/wordend");
+
+#ifdef emacs
+ case before_dot:
+ printf ("/before_dot");
+ break;
+
+ case at_dot:
+ printf ("/at_dot");
+ break;
+
+ case after_dot:
+ printf ("/after_dot");
+ break;
+
+ case syntaxspec:
+ printf ("/syntaxspec");
+ mcnt = *p++;
+ printf ("/%d", mcnt);
+ break;
+
+ case notsyntaxspec:
+ printf ("/notsyntaxspec");
+ mcnt = *p++;
+ printf ("/%d", mcnt);
+ break;
+#endif /* emacs */
+
+ case wordchar:
+ printf ("/wordchar");
+ break;
+
+ case notwordchar:
+ printf ("/notwordchar");
+ break;
+
+ case begbuf:
+ printf ("/begbuf");
+ break;
+
+ case endbuf:
+ printf ("/endbuf");
+ break;
+
+ default:
+ printf ("?%d", *(p-1));
+ }
+ }
+ printf ("/\n");
+}
+
+
+void
+print_compiled_pattern (bufp)
+ struct re_pattern_buffer *bufp;
+{
+ unsigned char *buffer = bufp->buffer;
+
+ print_partial_compiled_pattern (buffer, buffer + bufp->used);
+ printf ("%d bytes used/%d bytes allocated.\n", bufp->used, bufp->allocated);
+
+ if (bufp->fastmap_accurate && bufp->fastmap)
+ {
+ printf ("fastmap: ");
+ print_fastmap (bufp->fastmap);
+ }
+
+ printf ("re_nsub: %d\t", bufp->re_nsub);
+ printf ("regs_alloc: %d\t", bufp->regs_allocated);
+ printf ("can_be_null: %d\t", bufp->can_be_null);
+ printf ("newline_anchor: %d\n", bufp->newline_anchor);
+ printf ("no_sub: %d\t", bufp->no_sub);
+ printf ("not_bol: %d\t", bufp->not_bol);
+ printf ("not_eol: %d\t", bufp->not_eol);
+ printf ("syntax: %d\n", bufp->syntax);
+ /* Perhaps we should print the translate table? */
+}
+
+
+void
+print_double_string (where, string1, size1, string2, size2)
+ const char *where;
+ const char *string1;
+ const char *string2;
+ int size1;
+ int size2;
+{
+ unsigned this_char;
+
+ if (where == NULL)
+ printf ("(null)");
+ else
+ {
+ if (FIRST_STRING_P (where))
+ {
+ for (this_char = where - string1; this_char < size1; this_char++)
+ printchar (string1[this_char]);
+
+ where = string2;
+ }
+
+ for (this_char = where - string2; this_char < size2; this_char++)
+ printchar (string2[this_char]);
+ }
+}
+
+#else /* not DEBUG */
+
+#undef assert
+#define assert(e)
+
+#define DEBUG_STATEMENT(e)
+#define DEBUG_PRINT1(x)
+#define DEBUG_PRINT2(x1, x2)
+#define DEBUG_PRINT3(x1, x2, x3)
+#define DEBUG_PRINT4(x1, x2, x3, x4)
+#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e)
+#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2)
+
+#endif /* not DEBUG */
+
+/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can
+ also be assigned to arbitrarily: each pattern buffer stores its own
+ syntax, so it can be changed between regex compilations. */
+reg_syntax_t re_syntax_options = RE_SYNTAX_EMACS;
+
+
+/* Specify the precise syntax of regexps for compilation. This provides
+ for compatibility for various utilities which historically have
+ different, incompatible syntaxes.
+
+ The argument SYNTAX is a bit mask comprised of the various bits
+ defined in regex.h. We return the old syntax. */
+
+reg_syntax_t
+re_set_syntax (syntax)
+ reg_syntax_t syntax;
+{
+ reg_syntax_t ret = re_syntax_options;
+
+ re_syntax_options = syntax;
+ return ret;
+}
+
+/* This table gives an error message for each of the error codes listed
+ in regex.h. Obviously the order here has to be same as there. */
+
+static const char *re_error_msg[] =
+ { NULL, /* REG_NOERROR */
+ "No match", /* REG_NOMATCH */
+ "Invalid regular expression", /* REG_BADPAT */
+ "Invalid collation character", /* REG_ECOLLATE */
+ "Invalid character class name", /* REG_ECTYPE */
+ "Trailing backslash", /* REG_EESCAPE */
+ "Invalid back reference", /* REG_ESUBREG */
+ "Unmatched [ or [^", /* REG_EBRACK */
+ "Unmatched ( or \\(", /* REG_EPAREN */
+ "Unmatched \\{", /* REG_EBRACE */
+ "Invalid content of \\{\\}", /* REG_BADBR */
+ "Invalid range end", /* REG_ERANGE */
+ "Memory exhausted", /* REG_ESPACE */
+ "Invalid preceding regular expression", /* REG_BADRPT */
+ "Premature end of regular expression", /* REG_EEND */
+ "Regular expression too big", /* REG_ESIZE */
+ "Unmatched ) or \\)", /* REG_ERPAREN */
+ };
+
+/* Subroutine declarations and macros for regex_compile. */
+
+static void store_op1 (), store_op2 ();
+static void insert_op1 (), insert_op2 ();
+static boolean at_begline_loc_p (), at_endline_loc_p ();
+static boolean group_in_compile_stack ();
+static reg_errcode_t compile_range ();
+
+/* Fetch the next character in the uncompiled pattern---translating it
+ if necessary. Also cast from a signed character in the constant
+ string passed to us by the user to an unsigned char that we can use
+ as an array index (in, e.g., `translate'). */
+#define PATFETCH(c) \
+ do {if (p == pend) return REG_EEND; \
+ c = (unsigned char) *p++; \
+ if (translate) c = translate[c]; \
+ } while (0)
+
+/* Fetch the next character in the uncompiled pattern, with no
+ translation. */
+#define PATFETCH_RAW(c) \
+ do {if (p == pend) return REG_EEND; \
+ c = (unsigned char) *p++; \
+ } while (0)
+
+/* Go backwards one character in the pattern. */
+#define PATUNFETCH p--
+
+
+/* If `translate' is non-null, return translate[D], else just D. We
+ cast the subscript to translate because some data is declared as
+ `char *', to avoid warnings when a string constant is passed. But
+ when we use a character as a subscript we must make it unsigned. */
+#define TRANSLATE(d) (translate ? translate[(unsigned char) (d)] : (d))
+
+
+/* Macros for outputting the compiled pattern into `buffer'. */
+
+/* If the buffer isn't allocated when it comes in, use this. */
+#define INIT_BUF_SIZE 32
+
+/* Make sure we have at least N more bytes of space in buffer. */
+#define GET_BUFFER_SPACE(n) \
+ while (b - bufp->buffer + (n) > bufp->allocated) \
+ EXTEND_BUFFER ()
+
+/* Make sure we have one more byte of buffer space and then add C to it. */
+#define BUF_PUSH(c) \
+ do { \
+ GET_BUFFER_SPACE (1); \
+ *b++ = (unsigned char) (c); \
+ } while (0)
+
+
+/* Ensure we have two more bytes of buffer space and then append C1 and C2. */
+#define BUF_PUSH_2(c1, c2) \
+ do { \
+ GET_BUFFER_SPACE (2); \
+ *b++ = (unsigned char) (c1); \
+ *b++ = (unsigned char) (c2); \
+ } while (0)
+
+
+/* As with BUF_PUSH_2, except for three bytes. */
+#define BUF_PUSH_3(c1, c2, c3) \
+ do { \
+ GET_BUFFER_SPACE (3); \
+ *b++ = (unsigned char) (c1); \
+ *b++ = (unsigned char) (c2); \
+ *b++ = (unsigned char) (c3); \
+ } while (0)
+
+
+/* Store a jump with opcode OP at LOC to location TO. We store a
+ relative address offset by the three bytes the jump itself occupies. */
+#define STORE_JUMP(op, loc, to) \
+ store_op1 (op, loc, (to) - (loc) - 3)
+
+/* Likewise, for a two-argument jump. */
+#define STORE_JUMP2(op, loc, to, arg) \
+ store_op2 (op, loc, (to) - (loc) - 3, arg)
+
+/* Like `STORE_JUMP', but for inserting. Assume `b' is the buffer end. */
+#define INSERT_JUMP(op, loc, to) \
+ insert_op1 (op, loc, (to) - (loc) - 3, b)
+
+/* Like `STORE_JUMP2', but for inserting. Assume `b' is the buffer end. */
+#define INSERT_JUMP2(op, loc, to, arg) \
+ insert_op2 (op, loc, (to) - (loc) - 3, arg, b)
+
+
+/* This is not an arbitrary limit: the arguments which represent offsets
+ into the pattern are two bytes long. So if 2^16 bytes turns out to
+ be too small, many things would have to change. */
+#define MAX_BUF_SIZE (1L << 16)
+
+
+/* Extend the buffer by twice its current size via realloc and
+ reset the pointers that pointed into the old block to point to the
+ correct places in the new one. If extending the buffer results in it
+ being larger than MAX_BUF_SIZE, then flag memory exhausted. */
+#define EXTEND_BUFFER() \
+ do { \
+ unsigned char *old_buffer = bufp->buffer; \
+ if (bufp->allocated == MAX_BUF_SIZE) \
+ return REG_ESIZE; \
+ bufp->allocated <<= 1; \
+ if (bufp->allocated > MAX_BUF_SIZE) \
+ bufp->allocated = MAX_BUF_SIZE; \
+ bufp->buffer = (unsigned char *) realloc (bufp->buffer, bufp->allocated);\
+ if (bufp->buffer == NULL) \
+ return REG_ESPACE; \
+ /* If the buffer moved, move all the pointers into it. */ \
+ if (old_buffer != bufp->buffer) \
+ { \
+ b = (b - old_buffer) + bufp->buffer; \
+ begalt = (begalt - old_buffer) + bufp->buffer; \
+ if (fixup_alt_jump) \
+ fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer;\
+ if (laststart) \
+ laststart = (laststart - old_buffer) + bufp->buffer; \
+ if (pending_exact) \
+ pending_exact = (pending_exact - old_buffer) + bufp->buffer; \
+ } \
+ } while (0)
+
+
+/* Since we have one byte reserved for the register number argument to
+ {start,stop}_memory, the maximum number of groups we can report
+ things about is what fits in that byte. */
+#define MAX_REGNUM 255
+
+/* But patterns can have more than `MAX_REGNUM' registers. We just
+ ignore the excess. */
+typedef unsigned regnum_t;
+
+
+/* Macros for the compile stack. */
+
+/* Since offsets can go either forwards or backwards, this type needs to
+ be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. */
+typedef int pattern_offset_t;
+
+typedef struct
+{
+ pattern_offset_t begalt_offset;
+ pattern_offset_t fixup_alt_jump;
+ pattern_offset_t inner_group_offset;
+ pattern_offset_t laststart_offset;
+ regnum_t regnum;
+} compile_stack_elt_t;
+
+
+typedef struct
+{
+ compile_stack_elt_t *stack;
+ unsigned size;
+ unsigned avail; /* Offset of next open position. */
+} compile_stack_type;
+
+
+#define INIT_COMPILE_STACK_SIZE 32
+
+#define COMPILE_STACK_EMPTY (compile_stack.avail == 0)
+#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size)
+
+/* The next available element. */
+#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail])
+
+
+/* Set the bit for character C in a list. */
+#define SET_LIST_BIT(c) \
+ (b[((unsigned char) (c)) / BYTEWIDTH] \
+ |= 1 << (((unsigned char) c) % BYTEWIDTH))
+
+
+/* Get the next unsigned number in the uncompiled pattern. */
+#define GET_UNSIGNED_NUMBER(num) \
+ { if (p != pend) \
+ { \
+ PATFETCH (c); \
+ while (ISDIGIT (c)) \
+ { \
+ if (num < 0) \
+ num = 0; \
+ num = num * 10 + c - '0'; \
+ if (p == pend) \
+ break; \
+ PATFETCH (c); \
+ } \
+ } \
+ }
+
+#define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */
+
+#define IS_CHAR_CLASS(string) \
+ (STREQ (string, "alpha") || STREQ (string, "upper") \
+ || STREQ (string, "lower") || STREQ (string, "digit") \
+ || STREQ (string, "alnum") || STREQ (string, "xdigit") \
+ || STREQ (string, "space") || STREQ (string, "print") \
+ || STREQ (string, "punct") || STREQ (string, "graph") \
+ || STREQ (string, "cntrl") || STREQ (string, "blank"))
+
+/* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX.
+ Returns one of error codes defined in `regex.h', or zero for success.
+
+ Assumes the `allocated' (and perhaps `buffer') and `translate'
+ fields are set in BUFP on entry.
+
+ If it succeeds, results are put in BUFP (if it returns an error, the
+ contents of BUFP are undefined):
+ `buffer' is the compiled pattern;
+ `syntax' is set to SYNTAX;
+ `used' is set to the length of the compiled pattern;
+ `fastmap_accurate' is zero;
+ `re_nsub' is the number of subexpressions in PATTERN;
+ `not_bol' and `not_eol' are zero;
+
+ The `fastmap' and `newline_anchor' fields are neither
+ examined nor set. */
+
+static reg_errcode_t
+regex_compile (pattern, size, syntax, bufp)
+ const char *pattern;
+ int size;
+ reg_syntax_t syntax;
+ struct re_pattern_buffer *bufp;
+{
+ /* We fetch characters from PATTERN here. Even though PATTERN is
+ `char *' (i.e., signed), we declare these variables as unsigned, so
+ they can be reliably used as array indices. */
+ register unsigned char c, c1;
+
+ /* A random tempory spot in PATTERN. */
+ const char *p1;
+
+ /* Points to the end of the buffer, where we should append. */
+ register unsigned char *b;
+
+ /* Keeps track of unclosed groups. */
+ compile_stack_type compile_stack;
+
+ /* Points to the current (ending) position in the pattern. */
+ const char *p = pattern;
+ const char *pend = pattern + size;
+
+ /* How to translate the characters in the pattern. */
+ char *translate = bufp->translate;
+
+ /* Address of the count-byte of the most recently inserted `exactn'
+ command. This makes it possible to tell if a new exact-match
+ character can be added to that command or if the character requires
+ a new `exactn' command. */
+ unsigned char *pending_exact = 0;
+
+ /* Address of start of the most recently finished expression.
+ This tells, e.g., postfix * where to find the start of its
+ operand. Reset at the beginning of groups and alternatives. */
+ unsigned char *laststart = 0;
+
+ /* Address of beginning of regexp, or inside of last group. */
+ unsigned char *begalt;
+
+ /* Place in the uncompiled pattern (i.e., the {) to
+ which to go back if the interval is invalid. */
+ const char *beg_interval;
+
+ /* Address of the place where a forward jump should go to the end of
+ the containing expression. Each alternative of an `or' -- except the
+ last -- ends with a forward jump of this sort. */
+ unsigned char *fixup_alt_jump = 0;
+
+ /* Counts open-groups as they are encountered. Remembered for the
+ matching close-group on the compile stack, so the same register
+ number is put in the stop_memory as the start_memory. */
+ regnum_t regnum = 0;
+
+#ifdef DEBUG
+ DEBUG_PRINT1 ("\nCompiling pattern: ");
+ if (debug)
+ {
+ unsigned debug_count;
+
+ for (debug_count = 0; debug_count < size; debug_count++)
+ printchar (pattern[debug_count]);
+ putchar ('\n');
+ }
+#endif /* DEBUG */
+
+ /* Initialize the compile stack. */
+ compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t);
+ if (compile_stack.stack == NULL)
+ return REG_ESPACE;
+
+ compile_stack.size = INIT_COMPILE_STACK_SIZE;
+ compile_stack.avail = 0;
+
+ /* Initialize the pattern buffer. */
+ bufp->syntax = syntax;
+ bufp->fastmap_accurate = 0;
+ bufp->not_bol = bufp->not_eol = 0;
+
+ /* Set `used' to zero, so that if we return an error, the pattern
+ printer (for debugging) will think there's no pattern. We reset it
+ at the end. */
+ bufp->used = 0;
+
+ /* Always count groups, whether or not bufp->no_sub is set. */
+ bufp->re_nsub = 0;
+
+#if !defined (emacs) && !defined (SYNTAX_TABLE)
+ /* Initialize the syntax table. */
+ init_syntax_once ();
+#endif
+
+ if (bufp->allocated == 0)
+ {
+ if (bufp->buffer)
+ { /* If zero allocated, but buffer is non-null, try to realloc
+ enough space. This loses if buffer's address is bogus, but
+ that is the user's responsibility. */
+ RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char);
+ }
+ else
+ { /* Caller did not allocate a buffer. Do it for them. */
+ bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char);
+ }
+ if (!bufp->buffer) return REG_ESPACE;
+
+ bufp->allocated = INIT_BUF_SIZE;
+ }
+
+ begalt = b = bufp->buffer;
+
+ /* Loop through the uncompiled pattern until we're at the end. */
+ while (p != pend)
+ {
+ PATFETCH (c);
+
+ switch (c)
+ {
+ case '^':
+ {
+ if ( /* If at start of pattern, it's an operator. */
+ p == pattern + 1
+ /* If context independent, it's an operator. */
+ || syntax & RE_CONTEXT_INDEP_ANCHORS
+ /* Otherwise, depends on what's come before. */
+ || at_begline_loc_p (pattern, p, syntax))
+ BUF_PUSH (begline);
+ else
+ goto normal_char;
+ }
+ break;
+
+
+ case '$':
+ {
+ if ( /* If at end of pattern, it's an operator. */
+ p == pend
+ /* If context independent, it's an operator. */
+ || syntax & RE_CONTEXT_INDEP_ANCHORS
+ /* Otherwise, depends on what's next. */
+ || at_endline_loc_p (p, pend, syntax))
+ BUF_PUSH (endline);
+ else
+ goto normal_char;
+ }
+ break;
+
+
+ case '+':
+ case '?':
+ if ((syntax & RE_BK_PLUS_QM)
+ || (syntax & RE_LIMITED_OPS))
+ goto normal_char;
+ handle_plus:
+ case '*':
+ /* If there is no previous pattern... */
+ if (!laststart)
+ {
+ if (syntax & RE_CONTEXT_INVALID_OPS)
+ return REG_BADRPT;
+ else if (!(syntax & RE_CONTEXT_INDEP_OPS))
+ goto normal_char;
+ }
+
+ {
+ /* Are we optimizing this jump? */
+ boolean keep_string_p = false;
+
+ /* 1 means zero (many) matches is allowed. */
+ char zero_times_ok = 0, many_times_ok = 0;
+
+ /* If there is a sequence of repetition chars, collapse it
+ down to just one (the right one). We can't combine
+ interval operators with these because of, e.g., `a{2}*',
+ which should only match an even number of `a's. */
+
+ for (;;)
+ {
+ zero_times_ok |= c != '+';
+ many_times_ok |= c != '?';
+
+ if (p == pend)
+ break;
+
+ PATFETCH (c);
+
+ if (c == '*'
+ || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?')))
+ ;
+
+ else if (syntax & RE_BK_PLUS_QM && c == '\\')
+ {
+ if (p == pend) return REG_EESCAPE;
+
+ PATFETCH (c1);
+ if (!(c1 == '+' || c1 == '?'))
+ {
+ PATUNFETCH;
+ PATUNFETCH;
+ break;
+ }
+
+ c = c1;
+ }
+ else
+ {
+ PATUNFETCH;
+ break;
+ }
+
+ /* If we get here, we found another repeat character. */
+ }
+
+ /* Star, etc. applied to an empty pattern is equivalent
+ to an empty pattern. */
+ if (!laststart)
+ break;
+
+ /* Now we know whether or not zero matches is allowed
+ and also whether or not two or more matches is allowed. */
+ if (many_times_ok)
+ { /* More than one repetition is allowed, so put in at the
+ end a backward relative jump from `b' to before the next
+ jump we're going to put in below (which jumps from
+ laststart to after this jump).
+
+ But if we are at the `*' in the exact sequence `.*\n',
+ insert an unconditional jump backwards to the .,
+ instead of the beginning of the loop. This way we only
+ push a failure point once, instead of every time
+ through the loop. */
+ assert (p - 1 > pattern);
+
+ /* Allocate the space for the jump. */
+ GET_BUFFER_SPACE (3);
+
+ /* We know we are not at the first character of the pattern,
+ because laststart was nonzero. And we've already
+ incremented `p', by the way, to be the character after
+ the `*'. Do we have to do something analogous here
+ for null bytes, because of RE_DOT_NOT_NULL? */
+ if (TRANSLATE (*(p - 2)) == TRANSLATE ('.')
+ && zero_times_ok
+ && p < pend && TRANSLATE (*p) == TRANSLATE ('\n')
+ && !(syntax & RE_DOT_NEWLINE))
+ { /* We have .*\n. */
+ STORE_JUMP (jump, b, laststart);
+ keep_string_p = true;
+ }
+ else
+ /* Anything else. */
+ STORE_JUMP (maybe_pop_jump, b, laststart - 3);
+
+ /* We've added more stuff to the buffer. */
+ b += 3;
+ }
+
+ /* On failure, jump from laststart to b + 3, which will be the
+ end of the buffer after this jump is inserted. */
+ GET_BUFFER_SPACE (3);
+ INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump
+ : on_failure_jump,
+ laststart, b + 3);
+ pending_exact = 0;
+ b += 3;
+
+ if (!zero_times_ok)
+ {
+ /* At least one repetition is required, so insert a
+ `dummy_failure_jump' before the initial
+ `on_failure_jump' instruction of the loop. This
+ effects a skip over that instruction the first time
+ we hit that loop. */
+ GET_BUFFER_SPACE (3);
+ INSERT_JUMP (dummy_failure_jump, laststart, laststart + 6);
+ b += 3;
+ }
+ }
+ break;
+
+
+ case '.':
+ laststart = b;
+ BUF_PUSH (anychar);
+ break;
+
+
+ case '[':
+ {
+ boolean had_char_class = false;
+
+ if (p == pend) return REG_EBRACK;
+
+ /* Ensure that we have enough space to push a charset: the
+ opcode, the length count, and the bitset; 34 bytes in all. */
+ GET_BUFFER_SPACE (34);
+
+ laststart = b;
+
+ /* We test `*p == '^' twice, instead of using an if
+ statement, so we only need one BUF_PUSH. */
+ BUF_PUSH (*p == '^' ? charset_not : charset);
+ if (*p == '^')
+ p++;
+
+ /* Remember the first position in the bracket expression. */
+ p1 = p;
+
+ /* Push the number of bytes in the bitmap. */
+ BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH);
+
+ /* Clear the whole map. */
+ bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH);
+
+ /* charset_not matches newline according to a syntax bit. */
+ if ((re_opcode_t) b[-2] == charset_not
+ && (syntax & RE_HAT_LISTS_NOT_NEWLINE))
+ SET_LIST_BIT ('\n');
+
+ /* Read in characters and ranges, setting map bits. */
+ for (;;)
+ {
+ if (p == pend) return REG_EBRACK;
+
+ PATFETCH (c);
+
+ /* \ might escape characters inside [...] and [^...]. */
+ if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\')
+ {
+ if (p == pend) return REG_EESCAPE;
+
+ PATFETCH (c1);
+ SET_LIST_BIT (c1);
+ continue;
+ }
+
+ /* Could be the end of the bracket expression. If it's
+ not (i.e., when the bracket expression is `[]' so
+ far), the ']' character bit gets set way below. */
+ if (c == ']' && p != p1 + 1)
+ break;
+
+ /* Look ahead to see if it's a range when the last thing
+ was a character class. */
+ if (had_char_class && c == '-' && *p != ']')
+ return REG_ERANGE;
+
+ /* Look ahead to see if it's a range when the last thing
+ was a character: if this is a hyphen not at the
+ beginning or the end of a list, then it's the range
+ operator. */
+ if (c == '-'
+ && !(p - 2 >= pattern && p[-2] == '[')
+ && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^')
+ && *p != ']')
+ {
+ reg_errcode_t ret
+ = compile_range (&p, pend, translate, syntax, b);
+ if (ret != REG_NOERROR) return ret;
+ }
+
+ else if (p[0] == '-' && p[1] != ']')
+ { /* This handles ranges made up of characters only. */
+ reg_errcode_t ret;
+
+ /* Move past the `-'. */
+ PATFETCH (c1);
+
+ ret = compile_range (&p, pend, translate, syntax, b);
+ if (ret != REG_NOERROR) return ret;
+ }
+
+ /* See if we're at the beginning of a possible character
+ class. */
+
+ else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':')
+ { /* Leave room for the null. */
+ char str[CHAR_CLASS_MAX_LENGTH + 1];
+
+ PATFETCH (c);
+ c1 = 0;
+
+ /* If pattern is `[[:'. */
+ if (p == pend) return REG_EBRACK;
+
+ for (;;)
+ {
+ PATFETCH (c);
+ if (c == ':' || c == ']' || p == pend
+ || c1 == CHAR_CLASS_MAX_LENGTH)
+ break;
+ str[c1++] = c;
+ }
+ str[c1] = '\0';
+
+ /* If isn't a word bracketed by `[:' and:`]':
+ undo the ending character, the letters, and leave
+ the leading `:' and `[' (but set bits for them). */
+ if (c == ':' && *p == ']')
+ {
+ int ch;
+ boolean is_alnum = STREQ (str, "alnum");
+ boolean is_alpha = STREQ (str, "alpha");
+ boolean is_blank = STREQ (str, "blank");
+ boolean is_cntrl = STREQ (str, "cntrl");
+ boolean is_digit = STREQ (str, "digit");
+ boolean is_graph = STREQ (str, "graph");
+ boolean is_lower = STREQ (str, "lower");
+ boolean is_print = STREQ (str, "print");
+ boolean is_punct = STREQ (str, "punct");
+ boolean is_space = STREQ (str, "space");
+ boolean is_upper = STREQ (str, "upper");
+ boolean is_xdigit = STREQ (str, "xdigit");
+
+ if (!IS_CHAR_CLASS (str)) return REG_ECTYPE;
+
+ /* Throw away the ] at the end of the character
+ class. */
+ PATFETCH (c);
+
+ if (p == pend) return REG_EBRACK;
+
+ for (ch = 0; ch < 1 << BYTEWIDTH; ch++)
+ {
+ if ( (is_alnum && ISALNUM (ch))
+ || (is_alpha && ISALPHA (ch))
+ || (is_blank && ISBLANK (ch))
+ || (is_cntrl && ISCNTRL (ch))
+ || (is_digit && ISDIGIT (ch))
+ || (is_graph && ISGRAPH (ch))
+ || (is_lower && ISLOWER (ch))
+ || (is_print && ISPRINT (ch))
+ || (is_punct && ISPUNCT (ch))
+ || (is_space && ISSPACE (ch))
+ || (is_upper && ISUPPER (ch))
+ || (is_xdigit && ISXDIGIT (ch)))
+ SET_LIST_BIT (ch);
+ }
+ had_char_class = true;
+ }
+ else
+ {
+ c1++;
+ while (c1--)
+ PATUNFETCH;
+ SET_LIST_BIT ('[');
+ SET_LIST_BIT (':');
+ had_char_class = false;
+ }
+ }
+ else
+ {
+ had_char_class = false;
+ SET_LIST_BIT (c);
+ }
+ }
+
+ /* Discard any (non)matching list bytes that are all 0 at the
+ end of the map. Decrease the map-length byte too. */
+ while ((int) b[-1] > 0 && b[b[-1] - 1] == 0)
+ b[-1]--;
+ b += b[-1];
+ }
+ break;
+
+
+ case '(':
+ if (syntax & RE_NO_BK_PARENS)
+ goto handle_open;
+ else
+ goto normal_char;
+
+
+ case ')':
+ if (syntax & RE_NO_BK_PARENS)
+ goto handle_close;
+ else
+ goto normal_char;
+
+
+ case '\n':
+ if (syntax & RE_NEWLINE_ALT)
+ goto handle_alt;
+ else
+ goto normal_char;
+
+
+ case '|':
+ if (syntax & RE_NO_BK_VBAR)
+ goto handle_alt;
+ else
+ goto normal_char;
+
+
+ case '{':
+ if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES)
+ goto handle_interval;
+ else
+ goto normal_char;
+
+
+ case '\\':
+ if (p == pend) return REG_EESCAPE;
+
+ /* Do not translate the character after the \, so that we can
+ distinguish, e.g., \B from \b, even if we normally would
+ translate, e.g., B to b. */
+ PATFETCH_RAW (c);
+
+ switch (c)
+ {
+ case '(':
+ if (syntax & RE_NO_BK_PARENS)
+ goto normal_backslash;
+
+ handle_open:
+ bufp->re_nsub++;
+ regnum++;
+
+ if (COMPILE_STACK_FULL)
+ {
+ RETALLOC (compile_stack.stack, compile_stack.size << 1,
+ compile_stack_elt_t);
+ if (compile_stack.stack == NULL) return REG_ESPACE;
+
+ compile_stack.size <<= 1;
+ }
+
+ /* These are the values to restore when we hit end of this
+ group. They are all relative offsets, so that if the
+ whole pattern moves because of realloc, they will still
+ be valid. */
+ COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer;
+ COMPILE_STACK_TOP.fixup_alt_jump
+ = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0;
+ COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer;
+ COMPILE_STACK_TOP.regnum = regnum;
+
+ /* We will eventually replace the 0 with the number of
+ groups inner to this one. But do not push a
+ start_memory for groups beyond the last one we can
+ represent in the compiled pattern. */
+ if (regnum <= MAX_REGNUM)
+ {
+ COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2;
+ BUF_PUSH_3 (start_memory, regnum, 0);
+ }
+
+ compile_stack.avail++;
+
+ fixup_alt_jump = 0;
+ laststart = 0;
+ begalt = b;
+ /* If we've reached MAX_REGNUM groups, then this open
+ won't actually generate any code, so we'll have to
+ clear pending_exact explicitly. */
+ pending_exact = 0;
+ break;
+
+
+ case ')':
+ if (syntax & RE_NO_BK_PARENS) goto normal_backslash;
+
+ if (COMPILE_STACK_EMPTY)
+ if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)
+ goto normal_backslash;
+ else
+ return REG_ERPAREN;
+
+ handle_close:
+ if (fixup_alt_jump)
+ { /* Push a dummy failure point at the end of the
+ alternative for a possible future
+ `pop_failure_jump' to pop. See comments at
+ `push_dummy_failure' in `re_match_2'. */
+ BUF_PUSH (push_dummy_failure);
+
+ /* We allocated space for this jump when we assigned
+ to `fixup_alt_jump', in the `handle_alt' case below. */
+ STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1);
+ }
+
+ /* See similar code for backslashed left paren above. */
+ if (COMPILE_STACK_EMPTY)
+ if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD)
+ goto normal_char;
+ else
+ return REG_ERPAREN;
+
+ /* Since we just checked for an empty stack above, this
+ ``can't happen''. */
+ assert (compile_stack.avail != 0);
+ {
+ /* We don't just want to restore into `regnum', because
+ later groups should continue to be numbered higher,
+ as in `(ab)c(de)' -- the second group is #2. */
+ regnum_t this_group_regnum;
+
+ compile_stack.avail--;
+ begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset;
+ fixup_alt_jump
+ = COMPILE_STACK_TOP.fixup_alt_jump
+ ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1
+ : 0;
+ laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset;
+ this_group_regnum = COMPILE_STACK_TOP.regnum;
+ /* If we've reached MAX_REGNUM groups, then this open
+ won't actually generate any code, so we'll have to
+ clear pending_exact explicitly. */
+ pending_exact = 0;
+
+ /* We're at the end of the group, so now we know how many
+ groups were inside this one. */
+ if (this_group_regnum <= MAX_REGNUM)
+ {
+ unsigned char *inner_group_loc
+ = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset;
+
+ *inner_group_loc = regnum - this_group_regnum;
+ BUF_PUSH_3 (stop_memory, this_group_regnum,
+ regnum - this_group_regnum);
+ }
+ }
+ break;
+
+
+ case '|': /* `\|'. */
+ if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR)
+ goto normal_backslash;
+ handle_alt:
+ if (syntax & RE_LIMITED_OPS)
+ goto normal_char;
+
+ /* Insert before the previous alternative a jump which
+ jumps to this alternative if the former fails. */
+ GET_BUFFER_SPACE (3);
+ INSERT_JUMP (on_failure_jump, begalt, b + 6);
+ pending_exact = 0;
+ b += 3;
+
+ /* The alternative before this one has a jump after it
+ which gets executed if it gets matched. Adjust that
+ jump so it will jump to this alternative's analogous
+ jump (put in below, which in turn will jump to the next
+ (if any) alternative's such jump, etc.). The last such
+ jump jumps to the correct final destination. A picture:
+ _____ _____
+ | | | |
+ | v | v
+ a | b | c
+
+ If we are at `b', then fixup_alt_jump right now points to a
+ three-byte space after `a'. We'll put in the jump, set
+ fixup_alt_jump to right after `b', and leave behind three
+ bytes which we'll fill in when we get to after `c'. */
+
+ if (fixup_alt_jump)
+ STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
+
+ /* Mark and leave space for a jump after this alternative,
+ to be filled in later either by next alternative or
+ when know we're at the end of a series of alternatives. */
+ fixup_alt_jump = b;
+ GET_BUFFER_SPACE (3);
+ b += 3;
+
+ laststart = 0;
+ begalt = b;
+ break;
+
+
+ case '{':
+ /* If \{ is a literal. */
+ if (!(syntax & RE_INTERVALS)
+ /* If we're at `\{' and it's not the open-interval
+ operator. */
+ || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
+ || (p - 2 == pattern && p == pend))
+ goto normal_backslash;
+
+ handle_interval:
+ {
+ /* If got here, then the syntax allows intervals. */
+
+ /* At least (most) this many matches must be made. */
+ int lower_bound = -1, upper_bound = -1;
+
+ beg_interval = p - 1;
+
+ if (p == pend)
+ {
+ if (syntax & RE_NO_BK_BRACES)
+ goto unfetch_interval;
+ else
+ return REG_EBRACE;
+ }
+
+ GET_UNSIGNED_NUMBER (lower_bound);
+
+ if (c == ',')
+ {
+ GET_UNSIGNED_NUMBER (upper_bound);
+ if (upper_bound < 0) upper_bound = RE_DUP_MAX;
+ }
+ else
+ /* Interval such as `{1}' => match exactly once. */
+ upper_bound = lower_bound;
+
+ if (lower_bound < 0 || upper_bound > RE_DUP_MAX
+ || lower_bound > upper_bound)
+ {
+ if (syntax & RE_NO_BK_BRACES)
+ goto unfetch_interval;
+ else
+ return REG_BADBR;
+ }
+
+ if (!(syntax & RE_NO_BK_BRACES))
+ {
+ if (c != '\\') return REG_EBRACE;
+
+ PATFETCH (c);
+ }
+
+ if (c != '}')
+ {
+ if (syntax & RE_NO_BK_BRACES)
+ goto unfetch_interval;
+ else
+ return REG_BADBR;
+ }
+
+ /* We just parsed a valid interval. */
+
+ /* If it's invalid to have no preceding re. */
+ if (!laststart)
+ {
+ if (syntax & RE_CONTEXT_INVALID_OPS)
+ return REG_BADRPT;
+ else if (syntax & RE_CONTEXT_INDEP_OPS)
+ laststart = b;
+ else
+ goto unfetch_interval;
+ }
+
+ /* If the upper bound is zero, don't want to succeed at
+ all; jump from `laststart' to `b + 3', which will be
+ the end of the buffer after we insert the jump. */
+ if (upper_bound == 0)
+ {
+ GET_BUFFER_SPACE (3);
+ INSERT_JUMP (jump, laststart, b + 3);
+ b += 3;
+ }
+
+ /* Otherwise, we have a nontrivial interval. When
+ we're all done, the pattern will look like:
+ set_number_at <jump count> <upper bound>
+ set_number_at <succeed_n count> <lower bound>
+ succeed_n <after jump addr> <succed_n count>
+ <body of loop>
+ jump_n <succeed_n addr> <jump count>
+ (The upper bound and `jump_n' are omitted if
+ `upper_bound' is 1, though.) */
+ else
+ { /* If the upper bound is > 1, we need to insert
+ more at the end of the loop. */
+ unsigned nbytes = 10 + (upper_bound > 1) * 10;
+
+ GET_BUFFER_SPACE (nbytes);
+
+ /* Initialize lower bound of the `succeed_n', even
+ though it will be set during matching by its
+ attendant `set_number_at' (inserted next),
+ because `re_compile_fastmap' needs to know.
+ Jump to the `jump_n' we might insert below. */
+ INSERT_JUMP2 (succeed_n, laststart,
+ b + 5 + (upper_bound > 1) * 5,
+ lower_bound);
+ b += 5;
+
+ /* Code to initialize the lower bound. Insert
+ before the `succeed_n'. The `5' is the last two
+ bytes of this `set_number_at', plus 3 bytes of
+ the following `succeed_n'. */
+ insert_op2 (set_number_at, laststart, 5, lower_bound, b);
+ b += 5;
+
+ if (upper_bound > 1)
+ { /* More than one repetition is allowed, so
+ append a backward jump to the `succeed_n'
+ that starts this interval.
+
+ When we've reached this during matching,
+ we'll have matched the interval once, so
+ jump back only `upper_bound - 1' times. */
+ STORE_JUMP2 (jump_n, b, laststart + 5,
+ upper_bound - 1);
+ b += 5;
+
+ /* The location we want to set is the second
+ parameter of the `jump_n'; that is `b-2' as
+ an absolute address. `laststart' will be
+ the `set_number_at' we're about to insert;
+ `laststart+3' the number to set, the source
+ for the relative address. But we are
+ inserting into the middle of the pattern --
+ so everything is getting moved up by 5.
+ Conclusion: (b - 2) - (laststart + 3) + 5,
+ i.e., b - laststart.
+
+ We insert this at the beginning of the loop
+ so that if we fail during matching, we'll
+ reinitialize the bounds. */
+ insert_op2 (set_number_at, laststart, b - laststart,
+ upper_bound - 1, b);
+ b += 5;
+ }
+ }
+ pending_exact = 0;
+ beg_interval = NULL;
+ }
+ break;
+
+ unfetch_interval:
+ /* If an invalid interval, match the characters as literals. */
+ assert (beg_interval);
+ p = beg_interval;
+ beg_interval = NULL;
+
+ /* normal_char and normal_backslash need `c'. */
+ PATFETCH (c);
+
+ if (!(syntax & RE_NO_BK_BRACES))
+ {
+ if (p > pattern && p[-1] == '\\')
+ goto normal_backslash;
+ }
+ goto normal_char;
+
+#ifdef emacs
+ /* There is no way to specify the before_dot and after_dot
+ operators. rms says this is ok. --karl */
+ case '=':
+ BUF_PUSH (at_dot);
+ break;
+
+ case 's':
+ laststart = b;
+ PATFETCH (c);
+ BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]);
+ break;
+
+ case 'S':
+ laststart = b;
+ PATFETCH (c);
+ BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]);
+ break;
+#endif /* emacs */
+
+
+ case 'w':
+ laststart = b;
+ BUF_PUSH (wordchar);
+ break;
+
+
+ case 'W':
+ laststart = b;
+ BUF_PUSH (notwordchar);
+ break;
+
+
+ case '<':
+ BUF_PUSH (wordbeg);
+ break;
+
+ case '>':
+ BUF_PUSH (wordend);
+ break;
+
+ case 'b':
+ BUF_PUSH (wordbound);
+ break;
+
+ case 'B':
+ BUF_PUSH (notwordbound);
+ break;
+
+ case '`':
+ BUF_PUSH (begbuf);
+ break;
+
+ case '\'':
+ BUF_PUSH (endbuf);
+ break;
+
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ if (syntax & RE_NO_BK_REFS)
+ goto normal_char;
+
+ c1 = c - '0';
+
+ if (c1 > regnum)
+ return REG_ESUBREG;
+
+ /* Can't back reference to a subexpression if inside of it. */
+ if (group_in_compile_stack (compile_stack, c1))
+ goto normal_char;
+
+ laststart = b;
+ BUF_PUSH_2 (duplicate, c1);
+ break;
+
+
+ case '+':
+ case '?':
+ if (syntax & RE_BK_PLUS_QM)
+ goto handle_plus;
+ else
+ goto normal_backslash;
+
+ default:
+ normal_backslash:
+ /* You might think it would be useful for \ to mean
+ not to translate; but if we don't translate it
+ it will never match anything. */
+ c = TRANSLATE (c);
+ goto normal_char;
+ }
+ break;
+
+
+ default:
+ /* Expects the character in `c'. */
+ normal_char:
+ /* If no exactn currently being built. */
+ if (!pending_exact
+
+ /* If last exactn not at current position. */
+ || pending_exact + *pending_exact + 1 != b
+
+ /* We have only one byte following the exactn for the count. */
+ || *pending_exact == (1 << BYTEWIDTH) - 1
+
+ /* If followed by a repetition operator. */
+ || *p == '*' || *p == '^'
+ || ((syntax & RE_BK_PLUS_QM)
+ ? *p == '\\' && (p[1] == '+' || p[1] == '?')
+ : (*p == '+' || *p == '?'))
+ || ((syntax & RE_INTERVALS)
+ && ((syntax & RE_NO_BK_BRACES)
+ ? *p == '{'
+ : (p[0] == '\\' && p[1] == '{'))))
+ {
+ /* Start building a new exactn. */
+
+ laststart = b;
+
+ BUF_PUSH_2 (exactn, 0);
+ pending_exact = b - 1;
+ }
+
+ BUF_PUSH (c);
+ (*pending_exact)++;
+ break;
+ } /* switch (c) */
+ } /* while p != pend */
+
+
+ /* Through the pattern now. */
+
+ if (fixup_alt_jump)
+ STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
+
+ if (!COMPILE_STACK_EMPTY)
+ return REG_EPAREN;
+
+ free (compile_stack.stack);
+
+ /* We have succeeded; set the length of the buffer. */
+ bufp->used = b - bufp->buffer;
+
+#ifdef DEBUG
+ if (debug)
+ {
+ DEBUG_PRINT1 ("\nCompiled pattern: ");
+ print_compiled_pattern (bufp);
+ }
+#endif /* DEBUG */
+
+ return REG_NOERROR;
+} /* regex_compile */
+
+/* Subroutines for `regex_compile'. */
+
+/* Store OP at LOC followed by two-byte integer parameter ARG. */
+
+static void
+store_op1 (op, loc, arg)
+ re_opcode_t op;
+ unsigned char *loc;
+ int arg;
+{
+ *loc = (unsigned char) op;
+ STORE_NUMBER (loc + 1, arg);
+}
+
+
+/* Like `store_op1', but for two two-byte parameters ARG1 and ARG2. */
+
+static void
+store_op2 (op, loc, arg1, arg2)
+ re_opcode_t op;
+ unsigned char *loc;
+ int arg1, arg2;
+{
+ *loc = (unsigned char) op;
+ STORE_NUMBER (loc + 1, arg1);
+ STORE_NUMBER (loc + 3, arg2);
+}
+
+
+/* Copy the bytes from LOC to END to open up three bytes of space at LOC
+ for OP followed by two-byte integer parameter ARG. */
+
+static void
+insert_op1 (op, loc, arg, end)
+ re_opcode_t op;
+ unsigned char *loc;
+ int arg;
+ unsigned char *end;
+{
+ register unsigned char *pfrom = end;
+ register unsigned char *pto = end + 3;
+
+ while (pfrom != loc)
+ *--pto = *--pfrom;
+
+ store_op1 (op, loc, arg);
+}
+
+
+/* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2. */
+
+static void
+insert_op2 (op, loc, arg1, arg2, end)
+ re_opcode_t op;
+ unsigned char *loc;
+ int arg1, arg2;
+ unsigned char *end;
+{
+ register unsigned char *pfrom = end;
+ register unsigned char *pto = end + 5;
+
+ while (pfrom != loc)
+ *--pto = *--pfrom;
+
+ store_op2 (op, loc, arg1, arg2);
+}
+
+
+/* P points to just after a ^ in PATTERN. Return true if that ^ comes
+ after an alternative or a begin-subexpression. We assume there is at
+ least one character before the ^. */
+
+static boolean
+at_begline_loc_p (pattern, p, syntax)
+ const char *pattern, *p;
+ reg_syntax_t syntax;
+{
+ const char *prev = p - 2;
+ boolean prev_prev_backslash = prev > pattern && prev[-1] == '\\';
+
+ return
+ /* After a subexpression? */
+ (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash))
+ /* After an alternative? */
+ || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash));
+}
+
+
+/* The dual of at_begline_loc_p. This one is for $. We assume there is
+ at least one character after the $, i.e., `P < PEND'. */
+
+static boolean
+at_endline_loc_p (p, pend, syntax)
+ const char *p, *pend;
+ int syntax;
+{
+ const char *next = p;
+ boolean next_backslash = *next == '\\';
+ const char *next_next = p + 1 < pend ? p + 1 : NULL;
+
+ return
+ /* Before a subexpression? */
+ (syntax & RE_NO_BK_PARENS ? *next == ')'
+ : next_backslash && next_next && *next_next == ')')
+ /* Before an alternative? */
+ || (syntax & RE_NO_BK_VBAR ? *next == '|'
+ : next_backslash && next_next && *next_next == '|');
+}
+
+
+/* Returns true if REGNUM is in one of COMPILE_STACK's elements and
+ false if it's not. */
+
+static boolean
+group_in_compile_stack (compile_stack, regnum)
+ compile_stack_type compile_stack;
+ regnum_t regnum;
+{
+ int this_element;
+
+ for (this_element = compile_stack.avail - 1;
+ this_element >= 0;
+ this_element--)
+ if (compile_stack.stack[this_element].regnum == regnum)
+ return true;
+
+ return false;
+}
+
+
+/* Read the ending character of a range (in a bracket expression) from the
+ uncompiled pattern *P_PTR (which ends at PEND). We assume the
+ starting character is in `P[-2]'. (`P[-1]' is the character `-'.)
+ Then we set the translation of all bits between the starting and
+ ending characters (inclusive) in the compiled pattern B.
+
+ Return an error code.
+
+ We use these short variable names so we can use the same macros as
+ `regex_compile' itself. */
+
+static reg_errcode_t
+compile_range (p_ptr, pend, translate, syntax, b)
+ const char **p_ptr, *pend;
+ char *translate;
+ reg_syntax_t syntax;
+ unsigned char *b;
+{
+ unsigned this_char;
+
+ const char *p = *p_ptr;
+ int range_start, range_end;
+
+ if (p == pend)
+ return REG_ERANGE;
+
+ /* Even though the pattern is a signed `char *', we need to fetch
+ with unsigned char *'s; if the high bit of the pattern character
+ is set, the range endpoints will be negative if we fetch using a
+ signed char *.
+
+ We also want to fetch the endpoints without translating them; the
+ appropriate translation is done in the bit-setting loop below. */
+ range_start = ((unsigned char *) p)[-2];
+ range_end = ((unsigned char *) p)[0];
+
+ /* Have to increment the pointer into the pattern string, so the
+ caller isn't still at the ending character. */
+ (*p_ptr)++;
+
+ /* If the start is after the end, the range is empty. */
+ if (range_start > range_end)
+ return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR;
+
+ /* Here we see why `this_char' has to be larger than an `unsigned
+ char' -- the range is inclusive, so if `range_end' == 0xff
+ (assuming 8-bit characters), we would otherwise go into an infinite
+ loop, since all characters <= 0xff. */
+ for (this_char = range_start; this_char <= range_end; this_char++)
+ {
+ SET_LIST_BIT (TRANSLATE (this_char));
+ }
+
+ return REG_NOERROR;
+}
+
+/* Failure stack declarations and macros; both re_compile_fastmap and
+ re_match_2 use a failure stack. These have to be macros because of
+ REGEX_ALLOCATE. */
+
+
+/* Number of failure points for which to initially allocate space
+ when matching. If this number is exceeded, we allocate more
+ space, so it is not a hard limit. */
+#ifndef INIT_FAILURE_ALLOC
+#define INIT_FAILURE_ALLOC 5
+#endif
+
+/* Roughly the maximum number of failure points on the stack. Would be
+ exactly that if always used MAX_FAILURE_SPACE each time we failed.
+ This is a variable only so users of regex can assign to it; we never
+ change it ourselves. */
+int re_max_failures = 2000;
+
+typedef const unsigned char *fail_stack_elt_t;
+
+typedef struct
+{
+ fail_stack_elt_t *stack;
+ unsigned size;
+ unsigned avail; /* Offset of next open position. */
+} fail_stack_type;
+
+#define FAIL_STACK_EMPTY() (fail_stack.avail == 0)
+#define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0)
+#define FAIL_STACK_FULL() (fail_stack.avail == fail_stack.size)
+#define FAIL_STACK_TOP() (fail_stack.stack[fail_stack.avail])
+
+
+/* Initialize `fail_stack'. Do `return -2' if the alloc fails. */
+
+#define INIT_FAIL_STACK() \
+ do { \
+ fail_stack.stack = (fail_stack_elt_t *) \
+ REGEX_ALLOCATE (INIT_FAILURE_ALLOC * sizeof (fail_stack_elt_t)); \
+ \
+ if (fail_stack.stack == NULL) \
+ return -2; \
+ \
+ fail_stack.size = INIT_FAILURE_ALLOC; \
+ fail_stack.avail = 0; \
+ } while (0)
+
+
+/* Double the size of FAIL_STACK, up to approximately `re_max_failures' items.
+
+ Return 1 if succeeds, and 0 if either ran out of memory
+ allocating space for it or it was already too large.
+
+ REGEX_REALLOCATE requires `destination' be declared. */
+
+#define DOUBLE_FAIL_STACK(fail_stack) \
+ ((fail_stack).size > re_max_failures * MAX_FAILURE_ITEMS \
+ ? 0 \
+ : ((fail_stack).stack = (fail_stack_elt_t *) \
+ REGEX_REALLOCATE ((fail_stack).stack, \
+ (fail_stack).size * sizeof (fail_stack_elt_t), \
+ ((fail_stack).size << 1) * sizeof (fail_stack_elt_t)), \
+ \
+ (fail_stack).stack == NULL \
+ ? 0 \
+ : ((fail_stack).size <<= 1, \
+ 1)))
+
+
+/* Push PATTERN_OP on FAIL_STACK.
+
+ Return 1 if was able to do so and 0 if ran out of memory allocating
+ space to do so. */
+#define PUSH_PATTERN_OP(pattern_op, fail_stack) \
+ ((FAIL_STACK_FULL () \
+ && !DOUBLE_FAIL_STACK (fail_stack)) \
+ ? 0 \
+ : ((fail_stack).stack[(fail_stack).avail++] = pattern_op, \
+ 1))
+
+/* This pushes an item onto the failure stack. Must be a four-byte
+ value. Assumes the variable `fail_stack'. Probably should only
+ be called from within `PUSH_FAILURE_POINT'. */
+#define PUSH_FAILURE_ITEM(item) \
+ fail_stack.stack[fail_stack.avail++] = (fail_stack_elt_t) item
+
+/* The complement operation. Assumes `fail_stack' is nonempty. */
+#define POP_FAILURE_ITEM() fail_stack.stack[--fail_stack.avail]
+
+/* Used to omit pushing failure point id's when we're not debugging. */
+#ifdef DEBUG
+#define DEBUG_PUSH PUSH_FAILURE_ITEM
+#define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_ITEM ()
+#else
+#define DEBUG_PUSH(item)
+#define DEBUG_POP(item_addr)
+#endif
+
+
+/* Push the information about the state we will need
+ if we ever fail back to it.
+
+ Requires variables fail_stack, regstart, regend, reg_info, and
+ num_regs be declared. DOUBLE_FAIL_STACK requires `destination' be
+ declared.
+
+ Does `return FAILURE_CODE' if runs out of memory. */
+
+#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \
+ do { \
+ char *destination; \
+ /* Must be int, so when we don't save any registers, the arithmetic \
+ of 0 + -1 isn't done as unsigned. */ \
+ int this_reg; \
+ \
+ DEBUG_STATEMENT (failure_id++); \
+ DEBUG_STATEMENT (nfailure_points_pushed++); \
+ DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id); \
+ DEBUG_PRINT2 (" Before push, next avail: %d\n", (fail_stack).avail);\
+ DEBUG_PRINT2 (" size: %d\n", (fail_stack).size);\
+ \
+ DEBUG_PRINT2 (" slots needed: %d\n", NUM_FAILURE_ITEMS); \
+ DEBUG_PRINT2 (" available: %d\n", REMAINING_AVAIL_SLOTS); \
+ \
+ /* Ensure we have enough space allocated for what we will push. */ \
+ while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \
+ { \
+ if (!DOUBLE_FAIL_STACK (fail_stack)) \
+ return failure_code; \
+ \
+ DEBUG_PRINT2 ("\n Doubled stack; size now: %d\n", \
+ (fail_stack).size); \
+ DEBUG_PRINT2 (" slots available: %d\n", REMAINING_AVAIL_SLOTS);\
+ } \
+ \
+ /* Push the info, starting with the registers. */ \
+ DEBUG_PRINT1 ("\n"); \
+ \
+ for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \
+ this_reg++) \
+ { \
+ DEBUG_PRINT2 (" Pushing reg: %d\n", this_reg); \
+ DEBUG_STATEMENT (num_regs_pushed++); \
+ \
+ DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \
+ PUSH_FAILURE_ITEM (regstart[this_reg]); \
+ \
+ DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \
+ PUSH_FAILURE_ITEM (regend[this_reg]); \
+ \
+ DEBUG_PRINT2 (" info: 0x%x\n ", reg_info[this_reg]); \
+ DEBUG_PRINT2 (" match_null=%d", \
+ REG_MATCH_NULL_STRING_P (reg_info[this_reg])); \
+ DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg])); \
+ DEBUG_PRINT2 (" matched_something=%d", \
+ MATCHED_SOMETHING (reg_info[this_reg])); \
+ DEBUG_PRINT2 (" ever_matched=%d", \
+ EVER_MATCHED_SOMETHING (reg_info[this_reg])); \
+ DEBUG_PRINT1 ("\n"); \
+ PUSH_FAILURE_ITEM (reg_info[this_reg].word); \
+ } \
+ \
+ DEBUG_PRINT2 (" Pushing low active reg: %d\n", lowest_active_reg);\
+ PUSH_FAILURE_ITEM (lowest_active_reg); \
+ \
+ DEBUG_PRINT2 (" Pushing high active reg: %d\n", highest_active_reg);\
+ PUSH_FAILURE_ITEM (highest_active_reg); \
+ \
+ DEBUG_PRINT2 (" Pushing pattern 0x%x: ", pattern_place); \
+ DEBUG_PRINT_COMPILED_PATTERN (bufp, pattern_place, pend); \
+ PUSH_FAILURE_ITEM (pattern_place); \
+ \
+ DEBUG_PRINT2 (" Pushing string 0x%x: `", string_place); \
+ DEBUG_PRINT_DOUBLE_STRING (string_place, string1, size1, string2, \
+ size2); \
+ DEBUG_PRINT1 ("'\n"); \
+ PUSH_FAILURE_ITEM (string_place); \
+ \
+ DEBUG_PRINT2 (" Pushing failure id: %u\n", failure_id); \
+ DEBUG_PUSH (failure_id); \
+ } while (0)
+
+/* This is the number of items that are pushed and popped on the stack
+ for each register. */
+#define NUM_REG_ITEMS 3
+
+/* Individual items aside from the registers. */
+#ifdef DEBUG
+#define NUM_NONREG_ITEMS 5 /* Includes failure point id. */
+#else
+#define NUM_NONREG_ITEMS 4
+#endif
+
+/* We push at most this many items on the stack. */
+#define MAX_FAILURE_ITEMS ((num_regs - 1) * NUM_REG_ITEMS + NUM_NONREG_ITEMS)
+
+/* We actually push this many items. */
+#define NUM_FAILURE_ITEMS \
+ ((highest_active_reg - lowest_active_reg + 1) * NUM_REG_ITEMS \
+ + NUM_NONREG_ITEMS)
+
+/* How many items can still be added to the stack without overflowing it. */
+#define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail)
+
+
+/* Pops what PUSH_FAIL_STACK pushes.
+
+ We restore into the parameters, all of which should be lvalues:
+ STR -- the saved data position.
+ PAT -- the saved pattern position.
+ LOW_REG, HIGH_REG -- the highest and lowest active registers.
+ REGSTART, REGEND -- arrays of string positions.
+ REG_INFO -- array of information about each subexpression.
+
+ Also assumes the variables `fail_stack' and (if debugging), `bufp',
+ `pend', `string1', `size1', `string2', and `size2'. */
+
+#define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info)\
+{ \
+ DEBUG_STATEMENT (fail_stack_elt_t failure_id;) \
+ int this_reg; \
+ const unsigned char *string_temp; \
+ \
+ assert (!FAIL_STACK_EMPTY ()); \
+ \
+ /* Remove failure points and point to how many regs pushed. */ \
+ DEBUG_PRINT1 ("POP_FAILURE_POINT:\n"); \
+ DEBUG_PRINT2 (" Before pop, next avail: %d\n", fail_stack.avail); \
+ DEBUG_PRINT2 (" size: %d\n", fail_stack.size); \
+ \
+ assert (fail_stack.avail >= NUM_NONREG_ITEMS); \
+ \
+ DEBUG_POP (&failure_id); \
+ DEBUG_PRINT2 (" Popping failure id: %u\n", failure_id); \
+ \
+ /* If the saved string location is NULL, it came from an \
+ on_failure_keep_string_jump opcode, and we want to throw away the \
+ saved NULL, thus retaining our current position in the string. */ \
+ string_temp = POP_FAILURE_ITEM (); \
+ if (string_temp != NULL) \
+ str = (const char *) string_temp; \
+ \
+ DEBUG_PRINT2 (" Popping string 0x%x: `", str); \
+ DEBUG_PRINT_DOUBLE_STRING (str, string1, size1, string2, size2); \
+ DEBUG_PRINT1 ("'\n"); \
+ \
+ pat = (unsigned char *) POP_FAILURE_ITEM (); \
+ DEBUG_PRINT2 (" Popping pattern 0x%x: ", pat); \
+ DEBUG_PRINT_COMPILED_PATTERN (bufp, pat, pend); \
+ \
+ /* Restore register info. */ \
+ high_reg = (unsigned) POP_FAILURE_ITEM (); \
+ DEBUG_PRINT2 (" Popping high active reg: %d\n", high_reg); \
+ \
+ low_reg = (unsigned) POP_FAILURE_ITEM (); \
+ DEBUG_PRINT2 (" Popping low active reg: %d\n", low_reg); \
+ \
+ for (this_reg = high_reg; this_reg >= low_reg; this_reg--) \
+ { \
+ DEBUG_PRINT2 (" Popping reg: %d\n", this_reg); \
+ \
+ reg_info[this_reg].word = POP_FAILURE_ITEM (); \
+ DEBUG_PRINT2 (" info: 0x%x\n", reg_info[this_reg]); \
+ \
+ regend[this_reg] = (const char *) POP_FAILURE_ITEM (); \
+ DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \
+ \
+ regstart[this_reg] = (const char *) POP_FAILURE_ITEM (); \
+ DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \
+ } \
+ \
+ DEBUG_STATEMENT (nfailure_points_popped++); \
+} /* POP_FAILURE_POINT */
+
+/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in
+ BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible
+ characters can start a string that matches the pattern. This fastmap
+ is used by re_search to skip quickly over impossible starting points.
+
+ The caller must supply the address of a (1 << BYTEWIDTH)-byte data
+ area as BUFP->fastmap.
+
+ We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in
+ the pattern buffer.
+
+ Returns 0 if we succeed, -2 if an internal error. */
+
+int
+re_compile_fastmap (bufp)
+ struct re_pattern_buffer *bufp;
+{
+ int j, k;
+ fail_stack_type fail_stack;
+#ifndef REGEX_MALLOC
+ char *destination;
+#endif
+ /* We don't push any register information onto the failure stack. */
+ unsigned num_regs = 0;
+
+ register char *fastmap = bufp->fastmap;
+ unsigned char *pattern = bufp->buffer;
+ unsigned long size = bufp->used;
+ const unsigned char *p = pattern;
+ register unsigned char *pend = pattern + size;
+
+ /* Assume that each path through the pattern can be null until
+ proven otherwise. We set this false at the bottom of switch
+ statement, to which we get only if a particular path doesn't
+ match the empty string. */
+ boolean path_can_be_null = true;
+
+ /* We aren't doing a `succeed_n' to begin with. */
+ boolean succeed_n_p = false;
+
+ assert (fastmap != NULL && p != NULL);
+
+ INIT_FAIL_STACK ();
+ bzero (fastmap, 1 << BYTEWIDTH); /* Assume nothing's valid. */
+ bufp->fastmap_accurate = 1; /* It will be when we're done. */
+ bufp->can_be_null = 0;
+
+ while (p != pend || !FAIL_STACK_EMPTY ())
+ {
+ if (p == pend)
+ {
+ bufp->can_be_null |= path_can_be_null;
+
+ /* Reset for next path. */
+ path_can_be_null = true;
+
+ p = fail_stack.stack[--fail_stack.avail];
+ }
+
+ /* We should never be about to go beyond the end of the pattern. */
+ assert (p < pend);
+
+#ifdef SWITCH_ENUM_BUG
+ switch ((int) ((re_opcode_t) *p++))
+#else
+ switch ((re_opcode_t) *p++)
+#endif
+ {
+
+ /* I guess the idea here is to simply not bother with a fastmap
+ if a backreference is used, since it's too hard to figure out
+ the fastmap for the corresponding group. Setting
+ `can_be_null' stops `re_search_2' from using the fastmap, so
+ that is all we do. */
+ case duplicate:
+ bufp->can_be_null = 1;
+ return 0;
+
+
+ /* Following are the cases which match a character. These end
+ with `break'. */
+
+ case exactn:
+ fastmap[p[1]] = 1;
+ break;
+
+
+ case charset:
+ for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
+ if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))
+ fastmap[j] = 1;
+ break;
+
+
+ case charset_not:
+ /* Chars beyond end of map must be allowed. */
+ for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++)
+ fastmap[j] = 1;
+
+ for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
+ if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))))
+ fastmap[j] = 1;
+ break;
+
+
+ case wordchar:
+ for (j = 0; j < (1 << BYTEWIDTH); j++)
+ if (SYNTAX (j) == Sword)
+ fastmap[j] = 1;
+ break;
+
+
+ case notwordchar:
+ for (j = 0; j < (1 << BYTEWIDTH); j++)
+ if (SYNTAX (j) != Sword)
+ fastmap[j] = 1;
+ break;
+
+
+ case anychar:
+ /* `.' matches anything ... */
+ for (j = 0; j < (1 << BYTEWIDTH); j++)
+ fastmap[j] = 1;
+
+ /* ... except perhaps newline. */
+ if (!(bufp->syntax & RE_DOT_NEWLINE))
+ fastmap['\n'] = 0;
+
+ /* Return if we have already set `can_be_null'; if we have,
+ then the fastmap is irrelevant. Something's wrong here. */
+ else if (bufp->can_be_null)
+ return 0;
+
+ /* Otherwise, have to check alternative paths. */
+ break;
+
+
+#ifdef emacs
+ case syntaxspec:
+ k = *p++;
+ for (j = 0; j < (1 << BYTEWIDTH); j++)
+ if (SYNTAX (j) == (enum syntaxcode) k)
+ fastmap[j] = 1;
+ break;
+
+
+ case notsyntaxspec:
+ k = *p++;
+ for (j = 0; j < (1 << BYTEWIDTH); j++)
+ if (SYNTAX (j) != (enum syntaxcode) k)
+ fastmap[j] = 1;
+ break;
+
+
+ /* All cases after this match the empty string. These end with
+ `continue'. */
+
+
+ case before_dot:
+ case at_dot:
+ case after_dot:
+ continue;
+#endif /* not emacs */
+
+
+ case no_op:
+ case begline:
+ case endline:
+ case begbuf:
+ case endbuf:
+ case wordbound:
+ case notwordbound:
+ case wordbeg:
+ case wordend:
+ case push_dummy_failure:
+ continue;
+
+
+ case jump_n:
+ case pop_failure_jump:
+ case maybe_pop_jump:
+ case jump:
+ case jump_past_alt:
+ case dummy_failure_jump:
+ EXTRACT_NUMBER_AND_INCR (j, p);
+ p += j;
+ if (j > 0)
+ continue;
+
+ /* Jump backward implies we just went through the body of a
+ loop and matched nothing. Opcode jumped to should be
+ `on_failure_jump' or `succeed_n'. Just treat it like an
+ ordinary jump. For a * loop, it has pushed its failure
+ point already; if so, discard that as redundant. */
+ if ((re_opcode_t) *p != on_failure_jump
+ && (re_opcode_t) *p != succeed_n)
+ continue;
+
+ p++;
+ EXTRACT_NUMBER_AND_INCR (j, p);
+ p += j;
+
+ /* If what's on the stack is where we are now, pop it. */
+ if (!FAIL_STACK_EMPTY ()
+ && fail_stack.stack[fail_stack.avail - 1] == p)
+ fail_stack.avail--;
+
+ continue;
+
+
+ case on_failure_jump:
+ case on_failure_keep_string_jump:
+ handle_on_failure_jump:
+ EXTRACT_NUMBER_AND_INCR (j, p);
+
+ /* For some patterns, e.g., `(a?)?', `p+j' here points to the
+ end of the pattern. We don't want to push such a point,
+ since when we restore it above, entering the switch will
+ increment `p' past the end of the pattern. We don't need
+ to push such a point since we obviously won't find any more
+ fastmap entries beyond `pend'. Such a pattern can match
+ the null string, though. */
+ if (p + j < pend)
+ {
+ if (!PUSH_PATTERN_OP (p + j, fail_stack))
+ return -2;
+ }
+ else
+ bufp->can_be_null = 1;
+
+ if (succeed_n_p)
+ {
+ EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */
+ succeed_n_p = false;
+ }
+
+ continue;
+
+
+ case succeed_n:
+ /* Get to the number of times to succeed. */
+ p += 2;
+
+ /* Increment p past the n for when k != 0. */
+ EXTRACT_NUMBER_AND_INCR (k, p);
+ if (k == 0)
+ {
+ p -= 4;
+ succeed_n_p = true; /* Spaghetti code alert. */
+ goto handle_on_failure_jump;
+ }
+ continue;
+
+
+ case set_number_at:
+ p += 4;
+ continue;
+
+
+ case start_memory:
+ case stop_memory:
+ p += 2;
+ continue;
+
+
+ default:
+ abort (); /* We have listed all the cases. */
+ } /* switch *p++ */
+
+ /* Getting here means we have found the possible starting
+ characters for one path of the pattern -- and that the empty
+ string does not match. We need not follow this path further.
+ Instead, look at the next alternative (remembered on the
+ stack), or quit if no more. The test at the top of the loop
+ does these things. */
+ path_can_be_null = false;
+ p = pend;
+ } /* while p */
+
+ /* Set `can_be_null' for the last path (also the first path, if the
+ pattern is empty). */
+ bufp->can_be_null |= path_can_be_null;
+ return 0;
+} /* re_compile_fastmap */
+
+/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
+ ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use
+ this memory for recording register information. STARTS and ENDS
+ must be allocated using the malloc library routine, and must each
+ be at least NUM_REGS * sizeof (regoff_t) bytes long.
+
+ If NUM_REGS == 0, then subsequent matches should allocate their own
+ register data.
+
+ Unless this function is called, the first search or match using
+ PATTERN_BUFFER will allocate its own register data, without
+ freeing the old data. */
+
+void
+re_set_registers (bufp, regs, num_regs, starts, ends)
+ struct re_pattern_buffer *bufp;
+ struct re_registers *regs;
+ unsigned num_regs;
+ regoff_t *starts, *ends;
+{
+ if (num_regs)
+ {
+ bufp->regs_allocated = REGS_REALLOCATE;
+ regs->num_regs = num_regs;
+ regs->start = starts;
+ regs->end = ends;
+ }
+ else
+ {
+ bufp->regs_allocated = REGS_UNALLOCATED;
+ regs->num_regs = 0;
+ regs->start = regs->end = (regoff_t *) 0;
+ }
+}
+
+/* Searching routines. */
+
+/* Like re_search_2, below, but only one string is specified, and
+ doesn't let you say where to stop matching. */
+
+int
+re_search (bufp, string, size, startpos, range, regs)
+ struct re_pattern_buffer *bufp;
+ const char *string;
+ int size, startpos, range;
+ struct re_registers *regs;
+{
+ return re_search_2 (bufp, NULL, 0, string, size, startpos, range,
+ regs, size);
+}
+
+
+/* Using the compiled pattern in BUFP->buffer, first tries to match the
+ virtual concatenation of STRING1 and STRING2, starting first at index
+ STARTPOS, then at STARTPOS + 1, and so on.
+
+ STRING1 and STRING2 have length SIZE1 and SIZE2, respectively.
+
+ RANGE is how far to scan while trying to match. RANGE = 0 means try
+ only at STARTPOS; in general, the last start tried is STARTPOS +
+ RANGE.
+
+ In REGS, return the indices of the virtual concatenation of STRING1
+ and STRING2 that matched the entire BUFP->buffer and its contained
+ subexpressions.
+
+ Do not consider matching one past the index STOP in the virtual
+ concatenation of STRING1 and STRING2.
+
+ We return either the position in the strings at which the match was
+ found, -1 if no match, or -2 if error (such as failure
+ stack overflow). */
+
+int
+re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop)
+ struct re_pattern_buffer *bufp;
+ const char *string1, *string2;
+ int size1, size2;
+ int startpos;
+ int range;
+ struct re_registers *regs;
+ int stop;
+{
+ int val;
+ register char *fastmap = bufp->fastmap;
+ register char *translate = bufp->translate;
+ int total_size = size1 + size2;
+ int endpos = startpos + range;
+
+ /* Check for out-of-range STARTPOS. */
+ if (startpos < 0 || startpos > total_size)
+ return -1;
+
+ /* Fix up RANGE if it might eventually take us outside
+ the virtual concatenation of STRING1 and STRING2. */
+ if (endpos < -1)
+ range = -1 - startpos;
+ else if (endpos > total_size)
+ range = total_size - startpos;
+
+ /* If the search isn't to be a backwards one, don't waste time in a
+ search for a pattern that must be anchored. */
+ if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == begbuf && range > 0)
+ {
+ if (startpos > 0)
+ return -1;
+ else
+ range = 1;
+ }
+
+ /* Update the fastmap now if not correct already. */
+ if (fastmap && !bufp->fastmap_accurate)
+ if (re_compile_fastmap (bufp) == -2)
+ return -2;
+
+ /* Loop through the string, looking for a place to start matching. */
+ for (;;)
+ {
+ /* If a fastmap is supplied, skip quickly over characters that
+ cannot be the start of a match. If the pattern can match the
+ null string, however, we don't need to skip characters; we want
+ the first null string. */
+ if (fastmap && startpos < total_size && !bufp->can_be_null)
+ {
+ if (range > 0) /* Searching forwards. */
+ {
+ register const char *d;
+ register int lim = 0;
+ int irange = range;
+
+ if (startpos < size1 && startpos + range >= size1)
+ lim = range - (size1 - startpos);
+
+ d = (startpos >= size1 ? string2 - size1 : string1) + startpos;
+
+ /* Written out as an if-else to avoid testing `translate'
+ inside the loop. */
+ if (translate)
+ while (range > lim
+ && !fastmap[(unsigned char)
+ translate[(unsigned char) *d++]])
+ range--;
+ else
+ while (range > lim && !fastmap[(unsigned char) *d++])
+ range--;
+
+ startpos += irange - range;
+ }
+ else /* Searching backwards. */
+ {
+ register char c = (size1 == 0 || startpos >= size1
+ ? string2[startpos - size1]
+ : string1[startpos]);
+
+ if (!fastmap[(unsigned char) TRANSLATE (c)])
+ goto advance;
+ }
+ }
+
+ /* If can't match the null string, and that's all we have left, fail. */
+ if (range >= 0 && startpos == total_size && fastmap
+ && !bufp->can_be_null)
+ return -1;
+
+ val = re_match_2 (bufp, string1, size1, string2, size2,
+ startpos, regs, stop);
+ if (val >= 0)
+ return startpos;
+
+ if (val == -2)
+ return -2;
+
+ advance:
+ if (!range)
+ break;
+ else if (range > 0)
+ {
+ range--;
+ startpos++;
+ }
+ else
+ {
+ range++;
+ startpos--;
+ }
+ }
+ return -1;
+} /* re_search_2 */
+
+/* Declarations and macros for re_match_2. */
+
+static int bcmp_translate ();
+static boolean alt_match_null_string_p (),
+ common_op_match_null_string_p (),
+ group_match_null_string_p ();
+
+/* Structure for per-register (a.k.a. per-group) information.
+ This must not be longer than one word, because we push this value
+ onto the failure stack. Other register information, such as the
+ starting and ending positions (which are addresses), and the list of
+ inner groups (which is a bits list) are maintained in separate
+ variables.
+
+ We are making a (strictly speaking) nonportable assumption here: that
+ the compiler will pack our bit fields into something that fits into
+ the type of `word', i.e., is something that fits into one item on the
+ failure stack. */
+typedef union
+{
+ fail_stack_elt_t word;
+ struct
+ {
+ /* This field is one if this group can match the empty string,
+ zero if not. If not yet determined, `MATCH_NULL_UNSET_VALUE'. */
+#define MATCH_NULL_UNSET_VALUE 3
+ unsigned match_null_string_p : 2;
+ unsigned is_active : 1;
+ unsigned matched_something : 1;
+ unsigned ever_matched_something : 1;
+ } bits;
+} register_info_type;
+
+#define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p)
+#define IS_ACTIVE(R) ((R).bits.is_active)
+#define MATCHED_SOMETHING(R) ((R).bits.matched_something)
+#define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something)
+
+
+/* Call this when have matched a real character; it sets `matched' flags
+ for the subexpressions which we are currently inside. Also records
+ that those subexprs have matched. */
+#define SET_REGS_MATCHED() \
+ do \
+ { \
+ unsigned r; \
+ for (r = lowest_active_reg; r <= highest_active_reg; r++) \
+ { \
+ MATCHED_SOMETHING (reg_info[r]) \
+ = EVER_MATCHED_SOMETHING (reg_info[r]) \
+ = 1; \
+ } \
+ } \
+ while (0)
+
+
+/* This converts PTR, a pointer into one of the search strings `string1'
+ and `string2' into an offset from the beginning of that string. */
+#define POINTER_TO_OFFSET(ptr) \
+ (FIRST_STRING_P (ptr) ? (ptr) - string1 : (ptr) - string2 + size1)
+
+/* Registers are set to a sentinel when they haven't yet matched. */
+#define REG_UNSET_VALUE ((char *) -1)
+#define REG_UNSET(e) ((e) == REG_UNSET_VALUE)
+
+
+/* Macros for dealing with the split strings in re_match_2. */
+
+#define MATCHING_IN_FIRST_STRING (dend == end_match_1)
+
+/* Call before fetching a character with *d. This switches over to
+ string2 if necessary. */
+#define PREFETCH() \
+ while (d == dend) \
+ { \
+ /* End of string2 => fail. */ \
+ if (dend == end_match_2) \
+ goto fail; \
+ /* End of string1 => advance to string2. */ \
+ d = string2; \
+ dend = end_match_2; \
+ }
+
+
+/* Test if at very beginning or at very end of the virtual concatenation
+ of `string1' and `string2'. If only one string, it's `string2'. */
+#define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2)
+#define AT_STRINGS_END(d) ((d) == end2)
+
+
+/* Test if D points to a character which is word-constituent. We have
+ two special cases to check for: if past the end of string1, look at
+ the first character in string2; and if before the beginning of
+ string2, look at the last character in string1. */
+#define WORDCHAR_P(d) \
+ (SYNTAX ((d) == end1 ? *string2 \
+ : (d) == string2 - 1 ? *(end1 - 1) : *(d)) \
+ == Sword)
+
+/* Test if the character before D and the one at D differ with respect
+ to being word-constituent. */
+#define AT_WORD_BOUNDARY(d) \
+ (AT_STRINGS_BEG (d) || AT_STRINGS_END (d) \
+ || WORDCHAR_P (d - 1) != WORDCHAR_P (d))
+
+
+/* Free everything we malloc. */
+#ifdef REGEX_MALLOC
+#define FREE_VAR(var) if (var) free (var); var = NULL
+#define FREE_VARIABLES() \
+ do { \
+ FREE_VAR (fail_stack.stack); \
+ FREE_VAR (regstart); \
+ FREE_VAR (regend); \
+ FREE_VAR (old_regstart); \
+ FREE_VAR (old_regend); \
+ FREE_VAR (best_regstart); \
+ FREE_VAR (best_regend); \
+ FREE_VAR (reg_info); \
+ FREE_VAR (reg_dummy); \
+ FREE_VAR (reg_info_dummy); \
+ } while (0)
+#else /* not REGEX_MALLOC */
+/* Some MIPS systems (at least) want this to free alloca'd storage. */
+#define FREE_VARIABLES() alloca (0)
+#endif /* not REGEX_MALLOC */
+
+
+/* These values must meet several constraints. They must not be valid
+ register values; since we have a limit of 255 registers (because
+ we use only one byte in the pattern for the register number), we can
+ use numbers larger than 255. They must differ by 1, because of
+ NUM_FAILURE_ITEMS above. And the value for the lowest register must
+ be larger than the value for the highest register, so we do not try
+ to actually save any registers when none are active. */
+#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH)
+#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1)
+
+/* Matching routines. */
+
+#ifndef emacs /* Emacs never uses this. */
+/* re_match is like re_match_2 except it takes only a single string. */
+
+int
+re_match (bufp, string, size, pos, regs)
+ struct re_pattern_buffer *bufp;
+ const char *string;
+ int size, pos;
+ struct re_registers *regs;
+ {
+ return re_match_2 (bufp, NULL, 0, string, size, pos, regs, size);
+}
+#endif /* not emacs */
+
+
+/* re_match_2 matches the compiled pattern in BUFP against the
+ the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1
+ and SIZE2, respectively). We start matching at POS, and stop
+ matching at STOP.
+
+ If REGS is non-null and the `no_sub' field of BUFP is nonzero, we
+ store offsets for the substring each group matched in REGS. See the
+ documentation for exactly how many groups we fill.
+
+ We return -1 if no match, -2 if an internal error (such as the
+ failure stack overflowing). Otherwise, we return the length of the
+ matched substring. */
+
+int
+re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
+ struct re_pattern_buffer *bufp;
+ const char *string1, *string2;
+ int size1, size2;
+ int pos;
+ struct re_registers *regs;
+ int stop;
+{
+ /* General temporaries. */
+ int mcnt;
+ unsigned char *p1;
+
+ /* Just past the end of the corresponding string. */
+ const char *end1, *end2;
+
+ /* Pointers into string1 and string2, just past the last characters in
+ each to consider matching. */
+ const char *end_match_1, *end_match_2;
+
+ /* Where we are in the data, and the end of the current string. */
+ const char *d, *dend;
+
+ /* Where we are in the pattern, and the end of the pattern. */
+ unsigned char *p = bufp->buffer;
+ register unsigned char *pend = p + bufp->used;
+
+ /* We use this to map every character in the string. */
+ char *translate = bufp->translate;
+
+ /* Failure point stack. Each place that can handle a failure further
+ down the line pushes a failure point on this stack. It consists of
+ restart, regend, and reg_info for all registers corresponding to
+ the subexpressions we're currently inside, plus the number of such
+ registers, and, finally, two char *'s. The first char * is where
+ to resume scanning the pattern; the second one is where to resume
+ scanning the strings. If the latter is zero, the failure point is
+ a ``dummy''; if a failure happens and the failure point is a dummy,
+ it gets discarded and the next next one is tried. */
+ fail_stack_type fail_stack;
+#ifdef DEBUG
+ static unsigned failure_id = 0;
+ unsigned nfailure_points_pushed = 0, nfailure_points_popped = 0;
+#endif
+
+ /* We fill all the registers internally, independent of what we
+ return, for use in backreferences. The number here includes
+ an element for register zero. */
+ unsigned num_regs = bufp->re_nsub + 1;
+
+ /* The currently active registers. */
+ unsigned lowest_active_reg = NO_LOWEST_ACTIVE_REG;
+ unsigned highest_active_reg = NO_HIGHEST_ACTIVE_REG;
+
+ /* Information on the contents of registers. These are pointers into
+ the input strings; they record just what was matched (on this
+ attempt) by a subexpression part of the pattern, that is, the
+ regnum-th regstart pointer points to where in the pattern we began
+ matching and the regnum-th regend points to right after where we
+ stopped matching the regnum-th subexpression. (The zeroth register
+ keeps track of what the whole pattern matches.) */
+ const char **regstart, **regend;
+
+ /* If a group that's operated upon by a repetition operator fails to
+ match anything, then the register for its start will need to be
+ restored because it will have been set to wherever in the string we
+ are when we last see its open-group operator. Similarly for a
+ register's end. */
+ const char **old_regstart, **old_regend;
+
+ /* The is_active field of reg_info helps us keep track of which (possibly
+ nested) subexpressions we are currently in. The matched_something
+ field of reg_info[reg_num] helps us tell whether or not we have
+ matched any of the pattern so far this time through the reg_num-th
+ subexpression. These two fields get reset each time through any
+ loop their register is in. */
+ register_info_type *reg_info;
+
+ /* The following record the register info as found in the above
+ variables when we find a match better than any we've seen before.
+ This happens as we backtrack through the failure points, which in
+ turn happens only if we have not yet matched the entire string. */
+ unsigned best_regs_set = false;
+ const char **best_regstart, **best_regend;
+
+ /* Logically, this is `best_regend[0]'. But we don't want to have to
+ allocate space for that if we're not allocating space for anything
+ else (see below). Also, we never need info about register 0 for
+ any of the other register vectors, and it seems rather a kludge to
+ treat `best_regend' differently than the rest. So we keep track of
+ the end of the best match so far in a separate variable. We
+ initialize this to NULL so that when we backtrack the first time
+ and need to test it, it's not garbage. */
+ const char *match_end = NULL;
+
+ /* Used when we pop values we don't care about. */
+ const char **reg_dummy;
+ register_info_type *reg_info_dummy;
+
+#ifdef DEBUG
+ /* Counts the total number of registers pushed. */
+ unsigned num_regs_pushed = 0;
+#endif
+
+ DEBUG_PRINT1 ("\n\nEntering re_match_2.\n");
+
+ INIT_FAIL_STACK ();
+
+ /* Do not bother to initialize all the register variables if there are
+ no groups in the pattern, as it takes a fair amount of time. If
+ there are groups, we include space for register 0 (the whole
+ pattern), even though we never use it, since it simplifies the
+ array indexing. We should fix this. */
+ if (bufp->re_nsub)
+ {
+ regstart = REGEX_TALLOC (num_regs, const char *);
+ regend = REGEX_TALLOC (num_regs, const char *);
+ old_regstart = REGEX_TALLOC (num_regs, const char *);
+ old_regend = REGEX_TALLOC (num_regs, const char *);
+ best_regstart = REGEX_TALLOC (num_regs, const char *);
+ best_regend = REGEX_TALLOC (num_regs, const char *);
+ reg_info = REGEX_TALLOC (num_regs, register_info_type);
+ reg_dummy = REGEX_TALLOC (num_regs, const char *);
+ reg_info_dummy = REGEX_TALLOC (num_regs, register_info_type);
+
+ if (!(regstart && regend && old_regstart && old_regend && reg_info
+ && best_regstart && best_regend && reg_dummy && reg_info_dummy))
+ {
+ FREE_VARIABLES ();
+ return -2;
+ }
+ }
+#ifdef REGEX_MALLOC
+ else
+ {
+ /* We must initialize all our variables to NULL, so that
+ `FREE_VARIABLES' doesn't try to free them. */
+ regstart = regend = old_regstart = old_regend = best_regstart
+ = best_regend = reg_dummy = NULL;
+ reg_info = reg_info_dummy = (register_info_type *) NULL;
+ }
+#endif /* REGEX_MALLOC */
+
+ /* The starting position is bogus. */
+ if (pos < 0 || pos > size1 + size2)
+ {
+ FREE_VARIABLES ();
+ return -1;
+ }
+
+ /* Initialize subexpression text positions to -1 to mark ones that no
+ start_memory/stop_memory has been seen for. Also initialize the
+ register information struct. */
+ for (mcnt = 1; mcnt < num_regs; mcnt++)
+ {
+ regstart[mcnt] = regend[mcnt]
+ = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE;
+
+ REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE;
+ IS_ACTIVE (reg_info[mcnt]) = 0;
+ MATCHED_SOMETHING (reg_info[mcnt]) = 0;
+ EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0;
+ }
+
+ /* We move `string1' into `string2' if the latter's empty -- but not if
+ `string1' is null. */
+ if (size2 == 0 && string1 != NULL)
+ {
+ string2 = string1;
+ size2 = size1;
+ string1 = 0;
+ size1 = 0;
+ }
+ end1 = string1 + size1;
+ end2 = string2 + size2;
+
+ /* Compute where to stop matching, within the two strings. */
+ if (stop <= size1)
+ {
+ end_match_1 = string1 + stop;
+ end_match_2 = string2;
+ }
+ else
+ {
+ end_match_1 = end1;
+ end_match_2 = string2 + stop - size1;
+ }
+
+ /* `p' scans through the pattern as `d' scans through the data.
+ `dend' is the end of the input string that `d' points within. `d'
+ is advanced into the following input string whenever necessary, but
+ this happens before fetching; therefore, at the beginning of the
+ loop, `d' can be pointing at the end of a string, but it cannot
+ equal `string2'. */
+ if (size1 > 0 && pos <= size1)
+ {
+ d = string1 + pos;
+ dend = end_match_1;
+ }
+ else
+ {
+ d = string2 + pos - size1;
+ dend = end_match_2;
+ }
+
+ DEBUG_PRINT1 ("The compiled pattern is: ");
+ DEBUG_PRINT_COMPILED_PATTERN (bufp, p, pend);
+ DEBUG_PRINT1 ("The string to match is: `");
+ DEBUG_PRINT_DOUBLE_STRING (d, string1, size1, string2, size2);
+ DEBUG_PRINT1 ("'\n");
+
+ /* This loops over pattern commands. It exits by returning from the
+ function if the match is complete, or it drops through if the match
+ fails at this starting point in the input data. */
+ for (;;)
+ {
+ DEBUG_PRINT2 ("\n0x%x: ", p);
+
+ if (p == pend)
+ { /* End of pattern means we might have succeeded. */
+ DEBUG_PRINT1 ("end of pattern ... ");
+
+ /* If we haven't matched the entire string, and we want the
+ longest match, try backtracking. */
+ if (d != end_match_2)
+ {
+ DEBUG_PRINT1 ("backtracking.\n");
+
+ if (!FAIL_STACK_EMPTY ())
+ { /* More failure points to try. */
+ boolean same_str_p = (FIRST_STRING_P (match_end)
+ == MATCHING_IN_FIRST_STRING);
+
+ /* If exceeds best match so far, save it. */
+ if (!best_regs_set
+ || (same_str_p && d > match_end)
+ || (!same_str_p && !MATCHING_IN_FIRST_STRING))
+ {
+ best_regs_set = true;
+ match_end = d;
+
+ DEBUG_PRINT1 ("\nSAVING match as best so far.\n");
+
+ for (mcnt = 1; mcnt < num_regs; mcnt++)
+ {
+ best_regstart[mcnt] = regstart[mcnt];
+ best_regend[mcnt] = regend[mcnt];
+ }
+ }
+ goto fail;
+ }
+
+ /* If no failure points, don't restore garbage. */
+ else if (best_regs_set)
+ {
+ restore_best_regs:
+ /* Restore best match. It may happen that `dend ==
+ end_match_1' while the restored d is in string2.
+ For example, the pattern `x.*y.*z' against the
+ strings `x-' and `y-z-', if the two strings are
+ not consecutive in memory. */
+ DEBUG_PRINT1 ("Restoring best registers.\n");
+
+ d = match_end;
+ dend = ((d >= string1 && d <= end1)
+ ? end_match_1 : end_match_2);
+
+ for (mcnt = 1; mcnt < num_regs; mcnt++)
+ {
+ regstart[mcnt] = best_regstart[mcnt];
+ regend[mcnt] = best_regend[mcnt];
+ }
+ }
+ } /* d != end_match_2 */
+
+ DEBUG_PRINT1 ("Accepting match.\n");
+
+ /* If caller wants register contents data back, do it. */
+ if (regs && !bufp->no_sub)
+ {
+ /* Have the register data arrays been allocated? */
+ if (bufp->regs_allocated == REGS_UNALLOCATED)
+ { /* No. So allocate them with malloc. We need one
+ extra element beyond `num_regs' for the `-1' marker
+ GNU code uses. */
+ regs->num_regs = MAX (RE_NREGS, num_regs + 1);
+ regs->start = TALLOC (regs->num_regs, regoff_t);
+ regs->end = TALLOC (regs->num_regs, regoff_t);
+ if (regs->start == NULL || regs->end == NULL)
+ return -2;
+ bufp->regs_allocated = REGS_REALLOCATE;
+ }
+ else if (bufp->regs_allocated == REGS_REALLOCATE)
+ { /* Yes. If we need more elements than were already
+ allocated, reallocate them. If we need fewer, just
+ leave it alone. */
+ if (regs->num_regs < num_regs + 1)
+ {
+ regs->num_regs = num_regs + 1;
+ RETALLOC (regs->start, regs->num_regs, regoff_t);
+ RETALLOC (regs->end, regs->num_regs, regoff_t);
+ if (regs->start == NULL || regs->end == NULL)
+ return -2;
+ }
+ }
+ else
+ assert (bufp->regs_allocated == REGS_FIXED);
+
+ /* Convert the pointer data in `regstart' and `regend' to
+ indices. Register zero has to be set differently,
+ since we haven't kept track of any info for it. */
+ if (regs->num_regs > 0)
+ {
+ regs->start[0] = pos;
+ regs->end[0] = (MATCHING_IN_FIRST_STRING ? d - string1
+ : d - string2 + size1);
+ }
+
+ /* Go through the first `min (num_regs, regs->num_regs)'
+ registers, since that is all we initialized. */
+ for (mcnt = 1; mcnt < MIN (num_regs, regs->num_regs); mcnt++)
+ {
+ if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt]))
+ regs->start[mcnt] = regs->end[mcnt] = -1;
+ else
+ {
+ regs->start[mcnt] = POINTER_TO_OFFSET (regstart[mcnt]);
+ regs->end[mcnt] = POINTER_TO_OFFSET (regend[mcnt]);
+ }
+ }
+
+ /* If the regs structure we return has more elements than
+ were in the pattern, set the extra elements to -1. If
+ we (re)allocated the registers, this is the case,
+ because we always allocate enough to have at least one
+ -1 at the end. */
+ for (mcnt = num_regs; mcnt < regs->num_regs; mcnt++)
+ regs->start[mcnt] = regs->end[mcnt] = -1;
+ } /* regs && !bufp->no_sub */
+
+ FREE_VARIABLES ();
+ DEBUG_PRINT4 ("%u failure points pushed, %u popped (%u remain).\n",
+ nfailure_points_pushed, nfailure_points_popped,
+ nfailure_points_pushed - nfailure_points_popped);
+ DEBUG_PRINT2 ("%u registers pushed.\n", num_regs_pushed);
+
+ mcnt = d - pos - (MATCHING_IN_FIRST_STRING
+ ? string1
+ : string2 - size1);
+
+ DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt);
+
+ return mcnt;
+ }
+
+ /* Otherwise match next pattern command. */
+#ifdef SWITCH_ENUM_BUG
+ switch ((int) ((re_opcode_t) *p++))
+#else
+ switch ((re_opcode_t) *p++)
+#endif
+ {
+ /* Ignore these. Used to ignore the n of succeed_n's which
+ currently have n == 0. */
+ case no_op:
+ DEBUG_PRINT1 ("EXECUTING no_op.\n");
+ break;
+
+
+ /* Match the next n pattern characters exactly. The following
+ byte in the pattern defines n, and the n bytes after that
+ are the characters to match. */
+ case exactn:
+ mcnt = *p++;
+ DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt);
+
+ /* This is written out as an if-else so we don't waste time
+ testing `translate' inside the loop. */
+ if (translate)
+ {
+ do
+ {
+ PREFETCH ();
+ if (translate[(unsigned char) *d++] != (char) *p++)
+ goto fail;
+ }
+ while (--mcnt);
+ }
+ else
+ {
+ do
+ {
+ PREFETCH ();
+ if (*d++ != (char) *p++) goto fail;
+ }
+ while (--mcnt);
+ }
+ SET_REGS_MATCHED ();
+ break;
+
+
+ /* Match any character except possibly a newline or a null. */
+ case anychar:
+ DEBUG_PRINT1 ("EXECUTING anychar.\n");
+
+ PREFETCH ();
+
+ if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n')
+ || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000'))
+ goto fail;
+
+ SET_REGS_MATCHED ();
+ DEBUG_PRINT2 (" Matched `%d'.\n", *d);
+ d++;
+ break;
+
+
+ case charset:
+ case charset_not:
+ {
+ register unsigned char c;
+ boolean not = (re_opcode_t) *(p - 1) == charset_not;
+
+ DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : "");
+
+ PREFETCH ();
+ c = TRANSLATE (*d); /* The character to match. */
+
+ /* Cast to `unsigned' instead of `unsigned char' in case the
+ bit list is a full 32 bytes long. */
+ if (c < (unsigned) (*p * BYTEWIDTH)
+ && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
+ not = !not;
+
+ p += 1 + *p;
+
+ if (!not) goto fail;
+
+ SET_REGS_MATCHED ();
+ d++;
+ break;
+ }
+
+
+ /* The beginning of a group is represented by start_memory.
+ The arguments are the register number in the next byte, and the
+ number of groups inner to this one in the next. The text
+ matched within the group is recorded (in the internal
+ registers data structure) under the register number. */
+ case start_memory:
+ DEBUG_PRINT3 ("EXECUTING start_memory %d (%d):\n", *p, p[1]);
+
+ /* Find out if this group can match the empty string. */
+ p1 = p; /* To send to group_match_null_string_p. */
+
+ if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE)
+ REG_MATCH_NULL_STRING_P (reg_info[*p])
+ = group_match_null_string_p (&p1, pend, reg_info);
+
+ /* Save the position in the string where we were the last time
+ we were at this open-group operator in case the group is
+ operated upon by a repetition operator, e.g., with `(a*)*b'
+ against `ab'; then we want to ignore where we are now in
+ the string in case this attempt to match fails. */
+ old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
+ ? REG_UNSET (regstart[*p]) ? d : regstart[*p]
+ : regstart[*p];
+ DEBUG_PRINT2 (" old_regstart: %d\n",
+ POINTER_TO_OFFSET (old_regstart[*p]));
+
+ regstart[*p] = d;
+ DEBUG_PRINT2 (" regstart: %d\n", POINTER_TO_OFFSET (regstart[*p]));
+
+ IS_ACTIVE (reg_info[*p]) = 1;
+ MATCHED_SOMETHING (reg_info[*p]) = 0;
+
+ /* This is the new highest active register. */
+ highest_active_reg = *p;
+
+ /* If nothing was active before, this is the new lowest active
+ register. */
+ if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
+ lowest_active_reg = *p;
+
+ /* Move past the register number and inner group count. */
+ p += 2;
+ break;
+
+
+ /* The stop_memory opcode represents the end of a group. Its
+ arguments are the same as start_memory's: the register
+ number, and the number of inner groups. */
+ case stop_memory:
+ DEBUG_PRINT3 ("EXECUTING stop_memory %d (%d):\n", *p, p[1]);
+
+ /* We need to save the string position the last time we were at
+ this close-group operator in case the group is operated
+ upon by a repetition operator, e.g., with `((a*)*(b*)*)*'
+ against `aba'; then we want to ignore where we are now in
+ the string in case this attempt to match fails. */
+ old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
+ ? REG_UNSET (regend[*p]) ? d : regend[*p]
+ : regend[*p];
+ DEBUG_PRINT2 (" old_regend: %d\n",
+ POINTER_TO_OFFSET (old_regend[*p]));
+
+ regend[*p] = d;
+ DEBUG_PRINT2 (" regend: %d\n", POINTER_TO_OFFSET (regend[*p]));
+
+ /* This register isn't active anymore. */
+ IS_ACTIVE (reg_info[*p]) = 0;
+
+ /* If this was the only register active, nothing is active
+ anymore. */
+ if (lowest_active_reg == highest_active_reg)
+ {
+ lowest_active_reg = NO_LOWEST_ACTIVE_REG;
+ highest_active_reg = NO_HIGHEST_ACTIVE_REG;
+ }
+ else
+ { /* We must scan for the new highest active register, since
+ it isn't necessarily one less than now: consider
+ (a(b)c(d(e)f)g). When group 3 ends, after the f), the
+ new highest active register is 1. */
+ unsigned char r = *p - 1;
+ while (r > 0 && !IS_ACTIVE (reg_info[r]))
+ r--;
+
+ /* If we end up at register zero, that means that we saved
+ the registers as the result of an `on_failure_jump', not
+ a `start_memory', and we jumped to past the innermost
+ `stop_memory'. For example, in ((.)*) we save
+ registers 1 and 2 as a result of the *, but when we pop
+ back to the second ), we are at the stop_memory 1.
+ Thus, nothing is active. */
+ if (r == 0)
+ {
+ lowest_active_reg = NO_LOWEST_ACTIVE_REG;
+ highest_active_reg = NO_HIGHEST_ACTIVE_REG;
+ }
+ else
+ highest_active_reg = r;
+ }
+
+ /* If just failed to match something this time around with a
+ group that's operated on by a repetition operator, try to
+ force exit from the ``loop'', and restore the register
+ information for this group that we had before trying this
+ last match. */
+ if ((!MATCHED_SOMETHING (reg_info[*p])
+ || (re_opcode_t) p[-3] == start_memory)
+ && (p + 2) < pend)
+ {
+ boolean is_a_jump_n = false;
+
+ p1 = p + 2;
+ mcnt = 0;
+ switch ((re_opcode_t) *p1++)
+ {
+ case jump_n:
+ is_a_jump_n = true;
+ case pop_failure_jump:
+ case maybe_pop_jump:
+ case jump:
+ case dummy_failure_jump:
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+ if (is_a_jump_n)
+ p1 += 2;
+ break;
+
+ default:
+ /* do nothing */ ;
+ }
+ p1 += mcnt;
+
+ /* If the next operation is a jump backwards in the pattern
+ to an on_failure_jump right before the start_memory
+ corresponding to this stop_memory, exit from the loop
+ by forcing a failure after pushing on the stack the
+ on_failure_jump's jump in the pattern, and d. */
+ if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump
+ && (re_opcode_t) p1[3] == start_memory && p1[4] == *p)
+ {
+ /* If this group ever matched anything, then restore
+ what its registers were before trying this last
+ failed match, e.g., with `(a*)*b' against `ab' for
+ regstart[1], and, e.g., with `((a*)*(b*)*)*'
+ against `aba' for regend[3].
+
+ Also restore the registers for inner groups for,
+ e.g., `((a*)(b*))*' against `aba' (register 3 would
+ otherwise get trashed). */
+
+ if (EVER_MATCHED_SOMETHING (reg_info[*p]))
+ {
+ unsigned r;
+
+ EVER_MATCHED_SOMETHING (reg_info[*p]) = 0;
+
+ /* Restore this and inner groups' (if any) registers. */
+ for (r = *p; r < *p + *(p + 1); r++)
+ {
+ regstart[r] = old_regstart[r];
+
+ /* xx why this test? */
+ if ((int) old_regend[r] >= (int) regstart[r])
+ regend[r] = old_regend[r];
+ }
+ }
+ p1++;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+ PUSH_FAILURE_POINT (p1 + mcnt, d, -2);
+
+ goto fail;
+ }
+ }
+
+ /* Move past the register number and the inner group count. */
+ p += 2;
+ break;
+
+
+ /* \<digit> has been turned into a `duplicate' command which is
+ followed by the numeric value of <digit> as the register number. */
+ case duplicate:
+ {
+ register const char *d2, *dend2;
+ int regno = *p++; /* Get which register to match against. */
+ DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno);
+
+ /* Can't back reference a group which we've never matched. */
+ if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno]))
+ goto fail;
+
+ /* Where in input to try to start matching. */
+ d2 = regstart[regno];
+
+ /* Where to stop matching; if both the place to start and
+ the place to stop matching are in the same string, then
+ set to the place to stop, otherwise, for now have to use
+ the end of the first string. */
+
+ dend2 = ((FIRST_STRING_P (regstart[regno])
+ == FIRST_STRING_P (regend[regno]))
+ ? regend[regno] : end_match_1);
+ for (;;)
+ {
+ /* If necessary, advance to next segment in register
+ contents. */
+ while (d2 == dend2)
+ {
+ if (dend2 == end_match_2) break;
+ if (dend2 == regend[regno]) break;
+
+ /* End of string1 => advance to string2. */
+ d2 = string2;
+ dend2 = regend[regno];
+ }
+ /* At end of register contents => success */
+ if (d2 == dend2) break;
+
+ /* If necessary, advance to next segment in data. */
+ PREFETCH ();
+
+ /* How many characters left in this segment to match. */
+ mcnt = dend - d;
+
+ /* Want how many consecutive characters we can match in
+ one shot, so, if necessary, adjust the count. */
+ if (mcnt > dend2 - d2)
+ mcnt = dend2 - d2;
+
+ /* Compare that many; failure if mismatch, else move
+ past them. */
+ if (translate
+ ? bcmp_translate (d, d2, mcnt, translate)
+ : bcmp (d, d2, mcnt))
+ goto fail;
+ d += mcnt, d2 += mcnt;
+ }
+ }
+ break;
+
+
+ /* begline matches the empty string at the beginning of the string
+ (unless `not_bol' is set in `bufp'), and, if
+ `newline_anchor' is set, after newlines. */
+ case begline:
+ DEBUG_PRINT1 ("EXECUTING begline.\n");
+
+ if (AT_STRINGS_BEG (d))
+ {
+ if (!bufp->not_bol) break;
+ }
+ else if (d[-1] == '\n' && bufp->newline_anchor)
+ {
+ break;
+ }
+ /* In all other cases, we fail. */
+ goto fail;
+
+
+ /* endline is the dual of begline. */
+ case endline:
+ DEBUG_PRINT1 ("EXECUTING endline.\n");
+
+ if (AT_STRINGS_END (d))
+ {
+ if (!bufp->not_eol) break;
+ }
+
+ /* We have to ``prefetch'' the next character. */
+ else if ((d == end1 ? *string2 : *d) == '\n'
+ && bufp->newline_anchor)
+ {
+ break;
+ }
+ goto fail;
+
+
+ /* Match at the very beginning of the data. */
+ case begbuf:
+ DEBUG_PRINT1 ("EXECUTING begbuf.\n");
+ if (AT_STRINGS_BEG (d))
+ break;
+ goto fail;
+
+
+ /* Match at the very end of the data. */
+ case endbuf:
+ DEBUG_PRINT1 ("EXECUTING endbuf.\n");
+ if (AT_STRINGS_END (d))
+ break;
+ goto fail;
+
+
+ /* on_failure_keep_string_jump is used to optimize `.*\n'. It
+ pushes NULL as the value for the string on the stack. Then
+ `pop_failure_point' will keep the current value for the
+ string, instead of restoring it. To see why, consider
+ matching `foo\nbar' against `.*\n'. The .* matches the foo;
+ then the . fails against the \n. But the next thing we want
+ to do is match the \n against the \n; if we restored the
+ string value, we would be back at the foo.
+
+ Because this is used only in specific cases, we don't need to
+ check all the things that `on_failure_jump' does, to make
+ sure the right things get saved on the stack. Hence we don't
+ share its code. The only reason to push anything on the
+ stack at all is that otherwise we would have to change
+ `anychar's code to do something besides goto fail in this
+ case; that seems worse than this. */
+ case on_failure_keep_string_jump:
+ DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump");
+
+ EXTRACT_NUMBER_AND_INCR (mcnt, p);
+ DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt);
+
+ PUSH_FAILURE_POINT (p + mcnt, NULL, -2);
+ break;
+
+
+ /* Uses of on_failure_jump:
+
+ Each alternative starts with an on_failure_jump that points
+ to the beginning of the next alternative. Each alternative
+ except the last ends with a jump that in effect jumps past
+ the rest of the alternatives. (They really jump to the
+ ending jump of the following alternative, because tensioning
+ these jumps is a hassle.)
+
+ Repeats start with an on_failure_jump that points past both
+ the repetition text and either the following jump or
+ pop_failure_jump back to this on_failure_jump. */
+ case on_failure_jump:
+ on_failure:
+ DEBUG_PRINT1 ("EXECUTING on_failure_jump");
+
+ EXTRACT_NUMBER_AND_INCR (mcnt, p);
+ DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt);
+
+ /* If this on_failure_jump comes right before a group (i.e.,
+ the original * applied to a group), save the information
+ for that group and all inner ones, so that if we fail back
+ to this point, the group's information will be correct.
+ For example, in \(a*\)*\1, we need the preceding group,
+ and in \(\(a*\)b*\)\2, we need the inner group. */
+
+ /* We can't use `p' to check ahead because we push
+ a failure point to `p + mcnt' after we do this. */
+ p1 = p;
+
+ /* We need to skip no_op's before we look for the
+ start_memory in case this on_failure_jump is happening as
+ the result of a completed succeed_n, as in \(a\)\{1,3\}b\1
+ against aba. */
+ while (p1 < pend && (re_opcode_t) *p1 == no_op)
+ p1++;
+
+ if (p1 < pend && (re_opcode_t) *p1 == start_memory)
+ {
+ /* We have a new highest active register now. This will
+ get reset at the start_memory we are about to get to,
+ but we will have saved all the registers relevant to
+ this repetition op, as described above. */
+ highest_active_reg = *(p1 + 1) + *(p1 + 2);
+ if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
+ lowest_active_reg = *(p1 + 1);
+ }
+
+ DEBUG_PRINT1 (":\n");
+ PUSH_FAILURE_POINT (p + mcnt, d, -2);
+ break;
+
+
+ /* A smart repeat ends with `maybe_pop_jump'.
+ We change it to either `pop_failure_jump' or `jump'. */
+ case maybe_pop_jump:
+ EXTRACT_NUMBER_AND_INCR (mcnt, p);
+ DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt);
+ {
+ register unsigned char *p2 = p;
+
+ /* Compare the beginning of the repeat with what in the
+ pattern follows its end. If we can establish that there
+ is nothing that they would both match, i.e., that we
+ would have to backtrack because of (as in, e.g., `a*a')
+ then we can change to pop_failure_jump, because we'll
+ never have to backtrack.
+
+ This is not true in the case of alternatives: in
+ `(a|ab)*' we do need to backtrack to the `ab' alternative
+ (e.g., if the string was `ab'). But instead of trying to
+ detect that here, the alternative has put on a dummy
+ failure point which is what we will end up popping. */
+
+ /* Skip over open/close-group commands. */
+ while (p2 + 2 < pend
+ && ((re_opcode_t) *p2 == stop_memory
+ || (re_opcode_t) *p2 == start_memory))
+ p2 += 3; /* Skip over args, too. */
+
+ /* If we're at the end of the pattern, we can change. */
+ if (p2 == pend)
+ {
+ /* Consider what happens when matching ":\(.*\)"
+ against ":/". I don't really understand this code
+ yet. */
+ p[-3] = (unsigned char) pop_failure_jump;
+ DEBUG_PRINT1
+ (" End of pattern: change to `pop_failure_jump'.\n");
+ }
+
+ else if ((re_opcode_t) *p2 == exactn
+ || (bufp->newline_anchor && (re_opcode_t) *p2 == endline))
+ {
+ register unsigned char c
+ = *p2 == (unsigned char) endline ? '\n' : p2[2];
+ p1 = p + mcnt;
+
+ /* p1[0] ... p1[2] are the `on_failure_jump' corresponding
+ to the `maybe_finalize_jump' of this case. Examine what
+ follows. */
+ if ((re_opcode_t) p1[3] == exactn && p1[5] != c)
+ {
+ p[-3] = (unsigned char) pop_failure_jump;
+ DEBUG_PRINT3 (" %c != %c => pop_failure_jump.\n",
+ c, p1[5]);
+ }
+
+ else if ((re_opcode_t) p1[3] == charset
+ || (re_opcode_t) p1[3] == charset_not)
+ {
+ int not = (re_opcode_t) p1[3] == charset_not;
+
+ if (c < (unsigned char) (p1[4] * BYTEWIDTH)
+ && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
+ not = !not;
+
+ /* `not' is equal to 1 if c would match, which means
+ that we can't change to pop_failure_jump. */
+ if (!not)
+ {
+ p[-3] = (unsigned char) pop_failure_jump;
+ DEBUG_PRINT1 (" No match => pop_failure_jump.\n");
+ }
+ }
+ }
+ }
+ p -= 2; /* Point at relative address again. */
+ if ((re_opcode_t) p[-1] != pop_failure_jump)
+ {
+ p[-1] = (unsigned char) jump;
+ DEBUG_PRINT1 (" Match => jump.\n");
+ goto unconditional_jump;
+ }
+ /* Note fall through. */
+
+
+ /* The end of a simple repeat has a pop_failure_jump back to
+ its matching on_failure_jump, where the latter will push a
+ failure point. The pop_failure_jump takes off failure
+ points put on by this pop_failure_jump's matching
+ on_failure_jump; we got through the pattern to here from the
+ matching on_failure_jump, so didn't fail. */
+ case pop_failure_jump:
+ {
+ /* We need to pass separate storage for the lowest and
+ highest registers, even though we don't care about the
+ actual values. Otherwise, we will restore only one
+ register from the stack, since lowest will == highest in
+ `pop_failure_point'. */
+ unsigned dummy_low_reg, dummy_high_reg;
+ unsigned char *pdummy;
+ const char *sdummy;
+
+ DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n");
+ POP_FAILURE_POINT (sdummy, pdummy,
+ dummy_low_reg, dummy_high_reg,
+ reg_dummy, reg_dummy, reg_info_dummy);
+ }
+ /* Note fall through. */
+
+
+ /* Unconditionally jump (without popping any failure points). */
+ case jump:
+ unconditional_jump:
+ EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */
+ DEBUG_PRINT2 ("EXECUTING jump %d ", mcnt);
+ p += mcnt; /* Do the jump. */
+ DEBUG_PRINT2 ("(to 0x%x).\n", p);
+ break;
+
+
+ /* We need this opcode so we can detect where alternatives end
+ in `group_match_null_string_p' et al. */
+ case jump_past_alt:
+ DEBUG_PRINT1 ("EXECUTING jump_past_alt.\n");
+ goto unconditional_jump;
+
+
+ /* Normally, the on_failure_jump pushes a failure point, which
+ then gets popped at pop_failure_jump. We will end up at
+ pop_failure_jump, also, and with a pattern of, say, `a+', we
+ are skipping over the on_failure_jump, so we have to push
+ something meaningless for pop_failure_jump to pop. */
+ case dummy_failure_jump:
+ DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n");
+ /* It doesn't matter what we push for the string here. What
+ the code at `fail' tests is the value for the pattern. */
+ PUSH_FAILURE_POINT (0, 0, -2);
+ goto unconditional_jump;
+
+
+ /* At the end of an alternative, we need to push a dummy failure
+ point in case we are followed by a `pop_failure_jump', because
+ we don't want the failure point for the alternative to be
+ popped. For example, matching `(a|ab)*' against `aab'
+ requires that we match the `ab' alternative. */
+ case push_dummy_failure:
+ DEBUG_PRINT1 ("EXECUTING push_dummy_failure.\n");
+ /* See comments just above at `dummy_failure_jump' about the
+ two zeroes. */
+ PUSH_FAILURE_POINT (0, 0, -2);
+ break;
+
+ /* Have to succeed matching what follows at least n times.
+ After that, handle like `on_failure_jump'. */
+ case succeed_n:
+ EXTRACT_NUMBER (mcnt, p + 2);
+ DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt);
+
+ assert (mcnt >= 0);
+ /* Originally, this is how many times we HAVE to succeed. */
+ if (mcnt > 0)
+ {
+ mcnt--;
+ p += 2;
+ STORE_NUMBER_AND_INCR (p, mcnt);
+ DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p, mcnt);
+ }
+ else if (mcnt == 0)
+ {
+ DEBUG_PRINT2 (" Setting two bytes from 0x%x to no_op.\n", p+2);
+ p[2] = (unsigned char) no_op;
+ p[3] = (unsigned char) no_op;
+ goto on_failure;
+ }
+ break;
+
+ case jump_n:
+ EXTRACT_NUMBER (mcnt, p + 2);
+ DEBUG_PRINT2 ("EXECUTING jump_n %d.\n", mcnt);
+
+ /* Originally, this is how many times we CAN jump. */
+ if (mcnt)
+ {
+ mcnt--;
+ STORE_NUMBER (p + 2, mcnt);
+ goto unconditional_jump;
+ }
+ /* If don't have to jump any more, skip over the rest of command. */
+ else
+ p += 4;
+ break;
+
+ case set_number_at:
+ {
+ DEBUG_PRINT1 ("EXECUTING set_number_at.\n");
+
+ EXTRACT_NUMBER_AND_INCR (mcnt, p);
+ p1 = p + mcnt;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p);
+ DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p1, mcnt);
+ STORE_NUMBER (p1, mcnt);
+ break;
+ }
+
+ case wordbound:
+ DEBUG_PRINT1 ("EXECUTING wordbound.\n");
+ if (AT_WORD_BOUNDARY (d))
+ break;
+ goto fail;
+
+ case notwordbound:
+ DEBUG_PRINT1 ("EXECUTING notwordbound.\n");
+ if (AT_WORD_BOUNDARY (d))
+ goto fail;
+ break;
+
+ case wordbeg:
+ DEBUG_PRINT1 ("EXECUTING wordbeg.\n");
+ if (WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1)))
+ break;
+ goto fail;
+
+ case wordend:
+ DEBUG_PRINT1 ("EXECUTING wordend.\n");
+ if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1)
+ && (!WORDCHAR_P (d) || AT_STRINGS_END (d)))
+ break;
+ goto fail;
+
+#ifdef emacs
+#ifdef emacs19
+ case before_dot:
+ DEBUG_PRINT1 ("EXECUTING before_dot.\n");
+ if (PTR_CHAR_POS ((unsigned char *) d) >= point)
+ goto fail;
+ break;
+
+ case at_dot:
+ DEBUG_PRINT1 ("EXECUTING at_dot.\n");
+ if (PTR_CHAR_POS ((unsigned char *) d) != point)
+ goto fail;
+ break;
+
+ case after_dot:
+ DEBUG_PRINT1 ("EXECUTING after_dot.\n");
+ if (PTR_CHAR_POS ((unsigned char *) d) <= point)
+ goto fail;
+ break;
+#else /* not emacs19 */
+ case at_dot:
+ DEBUG_PRINT1 ("EXECUTING at_dot.\n");
+ if (PTR_CHAR_POS ((unsigned char *) d) + 1 != point)
+ goto fail;
+ break;
+#endif /* not emacs19 */
+
+ case syntaxspec:
+ DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt);
+ mcnt = *p++;
+ goto matchsyntax;
+
+ case wordchar:
+ DEBUG_PRINT1 ("EXECUTING Emacs wordchar.\n");
+ mcnt = (int) Sword;
+ matchsyntax:
+ PREFETCH ();
+ if (SYNTAX (*d++) != (enum syntaxcode) mcnt)
+ goto fail;
+ SET_REGS_MATCHED ();
+ break;
+
+ case notsyntaxspec:
+ DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt);
+ mcnt = *p++;
+ goto matchnotsyntax;
+
+ case notwordchar:
+ DEBUG_PRINT1 ("EXECUTING Emacs notwordchar.\n");
+ mcnt = (int) Sword;
+ matchnotsyntax:
+ PREFETCH ();
+ if (SYNTAX (*d++) == (enum syntaxcode) mcnt)
+ goto fail;
+ SET_REGS_MATCHED ();
+ break;
+
+#else /* not emacs */
+ case wordchar:
+ DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n");
+ PREFETCH ();
+ if (!WORDCHAR_P (d))
+ goto fail;
+ SET_REGS_MATCHED ();
+ d++;
+ break;
+
+ case notwordchar:
+ DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n");
+ PREFETCH ();
+ if (WORDCHAR_P (d))
+ goto fail;
+ SET_REGS_MATCHED ();
+ d++;
+ break;
+#endif /* not emacs */
+
+ default:
+ abort ();
+ }
+ continue; /* Successfully executed one pattern command; keep going. */
+
+
+ /* We goto here if a matching operation fails. */
+ fail:
+ if (!FAIL_STACK_EMPTY ())
+ { /* A restart point is known. Restore to that state. */
+ DEBUG_PRINT1 ("\nFAIL:\n");
+ POP_FAILURE_POINT (d, p,
+ lowest_active_reg, highest_active_reg,
+ regstart, regend, reg_info);
+
+ /* If this failure point is a dummy, try the next one. */
+ if (!p)
+ goto fail;
+
+ /* If we failed to the end of the pattern, don't examine *p. */
+ assert (p <= pend);
+ if (p < pend)
+ {
+ boolean is_a_jump_n = false;
+
+ /* If failed to a backwards jump that's part of a repetition
+ loop, need to pop this failure point and use the next one. */
+ switch ((re_opcode_t) *p)
+ {
+ case jump_n:
+ is_a_jump_n = true;
+ case maybe_pop_jump:
+ case pop_failure_jump:
+ case jump:
+ p1 = p + 1;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+ p1 += mcnt;
+
+ if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n)
+ || (!is_a_jump_n
+ && (re_opcode_t) *p1 == on_failure_jump))
+ goto fail;
+ break;
+ default:
+ /* do nothing */ ;
+ }
+ }
+
+ if (d >= string1 && d <= end1)
+ dend = end_match_1;
+ }
+ else
+ break; /* Matching at this starting point really fails. */
+ } /* for (;;) */
+
+ if (best_regs_set)
+ goto restore_best_regs;
+
+ FREE_VARIABLES ();
+
+ return -1; /* Failure to match. */
+} /* re_match_2 */
+
+/* Subroutine definitions for re_match_2. */
+
+
+/* We are passed P pointing to a register number after a start_memory.
+
+ Return true if the pattern up to the corresponding stop_memory can
+ match the empty string, and false otherwise.
+
+ If we find the matching stop_memory, sets P to point to one past its number.
+ Otherwise, sets P to an undefined byte less than or equal to END.
+
+ We don't handle duplicates properly (yet). */
+
+static boolean
+group_match_null_string_p (p, end, reg_info)
+ unsigned char **p, *end;
+ register_info_type *reg_info;
+{
+ int mcnt;
+ /* Point to after the args to the start_memory. */
+ unsigned char *p1 = *p + 2;
+
+ while (p1 < end)
+ {
+ /* Skip over opcodes that can match nothing, and return true or
+ false, as appropriate, when we get to one that can't, or to the
+ matching stop_memory. */
+
+ switch ((re_opcode_t) *p1)
+ {
+ /* Could be either a loop or a series of alternatives. */
+ case on_failure_jump:
+ p1++;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+
+ /* If the next operation is not a jump backwards in the
+ pattern. */
+
+ if (mcnt >= 0)
+ {
+ /* Go through the on_failure_jumps of the alternatives,
+ seeing if any of the alternatives cannot match nothing.
+ The last alternative starts with only a jump,
+ whereas the rest start with on_failure_jump and end
+ with a jump, e.g., here is the pattern for `a|b|c':
+
+ /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6
+ /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3
+ /exactn/1/c
+
+ So, we have to first go through the first (n-1)
+ alternatives and then deal with the last one separately. */
+
+
+ /* Deal with the first (n-1) alternatives, which start
+ with an on_failure_jump (see above) that jumps to right
+ past a jump_past_alt. */
+
+ while ((re_opcode_t) p1[mcnt-3] == jump_past_alt)
+ {
+ /* `mcnt' holds how many bytes long the alternative
+ is, including the ending `jump_past_alt' and
+ its number. */
+
+ if (!alt_match_null_string_p (p1, p1 + mcnt - 3,
+ reg_info))
+ return false;
+
+ /* Move to right after this alternative, including the
+ jump_past_alt. */
+ p1 += mcnt;
+
+ /* Break if it's the beginning of an n-th alternative
+ that doesn't begin with an on_failure_jump. */
+ if ((re_opcode_t) *p1 != on_failure_jump)
+ break;
+
+ /* Still have to check that it's not an n-th
+ alternative that starts with an on_failure_jump. */
+ p1++;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+ if ((re_opcode_t) p1[mcnt-3] != jump_past_alt)
+ {
+ /* Get to the beginning of the n-th alternative. */
+ p1 -= 3;
+ break;
+ }
+ }
+
+ /* Deal with the last alternative: go back and get number
+ of the `jump_past_alt' just before it. `mcnt' contains
+ the length of the alternative. */
+ EXTRACT_NUMBER (mcnt, p1 - 2);
+
+ if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info))
+ return false;
+
+ p1 += mcnt; /* Get past the n-th alternative. */
+ } /* if mcnt > 0 */
+ break;
+
+
+ case stop_memory:
+ assert (p1[1] == **p);
+ *p = p1 + 2;
+ return true;
+
+
+ default:
+ if (!common_op_match_null_string_p (&p1, end, reg_info))
+ return false;
+ }
+ } /* while p1 < end */
+
+ return false;
+} /* group_match_null_string_p */
+
+
+/* Similar to group_match_null_string_p, but doesn't deal with alternatives:
+ It expects P to be the first byte of a single alternative and END one
+ byte past the last. The alternative can contain groups. */
+
+static boolean
+alt_match_null_string_p (p, end, reg_info)
+ unsigned char *p, *end;
+ register_info_type *reg_info;
+{
+ int mcnt;
+ unsigned char *p1 = p;
+
+ while (p1 < end)
+ {
+ /* Skip over opcodes that can match nothing, and break when we get
+ to one that can't. */
+
+ switch ((re_opcode_t) *p1)
+ {
+ /* It's a loop. */
+ case on_failure_jump:
+ p1++;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+ p1 += mcnt;
+ break;
+
+ default:
+ if (!common_op_match_null_string_p (&p1, end, reg_info))
+ return false;
+ }
+ } /* while p1 < end */
+
+ return true;
+} /* alt_match_null_string_p */
+
+
+/* Deals with the ops common to group_match_null_string_p and
+ alt_match_null_string_p.
+
+ Sets P to one after the op and its arguments, if any. */
+
+static boolean
+common_op_match_null_string_p (p, end, reg_info)
+ unsigned char **p, *end;
+ register_info_type *reg_info;
+{
+ int mcnt;
+ boolean ret;
+ int reg_no;
+ unsigned char *p1 = *p;
+
+ switch ((re_opcode_t) *p1++)
+ {
+ case no_op:
+ case begline:
+ case endline:
+ case begbuf:
+ case endbuf:
+ case wordbeg:
+ case wordend:
+ case wordbound:
+ case notwordbound:
+#ifdef emacs
+ case before_dot:
+ case at_dot:
+ case after_dot:
+#endif
+ break;
+
+ case start_memory:
+ reg_no = *p1;
+ assert (reg_no > 0 && reg_no <= MAX_REGNUM);
+ ret = group_match_null_string_p (&p1, end, reg_info);
+
+ /* Have to set this here in case we're checking a group which
+ contains a group and a back reference to it. */
+
+ if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE)
+ REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret;
+
+ if (!ret)
+ return false;
+ break;
+
+ /* If this is an optimized succeed_n for zero times, make the jump. */
+ case jump:
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+ if (mcnt >= 0)
+ p1 += mcnt;
+ else
+ return false;
+ break;
+
+ case succeed_n:
+ /* Get to the number of times to succeed. */
+ p1 += 2;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+
+ if (mcnt == 0)
+ {
+ p1 -= 4;
+ EXTRACT_NUMBER_AND_INCR (mcnt, p1);
+ p1 += mcnt;
+ }
+ else
+ return false;
+ break;
+
+ case duplicate:
+ if (!REG_MATCH_NULL_STRING_P (reg_info[*p1]))
+ return false;
+ break;
+
+ case set_number_at:
+ p1 += 4;
+
+ default:
+ /* All other opcodes mean we cannot match the empty string. */
+ return false;
+ }
+
+ *p = p1;
+ return true;
+} /* common_op_match_null_string_p */
+
+
+/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN
+ bytes; nonzero otherwise. */
+
+static int
+bcmp_translate (s1, s2, len, translate)
+ unsigned char *s1, *s2;
+ register int len;
+ char *translate;
+{
+ register unsigned char *p1 = s1, *p2 = s2;
+ while (len)
+ {
+ if (translate[*p1++] != translate[*p2++]) return 1;
+ len--;
+ }
+ return 0;
+}
+
+/* Entry points for GNU code. */
+
+/* re_compile_pattern is the GNU regular expression compiler: it
+ compiles PATTERN (of length SIZE) and puts the result in BUFP.
+ Returns 0 if the pattern was valid, otherwise an error string.
+
+ Assumes the `allocated' (and perhaps `buffer') and `translate' fields
+ are set in BUFP on entry.
+
+ We call regex_compile to do the actual compilation. */
+
+const char *
+re_compile_pattern (pattern, length, bufp)
+ const char *pattern;
+ int length;
+ struct re_pattern_buffer *bufp;
+{
+ reg_errcode_t ret;
+
+ /* GNU code is written to assume at least RE_NREGS registers will be set
+ (and at least one extra will be -1). */
+ bufp->regs_allocated = REGS_UNALLOCATED;
+
+ /* And GNU code determines whether or not to get register information
+ by passing null for the REGS argument to re_match, etc., not by
+ setting no_sub. */
+ bufp->no_sub = 0;
+
+ /* Match anchors at newline. */
+ bufp->newline_anchor = 1;
+
+ ret = regex_compile (pattern, length, re_syntax_options, bufp);
+
+ return re_error_msg[(int) ret];
+}
+
+/* Entry points compatible with 4.2 BSD regex library. We don't define
+ them if this is an Emacs or POSIX compilation. */
+
+#if !defined (emacs)
+
+/* BSD has one and only one pattern buffer. */
+static struct re_pattern_buffer re_comp_buf;
+
+char *
+re_comp (s)
+ const char *s;
+{
+ reg_errcode_t ret;
+
+ if (!s)
+ {
+ if (!re_comp_buf.buffer)
+ return "No previous regular expression";
+ return 0;
+ }
+
+ if (!re_comp_buf.buffer)
+ {
+ re_comp_buf.buffer = (unsigned char *) malloc (200);
+ if (re_comp_buf.buffer == NULL)
+ return "Memory exhausted";
+ re_comp_buf.allocated = 200;
+
+ re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH);
+ if (re_comp_buf.fastmap == NULL)
+ return "Memory exhausted";
+ }
+
+ /* Since `re_exec' always passes NULL for the `regs' argument, we
+ don't need to initialize the pattern buffer fields which affect it. */
+
+ /* Match anchors at newlines. */
+ re_comp_buf.newline_anchor = 1;
+
+ ret = regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf);
+
+ /* Yes, we're discarding `const' here. */
+ return (char *) re_error_msg[(int) ret];
+}
+
+
+int
+re_exec (s)
+ const char *s;
+{
+ const int len = strlen (s);
+ return
+ 0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0);
+}
+#endif /* not emacs and not _POSIX_SOURCE */
+
+/* POSIX.2 functions. Don't define these for Emacs. */
+
+#ifndef emacs
+
+/* regcomp takes a regular expression as a string and compiles it.
+
+ PREG is a regex_t *. We do not expect any fields to be initialized,
+ since POSIX says we shouldn't. Thus, we set
+
+ `buffer' to the compiled pattern;
+ `used' to the length of the compiled pattern;
+ `syntax' to RE_SYNTAX_POSIX_EXTENDED if the
+ REG_EXTENDED bit in CFLAGS is set; otherwise, to
+ RE_SYNTAX_POSIX_BASIC;
+ `newline_anchor' to REG_NEWLINE being set in CFLAGS;
+ `fastmap' and `fastmap_accurate' to zero;
+ `re_nsub' to the number of subexpressions in PATTERN.
+
+ PATTERN is the address of the pattern string.
+
+ CFLAGS is a series of bits which affect compilation.
+
+ If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we
+ use POSIX basic syntax.
+
+ If REG_NEWLINE is set, then . and [^...] don't match newline.
+ Also, regexec will try a match beginning after every newline.
+
+ If REG_ICASE is set, then we considers upper- and lowercase
+ versions of letters to be equivalent when matching.
+
+ If REG_NOSUB is set, then when PREG is passed to regexec, that
+ routine will report only success or failure, and nothing about the
+ registers.
+
+ It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for
+ the return codes and their meanings.) */
+
+int
+regcomp (preg, pattern, cflags)
+ regex_t *preg;
+ const char *pattern;
+ int cflags;
+{
+ reg_errcode_t ret;
+ unsigned syntax
+ = (cflags & REG_EXTENDED) ?
+ RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC;
+
+ /* regex_compile will allocate the space for the compiled pattern. */
+ preg->buffer = 0;
+ preg->allocated = 0;
+
+ /* Don't bother to use a fastmap when searching. This simplifies the
+ REG_NEWLINE case: if we used a fastmap, we'd have to put all the
+ characters after newlines into the fastmap. This way, we just try
+ every character. */
+ preg->fastmap = 0;
+
+ if (cflags & REG_ICASE)
+ {
+ unsigned i;
+
+ preg->translate = (char *) malloc (CHAR_SET_SIZE);
+ if (preg->translate == NULL)
+ return (int) REG_ESPACE;
+
+ /* Map uppercase characters to corresponding lowercase ones. */
+ for (i = 0; i < CHAR_SET_SIZE; i++)
+ preg->translate[i] = ISUPPER (i) ? tolower (i) : i;
+ }
+ else
+ preg->translate = NULL;
+
+ /* If REG_NEWLINE is set, newlines are treated differently. */
+ if (cflags & REG_NEWLINE)
+ { /* REG_NEWLINE implies neither . nor [^...] match newline. */
+ syntax &= ~RE_DOT_NEWLINE;
+ syntax |= RE_HAT_LISTS_NOT_NEWLINE;
+ /* It also changes the matching behavior. */
+ preg->newline_anchor = 1;
+ }
+ else
+ preg->newline_anchor = 0;
+
+ preg->no_sub = !!(cflags & REG_NOSUB);
+
+ /* POSIX says a null character in the pattern terminates it, so we
+ can use strlen here in compiling the pattern. */
+ ret = regex_compile (pattern, strlen (pattern), syntax, preg);
+
+ /* POSIX doesn't distinguish between an unmatched open-group and an
+ unmatched close-group: both are REG_EPAREN. */
+ if (ret == REG_ERPAREN) ret = REG_EPAREN;
+
+ return (int) ret;
+}
+
+
+/* regexec searches for a given pattern, specified by PREG, in the
+ string STRING.
+
+ If NMATCH is zero or REG_NOSUB was set in the cflags argument to
+ `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at
+ least NMATCH elements, and we set them to the offsets of the
+ corresponding matched substrings.
+
+ EFLAGS specifies `execution flags' which affect matching: if
+ REG_NOTBOL is set, then ^ does not match at the beginning of the
+ string; if REG_NOTEOL is set, then $ does not match at the end.
+
+ We return 0 if we find a match and REG_NOMATCH if not. */
+
+int
+regexec (preg, string, nmatch, pmatch, eflags)
+ const regex_t *preg;
+ const char *string;
+ size_t nmatch;
+ regmatch_t pmatch[];
+ int eflags;
+{
+ int ret;
+ struct re_registers regs;
+ regex_t private_preg;
+ int len = strlen (string);
+ boolean want_reg_info = !preg->no_sub && nmatch > 0;
+
+ private_preg = *preg;
+
+ private_preg.not_bol = !!(eflags & REG_NOTBOL);
+ private_preg.not_eol = !!(eflags & REG_NOTEOL);
+
+ /* The user has told us exactly how many registers to return
+ information about, via `nmatch'. We have to pass that on to the
+ matching routines. */
+ private_preg.regs_allocated = REGS_FIXED;
+
+ if (want_reg_info)
+ {
+ regs.num_regs = nmatch;
+ regs.start = TALLOC (nmatch, regoff_t);
+ regs.end = TALLOC (nmatch, regoff_t);
+ if (regs.start == NULL || regs.end == NULL)
+ return (int) REG_NOMATCH;
+ }
+
+ /* Perform the searching operation. */
+ ret = re_search (&private_preg, string, len,
+ /* start: */ 0, /* range: */ len,
+ want_reg_info ? &regs : (struct re_registers *) 0);
+
+ /* Copy the register information to the POSIX structure. */
+ if (want_reg_info)
+ {
+ if (ret >= 0)
+ {
+ unsigned r;
+
+ for (r = 0; r < nmatch; r++)
+ {
+ pmatch[r].rm_so = regs.start[r];
+ pmatch[r].rm_eo = regs.end[r];
+ }
+ }
+
+ /* If we needed the temporary register info, free the space now. */
+ free (regs.start);
+ free (regs.end);
+ }
+
+ /* We want zero return to mean success, unlike `re_search'. */
+ return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH;
+}
+
+
+/* Returns a message corresponding to an error code, ERRCODE, returned
+ from either regcomp or regexec. We don't use PREG here. */
+
+size_t
+regerror (errcode, preg, errbuf, errbuf_size)
+ int errcode;
+ const regex_t *preg;
+ char *errbuf;
+ size_t errbuf_size;
+{
+ const char *msg;
+ size_t msg_size;
+
+ if (errcode < 0
+ || errcode >= (sizeof (re_error_msg) / sizeof (re_error_msg[0])))
+ /* Only error codes returned by the rest of the code should be passed
+ to this routine. If we are given anything else, or if other regex
+ code generates an invalid error code, then the program has a bug.
+ Dump core so we can fix it. */
+ abort ();
+
+ msg = re_error_msg[errcode];
+
+ /* POSIX doesn't require that we do anything in this case, but why
+ not be nice. */
+ if (! msg)
+ msg = "Success";
+
+ msg_size = strlen (msg) + 1; /* Includes the null. */
+
+ if (errbuf_size != 0)
+ {
+ if (msg_size > errbuf_size)
+ {
+ strncpy (errbuf, msg, errbuf_size - 1);
+ errbuf[errbuf_size - 1] = 0;
+ }
+ else
+ strcpy (errbuf, msg);
+ }
+
+ return msg_size;
+}
+
+
+/* Free dynamically allocated space used by PREG. */
+
+void
+regfree (preg)
+ regex_t *preg;
+{
+ if (preg->buffer != NULL)
+ free (preg->buffer);
+ preg->buffer = NULL;
+
+ preg->allocated = 0;
+ preg->used = 0;
+
+ if (preg->fastmap != NULL)
+ free (preg->fastmap);
+ preg->fastmap = NULL;
+ preg->fastmap_accurate = 0;
+
+ if (preg->translate != NULL)
+ free (preg->translate);
+ preg->translate = NULL;
+}
+
+#endif /* not emacs */
+
+/*
+Local variables:
+make-backup-files: t
+version-control: t
+trim-versions-without-asking: nil
+End:
+*/
diff --git a/contrib/cvs/lib/regex.h b/contrib/cvs/lib/regex.h
new file mode 100644
index 0000000..408dd21
--- /dev/null
+++ b/contrib/cvs/lib/regex.h
@@ -0,0 +1,490 @@
+/* Definitions for data structures and routines for the regular
+ expression library, version 0.12.
+
+ Copyright (C) 1985, 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef __REGEXP_LIBRARY_H__
+#define __REGEXP_LIBRARY_H__
+
+/* POSIX says that <sys/types.h> must be included (by the caller) before
+ <regex.h>. */
+
+#ifdef VMS
+/* VMS doesn't have `size_t' in <sys/types.h>, even though POSIX says it
+ should be there. */
+#include <stddef.h>
+#endif
+
+
+/* The following bits are used to determine the regexp syntax we
+ recognize. The set/not-set meanings are chosen so that Emacs syntax
+ remains the value 0. The bits are given in alphabetical order, and
+ the definitions shifted by one from the previous bit; thus, when we
+ add or remove a bit, only one other definition need change. */
+typedef unsigned reg_syntax_t;
+
+/* If this bit is not set, then \ inside a bracket expression is literal.
+ If set, then such a \ quotes the following character. */
+#define RE_BACKSLASH_ESCAPE_IN_LISTS (1)
+
+/* If this bit is not set, then + and ? are operators, and \+ and \? are
+ literals.
+ If set, then \+ and \? are operators and + and ? are literals. */
+#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
+
+/* If this bit is set, then character classes are supported. They are:
+ [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:],
+ [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:].
+ If not set, then character classes are not supported. */
+#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1)
+
+/* If this bit is set, then ^ and $ are always anchors (outside bracket
+ expressions, of course).
+ If this bit is not set, then it depends:
+ ^ is an anchor if it is at the beginning of a regular
+ expression or after an open-group or an alternation operator;
+ $ is an anchor if it is at the end of a regular expression, or
+ before a close-group or an alternation operator.
+
+ This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because
+ POSIX draft 11.2 says that * etc. in leading positions is undefined.
+ We already implemented a previous draft which made those constructs
+ invalid, though, so we haven't changed the code back. */
+#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1)
+
+/* If this bit is set, then special characters are always special
+ regardless of where they are in the pattern.
+ If this bit is not set, then special characters are special only in
+ some contexts; otherwise they are ordinary. Specifically,
+ * + ? and intervals are only special when not after the beginning,
+ open-group, or alternation operator. */
+#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1)
+
+/* If this bit is set, then *, +, ?, and { cannot be first in an re or
+ immediately after an alternation or begin-group operator. */
+#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1)
+
+/* If this bit is set, then . matches newline.
+ If not set, then it doesn't. */
+#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1)
+
+/* If this bit is set, then . doesn't match NUL.
+ If not set, then it does. */
+#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1)
+
+/* If this bit is set, nonmatching lists [^...] do not match newline.
+ If not set, they do. */
+#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1)
+
+/* If this bit is set, either \{...\} or {...} defines an
+ interval, depending on RE_NO_BK_BRACES.
+ If not set, \{, \}, {, and } are literals. */
+#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1)
+
+/* If this bit is set, +, ? and | aren't recognized as operators.
+ If not set, they are. */
+#define RE_LIMITED_OPS (RE_INTERVALS << 1)
+
+/* If this bit is set, newline is an alternation operator.
+ If not set, newline is literal. */
+#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1)
+
+/* If this bit is set, then `{...}' defines an interval, and \{ and \}
+ are literals.
+ If not set, then `\{...\}' defines an interval. */
+#define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1)
+
+/* If this bit is set, (...) defines a group, and \( and \) are literals.
+ If not set, \(...\) defines a group, and ( and ) are literals. */
+#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1)
+
+/* If this bit is set, then \<digit> matches <digit>.
+ If not set, then \<digit> is a back-reference. */
+#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1)
+
+/* If this bit is set, then | is an alternation operator, and \| is literal.
+ If not set, then \| is an alternation operator, and | is literal. */
+#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1)
+
+/* If this bit is set, then an ending range point collating higher
+ than the starting range point, as in [z-a], is invalid.
+ If not set, then when ending range point collates higher than the
+ starting range point, the range is ignored. */
+#define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1)
+
+/* If this bit is set, then an unmatched ) is ordinary.
+ If not set, then an unmatched ) is invalid. */
+#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1)
+
+/* This global variable defines the particular regexp syntax to use (for
+ some interfaces). When a regexp is compiled, the syntax used is
+ stored in the pattern buffer, so changing this does not affect
+ already-compiled regexps. */
+extern reg_syntax_t re_syntax_options;
+
+/* Define combinations of the above bits for the standard possibilities.
+ (The [[[ comments delimit what gets put into the Texinfo file, so
+ don't delete them!) */
+/* [[[begin syntaxes]]] */
+#define RE_SYNTAX_EMACS 0
+
+#define RE_SYNTAX_AWK \
+ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \
+ | RE_NO_BK_PARENS | RE_NO_BK_REFS \
+ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \
+ | RE_UNMATCHED_RIGHT_PAREN_ORD)
+
+#define RE_SYNTAX_POSIX_AWK \
+ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS)
+
+#define RE_SYNTAX_GREP \
+ (RE_BK_PLUS_QM | RE_CHAR_CLASSES \
+ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \
+ | RE_NEWLINE_ALT)
+
+#define RE_SYNTAX_EGREP \
+ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \
+ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \
+ | RE_NO_BK_VBAR)
+
+#define RE_SYNTAX_POSIX_EGREP \
+ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES)
+
+/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */
+#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC
+
+#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC
+
+/* Syntax bits common to both basic and extended POSIX regex syntax. */
+#define _RE_SYNTAX_POSIX_COMMON \
+ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \
+ | RE_INTERVALS | RE_NO_EMPTY_RANGES)
+
+#define RE_SYNTAX_POSIX_BASIC \
+ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM)
+
+/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes
+ RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this
+ isn't minimal, since other operators, such as \`, aren't disabled. */
+#define RE_SYNTAX_POSIX_MINIMAL_BASIC \
+ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS)
+
+#define RE_SYNTAX_POSIX_EXTENDED \
+ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \
+ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \
+ | RE_UNMATCHED_RIGHT_PAREN_ORD)
+
+/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS
+ replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */
+#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \
+ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \
+ | RE_NO_BK_PARENS | RE_NO_BK_REFS \
+ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD)
+/* [[[end syntaxes]]] */
+
+/* Maximum number of duplicates an interval can allow. Some systems
+ (erroneously) define this in other header files, but we want our
+ value, so remove any previous define. */
+#ifdef RE_DUP_MAX
+#undef RE_DUP_MAX
+#endif
+#define RE_DUP_MAX ((1 << 15) - 1)
+
+
+/* POSIX `cflags' bits (i.e., information for `regcomp'). */
+
+/* If this bit is set, then use extended regular expression syntax.
+ If not set, then use basic regular expression syntax. */
+#define REG_EXTENDED 1
+
+/* If this bit is set, then ignore case when matching.
+ If not set, then case is significant. */
+#define REG_ICASE (REG_EXTENDED << 1)
+
+/* If this bit is set, then anchors do not match at newline
+ characters in the string.
+ If not set, then anchors do match at newlines. */
+#define REG_NEWLINE (REG_ICASE << 1)
+
+/* If this bit is set, then report only success or fail in regexec.
+ If not set, then returns differ between not matching and errors. */
+#define REG_NOSUB (REG_NEWLINE << 1)
+
+
+/* POSIX `eflags' bits (i.e., information for regexec). */
+
+/* If this bit is set, then the beginning-of-line operator doesn't match
+ the beginning of the string (presumably because it's not the
+ beginning of a line).
+ If not set, then the beginning-of-line operator does match the
+ beginning of the string. */
+#define REG_NOTBOL 1
+
+/* Like REG_NOTBOL, except for the end-of-line. */
+#define REG_NOTEOL (1 << 1)
+
+
+/* If any error codes are removed, changed, or added, update the
+ `re_error_msg' table in regex.c. */
+typedef enum
+{
+ REG_NOERROR = 0, /* Success. */
+ REG_NOMATCH, /* Didn't find a match (for regexec). */
+
+ /* POSIX regcomp return error codes. (In the order listed in the
+ standard.) */
+ REG_BADPAT, /* Invalid pattern. */
+ REG_ECOLLATE, /* Not implemented. */
+ REG_ECTYPE, /* Invalid character class name. */
+ REG_EESCAPE, /* Trailing backslash. */
+ REG_ESUBREG, /* Invalid back reference. */
+ REG_EBRACK, /* Unmatched left bracket. */
+ REG_EPAREN, /* Parenthesis imbalance. */
+ REG_EBRACE, /* Unmatched \{. */
+ REG_BADBR, /* Invalid contents of \{\}. */
+ REG_ERANGE, /* Invalid range end. */
+ REG_ESPACE, /* Ran out of memory. */
+ REG_BADRPT, /* No preceding re for repetition op. */
+
+ /* Error codes we've added. */
+ REG_EEND, /* Premature end. */
+ REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */
+ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */
+} reg_errcode_t;
+
+/* This data structure represents a compiled pattern. Before calling
+ the pattern compiler, the fields `buffer', `allocated', `fastmap',
+ `translate', and `no_sub' can be set. After the pattern has been
+ compiled, the `re_nsub' field is available. All other fields are
+ private to the regex routines. */
+
+struct re_pattern_buffer
+{
+/* [[[begin pattern_buffer]]] */
+ /* Space that holds the compiled pattern. It is declared as
+ `unsigned char *' because its elements are
+ sometimes used as array indexes. */
+ unsigned char *buffer;
+
+ /* Number of bytes to which `buffer' points. */
+ unsigned long allocated;
+
+ /* Number of bytes actually used in `buffer'. */
+ unsigned long used;
+
+ /* Syntax setting with which the pattern was compiled. */
+ reg_syntax_t syntax;
+
+ /* Pointer to a fastmap, if any, otherwise zero. re_search uses
+ the fastmap, if there is one, to skip over impossible
+ starting points for matches. */
+ char *fastmap;
+
+ /* Either a translate table to apply to all characters before
+ comparing them, or zero for no translation. The translation
+ is applied to a pattern when it is compiled and to a string
+ when it is matched. */
+ char *translate;
+
+ /* Number of subexpressions found by the compiler. */
+ size_t re_nsub;
+
+ /* Zero if this pattern cannot match the empty string, one else.
+ Well, in truth it's used only in `re_search_2', to see
+ whether or not we should use the fastmap, so we don't set
+ this absolutely perfectly; see `re_compile_fastmap' (the
+ `duplicate' case). */
+ unsigned can_be_null : 1;
+
+ /* If REGS_UNALLOCATED, allocate space in the `regs' structure
+ for `max (RE_NREGS, re_nsub + 1)' groups.
+ If REGS_REALLOCATE, reallocate space if necessary.
+ If REGS_FIXED, use what's there. */
+#define REGS_UNALLOCATED 0
+#define REGS_REALLOCATE 1
+#define REGS_FIXED 2
+ unsigned regs_allocated : 2;
+
+ /* Set to zero when `regex_compile' compiles a pattern; set to one
+ by `re_compile_fastmap' if it updates the fastmap. */
+ unsigned fastmap_accurate : 1;
+
+ /* If set, `re_match_2' does not return information about
+ subexpressions. */
+ unsigned no_sub : 1;
+
+ /* If set, a beginning-of-line anchor doesn't match at the
+ beginning of the string. */
+ unsigned not_bol : 1;
+
+ /* Similarly for an end-of-line anchor. */
+ unsigned not_eol : 1;
+
+ /* If true, an anchor at a newline matches. */
+ unsigned newline_anchor : 1;
+
+/* [[[end pattern_buffer]]] */
+};
+
+typedef struct re_pattern_buffer regex_t;
+
+
+/* search.c (search_buffer) in Emacs needs this one opcode value. It is
+ defined both in `regex.c' and here. */
+#define RE_EXACTN_VALUE 1
+
+/* Type for byte offsets within the string. POSIX mandates this. */
+typedef int regoff_t;
+
+
+/* This is the structure we store register match data in. See
+ regex.texinfo for a full description of what registers match. */
+struct re_registers
+{
+ unsigned num_regs;
+ regoff_t *start;
+ regoff_t *end;
+};
+
+
+/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer,
+ `re_match_2' returns information about at least this many registers
+ the first time a `regs' structure is passed. */
+#ifndef RE_NREGS
+#define RE_NREGS 30
+#endif
+
+
+/* POSIX specification for registers. Aside from the different names than
+ `re_registers', POSIX uses an array of structures, instead of a
+ structure of arrays. */
+typedef struct
+{
+ regoff_t rm_so; /* Byte offset from string's start to substring's start. */
+ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */
+} regmatch_t;
+
+/* Declarations for routines. */
+
+/* To avoid duplicating every routine declaration -- once with a
+ prototype (if we are ANSI), and once without (if we aren't) -- we
+ use the following macro to declare argument types. This
+ unfortunately clutters up the declarations a bit, but I think it's
+ worth it. */
+
+#if __STDC__
+
+#define _RE_ARGS(args) args
+
+#else /* not __STDC__ */
+
+#define _RE_ARGS(args) ()
+
+#endif /* not __STDC__ */
+
+/* Sets the current default syntax to SYNTAX, and return the old syntax.
+ You can also simply assign to the `re_syntax_options' variable. */
+extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax));
+
+/* Compile the regular expression PATTERN, with length LENGTH
+ and syntax given by the global `re_syntax_options', into the buffer
+ BUFFER. Return NULL if successful, and an error string if not. */
+extern const char *re_compile_pattern
+ _RE_ARGS ((const char *pattern, int length,
+ struct re_pattern_buffer *buffer));
+
+
+/* Compile a fastmap for the compiled pattern in BUFFER; used to
+ accelerate searches. Return 0 if successful and -2 if was an
+ internal error. */
+extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer));
+
+
+/* Search in the string STRING (with length LENGTH) for the pattern
+ compiled into BUFFER. Start searching at position START, for RANGE
+ characters. Return the starting position of the match, -1 for no
+ match, or -2 for an internal error. Also return register
+ information in REGS (if REGS and BUFFER->no_sub are nonzero). */
+extern int re_search
+ _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
+ int length, int start, int range, struct re_registers *regs));
+
+
+/* Like `re_search', but search in the concatenation of STRING1 and
+ STRING2. Also, stop searching at index START + STOP. */
+extern int re_search_2
+ _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
+ int length1, const char *string2, int length2,
+ int start, int range, struct re_registers *regs, int stop));
+
+
+/* Like `re_search', but return how many characters in STRING the regexp
+ in BUFFER matched, starting at position START. */
+extern int re_match
+ _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
+ int length, int start, struct re_registers *regs));
+
+
+/* Relates to `re_match' as `re_search_2' relates to `re_search'. */
+extern int re_match_2
+ _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
+ int length1, const char *string2, int length2,
+ int start, struct re_registers *regs, int stop));
+
+
+/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
+ ENDS. Subsequent matches using BUFFER and REGS will use this memory
+ for recording register information. STARTS and ENDS must be
+ allocated with malloc, and must each be at least `NUM_REGS * sizeof
+ (regoff_t)' bytes long.
+
+ If NUM_REGS == 0, then subsequent matches should allocate their own
+ register data.
+
+ Unless this function is called, the first search or match using
+ PATTERN_BUFFER will allocate its own register data, without
+ freeing the old data. */
+extern void re_set_registers
+ _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs,
+ unsigned num_regs, regoff_t *starts, regoff_t *ends));
+
+/* 4.2 bsd compatibility. */
+extern char *re_comp _RE_ARGS ((const char *));
+extern int re_exec _RE_ARGS ((const char *));
+
+/* POSIX compatibility. */
+extern int regcomp _RE_ARGS ((regex_t *preg, const char *pattern, int cflags));
+extern int regexec
+ _RE_ARGS ((const regex_t *preg, const char *string, size_t nmatch,
+ regmatch_t pmatch[], int eflags));
+extern size_t regerror
+ _RE_ARGS ((int errcode, const regex_t *preg, char *errbuf,
+ size_t errbuf_size));
+extern void regfree _RE_ARGS ((regex_t *preg));
+
+#endif /* not __REGEXP_LIBRARY_H__ */
+
+/*
+Local variables:
+make-backup-files: t
+version-control: t
+trim-versions-without-asking: nil
+End:
+*/
diff --git a/contrib/cvs/lib/rename.c b/contrib/cvs/lib/rename.c
new file mode 100644
index 0000000..ce2805b
--- /dev/null
+++ b/contrib/cvs/lib/rename.c
@@ -0,0 +1,84 @@
+/* rename.c -- BSD compatible directory function for System V
+ Copyright (C) 1988, 1990 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#ifndef STDC_HEADERS
+extern int errno;
+#endif
+
+/* Rename file FROM to file TO.
+ Return 0 if successful, -1 if not. */
+
+int
+rename (from, to)
+ char *from;
+ char *to;
+{
+ struct stat from_stats;
+ int pid, status;
+
+ if (stat (from, &from_stats) == 0)
+ {
+ /* We don't check existence_error because the systems which need it
+ have rename(). */
+ if (unlink (to) && errno != ENOENT)
+ return -1;
+ if ((from_stats.st_mode & S_IFMT) == S_IFDIR)
+ {
+#ifdef MVDIR
+ /* I don't think MVDIR ever gets defined, but I don't think
+ it matters, because I don't think CVS ever calls rename()
+ on directories. */
+
+ /* Need a setuid root process to link and unlink directories. */
+ pid = fork ();
+ switch (pid)
+ {
+ case -1: /* Error. */
+ error (1, errno, "cannot fork");
+
+ case 0: /* Child. */
+ execl (MVDIR, "mvdir", from, to, (char *) 0);
+ error (255, errno, "cannot run `%s'", MVDIR);
+
+ default: /* Parent. */
+ while (wait (&status) != pid)
+ /* Do nothing. */ ;
+
+ errno = 0; /* mvdir printed the system error message. */
+ return status != 0 ? -1 : 0;
+ }
+#else /* no MVDIR */
+ error (1, 0, "internal error: cannot move directories");
+#endif /* no MVDIR */
+ }
+ else
+ {
+ /* We don't check existence_error because the systems which need it
+ have rename(). */
+ if (link (from, to) == 0 && (unlink (from) == 0 || errno == ENOENT))
+ return 0;
+ }
+ }
+ return -1;
+}
diff --git a/contrib/cvs/lib/savecwd.c b/contrib/cvs/lib/savecwd.c
new file mode 100644
index 0000000..b5b7cb8
--- /dev/null
+++ b/contrib/cvs/lib/savecwd.c
@@ -0,0 +1,141 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+#ifdef HAVE_DIRECT_H
+# include <direct.h>
+#endif
+
+#ifdef HAVE_IO_H
+# include <io.h>
+#endif
+
+#include <errno.h>
+# ifndef errno
+extern int errno;
+#endif
+
+#include "savecwd.h"
+#include "error.h"
+
+char *xgetwd __PROTO((void));
+
+/* Record the location of the current working directory in CWD so that
+ the program may change to other directories and later use restore_cwd
+ to return to the recorded location. This function may allocate
+ space using malloc (via xgetwd) or leave a file descriptor open;
+ use free_cwd to perform the necessary free or close. Upon failure,
+ no memory is allocated, any locally opened file descriptors are
+ closed; return non-zero -- in that case, free_cwd need not be
+ called, but doing so is ok. Otherwise, return zero. */
+
+int
+save_cwd (cwd)
+ struct saved_cwd *cwd;
+{
+ static int have_working_fchdir = 1;
+
+ cwd->desc = -1;
+ cwd->name = NULL;
+
+ if (have_working_fchdir)
+ {
+#ifdef HAVE_FCHDIR
+ cwd->desc = open (".", O_RDONLY);
+ if (cwd->desc < 0)
+ {
+ error (0, errno, "cannot open current directory");
+ return 1;
+ }
+
+# if __sun__ || sun
+ /* On SunOS 4, fchdir returns EINVAL if accounting is enabled,
+ so we have to fall back to chdir. */
+ if (fchdir (cwd->desc))
+ {
+ if (errno == EINVAL)
+ {
+ close (cwd->desc);
+ cwd->desc = -1;
+ have_working_fchdir = 0;
+ }
+ else
+ {
+ error (0, errno, "current directory");
+ close (cwd->desc);
+ cwd->desc = -1;
+ return 1;
+ }
+ }
+# endif /* __sun__ || sun */
+#else
+#define fchdir(x) (abort (), 0)
+ have_working_fchdir = 0;
+#endif
+ }
+
+ if (!have_working_fchdir)
+ {
+ cwd->name = xgetwd ();
+ if (cwd->name == NULL)
+ {
+ error (0, errno, "cannot get current directory");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Change to recorded location, CWD, in directory hierarchy.
+ If "saved working directory", NULL))
+ */
+
+int
+restore_cwd (cwd, dest)
+ const struct saved_cwd *cwd;
+ const char *dest;
+{
+ int fail = 0;
+ if (cwd->desc >= 0)
+ {
+ if (fchdir (cwd->desc))
+ {
+ error (0, errno, "cannot return to %s",
+ (dest ? dest : "saved working directory"));
+ fail = 1;
+ }
+ }
+ else if (chdir (cwd->name) < 0)
+ {
+ error (0, errno, "%s", cwd->name);
+ fail = 1;
+ }
+ return fail;
+}
+
+void
+free_cwd (cwd)
+ struct saved_cwd *cwd;
+{
+ if (cwd->desc >= 0)
+ close (cwd->desc);
+ if (cwd->name)
+ free (cwd->name);
+}
+
diff --git a/contrib/cvs/lib/savecwd.h b/contrib/cvs/lib/savecwd.h
new file mode 100644
index 0000000..f9802f8
--- /dev/null
+++ b/contrib/cvs/lib/savecwd.h
@@ -0,0 +1,20 @@
+#ifndef SAVE_CWD_H
+#define SAVE_CWD_H 1
+
+struct saved_cwd
+ {
+ int desc;
+ char *name;
+ };
+
+#if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
+#define __PROTO(args) args
+#else
+#define __PROTO(args) ()
+#endif /* GCC. */
+
+int save_cwd __PROTO((struct saved_cwd *cwd));
+int restore_cwd __PROTO((const struct saved_cwd *cwd, const char *dest));
+void free_cwd __PROTO((struct saved_cwd *cwd));
+
+#endif /* SAVE_CWD_H */
diff --git a/contrib/cvs/lib/sighandle.c b/contrib/cvs/lib/sighandle.c
new file mode 100644
index 0000000..ace7db3
--- /dev/null
+++ b/contrib/cvs/lib/sighandle.c
@@ -0,0 +1,414 @@
+/* sighandle.c -- Library routines for manipulating chains of signal handlers
+ Copyright (C) 1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Written by Paul Sander, HaL Computer Systems, Inc. <paul@hal.com>
+ Brian Berliner <berliner@Sun.COM> added POSIX support */
+
+/*************************************************************************
+ *
+ * signal.c -- This file contains code that manipulates chains of signal
+ * handlers.
+ *
+ * Facilities are provided to register a signal handler for
+ * any specific signal. When a signal is received, all of the
+ * registered signal handlers are invoked in the reverse order
+ * in which they are registered. Note that the signal handlers
+ * must not themselves make calls to the signal handling
+ * facilities.
+ *
+ * $CVSid: @(#)sighandle.c 1.13 94/10/07 $
+ *
+ *************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "system.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <signal.h>
+
+/* Add prototype support. */
+#ifndef PROTO
+#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
+#define PROTO(ARGS) ARGS
+#else
+#define PROTO(ARGS) ()
+#endif
+#endif
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+#if __STDC__
+char *calloc(unsigned nelem, unsigned size);
+char *malloc(unsigned size);
+#else
+char *calloc();
+char *malloc();
+#endif /* __STDC__ */
+#endif /* STDC_HEADERS */
+
+/* Define the highest signal number (usually) */
+#ifndef SIGMAX
+#define SIGMAX 64
+#endif
+
+/* Define linked list of signal handlers structure */
+struct SIG_hlist {
+ RETSIGTYPE (*handler)();
+ struct SIG_hlist *next;
+};
+
+/*
+ * Define array of lists of signal handlers. Note that this depends on
+ * the implementation to initialize each element to a null pointer.
+ */
+
+static struct SIG_hlist **SIG_handlers;
+
+/* Define array of default signal vectors */
+
+#ifdef POSIX_SIGNALS
+static struct sigaction *SIG_defaults;
+#else
+#ifdef BSD_SIGNALS
+static struct sigvec *SIG_defaults;
+#else
+static RETSIGTYPE (**SIG_defaults) PROTO ((int));
+#endif
+#endif
+
+/* Critical section housekeeping */
+static int SIG_crSectNest = 0; /* Nesting level */
+#ifdef POSIX_SIGNALS
+static sigset_t SIG_crSectMask; /* Signal mask */
+#else
+static int SIG_crSectMask; /* Signal mask */
+#endif
+
+/*
+ * Initialize the signal handler arrays
+ */
+
+static int SIG_init()
+{
+ int i;
+#ifdef POSIX_SIGNALS
+ sigset_t sigset_test;
+#endif
+
+ if (SIG_defaults && SIG_handlers) /* already allocated */
+ return (0);
+
+#ifdef POSIX_SIGNALS
+ (void) sigfillset(&sigset_test);
+ for (i = 1; i < SIGMAX && sigismember(&sigset_test, i) == 1; i++)
+ ;
+ if (i < SIGMAX)
+ i = SIGMAX;
+ i++;
+ if (!SIG_defaults)
+ SIG_defaults = (struct sigaction *)
+ calloc(i, sizeof(struct sigaction));
+ (void) sigemptyset(&SIG_crSectMask);
+#else
+ i = SIGMAX+1;
+#ifdef BSD_SIGNALS
+ if (!SIG_defaults)
+ SIG_defaults = (struct sigvec *)
+ calloc(i, sizeof(struct sigvec));
+#else
+ if (!SIG_defaults)
+ SIG_defaults = (RETSIGTYPE (**) PROTO ((int)) )
+ calloc(i, sizeof(RETSIGTYPE (**) PROTO ((int)) ));
+#endif
+ SIG_crSectMask = 0;
+#endif
+ if (!SIG_handlers)
+ SIG_handlers = (struct SIG_hlist **)
+ calloc(i, sizeof(struct SIG_hlist *));
+ return (!SIG_defaults || !SIG_handlers);
+}
+
+/*
+ * The following invokes each signal handler in the reverse order in which
+ * they were registered.
+ */
+static RETSIGTYPE SIG_handle PROTO ((int));
+
+static RETSIGTYPE SIG_handle(sig)
+int sig;
+{
+ struct SIG_hlist *this;
+
+ /* Dispatch signal handlers */
+ this = SIG_handlers[sig];
+ while (this != (struct SIG_hlist *) NULL)
+ {
+ (*this->handler)(sig);
+ this = this->next;
+ }
+
+ return;
+}
+
+/*
+ * The following registers a signal handler. If the handler is already
+ * registered, it is not registered twice, nor is the order in which signal
+ * handlers are invoked changed. If this is the first signal handler
+ * registered for a given signal, the old sigvec structure is saved for
+ * restoration later.
+ */
+
+int SIG_register(sig,fn)
+int sig;
+RETSIGTYPE (*fn)();
+{
+ int val;
+ struct SIG_hlist *this;
+#ifdef POSIX_SIGNALS
+ struct sigaction act;
+ sigset_t sigset_mask, sigset_omask;
+#else
+#ifdef BSD_SIGNALS
+ struct sigvec vec;
+ int mask;
+#endif
+#endif
+
+ /* Initialize */
+ if (SIG_init() != 0)
+ return (-1);
+ val = 0;
+
+ /* Block this signal while we look at handler chain */
+#ifdef POSIX_SIGNALS
+ (void) sigemptyset(&sigset_mask);
+ (void) sigaddset(&sigset_mask, sig);
+ (void) sigprocmask(SIG_BLOCK, &sigset_mask, &sigset_omask);
+#else
+#ifdef BSD_SIGNALS
+ mask = sigblock(sigmask(sig));
+#endif
+#endif
+
+ /* See if this handler was already registered */
+ this = SIG_handlers[sig];
+ while (this != (struct SIG_hlist *) NULL)
+ {
+ if (this->handler == fn) break;
+ this = this->next;
+ }
+
+ /* Register the new handler only if it is not already registered. */
+ if (this == (struct SIG_hlist *) NULL)
+ {
+
+ /*
+ * If this is the first handler registered for this signal,
+ * set up the signal handler dispatcher
+ */
+
+ if (SIG_handlers[sig] == (struct SIG_hlist *) NULL)
+ {
+#ifdef POSIX_SIGNALS
+ act.sa_handler = SIG_handle;
+ (void) sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ val = sigaction(sig, &act, &SIG_defaults[sig]);
+#else
+#ifdef BSD_SIGNALS
+ memset (&vec, 0, sizeof (vec));
+ vec.sv_handler = SIG_handle;
+ val = sigvec(sig, &vec, &SIG_defaults[sig]);
+#else
+ if ((SIG_defaults[sig] = signal(sig, SIG_handle)) == SIG_ERR)
+ val = -1;
+#endif
+#endif
+ }
+
+ /* If not, register it */
+ if ((val == 0) && (this == (struct SIG_hlist *) NULL))
+ {
+ this = (struct SIG_hlist *)
+ malloc(sizeof(struct SIG_hlist));
+ if (this == NULL)
+ {
+ val = -1;
+ }
+ else
+ {
+ this->handler = fn;
+ this->next = SIG_handlers[sig];
+ SIG_handlers[sig] = this;
+ }
+ }
+ }
+
+ /* Unblock the signal */
+#ifdef POSIX_SIGNALS
+ (void) sigprocmask(SIG_SETMASK, &sigset_omask, NULL);
+#else
+#ifdef BSD_SIGNALS
+ (void) sigsetmask(mask);
+#endif
+#endif
+
+ return val;
+}
+
+/*
+ * The following deregisters a signal handler. If the last signal handler for
+ * a given signal is deregistered, the default sigvec information is restored.
+ */
+
+int SIG_deregister(sig,fn)
+int sig;
+RETSIGTYPE (*fn)();
+{
+ int val;
+ struct SIG_hlist *this;
+ struct SIG_hlist *last;
+#ifdef POSIX_SIGNALS
+ sigset_t sigset_mask, sigset_omask;
+#else
+#ifdef BSD_SIGNALS
+ int mask;
+#endif
+#endif
+
+ /* Initialize */
+ if (SIG_init() != 0)
+ return (-1);
+ val = 0;
+ last = (struct SIG_hlist *) NULL;
+
+ /* Block this signal while we look at handler chain */
+#ifdef POSIX_SIGNALS
+ (void) sigemptyset(&sigset_mask);
+ (void) sigaddset(&sigset_mask, sig);
+ (void) sigprocmask(SIG_BLOCK, &sigset_mask, &sigset_omask);
+#else
+#ifdef BSD_SIGNALS
+ mask = sigblock(sigmask(sig));
+#endif
+#endif
+
+ /* Search for the signal handler */
+ this = SIG_handlers[sig];
+ while ((this != (struct SIG_hlist *) NULL) && (this->handler != fn))
+ {
+ last = this;
+ this = this->next;
+ }
+
+ /* If it was registered, remove it */
+ if (this != (struct SIG_hlist *) NULL)
+ {
+ if (last == (struct SIG_hlist *) NULL)
+ {
+ SIG_handlers[sig] = this->next;
+ }
+ else
+ {
+ last->next = this->next;
+ }
+ free((char *) this);
+ }
+
+ /* Restore default behavior if there are no registered handlers */
+ if (SIG_handlers[sig] == (struct SIG_hlist *) NULL)
+ {
+#ifdef POSIX_SIGNALS
+ val = sigaction(sig, &SIG_defaults[sig],
+ (struct sigaction *) NULL);
+#else
+#ifdef BSD_SIGNALS
+ val = sigvec(sig, &SIG_defaults[sig], (struct sigvec *) NULL);
+#else
+ if (signal(sig, SIG_defaults[sig]) == SIG_ERR)
+ val = -1;
+#endif
+#endif
+ }
+
+ /* Unblock the signal */
+#ifdef POSIX_SIGNALS
+ (void) sigprocmask(SIG_SETMASK, &sigset_omask, NULL);
+#else
+#ifdef BSD_SIGNALS
+ (void) sigsetmask(mask);
+#endif
+#endif
+
+ return val;
+}
+
+/*
+ * The following begins a critical section.
+ */
+
+void SIG_beginCrSect()
+{
+ if (SIG_init() == 0)
+ {
+ if (SIG_crSectNest == 0)
+ {
+#ifdef POSIX_SIGNALS
+ sigset_t sigset_mask;
+
+ (void) sigfillset(&sigset_mask);
+ (void) sigprocmask(SIG_SETMASK,
+ &sigset_mask, &SIG_crSectMask);
+#else
+#ifdef BSD_SIGNALS
+ SIG_crSectMask = sigblock(~0);
+#else
+ /* TBD */
+#endif
+#endif
+ }
+ SIG_crSectNest++;
+ }
+}
+
+/*
+ * The following ends a critical section.
+ */
+
+void SIG_endCrSect()
+{
+ if (SIG_init() == 0)
+ {
+ SIG_crSectNest--;
+ if (SIG_crSectNest == 0)
+ {
+#ifdef POSIX_SIGNALS
+ (void) sigprocmask(SIG_SETMASK, &SIG_crSectMask, NULL);
+#else
+#ifdef BSD_SIGNALS
+ (void) sigsetmask(SIG_crSectMask);
+#else
+ /* TBD */
+#endif
+#endif
+ }
+ }
+}
diff --git a/contrib/cvs/lib/strdup.c b/contrib/cvs/lib/strdup.c
new file mode 100644
index 0000000..46fc8a0
--- /dev/null
+++ b/contrib/cvs/lib/strdup.c
@@ -0,0 +1,43 @@
+/* strdup.c -- return a newly allocated copy of a string
+ Copyright (C) 1990 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef STDC_HEADERS
+#include <string.h>
+#include <stdlib.h>
+#else
+char *malloc ();
+char *strcpy ();
+#endif
+
+/* Return a newly allocated copy of STR,
+ or 0 if out of memory. */
+
+char *
+strdup (str)
+ char *str;
+{
+ char *newstr;
+
+ newstr = (char *) malloc (strlen (str) + 1);
+ if (newstr)
+ strcpy (newstr, str);
+ return newstr;
+}
diff --git a/contrib/cvs/lib/strerror.c b/contrib/cvs/lib/strerror.c
new file mode 100644
index 0000000..b0bec13
--- /dev/null
+++ b/contrib/cvs/lib/strerror.c
@@ -0,0 +1,813 @@
+/* Extended support for using errno values.
+ Copyright (C) 1992 Free Software Foundation, Inc.
+ Written by Fred Fish. fnf@cygnus.com
+
+This file is part of the libiberty library.
+Libiberty is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+Libiberty is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with libiberty; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+#include "config.h"
+
+#ifndef NEED_sys_errlist
+/* Note that errno.h (not sure what OS) or stdio.h (BSD 4.4, at least)
+ might declare sys_errlist in a way that the compiler might consider
+ incompatible with our later declaration, perhaps by using const
+ attributes. So we hide the declaration in errno.h (if any) using a
+ macro. */
+#define sys_errlist sys_errlist__
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#ifndef NEED_sys_errlist
+#undef sys_errlist
+#endif
+
+/* Routines imported from standard C runtime libraries. */
+
+#ifdef __STDC__
+#include <stddef.h>
+extern void *malloc (size_t size); /* 4.10.3.3 */
+extern void *memset (void *s, int c, size_t n); /* 4.11.6.1 */
+#else /* !__STDC__ */
+extern char *malloc (); /* Standard memory allocater */
+extern char *memset ();
+#endif /* __STDC__ */
+
+#ifndef MAX
+# define MAX(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+/* Translation table for errno values. See intro(2) in most UNIX systems
+ Programmers Reference Manuals.
+
+ Note that this table is generally only accessed when it is used at runtime
+ to initialize errno name and message tables that are indexed by errno
+ value.
+
+ Not all of these errnos will exist on all systems. This table is the only
+ thing that should have to be updated as new error numbers are introduced.
+ It's sort of ugly, but at least its portable. */
+
+struct error_info
+{
+ int value; /* The numeric value from <errno.h> */
+ char *name; /* The equivalent symbolic value */
+#ifdef NEED_sys_errlist
+ char *msg; /* Short message about this value */
+#endif
+};
+
+#ifdef NEED_sys_errlist
+# define ENTRY(value, name, msg) {value, name, msg}
+#else
+# define ENTRY(value, name, msg) {value, name}
+#endif
+
+static const struct error_info error_table[] =
+{
+#if defined (EPERM)
+ ENTRY(EPERM, "EPERM", "Not owner"),
+#endif
+#if defined (ENOENT)
+ ENTRY(ENOENT, "ENOENT", "No such file or directory"),
+#endif
+#if defined (ESRCH)
+ ENTRY(ESRCH, "ESRCH", "No such process"),
+#endif
+#if defined (EINTR)
+ ENTRY(EINTR, "EINTR", "Interrupted system call"),
+#endif
+#if defined (EIO)
+ ENTRY(EIO, "EIO", "I/O error"),
+#endif
+#if defined (ENXIO)
+ ENTRY(ENXIO, "ENXIO", "No such device or address"),
+#endif
+#if defined (E2BIG)
+ ENTRY(E2BIG, "E2BIG", "Arg list too long"),
+#endif
+#if defined (ENOEXEC)
+ ENTRY(ENOEXEC, "ENOEXEC", "Exec format error"),
+#endif
+#if defined (EBADF)
+ ENTRY(EBADF, "EBADF", "Bad file number"),
+#endif
+#if defined (ECHILD)
+ ENTRY(ECHILD, "ECHILD", "No child processes"),
+#endif
+#if defined (EWOULDBLOCK) /* Put before EAGAIN, sometimes aliased */
+ ENTRY(EWOULDBLOCK, "EWOULDBLOCK", "Operation would block"),
+#endif
+#if defined (EAGAIN)
+ ENTRY(EAGAIN, "EAGAIN", "No more processes"),
+#endif
+#if defined (ENOMEM)
+ ENTRY(ENOMEM, "ENOMEM", "Not enough space"),
+#endif
+#if defined (EACCES)
+ ENTRY(EACCES, "EACCES", "Permission denied"),
+#endif
+#if defined (EFAULT)
+ ENTRY(EFAULT, "EFAULT", "Bad address"),
+#endif
+#if defined (ENOTBLK)
+ ENTRY(ENOTBLK, "ENOTBLK", "Block device required"),
+#endif
+#if defined (EBUSY)
+ ENTRY(EBUSY, "EBUSY", "Device busy"),
+#endif
+#if defined (EEXIST)
+ ENTRY(EEXIST, "EEXIST", "File exists"),
+#endif
+#if defined (EXDEV)
+ ENTRY(EXDEV, "EXDEV", "Cross-device link"),
+#endif
+#if defined (ENODEV)
+ ENTRY(ENODEV, "ENODEV", "No such device"),
+#endif
+#if defined (ENOTDIR)
+ ENTRY(ENOTDIR, "ENOTDIR", "Not a directory"),
+#endif
+#if defined (EISDIR)
+ ENTRY(EISDIR, "EISDIR", "Is a directory"),
+#endif
+#if defined (EINVAL)
+ ENTRY(EINVAL, "EINVAL", "Invalid argument"),
+#endif
+#if defined (ENFILE)
+ ENTRY(ENFILE, "ENFILE", "File table overflow"),
+#endif
+#if defined (EMFILE)
+ ENTRY(EMFILE, "EMFILE", "Too many open files"),
+#endif
+#if defined (ENOTTY)
+ ENTRY(ENOTTY, "ENOTTY", "Not a typewriter"),
+#endif
+#if defined (ETXTBSY)
+ ENTRY(ETXTBSY, "ETXTBSY", "Text file busy"),
+#endif
+#if defined (EFBIG)
+ ENTRY(EFBIG, "EFBIG", "File too large"),
+#endif
+#if defined (ENOSPC)
+ ENTRY(ENOSPC, "ENOSPC", "No space left on device"),
+#endif
+#if defined (ESPIPE)
+ ENTRY(ESPIPE, "ESPIPE", "Illegal seek"),
+#endif
+#if defined (EROFS)
+ ENTRY(EROFS, "EROFS", "Read-only file system"),
+#endif
+#if defined (EMLINK)
+ ENTRY(EMLINK, "EMLINK", "Too many links"),
+#endif
+#if defined (EPIPE)
+ ENTRY(EPIPE, "EPIPE", "Broken pipe"),
+#endif
+#if defined (EDOM)
+ ENTRY(EDOM, "EDOM", "Math argument out of domain of func"),
+#endif
+#if defined (ERANGE)
+ ENTRY(ERANGE, "ERANGE", "Math result not representable"),
+#endif
+#if defined (ENOMSG)
+ ENTRY(ENOMSG, "ENOMSG", "No message of desired type"),
+#endif
+#if defined (EIDRM)
+ ENTRY(EIDRM, "EIDRM", "Identifier removed"),
+#endif
+#if defined (ECHRNG)
+ ENTRY(ECHRNG, "ECHRNG", "Channel number out of range"),
+#endif
+#if defined (EL2NSYNC)
+ ENTRY(EL2NSYNC, "EL2NSYNC", "Level 2 not synchronized"),
+#endif
+#if defined (EL3HLT)
+ ENTRY(EL3HLT, "EL3HLT", "Level 3 halted"),
+#endif
+#if defined (EL3RST)
+ ENTRY(EL3RST, "EL3RST", "Level 3 reset"),
+#endif
+#if defined (ELNRNG)
+ ENTRY(ELNRNG, "ELNRNG", "Link number out of range"),
+#endif
+#if defined (EUNATCH)
+ ENTRY(EUNATCH, "EUNATCH", "Protocol driver not attached"),
+#endif
+#if defined (ENOCSI)
+ ENTRY(ENOCSI, "ENOCSI", "No CSI structure available"),
+#endif
+#if defined (EL2HLT)
+ ENTRY(EL2HLT, "EL2HLT", "Level 2 halted"),
+#endif
+#if defined (EDEADLK)
+ ENTRY(EDEADLK, "EDEADLK", "Deadlock condition"),
+#endif
+#if defined (ENOLCK)
+ ENTRY(ENOLCK, "ENOLCK", "No record locks available"),
+#endif
+#if defined (EBADE)
+ ENTRY(EBADE, "EBADE", "Invalid exchange"),
+#endif
+#if defined (EBADR)
+ ENTRY(EBADR, "EBADR", "Invalid request descriptor"),
+#endif
+#if defined (EXFULL)
+ ENTRY(EXFULL, "EXFULL", "Exchange full"),
+#endif
+#if defined (ENOANO)
+ ENTRY(ENOANO, "ENOANO", "No anode"),
+#endif
+#if defined (EBADRQC)
+ ENTRY(EBADRQC, "EBADRQC", "Invalid request code"),
+#endif
+#if defined (EBADSLT)
+ ENTRY(EBADSLT, "EBADSLT", "Invalid slot"),
+#endif
+#if defined (EDEADLOCK)
+ ENTRY(EDEADLOCK, "EDEADLOCK", "File locking deadlock error"),
+#endif
+#if defined (EBFONT)
+ ENTRY(EBFONT, "EBFONT", "Bad font file format"),
+#endif
+#if defined (ENOSTR)
+ ENTRY(ENOSTR, "ENOSTR", "Device not a stream"),
+#endif
+#if defined (ENODATA)
+ ENTRY(ENODATA, "ENODATA", "No data available"),
+#endif
+#if defined (ETIME)
+ ENTRY(ETIME, "ETIME", "Timer expired"),
+#endif
+#if defined (ENOSR)
+ ENTRY(ENOSR, "ENOSR", "Out of streams resources"),
+#endif
+#if defined (ENONET)
+ ENTRY(ENONET, "ENONET", "Machine is not on the network"),
+#endif
+#if defined (ENOPKG)
+ ENTRY(ENOPKG, "ENOPKG", "Package not installed"),
+#endif
+#if defined (EREMOTE)
+ ENTRY(EREMOTE, "EREMOTE", "Object is remote"),
+#endif
+#if defined (ENOLINK)
+ ENTRY(ENOLINK, "ENOLINK", "Link has been severed"),
+#endif
+#if defined (EADV)
+ ENTRY(EADV, "EADV", "Advertise error"),
+#endif
+#if defined (ESRMNT)
+ ENTRY(ESRMNT, "ESRMNT", "Srmount error"),
+#endif
+#if defined (ECOMM)
+ ENTRY(ECOMM, "ECOMM", "Communication error on send"),
+#endif
+#if defined (EPROTO)
+ ENTRY(EPROTO, "EPROTO", "Protocol error"),
+#endif
+#if defined (EMULTIHOP)
+ ENTRY(EMULTIHOP, "EMULTIHOP", "Multihop attempted"),
+#endif
+#if defined (EDOTDOT)
+ ENTRY(EDOTDOT, "EDOTDOT", "RFS specific error"),
+#endif
+#if defined (EBADMSG)
+ ENTRY(EBADMSG, "EBADMSG", "Not a data message"),
+#endif
+#if defined (ENAMETOOLONG)
+ ENTRY(ENAMETOOLONG, "ENAMETOOLONG", "File name too long"),
+#endif
+#if defined (EOVERFLOW)
+ ENTRY(EOVERFLOW, "EOVERFLOW", "Value too large for defined data type"),
+#endif
+#if defined (ENOTUNIQ)
+ ENTRY(ENOTUNIQ, "ENOTUNIQ", "Name not unique on network"),
+#endif
+#if defined (EBADFD)
+ ENTRY(EBADFD, "EBADFD", "File descriptor in bad state"),
+#endif
+#if defined (EREMCHG)
+ ENTRY(EREMCHG, "EREMCHG", "Remote address changed"),
+#endif
+#if defined (ELIBACC)
+ ENTRY(ELIBACC, "ELIBACC", "Can not access a needed shared library"),
+#endif
+#if defined (ELIBBAD)
+ ENTRY(ELIBBAD, "ELIBBAD", "Accessing a corrupted shared library"),
+#endif
+#if defined (ELIBSCN)
+ ENTRY(ELIBSCN, "ELIBSCN", ".lib section in a.out corrupted"),
+#endif
+#if defined (ELIBMAX)
+ ENTRY(ELIBMAX, "ELIBMAX", "Attempting to link in too many shared libraries"),
+#endif
+#if defined (ELIBEXEC)
+ ENTRY(ELIBEXEC, "ELIBEXEC", "Cannot exec a shared library directly"),
+#endif
+#if defined (EILSEQ)
+ ENTRY(EILSEQ, "EILSEQ", "Illegal byte sequence"),
+#endif
+#if defined (ENOSYS)
+ ENTRY(ENOSYS, "ENOSYS", "Operation not applicable"),
+#endif
+#if defined (ELOOP)
+ ENTRY(ELOOP, "ELOOP", "Too many symbolic links encountered"),
+#endif
+#if defined (ERESTART)
+ ENTRY(ERESTART, "ERESTART", "Interrupted system call should be restarted"),
+#endif
+#if defined (ESTRPIPE)
+ ENTRY(ESTRPIPE, "ESTRPIPE", "Streams pipe error"),
+#endif
+#if defined (ENOTEMPTY)
+ ENTRY(ENOTEMPTY, "ENOTEMPTY", "Directory not empty"),
+#endif
+#if defined (EUSERS)
+ ENTRY(EUSERS, "EUSERS", "Too many users"),
+#endif
+#if defined (ENOTSOCK)
+ ENTRY(ENOTSOCK, "ENOTSOCK", "Socket operation on non-socket"),
+#endif
+#if defined (EDESTADDRREQ)
+ ENTRY(EDESTADDRREQ, "EDESTADDRREQ", "Destination address required"),
+#endif
+#if defined (EMSGSIZE)
+ ENTRY(EMSGSIZE, "EMSGSIZE", "Message too long"),
+#endif
+#if defined (EPROTOTYPE)
+ ENTRY(EPROTOTYPE, "EPROTOTYPE", "Protocol wrong type for socket"),
+#endif
+#if defined (ENOPROTOOPT)
+ ENTRY(ENOPROTOOPT, "ENOPROTOOPT", "Protocol not available"),
+#endif
+#if defined (EPROTONOSUPPORT)
+ ENTRY(EPROTONOSUPPORT, "EPROTONOSUPPORT", "Protocol not supported"),
+#endif
+#if defined (ESOCKTNOSUPPORT)
+ ENTRY(ESOCKTNOSUPPORT, "ESOCKTNOSUPPORT", "Socket type not supported"),
+#endif
+#if defined (EOPNOTSUPP)
+ ENTRY(EOPNOTSUPP, "EOPNOTSUPP", "Operation not supported on transport endpoint"),
+#endif
+#if defined (EPFNOSUPPORT)
+ ENTRY(EPFNOSUPPORT, "EPFNOSUPPORT", "Protocol family not supported"),
+#endif
+#if defined (EAFNOSUPPORT)
+ ENTRY(EAFNOSUPPORT, "EAFNOSUPPORT", "Address family not supported by protocol"),
+#endif
+#if defined (EADDRINUSE)
+ ENTRY(EADDRINUSE, "EADDRINUSE", "Address already in use"),
+#endif
+#if defined (EADDRNOTAVAIL)
+ ENTRY(EADDRNOTAVAIL, "EADDRNOTAVAIL","Cannot assign requested address"),
+#endif
+#if defined (ENETDOWN)
+ ENTRY(ENETDOWN, "ENETDOWN", "Network is down"),
+#endif
+#if defined (ENETUNREACH)
+ ENTRY(ENETUNREACH, "ENETUNREACH", "Network is unreachable"),
+#endif
+#if defined (ENETRESET)
+ ENTRY(ENETRESET, "ENETRESET", "Network dropped connection because of reset"),
+#endif
+#if defined (ECONNABORTED)
+ ENTRY(ECONNABORTED, "ECONNABORTED", "Software caused connection abort"),
+#endif
+#if defined (ECONNRESET)
+ ENTRY(ECONNRESET, "ECONNRESET", "Connection reset by peer"),
+#endif
+#if defined (ENOBUFS)
+ ENTRY(ENOBUFS, "ENOBUFS", "No buffer space available"),
+#endif
+#if defined (EISCONN)
+ ENTRY(EISCONN, "EISCONN", "Transport endpoint is already connected"),
+#endif
+#if defined (ENOTCONN)
+ ENTRY(ENOTCONN, "ENOTCONN", "Transport endpoint is not connected"),
+#endif
+#if defined (ESHUTDOWN)
+ ENTRY(ESHUTDOWN, "ESHUTDOWN", "Cannot send after transport endpoint shutdown"),
+#endif
+#if defined (ETOOMANYREFS)
+ ENTRY(ETOOMANYREFS, "ETOOMANYREFS", "Too many references: cannot splice"),
+#endif
+#if defined (ETIMEDOUT)
+ ENTRY(ETIMEDOUT, "ETIMEDOUT", "Connection timed out"),
+#endif
+#if defined (ECONNREFUSED)
+ ENTRY(ECONNREFUSED, "ECONNREFUSED", "Connection refused"),
+#endif
+#if defined (EHOSTDOWN)
+ ENTRY(EHOSTDOWN, "EHOSTDOWN", "Host is down"),
+#endif
+#if defined (EHOSTUNREACH)
+ ENTRY(EHOSTUNREACH, "EHOSTUNREACH", "No route to host"),
+#endif
+#if defined (EALREADY)
+ ENTRY(EALREADY, "EALREADY", "Operation already in progress"),
+#endif
+#if defined (EINPROGRESS)
+ ENTRY(EINPROGRESS, "EINPROGRESS", "Operation now in progress"),
+#endif
+#if defined (ESTALE)
+ ENTRY(ESTALE, "ESTALE", "Stale NFS file handle"),
+#endif
+#if defined (EUCLEAN)
+ ENTRY(EUCLEAN, "EUCLEAN", "Structure needs cleaning"),
+#endif
+#if defined (ENOTNAM)
+ ENTRY(ENOTNAM, "ENOTNAM", "Not a XENIX named type file"),
+#endif
+#if defined (ENAVAIL)
+ ENTRY(ENAVAIL, "ENAVAIL", "No XENIX semaphores available"),
+#endif
+#if defined (EISNAM)
+ ENTRY(EISNAM, "EISNAM", "Is a named type file"),
+#endif
+#if defined (EREMOTEIO)
+ ENTRY(EREMOTEIO, "EREMOTEIO", "Remote I/O error"),
+#endif
+ ENTRY(0, NULL, NULL)
+};
+
+/* Translation table allocated and initialized at runtime. Indexed by the
+ errno value to find the equivalent symbolic value. */
+
+static char **error_names;
+static int num_error_names = 0;
+
+/* Translation table allocated and initialized at runtime, if it does not
+ already exist in the host environment. Indexed by the errno value to find
+ the descriptive string.
+
+ We don't export it for use in other modules because even though it has the
+ same name, it differs from other implementations in that it is dynamically
+ initialized rather than statically initialized. */
+
+#ifdef NEED_sys_errlist
+
+static int sys_nerr;
+static char **sys_errlist;
+
+#else
+
+extern int sys_nerr;
+extern char *sys_errlist[];
+
+#endif
+
+
+/*
+
+NAME
+
+ init_error_tables -- initialize the name and message tables
+
+SYNOPSIS
+
+ static void init_error_tables ();
+
+DESCRIPTION
+
+ Using the error_table, which is initialized at compile time, generate
+ the error_names and the sys_errlist (if needed) tables, which are
+ indexed at runtime by a specific errno value.
+
+BUGS
+
+ The initialization of the tables may fail under low memory conditions,
+ in which case we don't do anything particularly useful, but we don't
+ bomb either. Who knows, it might succeed at a later point if we free
+ some memory in the meantime. In any case, the other routines know
+ how to deal with lack of a table after trying to initialize it. This
+ may or may not be considered to be a bug, that we don't specifically
+ warn about this particular failure mode.
+
+*/
+
+static void
+init_error_tables ()
+{
+ const struct error_info *eip;
+ int nbytes;
+
+ /* If we haven't already scanned the error_table once to find the maximum
+ errno value, then go find it now. */
+
+ if (num_error_names == 0)
+ {
+ for (eip = error_table; eip -> name != NULL; eip++)
+ {
+ if (eip -> value >= num_error_names)
+ {
+ num_error_names = eip -> value + 1;
+ }
+ }
+ }
+
+ /* Now attempt to allocate the error_names table, zero it out, and then
+ initialize it from the statically initialized error_table. */
+
+ if (error_names == NULL)
+ {
+ nbytes = num_error_names * sizeof (char *);
+ if ((error_names = (char **) malloc (nbytes)) != NULL)
+ {
+ memset (error_names, 0, nbytes);
+ for (eip = error_table; eip -> name != NULL; eip++)
+ {
+ error_names[eip -> value] = eip -> name;
+ }
+ }
+ }
+
+#ifdef NEED_sys_errlist
+
+ /* Now attempt to allocate the sys_errlist table, zero it out, and then
+ initialize it from the statically initialized error_table. */
+
+ if (sys_errlist == NULL)
+ {
+ nbytes = num_error_names * sizeof (char *);
+ if ((sys_errlist = (char **) malloc (nbytes)) != NULL)
+ {
+ memset (sys_errlist, 0, nbytes);
+ sys_nerr = num_error_names;
+ for (eip = error_table; eip -> name != NULL; eip++)
+ {
+ sys_errlist[eip -> value] = eip -> msg;
+ }
+ }
+ }
+
+#endif
+
+}
+
+/*
+
+NAME
+
+ errno_max -- return the max errno value
+
+SYNOPSIS
+
+ int errno_max ();
+
+DESCRIPTION
+
+ Returns the maximum errno value for which a corresponding symbolic
+ name or message is available. Note that in the case where
+ we use the sys_errlist supplied by the system, it is possible for
+ there to be more symbolic names than messages, or vice versa.
+ In fact, the manual page for perror(3C) explicitly warns that one
+ should check the size of the table (sys_nerr) before indexing it,
+ since new error codes may be added to the system before they are
+ added to the table. Thus sys_nerr might be smaller than value
+ implied by the largest errno value defined in <errno.h>.
+
+ We return the maximum value that can be used to obtain a meaningful
+ symbolic name or message.
+
+*/
+
+int
+errno_max ()
+{
+ int maxsize;
+
+ if (error_names == NULL)
+ {
+ init_error_tables ();
+ }
+ maxsize = MAX (sys_nerr, num_error_names);
+ return (maxsize - 1);
+}
+
+/*
+
+NAME
+
+ strerror -- map an error number to an error message string
+
+SYNOPSIS
+
+ char *strerror (int errnoval)
+
+DESCRIPTION
+
+ Maps an errno number to an error message string, the contents of
+ which are implementation defined. On systems which have the external
+ variables sys_nerr and sys_errlist, these strings will be the same
+ as the ones used by perror().
+
+ If the supplied error number is within the valid range of indices
+ for the sys_errlist, but no message is available for the particular
+ error number, then returns the string "Error NUM", where NUM is the
+ error number.
+
+ If the supplied error number is not a valid index into sys_errlist,
+ returns NULL.
+
+ The returned string is only guaranteed to be valid only until the
+ next call to strerror.
+
+*/
+
+char *
+strerror (errnoval)
+ int errnoval;
+{
+ char *msg;
+ static char buf[32];
+
+#ifdef NEED_sys_errlist
+
+ if (error_names == NULL)
+ {
+ init_error_tables ();
+ }
+
+#endif
+
+ if ((errnoval < 0) || (errnoval >= sys_nerr))
+ {
+ /* Out of range, just return NULL */
+ msg = NULL;
+ }
+ else if ((sys_errlist == NULL) || (sys_errlist[errnoval] == NULL))
+ {
+ /* In range, but no sys_errlist or no entry at this index. */
+ sprintf (buf, "Error %d", errnoval);
+ msg = buf;
+ }
+ else
+ {
+ /* In range, and a valid message. Just return the message. */
+ msg = sys_errlist[errnoval];
+ }
+
+ return (msg);
+}
+
+
+
+/*
+
+NAME
+
+ strerrno -- map an error number to a symbolic name string
+
+SYNOPSIS
+
+ char *strerrno (int errnoval)
+
+DESCRIPTION
+
+ Given an error number returned from a system call (typically
+ returned in errno), returns a pointer to a string containing the
+ symbolic name of that error number, as found in <errno.h>.
+
+ If the supplied error number is within the valid range of indices
+ for symbolic names, but no name is available for the particular
+ error number, then returns the string "Error NUM", where NUM is
+ the error number.
+
+ If the supplied error number is not within the range of valid
+ indices, then returns NULL.
+
+BUGS
+
+ The contents of the location pointed to are only guaranteed to be
+ valid until the next call to strerrno.
+
+*/
+
+char *
+strerrno (errnoval)
+ int errnoval;
+{
+ char *name;
+ static char buf[32];
+
+ if (error_names == NULL)
+ {
+ init_error_tables ();
+ }
+
+ if ((errnoval < 0) || (errnoval >= num_error_names))
+ {
+ /* Out of range, just return NULL */
+ name = NULL;
+ }
+ else if ((error_names == NULL) || (error_names[errnoval] == NULL))
+ {
+ /* In range, but no error_names or no entry at this index. */
+ sprintf (buf, "Error %d", errnoval);
+ name = buf;
+ }
+ else
+ {
+ /* In range, and a valid name. Just return the name. */
+ name = error_names[errnoval];
+ }
+
+ return (name);
+}
+
+/*
+
+NAME
+
+ strtoerrno -- map a symbolic errno name to a numeric value
+
+SYNOPSIS
+
+ int strtoerrno (char *name)
+
+DESCRIPTION
+
+ Given the symbolic name of a error number, map it to an errno value.
+ If no translation is found, returns 0.
+
+*/
+
+int
+strtoerrno (name)
+ char *name;
+{
+ int errnoval = 0;
+
+ if (name != NULL)
+ {
+ if (error_names == NULL)
+ {
+ init_error_tables ();
+ }
+ for (errnoval = 0; errnoval < num_error_names; errnoval++)
+ {
+ if ((error_names[errnoval] != NULL) &&
+ (strcmp (name, error_names[errnoval]) == 0))
+ {
+ break;
+ }
+ }
+ if (errnoval == num_error_names)
+ {
+ errnoval = 0;
+ }
+ }
+ return (errnoval);
+}
+
+
+/* A simple little main that does nothing but print all the errno translations
+ if MAIN is defined and this file is compiled and linked. */
+
+#ifdef MAIN
+
+main ()
+{
+ int errn;
+ int errnmax;
+ char *name;
+ char *msg;
+ char *strerrno ();
+ char *strerror ();
+
+ errnmax = errno_max ();
+ printf ("%d entries in names table.\n", num_error_names);
+ printf ("%d entries in messages table.\n", sys_nerr);
+ printf ("%d is max useful index.\n", errnmax);
+
+ /* Keep printing values until we get to the end of *both* tables, not
+ *either* table. Note that knowing the maximum useful index does *not*
+ relieve us of the responsibility of testing the return pointer for
+ NULL. */
+
+ for (errn = 0; errn <= errnmax; errn++)
+ {
+ name = strerrno (errn);
+ name = (name == NULL) ? "<NULL>" : name;
+ msg = strerror (errn);
+ msg = (msg == NULL) ? "<NULL>" : msg;
+ printf ("%-4d%-18s%s\n", errn, name, msg);
+ }
+}
+
+#endif
diff --git a/contrib/cvs/lib/strippath.c b/contrib/cvs/lib/strippath.c
new file mode 100644
index 0000000..39687f9
--- /dev/null
+++ b/contrib/cvs/lib/strippath.c
@@ -0,0 +1,80 @@
+/* strippath.c -- remove unnecessary components from a path specifier
+ Copyright (C) 1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if STDC_HEADERS || HAVE_STRING_H
+#include <string.h>
+/* An ANSI string.h and pre-ANSI memory.h might conflict. */
+#if !STDC_HEADERS && HAVE_MEMORY_H
+#include <memory.h>
+#endif /* not STDC_HEADERS and HAVE_MEMORY_H */
+#else /* not STDC_HJEADERS and not HAVE_STRING_H */
+#include <strings.h>
+/* memory.h and strings.h conflict on some systems. */
+#endif /* not STDC_HEADERS and not HAVE_STRING_H */
+
+#include <stdio.h>
+
+#if __STDC__
+static void remove_component(char *beginc, char *endc);
+void strip_trailing_slashes(char *path);
+#else
+static void remove_component();
+void strip_trailing_slashes();
+#endif /* __STDC__ */
+
+/* Remove unnecessary components from PATH. */
+
+void
+strip_path (path)
+ char *path;
+{
+ int stripped = 0;
+ char *cp, *slash;
+
+ for (cp = path; (slash = strchr(cp, '/')) != NULL; cp = slash)
+ {
+ *slash = '\0';
+ if ((!*cp && (cp != path || stripped)) ||
+ strcmp(cp, ".") == 0 || strcmp(cp, "/") == 0)
+ {
+ stripped = 1;
+ remove_component(cp, slash);
+ slash = cp;
+ }
+ else
+ {
+ *slash++ = '/';
+ }
+ }
+ strip_trailing_slashes(path);
+}
+
+/* Remove the component delimited by BEGINC and ENDC from the path */
+
+static void
+remove_component (beginc, endc)
+ char *beginc;
+ char *endc;
+{
+ for (endc++; *endc; endc++)
+ *beginc++ = *endc;
+ *beginc = '\0';
+}
diff --git a/contrib/cvs/lib/stripslash.c b/contrib/cvs/lib/stripslash.c
new file mode 100644
index 0000000..265950e
--- /dev/null
+++ b/contrib/cvs/lib/stripslash.c
@@ -0,0 +1,44 @@
+/* stripslash.c -- remove trailing slashes from a string
+ Copyright (C) 1990 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if STDC_HEADERS || HAVE_STRING_H
+#include <string.h>
+/* An ANSI string.h and pre-ANSI memory.h might conflict. */
+#if !STDC_HEADERS && HAVE_MEMORY_H
+#include <memory.h>
+#endif /* not STDC_HEADERS and HAVE_MEMORY_H */
+#else /* not STDC_HJEADERS and not HAVE_STRING_H */
+#include <strings.h>
+/* memory.h and strings.h conflict on some systems. */
+#endif /* not STDC_HEADERS and not HAVE_STRING_H */
+
+/* Remove trailing slashes from PATH. */
+
+void
+strip_trailing_slashes (path)
+ char *path;
+{
+ int last;
+
+ last = strlen (path) - 1;
+ while (last > 0 && path[last] == '/')
+ path[last--] = '\0';
+}
diff --git a/contrib/cvs/lib/strstr.c b/contrib/cvs/lib/strstr.c
new file mode 100644
index 0000000..e43bca0
--- /dev/null
+++ b/contrib/cvs/lib/strstr.c
@@ -0,0 +1,40 @@
+/******************************************************************************
+* *
+* s t r s t r *
+* *
+* Find the first occurrence of a string in another string. *
+* *
+* Format: *
+* return = strstr(Source,What); *
+* *
+* Parameters: *
+* *
+* Returns: *
+* *
+* Scope: PUBLIC *
+* *
+******************************************************************************/
+
+char *strstr(Source, What)
+register const char *Source;
+register const char *What;
+{
+register char WhatChar;
+register char SourceChar;
+register long Length;
+
+
+ if ((WhatChar = *What++) != 0) {
+ Length = strlen(What);
+ do {
+ do {
+ if ((SourceChar = *Source++) == 0) {
+ return (0);
+ }
+ } while (SourceChar != WhatChar);
+ } while (strncmp(Source, What, Length) != 0);
+ Source--;
+ }
+ return ((char *)Source);
+
+}/*strstr*/
diff --git a/contrib/cvs/lib/strtoul.c b/contrib/cvs/lib/strtoul.c
new file mode 100644
index 0000000..7d42c21
--- /dev/null
+++ b/contrib/cvs/lib/strtoul.c
@@ -0,0 +1,100 @@
+/*
+ * strtol : convert a string to long.
+ *
+ * Andy Wilson, 2-Oct-89.
+ */
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef ULONG_MAX
+#define ULONG_MAX ((unsigned long)(~0L)) /* 0xFFFFFFFF */
+#endif
+
+extern int errno;
+
+unsigned long
+strtoul(s, ptr, base)
+ const char *s; char **ptr; int base;
+{
+ unsigned long total = 0;
+ unsigned digit;
+ const char *start=s;
+ int did_conversion=0;
+ int overflow = 0;
+ int negate = 0;
+ unsigned long maxdiv, maxrem;
+
+ if (s==NULL)
+ {
+ errno = ERANGE;
+ if (!ptr)
+ *ptr = (char *)start;
+ return 0L;
+ }
+
+ while (isspace(*s))
+ s++;
+ if (*s == '+')
+ s++;
+ else if (*s == '-')
+ s++, negate = 1;
+ if (base==0 || base==16) /* the 'base==16' is for handling 0x */
+ {
+ int tmp;
+
+ /*
+ * try to infer base from the string
+ */
+ if (*s != '0')
+ tmp = 10; /* doesn't start with 0 - assume decimal */
+ else if (s[1] == 'X' || s[1] == 'x')
+ tmp = 16, s += 2; /* starts with 0x or 0X - hence hex */
+ else
+ tmp = 8; /* starts with 0 - hence octal */
+ if (base==0)
+ base = (int)tmp;
+ }
+
+ maxdiv = ULONG_MAX / base;
+ maxrem = ULONG_MAX % base;
+
+ while ((digit = *s) != '\0')
+ {
+ if (digit >= '0' && digit < ('0'+base))
+ digit -= '0';
+ else
+ if (base > 10)
+ {
+ if (digit >= 'a' && digit < ('a'+(base-10)))
+ digit = digit - 'a' + 10;
+ else if (digit >= 'A' && digit < ('A'+(base-10)))
+ digit = digit - 'A' + 10;
+ else
+ break;
+ }
+ else
+ break;
+ did_conversion = 1;
+ if (total > maxdiv
+ || (total == maxdiv && digit > maxrem))
+ overflow = 1;
+ total = (total * base) + digit;
+ s++;
+ }
+ if (overflow)
+ {
+ errno = ERANGE;
+ if (ptr != NULL)
+ *ptr = (char *)s;
+ return (ULONG_MAX);
+ }
+ if (ptr != NULL)
+ *ptr = (char *) ((did_conversion) ? (char *)s : (char *)start);
+ return negate ? -total : total;
+}
diff --git a/contrib/cvs/lib/system.h b/contrib/cvs/lib/system.h
new file mode 100644
index 0000000..363124d
--- /dev/null
+++ b/contrib/cvs/lib/system.h
@@ -0,0 +1,468 @@
+/* system-dependent definitions for CVS.
+ Copyright (C) 1989-1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* $CVSid: @(#)system.h 1.18 94/09/25 $ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef STAT_MACROS_BROKEN
+#undef S_ISBLK
+#undef S_ISCHR
+#undef S_ISDIR
+#undef S_ISREG
+#undef S_ISFIFO
+#undef S_ISLNK
+#undef S_ISSOCK
+#undef S_ISMPB
+#undef S_ISMPC
+#undef S_ISNWK
+#endif
+
+/* Not all systems have S_IFMT, but we probably want to use it if we
+ do. See ChangeLog for a more detailed discussion. */
+
+#if !defined(S_ISBLK) && defined(S_IFBLK)
+# if defined(S_IFMT)
+# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+# else
+# define S_ISBLK(m) ((m) & S_IFBLK)
+# endif
+#endif
+
+#if !defined(S_ISCHR) && defined(S_IFCHR)
+# if defined(S_IFMT)
+# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+# else
+# define S_ISCHR(m) ((m) & S_IFCHR)
+# endif
+#endif
+
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+# if defined(S_IFMT)
+# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+# else
+# define S_ISDIR(m) ((m) & S_IFDIR)
+# endif
+#endif
+
+#if !defined(S_ISREG) && defined(S_IFREG)
+# if defined(S_IFMT)
+# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+# else
+# define S_ISREG(m) ((m) & S_IFREG)
+# endif
+#endif
+
+#if !defined(S_ISFIFO) && defined(S_IFIFO)
+# if defined(S_IFMT)
+# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+# else
+# define S_ISFIFO(m) ((m) & S_IFIFO)
+# endif
+#endif
+
+#if !defined(S_ISLNK) && defined(S_IFLNK)
+# if defined(S_IFMT)
+# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+# else
+# define S_ISLNK(m) ((m) & S_IFLNK)
+# endif
+#endif
+
+#if !defined(S_ISSOCK) && defined(S_IFSOCK)
+# if defined(S_IFMT)
+# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+# else
+# define S_ISSOCK(m) ((m) & S_IFSOCK)
+# endif
+#endif
+
+#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */
+# if defined(S_IFMT)
+# define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
+# define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
+# else
+# define S_ISMPB(m) ((m) & S_IFMPB)
+# define S_ISMPC(m) ((m) & S_IFMPC)
+# endif
+#endif
+
+#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */
+# if defined(S_IFMT)
+# define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
+# else
+# define S_ISNWK(m) ((m) & S_IFNWK)
+# endif
+#endif
+
+#if !defined(HAVE_MKFIFO)
+#define mkfifo(path, mode) (mknod ((path), (mode) | S_IFIFO, 0))
+#endif
+
+#ifdef NEED_DECOY_PERMISSIONS /* OS/2, really */
+
+#define S_IRUSR S_IREAD
+#define S_IWUSR S_IWRITE
+#define S_IXUSR S_IEXEC
+#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)
+#define S_IRGRP S_IREAD
+#define S_IWGRP S_IWRITE
+#define S_IXGRP S_IEXEC
+#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
+#define S_IROTH S_IREAD
+#define S_IWOTH S_IWRITE
+#define S_IXOTH S_IEXEC
+#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH)
+
+#else /* ! NEED_DECOY_PERMISSIONS */
+
+#ifndef S_IRUSR
+#define S_IRUSR 0400
+#define S_IWUSR 0200
+#define S_IXUSR 0100
+/* Read, write, and execute by owner. */
+#define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR)
+
+#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */
+#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */
+#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */
+/* Read, write, and execute by group. */
+#define S_IRWXG (S_IRWXU >> 3)
+
+#define S_IROTH (S_IRGRP >> 3) /* Read by others. */
+#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */
+#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */
+/* Read, write, and execute by others. */
+#define S_IRWXO (S_IRWXG >> 3)
+#endif /* !def S_IRUSR */
+#endif /* NEED_DECOY_PERMISSIONS */
+
+#if defined(POSIX) || defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#include <limits.h>
+#else
+off_t lseek ();
+#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
+
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+
+#ifdef HAVE_DIRECT_H
+#include <direct.h>
+#endif
+
+#ifdef timezone
+#undef timezone /* needed for sgi */
+#endif
+
+#ifdef HAVE_SYS_TIMEB_H
+#include <sys/timeb.h>
+#else
+struct timeb {
+ time_t time; /* Seconds since the epoch */
+ unsigned short millitm; /* Field not used */
+ short timezone;
+ short dstflag; /* Field not used */
+};
+#endif
+
+#if !defined(HAVE_FTIME) && !defined(HAVE_TIMEZONE)
+#if !defined(timezone)
+extern long timezone;
+#endif
+#endif
+
+
+/*
+** MAXPATHLEN and PATH_MAX
+**
+** On most systems MAXPATHLEN is defined in sys/param.h to be 1024. Of
+** those that this is not true, again most define PATH_MAX in limits.h
+** or sys/limits.h which usually gets included by limits.h. On the few
+** remaining systems that neither statement is true, _POSIX_PATH_MAX
+** is defined.
+**
+** So:
+** 1. If PATH_MAX is defined just use it.
+** 2. If MAXPATHLEN is defined but not PATH_MAX, then define
+** PATH_MAX in terms of MAXPATHLEN.
+** 3. If neither is defined, include limits.h and check for
+** PATH_MAX again.
+** 3.1 If we now have PATHSIZE, define PATH_MAX in terms of that.
+** and ignore the rest. Since _POSIX_PATH_MAX (checked for
+** next) is the *most* restrictive (smallest) value, if we
+** trust _POSIX_PATH_MAX, several of our buffers are too small.
+** 4. If PATH_MAX is still not defined but _POSIX_PATH_MAX is,
+** then define PATH_MAX in terms of _POSIX_PATH_MAX.
+** 5. And if even _POSIX_PATH_MAX doesn't exist just put in
+** a reasonable value.
+** *. All in all, this is an excellent argument for using pathconf()
+** when at all possible. Or better yet, dynamically allocate
+** our buffers and use getcwd() not getwd().
+**
+** This works on:
+** Sun Sparc 10 SunOS 4.1.3 & Solaris 1.2
+** HP 9000/700 HP/UX 8.07 & HP/UX 9.01
+** Tektronix XD88/10 UTekV 3.2e
+** IBM RS6000 AIX 3.2
+** Dec Alpha OSF 1 ????
+** Intel 386 BSDI BSD/386
+** Intel 386 SCO OpenServer Release 5
+** Apollo Domain 10.4
+** NEC SVR4
+*/
+
+/* On MOST systems this will get you MAXPATHLEN.
+ Windows NT doesn't have this file, tho. */
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else
+# include <limits.h>
+# ifndef PATH_MAX
+# ifdef PATHSIZE
+# define PATH_MAX PATHSIZE
+# else /* no PATHSIZE */
+# ifdef _POSIX_PATH_MAX
+# define PATH_MAX _POSIX_PATH_MAX
+# else
+# define PATH_MAX 1024
+# endif /* no _POSIX_PATH_MAX */
+# endif /* no PATHSIZE */
+# endif /* no PATH_MAX */
+# endif /* MAXPATHLEN */
+#endif /* PATH_MAX */
+
+
+/* The NeXT (without _POSIX_SOURCE, which we don't want) has a utime.h
+ which doesn't define anything. It would be cleaner to have configure
+ check for struct utimbuf, but for now I'm checking NeXT here (so I don't
+ have to debug the configure check across all the machines). */
+#if defined (HAVE_UTIME_H) && !defined (NeXT)
+#include <utime.h>
+#elif defined (HAVE_SYS_UTIME_H)
+# include <sys/utime.h>
+#else
+#ifndef ALTOS
+struct utimbuf
+{
+ long actime;
+ long modtime;
+};
+#endif
+int utime ();
+#endif
+
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+ /* An ANSI string.h and pre-ANSI memory.h might conflict. */
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif /* not STDC_HEADERS and HAVE_MEMORY_H */
+#else /* not STDC_HEADERS and not HAVE_STRING_H */
+# include <strings.h>
+ /* memory.h and strings.h conflict on some systems. */
+#endif /* not STDC_HEADERS and not HAVE_STRING_H */
+
+#include <errno.h>
+
+/* Not all systems set the same error code on a non-existent-file
+ error. This tries to ask the question somewhat portably.
+ On systems that don't have ENOTEXIST, this should behave just like
+ x == ENOENT. "x" is probably errno, of course. */
+
+#ifdef ENOTEXIST
+# ifdef EOS2ERR
+# define existence_error(x) \
+ (((x) == ENOTEXIST) || ((x) == ENOENT) || ((x) == EOS2ERR))
+# else
+# define existence_error(x) \
+ (((x) == ENOTEXIST) || ((x) == ENOENT))
+# endif
+#else
+# ifdef EVMSERR
+# define existence_error(x) \
+((x) == ENOENT || (x) == EINVAL || (x) == EVMSERR)
+# else
+# define existence_error(x) ((x) == ENOENT)
+# endif
+#endif
+
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *getenv ();
+char *malloc ();
+char *realloc ();
+char *calloc ();
+extern int errno;
+#endif
+
+/* SunOS4 apparently does not define this in stdlib.h. */
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif
+
+#if defined(USG) || defined(POSIX)
+char *getcwd ();
+#else
+char *getwd ();
+#endif
+
+/* check for POSIX signals */
+#if defined(HAVE_SIGACTION) && defined(HAVE_SIGPROCMASK)
+# define POSIX_SIGNALS
+#endif
+
+/* MINIX 1.6 doesn't properly support sigaction */
+#if defined(_MINIX)
+# undef POSIX_SIGNALS
+#endif
+
+/* If !POSIX, try for BSD.. Reason: 4.4BSD implements these as wrappers */
+#if !defined(POSIX_SIGNALS)
+# if defined(HAVE_SIGVEC) && defined(HAVE_SIGSETMASK) && defined(HAVE_SIGBLOCK)
+# define BSD_SIGNALS
+# endif
+#endif
+
+/* Under OS/2, this must be included _after_ stdio.h; that's why we do
+ it here. */
+#ifdef USE_OWN_TCPIP_H
+#include "tcpip.h"
+#endif
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#include <sys/file.h>
+#endif
+
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+#endif
+
+#ifndef F_OK
+#define F_OK 0
+#define X_OK 1
+#define W_OK 2
+#define R_OK 4
+#endif
+
+#if HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# if HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# if HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# if HAVE_NDIR_H
+# include <ndir.h>
+# endif
+#endif
+
+/* Convert B 512-byte blocks to kilobytes if K is nonzero,
+ otherwise return it unchanged. */
+#define convert_blocks(b, k) ((k) ? ((b) + 1) / 2 : (b))
+
+#ifndef S_ISLNK
+#define lstat stat
+#endif
+
+/*
+ * Some UNIX distributions don't include these in their stat.h Defined here
+ * because "config.h" is always included last.
+ */
+#ifndef S_IWRITE
+#define S_IWRITE 0000200 /* write permission, owner */
+#endif
+#ifndef S_IWGRP
+#define S_IWGRP 0000020 /* write permission, grougroup */
+#endif
+#ifndef S_IWOTH
+#define S_IWOTH 0000002 /* write permission, other */
+#endif
+
+/* Under MS-DOS and its derivatives (like Windows NT), mkdir takes only one
+ argument; permission is handled very differently on those systems than in
+ in Unix. So we leave such systems a hook on which they can hang their
+ own definitions. */
+#ifndef CVS_MKDIR
+#define CVS_MKDIR mkdir
+#endif
+
+/* Some file systems are case-insensitive. If FOLD_FN_CHAR is
+ #defined, it maps the character C onto its "canonical" form. In a
+ case-insensitive system, it would map all alphanumeric characters
+ to lower case. Under Windows NT, / and \ are both path component
+ separators, so FOLD_FN_CHAR would map them both to /. */
+#ifndef FOLD_FN_CHAR
+#define FOLD_FN_CHAR(c) (c)
+#define fnfold(filename) (filename)
+#define fncmp strcmp
+#endif
+
+/* Different file systems have different path component separators.
+ For the VMS port we might need to abstract further back than this. */
+#ifndef ISDIRSEP
+#define ISDIRSEP(c) ((c) == '/')
+#endif
+
+
+/* On some systems, lines in text files should be terminated with CRLF,
+ not just LF, and the read and write routines do this translation
+ for you. LINES_CRLF_TERMINATED is #defined on such systems.
+ - OPEN_BINARY is the flag to pass to the open function for
+ untranslated I/O.
+ - FOPEN_BINARY_READ is the string to pass to fopen to get
+ untranslated reading.
+ - FOPEN_BINARY_WRITE is the string to pass to fopen to get
+ untranslated writing. */
+#if LINES_CRLF_TERMINATED
+#define OPEN_BINARY (O_BINARY)
+#define FOPEN_BINARY_READ ("rb")
+#define FOPEN_BINARY_WRITE ("wb")
+#else
+#define OPEN_BINARY (0)
+#define FOPEN_BINARY_READ ("r")
+#define FOPEN_BINARY_WRITE ("w")
+#endif
diff --git a/contrib/cvs/lib/valloc.c b/contrib/cvs/lib/valloc.c
new file mode 100644
index 0000000..674b60f
--- /dev/null
+++ b/contrib/cvs/lib/valloc.c
@@ -0,0 +1,25 @@
+/* valloc -- return memory aligned to the page size. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "system.h"
+
+#ifndef HAVE_GETPAGESIZE
+#define getpagesize() 4096
+#endif
+
+void *
+valloc (bytes)
+ size_t bytes;
+{
+ long pagesize;
+ char *ret;
+
+ pagesize = getpagesize ();
+ ret = (char *) malloc (bytes + pagesize - 1);
+ if (ret)
+ ret = (char *) ((long) (ret + pagesize - 1) &~ (pagesize - 1));
+ return ret;
+}
diff --git a/contrib/cvs/lib/vasprintf.c b/contrib/cvs/lib/vasprintf.c
new file mode 100644
index 0000000..45253b1
--- /dev/null
+++ b/contrib/cvs/lib/vasprintf.c
@@ -0,0 +1,171 @@
+/* Like vsprintf but provides a pointer to malloc'd storage, which must
+ be freed by the caller.
+ Copyright (C) 1994 Free Software Foundation, Inc.
+
+This file is part of the libiberty library.
+Libiberty is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+Libiberty is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with libiberty; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+extern int abs ();
+
+#ifdef TEST
+int global_total_width;
+#endif
+
+unsigned long strtoul ();
+char *malloc ();
+
+static int
+int_vasprintf (result, format, args)
+ char **result;
+ const char *format;
+ va_list *args;
+{
+ const char *p = format;
+ /* Add one to make sure that it is never zero, which might cause malloc
+ to return NULL. */
+ int total_width = strlen (format) + 1;
+ va_list ap;
+
+ memcpy (&ap, args, sizeof (va_list));
+
+ while (*p != '\0')
+ {
+ if (*p++ == '%')
+ {
+ while (strchr ("-+ #0", *p))
+ ++p;
+ if (*p == '*')
+ {
+ ++p;
+ total_width += abs (va_arg (ap, int));
+ }
+ else
+ total_width += strtoul (p, &p, 10);
+ if (*p == '.')
+ {
+ ++p;
+ if (*p == '*')
+ {
+ ++p;
+ total_width += abs (va_arg (ap, int));
+ }
+ else
+ total_width += strtoul (p, &p, 10);
+ }
+ while (strchr ("hlL", *p))
+ ++p;
+ /* Should be big enough for any format specifier except %s. */
+ total_width += 30;
+ switch (*p)
+ {
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ case 'c':
+ (void) va_arg (ap, int);
+ break;
+ case 'f':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ (void) va_arg (ap, double);
+ break;
+ case 's':
+ total_width += strlen (va_arg (ap, char *));
+ break;
+ case 'p':
+ case 'n':
+ (void) va_arg (ap, char *);
+ break;
+ }
+ }
+ }
+#ifdef TEST
+ global_total_width = total_width;
+#endif
+ *result = malloc (total_width);
+ if (*result != NULL)
+ return vsprintf (*result, format, *args);
+ else
+ return 0;
+}
+
+int
+vasprintf (result, format, args)
+ char **result;
+ const char *format;
+ va_list args;
+{
+ return int_vasprintf (result, format, &args);
+}
+
+#ifdef TEST
+void
+checkit
+#ifdef __STDC__
+ (const char* format, ...)
+#else
+ (va_alist)
+ va_dcl
+#endif
+{
+ va_list args;
+ char *result;
+
+#ifdef __STDC__
+ va_start (args, format);
+#else
+ char *format;
+ va_start (args);
+ format = va_arg (args, char *);
+#endif
+ vasprintf (&result, format, args);
+ if (strlen (result) < global_total_width)
+ printf ("PASS: ");
+ else
+ printf ("FAIL: ");
+ printf ("%d %s\n", global_total_width, result);
+}
+
+int
+main ()
+{
+ checkit ("%d", 0x12345678);
+ checkit ("%200d", 5);
+ checkit ("%.300d", 6);
+ checkit ("%100.150d", 7);
+ checkit ("%s", "jjjjjjjjjiiiiiiiiiiiiiiioooooooooooooooooppppppppppppaa\n\
+777777777777777777333333333333366666666666622222222222777777777777733333");
+ checkit ("%f%s%d%s", 1.0, "foo", 77, "asdjffffffffffffffiiiiiiiiiiixxxxx");
+}
+#endif /* TEST */
diff --git a/contrib/cvs/lib/wait.h b/contrib/cvs/lib/wait.h
new file mode 100644
index 0000000..db60434
--- /dev/null
+++ b/contrib/cvs/lib/wait.h
@@ -0,0 +1,32 @@
+/* wait.h -- POSIX macros for evaluating exit statuses
+ Copyright (C) 1990 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/types.h> /* For pid_t. */
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h> /* for rusage */
+#endif
+#include <sys/wait.h>
+#else
+#define WIFSTOPPED(w) (((w) & 0xff) == 0x7f)
+#define WIFSIGNALED(w) (((w) & 0xff) != 0x7f && ((w) & 0xff) != 0)
+#define WIFEXITED(w) (((w) & 0xff) == 0)
+
+#define WSTOPSIG(w) (((w) >> 8) & 0xff)
+#define WTERMSIG(w) ((w) & 0x7f)
+#define WEXITSTATUS(w) (((w) >> 8) & 0xff)
+#endif
diff --git a/contrib/cvs/lib/waitpid.c b/contrib/cvs/lib/waitpid.c
new file mode 100644
index 0000000..e8ddeb8
--- /dev/null
+++ b/contrib/cvs/lib/waitpid.c
@@ -0,0 +1,76 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "system.h"
+#include "wait.h"
+
+#include <stdio.h>
+
+struct unreaped {
+ pid_t pid;
+ int status;
+};
+static struct unreaped *unreaped;
+static int n;
+
+static struct unreaped *ualloc (oldptr, n)
+ struct unreaped *oldptr;
+ int n;
+{
+ n *= sizeof (struct unreaped);
+ if (n == 0)
+ n = 1;
+ if (oldptr)
+ oldptr = (struct unreaped *) realloc ((char *) oldptr, n);
+ else
+ oldptr = (struct unreaped *) malloc (n);
+ if (oldptr == 0)
+ {
+ fprintf (stderr, "cannot allocate %d bytes\n", n);
+ exit (1);
+ }
+ return oldptr;
+}
+
+pid_t waitpid (pid, status, options)
+ pid_t pid;
+ int *status;
+ int options;
+{
+ int i;
+
+ /* initialize */
+ if (unreaped == 0)
+ {
+ unreaped = ualloc (unreaped, 1);
+ unreaped[0].pid = 0;
+ n = 1;
+ }
+
+ for (i = 0; unreaped[i].pid; i++)
+ if (unreaped[i].pid == pid)
+ {
+ *status = unreaped[i].status;
+ while (unreaped[i].pid)
+ {
+ unreaped[i] = unreaped[i+1];
+ i++;
+ }
+ n--;
+ return pid;
+ }
+
+ while (1)
+ {
+ pid_t p = wait3 (status, options, (struct rusage *) 0);
+
+ if (p == 0 || p == -1 || p == pid)
+ return p;
+
+ n++;
+ unreaped = ualloc (unreaped, n);
+ unreaped[n-1].pid = p;
+ unreaped[n-1].status = *status;
+ }
+}
diff --git a/contrib/cvs/lib/xgetwd.c b/contrib/cvs/lib/xgetwd.c
new file mode 100644
index 0000000..8fe4ec1
--- /dev/null
+++ b/contrib/cvs/lib/xgetwd.c
@@ -0,0 +1,79 @@
+/* xgetwd.c -- return current directory with unlimited length
+ Copyright (C) 1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Derived from xgetcwd.c in e.g. the GNU sh-utils. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "system.h"
+
+#include <stdio.h>
+#include <errno.h>
+#ifndef errno
+extern int errno;
+#endif
+#include <sys/types.h>
+
+#ifndef HAVE_GETWD
+char *getwd ();
+#define GETWD(buf, max) getwd (buf)
+#else
+char *getcwd ();
+#define GETWD(buf, max) getcwd (buf, max)
+#endif
+
+/* Amount by which to increase buffer size when allocating more space. */
+#define PATH_INCR 32
+
+char *xmalloc ();
+char *xrealloc ();
+
+/* Return the current directory, newly allocated, arbitrarily long.
+ Return NULL and set errno on error. */
+
+char *
+xgetwd ()
+{
+ char *cwd;
+ char *ret;
+ unsigned path_max;
+
+ errno = 0;
+ path_max = (unsigned) PATH_MAX;
+ path_max += 2; /* The getcwd docs say to do this. */
+
+ cwd = xmalloc (path_max);
+
+ errno = 0;
+ while ((ret = GETWD (cwd, path_max)) == NULL && errno == ERANGE)
+ {
+ path_max += PATH_INCR;
+ cwd = xrealloc (cwd, path_max);
+ errno = 0;
+ }
+
+ if (ret == NULL)
+ {
+ int save_errno = errno;
+ free (cwd);
+ errno = save_errno;
+ return NULL;
+ }
+ return cwd;
+}
diff --git a/contrib/cvs/lib/yesno.c b/contrib/cvs/lib/yesno.c
new file mode 100644
index 0000000..86b0798
--- /dev/null
+++ b/contrib/cvs/lib/yesno.c
@@ -0,0 +1,42 @@
+/* yesno.c -- read a yes/no response from stdin
+ Copyright (C) 1990 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+
+/* Read one line from standard input
+ and return nonzero if that line begins with y or Y,
+ otherwise return 0. */
+
+int
+yesno ()
+{
+ int c;
+ int rv;
+
+ fflush (stderr);
+ fflush (stdout);
+ c = getchar ();
+ rv = (c == 'y') || (c == 'Y');
+ while (c != EOF && c != '\n')
+ c = getchar ();
+
+ return rv;
+}
diff --git a/contrib/cvs/man/ChangeLog b/contrib/cvs/man/ChangeLog
new file mode 100644
index 0000000..69017c9
--- /dev/null
+++ b/contrib/cvs/man/ChangeLog
@@ -0,0 +1,141 @@
+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/contrib/cvs/man/Makefile.in b/contrib/cvs/man/Makefile.in
new file mode 100644
index 0000000..894e4d78
--- /dev/null
+++ b/contrib/cvs/man/Makefile.in
@@ -0,0 +1,96 @@
+# 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.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+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)
+prefix = @prefix@
+mandir = $(prefix)/man
+man1dir = $(mandir)/man1
+man5dir = $(mandir)/man5
+man8dir = $(mandir)/man8
+
+all:
+.PHONY: all
+
+# This used to depend on installdirs, but someone took it out (I think
+# maybe it is/was some kind of Cygnus convention to not depend on installdirs;
+# I'm not sure). I don't know what the pro(s) and con(s) are.
+install: all
+ 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/contrib/cvs/man/cvs.1 b/contrib/cvs/man/cvs.1
new file mode 100644
index 0000000..7cef6e5
--- /dev/null
+++ b/contrib/cvs/man/cvs.1
@@ -0,0 +1,2181 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: cvs.1,v 1.9 1996/03/13 22:59:06 kingdon Exp $
+.TH CVS 1 "\*(Dt"
+.\" Full space in nroff; half space in troff
+.de SP
+.if n .sp
+.if t .sp .5
+..
+.\" quoted command
+.de `
+.RB ` "\|\\$1\|" '\\$2
+..
+.SH "NAME"
+cvs \- Concurrent Versions System
+.SH "SYNOPSIS"
+.TP
+\fBcvs\fP [ \fIcvs_options\fP ]
+.I cvs_command
+[
+.I command_options
+] [
+.I command_args
+]
+.SH "DESCRIPTION"
+.IX "revision control system" "\fLcvs\fR"
+.IX cvs "" "\fLcvs\fP \- concurrent versions system"
+.IX "concurrent versions system \- \fLcvs\fP"
+.IX "release control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system"
+.IX "source control system" "cvs command" "" "\fLcvs\fP \- concurrent versions system"
+.IX revisions "cvs command" "" "\fLcvs\fP \- source control"
+.B cvs
+is a front end to the
+.BR rcs ( 1 )
+revision control system which extends
+the notion of revision control from a collection of files in a single
+directory to a hierarchical collection of directories consisting of
+revision controlled files.
+These directories and files can be combined together to form a software
+release.
+.B cvs
+provides the functions necessary to manage these software releases and to
+control the concurrent editing of source files among multiple software
+developers.
+.SP
+.B cvs
+keeps a single copy of the master sources.
+This copy is called the source ``repository''; it contains all the
+information to permit extracting previous software releases at any
+time based on either a symbolic revision tag, or a date in the past.
+.SH "ESSENTIAL COMMANDS"
+.B cvs
+provides a rich variety of commands (\fIcvs_command\fP in the
+Synopsis), each of which often has a wealth of options, to satisfy the
+many needs of source management in distributed environments. However,
+you don't have to master every detail to do useful work with
+.BR cvs ;
+in fact, five commands are sufficient to use (and contribute to)
+the source repository.
+.TP
+\fBcvs checkout\fP \fImodules\fP\|.\|.\|.
+A necessary preliminary for most \fBcvs\fP work: creates your private
+copy of the source for \fImodules\fP (named collections of source; you
+can also use a path relative to the source repository here). You can
+work with this copy without interfering with others' work. At least
+one subdirectory level is always created.
+.TP
+.B cvs update
+Execute this command from \fIwithin\fP your private source
+directory when you wish to update your copies of source files from
+changes that other developers have made to the source in the
+repository.
+.TP
+\fBcvs add\fP \fIfile\fP\|.\|.\|.
+Use this command to enroll new files in \fBcvs\fP records of your
+working directory. The files will be added to the repository the next
+time you run
+.` "cvs commit".
+Note:
+You should use the
+.` "cvs import"
+command to bootstrap new sources into the source repository.
+.` "cvs add"
+is only used for new files to an already checked-out module.
+.TP
+\fBcvs remove\fP \fIfile\fP\|.\|.\|.
+Use this command (after erasing any files listed) to declare that you
+wish to eliminate files from the repository. The removal does not
+affect others until you run
+.` "cvs commit".
+.TP
+\fBcvs commit\fP \fIfile\fP\|.\|.\|.
+Use this command when you wish to ``publish'' your changes to other
+developers, by incorporating them in the source repository.
+.SH "OPTIONS"
+The
+.B cvs
+command line can include
+.IR cvs_options ,
+which apply to the overall
+.B cvs
+program; a
+.IR cvs_command ,
+which specifies a particular action on the source repository; and
+.I command_options
+and
+.I command_arguments
+to fully specify what the
+.I cvs_command
+will do.
+.SP
+.I Warning:
+you must be careful of precisely where you place options relative to the
+.IR cvs_command .
+The same option can mean different things depending on whether it
+is in the
+.I cvs_options
+position (to the left of a
+.B cvs
+command) or in the
+.I command_options
+position (to the right of a
+.B cvs
+command).
+.SP
+There are only two situations where you may omit
+.IR cvs_command :
+.` "cvs \-H"
+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.
+Overrides the setting of the
+.SM RCSBIN
+environment variable.
+This value should be specified as an absolute pathname.
+.TP
+\fB\-d\fP \fICVS_root_directory\fP
+Use
+.I CVS_root_directory
+as the root directory pathname of the master
+.SM RCS
+source repository.
+Overrides the setting of the
+.SM CVSROOT
+environment variable.
+This value should be specified as an absolute pathname.
+.TP
+\fB\-e\fP \fIeditor\fP
+Use
+.I editor
+to enter revision log information.
+Overrides the setting of the
+.SM 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
+\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
+.SM RCS
+control functions on the source repository. (Changes
+repository directly; uses working directory without changing it.)
+.TP
+.B checkout
+Make a working directory of source files for editing. (Creates or changes
+working directory.)
+.TP
+.B commit
+Apply to the source repository changes, additions, and deletions from your
+working directory. (Changes repository.)
+.TP
+.B diff
+Show differences between files in working directory and source
+repository, or between two revisions in source repository.
+(Does not change either repository or working directory.)
+.TP
+.B export
+Prepare copies of a set of source files for shipment off site.
+Differs from
+.` "cvs checkout"
+in that no
+.B cvs
+administrative directories are created (and therefore
+.` "cvs commit"
+cannot be executed from a directory prepared with
+.` "cvs export"),
+and a symbolic tag must be specified.
+(Does not change repository; creates directory similar to working
+directories).
+.TP
+.B history
+Show reports on
+.B cvs
+commands that you or others have executed on a particular file or
+directory in the source repository. (Does not change repository or
+working directory.) History logs are kept only if enabled by creation
+of the
+.` "$CVSROOT/CVSROOT/history"
+file; see
+.BR cvs ( 5 ).
+.TP
+.B import
+Incorporate a set of updates from off-site into the source repository,
+as a ``vendor branch''. (Changes repository.)
+.TP
+.B log
+Display
+.SM RCS
+log information.
+(Does not change repository or working directory.)
+.TP
+.B rdiff
+Prepare a collection of diffs as a patch file between two releases in
+the repository. (Does not change repository or working directory.)
+.TP
+.B release
+Cancel a
+.` "cvs checkout",
+abandoning any changes.
+(Can delete working directory; no effect on repository.)
+.TP
+.B remove
+Remove files from the source repository, pending a
+.` "cvs commit"
+on the same files. (Does not directly affect repository;
+changes working directory.)
+.TP
+.B rtag
+Explicitly specify a symbolic tag for particular revisions of files in the
+source repository. See also
+.` "cvs tag".
+(Changes repository directly; does not require or affect
+working directory.)
+.TP
+.B status
+Show current status of files: latest version, version in working
+directory, whether working version has been edited and, optionally,
+symbolic tags in the
+.SM RCS
+file. (Does not change
+repository or working directory.)
+.TP
+.B tag
+Specify a symbolic tag for files in the repository. 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 by the underlying
+.SM RCS
+facilities, similar to those described in
+.BR co ( 1 ),
+but not exactly the same.
+The \fIdate_spec\fP is interpreted as being in the local timezone, unless a
+specific timezone is specified.
+The specification is ``sticky'' when you use it to make a
+private copy of a source file; that is, when you get a working file
+using \fB\-D\fP, \fBcvs\fP records the date you
+specified, so that further updates in the same directory will use the
+same date (unless you explicitly override it; see the description of
+the \fBupdate\fP command).
+.B \-D
+is available with the
+.BR checkout ", " diff ", " history ", " export ", "
+.BR rdiff ", " rtag ", and "
+.B update
+commands.
+Examples of valid date specifications include:
+.in +1i
+.ft B
+.nf
+1 month ago
+2 hours ago
+400000 seconds ago
+last year
+last Monday
+yesterday
+a fortnight ago
+3/31/92 10:00:07 PST
+January 23, 1987 10:05pm
+22:00 GMT
+.fi
+.ft P
+.in -1i
+.TP
+.B \-f
+When you specify a particular date or tag to \fBcvs\fP commands, they
+normally ignore files that do not contain the tag (or did not exist on
+the date) that you specified. Use the \fB\-f\fP option if you want
+files retrieved even when there is no match for the tag or date. (The
+most recent version is used in this situation.)
+.B \-f
+is available with these commands:
+.BR checkout ", " export ", "
+.BR rdiff ", " rtag ", and " update .
+.TP
+.B \-H
+Help; describe the options available for this command. This is the
+only option supported for
+.I all
+.B cvs
+commands.
+.TP
+\fB\-k\fP \fIkflag\fP
+Alter the default
+.SM RCS
+processing of keywords; all the
+.B \-k
+options described in
+.BR co ( 1 )
+are available. 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,
+only compatible with
+.SM RCS
+version 5.7 or later), 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, in
+.SM RCS
+fashion.
+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
+.SM RCS
+source repository.
+The files or directories specified with
+.B add
+must already exist in the current directory (which must have been created
+with the
+.B checkout
+command).
+To add a whole new directory hierarchy to the source repository
+(for example, files received from a third-party vendor), use the
+.` "cvs import"
+command instead.
+.SP
+If the argument to
+.` "cvs add"
+refers to an immediate sub-directory, the directory is
+created at the correct place in the
+.SM RCS
+source repository, and the necessary
+.B cvs
+administration files are created in your working directory.
+If the directory already exists in the source repository,
+.` "cvs add"
+still creates the administration files in your version of the directory.
+This allows you to use
+.` "cvs add"
+to add a particular directory to your private sources even if
+someone else created that directory after your
+.B checkout
+of the sources. You can do the following:
+.SP
+.in +1i
+.ft B
+.nf
+example% mkdir new_directory
+example% cvs add new_directory
+example% cvs update new_directory
+.fi
+.ft P
+.in -1i
+.SP
+An alternate approach using
+.` "cvs update"
+might be:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs update -d new_directory
+.fi
+.ft P
+.in -1i
+.SP
+(To add \fIany available\fP new directories to your working directory, it's
+probably simpler to use
+.` "cvs checkout"
+or
+.` "cvs update -d".)
+.SP
+The added files are not placed in the
+.SM RCS
+source repository until you use
+.` "cvs commit"
+to make the change permanent.
+Doing a
+.` "cvs add"
+on a file that was removed with the
+.` "cvs remove"
+command will resurrect the file, if no
+.` "cvs commit"
+command intervened.
+.SP
+You will have the opportunity to specify a logging message, as usual,
+when you use
+.` "cvs commit"
+to make the new file permanent. If you'd like to have another
+logging message associated with just
+.I creation
+of the file (for example, to describe the file's purpose), you can
+specify it with the
+.` "\-m \fImessage\fP"
+option to the
+.B add
+command.
+.SP
+The
+.` "-k kflag"
+option specifies the default way that this
+file will be checked out.
+The
+.` "kflag"
+argument is stored in the
+.SM RCS
+file and can be changed with
+.` "cvs admin".
+Specifying
+.` "-ko"
+is useful for checking in binaries that
+shouldn't have the
+.SM RCS
+id strings expanded.
+.TP
+\fBadmin\fP [\fIrcs-options\fP] \fIfiles.\|.\|.\fP
+.I Requires:
+repository, working directory.
+.br
+.I Changes:
+repository.
+.br
+.I Synonym:
+.B rcs
+.br
+This is the
+.B cvs
+interface to assorted administrative
+.SM RCS
+facilities, documented in
+.BR rcs ( 1 ).
+.` "cvs admin"
+simply passes all its options and arguments to the
+.B rcs
+command; it does no filtering or other processing.
+This command does work recursively, however, so extreme care should be
+used.
+.TP
+\fBcheckout\fP [\fBoptions\fP] \fImodules\fP.\|.\|.
+.I Requires:
+repository.
+.br
+.I Changes:
+working directory.
+.br
+.I Synonyms:
+.BR co ", " get
+.br
+Make a working directory containing copies of the source files specified by
+.IR modules .
+You must execute
+.` "cvs checkout"
+before using most of the other
+.B cvs
+commands, since most of them operate on your working directory.
+.SP
+\fImodules\fP are either symbolic names (themselves defined as the
+module
+.` "modules"
+in the source repository; see
+.BR cvs ( 5 ))
+for some collection of source directories and files, or paths to
+directories or files in the repository.
+.SP
+Depending on the
+.I modules
+you specify,
+.B checkout
+may recursively create directories and populate them with the appropriate
+source files.
+You can then edit these source files at any time (regardless of whether
+other software developers are editing their own copies of the sources);
+update them to include new changes applied by others to the source
+repository; or commit your work as a permanent change to the
+.SM RCS
+repository.
+.SP
+Note that
+.B checkout
+is used to create directories.
+The top-level directory created is always added to the directory
+where
+.B checkout
+is invoked, and usually has the same name as the specified
+.IR module .
+In the case of a
+.I module
+alias, the created sub-directory may have a different name, but you can be
+sure that it will be a sub-directory, and that
+.B checkout
+will show the relative path leading to each file as it is extracted into
+your private work area (unless you specify the
+.B \-Q
+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
+.SM RCS
+source repository file.
+You can instead specify the log message on the command line with the
+.B \-m
+option, thus suppressing the editor invocation, or use the
+.B \-F
+option to specify that the argument \fIfile\fP contains the log message.
+.SP
+The
+.B \-r
+option can be used to commit to a particular symbolic or numeric revision
+within the
+.SM RCS
+file.
+For example, to bring all your files up to the
+.SM RCS
+revision ``3.0'' (including those that haven't changed), you might do:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs commit -r3.0
+.fi
+.ft P
+.in -1i
+.SP
+.B cvs
+will only allow you to commit to a revision that is on the main trunk (a
+revision with a single dot).
+However, you can also commit to a branch revision (one that has an even
+number of dots) with the
+.B \-r
+option.
+To create a branch revision, one typically use the
+.B \-b
+option of the
+.BR rtag " or " tag
+commands.
+Then, either
+.BR checkout " or " update
+can be used to base your sources on the newly created branch.
+From that point on, all
+.B commit
+changes made within these working sources will be automatically added
+to a branch revision, thereby not perturbing main-line development in any
+way.
+For example, if you had to create a patch to the 1.2 version of the
+product, even though the 2.0 version is already under development, you
+might do:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs rtag -b -rFCS1_2 FCS1_2_Patch product_module
+example% cvs checkout -rFCS1_2_Patch product_module
+example% cd product_module
+[[ hack away ]]
+example% cvs commit
+.fi
+.ft P
+.in -1i
+.SP
+Say you have been working on some extremely experimental software, based on
+whatever revision you happened to checkout last week.
+If others in your group would like to work on this software with you, but
+without disturbing main-line development, you could commit your change to a
+new branch.
+Others can then checkout your experimental stuff and utilize the full
+benefit of
+.B cvs
+conflict resolution.
+The scenario might look like:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs tag -b EXPR1
+example% cvs update -rEXPR1
+[[ hack away ]]
+example% cvs commit
+.fi
+.ft P
+.in -1i
+.SP
+Others would simply do
+.` "cvs checkout -rEXPR1 whatever_module"
+to work with you on the experimental change.
+.TP
+\fBdiff\fP [\fB\-kl\fP] [\fIrcsdiff_options\fP] [[\fB\-r\fP \fIrev1\fP | \fB\-D\fP \fIdate1\fP] [\fB\-r\fP \fIrev2\fP | \fB\-D\fP \fIdate2\fP]] [\fIfiles.\|.\|.\fP]
+.I Requires:
+working directory, repository.
+.br
+.I Changes:
+nothing.
+.br
+You can compare your working files with revisions in the source
+repository, with the
+.` "cvs diff"
+command. If you don't specify a particular revision, your files
+are compared with the revisions they were based on. You can also use
+the standard
+.B cvs
+command option
+.B \-r
+to specify a particular revision to compare your files with. Finally,
+if you use
+.B \-r
+twice, you can see differences between two revisions in the
+repository.
+You can also specify
+.B \-D
+options to diff against a revision in the past.
+The
+.B \-r
+and
+.B \-D
+options can be mixed together with at most two options ever specified.
+.SP
+See
+.BR rcsdiff ( 1 )
+for a list of other accepted options.
+.SP
+If you don't specify any files,
+.B diff
+will display differences for all those files in the current directory
+(and its subdirectories, unless you use the standard option
+.BR \-l )
+that
+differ from the corresponding revision in the source repository
+(i.e. files that
+.I you
+have changed), or that differ from the revision specified.
+.TP
+\fBexport\fP [\-\fBf\|lNnQq\fP] \fB\-r\fP \fIrev\fP\||\|\fB\-D\fP \fIdate\fP [\fB\-d\fP \fIdir\fP] [\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
+.SM RCS
+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 the history file.
+This differs from the \fB-r\fP flag above in that it reads
+only the history file, not the
+.SM RCS
+files, and is much faster.
+.TP 1i
+\fB\ \ \ \ \ \ \-u\fP \fIname\fP
+Show records for user \fIname\fP.
+.PP
+.TP
+\fBimport\fP [\fB\-\fP\fIoptions\fP] \fIrepository vendortag releasetag\fP.\|.\|.
+.I Requires:
+Repository, source distribution directory.
+.br
+.I Changes:
+repository.
+.br
+Use
+.` "cvs import"
+to incorporate an entire source
+distribution from an outside source (e.g., a source vendor) into your
+source repository directory. You can use this command both for
+initial creation of a repository, and for wholesale updates to the
+module form the outside source.
+.SP
+The \fIrepository\fP argument gives a directory name (or a path to a
+directory) under the CVS root directory for repositories; if the
+directory did not exist, \fBimport\fP creates it.
+.SP
+When you use \fBimport\fP for updates to source that has been modified in your
+source repository (since a prior \fBimport\fP), it
+will notify you of any files that conflict in the two branches of
+development; use
+.` "cvs checkout -j"
+to reconcile the differences, as \fBimport\fP instructs you to do.
+.SP
+By default, certain file names are ignored during
+.` "cvs import":
+names associated with
+.SM CVS
+administration, or with other common source control systems; common
+names for patch files, object files, archive files, and editor backup
+files; and other names that are usually artifacts of assorted utilities.
+Currently, the default list of ignored files includes files matching
+these names:
+.SP
+.in +1i
+.ft B
+.nf
+RCSLOG RCS SCCS
+CVS* cvslog.*
+tags TAGS
+\&.make.state .nse_depinfo
+*~ #* .#* ,*
+*.old *.bak *.BAK *.orig *.rej .del\-*
+*.a *.o *.so *.Z *.elc *.ln core
+.fi
+.ft P
+.in -1i
+.SP
+The outside source is saved in a first-level
+.SM RCS
+branch, by default
+.` "1.1.1".
+Updates are leaves of this
+branch; for example, files from the first imported collection of
+source will be revision
+.` "1.1.1.1",
+then files from the first
+imported update will be revision
+.` "1.1.1.2",
+and so on.
+.SP
+At least three arguments are required. \fIrepository\fP is needed to
+identify the collection of source. \fIvendortag\fP is a tag for the
+entire branch (e.g., for
+.` "1.1.1").
+You must also specify at
+least one \fIreleasetag\fP to identify the files at the leaves created
+each time you execute
+.` "cvs import".
+.SP
+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.
+.` "cvs log"
+calls
+the
+.SM RCS
+utility \fBrlog\fP; all the options described in
+.BR rlog ( 1 )
+are available. Among the more useful \fBrlog\fP options are \fB\-h\fP
+to display only the header (including tag definitions, but omitting
+most of the full log); \fB\-r\fP to select logs on particular
+revisions or ranges of revisions; and \fB\-d\fP to select particular
+dates or date ranges. See
+.BR rlog ( 1 )
+for full explanations.
+This command is recursive by default, unless the
+.B \-l
+option is specified.
+.TP
+\fBrdiff\fP [\fB\-\fP\fIflags\fP] [\fB\-V\fP \fIvn\fP] [\fB\-r\fP \fIt\fP|\fB\-D\fP \fId\fP [\fB\-r\fP \fIt2\fP|\fB\-D\fP \fId2\fP]] \fImodules\|.\|.\|.\fP
+.I Requires:
+repository.
+.br
+.I Changes:
+nothing.
+.br
+.I Synonym:
+.B patch
+.br
+Builds a Larry Wall format
+.BR patch ( 1 )
+file between two releases, that can be fed directly into the
+.B patch
+program to bring an old release up-to-date with the new release.
+(This is one of the few \fBcvs\fP commands that operates directly from
+the repository, and doesn't require a prior
+.BR checkout .)
+The diff output is sent to the standard output device.
+You can specify (using the standard \fB\-r\fP and \fB\-D\fP options)
+any combination of one or two revisions or dates.
+If only one revision or date is specified, the
+patch file reflects differences between that revision or date and the
+current ``head'' revisions in the
+.SM RCS
+file.
+.SP
+Note that if the software release affected
+is contained in more than one directory, then it may be necessary to
+specify the
+.B \-p
+option to the
+.B patch
+command when patching the old sources, so that
+.B patch
+is able to find the files that are located in other directories.
+.SP
+If you use the option \fB\-V\fP \fIvn\fP,
+.SM RCS
+keywords are expanded according to the rules current in
+.SM RCS
+version \fIvn\fP (the expansion format changed with
+.SM RCS
+version 5).
+.SP
+The standard option \fIflags\fP \fB\-f\fP, 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\-Adf\|lPpQqR\fP] [\fB\-d\fP] [\fB\-r\fP \fItag\fP|\fB\-D\fP \fIdate\fP] \fIfiles\|.\|.\|.\fP
+.I Requires:
+repository, working directory.
+.br
+.I Changes:
+working directory.
+.br
+After you've run
+.B checkout
+to create your private copy of source from the common repository,
+other developers will continue changing the central source. From time
+to time, when it is convenient in your development process, you can
+use the
+.B update
+command
+from within your working directory to reconcile your work with any
+revisions applied to the source repository since your last
+.B checkout
+or
+.BR update .
+.SP
+.B update
+keeps you informed of its progress by printing a line for each file,
+prefaced with one of the characters
+.` "U A R M C ?"
+to indicate the status of the file:
+.TP 1i
+\fBU\fP \fIfile\fP
+The file was brought \fIup to date\fP with respect to the repository.
+This is done for any file that exists in the repository but not in
+your source, and for files that you haven't changed but are not the most
+recent versions available in the repository.
+.TP 1i
+\fBA\fP \fIfile\fP
+The file has been \fIadded\fP to your private copy of the sources, and
+will be added to the
+.SM RCS
+source repository when you run
+.` "cvs commit"
+on the file.
+This is a reminder to you that the file needs to be committed.
+.TP 1i
+\fBR\fP \fIfile\fP
+The file has been \fIremoved\fP from your private copy of the sources, and
+will be removed from the
+.SM RCS
+source repository when you run
+.` "cvs commit"
+on the file.
+This is a reminder to you that the file needs to be committed.
+.TP 1i
+\fBM\fP \fIfile\fP
+The file is \fImodified\fP in your working directory.
+.` "M"
+can indicate one of two states for a file you're working on: either
+there were no modifications to the same file in the repository, so
+that your file remains as you last saw it; or there were modifications
+in the repository as well as in your copy, but they were
+\fImerged\fP successfully, without conflict, in your working
+directory.
+.TP 1i
+\fBC\fP \fIfile\fP
+A \fIconflict\fP was detected while trying to merge your changes to
+\fIfile\fP with changes from the source repository. \fIfile\fP (the
+copy in your working directory) is now the output of the
+.BR rcsmerge ( 1 )
+command on the two versions; an unmodified copy of your file is also
+in your working directory, with the name `\fB.#\fP\fIfile\fP\fB.\fP\fIversion\fP',
+where
+.I version
+is the
+.SM RCS
+revision that your modified file started from.
+(Note that some systems automatically purge files that begin with
+\&
+.` ".#"
+if they have not been accessed for a few days.
+If you intend to keep a copy of your original file, it is a very good
+idea to rename it.)
+.TP 1i
+\fB?\fP \fIfile\fP
+\fIfile\fP is in your working directory, but does not correspond to
+anything in the source repository, and is not in the list of files
+for \fBcvs\fP to ignore (see the description of the \fB\-I\fP option).
+.PP
+.RS .5i
+.SP
+Use the
+.B \-A
+option to reset any sticky tags, dates, or
+.B \-k
+options. (If you get a working copy of a file by using one of the
+\fB\-r\fP, \fB\-D\fP, or \fB\-k\fP options, \fBcvs\fP remembers the
+corresponding tag, date, or \fIkflag\fP and continues using it on
+future updates; use the \fB\-A\fP option to make \fBcvs\fP forget these
+specifications, and retrieve the ``head'' version of the file).
+.SP
+The \fB\-j\fP\fIbranch\fP option
+merges the changes made between the
+resulting revision and the revision that it is based on (e.g., if
+the tag refers to a branch,
+.B cvs
+will merge all changes made in
+that branch into your working file).
+.SP
+With two \fB-j\fP options,
+.B cvs
+will merge in the changes between the two respective revisions.
+This can be used to ``remove'' a certain delta from your working file.
+E.g., If the file foo.c is based on
+revision 1.6 and I want to remove the changes made between 1.3 and
+1.5, I might do:
+.SP
+.in +1i
+.ft B
+.nf
+example% cvs update -j1.5 -j1.3 foo.c # note the order...
+.fi
+.ft P
+.in -1i
+.SP
+In addition, each \fB-j\fP option can contain on optional date
+specification which, when used with branches, can limit the chosen
+revision to one within a specific date.
+An optional date is specified by adding a colon (:) to the tag.
+.SP
+.in +1i
+.ft B
+.nf
+-jSymbolic_Tag:Date_Specifier
+.fi
+.ft P
+.in -1i
+.SP
+Use the
+.B \-d
+option to create any directories that exist in the repository if they're
+missing from the working directory. (Normally, update acts only on
+directories and files that were already enrolled in your
+working directory.) This is useful for updating directories
+that were created in the repository since the initial
+\fBcheckout\fP; but it has an unfortunate side effect. If you
+deliberately avoided certain directories in the repository when you
+created your working directory (either through use of a module name or by
+listing explicitly the files and directories you wanted on the
+command line), then updating with
+.B \-d
+will create those directories, which may not be what you want.
+.SP
+Use \fB\-I\fP \fIname\fP to ignore files whose names match \fIname\fP
+(in your working directory) during the update. You can specify
+\fB\-I\fP more than once on the command line to specify several files
+to ignore. By default,
+\fBupdate\fP ignores files whose names match any of the following:
+.SP
+.in +1i
+.ft B
+.nf
+RCSLOG RCS SCCS
+CVS* cvslog.*
+tags TAGS
+\&.make.state .nse_depinfo
+*~ #* .#* ,*
+*.old *.bak *.BAK *.orig *.rej .del\-*
+*.a *.o *.so *.Z *.elc *.ln core
+.fi
+.ft P
+.in -1i
+.SP
+Use
+.` "\-I !"
+to avoid ignoring any files at all.
+.SP
+The standard \fBcvs\fP command options \fB\-f\fP, \fB\-k\fP,
+\fB\-l\fP, \fB\-P\fP, \fB\-p\fP, 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
+.SM RCS
+source repository.
+.TP
+#cvs.tfl.\fIpid\fP
+Temporary lock file for repository.
+.TP
+#cvs.rfl.\fIpid\fP
+A read lock.
+.TP
+#cvs.wfl.\fIpid\fP
+A write lock.
+.SH "ENVIRONMENT VARIABLES"
+.TP
+.SM CVSROOT
+Should contain the full pathname to the root of the
+.B cvs
+source repository (where the
+.SM RCS
+files are kept). This information must be available to \fBcvs\fP for
+most commands to execute; if
+.SM CVSROOT
+is not set, or if you wish to override it for one invocation, you can
+supply it on the command line:
+.` "cvs \-d \fIcvsroot cvs_command\fP\|.\|.\|."
+You may not need to set
+.SM CVSROOT
+if your \fBcvs\fP binary has the right path compiled in; use
+.` "cvs \-v"
+to display all compiled-in paths.
+.TP
+.SM CVSREAD
+If this is set,
+.B checkout
+and
+.B update
+will try hard to make the files in your working directory read-only.
+When this is not set, the default behavior is to permit modification
+of your working files.
+.TP
+.SM RCSBIN
+Specifies the full pathname where to find
+.SM RCS
+programs, such as
+.BR co ( 1 )
+and
+.BR ci ( 1 ).
+If not set, a compiled-in value is used; see the display from
+.` "cvs \-v".
+.TP
+.SM 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").
+.SH "SEE ALSO"
+.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/contrib/cvs/man/cvs.5 b/contrib/cvs/man/cvs.5
new file mode 100644
index 0000000..fb2bcec
--- /dev/null
+++ b/contrib/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/contrib/cvs/man/cvsbug.8 b/contrib/cvs/man/cvsbug.8
new file mode 100644
index 0000000..496ef14
--- /dev/null
+++ b/contrib/cvs/man/cvsbug.8
@@ -0,0 +1,269 @@
+.\" -*- 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.
+.\"
+.\" You should have received a copy of the GNU Library General Public
+.\" License along with this program; if not, write to the Free
+.\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
+.\"
+.\" ---------------------------------------------------------------------------
+.nh
+.TH CVSBUG 1 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 EMACS USER INTERFACE
+An Emacs user interface for
+.B cvsbug
+with completion of field values is part of the
+.B cvsbug
+distribution (invoked with
+.BR "M-x cvsbug" ).
+See the file
+.B cvsbug.info
+or the ASCII file
+.B INSTALL
+in the top level directory of the distribution for configuration and
+installation information. The Emacs LISP template file is
+.B cvsbug-el.in
+and is installed as
+.BR cvsbug.el .
+.SH INSTALLATION AND CONFIGURATION
+See
+.B cvsbug.info
+or
+.B INSTALL
+for installation instructions.
+.SH SEE ALSO
+.I Reporting Problems Using cvsbug
+(also installed as the GNU Info file
+.BR cvsbug.info ).
+.LP
+.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/contrib/cvs/mkinstalldirs b/contrib/cvs/mkinstalldirs
new file mode 100755
index 0000000..91f6d04
--- /dev/null
+++ b/contrib/cvs/mkinstalldirs
@@ -0,0 +1,32 @@
+#!/bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+# Created: 1993-05-16
+# Last modified: 1994-03-25
+# Public domain
+
+errstatus=0
+
+for file in ${1+"$@"} ; do
+ set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+ shift
+
+ pathcomp=
+ for d in ${1+"$@"} ; do
+ pathcomp="$pathcomp$d"
+ case "$pathcomp" in
+ -* ) pathcomp=./$pathcomp ;;
+ esac
+
+ if test ! -d "$pathcomp"; then
+ echo "mkdir $pathcomp" 1>&2
+ mkdir "$pathcomp" || errstatus=$?
+ fi
+
+ pathcomp="$pathcomp/"
+ done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
diff --git a/contrib/cvs/src/ChangeLog b/contrib/cvs/src/ChangeLog
new file mode 100644
index 0000000..99161b0
--- /dev/null
+++ b/contrib/cvs/src/ChangeLog
@@ -0,0 +1,1373 @@
+Sun May 5 21:39:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c (Version_TS): If sdtp is NULL, go ahead and check
+ RCS_getexpand for options. Fixes binaries and non-unix clients.
+ * sanity.sh: Fix binfiles-5.5 to test for the correct behavior
+ rather than the buggy behavior which existed when the binfiles-5.5
+ test was written.
+ (binfiles-14c,binfiles-14f): Likewise.
+
+Sun May 5 17:38:21 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ Integrated changes submitted by Ian Taylor <ian@cygnus.com>
+
+ * update.c (update_dirent_proc): cvs co -p doesn't print
+ anything when run from an empty directory.
+
+ * import.c (import_descend_dir): Check for a file in the
+ repository which will be checked out to the same name as the
+ directory.
+
+Thu May 2 13:34:37 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * Version 1.7.88
+
+Thu May 2 01:40:55 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * server.c (HAVE_INITGROUPS): Use initgroups() only if
+ located by configure, in the event a system has crypt(), but
+ no initgroups()
+
+Wed May 1 18:05:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (basica): When testing rejection of reserved tag name,
+ use BASE instead of RESERVED.
+
+Wed May 1 15:15:11 1996 Tom Jarmolowski <tjj@booklink.com>
+
+ * rcs.c (linevector_delete): Only copy up to vec->nlines - nlines,
+ not to vec->nlines.
+
+Wed May 1 15:43:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcscmds.c (RCS_settag): Instead of reserving all tag names
+ containing only uppercase letters, reserve only BASE and HEAD.
+ * sanity.sh (mflag): Revert 26 Mar change; use all-uppercase tag
+ name again.
+
+Wed May 1 15:15:11 1996 Tom Jarmolowski <tjj@booklink.com>
+
+ * rcs.c (linevector_add): Move increment of i out of larger
+ statement, to avoid assumptions about evaluation order.
+
+Tue Apr 30 15:46:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.7.87.
+
+ * server.c (check_password): Don't use ANSI string concatenation.
+ Reindent function.
+
+Wed Apr 24 17:27:53 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * vers_ts.c (Version_TS): xmalloc enough space (1 more
+ byte). Thanks to purify!
+
+Fri Apr 19 11:22:35 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * Version 1.7.86
+
+Thu Apr 18 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * client.c (try_read_from_server): Compare return value from fwrite
+ with a size_t not an int (Visual C++ lint).
+
+Wed Apr 17 11:56:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (try_read_from_server): New function.
+ (read_from_server): Use it.
+ (read_counted_file): New function.
+ * client.c, server.c: Add Template response.
+ * cvs.h (CVSADM_TEMPLATE): Added.
+ * logmsg.c (do_editor): If repository is NULL, use CVSADM_TEMPLATE
+ file in place of rcsinfo.
+ * server.c, server.h (server_template): New function.
+ * create_adm.c (Create_Admin): Call it.
+
+Tue Apr 16 13:56:06 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * repos.c (Name_Repository): Fix comments.
+ * create_adm.c (Create_Admin): Fix indentation.
+
+Wed Apr 10 16:46:54 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * options.h.in: Include relevant information here rather than
+ citing (former) FAQ.
+
+ * ChangeLog-9395: Fix typo in introductory paragraph.
+
+Wed Apr 10 14:55:10 1996 code by Mike Spengler mks@msc.edu
+ comments by Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * filesubr.c (unlink_file_dir,deep_remove_dir): Don't call unlink
+ on something which might be a directory; check using isdir instead.
+
+Wed Apr 10 14:55:10 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (build_dirs_and_chdir): Pass path, not cp, to
+ Create_Admin. The former is the correct update dir.
+ * sanity.sh (modules): New tests modules-155* test, for above fix.
+
+Mon Apr 8 13:53:27 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * rcs.c (annotate_fileproc): If the file is not under CVS control,
+ return instead of dumping a core. Don't bug on files with an empty
+ first revision.
+
+Fri Mar 29 16:08:28 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (annotate_fileproc): If last line of add-chunk is not
+ newline terminated, end the loop when we find that out.
+
+Fri Mar 29 16:59:34 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * rcs.c (annotate_fileproc): allow last line of add-chunk not to
+ be newline terminated
+
+Thu Mar 28 10:56:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ Add more diff tests:
+ * sanity.sh (basic2): Use dotest for test 61.
+ (basica): Add test basica-6.2.
+ (branches): Add tests branches-14.4 and branches-14.5.
+ (basic1): Remove tests 19, 20, 25, and 26. The only thing this
+ might miss out on is diff's interaction with added and removed
+ files, but those tests didn't test that very well anyway.
+
+ * rcs.c (RCS_getrevtime): Add comment regarding years after 1999.
+
+ * rcs.c: Add "cvs annotate" command and related code.
+ (getrcskey): Move special handling of RCSDESC from here to
+ callers. Handle those keys (desc, log, text) which do not
+ end in a semicolon.
+ * rcs.h (RCSVers): Add author field.
+ * rcs.c (RCS_reparsercsfile): Set it.
+ * cvs.h (annotate), main.c (cmd_usage, cmds), client.h client.c
+ (client_annotate), server.c (serve_annotate, requests): Usual
+ machinery to add a new command.
+ * sanity.sh (basica): Test cvs annotate.
+
+ * sanity.sh (branches): More tests, of things like adding files on
+ the trunk after a branch has been made.
+
+Tue Mar 26 09:48:49 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * expand_path.c: Don't declare free and xmalloc; cvs.h already
+ takes care of that.
+
+ * sanity.sh (mflag): Don't use tag name reserved to CVS.
+
+ NT local changes plus miscellaneous things noticed in the process:
+ * import.c (add_rcs_file): Use binary mode to write RCS file. Use
+ \012 where linefeed is intended. Copy data a small block at a
+ time, until we hit EOF, rather than trying to read the whole file
+ into memory at once.
+ * client.c (send_modified): Add comments regarding st_size.
+ * commit.c (commit): Add comments regarding binary mode and read().
+ * logmsg.c (do_editor): Add comments regarding st_size.
+ * server.c (server_updated): Use binary mode to read file we are
+ sending.
+
+ * rcscmds.c (RCS_settag): Complain if user tries to add a tag name
+ reserved to CVS.
+ * sanity.sh (basica): Test for this behavior.
+
+ * sanity.sh (binfiles): New tests test ability to change keyword
+ expansion.
+
+Mon Mar 25 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * cvs.h, filesubr.c (expand_wild): New function.
+ * recurse.c (start_recursion): Call expand_wild at beginning and
+ free its results at the end.
+ * cvs.h, subr.c (xrealloc): Make argument and return value void *.
+ * client.h, client.c (send_file_names): Add flags argument. If
+ SEND_EXPAND_WILD flag is passed, call expand_wild at beginning and
+ free its results at the end.
+ * admin.c, add.c, log.c, tag.c, status.c, edit.c, watch.c,
+ update.c, commit.c, remove.c, client.c, diff.c: Update callers.
+
+Fri Mar 22 10:09:55 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * error.c (error, fperror): Exit with status EXIT_FAILURE rather
+ than STATUS. We had been neglecting to check for 256, and the
+ value of providing a count of errors is probably minimal anyway.
+ * add.c, modules.c, mkmodules.c, tag.c, server.c, main.c,
+ import.c, client.c, scramble.c, recurse.c: Exit with status
+ EXIT_FAILURE rather than 1. On VMS, 1 is success, not failure.
+ * main.c (main): Return EXIT_FAILURE or 0. The value of providing
+ a count of errors is minimal.
+
+ * client.c (init_sockaddr): Exit with status 1 rather than
+ EXIT_FAILURE. The latter apparently doesn't exist on SunOS4.
+ Reindent function.
+
+Mon Mar 18 14:28:00 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h, ignore.c: New variable ign_case.
+ * ignore.c (ign_name): If it is set, match in a case-insensitive
+ fashion.
+ * server.c (serve_case): New function.
+ (requests): Add Case request.
+ * client.c (start_server): If FILENAMES_CASE_INSENSITIVE is
+ defined, send Case request.
+
+Sat Mar 16 08:20:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ For reference, this change takes cvs's text segment from 315392
+ bytes to 311296 bytes (one 4096 byte page).
+ * cvs.h (struct file_info): Add fullname field.
+ * recurse.c (do_file_proc): Set it.
+ * commit.c (find_fileproc), client.c (send_fileproc), commit.c
+ (check_fileproc), diff.c (diff_fileproc), edit.c
+ (unedit_fileproc), patch.c (patch_fileproc), remove.c
+ (remove_fileproc), rtag.c (rtag_fileproc), tag.c (tag_fileproc),
+ update.c (update_fileproc), watch.c (watchers_fileproc): Use it
+ instead of computing it each time.
+ * diff.c (diff_fileproc), remove.c (remove_fileproc): Use fullname
+ where we had been (bogusly) omitting the directory from user
+ messages.
+ * edit.c (unedit_fileproc, edit_fileproc): If we cannot close
+ CVSADM_NOTIFY, mention CVSADM_NOTIFY rather than finfo->file in
+ error message.
+ * rtag.c (rtag_fileproc), tag.c (tag_fileproc): Reindent.
+
+Fri Mar 15 15:12:11 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * server.h: fix prototype of server_pause_check (was
+ server_check_pause)
+
+Thu Mar 14 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * vers_ts.c (Version_TS), entries.c (Scratch_Entry, AddEntryNode):
+ Change findnode to findnode_fn.
+
+ * main.c: Depending on HAVE_WINSOCK_H, include winsock.h or
+ declare gethostname.
+ * cvs.h: Don't declare it here.
+
+Thu Mar 14 07:06:59 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (find_fileproc): If vn_user is NULL and ts_user is not,
+ print an error rather than silently succeeding.
+ * sanity.sh (basica-notadded): New test, for above fix.
+ (dotest_internal): New function.
+ (dotest,dotest_fail): Call it instead of duplicating code between
+ these two functions.
+
+ * sanity.sh: Skip tests binfiles-9 through binfiles-13 for remote.
+
+ * options.h.in: Adjust comment to reflect kfogel change.
+
+Thu Mar 14 01:38:30 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * options.h.in (AUTH_CLIENT_SUPPORT): turn on by default.
+
+Wed Mar 13 09:25:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c (Version_TS): Don't try to override options from rcs
+ file if there isn't an rcs file (e.g. called from send_fileproc).
+ This fixes a bug detected by test 59 in "make remotecheck".
+
+ * rcs.c (RCS_reparsercsfile, RCS_getexpand): Assert that argument
+ is not NULL.
+
+ Fix a gcc -Wall warning:
+ * rcs.c, rcs.h (RCS_getexpand): New function.
+ * vers_ts.c (Version_TS): Call it.
+ * rcs.c (RCS_reparsercsfile): Make static.
+
+ Add a "cvs init" command. This is needed because cvsinit.sh
+ invoked mkmodules which doesn't exist any more.
+ * mkmodules.c: Break filelist out of mkmodules function, rename
+ struct _checkout_file to struct admin_file (for namespace
+ correctness), and add contents field.
+ (init,mkdir_if_needed): New functions.
+ * cvs.h (init): Declare.
+ * main.c (cmds): Add init.
+ (main): If command is init, don't require cvsroot to exist.
+ * client.c, client.h (client_init, send_init_command): New functions.
+ * client.c (start_server): Don't send Root request if command is init.
+ * server.c (serve_init): New function.
+ (requests): Add "init".
+
+Wed Mar 13 09:51:03 MET 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * vers_ts.c (Version_TS): set options to default option if the
+ file if no -k option but -A was given. This avoids the (wrong)
+ update message for binary files which are up-to-date when
+ running 'cvs -A'.
+
+ * update.c (checkout_file): remove test of -k option stored in the
+ file itself because it was moved to vers_ts.c
+
+ * sanity.sh: added tests for the above fix.
+
+Tue Mar 12 13:47:09 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * hash.c (findnode): Adjust comment regarding errors.
+
+ * hash.c (findnode, findnode_fn): Assert that key != NULL. This
+ way the check still happens even if the function is later
+ rewritten to not start out by calling hashp.
+
+Mon Mar 11 10:21:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: If expr accepts multi-line patterns but is too
+ liberal in matching them, print a warning but keep going.
+
+ * sanity.sh: Add QUESTION variable, analogous to PLUS. Use it
+ instead of \? to match a question mark.
+
+ * cvs.h (CVSMODULE_OPTS, CVSMODULE_SPEC): Move from here...
+ * modules.c: ...to here. They are only used here and the code to
+ handle the syntax of modules files should not be scattered all over.
+ * modules.c (CVSMODULE_OPTS): Add "+" as first character.
+ * sanity.sh (modules): New tests 148a0 and 148a1 test for
+ above-fixed bug.
+
+Mon Mar 11 13:11:04 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * modules.c (cat_module): set optind to 0 to force getopt() to
+ reinitialize its internal nextchar
+
+Mon Mar 11 00:09:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * hash.c (findnode, findnode_fn): Revert changes of 7-8 Mar 1996.
+ The correct style is to assert() that key != NULL (see HACKING),
+ which is already done in the hashp function.
+ * fileattr.c (fileattr_delproc): Likewise, assert() that
+ node->data != NULL rather than trying to deal with it being NULL.
+
+Fri Mar 8 01:31:04 1996 Greg A. Woods <woods@most.weird.com>
+
+ * hash.c (findnode_fn): one more place to avoid calling hashp()
+ with a NULL key
+
+Thu Mar 7 17:30:01 1996 Greg A. Woods <woods@most.weird.com>
+
+ * hash.c (findnode): also return NULL if key is not set
+ [[ reported by Chris_Eich@optilink.optilink.dsccc.com, and
+ supposedly in a PR that should be marked "fixed"..... ]]
+
+ * fileattr.c (fileattr_set): set node->data to NULL after freeing
+ it to prevent subsequent accesses
+ (fileattr_delproc): don't free node->data if it's NULL, and set it
+ to NULL after freeing
+ [[ reported by Chris_Eich@optilink.optilink.dsccc.com, and
+ supposedly in a PR that should be marked "fixed"..... ]]
+
+Fri Mar 1 14:56:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (basica): New test basica-4a tests for bug fixed by
+ sam@inf.enst.fr on 1 Mar 96.
+
+Fri Mar 1 18:10:49 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * tag.c (check_fileproc): Check for file existence before trying
+ to tag it.
+
+Fri Mar 1 07:51:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (update_entries): If command is export, set options to
+ NULL.
+
+Thu Feb 29 16:54:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * lock.c (write_lock, Reader_Lock): Remove
+ BOGUS_UNLESS_PROVEN_OTHERWISE code. It was pretty bogus, and has
+ been ifdeffed out for a long time.
+ * cvs.h (CVSTFL): Removed; no longer used.
+
+ * cvsrc.c, cvs.h (read_cvsrc): Pass in command name rather than
+ using global variable command_name.
+ * main.c (command_name): Initialize to "", not "cvs" so that error
+ messages don't say "cvs cvs". Update calls to read_cvsrc to pass
+ in command_name or "cvs" as appropriate.
+ * sanity.sh (basica): New test basica-9 tests for above-fixed bug.
+
+ * lock.c: Rename unlock to lock_simple_remove to avoid conflict
+ with builtin function on QNX.
+
+Thu Feb 29 17:02:22 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * fileattr.c (fileattr_get): Removed NULL pointer dereference
+ which occurred in the absence of default attribute.
+
+Thu Feb 29 07:36:57 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.c (RCS_isbranch, RCS_whatbranch): Remove no longer used file
+ argument, swap order of remaining two arguments to be like other
+ RCS_* functions.
+ (RCS_nodeisbranch): swap order of arguments to be like other RCS_*
+ functions.
+ * rcs.h (RCS_isbranch, RCS_whatbranch, RCS_nodeisbranch): Update
+ prototypes for above changes.
+ * commit.c, rtag.c, status.c, tag.c: Update for above calling
+ convention changes.
+
+Thu Feb 29 08:39:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (start_server): Revert changes which claimed to fall
+ back to a different way of connecting. Add comments explaining
+ why. (I don't think the changes did what they claimed, anyway).
+ Use indentation rather than comments to line up #if, #else, and
+ #endif.
+
+ * patch.c (patch, patch_fileproc): Revert change to add optional
+ arguments to -c and -u. Optional arguments are evil and in
+ violation of the POSIX argument syntax guidelines. The correct
+ way to do this is -C and -U. Also change DIFF back to "diff" in
+ output (see comments).
+
+ gcc -Wall lint:
+ * client.c (copy_a_file): Declare p inside the #ifdef in which is
+ it used.
+ * commit.c (remove_file): Remove unused variable p.
+ * commit.c (checkaddfile): Remove unused variables p.
+ * rcs.c (RCS_isbranch): Remove unused variable p.
+ * rcs.c: Remove unused declarations and definitions of
+ parse_rcs_proc, rcsnode_delproc, rcslist, and repository.
+ * rtag.c (rtag_fileproc): Remove unused variable p.
+ * patch.c (patch_fileproc): Remove unused variable p.
+ * tag.c (val_fileproc): Remove unused variable node.
+ * client.c, import.c, lock.c, server.c: Cast pid_t to long before
+ passing it to %ld.
+
+ * cvs.h: Don't prototype gethostname; merely declare it (on linux,
+ second argument is size_t not int).
+
+Thu Feb 29 10:29:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de>
+
+ * sanity.sh: added "cat > /dev/null" to loginfo entry to avoid the
+ SIGPIPE signal
+
+Thu Feb 29 10:28:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de>
+
+ * patch.c: added new variable diff_opt
+ (patch): allow optional parameter to -c and -u option, send it to
+ server
+ (patch_fileproc): cleaned up the code which prints the current
+ filename. For "-s" option, print the pathname relative to CVSROOT
+ instead of just the filename.
+
+ * filesubr.c (xchmod): added cast to shut up gcc
+
+ * cvs.h: added prototype for gethostname
+
+Thu Feb 29 10:27:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de>
+
+ * lock.c (write_lock), (Reader_Lock), import.c (update_rcs_file),
+ client.c (update_entries), (send_modified), server.c (server),
+ (receive_file), (server_updated): use %ld for printing pid_t
+ variables
+
+Thu Feb 29 02:22:12 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * run.c (run_exec): Added VMS return status support.
+
+Thu Feb 29 01:07:43 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * client.c (send_to_server): wrtn wasn't being declared under
+ VMS for some reason.
+
+Wed Feb 28 23:27:04 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * client.c: Changed #ifdef VMS && NO_SOCKET_TO_FD to
+ #if defined(VMS) && defined(NO_SOCKET_TO_FD)
+
+Wed Feb 28 22:28:43 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * build_src.com: Added DCL command procedure to build
+ and link CVS client for VMS.
+
+Wed Feb 28 22:07:20 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * client.c: VMS CVS client specific changes.
+
+ Added USE_DIRECT_TCP to allow CVS_PORT to be used to specify
+ a TCP connection port (no Kerberos). Changed
+ start_kerberos_server() to start_tcp_server().
+
+ In copy_a_file(): transform a backup file to have a
+ VMS-friendly name.
+
+ Added HAVE_CONFIG_H to include "config.h".
+
+ start_server() will starts the first successful of any
+ mutually exclusive methods of starting the CVS server
+ which might be enabled.
+
+ Initialized use_socket_style and server_sock for VMS in
+ start_server().
+
+Wed Feb 28 21:49:48 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * find_names.c, recurse.c, cvs.h: Changed Find_Dirs() to
+ Find_Directories().
+ * cvs.h: Added VMS filenames enabled through USE_VMS_FILENAMES
+ VMS POSIX will require to use the regular CVS filenames
+ while VMS is #define'd.
+
+Wed Feb 28 21:26:22 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * ignore.c: Added the patterns *.olb *.exe _$* *$ to default
+ ignore list for VMS.
+
+Wed Feb 28 13:32:28 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * logmsg.c (do_editor): Fix indentation.
+
+Wed Feb 28 12:56:49 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * logmsg.c (do_editor): If no editor is defined, exit and print
+ a message.
+
+Wed Feb 28 10:40:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c (time_stamp, time_stamp_server): Reindent and revise
+ comments.
+
+Tue Feb 27 23:57:55 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * vers_ts.c: gmtime() returns NULL on some systems (VMS)
+ revert to local time via ctime() if GMT is not avaiable.
+
+Tue Feb 27 13:07:45 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ The changes listed below cause cvs to parse each rcs file (and
+ free the associated rcsnode after the file has been processed)
+ sequentially. cvs used to parse all files in a directory, an
+ approach that does not scale to huge repositories with lots
+ of revisions/branches/tags/etc.
+
+ * cvs.h (struct file_info): Removed srcfiles field. Added rcs
+ (node) field.
+ * recurse.c (do_recursion): Removed code that pre-parsed all
+ rcs files in the directory.
+ (do_file_proc): Parse current rcs file.
+ * rcs.c (RCS_parsefiles, parse_rcs_proc, RCS_addnode): Removed.
+ (RCS_isbranch, RCS_whatbranch): Changed srcfiles argument to
+ rcs (node).
+ * rcs.h (RCS_parsefiles, RCS_addnode): Removed prototypes.
+ (RCS_isbranch, RCS_whatbranch): Updated prototypes.
+ * add.c, admin.c, checkin.c, checkout.c, classify.c, client.c,
+ commit.c, diff.c, history.c, import.c, log.c, patch.c, remove.c,
+ rtag.c, status.c, tag.c, update.c, vers_ts: Updated for above
+ calling convention / data structure changes.
+
+Mon Feb 26 16:07:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.7.3.
+
+ * Version 1.7.2.
+
+Mon Feb 26 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * recurse.c (start_recursion): Use last_component rather than
+ checking for '/' directly.
+ (do_dir_proc): Likewise.
+
+ Visual C++ lint:
+ * client.c (send_to_server): Change wrtn to size_t.
+ (connect_to_pserver): Put tofd and fromfd declarations inside
+ #ifndef NO_SOCKET_TO_FD.
+ * scramble.c (shifts): Change from array of char to array of
+ unsigned char.
+
+Mon Feb 26 13:31:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (check_repository_password): Remove unused variables
+ linelen, ch.
+
+ * client.c (send_file_names): Translate ISDIRSEP characters to '/'.
+
+Sat Feb 24 21:25:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (safe_location): Re-indent one line.
+
+Sat Feb 24 10:50:42 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * checkout.c (safe_location): put assignment to hardpath[x] in an
+ `else'-clause, so we don't do it when x == -1.
+
+Sat Feb 24 01:40:28 1996 Marcus Daniels <marcus@sayre.sysc.pdx.edu>
+ via Karl Fogel <kfogel@floss.red-bean.com>
+
+ * server.c (check_repository_password): Return by reference an
+ optional username, the `host_user', from the passwd file. The
+ host_user will be the user-id under which the cvs repository is
+ run.
+ (check_repository_password): Use `read_line' instead of fgets to
+ allow for passwords larger than 32 characters, as well as the
+ optional host user argument.
+ (check_password): Modify to use host_user.
+ (authenticate_connection): Modify to use host_user.
+
+Sat Feb 24 01:05:21 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * scramble.c (descramble): just shift descrambled string to get
+ rid of tag char, instead of allocating a whole new copy.
+ (scramble): cast return value of xmalloc to avoid unsightly
+ compiler warnings.
+
+ * options.h.in (RCSBIN_DFLT): don't refer to AUTH_SERVER_SUPPORT
+ in comment anymore, now that it's not defined in this file.
+
+Fri Feb 23 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * client.c: Ifdef HAVE_WINSOCK_H, include winsock.h
+ instead of sys/socket.h and friends.
+ * login.c: Don't include sys/socket.h and friends.
+ * login.c (login): Only fclose fp in the case where it was
+ successfully fopen'd.
+ * login.c: Declare getpass.
+ * filesubr.c, cvs.h (get_homedir): New function.
+ * cvsrc.c, expand_path.c, history.c, login.c: Call it instead
+ of getenv ("HOME").
+
+Fri Feb 23 09:23:20 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (connect_to_pserver): Remove unused variable host.
+ * login.c: Include getline.h.
+ (login): Remove unused variables i and username.
+ (get_cvs_password): Move free of linebuf to where it actually will
+ be called. Add a "return NULL" at the end of the function to shut
+ up gcc -Wall.
+
+ * options.h.in: Remove AUTH_SERVER_SUPPORT.
+ * client.h (authenticate_connection): Declare.
+ * scramble.c (scramble): Cast char to unsigned char before using
+ it to look up in table (char might be signed).
+ * server.c [AUTH_SERVER_SUPPORT]: Include grp.h
+ (authenticate_connection): Remove unused variables len and
+ server_user.
+
+ * sanity.sh (basica): Add comments regarding creating a top-level
+ directory.
+ (basic1): Don't try to remove first-dir and
+ ${CVSROOT_DIRNAME}/first-dir at start of test; tests are now
+ responsible for cleaning up at the end.
+ (PLUS,DOTSTAR,ENDANCHOR): Add comments regarding fixed GNU expr.
+
+Thu Feb 22 22:34:11 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Remove alloca cruft.
+
+Wed Feb 21 07:30:16 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * modules.c (do_module): call free_cwd before exiting.
+
+ * recurse.c: Removed entries global variable.
+ (do_recursion): Declare entries. Moved call to Entries_Close so
+ entries list is closed on all code paths.
+ (start_recursion): Removed call to Entries_Close, entries list has
+ been moved to do_recursion only.
+
+Tue Feb 20 22:10:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (update_dirent_proc): If dir lacks a CVS subdirectory,
+ don't recurse into it.
+ * sanity.sh (conflicts): Test for above-fixed bug.
+
+ * update.c (merge_file): Use write_letter not printf.
+
+Tue Feb 20 12:34:07 EST 1996: Gary Oberbrunner <garyo@avs.com>
+ and Jim Kingdon <kingdon@cyclic.com>
+
+ * history.c (history_write): Change username to char * and call
+ getcaller() to set it. Setting username accidentally got deleted
+ 8 Feb 96.
+ * sanity.sh: Revise test 64 to test for above-fixed bug.
+ * sanity.sh (PLUS): New variable, work around yet another GNU expr
+ bug.
+
+Tue Feb 20 14:07:50 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Merge test rtags into test basic2. They never were
+ capable of running separately of each other.
+
+ * sanity.sh (deep): New test, to test ability to operate in deeply
+ nested directories (more quickly than basic2 test did).
+ (basic2,rtags): Remove directories dir3 and dir4. Remove file8,
+ file10, file12, file9, file11, file13, file15, file16, file17.
+ These additional files slowed down the tests considerably without
+ significantly increasing coverage.
+
+ * sanity.sh (PROG): New variable. Use it instead of "cvs"
+ to match the name cvs prints out for itself.
+
+Mon Feb 19 09:00:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ This fixes a bug whereby old default attributes would get
+ clobbered instead of added to on "cvs watch add".
+ * hash.c (findnode): Don't check for key == NULL; let the
+ assertion in hashp take care of it.
+ * fileattr.h, fileattr.c (fileattr_get): If filename is NULL,
+ return default attributes.
+
+ * client.c (send_repository): Fix indentation.
+
+Mon Feb 19 01:10:01 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * login.c (login): print out full repos so user knows which server
+ she's logging into.
+
+ * client.c (send_repository): die if `repos' is NULL. This is a
+ lame solution; see comments in code.
+
+Thu Feb 15 15:04:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * error.c (error): Free entire and mess when done with them.
+
+ * sanity.sh (info): Correct syntax of .cvsrc file.
+
+ * cvs.h, expand_path.c, edit.c, parseinfo.c, wrapper.c:
+ expand_path now takes arguments containing file and line for error
+ message, and it prints the error message itself.
+ * sanity.sh (info-6a): Test printing of error message.
+
+ * expand_path.c (expand_variable): Add USER internal variable.
+ * sanity.sh (info): Test USER and CVSROOT internal variables too.
+
+Wed Feb 14 19:11:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * main.c (usg): Add -s option.
+
+Tue Feb 13 20:26:06 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ gcc -Wall lint:
+ * mkmodules.c (mkmodules_usage): Remove declaration of
+ non-existent function.
+ * cvs.h (mkmodules): Declare.
+
+Mon Feb 12 12:20:04 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * mkmodules.c: Rename main to mkmodules and remove various pieces
+ of scaffolding which it used to emulate non-existent parts of CVS.
+ Change calling convention to just take a char * not argc,argv.
+ Save and restore working directory.
+ * commit.c (commit_filesdoneproc): Call it if checking files into
+ CVSROOT.
+ * Makefile.in (SOURCES): Add mkmodules.c.
+ (OBJECTS): Add mkmodules.o.
+ (MSOURCES,MOBJECTS): Removed.
+ (COMMON_OBJECTS): Removed; move former contents into OBJECTS.
+ Update other rules accordingly.
+ * sanity.sh: Adjust to reflect nonexistence of mkmodules.
+
+ These changes introduce functions cvs_output and cvs_outerr;
+ eventually all server output will go through them rather than
+ stdio directly.
+ * server.c (saved_output, saved_outerr): New variables.
+ (do_cvs_command): Initialize them.
+ (buf_output): Don't require that buf->output be set; saved_* use
+ this to shove some data in a buffer which buf_copy_lines will
+ later want to get data from.
+ * server.c, cvs.h (cvs_output, cvs_outerr): New functions.
+ * mkmodules.c (cvs_outerr): New function, so error() works.
+ * error.c: Reindent. Don't declare program_name and command_name;
+ cvs.h declares them.
+ (error): Use vasprintf and cvs_outerr (or fputs in the
+ error_use_protocol case) rather than stdio directly.
+ * import.c (import_descend_dir): Remove kludge which had prevented
+ messages from error() from being out of order with respect to
+ messages from printf; cvs_output and cvs_outerr are a cleaner
+ solution to the problem.
+ (add_log, import): Use cvs_output not printf.
+ * update.c (write_letter): Use cvs_output not printf.
+ (checkout_file): Use write_letter not printf.
+ * sanity.sh: Use dotest for test 56 (test that output is actually
+ correct). In theory should test that the import.c bug is fixed,
+ but I was unable to reproduce the bug (it is timing dependent).
+
+Mon Feb 12 16:07:45 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * commit.c: define last_register_time
+ (commit): make sure cvs doesn't exit in the same second it wrote
+ the last timestamp
+ (commit_fileproc): set last_register_time
+ (finaladd): set last_register_time
+
+ * run.c, cvs.h: Changed more Popen() to run_popen()
+
+Mon Feb 12 03:06:50 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * release.c, rtag.c, tag.c: changed 'delete' to 'delete_flag'
+ to avoid symbol collision with DEC C RTL function delete()
+
+Mon Feb 12 03:01:48 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * mkmodules.c: changed 'void Lock_Cleanup()' to 'void static
+ Lock_Cleanup() to avoid conflict with more substantial
+ Lock_Cleanup() in lock.c
+
+Mon Feb 12 02:50:19 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * edit.c, logmsg.c, release.c, run.c: Changed Popen() to
+ run_popen(). VMS' linker is not case sensitive and considered
+ popen() and Popen() to be identical symbols.
+
+Sun Feb 11 10:51:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * main.c (main) [!CLIENT_SUPPORT]: Silently ignore gzip level
+ rather than printing usage message.
+
+ * cvs.h, expand_path.c (variable_list): New variable.
+ (variable_set): New function.
+ * hash.h (enum ntype), hash.c (nodetypestring): Add VARIABLE.
+ * expand_path.c (expand_path, expand_variable): Reindent.
+ (expand_variable): Use user variables not environment variables
+ for ${=VAR} syntax. The environment variables didn't work
+ client/server.
+ * main.c (main): Process new -s global option.
+ * client.c (send_variable_proc): New function.
+ (start_server): Call it, to send user variables.
+ * server.c (serve_set): New function.
+ (requests): Add Set request.
+ * sanity.sh: Revise info test to use user variables rather than
+ environment variables.
+
+Sat Feb 10 16:55:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ By itself this is only a small cleanup, but in the long run it
+ will be way cool (for reference, it takes CVS's text segment from
+ 290816 bytes to 294912, which I expect will be made up by future
+ changes which this enables):
+ * cvs.h (struct file_info): Added.
+ (FILEPROC): Replace 5 args with single struct file_info *.
+ * recurse.c (do_file_proc): Adjust args to fileproc; passed in
+ instead of from globals.
+ (do_recursion): Call do_file_proc accordingly. Remove srcfiles
+ global variable.
+ * update.c (update_fileproc): Renamed from update_file_proc.
+ * admin.c, client.c, commit.c, diff.c, edit.c, log.c, patch.c,
+ remove.c, rtag.c, status.c, tag.c, update.c, watch.c: Update
+ fileprocs to new calling convention.
+
+Fri Feb 9 15:30:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * expand_path.c (expand_variable): Accept a variable name starting
+ with '=' as a way to specify an environment variable.
+ * sanity.sh (info): New tests, for above behavior.
+
+ * Makefile.in (clean): Also remove check.log check.plog.
+
+ * import.c (comtable): Remove SYSTEM_COMMENT_TABLE; the table
+ should *not* depend on what kind of machine the server happens to
+ be. Add "mak", "rc", "dlg", "frm", and "bas" types which were
+ formerly included via SYSTEM_COMMENT_TABLE.
+
+ * cvs.h, rcs.h, add.c, checkin.c, classify.c, commit.c, diff.c,
+ import.c, patch.c, rcs.c, update.c, vers_ts.c: Remove
+ DEATH_SUPPORT ifdefs. They were introduced to facilitate merging
+ between Cygnus and Berliner variants of CVS, not because it was
+ intended to subset CVS this way. And they clutter up the code
+ quite a bit.
+ * cvs.h, create_adm.c, main.c, update.c: Likewise, remove
+ CVSADM_ROOT ifdefs (it is still a #define, of course). I believe
+ they had a more-or-less similar motivation.
+
+ * sanity.sh: Move setting of HOME from ignore test to the start of
+ the tests so it applies to all tests.
+ (CVS): Remove -f; the above change takes care of it.
+
+ * rcs.h (RCS_MERGE): Removed; unused.
+
+ * commit.c (checkaddfile): Fix memory leak.
+
+ * admin.c, commit.c, diff.c, log.c, mkmodules.c: Pass -x,v/ to RCS
+ commands.
+
+ * rcscmds.c, cvs.h (RCS_checkin): New function.
+ * checkin.c, commit.c, import.c: Call it, rather than run_*.
+ * cvs.h, commit.c: Remove DEATH_STATE define; the behavior
+ which used to be the default (DEATH_STATE) is now the only one.
+ Failing to define DEATH_STATE has been commented as obsolete at
+ least since CVS 1.5. We still can read repositories created with
+ such a CVS, however.
+ * rcs.h, rcs.c: Adjust comments regarding DEATH_STATE.
+ * subr.c (make_message_rcslegal): Add comment, describing
+ allocation of returned value.
+
+Fri Feb 9 09:53:44 MET 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * sanity.sh: use "${testcvs}" instead of "cvs" in devcom tests
+
+ * hash.c: fix "dereferencing a NULL pointer" bug triggered with
+ "cvs watch add"
+ (findnode): return NULL if key == NULL
+ (hashp): assert (key != NULL)
+
+Fri Feb 9 00:46:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (RCS_reparsercsfile): Remove unused variable date.
+
+ * myndbm.c (mydbm_load_file): Fix typo ('015' -> '\015').
+
+Thu Feb 8 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * rcs.c (RCS_parse, RCS_parsercsfile, RCS_reparsercsfile),
+ fileattr.c (fileattr_read), myndbm.c (myndbm_open):
+ Use FOPEN_BINARY_READ.
+ * fileattr.c (fileattr_write), myndbm.c (myndbm_close):
+ Use FOPEN_BINARY_WRITE.
+ * history.c (history_write, read_hrecs): Specify OPEN_BINARY.
+ * rcs.c: Remove calls to abort.
+ * myndbm.c (myndbm_load_file): Ignore CRs from ends of lines
+ if present.
+ * myndbm.c, fileattr.c: While I am at it, change \n to \012
+ a few places where LF is intended.
+ * history.c (history_write): Use getenv ("HOME"), not getpwnam,
+ to find home directory. If it isn't set, just keep going; don't
+ print a message.
+ * rcscmds.c, cvs.h (RCS_checkout): New function.
+ * update.c, checkin.c, commit.c, diff.c, import.c, no_diff.c,
+ patch.c: Call it instead of run_*.
+ * patch.c (patch_fileproc): Clean up inconsistent handling of
+ noexec flag.
+ * rcscmds.c (RCS_*): Pass -x,v/ to RCS commands; elsewhere in
+ CVS it is assumed that ,v is a suffix.
+
+Fri Feb 2 14:07:32 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.h (struct rcsnode): Remove dates field (list of rcsversnodes
+ indexed by date). CVS maintained this list for each RCS file even
+ though it was never used. This resulted in higher then necessary
+ memory requirements (and run time too). Even if revision info was
+ needed, CVS' List data structure is inappropriate because can't
+ handle duplicate keys. The above was discovered by tracking down
+ a memory leak.
+ * rcs.c (RCS_reparsercsfile): Don't build dates list.
+ (freercsnode): Don't delete dates list.
+ (rcsvers_delproc): Free date field.
+ (null_delproc): Removed.
+
+Thu Feb 1 12:28:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * remove.c (cvsremove): Don't tell user the name of the program
+ which they use to remove files; we don't have any way of knowing
+ that, and besides which they might use a GUI or emacs 'dired' anyway.
+ * update.c (update_filesdone_proc, update_dirleave_proc): Call
+ unlink_file_dir instead of rm -rf.
+ * options.h.in: Remove RM; no longer used.
+
+ * sanity.sh: New tests devcom-a* test "cvs watch add",
+ "cvs watch remove", and "cvs watchers".
+
+ * sanity.sh: New test 171a0 tests for watch.c bug just fixed by kfogel.
+
+ * Most .c files: Remove rcsids.
+ * cvs.h: Remove USE macro.
+
+Thu Feb 1 13:07:15 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * tag.c, rtag.c: Update various comments to reflect function name
+ changes.
+
+Thu Feb 1 14:14:31 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * recurse.c (do_recursion): comment #endif.
+
+ * edit.c (notify_check): surround with #ifdef CLIENT_SUPPORT; else
+ CVS won't compile if CLIENT_SUPPORT is undefined.
+
+ * edit.h (notify_check): surround declaration with #ifdef
+ CLIENT_SUPPORT.
+
+ * watch.c (watch): if argc <= 1, then just give usage (previously
+ was "argc == -1").
+
+Thu Feb 1 12:28:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README-rm-add: Remove information which is now in cvs.texinfo.
+
+ * sanity.sh: Remove basic0 tests. Move a few to new tests
+ basica-1a* (but there is no need to test that *every* command
+ gracefully does nothing on an empty directory; exhaustive testing
+ is impractical and the generic recursion processor handles this
+ anyway).
+
+ * sanity.sh: New tests 69a* test use of update -p to restore old
+ version of dead file.
+
+Wed Jan 31 18:32:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ChangeLog-9395: Remove duplicate entries from 1996 which
+ accidentally got into this file.
+
+ * client.c (read_line, read_from_server): Change "premature end of
+ file from server" message to "end of file from server (consult
+ above messages if any)" because 99% of the time it means rsh has
+ printed an error message and exited.
+
+Wed Jan 31 15:09:51 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * edit.c (ncheck_fileproc): Fix memory leak; free line before
+ returning.
+
+Tue Jan 30 18:06:12 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * recurse.c (do_recursion): Add comment about the fact that we
+ don't have locks in place at certain points.
+
+Tue Jan 30 09:43:34 1996 Vince Demarco <vdemarco@bou.shl.com>
+
+ * edit.c (notify_proc): have notify_proc call expand_path with
+ the name of the filter program. The user may have used a
+ cvs environmental variable. (Popen will expand it, but it may not
+ use the correct value)
+
+Tue Jan 30 09:43:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ChangeLog: take the pre-1996 changes and put them in a new file
+ ChangeLog-9395.
+ * ChangeLog-9194: Renamed from ChangeLog.fsf.
+ * ChangeLog-9194, ChangeLog-9395, ChangeLog: Add additional text
+ explaining the difference between all these logs and pointing to
+ older logs.
+ * Makefile.in (DISTFILES): Add ChangeLog-9194 and ChangeLog-9395;
+ remove ChangeLog.fsf.
+
+ * modules.c (do_module): Don't fall through from 'l' to 'o' case
+ of option processing switch statement.
+
+Tue Jan 30 06:50:19 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * client.c (send_repository): Fix memory leak; free adm_name
+ before returning.
+ * diff.c (diff_file_nodiff): Fix memory leak; free xvers before
+ returning.
+ * rtag.c (rtag_fileproc): Fix memory leak; if branch_mode is set,
+ free rev before returning.
+ * status.c (status_fileproc, tag_list_proc): Fix memory leak; free
+ return value of RCS_whatbranch.
+ * tag.c (tag_fileproc): Fix memory leak; free vers before
+ returning.
+ (val_fileproc): Fix memory leak; free return value of RCS_gettag.
+ * watch.c (watch_modify_watchers): Fix memory leak; free mynewattr
+ before returning.
+
+Tue Jan 30 09:43:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * lock.c (readers_exist): If stat gave an error, print an error
+ message saying it was from stat, rather than from "reading
+ directory". Skip the message completely if it was an
+ existence_error.
+
+ * sanity.sh (branches): New tests (branches off of branches, etc.).
+
+Tue Jan 30 11:55:34 MET 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * main.c (main): Add change to run getopt_long twice again.
+
+Mon Jan 29 15:59:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ gcc -Wall lint:
+ * client.c: Include edit.h
+
+Sun Jan 28 09:45:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * edit.c, edit.h (mark_up_to_date): New function, to remove file
+ in CVS/Base.
+ * client.c (update_entries): Call it if file is up to date.
+ * checkin.c (Checkin): Call it in non-server (local) case.
+ * sanity.sh: New test 182.5, tests for above-fixed bug.
+
+Sun Jan 28 01:07:22 1996 Jim Kingdon (kingdon@beezley)
+
+ * client.c (change_mode): Separate out CHMOD_BROKEN code to parse
+ mode_string, rather than going through a mode_t. Cleaner than
+ the previous CHMOD_BROKEN code (which also had a typo of && not &).
+
+Sat Jan 27 23:29:46 1996 Jim Kingdon (kingdon@beezley)
+
+ * edit.c (edit_fileproc): Check for EACCESS as well as EEXIST.
+
+Sat Jan 27 16:26:30 1996 Karl Fogel (kfogel@floss.cyclic.com)
+
+ * client.c (notified_a_file): use rename_file() instead of
+ rename() (but temporarily set `noexec' to 0 so it runs
+ unconditionally).
+ (change_mode): deal with CHMOD_BROKEN.
+
+Fri Jan 26 00:14:00 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * server.c: renamed `dirname' to `dir_name', to avoid conflicts
+ with system headers.
+
+ * client.c: renamed `dirname' and `last_dirname' to `dir_name' and
+ last_dir_name' (see above). Not strictly necessary, but
+ consistency is nice -- as long as you do it all the time.
+
+Thu Jan 25 00:41:59 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * options.h.in (AUTH_SERVER_SUPPORT, AUTH_CLIENT_SUPPORT): change
+ comment now that no longer under construction.
+
+Wed Jan 24 15:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.7.1.
+
+ * Version 1.7.
+
+Sat Jan 20 00:05:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.87.
+
+Mon Jan 15 18:14:55 1996 Gary Oberbrunner <garyo@avs.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * tag.c (val_direntproc): New function to ignore
+ nonexistent dirs when recursing to check tag validity.
+ (tag_check_valid): Pass it to start_recursion.
+ * sanity.sh (death): New tests 65a0-65a6 cause test 74 to test for
+ above-fixed bug.
+
+Mon Jan 15 12:55:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * main.c: Revert change to run getopt_long twice. This can go in
+ after 1.7.
+
+Mon Jan 15 13:03:28 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * filesubr.c (deep_remove_dir): added test of EEXIST for nonempty
+ directory (Posix states that both ENOTEMPTY (BSD) and EEXIST
+ (SYSV) are valid)
+
+ * main.c (main): run getopt_long twice to allow command-line
+ suppression of reading the cvsrc file
+
+Fri Jan 12 10:02:43 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.86.
+
+Thu Jan 11 23:28:05 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * fileattr.h (fileattr_startdir): Add comment about REPOS == NULL.
+ * fileattr.c (fileattr_read, fileattr_write): Assert that
+ fileattr_stored_repos != NULL.
+ (fileattr_free): If fileattr_stored_repos is NULL, don't free it.
+
+Thu Jan 11 18:03:21 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * scramble.c (descramble): deal with DIAGNOSTIC better.
+
+Thu Jan 11 12:04:42 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * main.c: remove CVS_NOADMIN.
+
+ * options.h.in: remove CVS_NOADMIN
+
+Thu Jan 11 10:28:44 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * scramble.c (descramble): make sure the string returned is safe
+ to free().
+
+Wed Jan 10 01:11:23 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (serve_notify): Cast return value from malloc.
+
+ * edit.c (notify_do): Use struct assignment, not struct
+ initialization (which SunOS4 /bin/cc doesn't have).
+
+Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.85.
+
+ We use version numbers instead of patchlevels. But there was some
+ confusing patchlevel stuff lying around. Nuke it:
+ * Makefile.in (HEADERS): Remove patchlevel.h
+ * patchlevel.h: Removed.
+ * main.c: Don't include patchlevel.h.
+ (main): Don't print patch level.
+
+ * server.c (check_repository_password): Check for errors from
+ system calls; reindent function.
+
+Tue Jan 9 23:15:30 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * expand_path.c: fix comments (explain expand_path()'s behavior
+ correctly).
+
+Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * edit.c (notify_proc): After copying in string following %s,
+ don't clobber it. Instead set up q to end of string.
+
+ * watch.c (watch_modify_watchers), edit.c (editor_set): Fix sense
+ of test in trying to decide whether attributes are changed.
+
+ * cvs.h (CVSROOTADM_USERS): New macro.
+ * edit.c (notify_do): Look up notifyee in CVSROOTADM_USERS if it
+ exists.
+
+Tue Jan 9 21:39:45 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * expand_path.c: don't redundantly #include things that cvs.h
+ already #includes (i.e., stdio.h, ctype.h, string[s].h).
+
+Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ignore.c (ign_default): Add *.obj.
+
+ * server.c: Put /* */ around #endif comment.
+
+Mon Jan 8 20:37:17 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * client.c (connect_to_pserver): check return value of recv().
+
+Mon Jan 8 11:37:57 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (connect_to_pserver): Check for error from connect;
+ reindent function.
+
+ * sanity.sh (4.75): Use dotest, so we get a PASS if test passes.
+
+ * sanity.sh (dotest): New argument OUTPUT2.
+ (188a): Use it instead of \|.
+
+ * sanity.sh (import): Avoid using string $ followed by Id followed
+ by $ in sanity.sh source, in case sanity.sh itself is under CVS.
+ I hate keyword expansion.
+
+ * sanity.sh: If expr cannot handle multiline expressions, fail and
+ tell the user to get one which can.
+
+ * release.c (release_delete): Remove unused variable retcode.
+
+Fri Jan 5 13:30:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * release.c (release_delete): Call unlink_file_dir rather
+ than "rm -rf".
+
+Thu Jan 4 09:58:30 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (find_fileproc): Print "nothing known about foo" and
+ return 1 if the file doesn't exist and isn't in CVS/Entries.
+ (commit): If the recursion over find_fileproc returns an error,
+ print "correct above errors first!" just like local CVS.
+ * sanity.sh (basica): Test for above-fixed bug.
+
+ * release.c (release): If we are the client, only unedit if the
+ server supports it.
+
+ * sanity.sh: Remove STARTANCHOR stuff; expr patterns are
+ automatically anchored to the start. ENDANCHOR remains.
+
+ * commit.c (commit): Don't start the server until we have
+ determined that there is something to commit.
+
+Thu Jan 4 09:48:33 1996 Ben Laurie <ben@gonzo.ben.algroup.co.uk>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (start_server): dup the file descriptor before
+ fdopening it.
+
+Wed Jan 3 18:25:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Remove tests 5, 5.5, and 5.75. All that stuff is
+ tested elsewhere.
+
+ * ignore.c (ign_default): Change CVS* to CVS CVS.adm. CVS* is too
+ broad, especially in a case-insensitive filesystem.
+
+ * Makefile.in (cvsbug): version.c is in srcdir.
+
+Wed Jan 3 17:30:45 1996 Phi-Long Tran <ptran@autodesk.com>
+
+ * modules.c (do_module): Honor error_use_protocol in printing trace.
+ * server.c (server_register): Move check for options NULL to above
+ printing of the trace.
+
+Wed Jan 3 01:19:53 1996 Mark Immel <immel@centerline.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (checkout_file): Do not resurrect file on join if it
+ doesn't contain the revisions we are joining. Probably not a
+ perfect test, but should be an improvement.
+ * sanity.sh (death): New death-file4-* tests, for bug fixed above.
+
+Wed Jan 3 01:19:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * add.c, admin.c, checkout.c, client.c, commit.c, diff.c, edit.c,
+ history.c, import.c, log.c, patch.c, release.c, remove.c, rtag.c,
+ status.c, tag.c, update.c, watch.c: In calling send_to_server,
+ pass \012 not \n. On the Mac \n is CR, not LF, and we want to
+ send LF. I didn't try to deal with whether files in CVSADM should
+ contain CR or LF--in fact there is some code in client.c which
+ reads \n from CVSADM files and passes it to send_to_server; it
+ needs to be cleaned up one way or the other.
+
+ * entries.c (Entries_Open): Don't try to close fpin twice.
+
+ * client.c (update_entries): Fix typo ("strlen (filename + 10)"
+ -> "strlen (filename) + 10").
+
+ * commit.c (checkaddfile): Remove arbitrary limit.
+
+Tue Jan 2 11:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (commit): Only pass files which were modified, added,
+ or removed to send_file_names. This has as a side effect a
+ semantic change--the up-to-date check is now skipped for other
+ files--but probably a good one, or at least not a bad one.
+ * sanity.sh (basica): New test; tests for bug fixed above.
+ * sanity.sh (187a3): Adjust for new 'cvs commit' output. Set up
+ DOTSTAR to match arbitrary text (another GNU expr bug/misfeature,
+ sigh).
+
+ * sanity.sh: Test that the commit in test 43 actually worked.
+ Merge tests basic2 and basic3 and make them independent of basic1.
+ (pass,fail): Don't insert spurious space.
+ (45.5): Fix typo in directory name.
+
+Tue Jan 2 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ Visual C++ lint:
+ * myndbm.c: Prototype write_item.
+
+Tue Jan 2 11:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ gcc -Wall lint:
+ * client.c (client_expand_modules): Pass error message not "" to error.
+ * client.c (supported_request), server.c (supported_response):
+ Return a value (gcc -Wall can't know that error doesn't return).
+ * commit.c (copy_ulist): Return a value.
+ * history.c (fill_hrec): Don't make assumptions about whether
+ time_t is "int" or "long" or what.
+ * cvs.h: Declare link_file.
+ * server.c: Include fileattr.h.
+ * server.c (server_notify): Remove unused variable val.
+ * tag.c (val_fileproc): Remove unused variable foundtag.
+
+Mon Jan 1 09:49:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.5.
+
+ * Version 1.6.4.
+
+ * filesubr.c (link_file): Add comment about link vs. copy semantics.
+
+ * cvs.h (struct vers_ts): Fix comments.
+ * commit.c (commit): Before we ask for a log message, figure out
+ what is modified and what is not and pass the information to
+ do_editor.
+ (copy_ulist,find_fileproc): New helper functions for above code.
+
+ * client.c (read_line): When writing to from_server_logfile, write
+ the \n too.
+
+ * client.c (send_files): No longer call send_file_names.
+ * client.h: Update comment.
+ * add.c, admin.c, commit.c, diff.c, edit.c, log.c, remove.c,
+ status.c, tag.c, update.c, watch.c: Call send_file_names before
+ send_files.
+ * client.c: New variables module_argc, module_argv.
+ (client_expand_modules): Set them, to arguments.
+ (client_send_expansions): Use them instead of modules_vector to
+ send arguments.
+ * sanity.sh (modules): Add test of modules -d flag.
+
+
+For older changes see ChangeLog-9395.
diff --git a/contrib/cvs/src/ChangeLog-9194 b/contrib/cvs/src/ChangeLog-9194
new file mode 100644
index 0000000..eb79efc
--- /dev/null
+++ b/contrib/cvs/src/ChangeLog-9194
@@ -0,0 +1,524 @@
+Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * subr.c (run_setup, run_args): Check USE_PROTOTYPES if defined
+ instead of __STDC__, just like cvs.h does.
+
+Thu Sep 15 00:14:58 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * main.c: rename nocvsrc to use_cvsrc, don`t read ~/.cvsrc when -H
+ has been seen
+
+Wed Sep 14 21:55:17 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * cvs.h, subr.c: use size_t for xmalloc, xrealloc, and xstrdup
+ parameters
+
+ * cvsrc.c: optimize away two calls of getenv
+
+ * commit.c, subr.c: use mode_t for file mode values (Thanks to jtc@cygnus.com)
+
+ * main.c: update copyrights in -v message
+
+Tue Sep 6 10:29:13 1994 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * hash.c (hashp): Replace hash function with one from p436 of the
+ Dragon book (via libg++'s hash.cc) which has *much* better
+ behavior.
+
+Wed Aug 17 09:37:44 1994 J.T. Conklin (jtc@cygnus.com)
+
+ * find_names.c (find_dirs): Use 4.4BSD filesystem feature (it
+ contains the file type in the dirent structure) to avoid
+ stat'ing each file.
+
+Tue Aug 16 11:15:12 1994 J.T. Conklin (jtc@cygnus.com)
+
+ * rcs.h (struct rcsnode): add symbols_data field.
+ * rcs.c (RCS_parsercsfile_i): store value of rcs symbols in
+ symbols_data instead of parsing it.
+ (RCS_symbols): New function used for lazy symbols parsing.
+ Build a list out of symbols_data and store it in symbols if it
+ hasn't been done already, and return the list of symbols.
+ (RCS_gettag, RCS_magicrev, RCS_nodeisbranch, RCS_whatbranch):
+ Use RCS_symbols.
+ * status.c: (status_fileproc): Use RCS_symbols.
+
+Thu Jul 14 13:02:51 1994 david d `zoo' zuhn (zoo@monad.armadillo.com)
+
+ * src/diff.c (diff_fileproc): add support for "cvs diff -N" which
+ allows for adding or removing files via patches. (from
+ K. Richard Pixley <rich@cygnus.com>)
+
+Wed Jul 13 10:52:56 1994 J.T. Conklin (jtc@phishhead.cygnus.com)
+
+ * cvs.h: Add macro CVSRFLPAT, a string containing a shell wildcard
+ expression that matches read lock files.
+ * lock.c (readers_exist): Reorganized to use CVSRFLPAT and to not
+ compute the full pathname unless the file matches.
+
+ * rcs.h: Add macro RCSPAT, a string containing a shell wildcard
+ expression that matches RCS files.
+ * find_names.c (find_rcs, find_dirs): Use RCSPAT.
+
+Fri Jul 8 07:02:08 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * entries.c (Register): Pass two arguments to write_ent_proc, in
+ accordance with its declaration.
+
+Thu Jun 30 09:08:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * logmsg.c (do_editor): Fix typo ("c)continue" -> "c)ontinue").
+
+Thu Jun 23 18:28:12 1994 J.T. Conklin (jtc@phishhead.cygnus.com)
+
+ * find_names.c (find_rcs, find_dirs): use fnmatch instead of
+ re_comp/re_exec for wildcard matching.
+ * lock.c (readers_exist): Likewise.
+
+Fri May 20 08:13:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * modules.c (do_module): If something is aliased to itself, print
+ an error message rather than recursing.
+
+Fri May 6 19:25:28 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * cvsrc.c (read_cvsrc): use open_file for error checking
+
+Sat Feb 26 10:59:37 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * import.c: use $TMPDIR if available, instead of relying on /tmp
+
+Mon Jan 24 19:10:03 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * update.c (joining): compare join_rev1 with NULL instead of
+ casting pointer to an int
+
+ * options.h: remove S_IWRITE, S_IWGRP, S_IWOTH macros
+
+ * logmsg.c: #if 0 around gethostbyname prototype
+
+ * hash.c (printnode), find_names.c (add_entries_proc),
+ entries.c (write_ent_proc): correct declaration for function
+ (added void *closure)
+
+ * cvs.h: header include order reorganization: First include the
+ program config headers (config.h, options.h). Then include any
+ system headers (stdio.h, unistd.h). Last, get the program
+ headers and any cvs supplied library support
+
+ * commit.c: use xstrdup instead of strdup
+
+ * cvs.h: redefined USE(var) macro; comment after an #endif
+
+ * all .c files: remove the semicolon from after the USE(var)
+
+Sat Dec 18 00:17:27 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * cvs.h: include errno.h if available, otherwise declare errno if
+ it's not somehow else defined
+
+ * commit.c (checkaddfile): remove unused file argument from
+ RCS_nodeisbranch call
+
+ * rcs.c (RCS_nodeisbranch): remove file from arguments (was unused)
+
+ * rcs.h (RCS_nodeisbranch): remove file from prototype
+
+ * main.c: don't use rcsid when printing version number (the CVS
+ version number is independent of the repository that it comes
+ from)
+
+ * hash.c (printlist, printnode): use %p to print pointers, not %x
+ (avoids gcc format warnings)
+
+ * cvs.h: define USE if GCC 2, to avoid unused variable warning
+
+ * all .c files: use USE(rcsid)
+
+ * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead
+ (COMMON_OBJECTS): define, and use in several places
+ (OBJECTS): reorder alphabetically
+
+ * hash.c (nodetypestring): handle default return value better
+
+ * modules.c (do_module): remove extra argument to ign_dir_add
+
+ * main.c (main): initialize cvs_update_env to 0 (zero)
+
+ * modules.c (do_module): return error code when ignoring directory
+ (instead of a bare return). error code should be zero here
+
+ * cvs.h: add prototypes for ignore_directory, ign_dir_add
+
+ * ignore.c: add comments about ignore_directory
+
+ * root.c (Name_Root): remove unused variables has_cvsadm and path
+
+ * checkin.c (Checkin): only use -m<message> when message is non-NULL
+
+ * cvsrc.c (read_cvsrc): make sure homeinit is never used while
+ uninitialized (could have happened if getenv("HOME") had failed)
+
+ * cvs.h: include unistd.h if available
+
+Fri Dec 17 23:54:58 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * all files: now use strchr, strrchr, and memset instead of index,
+ rindex, and bzero respectively
+
+Sat Dec 11 09:50:03 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * version.c (version_string): bump to +104z
+
+ * Makefile.in: set standard directory variables, CC, and other
+ variables needed to be able to do 'make all' in this directory
+
+ * import.c: implement -k<subst> options, for setting the RCS
+ keyword expansion mode
+
+ * all files: use PROTO() macro for ANSI function prototypes
+ instead of #ifdef __STDC__/#else/#endif around two sets of
+ declarations
+
+Thu Nov 18 19:02:51 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * add.c (add), import.c (import), commit.c (commit): change
+ xmalloc & strcpy to xstrdup.
+
+ * commit.c (remove_file): correct another static buffer problem.
+
+Wed Nov 10 15:01:34 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * recurse.c (start_recursion): directories in repository but not
+ in working directory should be added to dirlist. Fixes "update
+ -d dir" case.
+
+ * version.c (version_string): bump to +103r.
+
+ * commit.c (checkaddfile): mkdir attic only if it does not already
+ exist. comment changes. changed diagnostic about adding on a
+ branch. if a file is added on a branch, remove and replace the
+ internal representation of that rcs file.
+
+Tue Nov 9 18:02:01 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * add.c (add): if a file is being added on a branch, then say so;
+ add quotes around file names in error messages.
+
+Thu Nov 4 16:58:33 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * version.c (version_string): bump to +102r.
+
+ * recurse.c (unroll_files_proc, addfile): new files, forward
+ decls, and prototypes.
+ (recursion_frame): new struct.
+ (start_recursion): rewrite to handle the case of "file1 file2
+ dir1/file3".
+
+ * rcs.c (RCS_parsercsfile): trap and error out on the case where
+ getrcskey tells us it hit an error while reading the file.
+
+ * commit.c (lock_filesdoneproc): add comment about untrapped error
+ condition.
+
+ * hash.c (addnode): comment change.
+
+ * subr.c: add comment about caching.
+
+ * sanity.sh: updated copyright.
+
+Wed Nov 3 14:49:15 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * version.c (version_string): bump to +101r.
+
+ * hash.c (walklist): add a closure for called routines. All
+ callers, callees, and prototypes changed.
+
+ * hash.c (nodetypestring, printnode, printlist): new functions for
+ dumping lists & nodes.
+
+ * tag.c (tag_fileproc): fatal out on failure to set tag.
+
+Tue Nov 2 14:26:38 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * version.c (version_string): bump version to +99.
+
+Mon Nov 1 15:54:51 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ Change buffer allocation for check in messages from static to
+ dynamic.
+ * add.c (add): dynamically allocate message.
+ (build_entry): check (message != NULL) now that message is a
+ pointer.
+ * commit.c (got_message, commit, commit_fileproc,
+ commit_filesdoneproc, commit_direntproc): removed. Replaced by
+ (message != NULL). Dynamically allocate message.
+ * cvs.h: adjust do_editor prototype and forward decl.
+ (MAXMESGLEN): removed.
+ * import.c (import): dynamically allocate message.
+ * logmsg.c (do_editor): change return type to char *. Remove
+ message parameter. Slight optimization to algorythm for
+ removing CVSEDITPREFIX lines. Add comment about fgets lossage.
+
+ * subr.c (xmalloc): change error message to print number of bytes
+ we were attempting to allocate.
+
+Fri Oct 29 14:22:02 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * add.c (add): prevent adding a directory if there exists a dead
+ file of the same name.
+
+ * sanity.sh: update argument to diff from "+ignore-file" to
+ "--exclude=".
+
+ * Makefile.in (TAGS): extend to work from an objdir.
+
+Mon Oct 18 18:45:45 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ * tag.c, rtag.c: change the default actions to make writing over
+ existing tags harder (but not impossible)
+
+Thu Oct 14 18:00:53 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ CVS/Root changes from Mark Baushke (mdb@cisco.com)
+
+ * Makefile.in: added new file called root.c
+
+ * create_adm.c: will create CVS/Root at the same time that the
+ other CVS files are being created
+
+ * cvs.h: new CVSADM_ROOT define plus new function externs
+
+ * main.c: default to using CVS/Root contents for CVSROOT
+ if neither the environment variable or the command line
+ "-d" switch is given. If either are given, perform a
+ sanity check that this directory belongs to that repository.
+
+ * update.c: if CVS/Root does not exist, then create it
+ during an update -- this may be removed if CVS/Root becomes a
+ standard feature
+
+ * root.c: implement new functions to manipulate CVS/Root
+ [this may be integrated with other utility functions in
+ a future revision if CVS/Root becomes a standard feature.]
+
+Wed Sep 29 17:01:40 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ * patch.c (patch_fileproc): output an Index: line for each file
+
+Mon Sep 6 18:40:22 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ * cvs.h: wrap definition of PATH_MAX in #ifndef PATH_MAX/#endif
+
+Tue Aug 9 21:52:10 1994 Mark Eichin (eichin@cygnus.com)
+
+ * commit.c (remove_file): actually allocate space for the
+ filename, not just the directory.
+
+Tue Jul 6 19:05:37 1993 david d `zoo' zuhn (zoo@cygnus.com)
+
+ * diff.c: patches to print an Index: line
+
+Mon Jun 14 12:19:35 1993 david d `zoo' zuhn (zoo at rtl.cygnus.com)
+
+ * Makefile.in: update install target
+
+Tue Jun 1 17:03:05 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * Makefile.in: link cvs against libiberty
+
+Wed May 19 14:10:34 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * ignore.c: add code for keeping lists of directories to ignore.
+
+ * modules.c: new syntax for modules file, !dirname is added to
+ the list of directories to ignore
+
+ * update.c: don't process directories on the ignore list
+
+Tue Apr 6 14:22:48 1993 Ian Lance Taylor (ian@cygnus.com)
+
+ * cvs.h: Removed gethostname prototype, since it is unnecessary
+ and does not match prototype in <unistd.h> on HP/UX.
+
+Mon Mar 22 23:25:16 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * Makefile.in: rename installtest to installcheck
+
+Mon Feb 1 12:53:34 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * Makefile.in (check, installtest): set RCSBIN so that we
+ explicitly test the appropriate version of rcs as well.
+
+Fri Jan 29 13:37:35 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * version.c: bump version to +2.
+
+Thu Jan 28 18:11:34 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * import.c (update_rcs_file): if a file was dead, be sure to check
+ in the new version.
+
+ * update.c (checkout_file): if file_is_dead and we *did* have an
+ entry, scratch it.
+
+Tue Jan 26 16:16:48 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * sanity.sh: parcel into pieces for easier truncation when
+ debugging.
+
+ * update.c (checkout_file): print the "no longer pertinent"
+ message only if there was a user file.
+
+Wed Jan 20 17:08:09 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * update.c (checkout_file): remove unused variable s.
+ (join_file): remove unused variables rev & baserev. Fix a typo.
+
+ * commit.c (commit_fileproc): remove unused variable magicbranch.
+
+ * sanity.sh: bring back test 45 even though it fails. Update
+ tests against imported files.
+
+ * add.c (add_directory): move declaration of unused variable.
+
+ * Makefile.in (xxx): when building in this directory, pass CC for
+ the recursion.
+
+Mon Jan 18 13:48:33 1993 K. Richard Pixley (rich@cygnus.com)
+
+ * commit.c (remove_file): fix for files removed in trunk
+ immediately after import.
+
+ * commit.c (remove_file): initialize some variables. Otherwise we
+ end up free'ing some rather inconvenient things.
+
+Wed Jan 13 15:55:36 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * Makefile.in (check, install, installtest): use the sanity test.
+
+ * sanity.el: make into real functions and bind to sun keys.
+
+ * sanity.sh: bring back to working order. Add test for death
+ after import.
+
+Tue Dec 22 17:45:19 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * commit.c (remove_file): when checking in a dead revision to a
+ branch as we are creating the branch, do not lock the underlying
+ revision. Also free some malloc'd memory.
+
+Wed Dec 2 13:09:48 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * RCS-patches: new file.
+
+Fri Nov 27 20:12:48 1992 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ Added support for adding previously removed files, as well as
+ adding and removing files in branches.
+
+ * add.c (build_entry): add new argument, tag, so as to store in
+ Entries the per directory sticky tag under which a file is
+ added. Changed prototype and caller.
+ (build_entry): Do not prevent file additions if the file exists
+ in the Attic.
+ (add): if the file being adding was previously dead, say so, and
+ mark the Entries file with the addition.
+ * checkin.c (Checkin): adding with a tag no longer means to add,
+ then tag. Hence, remove the tagging operation.
+ * classify.c (Classify_File): if the base RCS version is dead,
+ then the file is being added. If a file being added already
+ exists in the attic, and the base RCS version is NOT dead, then
+ we have a conflict.
+ * commit.c (checkaddfile): add the list of srcfiles to calling
+ convention. Change prototype and callers.
+ (remove_file): add message and list of srcfiles to calling
+ convention. Change prototype and callers. When removing a file
+ with a tag, remove the tag only when the tag does not represent
+ a branch. Remove files by committing dead revisions in the
+ appropriate branch. When removing files from the trunk, also
+ move the RCS file into the Attic.
+ (check_fileproc): when adding, and looking for previously
+ existing RCS files, do not look in the Attic.
+ (commit_fileproc): adding files with tags now implies adding the
+ file on a branch with that tag.
+ (checkaddfile): When adding a file on a branch, in addition to
+ creating the rcs file in the Attic, also create a dead, initial
+ revision on the trunk and stub in a magic branch tag.
+ * cvs.h (joining, gca): added prototypes.
+ * rcs.c (RCS_getbranch): now global rather than static.
+ remove prototype and forward decl.
+ (parse_rcs_proc): use RCS_addnode.
+ (RCS_addnode): new function.
+ (RCS_parsercsfile): recognize the new RCS revision
+ newphrase, "dead". Mark the node for the revision.
+ (RCS_gettag): requesting the head of a file in the attic now
+ returns the head of the file in the attic rather than NULL.
+ (RCS_isbranch): use RCS_nodeisbranch.
+ (RCS_nodeisbranch): new function.
+ (RCS_isdead): new function.
+ * rcs.h (RCSDEAD): new macro for new rcs keyword.
+ (struct rcsversnode): new field to flag dead revisions.
+ (RCS_nodeisbranch, RCS_isdead, RCS_addnode): new functions,
+ new prototypes, new externs.
+ (RCS_getbranch): now global, so prototype and extern moved
+ to here.
+ * subr.c (gca): new function.
+ * update.c (join_file): add entries list to calling
+ convention. Caller changed.
+ (update): also search the Attic when joining.
+ (checkout_file): when joining, checkout dead revisions too. If
+ a file has died across an update then say so.
+ (join_file): support joins of dead files against live ones, live
+ files against dead ones, and added files. Change the semantic
+ of a join with only rev specified to mean join specified rev
+ against checked out files via the greatest common ancestor of
+ the specified rev and the base rev of the checked out files.
+ (joining): new function.
+ * vers_ts.c (Version_TS): ALWAYS get the rcs version number.
+
+ * update.c (update): write the 'C' letter for conflicts.
+
+ * cvs.h (ParseTag): remove duplicate extern.
+
+ * add.c (add_directory): do not prompt for interactive
+ verification before adding a directory. Doing so prevents
+ scripted testing.
+
+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 01:24:40 1991 K. Richard Pixley (rich at cygnus.com)
+
+ * diff.c: do not pass an empty -r option to rcsdiff.
+
+ * update.c: fix bug where return code from rcsmerge wasn't being
+ handled properly.
+
+ * main.c: "rm" and "delete" now synonyms for "remove".
+
+ * commit.c: abort if editor session fails, but remember to clear
+ locks.
+
+ * Makefile.in: remove conf.h and checkin.configured on clean.
+ infodir belongs in datadir.
+
+Thu Dec 5 22:46:03 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:47:13 1991 K. Richard Pixley (rich at sendai)
+
+ * brought Makefile.in's up to standards.text.
+
+ * fresh changelog.
+
+
+For older changes, there might be some relevant stuff in the bottom of
+the NEWS file, but I'm afraid probably a lot of them are lost in the
+mists of time.
diff --git a/contrib/cvs/src/ChangeLog-9395 b/contrib/cvs/src/ChangeLog-9395
new file mode 100644
index 0000000..c2d2111
--- /dev/null
+++ b/contrib/cvs/src/ChangeLog-9395
@@ -0,0 +1,3731 @@
+Note: this log overlaps in time with ChangeLog-9194. There was a time
+during which changes which had been merged into the official CVS
+(which produced releases such as 1.4A1 and 1.4A2) went into what has
+become ChangeLog-9194, and changes which existed only at Cygnus went
+into this file (ChangeLog-9395). Eventually the Cygnus release became
+Cyclic CVS (as it was then called), which became CVS 1.5, so probably
+all the changes in both (what are now) ChangeLog-9194 and
+ChangeLog-9395 made it into 1.5.
+
+Sun Dec 31 17:33:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * import.c (add_rev): Revert portion of 31 Aug 95 change which
+ passes -u to ci instead of using a hard link.
+ * sanity.sh (import): Add test for above-fixed bug.
+
+Sun Dec 31 16:40:41 1995 Peter Chubb <peterc@bookworm.sw.oz.au>
+ and Jim Kingdon <kingdon@cyclic.com>
+
+ * admin.c (admin_fileproc): Call freevers_ts before returning.
+
+Mon Dec 25 12:20:06 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * logmsg.c (rcsinfo_proc): initialise line and
+ line_chars_allocated so they dont cause malloc problems within
+ getline(). This was causing rcsinfo templates to not work.
+
+Sun Dec 24 01:38:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): clarify protocol.
+
+ * login.c (login): deprolixify the password prompt.
+
+Sat Dec 23 10:46:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * myndbm.h, myndbm.c (dbm_store): New function.
+ * myndbm.h (DBM): Add modified and filename fields.
+ * myndbm.c (dbm_open, dbm_close): Manipulate new fields. dbm_open
+ no longer fails if the file doesn't exist and O_CREAT is set.
+ * cvs.h (CVSROOTADM_VALTAGS): Added.
+ * tag.c, cvs.h (tag_check_valid): New function.
+ * update.c (update), checkout.c (checkout_proc), commit.c (commit),
+ diff.c (diff), patch.c (patch_proc), rtag.c (rtag_proc), tag.c (tag):
+ Call it.
+ * sanity.sh: Test for rejection of invalid tagname.
+
+Fri Dec 22 18:21:39 1995 Karl Fogel <kfogel@csxt.cs.oberlin.edu>
+
+ * client.c (start_server): don't use kerberos if authenticating
+ server was specified.
+
+Fri Dec 22 16:35:57 1995 Karl Fogel <kfogel@csxt.cs.oberlin.edu>
+
+ * login.c (login): deal with new scramble methods.
+ (get_cvs_password): same.
+
+ * server.c (check_repository_password): remove arbitrary limit on
+ line length.
+ (authenticate_connection): use a separate variable for the
+ descrambled password, now that we no longer scramble in place.
+ Set `error_use_protocol' to 1 and just use error() where used to
+ do its job inline.
+
+ * cvs.h (scramble, descramble): adjust prototype.
+
+ * scramble.c (scramble, descramble): return char *.
+
+Fri Dec 22 13:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * release.c (release): If SERVER_SUPPORT is not defined, still
+ set up arg_start_idx.
+
+ * release.c (release): When calling unedit, set argv[1] to
+ NULL (since argc is only 1).
+
+ * edit.c: Pass dosrcs 0 to all calls to start_recursion.
+ None of the fileprocs were using it, so it just slowed things
+ down and caused potentially harmful checks for rcs files.
+
+ * edit.c (send_notifications): In client case, do not readlock.
+
+Thu Dec 21 16:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ Clean up Visual C++ lint:
+ * client.c (read_line): Change input_index and result_size to size_t.
+ (update_entries): Remove unused variables buf2, size_left, size_read.
+ (handle_mode): Prototype.
+ * client.c, client.h (send_to_server, read_from_server): Change
+ len to size_t.
+ * client.c (send_to_server): Change wrtn to size_t.
+ (read_from_server): Change red to size_t.
+ * client.c, myndbm.c, edit.c, fileattr.c: Include getline.h.
+ * checkin.c, commit.c, update.c: Include fileattr.h.
+ * commit.c, update.c: Include edit.h.
+ * edit.c (onoff_filesdoneproc): Prototype.
+ (ncheck_fileproc,edit_fileproc): Change "return" to "return 0".
+ (notify_do): Cast a signed value to unsigned before comparing
+ with unsigned value.
+
+Thu Dec 21 15:24:37 1995 Karl Fogel <kfogel@occs.cs.oberlin.edu>
+
+ * client.c: don't include socket headers twice just because
+ both HAVE_KERBEROS and AUTH_CLIENT_SUPPORT are set.
+ (start_kerberos_server): if fail to connect to kerberos, print out
+ a more specific error message, mainly so pcl-cvs can know what
+ happened and not panic.
+ (start_server): don't assume sprintf() returns len
+ written (only some systems provide this); instead, have
+ send_to_server() calculate the length itself.
+ (send_modified): same.
+ (send_fileproc): same.
+ (send_file_names): same.
+
+Wed Dec 20 14:00:28 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (ignore_files): Move from here...
+ * ignore.c (ignore_files): ...to here. No longer static. Take
+ new argument PROC.
+ * cvs.h (ignore_files): Declare.
+ * client.c (send_filesdoneproc): Split off from
+ update_filesdone_proc. Pass new function send_ignproc to
+ ignore_files (to ask server about ignored file before printing
+ "?").
+ * server.c: Rename outbuf from but_to_net and take it from
+ do_cvs_command to a global. Move initialization accordingly.
+ (serve_questionable): New function.
+ (requests): Add it.
+ * update.c (update_filesdone_proc): Remove client stuff. Pass new
+ function update_ignproc to ignore_files.
+ * cvs.h (joining, do_update): Move declarations from here...
+ * update.h: ...to here.
+ * cvs.h: Include update.h.
+ * update.c, client.c: Don't include update.h
+ * ignore.c, cvs.h: New variable ign_inhibit_server, set on -I !.
+ * import.c (import): Pass -I ! to server if specified.
+ (import_descend): If server, ignore CVS directories even if -I !.
+ * update.c (update), import.c (import): Only call ign_setup before
+ argument processing; don't call it again afterwards in client case.
+ * sanity.sh (ignore): Test above-fixed bugs and other ignore behaviors.
+ (dotest): New function.
+ Move modules checkin from modules test to start, so that other
+ tests can use mkmodules without a warning message.
+
+Wed Dec 20 13:06:17 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_to_server): don't check string's length twice.
+
+Wed Dec 20 02:05:19 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): took out debugging printf's.
+ (login): Removed unused variable `p'.
+
+Wed Dec 20 00:27:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): prefix scrambled password with 'A', so we know
+ which version of scrambling was used. This may be useful in the
+ future.
+ (get_cvs_password): skip past the leading 'A'.
+ Scramble $CVS_PASSWORD before returning it.
+
+ * scramble.c: made this work.
+
+Tue Dec 19 17:45:11 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (cvs_password): new static var, init to NULL.
+ (login): scramble() the password before using it.
+ Verify the password with the server.
+ Check CVSroot more carefully to insure that it is
+ "fully-qualified".
+ (get_cvs_password): if cvs_password is not NULL, just return it.
+ Never prompt -- just tell user why failed, then exit.
+ Try CVS_PASSWORD environment variable first.
+ (construct_cvspass_filename): try CVS_PASSFILE environment
+ variable first.
+
+ * client.h (connect_to_pserver): update prototype.
+
+ * client.c (cvsroot_parsed): new static var.
+ (parse_cvsroot): set `cvsroot_parsed' to 1 when done.
+ (connect_to_pserver): return int.
+ Take `verify_only' arg. If it is non-zero, perform password
+ verification with the server and then shut down the connection and
+ return.
+ Call parse_cvsroot() before doing anything.
+
+ * server.c (authenticate_connection): deal with verification
+ requests as well as authorization requests.
+ descramble() the password before hashing it.
+
+ * cvs.h: prototype scramble() and descramble().
+
+ * Makefile.in: build scramble.o.
+
+ * scramble.c: new file, provides trivial encoding but NOT real
+ encryption.
+
+Mon Dec 18 20:57:58 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): don't insert extra newlines. They were
+ harmless, but confusing.
+
+Mon Dec 18 15:32:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * hash.c, hash.h (findnode_fn): New function.
+ * hash.c (hashp): Tweak hash function so that findnode_fn works.
+ * update.c (ignore_files): Call findnode_fn, not findnode.
+
+Mon Dec 18 09:34:56 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * myndbm.c: Remove arbitrary limit.
+
+ * client.c: Fix comment--Windows 95 requires NO_SOCKET_TO_FD, not
+ Windows NT.
+
+Mon Dec 18 01:06:20 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (server_sock): replaces `server_socket'.
+ (start_kerberos_server): added FIXME comment about how
+ NO_SOCKET_TO_FD is not dealt with in the kerberos case.
+ (connect_to_pserver): deal with NO_SOCKET_TO_FD case.
+ (read_line): deal with NO_SOCKET_TO_FD case.
+ (read_from_server): deal with NO_SOCKET_TO_FD case.
+ (send_to_server): deal with NO_SOCKET_TO_FD case.
+ (get_responses_and_close): deal with NO_SOCKET_TO_FD case.
+
+ * client.c (send_to_server): error check logging.
+ (start_server): error check opening of logfiles.
+ (read_from_server): error check logging.
+ (read_line): use fwrite() to log, & error_check it.
+ Don't log if using socket style, because read_from_server()
+ already logged for us.
+
+Mon Dec 18 00:52:26 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (use_socket_style): new static var, init to 0.
+ (server_socket): new static var.
+ (connect_to_pserver): don't deal with logging here.
+ Caller changed.
+ (start_kerberos_server): don't deal with logging here either.
+ Caller changed.
+
+Mon Dec 18 00:40:46 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_modified): don't error-check `to_server';
+ send_to_server() does that now.
+
+Mon Dec 18 00:19:16 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (get_cvs_password): Init `linebuf' to NULL.
+ free() `linebuf' and reset it for each new line.
+ (login): same as above.
+
+ * client.c: Removed all the varargs prototyping gunk.
+ (to_server, from_server): make these static.
+ (from_server_logfile, to_server_logfile): new vars.
+ (start_server): init above two new vars to NULL.
+ (send_to_server): return void.
+ Correct bug in which amount to be written would be too high if the
+ loop ever ran more than once.
+ Log to `to_server_logfile' if it's non-NULL.
+ (read_from_server): new func, does raw reading from server.
+ Logs to `from_server_logfile' if it's non-NULL.
+ (update_entries): just use read_from_server() instead of looping
+ to fread() directly from `from_server'.
+ (read_line): Log to `from_server_logfile' if it's non-NULL.
+
+ * client.h: send_to_server() returns void now.
+ (read_from_server): prototype.
+
+Sun Dec 17 19:38:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (checkout_proc), client.c, lock.c (readers_exist),
+ login.c, modules.c (cat_module, do_module): Remove arbitrary limits.
+
+ * client.c (send_to_server): Fix typo (NULL -> '\0').
+ (get_responses_and_close): Set server_started to 0 instead of
+ setting to_server and from_server to NULL.
+ * client.c: Make to_server and from_server static.
+
+Sun Dec 17 17:59:04 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.h (to_server, from_server): don't declare these anymore.
+ They are now entirely private to client.c (and in fact will go
+ away soon there too).
+
+Sun Dec 17 15:40:58 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.h: update prototype of send_to_server().
+
+ * client.c, watch.c, update.c, tag.c, status.c, rtag.c, remove.c,
+ release.c, patch.c, log.c, import.c, history.c, edit.c, diff.c,
+ commit.c, client.c, checkout.c, admin.c, add.c:
+ Convert all send_to_server() calls that used formatting to send
+ pre-formatted strings instead. And don't error check
+ send_to_server(), because it does its own error checking now.
+
+ * client.c (send_to_server): don't use vasprintf(), just fwrite a
+ certain number of bytes to the server. And do error checking
+ here, so our callers don't have to.
+ (send_arg): use send_to_server() instead of putc()'ing
+ directly to `to_server'.
+
+Sun Dec 17 14:37:52 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * options.h.in (AUTH_CLIENT_SUPPORT, AUTH_SERVER_SUPPORT):
+ Define to 1 but leave commented out, instead of #undef'ing them.
+ This treats them like everything else in this file.
+
+ * client.c: define server_started, init to 0.
+ (start_server): set server_started to 1.
+
+ * client.h: declare `server_started', extern.
+ AUTH_CLIENT_SUPPORT moved here from cvs.h.
+
+ * cvs.h: moved AUTH_CLIENT_SUPPORT stuff to client.h.
+
+ * edit.c (notify_check): use new var server_started.
+
+Sun Dec 17 00:44:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (get_responses_and_close): Really stop ignoring ECHILD
+ errors. The Nov 30 1995 change claimed to do this, but the code
+ was not actually changed.
+
+ * update.c (ignore_files): Revert H.J. Lu change; it was wrong for
+ directories and sometimes looked at sb.st_mode when it wasn't set.
+ * import.c (import_descend): Revert H.J. Lu change; it was wrong
+ for directories and the extra lstat call was an unnecessary
+ performance hit.
+ * sanity.sh (import): Add test for the second of these two bugs.
+
+Sat Dec 16 17:26:08 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (send_to_server): Remove arbitrary limit. Also remove
+ !HAVE_VPRINTF code; all relevant systems have vprintf these days.
+
+Sat Dec 16 21:35:31 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * checkout.c (checkout): use send_to_server() now.
+
+Sat Dec 16 21:18:16 1995 H.J. Lu (hjl@gnu.ai.mit.edu)
+ (applied by kfogel@cyclic.com)
+
+ * import.c (import_descend): We ignore an entry if it is
+ 1. not a file, nor a link, nor a directory, or
+ 2. a file and on the ignore list.
+
+ * update.c (ignore_files): We ignore any thing which is
+ 1. not a file, or
+ 2. it is a file on the ignore list.
+
+Sat Dec 16 00:14:19 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_to_server): corrected comment.
+
+ * client.h: prototype new func send_to_server().
+
+ * add.c, admin.c, client.c, commit.c, diff.c, edit.c, history.c,
+ import.c, log.c, patch.c, release.c, remove.c, rtag.c, status.c,
+ tag.c, update.c, watch.c:
+ Use send_to_server() instead of writing directly to to_server.
+
+ * client.c: conditionally include the right stuff for variable arg
+ lists.
+ (send_to_server): new func.
+
+Fri Dec 15 23:10:22 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * error.c: expanded comments.
+
+ * client.c (connect_to_pserver): verbosify errors.
+ (connect_to_pserver): use send() and recv(), not write() and
+ read(). Sockets are not file descriptors on all systems.
+
+Fri Dec 15 22:36:05 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (connect_to_pserver): oops, removed old debugging
+ printf.
+
+Fri Dec 15 18:21:16 1995 Karl Fogel (kfogel@floss.cyclic.com)
+
+ * client.c (auth_server_port_number): don't call htons();
+ init_sockaddr() does that for us.
+ (init_sockaddr): zero the sockadder_in struct before doing
+ anything with it. IBM TCP/IP docs recommend this, and it can't
+ hurt.
+
+Fri Dec 15 15:21:53 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (connect_to_pserver): new var `port_number', initialize
+ with new func auth_server_port_number() and pass to
+ init_sockaddr().
+ (auth_server_port_number): new func. Right now it just returns
+ `htons (CVS_AUTH_PORT)'. We'll probably add the ability to
+ specify the port at run time soon, anyway, so having this function
+ will make that easier.
+
+Wed Dec 6 18:08:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Add CVSREP.
+ * find_names.c (find_dirs): Skip CVSREP too.
+ * fileattr.h, fileattr.c: New files, to manipulate file attributes.
+ * hash.c (nodetypestring), hash.h (enum ntype): Add FILEATTR.
+ * hash.c, hash.h (list_isempty): New function.
+ * recurse.c (do_recursion): Call fileattr_startdir before
+ processing files in a directory and fileattr_write and
+ fileattr_free (after files, before recursing).
+ * watch.c, watch.h: New files, to handle notification features.
+ * edit.c, edit.h: New file, to handle new read-only checkout features.
+ * client.c, server.c: Add "Mode" request, to change the mode of a file
+ when it is checked in.
+ * main.c (cmds): Add "watch", "edit", "unedit", "watchers", "editors".
+ * main.c: Split command help from usg into new variable cmd_usage,
+ which.
+ (main): Add --help-commands option to print out cmd_usage.
+ * cvs.h: Declare watch, edit, unedit, watchers, editors.
+ * client.c, client.h: Add client_watch, client_edit, client_unedit,
+ client_watchers, client_editors.
+ * client.c, server.c: Add notification stuff.
+ * update.c (checkout_file, patch_file), checkin.c (Checkin): Check
+ _watched attribute when deciding read-only or read-write.
+ * commit.c (checkaddfile): Call fileattr_newfile to set attributes
+ on newly created files.
+ * release.c (release):
+ * cvs.h: Add CVSADM_NOTIFY and CVSADM_NOTIFYBAK.
+ * recurse.c (do_recursion): Call notify_check.
+ * commit.c (commit_fileproc): Call notify_do after committing file.
+ * client.c (get_responses_and_close): Set to_server and from_server
+ to NULL so that it is possible to tell whether we are speaking to
+ the server.
+ * cvs.h: Add CVSROOTADM_NOTIFY.
+ * mkmodules.c (main): Add CVSROOTADM_NOTIFY to filelist.
+ * Makefile.in (SOURCES,OBJECTS,HEADERS): Add new files mentioned above.
+ * lock.c, cvs.h (lock_tree_for_write, lock_tree_cleanup): New
+ functions, taken from old commit.c writelock code. As part of
+ this, fsortcmp and lock_filesdoneproc go from commit.c to lock.c.
+ So does locklist but it gets renamed to lock_tree_list.
+ * commit.c: Use lock_tree_*.
+
+Fri Dec 15 10:37:00 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * tag.c (tag_usage): Added -r and -D flags to usage string.
+ (tag): Detect when user specifies both -r and -D arguments.
+ Pass -r and -D arguments to server.
+
+Thu Dec 14 11:56:13 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (start_rsh_server): use RSH_NEEDS_BINARY_FLAG to
+ conditionalize "-b" option to "rsh".
+
+ * run.c (filter_stream_through_program): document return value and
+ error behavior.
+
+ * client.c (filter_through_gunzip): pass the supposedly
+ superfluous "-d" option to gunzip, to avoid stimulating what seems
+ to be an argument-passing bug in spawn() under OS/2 with IBM
+ C/C++. Yucko.
+
+Wed Dec 13 20:08:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * options.h.in (RCSBIN_DFLT): Recommend specifying -b in
+ inetd.conf for pserver. That is a pretty good solution.
+
+Wed Dec 13 18:29:59 1995 Preston L. Bannister <pbannister@ca.mdis.com>
+ and Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_modified): make sure that vers and vers->options
+ are non-NULL before strcmp()'ing them with "-kb".
+ Initialize `bin' near where it is used, not at beginning of
+ function.
+ (update_entries): make sure `options' is non-NULL before
+ strcmp()'ing with "-kb".
+ Initialize `bin' near where it is used, not at beginning of
+ function.
+
+Tue Dec 12 18:56:38 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * options.h.in (RCSBIN_DFLT): document the probable need for this
+ to be set in the authenticating server.
+
+Tue Dec 12 11:56:43 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (expand_proc): If mfile is non-NULL, return it too as
+ part of the expansion.
+ * sanity.sh (modules): Add tests for above-fixed bug.
+
+Mon Dec 11 21:39:07 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * dog.c (flea_bath): Take `suds' arg.
+ All collars changed.
+
+Mon Dec 11 15:58:47 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): if client password file doesn't exist, create
+ it, duh.
+
+ * main.c (main): die if CVSroot has access-method but no
+ username.
+
+ * root.c: added some comments.
+
+ * main.c: removed all code pertaining to the "-a" option. We
+ specify access-method in CVSroot now.
+
+ * client.c (parse_cvsroot): new var, `access_method'. If CVSroot
+ is prepended with an access method (i.e.,
+ ":pserver:user@host:/path"), then handle it.
+
+ * login.c (login): use || when checking if CVSroot is "fully
+ qualified".
+ Prepend ":pserver:" before writing to ~/.cvspass.
+ (get_cvs_password): Take no parameters; we'll just use CVSroot to
+ get the password.
+
+Mon Dec 11 12:43:35 1995 adamg <adamg@microsoft.com>
+
+ * error.c, client.c, remove.c, main.c: Add explicit casts for some
+ function pointers to remove warnings under MS VC.
+ * main.c (main): remove use of NEED_CALL_SOCKINIT in favor of the
+ more generic INITIALIZE_SOCKET_SUBSYSTEM. Note that the code assumes
+ that if INITIALIZE_SOCKET_SUBSYSTEM() returns, socket subsystem
+ initialization has been successful.
+
+Sat Dec 9 22:01:41 1995 Dan O'Connor <doconnor@tii.com>
+
+ * commit.c (check_fileproc): pass RUN_REALLY flag to run_exec,
+ because it's okay to examine the file with noexec set.
+
+Sat Dec 9 20:28:01 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (update_entries): new var, `bin, init to 0.
+ Use it in determining whether to convert the file.
+ (send_modified): same as above.
+
+Fri Dec 8 17:47:39 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (downcase_string): removed.
+ (check_repository_password): don't deal with case-insensitivity
+ anymore.
+
+ * options.h.in (CVS_PASSWORDS_CASE_SENSITIVE): deleted this. No
+ need for it anymore.
+
+Thu Dec 7 21:08:39 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (check_repository_password): when checking for false
+ prefix-matches, look for ':', not '@'. Duh.
+
+Thu Dec 7 18:44:51 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * options.h.in (CVS_PASSWORDS_CASE_SENSITIVE): replaces
+ CVS_PASSWORDS_CASE_INSENSITIVE; passwords are now insensitive by
+ default. Expanded explanatory comment.
+
+ * login.c (get_cvs_password): Use memset(), not bzero(). I
+ botched this change earlier.
+
+ * server.c (check_repository_password): no need to check
+ xmalloc()'s return value.
+ (check_repository_password): check for false prefix-matches (for
+ example, username is "theo" and linebuf contains user
+ "theocracy").
+
+Thu Dec 7 14:49:16 1995 Jim Meyering (meyering@comco.com)
+
+ * filesubr.c (isaccessible): Rename from isaccessable.
+ Update callers.
+ * cvs.h: Update prototype.
+ * main.c (main): Update callers.
+ * server.c (main): Update callers.
+
+Thu Dec 7 12:50:20 1995 Adam Glass <glass@NetBSD.ORG>
+
+ * cvs.h: "isaccessible" is the correct spelling.
+ Also add "const" to second arg to make prototype match
+ declaration.
+
+Thu Dec 7 11:06:51 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c, login.c: memset() instead of bzero().
+
+Thu Dec 7 00:08:53 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): document server's side of
+ the Authentication Protocol too.
+
+ * client.c (connect_to_pserver): when printing out "unrecognized
+ response", also print out the offending response.
+
+ * server.c (check_password): take `repository' arg too now.
+ Call check_repository_password() before checking /etc/passwd.
+ (check_repository_password): new func.
+
+ * options.h.in (CVS_PASSWORDS_CASE_INSENSITIVE): new define, unset
+ by default.
+
+Wed Dec 6 18:51:16 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (check_password): If user has a null password, then
+ return 1 if arg is also null.
+ Reverse sense of return value. Caller changed.
+
+Wed Dec 6 14:42:57 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (check_password): new func.
+ (authenticate_connection): call above new func.
+
+ * login.c (login): use construct_cvspass_filename().
+ If CVSroot is not "fully-qualified", then insist the user qualify
+ it before going on.
+ (get_cvs_password): fleshed out. Now reads from ~/.cvspass, or
+ prompts if no appropriate password found.
+ (construct_cvspass_filename): new func.
+
+ * server.c (authenticate_connection): send ACK or NACK to client.
+
+ * client.c (connect_to_pserver): check for ACK vs NACK response
+ from server after sending authorization request.
+
+ * login.c (get_cvs_password): new func.
+
+ * client.c (connect_to_pserver): use new func get_cvs_password().
+ Prototype it at top of file. Hmmm.
+
+Wed Dec 6 13:29:22 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c: same as below (AUTH_SERVER_SUPPORT).
+
+ * main.c: same as below (AUTH_SERVER_SUPPORT where appropriate).
+
+ * login.c: same same as below.
+
+ * cvs.h: same as below.
+
+ * client.c: use AUTH_CLIENT_SUPPORT, not CVS_LOGIN.
+
+ * options.h.in (AUTH_CLIENT_SUPPORT, AUTH_SERVER_SUPPORT): these
+ replace CVS_LOGIN.
+
+Wed Dec 6 00:04:58 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): expanded comment.
+
+Tue Dec 5 23:37:39 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (connect_to_pserver): read password from prompt for
+ now.
+
+ * server.c (authenticate_connection): if the password passes
+ muster, then don't abort.
+
+Tue Dec 5 22:46:37 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * subr.c (strip_trailing_newlines): new func.
+
+ * client.c (connect_to_pserver): took out print statements.
+
+ * server.c (authenticate_connection): removed print statments.
+ Use new func strip_trailing_newlines() to purify `repository',
+ `username', and `password'.
+ Run a primitive password check, just for testing.
+
+ * client.c (connect_to_pserver): use CVS_AUTH_PORT.
+ Take tofdp, fromfdp, and log args. Caller changed.
+ (get_responses_and_close): either kerberos and CVS_LOGIN might
+ have one fd for both directions, so adjust #ifdef accordingly.
+
+ * cvs.h (CVS_AUTH_PORT): new define, default to 2401.
+ Prototype strip_trailing_newlines().
+
+Tue Dec 5 16:53:35 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): new func.
+
+ * client.c (init_sockaddr): func moved here from login.c.
+ (connect_to_pserver): same as above. Take no args, now.
+ Include <sys/socket.h>, <netinet/in.h>, <netdb.h>, if CVS_LOGIN.
+
+ * cvs.h: Declare use_authenticating_server, as extern int.
+ Declare connect_to_pserver().
+
+ * main.c (main): call authenticate_connection(). Removed testing
+ code.
+ Add 'a' to the short-option string in the getopt() call.
+
+ * login.c (connect_to_pserver): moved to client.c.
+
+Tue Dec 5 16:01:42 1995 Peter Chubb <peterc@bookworm.sw.oz.au>
+ (patch applied by Karl Fogel <kfogel@cyclic.com>)
+
+ * update.c (join_file): if vers->vn_user is "0", file has been
+ removed on the current branch, so print an error and return.
+
+Mon Dec 4 14:27:42 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.3.
+
+Mon Dec 4 16:28:25 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * release.c (release): add return (0) as last line
+
+ * cvs.h: declare program_path
+
+ * main.c define program_path
+ (main): set program_path
+
+ * release.c (release): use program_path for update_cmd
+
+Mon Dec 4 11:22:42 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.2.
+
+Sun Dec 3 20:02:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.h (struct rcsnode), rcs.c (freercsnode): Add expand field.
+ * rcs.h (RCSEXPAND): New #define.
+ * rcs.c (RCS_reparsercsfile): Record keyword expansion in expand
+ field of struct rcsnode.
+ * update.c (checkout_file): Set keyword expansion in Entries file
+ from rcs file if there is nowhere else to set it from.
+ * client.c (send_modified, update_entries) [LINES_CRLF_TERMINATED]:
+ If -kb is in effect, don't convert.
+
+ * update.c (update_file_proc), commit.c (check_fileproc),
+ rcscmds.c (RCS_merge): Direct stdout to DEVNULL rather than
+ passing -s option to grep. This avoids trouble with respect to
+ finding a grep which support -s and whether we should use the (GNU
+ grep) -q option if it exists.
+ * options.h.in: Change "@ggrep_path@" to "grep".
+
+Fri Dec 1 11:53:19 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * rcs.c (RCS_gettag): new parameter return_both force return both
+ tags: the symbolic and the numeric one.
+ (RCS_getversion): new parameter return_both is forwarded to
+ RCS_gettag.
+
+ * rtag.c, tag.c, commit.c, patch.c, update.c: pass 0 as additional
+ last parameter to RCS_getversion and RCS_gettag
+
+ * rcs.h (RCS_gettag): new parameter return_both.
+ (RCS_getversion): new parameter return_both.
+
+ * cvs.h (struct vers_ts): add vn_tag slot for symbolic tag name
+
+ * vers_ts.c (Version_TS): call RCS_getversion with 1 for
+ return_both and split output into vn_rcs and vn_tag
+ (freevers_ts): free vn_tag
+
+ * update.c (checkout_file): use vn_tag instead of vn_rcs when
+ calling 'rcs co' to allow rcs expansion of :$Name :
+
+Thu Nov 30 20:44:30 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (get_responses_and_close): undo previous change
+ regarding waitpid(). The problem has been solved by modifying
+ os2/waitpid.c instead of its callers.
+
+Thu Nov 30 16:37:10 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c: All these changes are for OS/2, which will no longer have
+ a separate client.c:
+ (start_kerberos_server): new func, contains code that
+ used to be in start_server().
+ (start_server): moved kerberos code to above function, reorganized
+ the rest. Added authentication clause.
+ (call_in_directory): test errno against EACCESS, if EACCESS is
+ defined (this is for OS/2's oddball mkdir).
+ (change_mode): don't set execute permission on anything if
+ EXECUTE_PERMISSION_LOSES is defined.
+ (get_responses_and_close): if START_RSH_WITH_POPEN_RW, then use
+ pclose() instead of fclose().
+ If waitpid errors with ECHILD, don't die. This is okay.
+ (start_rsh_server): alternate definition if
+ START_RSH_WITH_POPEN_RW.
+
+ * main.c: [all these changes conditional on CVS_LOGIN: ]
+ Don't prototype connect_to_pserver, don't enter it in cmds[]
+ (actually, it was never in there, I don't know why my previous
+ change said it was).
+ (use_authenticating_server): new global var.
+ (main): if "-a", then set above new var to TRUE.
+ (usg): document "-a" option.
+
+Wed Nov 29 12:55:10 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c: Prototype connect_to_pserver(), and enter it in cmds[].
+ (main): test some extremely primitive authentication.
+
+ * login.c: Include <sys/socket.h>
+ (connect_to_pserver): new func.
+ (init_sockaddr): new func.
+
+Mon Nov 20 14:07:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (TAGFILES): Separate out from DISTFILES, for C code.
+ (TAGS,tags): Use TAGFILES not DISTFILES.
+
+Sun Nov 19 11:22:43 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * recurse.c (do_recursion): Don't call server_pause_check if there
+ are writelocks around. Revise comment to reflect fact we are no
+ longer relying on a writelock'd operations being "unable" to
+ generate enough data to pause.
+
+Sun Nov 19 10:04:50 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * server.c, server.h, options.h.in: Implement hooks for doing
+ simple flow control on the server to prevent VM exhaustion on a
+ slow network with a fast server.
+ * recurse.c: Call the flow control check at a convenient location
+ while no locks are active. This is a convenience tradeoff against
+ accurate flow control - if you have a large directory it will all
+ be queued up, bypassing the flow control check until the next
+ directory is processed.
+
+Sat Nov 18 16:22:06 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c, update.c, vers_ts.c, server.c, rcs.c, lock.c,
+ ignore.c, entries.c, diff.c, commit.c, checkin.c:
+ Use new macro `existence_error', instead of comparing errno to
+ ENOENT directly.
+
+Fri Nov 17 14:56:12 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (start_server): removed alternate version of this func,
+ since os2/client.c will now be used under OS/2.
+
+Thu Nov 16 22:57:12 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (start_server): ifdef HAVE_POPEN_RW, use a different
+ version of start_server(). This is maybe not the cleanest cut to
+ make, but it's better than mucking around with yet more #ifdefs in
+ the middle of the old start_server() function. Once things are
+ up, I may reposition this code.
+
+Wed Nov 15 15:33:37 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): ifdef NEED_CALL_SOCKINIT, then call SockInit().
+ Only OS/2 needs this initialization.
+
+Tue Nov 14 18:54:01 1995 Greg A. Woods <woods@most.weird.com>
+
+ * patch.c:
+ - fix orientation of test for result of getline() call
+ - use fputs() not printf() when just copying file out
+
+ * cvsbug.sh:
+ - add space after #!
+ - new rcs id
+ - allow version to be edited by Makefile.
+
+ * Makefile.in:
+ - make Makefile a dependent of all (this might not be perfect, but
+ it at least gives you a chance to catch up on the second
+ go-around).
+ - filter cvsbug.sh in a manner similar to cvsinit.sh to get the
+ version number set from version.c
+
+Tue Nov 14 13:28:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Call old log file check.plog, not check.olog.
+
+ * sanity.sh: Convert remaining tests from old-style ('***' on fail
+ and nothing on pass), to new-style (FAIL on fail and PASS on pass).
+
+ * sanity.sh: Fix ability to run only some of the tests (always run
+ tests 1-4.75 to set up repository, document better how it works).
+
+ * sanity.sh: Change "completed successfully" to "completed" in
+ message--many tests, but not all, exit if they fail.
+
+Tue Nov 14 15:10:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * sanity.sh: test 63 doesn't work and probably can't
+
+Tue Nov 14 12:22:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * sanity.sh: many minor tweaks:
+ - make the optional arguments almost work
+ - use a function 'directory_cmp' instead of 'diff -r'
+ - fix up a few more tests that weren't working....
+
+Mon Nov 13 07:33:55 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h: ifdef USE_OWN_POPEN, #include "popen.h". Only OS/2 has
+ its own popen()/pclose() right now.
+
+Mon Nov 13 04:06:10 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h: conform to 80 column standard (yes, I'm a pedant).
+
+Sat Nov 11 13:45:13 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (process_prune_candidates): use unlink_file_dir() to
+ remove the directory, instead of invoking "rm" via run_exec().
+
+Fri Nov 10 14:38:56 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): removed "#define KF_GETOPT_LONG 1", since that
+ change is no longer in testing.
+
+Thu Nov 9 20:32:12 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * release.c (release): Use Popen(), not popen().
+
+Wed Nov 8 10:20:20 1995 Jim Meyering (meyering@comco.com)
+
+ * entries.c (ParseTag): Remove dcl of unused local.
+
+ * patch.c: Include getline.h.
+
+Wed Nov 8 11:57:31 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * options.h.in: add configuration option STEXID_SUPPORT (default
+ is off i.e. old semantics)
+
+ * filesubr.c (isaccessable): new function. Checks access-rights
+ for files like access(), but is getxid-safe. Falls back to
+ access() if SETXID_SUPPORT is not enabled.
+ (isfile): replace stat() by isaccessable(file, F_OK)
+ (isreadable): replace access() by isaccessable()
+ (iswritable): ditto
+ (make_directory): rename local variable buf to sb
+
+ * cvs.h: add prototype for new function isaccessable.
+
+ * server.c (serve_root): replace access() by isaccessable()
+
+ * cvsrc.c (read_cvsrc): replace access() by isreadable()
+
+ * main.c (main): replace access() by isaccessable()
+
+Wed Nov 8 10:22:41 1995 Greg A. Woods <woods@most.weird.com>
+
+ * entries.c (fgetentent): change definition to static to match the
+ declaration at the top of the file
+
+Tue Nov 7 16:59:25 1995 J.T. Conklin <jtc@lestat.cygnus.com>
+
+ * rcs.c (RCS_getbranch, RCS_getdate, RCS_getrevtime, RCS_gettag,
+ RCS_getversion, RCS_head): Use assert() instead of attempting to
+ "do the right thing" with a bogus RCSNode argument.
+
+Mon Nov 6 14:24:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c: Remove ctime define. It is just asking for trouble.
+
+Mon Nov 6 11:58:26 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * vers_ts.c: ifdef ctime, undef it before redefining it. It is a
+ macro on some systems.
+
+ * lock.c: don't prototype ctime() here. (See note below about
+ fgetentent() in entries.c.)
+
+Sun Nov 5 16:06:01 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * entries.c (fgetentent): don't prototype ctime here; we include
+ cvs.h, which includes system.h, which includes <time.h>
+ unconditionally (either as <time.h> or <sys/time.h>). Anyway, IBM
+ C/C++ chokes on mid-function, or even mid-file, prototypes. Sigh.
+
+Thu Nov 2 21:51:04 1995 Dan Wilder <dan@gasboy.com>
+
+ * rtag.c (rtag): Fix typo ("-T" -> "-F").
+
+Tue Oct 31 19:09:11 1995 Dan Wilder <dan@gasboy.com>
+
+ * diff.c (diff_dirproc): just return R_SKIP_ALL if dir not exist.
+ (diff_file_nodiff): don't complain if file doesn't exist, just
+ ignore.
+
+Tue Oct 31 09:25:10 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * sanity.sh: Use absolute pathname for mkmodules.
+
+Sat Oct 28 01:01:41 1995 Jim Meyering (meyering@comco.com)
+
+ * entries.c (ParseTag): Use getline instead of fgets.
+
+Fri Oct 27 13:44:20 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h: do nothing about alloca ifdef ALLOCA_IN_STDLIB. I am
+ rather suspicious of this solution, and will not be surprised to
+ find out that there's a Right Way to handle this situation ("this
+ situation" being that OS/2 simply declares alloca in <stdlib.h>).
+ Suggestions are welcome; see src/cvs.h and lib/system.h to see why
+ I was getting a conflict in the first place.
+
+Wed Oct 25 16:03:20 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * cvs.h (struct entnode): Add user field.
+ * entries.c (fputentent): New function, write entries line.
+ (write_ent_proc): Call fputentent to write entries line.
+ (Entnode_Create): New function, construct new Entnode.
+ (Entnode_Destroy): New function, destruct old Entnode.
+ (AddEntryNode): Changed to take an Entnode argument instead of
+ separate user, version, timestamp, etc. arguments.
+ (fgetentent): Changed to return Entnode.
+ (struct entent, free_entent): Removed.
+
+Wed Oct 25 12:44:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * admin.c (admin): Don't rely on ANSI C string concatenation;
+ SunOS 4.1.3 /bin/cc doesn't support it.
+
+Tue Oct 24 22:34:22 1995 Anthony J. Lill <ajlill@ajlc.waterloo.on.ca>
+
+ * import.c (expand_at_signs): Check errno as well as return value
+ from putc. Some systems bogusly return EOF when successfully
+ writing 0xff.
+
+Tue Oct 24 14:32:45 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * admin.c (admin): use getcaller() instead of getpwuid
+
+ * subr.c (getcaller): prefer getlogin() to $USER and $LOGNAME
+ (especially useful for NT where getuid always returns 0)
+
+Tue Oct 24 06:22:08 1995 Jim Meyering (meyering@comco.com)
+
+ * cvsrc.c (read_cvsrc): Use getline instead of fgets.
+ * patch.c (patch_fileproc): Use getline instead of fgets.
+
+ * entries.c (fgetentent): Use getline instead of fgets.
+ Use xmalloc to allocate space for each returned entry.
+ Since LINE is no longer static, save it in struct entent.
+ (struct entent): New member, line.
+ (free_entent): New function.
+ (Entries_Open): Call it after each call to fgetentent.
+
+Tue Oct 24 11:13:15 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.h: Declare valloc again, but this time with the right
+ signature (also changed in libs/valloc.c)
+
+Mon Oct 23 12:17:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * logmsg.c (do_editor): Check for errors from stdio calls.
+
+Mon Oct 23 12:37:06 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Don't declare valloc. Some systems (e.g. linux) declare
+ it in stdlib.h in a conflicting way.
+
+Mon Oct 23 08:41:25 1995 Jim Meyering (meyering@comco.com)
+
+ * commit.c (commit_filesdoneproc): Use getline instead of fgets.
+
+ * logmsg.c (do_editor): Use getline instead of fgets.
+ (rcsinfo_proc): Likewise.
+
+ * logmsg.c (do_editor): Lose if fclose of temp file output
+ stream fails.
+
+Mon Oct 23 11:59:41 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.h: add valloc declaration
+
+ * server.h: add server_cleanup prototype
+
+ * server.c: remove server_cleanup prototype
+
+ * mkmodules.c (server_cleanup): fix parameter type
+
+ * server.c: encapsulate wait_sig in #ifdef sun (it's only used in
+ code which is also encapsulated in #ifdef sun)
+
+ * rcscmds.c (RCS_deltag, RCS_lock): add definition of noerr
+ parameter
+
+ * error.c: include cvs.h instead of config.h, add USE(rcsid)
+
+ * error.c (error): fix parameter type
+
+ * update.c (join_file): encapsulate recent changes from garyo
+ within #ifdef SERVER_SUPPORT
+
+Sun Oct 22 13:47:53 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * client.c (update_entries): Fix memory leak; free mode_string and
+ file_timestamp.
+ (send_fileproc): Fix memory leak; call freevers_ts before exiting.
+
+ * module.c (do_module): Partially fix memory leak; added
+ variable so that the address of memory allocated by line2argv
+ is retained, but comment out the call to free_names. Freeing
+ the vector at that point loses because some of the elements
+ may be used later in the function.
+ (cat_module): fix memory leak.
+
+ * recurse.c (start_recursion): Fix memory leak; free return
+ value of Name_Repository after it has been used.
+
+Sat Oct 21 23:24:26 1995 Jim Meyering (meyering@comco.com)
+
+ * client.c (send_modified) [LINES_CRLF_TERMINATED]: Comment text
+ after #endif.
+
+Fri Oct 20 14:41:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Add test 87a, to test for bug fixed by garyo in
+ change below.
+
+Fri Oct 20 10:59:58 1995 Gary Oberbrunner <garyo@darkstar.avs.com>
+
+ * update.c (join_file): send file back to client even if no
+ conflicts were detected, by calling Register().
+
+Fri Oct 20 10:46:45 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * lock.c: Add prototype for Check_Owner
+
+Thu Oct 19 16:38:14 1995 Jim Meyering (meyering@comco.com)
+
+ * lock.c (Check_Owner): Declare function `static int'.
+
+Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * expand_path.c (expand_variable): Fix typo ('*'->'(').
+
+Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (commit_filesdoneproc): Check for errors from fopen,
+ fgets, and fclose.
+
+ * rcscmds.c (RCS_merge): Remove comment about rcsmerge -E.
+ Hacking CVS was never a very good solution; the situation is fixed
+ in RCS 5.7, and is documented in ../INSTALL.
+
+Thu Oct 19 15:06:15 1995 Jim Meyering (meyering@comco.com)
+
+ * filesubr.c (xchmod): Parenthesize arithmetic in operand of |
+ to placate gcc -Wall.
+
+ * expand_path.c (expand_path): Parenthesize assignments used as
+ truth values to placate gcc -Wall.
+
+ * commit.c (checkaddfile): Remove dcls of unused variables.
+ * lock.c (unlock): Remove dcl of unused variable.
+
+Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * root.c (Create_Root): If noexec, don't create CVS/Root.
+
+Wed Oct 18 11:19:40 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * lock.c (unlock): Change order of comparison so that Check_Owner
+ is called only if other conditions are true. This performance
+ enhancement was broken when the AFS support was added.
+
+Wed Oct 18 12:51:33 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): check if argv[0] is "pserver" with else-if, not
+ if, since we've already asked if it's "kserver".
+
+Tue Oct 17 18:09:23 1995 Warren Jones <wjones@tc.fluke.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Deal with supplying a relative cvs filename, or
+ with a cvs filename which doesn't have basename "cvs".
+
+Mon Oct 16 15:58:31 1995 Vince Demarco <vdemarco@bou.shl.com>
+
+ * parseinfo.c (Parse_Info): if the Keyword isn't ALL the current
+ version doesn't use the expanded variable, It should.
+
+Mon Oct 16 15:58:31 1995 Gary Oberbrunner <garyo@avs.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (server_register): Don't pass NULL to printf if tag,
+ date, or conflict is NULL.
+
+Thu Oct 12 12:13:42 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): begin to handle "pserver"; support not complete
+ yet, however.
+
+Thu Oct 12 02:52:13 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * expand_path.c: Don't #include <pwd.h>, since cvs.h already does,
+ and not all systems' <pwd.h>s are protected from multiple inclusion.
+ * login.c: Likewise.
+
+Wed Oct 11 15:23:24 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): handle everything correctly now.
+
+Wed Oct 11 12:02:48 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * rcs.c (RCS_gettag): support RCS keyword Name
+
+Tue Oct 10 19:11:16 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * options.h.in (CVS_LOGIN): discuss, but leave commented out.
+ The "cvs login" command is still under construction; however, the
+ repository was changing so fast that instead of creating a branch
+ and dealing with the attendant hair, I'm just developing on the
+ trunk, making sure that everything is surrounded by "#ifdef
+ CVS_LOGIN ... #endif" so I don't get in anyone's way.
+
+ * login.c: include cvs.h before checking CVS_LOGIN, so it has a
+ chance to get defined before we ask if it's defined.
+ (login): oops, use semi not comma in `for' loop init.
+
+ * Makefile.in (SOURCES, OBJECTS): include login.c, login.o.
+
+ * main.c: added protoype for login().
+ Added "login" entry to cmds[].
+ (usg): added line about "login".
+
+ * login.c: new file.
+
+Tue Oct 10 18:33:47 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * Makefile.in (COMMON_OBJECTS): added error.o.
+ (OBJECTS): took error.o out; it's in COMMON_OBJECTS now.
+
+Tue Oct 10 12:02:37 1995 Thorsten Lockert <tholo@sigmasoft.com>
+
+ * cvsbug.sh: Cater to lame versions of sh (4.4BSD ash) by using
+ ${foo-bar} instead of `if....`.
+
+Tue Oct 10 12:02:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * remove.c (remove_fileproc): If noexec, don't remove file. Check
+ for error when removing file.
+
+Sun Oct 8 12:32:15 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * run.c: detect/use POSIX/BSD style reliable signals for critical
+ section masking etc. Helps prevent stray locks on interruption.
+
+Sat Oct 7 23:26:54 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * admin.c (admin): If group CVS_ADMIN_GROUP exists, allow only
+ users in that group to use "cvs admin".
+ * options.h.in: Default CVS_ADMIN_GROUP to "cvsadmin".
+
+Sat Oct 7 23:05:24 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * add.c, checkout.c, commit.c, cvs.h, filesubr.c, import.c,
+ lock.c, main.c, modules.c, options.h.in: New variable cvsumask
+ which is used to set mode of files in repository (regardless of
+ umask in effect when cvs is run).
+
+Sat Oct 7 22:40:17 1995 Stephen Bailey <sjbailey@sand.npl.washington.edu>
+
+ * lock.c: Include AFSCVS ifdefs to deal with AFS's lack of
+ correspondance between userid's from stat and from geteuid.
+
+Sat Oct 7 22:28:49 1995 Scott Carson <sdc@TracerTech.COM>
+
+ * add.c (add): Pass -ko, not -k -ko, to set keyword expansion options.
+
+ * admin.c (admin): Don't skip first argument when sending to server.
+
+Fri Oct 6 21:45:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * version.c: Version 1.6.1.
+
+Fri Oct 6 21:31:28 1995 Jeff Johnson <jbj@brewster.jbj.org>
+
+ * cvs.h, admin.c, client.c, commit.c, log.c, modules.c,
+ parseinfo.c, patch.c, recurse.c, rtag.c, status.c, tag.c:
+ Prototype when dealing in pointers to functions.
+
+Fri Oct 6 21:07:22 1995 Mark H. Wilkinson <mhw@minster.york.ac.uk>
+
+ * cvsrc.c (read_cvsrc): fix look up of command names in cvsrc file
+ to use full name from command table rather than possible nickname
+ in argv. Fixes errors with things like `cvs di' when cvsrc has
+ `diff -u5' in it.
+
+Thu Aug 3 01:03:52 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * parseinfo.c (Parse_Info): Add code to call expand_path function
+ instead of using built in code.
+
+ * wrapper.c (wrap_add): Add code to call expand_path function to
+ expand all built in variables.
+
+ * expand_path.c (New file): expand things that look like
+ environmental variables (only expand local CVS environmental
+ variables) and user names like ~/.
+ * cvs.h: Declare expand_path.
+
+ * Makefile.in (SOURCES, OBJECTS): Added expand_path.c,
+ expand_path.o.
+
+Fri Oct 6 14:03:09 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ignore.c (ign_setup): Don't try to look for a file in CVSroot if
+ client. (The recent tightening of the error checking detects this).
+
+ * commit.c (checkaddfile): Don't try to pass options if it is "".
+
+Thu Oct 5 18:04:46 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * sanity.sh: unset CVSREAD, since it causes the script to bomb.
+
+Thu Oct 5 18:29:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * remove.c, add.c, commit.c, cvs.h: Remove CVSEXT_OPT stuff; it
+ has been broken for ages and the options are already stored in the
+ Entries file.
+
+Thu Oct 5 18:20:13 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * commit.c (checkaddfile): New argument options; pass it to RCS.
+ (commit_fileproc): Pass it.
+
+Tue Oct 3 09:26:00 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: upped to 1.6.
+
+Mon Oct 2 18:10:35 1995 Larry Jones <larry.jones@sdrc.com>
+
+ * server.c: if HAVE_SYS_BSDTYPES_H, include <sys/bsdtypes.h>.
+
+Mon Oct 2 10:34:53 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: Upped version to 1.5.95.
+
+Mon Oct 2 15:16:47 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * tag.c, rtag.c: pass "mov" instead of "add" if tag will be moved
+ (i.e. invoked with -F)
+
+Sun Oct 1 18:36:34 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: upped to 1.5.94.
+
+ * server.c: reverted earlier ISC change (of Sep. 28).
+
+ * version.c: upped to 1.5.93, for Peter Wemm's new SVR4 patch.
+
+Sun Oct 1 14:51:59 1995 Harlan Stenn <Harlan.Stenn@pfcs.com>
+
+ * main.c: don't #include <pwd.h>; cvs.h does that already.
+
+Fri Sep 29 15:21:35 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * version.c: upped to 1.5.91 for another pre-1.6 release.
+
+Fri Sep 29 14:41:14 1995 <bmeier@rzu.unizh.ch>
+
+ * root.c: start rcsid[] with "CVSid".
+
+Fri Sep 29 13:22:44 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * diff.c (diff): Doc fix.
+
+Fri Sep 29 14:32:36 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * repos.c (Short_Repository): chop superfluous "/".
+
+ * tag.c (pretag_proc): correct user-visible string.
+
+ * rtag.c (pretag_proc): correct user-visible string.
+
+Fri Sep 29 13:45:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h (USE): if __GNUC__ != 2, expand to a dummy var instead of
+ nothing.
+
+Thu Sep 28 13:37:05 1995 Larry Jones <larry.jones@sdrc.com>
+
+ * server.c: ifdef ISC, include <sys/bsdtypes.h>.
+
+Fri Sep 29 07:54:22 1995 Mike Sutton <mws115@llcoolj.dayton.saic.com>
+
+ * filesubr.c (last_component): Don't use ANSI style declaration.
+
+Wed Sep 27 15:24:00 1995 Del <del@matra.com.au>
+
+ * tag.c, rtag.c: Pass a few extra options to the script
+ named in taginfo (del/add, and revision number).
+
+ * tag.c: Support a -r option (at long last). Also needs
+ a -f option to tag the head if there is no matching -r tag.
+
+Tue Sep 26 11:41:08 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: Upped version to 1.5.89 for test release preceding
+ 1.6.
+
+Wed Sep 20 15:32:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ignore.c (ign_add_file): Check for errors from fopen and fclose.
+
+Tue Sep 19 18:02:16 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (DISTFILES): Remove sanity.el from this list; the
+ file has been deleted.
+
+Thu Sep 14 14:17:52 1995 Peter Wemm <peter@haywire.dialix.com>
+
+ * import.c: Recover from being unable to open the user file.
+
+ * update.c (join_file): Print a message in the case where the file
+ was added.
+
+ * mkmodules.c: Deal with .db as well as .pag/.dir (for use with
+ BSD 4.4 and real dbm support).
+
+Mon Sep 11 15:44:13 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * release.c (release): Revise comment regarding why and how we
+ skip argv[0].
+
+Mon Sep 11 10:03:59 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * release.c (release): use return value of pclose to determine
+ success of update.
+
+Mon Sep 11 09:56:33 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * release.c (release_delete): Fix comment.
+
+Sun Sep 10 18:48:35 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * release.c (release): made work with client/server.
+ Don't ask if <arg> is mentioned in `modules'.
+
+Fri Sep 8 13:25:55 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: When committing a removal, send stdout to LOGFILE;
+ this is no longer a silent operation.
+
+ * sanity.sh: Remove OUTPUT variable; it is unused.
+
+ * client.c: Add comment regarding deleting temp file.
+ * main.c: Add comment regarding getopt REQUIRE_ORDER.
+
+Thu Sep 7 20:24:46 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): use getopt_long(), accept "--help" and
+ "--version".
+ Don't assume EOF is -1.
+
+Thu Sep 7 19:18:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * cvs.h (unlink_file_dir): Add prototype for this.
+
+Thu Sep 7 14:38:06 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * ALL FILES: add semicolon, as indicated below.
+
+ * cvs.h (USE): don't provide semicolon in the expansion of the USE
+ macro; we'd rather the callers provided it themselves because that
+ way etags doesn't get fooled.
+
+Mon Sep 4 23:30:41 1995 Magnus Hyllander <mhy@os.se>
+
+ * checkout.c: cvs export now takes -k option and does not default
+ to -kv.
+ * checkout.c, cvs.h, modules.c: Modules file now takes -e option
+ for cvs export.
+
+Mon Sep 4 23:30:41 1995 Kirby Koster <koster@sctc.com>
+
+ * commit.c: When committing a removal, print a message saying what
+ we are doing.
+
+Wed Aug 2 10:06:51 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * server.c: fix compiler warnings (on NeXT) (declare functions as
+ static inline instead of just static) functions: get_buffer_date,
+ buf_append_char, and buf_append_data
+
+Mon Sep 4 22:31:28 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (update_entries), import.c (expand_at_signs): Check for
+ errors from fread and putc.
+
+Fri Sep 1 00:03:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Fix TODO item pathname.
+
+ * sanity.el: Removed. It was out of date, didn't do much, and I
+ doubt anyone was using it.
+
+ * no_diff.c (No_Difference): Don't change the modes of the files.
+
+Thu Aug 31 13:14:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * version.c: Change version to 1.5.1.
+
+ * client.c (start_rsh_server): Don't pass -d to "cvs server"
+ invocation via rsh (restore change which was lost when NT stuff
+ was merged in).
+ * sanity.sh: Add TODO item suggesting test for bug which this fixes.
+
+Wed Aug 30 12:36:37 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * sanity.sh (basic1): Make sure first-dir is deleted before
+ running this set of tests.
+
+ * subr.c: Extract file twiddling functions to a different file,
+ because we want to use different versions of many of these
+ routines under Windows NT.
+ (copy_file, isdir, islink, isfile, isreadable, iswritable,
+ open_file, make_directory, make_directories, xchmod,
+ rename_file, link_file, unlink_file, xcmp, tmpnam,
+ unlink_file_dir, deep_remove_dir): Moved to...
+ * filesubr.c: ...this file, which is new.
+ * Makefile.in (SOURCES): Mention filesubr.c.
+ (COMMON_OBJECTS): Mention filesubr.o.
+
+ * subr.c: Extract process execution guts to a different file,
+ because we want to replace these routines entirely under
+ Windows NT.
+ (VA_START, va_alist, va_dcl): Move this stuff...
+ (run_add_arg, run_init_prog): and these declarations...
+ (run_prog, run_argv, run_argc, run_argc_allocated): and these
+ variables...
+ (run_setup, run_arg, run_args, run_add_arg, run_init_prog,
+ run_exec, run_print, Popen): and these functions...
+ * run.c: To this file, which is new.
+ * Makefile.in (SOURCES): Mention run.c.
+ (COMMON_OBJECTS): Mention run.o.
+
+ * status.c (status): Call ign_setup, if client_active. Otherwise,
+ we don't end up ignoring CVS directories and such.
+
+ * server.c (mkdir_p, dirswitch): Use CVS_MKDIR instead of mkdir.
+
+ * repos.c (Name_Repository): Use the isabsolute function instead of
+ checking the first character of the path.
+ * root.c (Name_Root): Same.
+
+ * release.c (release): Use fncmp instead of strcmp to compare
+ filenames.
+
+ * rcs.c (RCS_parse, RCS_parsercsfile) [LINES_CRLF_TERMINATED]:
+ Abort, because we have strong reason to believe this code is
+ wrong.
+
+ * patch.c (patch): Register signal handlers iff the signal name is
+ #defined.
+
+ * no_diff.c (No_Difference): Don't try to include server_active in
+ trace message unless SERVER_SUPPORT is #defined.
+
+ * modules.c (do_module): Use CVS_MKDIR instead of mkdir.
+
+ * mkmodules.c (main): Call last_component instead of writing it out.
+
+ * main.c (main): Call last_component instead of writing it out.
+ Break up the long copyright string into several strings; Microsoft
+ Visual C++ can't handle a line that long. Feh.
+ Use fncmp instead of strcmp to compare filenames.
+ Register signal handlers iff the signal name is #defined.
+
+ * lock.c (readers_exist): Don't check return value of closedir.
+ Most of the rest of the code doesn't, and some systems don't
+ provide a return value anyway.
+ (set_lock): Use CVS_MKDIR instead of mkdir.
+
+ * import.c (import): Use the isabsolute function instead of
+ checking the first character of the path.
+ Try to delete the temporary file again after we close it, so it'll
+ get deleted on systems that don't let you delete files that are
+ open.
+ (add_rev): Instead of making a hard link to the working file and
+ checking in the revision with ci -r, use ci -u and restore the
+ permission bits.
+ (comtable): Include lines from SYSTEM_COMMENT_TABLE, if it is
+ #defined.
+ (add_rcs_file) [LINES_CRLF_TERMINATED]: Abort, because we have
+ strong reason to believe this code is wrong.
+ (import_descend_dir): Use CVS_MKDIR instead of mkdir.
+
+ * history.c (read_hrecs): Open the file with OPEN_BINARY.
+
+ * find_names.c (add_entries_proc, fsortcmp): Add prototypes.
+ * entries.c (write_ent_proc): Add prototype.
+ * hash.c (walklist): Add prototype for PROC argument.
+ (sortlist): Add prototype for COMP argument.
+ (printnode): Add a prototype, and make it static.
+
+ * cvs.h (wrap_add_file, wrap_add): Add extern decls for these;
+ they're used in import.c and update.c.
+ * wrapper.c (wrap_add_file, wrap_add): Remove them from here.
+
+ * cvs.h (RUN_NORMAL, RUN_COMBINED, RUN_REALLY, RUN_STDOUT_APPEND,
+ RUN_STDERR_APPEND, RUN_SIGNIGNORE, RUN_TTY, run_arg, run_print,
+ run_setup, run_args, run_exec, Popen, piped_child, close_on_exec,
+ filter_stream_through_program, waitpid): Move all these
+ declarations and definitions to the same section.
+
+ * cvs.h (error_set_cleanup): Fix prototype.
+
+ * cvs.h (isabsolute, last_component): New extern decls.
+
+ * cvs.h (link_file): Function is deleted; remove extern decl.
+
+ * cvs.h (DEATH_STATE, DEATH_SUPPORT): Move #definitions of these
+ above the point where we #include rcs.h, since rcs.h tests them
+ (or DEATH_SUPPORT, at least).
+
+ * cvs.h (DEVNULL): #define this iff it isn't already #defined.
+ config.h may want to override it.
+
+ * cvs.h (SERVER_SUPPORT, CLIENT_SUPPORT): Don't #define these
+ here; let config.h do that. On some systems, we don't have any
+ server support.
+
+ * cvs.h: Don't #include <io.h> or <direct.h>; we take care of
+ those in lib/system.h.
+
+ * commit.c (commit): Open logfile with the OPEN_BINARY flag.
+ (precommit_proc): Use the isabsolute function, instead of
+ comparing the first character with /.
+ (remove_file, checkaddfile): Use CVS_MKDIR instead of mkdir.
+
+ * client.c (send_repository): Use larger line buffers.
+
+ * client.c [LINES_CRLF_TERMINATED] (update_entries): If we've just
+ received a gzipped file, copy it over, converting LF to CRLF,
+ instead of just renaming it into place.
+ [LINES_CRLF_TERMINATED] (send_modified): Convert file to LF format
+ before sending with gzip.
+ (send_modified): Don't be disturbed if we get fewer than
+ sb.st_size characters when we read. The read function may be
+ collapsing CRLF to LF for us.
+
+ * client.c: Add forward declarations for all the cvs command
+ functions we call.
+
+ * client.c: Add forward static declarations for all the
+ handle_mumble functions.
+
+ On some systems, RSH converts LF to CRLF; this screws us up.
+ * client.c (rsh_pid): Declare this iff RSH_NOT_TRANSPARENT is not
+ #defined.
+ (get_responses_and_close): Use SHUTDOWN_SERVER if it is #defined.
+ Only wait for rsh process to exit if RSH_NOT_TRANSPARENT is not
+ #defined.
+ (start_rsh_server): Declare and define only if
+ RSH_NOT_TRANSPARENT is not #defined. Use piped_child, instead of
+ writing all that out.
+ (start_server): Only try to call start_rsh_server if
+ RSH_NOT_TRANSPARENT is not #defined. Use START_SERVER if it is
+ #defined. Convert file descriptors to stdio file pointers using
+ the FOPEN_BINARY_WRITE and FOPEN_BINARY_READ strings.
+
+ * client.h (rsh_pid): Don't declare this; it's never used elsewhere.
+ (supported_request): Add external declaration for this;
+ it's used in checkout.c.
+
+ Move process-running functions to run.c; we need to totally
+ replace these on other systems, like Windows NT.
+ * client.c (close_on_exec, filter_stream_through_program): Moved
+ to run.c.
+ * run.c (close_on_exec, filter_stream_through_program): Here they
+ are.
+
+ * add.c (add_directory): Use CVS_MKDIR instead of straight mkdir.
+ * checkout.c (checkout, build_dirs_and_chdir): Same.
+ (checkout_proc): Use fncmp instead of strcmp.
+ * client.c (call_in_directory): Use CVS_MKDIR instead of straight
+ mkdir.
+
+ * client.c (handle_checksum): Cast return value of strtol.
+
+Wed Aug 30 10:35:46 1995 Stefan Monnier <stefan.monnier@epfl.ch>
+
+ * main.c (main): Allow -d to override CVSROOT_ENV.
+
+Thu Aug 24 18:57:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h, rcscmds.c (RCS_unlock, RCS_deltag, RCS_lock): Add extra
+ parameter for whether to direct stderr to DEVNULL.
+ * checkin.c, tag.c, rtag.c, import.c, commit.c: Pass extra
+ argument. 1 if stderr had been directed to DEVNULL before
+ rcscmds.c was in use, 0 if it was RUN_TTY.
+
+ * cvs.h: Add comment regarding attic.
+
+Tue Aug 22 10:09:29 1995 Alexander Dupuy <dupuy@smarts.com>
+
+ * rcs.c (whitespace): Cast to unsigned char in case char is signed
+ and value is negative.
+
+Tue Aug 22 10:09:29 1995 Kirby Koster <koster@sctc.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (join_file): If vers->vn_user is NULL, just return.
+
+Tue Aug 22 10:09:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c, client.c: Add comments about modes and umasks.
+
+Mon Aug 21 12:54:14 1995 Rick Sladkey <jrs@world.std.com>
+
+ * update.c (update_filesdone_proc): If pipeout, don't try to
+ create CVS/Root.
+
+Mon Aug 21 12:54:14 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (start_rsh_server): Don't pass -d to "cvs server"
+ invocation via rsh.
+
+ * server.c (serve_root): Report errors via pending_error_text.
+ (serve_valid_requests): Check for pending errors.
+
+Sun Aug 20 00:59:46 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * options.h.in: Document usage of DIFF in update.c
+ * update.c: Use DIFF -c, not DIFF -u. The small improvement in
+ diff size is not worth the hassle in terms of everyone having to
+ make sure that DIFF is GNU diff (IMHO).
+
+Sat Aug 19 22:05:46 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * recurse.c (start_recursion): Doc fix.
+
+ * server.c (do_cvs_command): Clear error_use_protocol in the
+ child.
+ (server): Set error_use_protocol.
+
+Sun Aug 13 15:33:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (do_cvs_command): Don't select on exceptions.
+
+Fri Aug 4 00:13:47 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (LDFLAGS): Set to @LDFLAGS@.
+ (options.h): Depend on ../config.status and options.h.in.
+ Add rule to build it from dependents.
+
+ * add.c: Include save-cwd.h.
+ (add_directory): Use save_cwd and restore_cwd instead of
+ explicit getwd then chdir.
+ * import.c (import_descend_dir): Likewise.
+ * modules.c (do_module): Likewise.
+
+ * recurse.c (save_cwd, restore_cwd, free_cwd): Remove functions.
+ New versions have been broken out into save-cwd.c.
+ (do_dir_proc): Adapt to handle status code returned by new versions
+ of save_cwd and restore_cwd -- and one fewer argument to restore_cwd.
+ (unroll_files_proc): Likewise.
+
+ * wrapper.c (wrap_name_has): Add default: abort () to switch
+ statement to avoid warning from gcc -Wall.
+ (wrap_matching_entry): Remove dcl of unused TEMP.
+ (wrap_tocvs_process_file): Remove dcl of unused ERR.
+ (wrap_fromcvs_process_file): Likewise.
+
+ * cvs.h: Remove prototype for error. Instead, include error.h.
+ Also, remove trailing white space.
+
+Thu Aug 3 10:12:20 1995 Jim Meyering (meyering@comco.com)
+
+ * import.c (import_descend_dir): Don't print probably-bogus CWD
+ in error messages saying `cannot get working directory'.
+
+Sun Jul 30 20:52:04 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * parseinfo.c (Parse_Info): Revise comments and indentation.
+
+Sun Jul 30 15:30:16 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * history.c: put ifdef SERVER_SUPPORT around tracing code incase
+ the client/server code is not compiled into the program.
+
+Sat Jul 29 16:59:49 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * subr.c (deep_remove_dir): Use struct dirent, not struct direct.
+
+Sat Jul 29 18:32:06 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * add.c: Check wrap_name_has.
+
+ * diff.c, checkin.c, import.c: have code call unlink_file_dir in
+ the appropriate places instead of just calling unlink_file.
+
+ * checkin.c: Remove one unlink call.
+
+ * import.c (comtable): Add .m .psw .pswm.
+
+ * import.c (add_rcs_file): Remove tocvsPath before returning.
+
+ * subr.c (unlink_file_dir): Add new function. unlinks the file if
+ it is a file. or will do a recursive delete if the path is
+ actually a directory.
+ (deep_remove_dir): New function, helps unlink_file_dir.
+
+ * mkmodules.c: Added CVSROOTADM_WRAPPER (cvswrappers file) to the
+ checkout file list.
+
+Fri Jul 28 16:27:56 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (safe_location): Use PATH_MAX not MAXPATHLEN.
+
+Fri Jul 28 19:37:03 1995 Paul Eggert <eggert@twinsun.com>
+
+ * log.c (cvslog, log_fileproc): Pass all options (except -l)
+ to rlog as-is, so that users can put spaces in options,
+ can specify multiple -d options, etc.
+ (ac, av): New variables.
+ (log_option_with_arg, options): Remove.
+
+ (log_fileproc): Don't prepend `/' to file name if update_dir is empty.
+
+Tue Jul 25 00:52:26 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (safe_location): Don't use PROTO in function definition.
+
+Mon Jul 24 18:32:06 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * checkout.c (safe_location): fix a compiler warning. (Declare
+ safe_location). Changed code in safe_location to call getwd
+ instead of getcwd. getwd is declared in the ../lib directory and
+ used exclusively thoughout the code. (this helps portability on
+ non POSIX systems).
+
+ * wrapper.c: updated Andrew Athan's email address.
+
+ * main.c: fix an ifdef so the code will compile. syntax error in
+ the ifdef for CVS_NOADMIN.
+
+Mon Jul 24 13:25:00 1995 Del <del@babel.dialix.oz.au>
+
+ * checkout.c: New procedure safe_location.
+ Ensures that you don't check out into the repository
+ itself.
+
+ * tag.c, rtag.c, cvs.h, mkmodules.c: Added a "taginfo" file in
+ CVSROOT to perform pre-tag checks.
+
+ * main.c, options.h.in: Added a compile time option to
+ disable the admin command.
+
+Fri Jul 21 17:07:42 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c, status.c, patch.c, checkout.c, import.c, release.c,
+ rtag.c, tag.c: Now -q and -Q options just print an error message
+ telling you to use global -q and -Q options. The non-global
+ options were a mess because some commands accepted them and some
+ did not, and they were redundant with -q and -Q global options.
+
+ * rcs.c, cvs.h, commit.c, log.c, find_names.c: Remove CVS.dea
+ stuff. It is slower than the alternatives and I don't think
+ anyone ever actually used it.
+
+Fri Jul 21 10:35:10 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * Makefile.in (SOURCES, OBJECTS): Add wrapper.c, wrapper.o.
+ * add.c, admin.c, checkout.c, commit.c, diff.c, import.c, log.c,
+ remove.c, status.c: Call wrap_setup at start of commands.
+ * add.c (add): Check for wrapper, as well as directory, in repository.
+ * checkin.c: Add tocvsPath variable and associated handling.
+ * cvs.h: Add wrapper declarations.
+ * diff.c: Add tocvsPath variable and associated handling.
+ * import.c: Add -W option, CVSDOTWRAPPER handling.
+ (import_descend): check wrap_name_has.
+ (update_rcs_file, add_rev, add_rcs_file): add tocvsPath
+ variable and associated handling.
+ * no_diff.c: Add tocvsPath variable and associated handling.
+ * recurse.c (start_recursion): Check wrap_name_has.
+ * update.c: Copy, don't merge, copy-by-merge files. Attempt to
+ use -j on a copy-by-merge file generates a warning and no further
+ action.
+ * update.c: Add CVSDOTWRAPPER handling.
+ * wrapper.c: Added.
+
+Fri Jul 21 00:20:52 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c: Revert David Lamkin patch, except for the bits about
+ removing temp_filename and the .rej file.
+ * sanity.sh (errmsg1): Test for the underlying bug which Lamkin
+ kludged around.
+ * client.c (call_in_directory): Set short_pathname to include the
+ filename, not just the directory. Improve comments regarding what
+ is passed to FUNC.
+
+Thu Jul 20 17:51:54 1995 David Lamkin <drl@net-tel.co.uk>
+
+ * client.c (short_pathname): Fixes the fetching of the whole file
+ after a patch to bring it up to date has failed:
+ - failed_patches[] now holds short path to file that failed
+ - patch temp files are unlinked where the patch is done
+
+Thu Jul 20 12:37:10 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Declare error_set_cleanup
+ * main.c: Call it.
+ (error_cleanup): New function.
+
+Thu Jul 20 12:17:16 1995 Mark H. Wilkinson <mhw@minster.york.ac.uk>
+
+ * add.c, admin.c, checkin.c, checkout.c, classify.c, client.c,
+ client.h, commit.c, create_adm.c, cvs.h, diff.c, entries.c,
+ history.c, import.c, log.c, main.c, modules.c, no_diff.c, patch.c,
+ release.c, remove.c, repos.c, rtag.c, server.c, server.h,
+ status.c, subr.c, tag.c, update.c, vers_ts.c, version.c: Put
+ client code inside #ifdef CLIENT_SUPPORT, server code inside
+ #ifdef SERVER_SUPPORT. When reporting version, report whether
+ client and/or server are compiled in.
+
+Wed Jul 19 18:00:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * subr.c (copy_file): Declare local var n to be an int,
+ not a size_t. size_t is unsigned, and the return values
+ of read and write are definitely not unsigned.
+
+ * cvs.h [HAVE_IO_H]: #include <io.h>.
+ [HAVE_DIRECT_H]: #include <direct.h>.
+
+Fri Jul 14 22:28:46 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * server.c (dirswitch, serve_static_directory, serve_sticky,
+ serve_lost, server_write_entries, serve_checkin_prog,
+ serve_update_prog): Include more information in error messages.
+ (Thanks, DJM.)
+
+ * cvsbug.sh: Use /usr/sbin/sendmail, unless it doesn't
+ exist, in which case use /usr/lib/sendmail. (Thanks, DJM.)
+
+ * server.c (server, server_cleanup): Use "/tmp" instead of
+ "/usr/tmp" when the TMPDIR environment variable isn't set. This
+ is what the rest of the code uses.
+
+Thu Jul 13 11:03:17 1995 Jim Meyering (meyering@comco.com)
+
+ * recurse.c (free_cwd): New function.
+ (save_cwd, restore_cwd): Use it instead of simply freeing any
+ string. The function also closes any open file descriptor.
+
+ * import.c (comtable): Now static.
+ (comtable): Put braces around each element of initializer.
+
+ * cvs.h: Add prototype for xgetwd.
+ * recurse.c (save_cwd, restore_cwd): New functions to encapsulate
+ run-time solution to secure-SunOS vs. fchown problem.
+ (do_dir_proc, unroll_files_proc): Use new functions instead of
+ open-coded fchdir/chdir calls with cpp directives.
+
+ * sanity.sh: Change out of TESTDIR before removing it.
+ Some versions of rm fail when asked to delete the current directory.
+
+Wed Jul 12 22:35:04 1995 Jim Meyering (meyering@comco.com)
+
+ * client.c (get_short_pathname): Add const qualifier to parameter dcl.
+ (copy_a_file): Remove set-but-not-used variable, LEN.
+ (handle_clear_static_directory): Likewise: SHORT_PATHNAME.
+ (set_sticky): Likewise: LEN.
+ (handle_set_sticky): Likewise: SHORT_PATHNAME.
+ (handle_clear_sticky): Likewise: SHORT_PATHNAME.
+ (start_rsh_server): Convert perl-style `cond || stmt' to more
+ conventional C-style `if (cond) stmt.' Sheesh.
+ Remove dcl of unused file-static, SEND_CONTENTS.
+
+ * history.c: Remove dcls of set-but-not-used file-statics,
+ HISTSIZE, HISTDATA.
+ (read_hrecs): Don't set them.
+
+ * import.c (add_rev): Remove dcl of set-but-not-used local, RETCODE.
+
+ * repos.c (Name_Repository): Remove dcl of set-but-not-used local,
+ HAS_CVSADM.
+
+ * cvsrc.c (read_cvsrc): Parenthesize assignment used as truth value.
+
+Tue Jul 11 16:49:41 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * hash.h (struct entnode, Entnode): moved from here...
+ * cvs.h: to here.
+
+Wed Jul 12 19:45:24 1995 Dominik Westner (dominik@gowest.ppp.informatik.uni-muenchen.de)
+
+ * client.c (server_user): new var.
+ (parse_cvsroot): set above if repo is "user@host:/dir".
+ (start_rsh_server): if server_user set, then use it.
+
+Wed Jul 12 10:53:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * sanity.sh: remove the TESTDIR after done.
+
+ * cvsbug.sh (GNATS_ADDR): now bug-cvs@prep.ai.mit.edu again.
+
+Tue Jul 11 15:53:08 1995 Greg A. Woods <woods@most.weird.com>
+
+ * options.h.in: depend on configure for grep and diff, now that
+ changes to configure.in are applied.
+
+Tue Jul 11 14:32:14 1995 Michael Shields <shields@tembel.org>
+
+ * Makefile.in (LDFLAGS): Pick up from configure.
+
+Tue Jul 11 14:20:00 1995 Loren James Rittle <rittle@supra.comm.mot.com>
+
+ * import.c (add_rev), commit.c (remove_file, ci_new_rev),
+ checkin.c (Checkin), subr.c (make_message_rcslegal), cvs.h:
+ Always perform sanity check and fix-up on messages to be passed
+ directly to RCS via the '-m' switch. RCS 5.7 requires that a
+ non-total-whitespace, non-null message be provided or it will
+ abort with an error. CVS is not setup to handle any returned
+ error from 'ci' gracefully and, thus, the repository entered a
+ trashed state.
+
+ * sanity.sh: Add regression tests for new code and interactions
+ with RCS 5.7.
+
+Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * .cvsignore: added new backup file
+
+ * options.h.in: our new configure.in finds the right diff and
+ grep paths now....
+
+ * subr.c: quote the string in run_print() for visibility
+ - indent a comment
+ - Jun Hamano's xchmod() patch to prevent writable files
+ (from previous local changes)
+
+ * logmsg.c: fix a NULL pointer de-reference
+ - clean up some string handling code...
+ (from previous local changes)
+
+ * parseinfo.c: add hack to expand $CVSROOT in an *info file.
+ - document "ALL" and "DEFAULT" in opening comment for Parse_Info()
+ - fix the code to match the comments w.r.t. callbacks for "ALL"
+ - add a line of trace output...
+ (from previous local changes)
+
+ * mkmodules.c: add support for comments in CVSROOT/checkoutlist
+ - add CVSroot used by something other .o, ala main.c
+ (from previous local changes)
+
+ * main.c, cvs.h: add support for $VISUAL as log msg editor
+ (from previous local changes)
+
+ * status.c: add support for -q and -Q (from previous local changes)
+
+
+Sun Jul 9 18:44:32 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * log.c: trivial change to test ChangeLog stuff.
+
+Sat Jul 8 20:33:57 1995 Paul Eggert <eggert@twinsun.com>
+
+ * history.c: (history_write): Don't assume that fopen(..., "a")
+ lets one interleave writes to the history file from different processes
+ without interlocking. Use open's O_APPEND option instead.
+ Throw in an lseek to lessen the race bugs on non-Posix hosts.
+ * cvs.h, subr.c (Fopen): Remove.
+
+ * log.c (log_fileproc): Pass working file name to rlog, so that
+ the name is reported correctly.
+
+Fri Jul 7 18:29:37 1995 Michael Hohmuth <hohmuth@inf.tu-dresden.de>
+
+ * client.c, client.h (client_import_setup): New function.
+ (client_import_done, client_process_import_file): Add comments
+ regarding now-redundant code.
+ * import.c (import): Call client_import_setup.
+
+Tue Jul 4 09:21:26 1995 Bernd Leibing <bernd.leibing@rz.uni-ulm.de>
+
+ * rcs.c (RCS_parsercsfile_i): Rename error to l_error; SunOS4 /bin/cc
+ doesn't like a label and function with the same name.
+
+Sun Jul 2 12:51:33 1995 Fred Appelman <Fred.Appelman@cv.ruu.nl>
+
+ * logmsg.c: Rename strlist to str_list to avoid conflict with
+ Unixware 2.01.
+
+Thu Jun 29 17:37:22 1995 Paul Eggert <eggert@twinsun.com>
+
+ * rcs.c (RCS_check_kflag): Allow RCS 5.7's new -kb option.
+
+Wed Jun 28 09:53:14 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (HEADERS): Remove options.h.in.
+ (DISTFILES): Add options.h.in.
+ Depend on options.h in addition to HEADERS.
+
+Tue Jun 27 22:37:28 1995 Vince Demarco <vdemarco@bou.shl.com>
+
+ * subr.c: Don't try to do fancy waitstatus stuff for NeXT,
+ lib/wait.h is sufficient.
+
+Mon Jun 26 15:17:45 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (DISTFILES): Remove RCS-patches and convert.sh.
+
+Fri Jun 23 13:38:28 1995 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * server.c (dirswitch, serve_co): Use CVSADM macro instead of
+ literal "CVS".
+
+Fri Jun 23 00:00:51 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * README-rm-add: Do not talk about patching RCS, that only
+ confuses people.
+ * RCS-patches, convert.sh: Removed (likewise).
+
+Thu Jun 22 10:41:41 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * subr.c: Change -1 to (size_t)-1 when comparing against a size_t.
+
+Wed Jun 21 16:51:54 1995 nk@ipgate.col.sw-ley.de (Norbert Kiesel)
+
+ * create_adm.c, entries.c, modules.c: Avoid coredumps if
+ timestamps, tags, etc., are NULL.
+
+Tue Jun 20 15:52:53 1995 Jim Meyering (meyering@comco.com)
+
+ * checkout.c (checkout): Remove dcl of unused variable.
+ * client.c (call_in_directory, handle_clear_static_directory,
+ handle_set_sticky, handle_clear_sticky, send_a_repository,
+ send_modified, send_dirent_proc): Remove dcls of unused variables.
+ * server.c (receive_file, serve_modified, server_cleanup):
+ Remove dcls of unused variables.
+ * subr.c (copy_file): Remove dcl of unused variable.
+ * vers_ts.c (time_stamp_server): Remove dcl of unused variable.
+
+Mon Jun 19 13:49:35 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * sanity.sh: Fix commencement message --- the test suite says
+ "Ok." when it's done.
+
+Fri Jun 16 11:23:44 1995 Jim Meyering (meyering@comco.com)
+
+ * entries.c (fgetentent): Parenthesize assignment in if-conditional.
+
+Thu Jun 15 17:33:28 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * server.c (get_buffer_data, buf_append_char, buf_append_data):
+ Don't conditionalize use of "inline". Autoconf takes care of
+ defining it away on systems that don't grok it.
+
+Thu Jun 15 13:43:38 1995 Jim Kingdon (kingdon@cyclic.com)
+
+ * options.h.in (DIFF): Default to "diff" not "diff -a" since diff
+ might not support the -a option.
+
+Wed Jun 14 11:29:42 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * import.c (import_descend): Initialize dirlist to NULL.
+
+ * subr.c (copy_file): Fix infinite loop.
+
+ * server.c (serve_directory): fix a memory leak.
+
+ * checkout.c, commit.c, diff.c, history.c, import.c, log.c,
+ patch.c, release.c, remove.c, rtag.c, status.c, tag.c, update.c:
+ Use send_arg() to send command line arguments to server.
+
+ * commit.c (fsortcmp), find_names (fsortcmp), hash.c (hashp,
+ findnode), hash.h (findnode), rcs.c (RCS_addnode,
+ RCS_check_kflag, RCS_check_tag, RCS_isdead, RCS_parse,
+ RCS_parsercsfile_i), rcs.h (RCS_addnode, RCS_check_kflag,
+ RCS_check_tag, RCS_parse): Added const qualifiers as
+ appropriate.
+ * rcs.h (RCS_isdead): Added prototype.
+
+ * hash.h (walklist, sortlist): correct function prototypes.
+
+ * ignore.c (ign_setup): don't bother checking to see if file
+ exists before calling ign_add_file.
+
+Fri Jun 9 11:24:06 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * all source files (rcsid): Added const qualifer.
+ * ignore.c (ign_default): Added const qualifier.
+ * subr.c (numdots): Added const qualifier to function argument.
+ * cvs.h (numdots): Added const qualifier to prototype argument.
+
+ * client.c (change_mode): Tied consecutive if statements testing
+ the same variable together with else if.
+
+ * import.c (import_descend): Build list of subdirectories when
+ reading directory, and then process the subdirectories in that
+ list. This change avoids I/O overhead of rereading directory
+ and reloading ignore list (.cvsignore) for each subdirectory.
+
+Thu Jun 8 11:54:24 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * import.c (import_descend): Use 4.4BSD d_type field if it is
+ present.
+
+ * lock.c (set_lockers_name): Use %lu in format and cast st_uid
+ field to unsigned long.
+
+ * import.c (import): Use RCS_check_kflag() to check -k options.
+ (keyword_usage, str2expmode, strn2expmode, expand_names):
+ Removed.
+ * rcs.c (RCS_check_kflag): Added keyword_usage array from import.c
+ for more descriptive error messages.
+
+ * subr.c (run_setup, run_args): Changed variable argument
+ processing to work on machines that use <varargs.h>.
+
+ * subr.c (copy_file, xcmp): Changed to read the file(s) by blocks
+ rather than by reading the whole file into a huge buffer. The
+ claim that this was reasonable because source files tend to be
+ small does not hold up in real world situations. CVS is used
+ to manage non-source files, and mallocs of 400K+ buffers (x2
+ for xcmp) can easily fail due to lack of available memory or
+ even memory pool fragmentation.
+ (block_read): New function, taken from GNU cmp and slightly
+ modified.
+
+ * subr.c (xcmp): Added const qualifier to function arguments.
+ * cvs.h (xcmp): Added const qualifer to prototype arguments.
+
+Wed Jun 7 11:28:31 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * cvs.h (Popen): Added prototype.
+ (Fopen, open_file, isreadable, iswritable, isdir, isfile,
+ islink, make_directory, make_directories, rename_file,
+ link_file, unlink_file, copy_file): Added const qualifer to
+ prototype arguments.
+ * subr.c (Fopen, Popen, open_file, isreadable, iswritable, isdir,
+ isfile, islink, make_directory, make_directories, rename_file,
+ link_file, unlink_file, copy_file): Added const qualifier to
+ function arguments.
+
+ * logmsg.c (logfile_write), recurse.c (do_recursion, addfile):
+ Don't cast void functions to a void expression. There is at
+ least one compiler (MPW) that balks at this.
+
+ * rcs.c (keysize, valsize): Change type to size_t.
+
+ * add.c (add_directory): Don't cast umask() argument to int.
+
+ * import.c (add_rcs_file): Changed type of mode to mode_t.
+
+ * rcscmds.c (RCS_merge): New function.
+ * cvs.h (RCS_merge): Declare.
+ * update.c (merge_file, join_file): Call RCS_merge instead of
+ invoking rcsmerge directly.
+
+ * cvs.h: Include <stdlib.h> if HAVE_STDC_HEADERS, otherwise
+ declared getenv().
+ * cvsrc.c, ignore.c, main.c: Removed getenv() declaration.
+
+ * client.c (mode_to_string): Changed to take mode_t instead of
+ struct statb argument. Simplified implementation, no longer
+ overallocates storage for returned mode string.
+ * client.h (mode_to_string): Updated declaration.
+ * server.c (server_updated): Updated for new calling conventions,
+ pass st_mode instead of pointer to struct statb.
+
+ * cvs.h (CONST): Removed definition, use of const qualifier is
+ determined by autoconf.
+ * history.c, modules.c, parseinfo.c: Use const instead of CONST.
+
+ * add.c, admin.c, checkout.c, commit.c, diff.c, import.c, log.c,
+ main.c, mkmodules.c, patch.c, recurse.c, remove.c, rtag.c,
+ server.c, status.c, subr.c, tag.c, update.c: Changed function
+ arguments "char *argv[]" to "char **argv" to silence lint
+ warnings about performing arithmetic on arrays.
+
+Tue Jun 6 18:57:21 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * version.c: Fix up version string, to say that this is Cyclic
+ CVS.
+
+Tue Jun 6 15:26:16 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * subr.c (run_setup, run_args, run_add_arg, xstrdup): Add const
+ qualifier to format argument.
+ * cvs.h (run_setup, run_args, xstrdup): Likewise.
+
+ * Makefile.in (SOURCES): Added rcscmds.c.
+ (OBJECTS): Added rcscmds.o.
+
+ * rcscmds.c: New file, with new functions RCS_settag, RCS_deltag,
+ RCS_setbranch, RCS_lock, RCS_unlock.
+ * checkin.c, commit.c, import.c, rtag.c, tag.c: Call above
+ functions instead of exec'ing rcs commands.
+ * cvs.h: Declare new functions.
+
+Mon May 29 21:40:54 1995 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * recurse.c (start_recursion, do_recursion): Set entries to NULL
+ after calling Entries_Close().
+
+Sat May 27 08:08:18 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (check): Export RCSBIN only if there exists an
+ `rcs' executable in ../../rcs/src. Before, tests would fail when
+ the directory existed but contained no executables.
+ (distclean): Remove options.h, now that it's generated.
+ (Makefile): Regenerate only *this* file when Makefile.in is
+ out of date. Depend on ../config.status.
+
+Fri May 26 14:34:28 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * entries.c (Entries_Open): Added missing fclose().
+ (Entries_Close): Don't write Entries unless Entries.Log exists.
+
+ * entries.c (Entries_Open): Renamed from ParseEntries; changed to
+ process Entries Log files left over from previous crashes or
+ aborted runs.
+ (Entries_Close): New function, write out Entries file if
+ neccessary and erase Log file.
+ (Register): Append changed records to Log file instead of
+ re-writing file.
+ (fgetentent): New function, parse one Entry record from a file.
+ (AddEntryNode): It's no longer an error for two records with the
+ same name to be added to the list. New records replace older
+ ones.
+ * cvs.h (Entries_Open, Entries_Close): Add prototypes.
+ (CVSADM_ENTLOG): New constant, name of Entries Log file.
+ * add.c, checkout.c, client.c, find_names.c, recurse.c: Use
+ Entries_Open()/Entries_Close() instead of ParseEntries()/dellist().
+
+ * add.c, admin.c, checkout.c, client.c, commit.c, diff.c,
+ history.c, import.c, log.c, patch.c, release.c, remove.c,
+ rtag.c, server.c, status.c, tag.c, update.c: Changed
+ conditionals so that return value of *printf is tested less than
+ 0 instead of equal to EOF.
+
+Thu May 25 08:30:12 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * subr.c (xmalloc): Never try to malloc zero bytes; if the user
+ asks for zero bytes, malloc one instead.
+
+Wed May 24 12:44:25 1995 Ken Raeburn <raeburn@cujo.cygnus.com>
+
+ * subr.c (xmalloc): Don't complain about NULL if zero bytes were
+ requested.
+
+Tue May 16 21:49:05 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * subr.c (xmalloc): Never try to malloc zero bytes; if the user
+ asks for zero bytes, malloc one instead.
+
+Mon May 15 14:35:11 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * lock.c (L_LOCK_OWNED): Removed.
+
+ * add.c, checkout.c, client.c, create_adm.c, cvs.h, entries.c,
+ find_names.c modules.c, recurse.c, release.c, repos.c, update.c:
+ removed CVS 1.2 compatibility/upgrade code.
+
+Mon May 8 11:25:07 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * lock.c (write_lock): Missed one instance where rmdir(tmp) should
+ have been changed to clear_lock().
+
+Wed May 3 11:08:32 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * create_adm.c, entries.c, import.c, root.c: Changed conditionals
+ so that return value of *printf is tested less than 0 instead of
+ equal to EOF --- That's all Standard C requires.
+
+Wed May 3 18:03:37 1995 Samuel Tardieu <tardieu@emma.enst.fr>
+
+ * rcs.h: removed #ifdef CVS_PRIVATE and #endif because cvs didn't
+ compile anymore.
+
+Mon May 1 13:58:53 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.c, rcs.h: Implemented lazy parsing of rcs files.
+ RCS_parsercsfile_i modified to read only the first two records
+ of rcs files, a new function RCS_reparsercsfile is called only
+ when additional information (tags, revision numbers, dates,
+ etc.) is required.
+
+Mon May 1 12:20:02 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Makefile.in (INCLUDES): Include -I. for options.h.
+
+Fri Apr 28 16:16:33 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (SOURCES, HEADERS, DISTFILES): Updated.
+ (dist-dir): Renamed from dist; changed to work with DISTDIR
+ variable passed from parent.
+
+ We don't want to include a file the user has to edit in the
+ distribution.
+ * options.h: No longer distributed.
+ * options.h.in: Distribute this instead.
+ * ../INSTALL, ../README: Installation instructions updated.
+
+ * client.c (start_rsh_server): Send the remote command to rsh as a
+ single string.
+
+Fri Apr 28 00:29:49 1995 Noel Cragg <noel@vo.com>
+
+ * commit.c: Added initializer for FORCE_CI
+
+ * sanity.sh: Fix tests added 25 Apr -- they were expecting the
+ server to make noise, but the CVS_SERVER variable had been
+ accidentally set with the `-Q' flag. Ran all tests -- both
+ locally and remotely -- to verify that the change didn't break
+ anything.
+
+Thu Apr 27 12:41:52 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Makefile.in: Revise comment regarding check vs. remotecheck.
+
+Thu Apr 27 12:52:28 1995 Bryan O'Sullivan <bos@cyclic.com>
+
+ * client.c (start_rsh_server): If the CVS_RSH environment variable
+ is set, use its contents as the name of the program to invoke
+ instead of `rsh'.
+
+Thu Apr 27 12:18:38 1995 Noel Cragg <noel@vo.com>
+
+ * checkout.c (checkout): To fix new bug created by Apr 23 change,
+ re-enabled "expand-module" functionality, because it has the side
+ effect of setting the checkin/update programs for a directory. To
+ solve the local/remote checkout problem that prompted this change
+ in the first place, I performed the next change.
+ * server.c (expand_proc): Now returns expansions for aliases only.
+
+Wed Apr 26 12:07:42 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.c (getrcskey): Rewritten to process runs of whitespace chars
+ and rcs @ strings instead of using state variables "white" and
+ "funky".
+
+Fri Apr 7 15:49:25 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * lock.c (unlock): Only call stat if we need to.
+
+Wed Apr 26 10:48:44 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (new_entries_line): Don't prototype.
+
+Tue Apr 25 22:19:16 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * sanity.sh: Add new tests to catch bugs in Apr 23 change.
+
+Tue Apr 25 17:10:55 1995 Roland McGrath <roland@baalperazim.frob.com>
+
+ * create_adm.c (Create_Admin): Use getwd instead of getcwd.
+
+Sun Apr 23 20:58:32 1995 Noel Cragg <noel@vo.com>
+
+ * checkout.c (checkout): Disabled "expand-module" functionality on
+ remote checkout, since it makes modules behave like aliases (see
+ longer note there). This change necessitated the change below.
+ Also merged the like parts of a conditional.
+
+ * client.c (call_in_directory): Changed the algorithm that created
+ nested and directories and the "CVS" administration directories
+ therein. The algoithm wrongly assumed that the name of the
+ directory that that was to be created and the repository name were
+ the same, which breaks modules.
+
+ * create_adm.c (Create_Admin), module.c (do_module), server.c
+ (server_register), subr.c, entries.c: Added fprintfs for trace-mode
+ debugging.
+
+ * client.c (client_send_expansions): Argument to function didn't
+ have a type -- added one.
+
+ * server.c (new_entries_line): Arguments to this function are
+ never used -- reoved them and fixed callers.
+
+Sat Apr 22 11:17:20 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * rcs.c (RCS_parse): If we can't open the file, give an error
+ message (except for ENOENT in case callers rely on that).
+
+Wed Apr 19 08:52:37 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_repository): Check for CVSADM_ENTSTAT in `dir', not
+ in `.'.
+
+ * sanity.sh: Add TODO list. Revise some comments. Add tests of
+ one working directory adding a file and other updating it.
+
+Sat Apr 8 14:52:55 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (CFLAGS): Let configure set the default for CFLAGS.
+ Under GCC, we want -g -O.
+
+Fri Apr 7 15:49:25 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * root.c (Name_Root): merge identical adjacent conditionals.
+
+ * create_admin.c (Create_Admin): Rearranged check for CVSADM and
+ OCVSADM directories so that CVSADM pathname is only built once.
+
+ * update.c (update_dirleave_proc): Removed code to remove CVS
+ administration directory if command_name == "export" and to
+ create CVS/Root file if it is not present. Identical code
+ in update_filesdone_proc() will perform these same actions.
+ Also removed code that read and verfied CVS/Root. This is
+ expensive, and if it is necessary should happen in the
+ general recursion processor rather than in the update
+ callbacks.
+
+ * lock.c (masterlock): New variable, pathname of master lockdir.
+ (set_lock): removed lockdir argument, now constructs it itself
+ and stores it in masterlock.
+ (clear_lock): new function, removes master lockdir.
+ (Reader_Lock, write_lock): call clear_lock instead of removing
+ master lockdir.
+ (Reader_Lock, write_lock): #ifdef'd out CVSTFL code.
+
+ * main.c (main): register Lock_Cleanup signal handler.
+ * lock.c (Reader_Lock, write_lock): no longer register
+ Lock_Cleanup.
+
+ * main.c (main): initialize new array hostname.
+ * lock.c (Reader_Lock, write_lock): Use global hostname array.
+ * logmsg.c (logfile_write): Likewise.
+
+ * recurse.c (do_dir_proc, unroll_files_proc): Use open()/fchdir()
+ instead of getwd()/chdir() on systems that support the fchdir()
+ system call.
+
+Fri Apr 7 06:57:20 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c: Include the word "server" in error message for memory
+ exhausted, so the user knows which machine ran out of memory.
+
+ * sanity.sh: For remote, set CVS_SERVER to test the right server,
+ rather than a random one from the PATH.
+
+ * commit.c [DEATH_STATE]: Pass -f to `ci'.
+
+Thu Apr 6 13:05:15 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * commit.c (checkaddfile): If we didn't manage to fopen the file,
+ don't try to fclose it.
+
+ * client.c (handle_m, handle_e): Use fwrite, rather than a loop of
+ putc's. Sometimes these streams are unbuffered.
+
+Tue Apr 4 11:33:56 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * (DISTFILES): Include cvsbug.sh, ChangeLog, NOTES, RCS-patches,
+ README-rm-add, ChangeLog.fsf, sanity.sh, sanity.el, and
+ .cvsignore.
+
+Mon Mar 27 08:58:42 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * rcs.c (RCS_parsercsfile_i): Accept `dead' state regardless of
+ DEATH_STATE define. Revise comments regarding DEATH_STATE versus
+ CVSDEA versus the scheme which uses a patched RCS.
+ * README-rm-add, RCS-patches: Explain what versions of CVS need
+ RCS patches.
+
+Sat Mar 25 18:51:39 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * server.c (server_cleanup): Only do the abysmal kludge of waiting
+ for command and draining the pipe #ifdef sun. The code makes
+ assumptions not valid on all systems, and is only there to
+ workaround a SunOS bug.
+
+Wed Mar 22 21:55:56 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (mkdir_p): Call stat only if we get the EACCES. Faster
+ and more elegant.
+
+Tue Jan 31 20:59:19 1995 Ken Raeburn <raeburn@cujo.cygnus.com>
+
+ * server.c: Try to avoid starting the "rm -rf" at cleanup time
+ until after subprocesses have finished.
+ (command_fds_to_drain, max_command_fd): New variables.
+ (do_cvs_command): Set them.
+ (command_pid_is_dead): New variable.
+ (wait_sig): New function.
+ (server_cleanup): If command_pid is nonzero, wait for it to die,
+ draining output from it in the meantime. If nonzero SIG was
+ passed, send a signal to the subprocess, to encourage it to die
+ soon.
+
+ * main.c (usage): Argument is now `const char *const *'.
+ * cvs.h (usage): Changed prototype.
+ (USE): Make new variable `const'.
+ * add.c (add_usage), admin.c (admin_usage), checkout.c
+ (checkout_usage, export_usage, checkout), commit.c (commit_usage),
+ diff.c (diff_usage), history.c (history_usg), import.c
+ (import_usage, keyword_usage), log.c (log_usage), main.c (usg),
+ patch.c (patch_usage), release.c (release_usage), remove.c
+ (remove_usage), rtag.c (rtag_usage), server.c (server), status.c
+ (status_usage), tag.c (tag_usage), update.c (update_usage): Usage
+ messages are now const arrays of pointers to const char.
+
+ * import.c (comtable): Now const.
+ * main.c (rcsid): Now static.
+ (cmd): Now const.
+ (main): Local variable CM now points to const.
+ * server.c (outbuf_memory_error): Local var MSG now const.
+
+ * client.c (client_commit_usage): Deleted.
+
+Sat Dec 31 15:51:55 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * logmsg.c (do_editor): Allocate enough space for trailing '\0'.
+
+Fri Mar 3 11:59:49 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * cvsbug.sh: Call it "Cyclic CVS" now, not "Remote CVS". Call it
+ version C1.4A, not 1.4A2-remote. Send bugs to cyclic-cvs, not
+ remote-cvs.
+
+ * classify.c (Classify_File): Put check for dead file inside
+ "#ifdef DEATH_SUPPORT".
+
+Thu Feb 23 23:03:43 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * update.c (join_file): Don't pass the -E option to rcsmerge here,
+ either (see Jan 22 change).
+
+Mon Feb 13 13:28:46 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * cvsbug.sh: Send bug reports to remote-cvs@cyclic.com, rather
+ than to the ordinary CVS bug address. This does mean we'll have
+ to wade through GNATS-style bug reports, sigh.
+
+Wed Feb 8 06:42:27 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * server.c: Don't include <sys/stat.h>; system.h already does, and
+ 4.3BSD can't take it twice.
+
+ * subr.c [! HAVE_VPRINTF] (run_setup, run_args): Don't use va_dcl
+ in declaration. Declare the a1..a8 args which are used in the
+ sprintf call.
+ * cvs.h [! HAVE_VPRINTF] (run_setup, run_args): Don't prototype
+ args, to avoid conflicting with the function definitions
+ themselves.
+
+Tue Feb 7 20:10:00 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * client.c (update_entries): Pass the patch subprocess the switch
+ "-b ~", not "-b~"; the latter form seems not to work with patch
+ version 2.0 and earlier --- it takes the next argv element as the
+ backup suffix, and thus doesn't notice that the patch file's name
+ has been specified, thus doesn't find the patch, thus... *aargh*
+
+Fri Feb 3 20:28:21 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * log.c (log_option_with_arg): New function.
+ (cvslog): Use it and send_arg to handle the rlog options that take
+ arguments. The code used to use send_option_string for
+ everything, which assumes that "-d1995/01/02" is equivalent to
+ "-d -1 -9 -9 -5 ...".
+
+Tue Jan 31 15:02:01 1995 Jim Blandy <jimb@floss.life.uiuc.edu>
+
+ * server.c: #include <sys/stat.h> for the new stat call in mkdir_p.
+ (mkdir_p): Don't try to create the intermediate directory if it
+ exists already. Some systems return EEXIST, but others return
+ EACCES, which we can't otherwise distinguish from a real access
+ problem.
+
+Sun Jan 22 15:25:45 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * update.c (merge_file): My rcsmerge doesn't accept a -E option,
+ and it doesn't look too important, so don't pass it.
+
+Fri Jan 20 14:24:58 1995 Ian Lance Taylor <ian@sanguine.cygnus.com>
+
+ * client.c (do_deferred_progs): Don't try to chdir to toplevel_wd
+ if it has not been set.
+ (process_prune_candidates): Likewise.
+
+Mon Nov 28 09:59:14 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (client_commit): Move guts of function from here...
+ * commit.c (commit): ...to here.
+
+Mon Nov 28 15:14:36 1994 Ken Raeburn <raeburn@cujo.cygnus.com>
+
+ * server.c (buf_input_data, buf_send_output): Start cpp directives
+ in column 1, otherwise Sun 4 pcc complains.
+
+Mon Nov 28 09:59:14 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (add_prune_candidate): Don't try to prune ".".
+
+Tue Nov 22 05:27:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c, client.c: More formatting cleanups.
+
+ * client.h, client.c: New variable client_prune_dirs.
+ * update.c (update), checkout.c (checkout): Set it.
+ * client.c (add_prune_candidate, process_prune_candidates): New
+ functions.
+ (send_repository, call_in_directory, get_responses_and_close):
+ Call them.
+
+Wed Nov 23 01:17:32 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * server.c (do_cvs_command): Don't select on STDOUT_FILENO unless
+ we have something to write.
+
+Tue Nov 22 05:27:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * remove.c (remove_fileproc): Only call server_checked_in if we
+ actually are changing the entries file.
+
+ * server.c (server_write_entries): New function.
+ (dirswitch, do_cvs_command): Call it.
+ (serve_entry, serve_updated): Just update in-memory data
+ structures, don't mess with CVS/Entries file.
+
+Mon Nov 21 10:15:11 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (server_checked_in): Set scratched_file to NULL after
+ using it.
+
+ * checkin.c (Checkin): If the file was changed by the checkin,
+ call server_updated not server_checked_in.
+
+Sun Nov 20 08:01:51 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_repository): Move check for update_dir NULL to
+ before where we check last_update_dir. Check for "" here too.
+
+ * client.c (send_repository): Use new argument dir.
+
+ * client.c: Pass new argument dir to send_repository and
+ send_a_repository.
+
+ * server.c, server.h (server_prog): New function.
+ * modules.c (do_modules): Call it if server_expanding.
+ * client.c: Support Set-checkin-prog and Set-update-prog responses.
+ * server.c, client.c: Add Checkin-prog and Update-prog requests.
+
+Fri Nov 18 14:04:38 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (get_short_pathname, is_cvsroot_level,
+ call_in_directory): Base whether this is new-style or
+ old-style based on whether we actually used the Directory request,
+ not based on whether the pathname is absolute. Rename
+ directory_supported to use_directory.
+ * server.c: Rename use_relative_pathnames to use_dir_and_repos.
+ * client.c (send_a_repository): If update_dir is absolute, don't
+ use it to try to reconstruct how far we have recursed.
+
+ * server.c, server.h, client.c, client.h, vers_ts.c, update.h:
+ More cosmetic changes (identation, PARAMS vs. PROTO, eliminate
+ alloca, etc.) to remote CVS to make it more like the rest of CVS.
+
+ * server.c: Make server_temp_dir just the dir name, not the name
+ with "%s" at the end.
+ * server.c, client.c: Add "Max-dotdot" request, and use it to make
+ extra directories in server_temp_dir if needed.
+
+Thu Nov 17 09:03:28 1994 Jim Kingdon <kingdon@cygnus.com>
+
+ * client.c: Fix two cases where NULL was used and 0 was meant.
+
+Mon Nov 14 08:48:41 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (serve_unchanged): Set noexec to 0 when calling Register.
+
+ * update.c (merge_file): Don't call xcmp if noexec.
+
+Fri Nov 11 13:58:22 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (call_in_directory): Deal with it if reposdirname is
+ not a subdirectory of toplevel_repos.
+
+Mon Nov 7 09:12:01 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * patch.c: If file is removed and we don't have a tag or date,
+ just print "current release".
+
+ * classify.c (Classify_File): Treat dead files appropriately.
+
+Fri Nov 4 07:33:03 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * main.c (main) [SERVER_SUPPORT]: Move call to getwd past where we
+ know whether we are the server or not. Set CurDir to "<remote>"
+ if we are the server.
+
+ * client.c: Remove #if 0'd function option_with_arg.
+ Remove #if 0'd code pertaining to the old way of logging the
+ session.
+
+ * client.c (start_rsh_server): Don't invoke the server with the
+ -d option.
+ * server.c (serve_root): Test root for validity, just like main.c
+ does for non-remote CVS.
+ * main.c (main): If `cvs server' happens with a colon in the
+ CVSroot, just handle it normally; don't make it an error.
+
+Wed Nov 2 11:09:38 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_dirent_proc): If dir does not exist, just return
+ R_SKIP_ALL.
+
+ * server.c, client.c: Add Directory request and support for
+ local relative pathnames (along with the repository absolute
+ pathnames).
+ * update.c, add.c, checkout.c, checkin.c, cvs.h, create_adm.c,
+ commit.c, modules.c, server.c, server.h, remove.c, client.h:
+ Pass update_dir to server_* functions. Include update_dir in
+ more error messages.
+
+Fri Oct 28 08:54:00 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c: Reformat to bring closer to cvs standards for brace
+ position, comment formatting, etc.
+
+ * sanity.sh: Remove wrong "last mod" line. Convert more tests to
+ put PASS or FAIL in log file. Change it so arguments to the
+ script specify which tests to run.
+
+ * client.c, client.h, server.c, checkout.c: Expand modules in
+ separate step from the checkout itself.
+
+Sat Oct 22 20:33:35 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * update.c (join_file): When checking for null return from
+ RCS_getversion, still do return even if quiet flag is set.
+
+Thu Oct 13 07:36:11 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_files): Call send_repository even if
+ toplevel_repos was NULL.
+
+ * server.c (server_updated): If joining, don't remove file.
+
+ * update.c (join_file): If server and file is unmodified, check it
+ out before joining. After joining, call server_updated. New
+ argument repository.
+
+ * server.c, server.h (server_copy_file): New function.
+ * update.c (update_file_proc, join_file): Call it.
+ * client.c (copy_file, handle_copy_file): New functions.
+ * client.c (responses): Add "Copy-file".
+
+ * client.c, client.h: Make toplevel_wd, failed_patches and
+ failed_patches_count extern.
+ * client.c (client_update): Move guts of function from here...
+ * update.c (update): ...to here.
+
+ * client.c, checkout.c: Likewise for checkout.
+
+ * client.c (is_cvsroot_level): New function.
+ (handle_set_sticky, handle_clear_sticky,
+ handle_clear_static_directory): Call it, instead of checking
+ short_pathname for a slash.
+
+ * client.c, client.h (client_process_import_file,
+ client_import_done): New functions.
+ * import.c (import, import_descend): Use them.
+ * import.c (import_descend): If server, don't mention ignored CVS
+ directories.
+ * import.c (import_descend_dir): If client, don't print warm
+ fuzzies, or make directories in repository. If server, print warm
+ fuzzies to stdout not stderr.
+ * client.c (send_modified): New function, broken out from
+ send_fileproc.
+ (send_fileproc): Call it.
+
+ * client.c (handle_clear_sticky, handle_set_sticky,
+ handle_clear_static_directory, handle_set_static_directory): If
+ command is export, just return.
+ (call_in_directory, update_entries): If command is export, don't
+ create CVS directories, CVS/Entries files, etc.
+ * update.c (update_filesdone_proc): Don't remove CVS directories if
+ client_active.
+
+ * client.c (send_a_repository): Instead of insisting that
+ repository end with update_dir, just strip as many pathname
+ components from the end as there are in update_dir.
+
+ * Makefile.in (remotecheck): New target, pass -r to sanity.sh.
+ * sanity.sh: Accept -r argument which means to test remote cvs.
+
+ * tag.c (tag), rtag.c (rtag), patch.c (patch), import.c (import),
+ admin.c (admin), release.c (release): If client_active, connect to
+ the server and send the right requests.
+ * main.c (cmds): Add these commands.
+ (main): Remove code which would strip hostname off cvsroot and try
+ the command locally. There are no longer any commands which are
+ not supported.
+ * client.c, client.h (client_rdiff, client_tag, client_rtag,
+ client_import, client_admin, client_export, client_history,
+ client_release): New functions.
+ * server.c (serve_rdiff, serve_tag, serve_rtag, serve_import,
+ serve_admin, serve_export, serve_history, serve_release): New
+ functions.
+ (requests): List them.
+ * server.c: Declare cvs commands (add, admin, etc.).
+ * cvs.h, server.h: Don't declare any of them here.
+ * main.c: Restore declarations of cvs commands which were
+ previously removed.
+
+ * cvs.h: New define DEATH_STATE, commented out for now.
+ * rcs.c (RCS_parsercsfile_i), commit.c (remove_file, checkaddfile)
+ [DEATH_STATE]: Use RCS state to record a dead file.
+
+Mon Oct 3 09:44:54 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * status.c (status_fileproc): Now that ts_rcs is just one time,
+ don't try to print the second time from it. (Same as raeburn 20
+ Aug change, it accidentally got lost in 1.4 Alpha-1 merge).
+
+ * cvs.h (CVSDEA): Added (but commented out for now).
+ * rcs.c (RCS_parsercsfile_i) [CVSDEA]: Also look in CVSDEA to see if
+ something is dead.
+ * commit.c (ci_new_rev, mark_file) [CVSDEA]: New functions.
+ (remove_file, checkaddfile) [CVSDEA]: Use them instead of ci -K.
+ * find_names.c (find_dirs) [CVSDEA]: Don't match CVSDEA directories.
+ * update.c (checkout_file): Check RCS_isdead rather than relying
+ on co to not create the file.
+
+ * sanity.sh: Direct output to logfile, not /dev/null.
+
+ * subr.c (run_exec): Print error message if we are unable to exec.
+
+ * commit.c (remove_file): Call Scratch_Entry when removing tag
+ from file. The DEATH_SUPPORT ifdef was erroneous.
+
+Sun Oct 2 20:33:27 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * commit.c (checkaddfile): Instead of calling isdir before
+ attempting to create the directory, just ignore EEXIST errors from
+ mkdir. (This removes some DEATH_SUPPORT ifdefs which actually had
+ nothing to do with death support).
+
+Thu Sep 29 09:23:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * diff.c (diff): Search attic too if we have a second tag/date.
+ (diff_fileproc): If we have a second tag/date, don't do all the
+ checking regarding the user file.
+
+Mon Sep 26 12:02:15 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * checkin.c (Checkin): Check for error from unlink_file.
+
+Mon Sep 26 08:51:10 1994 Anthony J. Lill (ajlill@ajlc.waterloo.on.ca)
+
+ * rcs.c (getrcskey): Allocate space for terminating '\0' if
+ necessary.
+
+Sat Sep 24 09:07:37 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * commit.c (commit_fileproc): Set got_message = 1 when calling
+ do_editor (accidentally omitted from last change).
+
+Fri Sep 23 11:59:25 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Revert buggy parts of Rich's change of 1 Nov 1993 (keeping the
+ dynamic buffer allocation, which was the point of that change).
+ * logmsg.c (do_editor): Reinstate message arg, but make it char
+ **messagep instead of char *message. Change occurances of message
+ to *messagep. Char return type from char * back to void.
+ * cvs.h: Change do_editor declaration.
+ * commit.c: Reinstate got_message variable
+ (commit_filesdoneproc, commit_fileproc, commit_direntproc): Use it.
+ * import.c (import), commit.c (commit_fileproc,
+ commit_direntproc): Pass &message to do_editor; don't expect it to
+ return a value.
+ * client.c (client_commit): Likewise.
+ * import.c (import): Deal with it if message is NULL.
+
+Wed Sep 21 09:43:25 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (server_updated): If the file doesn't exist, skip it.
+
+ * diff.c, client.h, client.c: Rename diff_client_senddate to
+ client_senddate and move from diff.c to client.c.
+ * client.c (client_update, client_checkout): Use it.
+
+Sat Sep 17 08:36:58 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * checkout.c (checkout_proc): Don't pass NULL to Register for
+ version. (should fix "cvs co -r <nonexistent-tag> <file>"
+ coredump on Solaris).
+
+Fri Sep 16 08:38:02 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * diff.c (diff_fileproc): Set top_rev from vn_user, not vn_rcs.
+ Rename it to user_file_rev because it need not be the head of any
+ branch.
+ (diff_file_nodiff): After checking user_file_rev, if we have both
+ use_rev1 and use_rev2, compare them instead of going on to code
+ which assumes use_rev2 == NULL.
+
+Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * status.c (status): Return a value in client_active case.
+
+Thu Sep 15 15:02:12 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * server.c (serve_modified): Create the file even if the size is
+ zero.
+
+Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * lock.c (readers_exist): Clear errno each time around the loop,
+ not just the first time.
+
+ * client.c (start_server): Don't send Global_option -q twice.
+
+ * no_diff.c (No_Difference): Check for error from unlink.
+
+ * no_diff.c, cvs.h (No_Difference): New args repository,
+ update_dir. Call server_update_entries if needed. Use update_dir
+ in error message.
+ * classify.c (Classify_File): Pass new args to No_Difference.
+
+ * server.c (server_update_entries, server_checked_in,
+ server_updated): Don't do anything if noexec.
+
+ * client.c (send_fileproc): Rather than guessing how big the gzip
+ output may be, just realloc the buffer as needed.
+
+Tue Sep 13 13:22:03 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * lock.c: Check for errors from unlink, readdir, and closedir.
+
+ * classify.c (Classify_File): Pass repository and update_dir to
+ sticky_ck.
+ (sticky_ck): New args repository and update_dir.
+ * server.c, server.h (server_update_entries): New function.
+ * classify.c (sticky_ck): Call it.
+ * client.c: New response "New-entry".
+ * client.c (send_fileproc): Send tag/date from vers->entdata, not
+ from vers itself.
+
+Mon Sep 12 07:07:05 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c: Clean up formatting ("= (errno)" -> "= errno").
+
+ * cvs.h: Declare strerror.
+
+ * client.c: Add code to deal with Set-sticky and Clear-sticky
+ responses, and Sticky request.
+ * server.c: Add code to deal with Sticky request.
+ * server.c, server.h (server_set_sticky): New function.
+ * create_adm.c (Create_Admin), update.c (update, update_dirent_proc),
+ commit.c (commit_dirleaveproc): Call it.
+ * client.c, client.h (send_files): Add parameter aflag.
+ * add.c (add), diff.c (diff), log.c (cvslog), remove.c (cvsremove),
+ status.c (status),
+ client.c (client_commit, client_update, client_checkout): Pass it.
+ * client.c (client_update): Add -A flag.
+
+Fri Sep 9 07:05:35 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * entries.c (WriteTag): Check for error from unlink_file.
+
+ * server.c (server_updated): Initialize size to 0. Previously if
+ the file was zero length, the variable size got used without being
+ set.
+
+Thu Sep 8 14:23:05 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (serve_repository): Check for error from fopen on
+ CVSADM_ENT.
+
+ * update.c (update, update_dirent_proc): Check for errors when
+ removing Entries.Static.
+
+ * client.c: Add code to deal with Set-static-directory and
+ Clear-static-directory responses, and Static-directory request.
+ * server.c, server.h (server_clear_entstat, server_set_entstat):
+ New functions.
+ * update.c, checkout.c, modules.c: Call them.
+ * server.c: Add code to deal with Static-directory request.
+
+ * server.c, client.c: Use strchr and strrchr instead of index and
+ rindex.
+
+ * server.c (serve_unchanged, serve_lost): Change comments which
+ referred to changing timestamp; we don't always change the
+ timestamp in those cases anymore.
+
+Wed Sep 7 10:58:12 1994 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * cvsrc.c (read_cvsrc): Don't call getenv() three times when one
+ time will do.
+
+ * subr.c (xmalloc, xrealloc): Change type of bytes argument from
+ int to size_t and remove the test that checks if it is less than
+ zero.
+ * cvs.h (xmalloc, xrealloc): Update prototype.
+
+Thu Sep 1 12:22:20 1994 Jim Kingdon (kingdon@cygnus.com)
+
+ * update.c (merge_file, join_file): Pass -E to rcsmerge.
+ (merge_file): If rcsmerge doesn't change the file, say so.
+
+ * recurse.c, cvs.h (start_recursion): New argument wd_is_repos.
+ * recurse.c (start_recursion): Use it instead of checking whether
+ command_name is rtag to find out if we are cd'd to the repository.
+ * client.c, update.c, commit.c, status.c, diff.c, log.c, admin.c,
+ remove.c, tag.c: Pass 0 for wd_is_repos.
+ * rtag.c, patch.c: Pass 1 for wd_is_repos.
+
+ * classify.c, cvs.h (Classify_File): New argument pipeout.
+ * classify.c (Classify_File): If pipeout, don't complain if the
+ file is already there.
+ * update.c, commit.c, status.c: Change callers.
+
+ * mkmodules.c (main): Don't print "reminders" if commitinfo,
+ loginfo, rcsinfo, or editinfo files are missing.
+
+Mon Aug 22 23:22:59 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * server.c (strerror): Static definition replaced by extern
+ declaration.
+
+Sun Aug 21 07:16:27 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * client.c (update_entries): Run "patch" with input from
+ /dev/null, so if it's the wrong version, it fails quickly rather
+ than waiting for EOF from terminal before failing.
+
+Sat Aug 20 04:16:33 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * server.c (serve_unchanged): Instead of creating a file with a
+ zero timestamp, rewrite the entries file to have "=" in the
+ timestamp field.
+ * vers_ts.c (mark_lost, mark_unchanged): New macros.
+ (time_stamp_server): Use them, for clarity. Interpret "="
+ timestamp as an unchanged file. A zero-timestamp file should
+ never be encountered now in use_unchanged mode.
+
+ * client.c (start_server): If CVS_CLIENT_PORT indicates a
+ non-positive port number, skip straight to rsh connection.
+
+ * status.c (status_fileproc): Fix ts_rcs reference when printing
+ version info, to correspond to new Entries file format. Don't
+ print it at all if server_active, because it won't have any useful
+ data.
+
+Thu Aug 18 14:38:21 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * cvs.h (status): Declare.
+ * client.c (client_status): New function.
+
+ * client.h (client_status): Declare.
+ * main.c (cmds): Include it.
+ * server.c (serve_status): New function.
+ (requests): Add it.
+ * status.c (status): Do the remote thing if client_active.
+
+ * client.c (supported_request): New function.
+ (start_server): Use it.
+
+ * server.c (receive_partial_file): New function, broken out from
+ serve_modified. Operate with fixed-size local buffer, instead of
+ growing stack frame by entire file size.
+ (receive_file): New function, broken out from serve_modified.
+ (serve_modified): Call it.
+ (server): Print out name of unrecognized request.
+
+ More generic stream-filtering support:
+ * client.c (close_on_exec, filter_stream_through_program): New
+ functions.
+ (server_fd): New variable.
+ (get_responses_and_close): Direct non-rsh connection is now
+ indicated by server_fd being non-negative. File descriptors for
+ to_server and from_server may now be different in case "tee"
+ filtering is being done. Wait for rsh_pid specifically.
+ (start_server): Use filter_stream_through_program for "tee"
+ filter, and enable it for direct Kerberos-authenticated
+ connections. Use dup to create new file descriptors for server
+ connection if logging is enabled.
+ (start_rsh_server): Disable code that deals with logging.
+
+ Per-file compression support:
+ * cvs.h (gzip_level): Declare.
+ * main.c (usg): Describe new -z argument.
+ (main): Recognize it and set gzip_level.
+ * client.c (filter_through_gzip, filter_through_gunzip): New
+ functions to handle compression.
+ (update_entries): If size starts with "z", uncompress
+ (start_server): If gzip_level is non-zero and server supports it,
+ issue gzip-file-contents request.
+ (send_fileproc): Optionally compress file contents. Use a
+ slightly larger buffer, anticipating the worst case.
+ * server.c (gzip_level): Define here.
+ (receive_file): Uncompress file contents if needed.
+ (serve_modified): Recognize "z" in file size and pass receive_file
+ appropriate flag.
+ (buf_read_file_to_eof, buf_chain_length): New functions.
+ (server_updated): Call them when sending a compressed file.
+ (serve_gzip_contents): New function; set gzip_level.
+ (requests): Added gzip-file-contents request.
+
+Wed Aug 17 09:37:44 1994 J.T. Conklin (jtc@cygnus.com)
+
+ * find_names.c (find_dirs): Use 4.4BSD filesystem feature (it
+ contains the file type in the dirent structure) to avoid
+ stat'ing each file.
+
+ * commit.c (remove_file,checkaddfile): Change type of umask
+ variables from int to mode_t.
+ * subr.c (): Likewise.
+
+Tue Aug 16 19:56:34 1994 Mark Eichin (eichin@cygnus.com)
+
+ * diff.c (diff_fileproc): Don't use diff_rev* because they're
+ invariant across calls -- add new variable top_rev.
+ (diff_file_nodiff): After checking possible use_rev* values, if
+ top_rev is set drop it in as well (if we don't already have two
+ versions) and then clear it for next time around.
+
+Wed Aug 10 20:50:47 1994 Mark Eichin (eichin@cygnus.com)
+
+ * diff.c (diff_fileproc): if ts_user and ts_rcs match, then the
+ file is at the top of the tree -- so we might not even have a
+ copy. Put the revision into diff_rev1 or diff_rev2.
+
+Wed Aug 10 14:55:38 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * server.c (do_cvs_command): Use waitpid.
+
+ * subr.c (run_exec): Always use waitpid.
+
+ * Makefile.in (CC, LIBS): Define here, in case "make" is run in
+ this directory instead of top level.
+
+Wed Aug 10 13:57:06 1994 Mark Eichin (eichin@cygnus.com)
+
+ * client.c (krb_get_err_text): use HAVE_KRB_GET_ERR_TEXT to
+ determine if we need to use the array or the function.
+ * main.c: ditto.
+
+Tue Aug 9 16:43:30 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * entries.c (ParseEntries): If timestamp is in old format, rebuild
+ it in the new format. Fudge an unmatchable entry that won't
+ trigger this code next time around, if the file is modified.
+
+ * vers_ts.c (time_stamp): Only put st_mtime field into timestamp,
+ and use GMT time for it. With st_ctime or in local time, copying
+ trees between machines in different time zones makes all the files
+ look modified.
+ (time_stamp_server): Likewise.
+
+Tue Aug 9 19:40:51 1994 Mark Eichin (eichin@cygnus.com)
+
+ * main.c (main): use krb_get_err_text function instead of
+ krb_err_txt array.
+
+Thu Aug 4 15:37:50 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * main.c (main): When invoked as kserver, set LOGNAME and USER
+ environment variables to the remote user name.
+
+Thu Aug 4 07:44:37 1994 Mark Eichin (eichin@cygnus.com)
+
+ * client.c: (handle_valid_requests): if we get an option that has
+ rq_enableme set, then send that option. If it is UseUnchanged, set
+ use_unchanged so that the rest of the client knows about
+ it. (Could become a more general method for dealing with protocol
+ upgrades.)
+ (send_fileproc): if use_unchanged didn't get set, send an
+ old-style "Lost" request, otherwise send an "Unchanged" request.
+ * server.c (serve_unchanged): new function, same as serve_lost,
+ but used in the opposite case.
+ (requests): add new UseUnchanged and Unchanged requests, and make
+ "Lost" optional (there isn't a good way to interlock these.)
+ * server.h (request.status): rq_enableme, new value for detecting
+ compatibility changes.
+ * vers_ts.c (time_stamp_server): swap meaning of zero timestamp if
+ use_unchanged is set.
+
+Tue Jul 26 10:19:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * sanity.sh: Separate CVSROOT_FILENAME, which must be the filename
+ of the root, from CVSROOT, which can include a hostname for
+ testing remote CVS. (but the tests aren't yet prepared to deal
+ with the bugs in remote CVS).
+
+ * import.c (update_rcs_file): Change temporary file name in TMPDIR
+ from FILE_HOLDER to cvs-imp<process-id>.
+
+ * sanity.sh: Add ">/dev/null" and "2>/dev/null" many places to
+ suppress spurious output. Comment out tests which don't work (cvs
+ add on top-level directory, cvs diff when non-committed adds or
+ removes have been made, cvs release, test 53 (already commented as
+ broken), retagging without deleting old tag, test 63). Now 'make
+ check' runs without any failures.
+
+Fri Jul 15 12:58:29 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * Makefile.in (install): Do not depend upon installdirs.
+
+Thu Jul 14 15:49:42 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c, server.c: Don't try to handle alloca here; it's
+ handled by cvs.h.
+
+Tue Jul 12 13:32:40 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (update_entries): Reset stored_checksum_valid if we
+ quit early because of a patch failure.
+
+Fri Jul 8 11:13:05 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (responses): Mark "Remove-entry" as optional.
+
+Thu Jul 7 14:07:58 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * server.c (server_updated): Add new checksum argument. If it is
+ not NULL, and the client supports the "Checksum" response, send
+ it.
+ * server.h (server_updated): Update prototype.
+ * update.c: Include md5.h.
+ (update_file_proc): Pass new arguments to patch_file and
+ server_updated.
+ (patch_file): Add new checksum argument. Set it to the MD5
+ checksum of the version of the file being checked out.
+ (merge_file): Pass new argument to server_updated.
+ * client.c: Include md5.h.
+ (stored_checksum_valid, stored_checksum): New static variables.
+ (handle_checksum): New static function.
+ (update_entries): If a checksum was received, check it against the
+ MD5 checksum of the final file.
+ (responses): Add "Checksum".
+ (start_server): Clear stored_checksum_valid.
+ * commit.c (commit_fileproc): Pass new argument to server_updated.
+
+ * client.h (struct response): Move definition in from client.c,
+ add status field.
+ (responses): Declare.
+ * client.c (struct response): Remove definition; moved to
+ client.h.
+ (responses): Make non-static. Initialize status field.
+ * server.c (serve_valid_responses): Check and record valid
+ responses, just as in handle_valid_requests in client.c.
+
+ * diff.c (diff_client_senddate): New function.
+ (diff): Use it to send -D arguments to server.
+
+Wed Jul 6 12:52:37 1994 J.T. Conklin (jtc@phishhead.cygnus.com)
+
+ * rcs.c (RCS_parsercsfile_i): New function, parse RCS file
+ referenced by file ptr argument.
+ (RCS_parsercsfile): Open file and pass its file ptr to above function.
+ (RCS_parse): Likewise.
+
+Wed Jul 6 01:25:38 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * client.c (update_entries): Print message indicating that an
+ unpatchable file will be refetched.
+ (client_update): Print message when refetching unpatchable files.
+
+Fri Jul 1 07:16:29 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_dirent_proc): Don't call send_a_repository if
+ repository is "".
+
+Fri Jul 1 13:58:11 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (last_dirname, last_repos): Move out of function.
+ (failed_patches, failed_patches_count): New static variables.
+ (update_entries): If patch program fails, save short_pathname in
+ failed_patches array, only exit program if retcode is -1, and
+ return out of the function rather than update the Entries line.
+ (start_server): Clear toplevel_repos, last_dirname, last_repos.
+ (client_update): If failed_patches is not NULL after doing first
+ update, do another update, but remove all the failed files first.
+
+Thu Jun 30 09:08:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (requests): Add request "Global_option".
+ (serve_global_option): New function, to handle it.
+ * client.c (start_server): Deal with global options. Check for
+ errors from fprintf.
+
+ * client.c (send_fileproc): Split out code which sends repository
+ into new function send_a_repository. Also, deal with update_dir
+ being ".".
+ (send_dirent_proc): Call send_a_repository.
+ * add.c (add): If client_active, do special processing for
+ directories.
+ (add_directory): If server_active, don't try to create CVSADM
+ directory.
+
+Thu Jun 30 11:58:52 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (update_entries): If patch succeeds, remove the backup
+ file.
+ * server.c (server_updated): Add new argument file_info. If it is
+ not NULL, use it rather than sb to get the file mode.
+ * server.h (server_updated): Update prototype for new argument.
+ * update.c (update_file_proc): Pass new arguments to patch_file
+ and server_updated.
+ (patch_file): Add new argument file_info. Don't use -p to check
+ out new version, check it out into file and rename that to file2.
+ If result is not readable, assume file is dead and set docheckout.
+ Call xchmod on file2. Close the patch file after checking for a
+ binary diff. Set file_info to the results of stat on file2.
+ (merge_file): Pass new argument to server_updated.
+ * commit.c (commit_fileproc): Pass new argument to server_updated.
+
+Wed Jun 29 13:00:41 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (krb_realmofhost): Declare, since it's not the current
+ <krb.h>.
+ (start_server): Save the name returned by gethostbyname. Call
+ krb_realmofhost to get the realm. Pass the resulting realm to
+ krb_sendauth. Pass the saved real name to krb_sendauth, rather
+ than server_host.
+
+ * update.c (update_file_proc): Pass &docheckout to patch_file. If
+ it is set to 1, fall through to T_CHECKOUT case.
+ (patch_file): Add docheckout argument. Set it to 1 if we can't
+ make a patch. Check out the files and run diff rather than
+ rcsdiff. If either file does not end in a newline, we can't make
+ a patch. If the patch starts with the string "Binary", assume
+ one or the other is a binary file, and that we can't make a patch.
+
+Tue Jun 28 11:57:29 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (update_entries): If the patch file is empty, don't run
+ patch program; avoids error message.
+
+ * classify.c (Classify_File): Return T_CHECKOUT, not T_PATCH, if
+ the file is in the Attic.
+
+ * cvs.h (enum classify_type): Add T_PATCH.
+ * config.h (PATCH_PROGRAM): Define.
+ * classify.c (Classify_File): If user file exists and is not
+ modified, and using the same -k options, return T_PATCH instead of
+ T_CHECKOUT.
+ * update.c (patches): New static variable.
+ (update): Add u to gnu_getopt argument. Handle it.
+ (update_file_proc): Handle T_PATCH.
+ (patch_file): New static function.
+ * server.h (enum server_updated_arg4): Add SERVER_PATCHED.
+ * server.c (server_updated): Handle SERVER_PATCHED by sending
+ "Patched" command.
+ (serve_ignore): New static function.
+ (requests): Add "update-patches".
+ (client_update): If the server supports "update-patches", send -u.
+ * client.c (struct update_entries_data): Change contents field
+ from int to an unnamed enum.
+ (update_entries): Correponding change. If contents is
+ UPDATE_ENTRIES_PATCH, pass the input to the patch program.
+ (handle_checked_in): Initialize contents to enum value, not int.
+ (handle_updated, handle_merged): Likewise.
+ (handle_patched): New static function.
+ (responses): Add "Patched".
+ * commit.c (check_fileproc): Handle T_PATCH.
+ * status.c (status_fileproc): Likewise.
+
+ * client.c (start_server): If CVS_CLIENT_PORT is set in the
+ environment, connect to that port, rather than looking up "cvs" in
+ /etc/services. For debugging.
+
+Tue Jun 21 12:48:16 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * update.c (joining): Return result of comparing pointer with
+ NULL, not result of casting (truncating, on Alpha) pointer to int.
+
+ * main.c (main) [HAVE_KERBEROS]: Impose a umask if starting as
+ Kerberos server, so temp directories won't be world-writeable.
+
+ * update.c (update_filesdone_proc) [CVSADM_ROOT]: If environment
+ variable CVS_IGNORE_REMOTE_ROOT is set and repository is remote,
+ don't create CVS/Root file.
+ * main.c (main): If env var CVS_IGNORE_REMOTE_ROOT is set, don't
+ check CVS/Root.
+
+Fri Jun 10 18:48:32 1994 Mark Eichin (eichin@cygnus.com)
+
+ * server.c (O_NDELAY): use POSIX O_NONBLOCK by default, unless it
+ isn't available (in which case substitute O_NDELAY.)
+
+Thu Jun 9 19:17:44 1994 Mark Eichin (eichin@cygnus.com)
+
+ * server.c (server_cleanup): chdir out of server_temp_dir before
+ deleting it (so that it works on non-BSD systems.) Code for choice
+ of directory cloned from server().
+
+Fri May 27 18:16:01 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * client.c (update_entries): Add return type of void.
+ (get_responses_and_close): If using Kerberos and from_server and
+ to_server are using the same file descriptor, use shutdown, not
+ fclose. Close from_server.
+ (start_server): New function; most of old version renamed to
+ start_rsh_server.
+ (start_rsh_server): Mostly renamed from old start_server.
+ (send_fileproc): Use %lu and cast sb.st_size in fprintf call.
+ (send_files): Remove unused variables repos and i.
+ (option_no_arg): Comment out; unused.
+ * main.c (main): Initialize cvs_update_env to 0. If command is
+ "kserver", authenticate and change command to "server". If
+ command is "server", don't call Name_Root, don't check access to
+ history file, and don't assume that CVSroot is not NULL.
+ * server.c (my_memmove): Removed.
+ (strerror): Change check from STRERROR_MISSING to HAVE_STRERROR.
+ (serve_root): Likewise for putenv.
+ (serve_modified): Initialize buf to NULL.
+ (struct output_buffer, buf_try_send): Remove old buffering code.
+ (struct buffer, struct buffer_data, BUFFER_DATA_SIZE,
+ allocate_buffer_datas, get_buffer_data, buf_empty_p,
+ buf_append_char, buf_append_data, buf_read_file, buf_input_data,
+ buf_copy_lines): New buffering code.
+ (buf_output, buf_output0, buf_send_output, set_nonblock,
+ set_block, buf_send_counted, buf_copy_counted): Rewrite for new
+ buffering code.
+ (protocol, protocol_memory_error, outbuf_memory_error,
+ do_cvs_command, server_updated): Rewrite for new buffering code.
+ (input_memory_error): New function.
+ (server): Put Rcsbin at start of PATH in environment.
+ * Makefile.in: Add @includeopt@ to DEFS.
+
+Fri May 20 08:13:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvs.h, classify.c (Classify_File): New argument update_dir.
+ Include it in user messages.
+ * commit.c (check_fileproc), status.c (status_fileproc), update.c
+ (update_file_proc): Pass update_dir to Classify_File.
+ * commit.c (check_fileproc), update.c (checkout_file):
+ Include update_dir in user messages.
+ * commit.c (check_fileproc) update.c (update_file_proc): Re-word
+ "unknown status" message.
+
+ * server.c (server_checked_in): Deal with the case where
+ scratched_file is set rather than entries_line.
+
+ * entries.c (Register): Write file even if server_active.
+ * add.c (add): Add comment about how we depend on above behavior.
+
+Tue May 17 08:16:42 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * mkmodules.c: Add dummy server_active and server_cleanup, to go
+ with the dummy Lock_Cleanup already there.
+
+ * server.c (server_cleanup): No longer static.
+
+Sat May 7 10:17:17 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Deal with add and remove:
+ * commit.c (checkaddfile): If CVSEXT_OPT or CVSEXT_LOG file does
+ not exist, just silently keep going.
+ (remove_file): If server_active, remove file before creating
+ temporary file with that name.
+ * server.c (serve_remove, serve_add): New functions.
+ (requests): Add them.
+ * server.c (server_register): If options is NULL, it means there
+ are no options.
+ * server.c, server.h (server_scratch_entry_only): New function.
+ New variable kill_scratched_file.
+ (server_scratch, server_updated): Deal with kill_scratched_file.
+ * commit.c (commit_fileproc): If server_active, call
+ server_scratch_entry_only and server_updated.
+ * add.c (add): Add client_active code.
+ (add): If server_active, call server_checked_in for each file added.
+ * remove.c (remove): Add client_active code.
+ (remove_fileproc): If server_active, call server_checked_in.
+ * main.c (cmds), client.c, client.h: New functions client_add and
+ client_remove.
+ * Move declarations of add, cvsremove, diff, and cvslog from
+ main.c to cvs.h.
+ * client.c (call_in_directory): Update comment regarding Root and
+ Repository files.
+ (send_fileproc): Only send Entries line if Version_TS really finds
+ an entry. If it doesn't find one, send Modified.
+ (update_entries): If version is empty or starts with 0 or -,
+ create a dummy timestamp.
+
+Thu May 5 19:02:51 1994 Per Bothner (bothner@kalessin.cygnus.com)
+
+ * recurse/c (start_recursion): If we're doing rtag, and thus
+ have cd'd to the reporsitory, add ,v to a file name before stat'ing.
+
+Wed Apr 20 15:01:45 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * client.c (client_commit): Call ign_setup.
+ (client_update, client_checkout): Likewise.
+ * diff.c (diff): If client, call ign_setup.
+ * log.c (cvslog): Likewise.
+ * update.h (ignlist): Change definition to declaration to avoid
+ depending upon common semantics (not required by ANSI C, and not
+ the default on Irix 5).
+ * update.c (ignlist): Define.
+
+Tue Apr 19 00:02:54 1994 John Gilmore (gnu@cygnus.com)
+
+ Add support for remote `cvs log'; clean up `cvs diff' a bit.
+
+ * client.c (send_arg): Make external.
+ (send_option_string): New function.
+ (client_diff_usage): Remove, unused.
+ (client_diff): Just call diff, not do_diff.
+ (client_log): Add.
+ * client.h (client_log, send_arg, send_option_string): Declare.
+ * cvs.h (cvslog): Declare.
+ * diff.c (do_diff): Fold back into diff(), distinguish by checking
+ client_active.
+ (diff): Remove `-*' arg parsing crud; use send_option_string.
+ * log.c (cvslog): If a client, start the server, pass options
+ and files, and handle server responses.
+ * main.c (cmds): Add client_log.
+ (main): Remove obnoxious message every time CVS/Root is used.
+ Now CVS will be quiet about it -- unless there is a conflict
+ between $CVSROOT or -d value versus CVS/Root.
+ * server.c (serve_log): Add.
+ (requests): Add "log".
+
+Mon Apr 18 22:07:53 1994 John Gilmore (gnu@cygnus.com)
+
+ Add support for remote `cvs diff'.
+
+ * diff.c (diff): Break guts out into new fn do_diff.
+ Add code to handle starting server, writing args,
+ sending files, and retrieving responses.
+ (includes): Use PARAMS for static function declarations.
+ * client.c (to_server, from_server, rsh_pid,
+ get_responses_and_close, start_server, send_files,
+ option_with_arg): Make external.
+ (send_file_names): New function.
+ (client_diff): New function.
+ * client.h (client_diff, to_server, from_server,
+ rsh_pid, option_with_arg, get_responses_and_close, start_server,
+ send_file_names, send_files): Declare.
+ * cvs.h (diff): Declare.
+ * main.c (cmds): Add client_diff to command table.
+ * server.c (serve_diff): New function.
+ (requests): Add serve_diff.
+ (server): Bug fix: avoid free()ing incremented cmd pointer.
+ * update.h (update_filesdone_proc): Declare with PARAMS.
+
+Sat Apr 16 04:20:09 1994 John Gilmore (gnu@cygnus.com)
+
+ * root.c (Name_root): Fix tyop (CVSroot when root meant).
+
+Sat Apr 16 03:49:36 1994 John Gilmore (gnu@cygnus.com)
+
+ Clean up remote `cvs update' to properly handle ignored
+ files (and files that CVS can't identify), and to create
+ CVS/Root entries on the client side, not the server side.
+
+ * client.c (send_fileproc): Handle the ignore list.
+ (send_dirent_proc): New function for handling ignores.
+ (send_files): Use update_filesdone_proc and send_dirent_proc
+ while recursing through the local filesystem.
+ * update.h: New file.
+ * update.c: Move a few things into update.h so that client.c
+ can use them.
+
+Fri Mar 11 13:13:20 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * server.c: If O_NDELAY is not defined, but O_NONBLOCK is, define
+ O_NDELAY to O_NONBLOCK.
+
+Wed Mar 9 21:08:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Fix some spurious remote CVS errors caused by the CVS/Root patches:
+ * update.c (update_filesdone_proc): If server_active, don't try to
+ create CVS/Root.
+ * root.c (Name_Root): Make error messages which happen if root is
+ not an absolute pathname or if it doesn't exist a bit clearer.
+ Skip them if root contains a colon.
+
+Mon Nov 1 15:54:51 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * client.c (client_commit): dynamically allocate message.
+
+Tue Jun 1 17:03:05 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * server.h: remove alloca cruft
+
+ * server.c: replace with better alloca cruft
+
+Mon May 24 11:25:11 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * entries.c (Scratch_Entry): Update our local Entries file even if
+ server_active.
+
+ * server.c (server_scratch, server_register): If both Register
+ and Scratch_Entry happen, use whichever one happened later.
+ If neither happen, silently continue.
+
+ * client.c (client_checkout): Initialize tag and date (eichin and
+ I independently discovered this bug at the same time).
+
+Wed May 19 10:11:51 1993 Mark Eichin (eichin@cygnus.com)
+
+ * client.c (update_entries): handle short reads over the net
+ (SVR4 fread is known to be broken, specifically for short
+ reads off of streams.)
+
+Tue May 18 15:53:44 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (do_cvs_command): Fix fencepost error in setting
+ num_to_check.
+
+ * server.c (do_cvs_command): If terminated with a core dump, print
+ message and set dont_delete_temp.
+ (server_cleanup): If dont_delete_temp, don't delete it.
+
+ * client.c (get_server_responses): Don't change cmd since we
+ are going to "free (cmd)".
+
+ * server.c: Rename memmove to my_memmove pending a real fix.
+
+ * server.c (do_cvs_command): Set num_to_check to largest descriptor
+ we try to use, rather than using (non-portable) getdtablesize.
+
+Wed May 12 15:31:40 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Add CVS client feature:
+ * client.{c,h}: New files.
+ * cvs.h: Include client.h.
+ * main.c: If CVSROOT has a colon, use client commands instead.
+ * vers_ts.c (Version_TS): If repository arg is NULL, don't worry
+ about the repository.
+ * logmsg.c (do_editor): If repository or changes is NULL, just don't
+ use those features.
+ * create_adm.c (Create_Admin), callers: Move the test for whether
+ the repository exists from here to callers.
+ * repos.c (Name_Repository): Don't test whether the repository exists
+ if client_active set (might be better to move test to callers).
+
+ Add CVS server feature:
+ * server.{c,h}: New files.
+ * cvs.h: Include server.h.
+ * checkin.c (Checkin): Call server_checked_in.
+ * update.c (update_file_proc, merge_files): Call server_updated.
+ * entries.c (Register): Call server_register.
+ (Scratch_Entry): Call server_scratch.
+ * main.c: Add server to cmds.
+ * vers_ts.c (Version_TS): If server_active, call new function
+ time_stamp_server to set ts_user.
+
+
+For older changes, there might be some relevant stuff in the bottom of
+the NEWS file, but I'm afraid probably a lot of them are lost in the
+mists of time.
diff --git a/contrib/cvs/src/Makefile.in b/contrib/cvs/src/Makefile.in
new file mode 100644
index 0000000..acb3343
--- /dev/null
+++ b/contrib/cvs/src/Makefile.in
@@ -0,0 +1,198 @@
+# Makefile for GNU CVS program.
+# 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.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# $CVSid: @(#)Makefile.in 1.19 94/09/29 $
+
+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
+
+# Use cp if you don't have install.
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+
+LIBS = @LIBS@
+
+SOURCES = add.c admin.c checkin.c checkout.c classify.c client.c commit.c \
+create_adm.c cvsrc.c diff.c edit.c entries.c error.c expand_path.c \
+fileattr.c find_names.c hash.c history.c ignore.c import.c \
+lock.c log.c login.c logmsg.c main.c mkmodules.c modules.c myndbm.c no_diff.c \
+parseinfo.c patch.c rcs.c rcscmds.c recurse.c release.c remove.c repos.c \
+root.c rtag.c scramble.c server.c status.c subr.c filesubr.c run.c \
+tag.c update.c watch.c wrapper.c vers_ts.c version.c
+
+OBJECTS = add.o admin.o checkin.o checkout.o classify.o client.o commit.o \
+create_adm.o cvsrc.o diff.o edit.o entries.o expand_path.o \
+fileattr.o find_names.o hash.o history.o ignore.o import.o \
+lock.o log.o login.o logmsg.o main.o mkmodules.o modules.o myndbm.o no_diff.o \
+parseinfo.o patch.o rcs.o rcscmds.o recurse.o release.o remove.o repos.o \
+root.o rtag.o scramble.o server.o status.o tag.o update.o \
+watch.o wrapper.o vers_ts.o \
+subr.o filesubr.o run.o version.o error.o
+
+HEADERS = cvs.h rcs.h hash.h myndbm.h \
+ update.h server.h client.h error.h fileattr.h edit.h watch.h
+
+TAGFILES = $(HEADERS) options.h.in $(SOURCES)
+
+DISTFILES = .cvsignore Makefile.in ChangeLog ChangeLog-9395 ChangeLog-9194 \
+ NOTES README-rm-add \
+ sanity.sh cvsbug.sh $(TAGFILES)
+
+PROGS = cvs cvsbug
+
+DEFS = @DEFS@ @includeopt@
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS =
+LDFLAGS = @LDFLAGS@
+
+INCLUDES = -I. -I.. -I$(srcdir) -I$(top_srcdir)/lib
+.c.o:
+ $(CC) $(CPPFLAGS) $(INCLUDES) $(DEFS) $(CFLAGS) -c $<
+
+all: Makefile $(PROGS)
+.PHONY: all
+
+saber_cvs:
+ @cd ..; $(MAKE) saber SUBDIRS=src
+
+lint:
+ @cd ..; $(MAKE) lint SUBDIRS=src
+
+# CYGNUS LOCAL: Do not depend upon installdirs
+install:
+ @for prog in $(PROGS); do \
+ echo Installing $$prog in $(bindir); \
+ $(INSTALL) $$prog $(bindir)/$$prog ; \
+ done
+
+installdirs:
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(bindir)
+
+.PHONY: install installdirs
+
+installcheck:
+ RCSBIN=$(bindir) ; export RCSBIN ; $(SHELL) $(srcdir)/sanity.sh $(bindir)/cvs
+.PHONY: installcheck
+
+check: all
+ if [ -x ../../rcs/src/rcs ] ; then \
+ RCSBIN=`pwd`/../../rcs/src ; \
+ export RCSBIN ; \
+ fi ; \
+ $(SHELL) $(srcdir)/sanity.sh `pwd`/cvs
+.PHONY: check
+
+# I'm not sure there is any remaining reason for this to be separate from
+# `make check'.
+remotecheck: all
+ $(SHELL) $(srcdir)/sanity.sh -r `pwd`/cvs
+.PHONY: remotecheck
+
+tags: $(TAGFILES)
+ ctags $(TAGFILES)
+
+TAGS: $(TAGFILES)
+ etags `for i in $(TAGFILES); do echo $(srcdir)/$$i; done`
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+clean:
+ /bin/rm -f $(PROGS) *.o core check.log check.plog
+.PHONY: clean
+
+distclean: clean
+ rm -f tags TAGS Makefile options.h
+.PHONY: distclean
+
+realclean: distclean
+.PHONY: realclean
+
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+.PHONY: dist-dir
+
+# Linking rules.
+
+$(PROGS): ../lib/libcvs.a
+
+cvs: $(OBJECTS)
+ $(CC) $(OBJECTS) ../lib/libcvs.a $(LIBS) $(LDFLAGS) -o $@
+
+xlint: $(SOURCES)
+ files= ; \
+ for i in $(SOURCES) ; do \
+ files="$$files $(srcdir)/$$i" ; \
+ done ; \
+ sh -c "lint $(DEFS) $(INCLUDES) $$files | grep -v \"possible pointer alignment problem\" \
+ | grep -v \"argument closure unused\""
+
+saber: $(SOURCES)
+ # load $(CFLAGS) $(SOURCES)
+ # load ../lib/libcvs.a $(LIBS)
+
+cvsbug: cvsbug.sh
+ echo > .fname \
+ cvs-`sed < $(srcdir)/version.c \
+ -e '/version_string/!d' \
+ -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \
+ -e q`
+ sed -e 's,xLIBDIRx,$(libdir)/cvs,g' \
+ -e "s,xVERSIONx,`cat .fname`,g" $(srcdir)/$@.sh > $@-t
+ rm -f .fname
+ mv $@-t $@
+ chmod a+x $@
+
+# Compilation rules.
+
+$(OBJECTS): $(HEADERS) options.h
+
+subdir = src
+Makefile: ../config.status Makefile.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
+
+options.h: ../config.status options.h.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/contrib/cvs/src/NOTES b/contrib/cvs/src/NOTES
new file mode 100644
index 0000000..646ebdf
--- /dev/null
+++ b/contrib/cvs/src/NOTES
@@ -0,0 +1,60 @@
+wishlist - Tue Nov 2 15:22:58 PST 1993
+
+* bcopy -> memcpy & friends.
+ ** done 12/18/93
+
+* remove static buffers.
+* replace list & node cache with recursive obstacks, (xmalloc,
+ getnode, getlist)
+* check all io functions for error return codes. also check all
+ system calls.
+* error check mkdir.
+
+---
+Old notes...
+
+* All sizing limits are gone. The rest of these items were incidental
+ in that effort.
+
+* login name from history was duplicated. taught existing routine to
+ cache and use that instead. Also add routines to cache uid, pid,
+ etc.
+
+* ign strings were never freed. Now they are.
+
+* there was a printf("... %s ...", cp) vs *cp bug in history.c. Now
+ fixed.
+
+* The environment variables TMPDIR, HOME, and LOGNAME were not
+ honored. Now they are.
+
+* extra line inserted by do_editor() is gone. Then obviated. Editor
+ is now called exactly once per checkin.
+
+* revised editor behaviour. Never use /dev/tty. If the editor
+ session fails, we haven't yet done anything. Therefor the user can
+ safely rerun cvs and we should just fail. Also use the editor for
+ initial log messages on added files. Also omit the confirmation
+ when adding directories. Adding directories will require an
+ explicit "commit" step soon. Make it possible to prevent null login
+ messages using #define REQUIRE_LOG_MESSAGES
+
+* prototypes for all callbacks.
+
+* all callbacks get ref pointers.
+
+* do_recursion/start_recursion now use recusion_frame's rather than a
+ list of a lot of pointers and global variables.
+
+* corrected types on status_dirproc().
+
+* CONFIRM_DIRECTORY_ADDS
+
+* re_comp was innappropriate in a few places. I've eliminated it.
+
+* FORCE_MESSAGE_ON_ADD
+
+* So I built a regression test. Let's call it a sanity check to be
+ less ambitious. It exposed that cvs is difficult to call from
+ scripts.
+
diff --git a/contrib/cvs/src/README-rm-add b/contrib/cvs/src/README-rm-add
new file mode 100644
index 0000000..87fd7c6
--- /dev/null
+++ b/contrib/cvs/src/README-rm-add
@@ -0,0 +1,31 @@
+WHAT THE "DEATH SUPPORT" FEATURES DO:
+
+(Some of the death support stuff is documented in the main manual, but
+this file is for stuff which noone has gotten around to adding to the
+main manual yet).
+
+CVS with death support can record when a file is active, or alive, and
+when it is removed, or dead. With this facility you can record the
+history of a file, including the fact that at some point in its life
+the file was removed and then later added.
+
+Files can now be added or removed in a branch and later merged
+into the trunk.
+
+ 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
+
+Added and removed files may also be merged between branches.
+
+Files removed in the trunk may be merged into branches.
+
+Files added on the trunk are a special case. They cannot be merged
+into a branch. Instead, simply branch the file by hand.
diff --git a/contrib/cvs/src/add.c b/contrib/cvs/src/add.c
new file mode 100644
index 0000000..82efefe
--- /dev/null
+++ b/contrib/cvs/src/add.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Add
+ *
+ * Adds a file or directory to the RCS source repository. For a file,
+ * the entry is marked as "needing to be added" in the user's own CVS
+ * directory, and really added to the repository when it is committed.
+ * For a directory, it is added at the appropriate place in the source
+ * repository and a CVS directory is generated within the directory.
+ *
+ * The -m option is currently the only supported option. Some may wish to
+ * supply standard "rcs" options here, but I've found that this causes more
+ * trouble than anything else.
+ *
+ * The user files or directories must already exist. For a directory, it must
+ * not already have a CVS file in it.
+ *
+ * An "add" on a file that has been "remove"d but not committed will cause the
+ * file to be resurrected.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+static int add_directory PROTO((char *repository, char *dir));
+static int build_entry PROTO((char *repository, char *user, char *options,
+ char *message, List * entries, char *tag));
+
+static const char *const add_usage[] =
+{
+ "Usage: %s %s [-k rcs-kflag] [-m message] files...\n",
+ "\t-k\tUse \"rcs-kflag\" to add the file with the specified kflag.\n",
+ "\t-m\tUse \"message\" for the creation log.\n",
+ NULL
+};
+
+int
+add (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *message = NULL;
+ char *user;
+ int i;
+ char *repository;
+ int c;
+ int err = 0;
+ int added_files = 0;
+ char *options = NULL;
+ List *entries;
+ Vers_TS *vers;
+
+ if (argc == 1 || argc == -1)
+ usage (add_usage);
+
+ wrap_setup ();
+
+ /* parse args */
+ optind = 1;
+ while ((c = getopt (argc, argv, "k:m:")) != -1)
+ {
+ switch (c)
+ {
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+
+ case 'm':
+ message = xstrdup (optarg);
+ break;
+ case '?':
+ default:
+ usage (add_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc <= 0)
+ usage (add_usage);
+
+ /* find the repository associated with our current dir */
+ repository = Name_Repository ((char *) NULL, (char *) NULL);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int i;
+ start_server ();
+ ign_setup ();
+ if (options) send_arg(options);
+ option_with_arg ("-m", message);
+ for (i = 0; i < argc; ++i)
+ /* FIXME: Does this erroneously call Create_Admin in error
+ conditions which are only detected once the server gets its
+ hands on things? */
+ if (isdir (argv[i]))
+ {
+ char *tag;
+ char *date;
+ char *rcsdir = xmalloc (strlen (repository)
+ + strlen (argv[i]) + 10);
+
+ /* before we do anything else, see if we have any
+ per-directory tags */
+ ParseTag (&tag, &date);
+
+ sprintf (rcsdir, "%s/%s", repository, argv[i]);
+
+ Create_Admin (argv[i], argv[i], rcsdir, tag, date);
+
+ if (tag)
+ free (tag);
+ if (date)
+ free (date);
+ free (rcsdir);
+ }
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_files (argc, argv, 0, 0);
+ send_to_server ("add\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ entries = Entries_Open (0);
+
+ /* walk the arg list adding files/dirs */
+ for (i = 0; i < argc; i++)
+ {
+ int begin_err = err;
+ int begin_added_files = added_files;
+
+ user = argv[i];
+ strip_trailing_slashes (user);
+ if (strchr (user, '/') != NULL)
+ {
+ error (0, 0,
+ "cannot add files with '/' in their name; %s not added", user);
+ err++;
+ continue;
+ }
+
+ vers = Version_TS (repository, options, (char *) NULL, (char *) NULL,
+ user, 0, 0, entries, (RCSNode *) NULL);
+ if (vers->vn_user == NULL)
+ {
+ /* No entry available, ts_rcs is invalid */
+ if (vers->vn_rcs == NULL)
+ {
+ /* There is no RCS file either */
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file either */
+ error (0, 0, "nothing known about %s", user);
+ err++;
+ }
+ else if (!isdir (user) || wrap_name_has (user, WRAP_TOCVS))
+ {
+ /*
+ * See if a directory exists in the repository with
+ * the same name. If so, blow this request off.
+ */
+ char dname[PATH_MAX];
+ (void) sprintf (dname, "%s/%s", repository, user);
+ if (isdir (dname))
+ {
+ error (0, 0,
+ "cannot add file `%s' since the directory",
+ user);
+ error (0, 0, "`%s' already exists in the repository",
+ dname);
+ error (1, 0, "illegal filename overlap");
+ }
+
+ /* There is a user file, so build the entry for it */
+ if (build_entry (repository, user, vers->options,
+ message, entries, vers->tag) != 0)
+ err++;
+ else
+ {
+ added_files++;
+ if (!quiet)
+ {
+ if (vers->tag)
+ error (0, 0, "\
+scheduling %s `%s' for addition on branch `%s'",
+ (wrap_name_has (user, WRAP_TOCVS)
+ ? "wrapper"
+ : "file"),
+ user, vers->tag);
+ else
+ error (0, 0, "scheduling %s `%s' for addition",
+ (wrap_name_has (user, WRAP_TOCVS)
+ ? "wrapper"
+ : "file"),
+ user);
+ }
+ }
+ }
+ }
+ else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
+ {
+ if (isdir (user) && !wrap_name_has (user, WRAP_TOCVS))
+ {
+ error (0, 0, "the directory `%s' cannot be added because a file of the", user);
+ error (1, 0, "same name already exists in the repository.");
+ }
+ else
+ {
+ if (vers->tag)
+ error (0, 0, "file `%s' will be added on branch `%s' from version %s",
+ user, vers->tag, vers->vn_rcs);
+ else
+ error (0, 0, "version %s of `%s' will be resurrected",
+ vers->vn_rcs, user);
+ Register (entries, user, "0", vers->ts_user, NULL,
+ vers->tag, NULL, NULL);
+ ++added_files;
+ }
+ }
+ else
+ {
+ /*
+ * There is an RCS file already, so somebody else must've
+ * added it
+ */
+ error (0, 0, "%s added independently by second party", user);
+ err++;
+ }
+ }
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ {
+
+ /*
+ * An entry for a new-born file, ts_rcs is dummy, but that is
+ * inappropriate here
+ */
+ error (0, 0, "%s has already been entered", user);
+ err++;
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ /* An entry for a removed file, ts_rcs is invalid */
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file (as it should be) */
+ if (vers->vn_rcs == NULL)
+ {
+
+ /*
+ * There is no RCS file, so somebody else must've removed
+ * it from under us
+ */
+ error (0, 0,
+ "cannot resurrect %s; RCS file removed by second party", user);
+ err++;
+ }
+ else
+ {
+
+ /*
+ * There is an RCS file, so remove the "-" from the
+ * version number and restore the file
+ */
+ char *tmp = xmalloc (strlen (user) + 50);
+
+ (void) strcpy (tmp, vers->vn_user + 1);
+ (void) strcpy (vers->vn_user, tmp);
+ (void) sprintf (tmp, "Resurrected %s", user);
+ Register (entries, user, vers->vn_user, tmp, vers->options,
+ vers->tag, vers->date, vers->ts_conflict);
+ free (tmp);
+
+ /* XXX - bugs here; this really resurrect the head */
+ /* Note that this depends on the Register above actually
+ having written Entries, or else it won't really
+ check the file out. */
+ if (update (2, argv + i - 1) == 0)
+ {
+ error (0, 0, "%s, version %s, resurrected", user,
+ vers->vn_user);
+ }
+ else
+ {
+ error (0, 0, "could not resurrect %s", user);
+ err++;
+ }
+ }
+ }
+ else
+ {
+ /* The user file shouldn't be there */
+ error (0, 0, "%s should be removed and is still there (or is back again)", user);
+ err++;
+ }
+ }
+ else
+ {
+ /* A normal entry, ts_rcs is valid, so it must already be there */
+ error (0, 0, "%s already exists, with version number %s", user,
+ vers->vn_user);
+ err++;
+ }
+ freevers_ts (&vers);
+
+ /* passed all the checks. Go ahead and add it if its a directory */
+ if (begin_err == err
+ && isdir (user)
+ && !wrap_name_has (user, WRAP_TOCVS))
+ {
+ err += add_directory (repository, user);
+ continue;
+ }
+#ifdef SERVER_SUPPORT
+ if (server_active && begin_added_files != added_files)
+ server_checked_in (user, ".", repository);
+#endif
+ }
+ if (added_files)
+ error (0, 0, "use 'cvs commit' to add %s permanently",
+ (added_files == 1) ? "this file" : "these files");
+
+ Entries_Close (entries);
+
+ if (message)
+ free (message);
+
+ return (err);
+}
+
+/*
+ * The specified user file is really a directory. So, let's make sure that
+ * it is created in the RCS source repository, and that the user's directory
+ * is updated to include a CVS directory.
+ *
+ * Returns 1 on failure, 0 on success.
+ */
+static int
+add_directory (repository, dir)
+ char *repository;
+ char *dir;
+{
+ char rcsdir[PATH_MAX];
+ struct saved_cwd cwd;
+ char message[PATH_MAX + 100];
+ char *tag, *date;
+
+ if (strchr (dir, '/') != NULL)
+ {
+ error (0, 0,
+ "directory %s not added; must be a direct sub-directory", dir);
+ return (1);
+ }
+ if (strcmp (dir, CVSADM) == 0)
+ {
+ error (0, 0, "cannot add a `%s' directory", CVSADM);
+ return (1);
+ }
+
+ /* before we do anything else, see if we have any per-directory tags */
+ ParseTag (&tag, &date);
+
+ /* now, remember where we were, so we can get back */
+ if (save_cwd (&cwd))
+ return (1);
+ if (chdir (dir) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", dir);
+ return (1);
+ }
+#ifdef SERVER_SUPPORT
+ if (!server_active && isfile (CVSADM))
+#else
+ if (isfile (CVSADM))
+#endif
+ {
+ error (0, 0, "%s/%s already exists", dir, CVSADM);
+ goto out;
+ }
+
+ (void) sprintf (rcsdir, "%s/%s", repository, dir);
+ if (isfile (rcsdir) && !isdir (rcsdir))
+ {
+ error (0, 0, "%s is not a directory; %s not added", rcsdir, dir);
+ goto out;
+ }
+
+ /* setup the log message */
+ (void) sprintf (message, "Directory %s added to the repository\n", rcsdir);
+ if (tag)
+ {
+ (void) strcat (message, "--> Using per-directory sticky tag `");
+ (void) strcat (message, tag);
+ (void) strcat (message, "'\n");
+ }
+ if (date)
+ {
+ (void) strcat (message, "--> Using per-directory sticky date `");
+ (void) strcat (message, date);
+ (void) strcat (message, "'\n");
+ }
+
+ if (!isdir (rcsdir))
+ {
+ mode_t omask;
+ Node *p;
+ List *ulist;
+
+#if 0
+ char line[MAXLINELEN];
+
+ (void) printf ("Add directory %s to the repository (y/n) [n] ? ",
+ rcsdir);
+ (void) fflush (stdout);
+ clearerr (stdin);
+ if (fgets (line, sizeof (line), stdin) == NULL ||
+ (line[0] != 'y' && line[0] != 'Y'))
+ {
+ error (0, 0, "directory %s not added", rcsdir);
+ goto out;
+ }
+#endif
+
+ omask = umask (cvsumask);
+ if (CVS_MKDIR (rcsdir, 0777) < 0)
+ {
+ error (0, errno, "cannot mkdir %s", rcsdir);
+ (void) umask (omask);
+ goto out;
+ }
+ (void) umask (omask);
+
+ /*
+ * Set up an update list with a single title node for Update_Logfile
+ */
+ ulist = getlist ();
+ p = getnode ();
+ p->type = UPDATE;
+ p->delproc = update_delproc;
+ p->key = xstrdup ("- New directory");
+ p->data = (char *) T_TITLE;
+ (void) addnode (ulist, p);
+ Update_Logfile (rcsdir, message, (char *) NULL, (FILE *) NULL, ulist);
+ dellist (&ulist);
+ }
+
+#ifdef SERVER_SUPPORT
+ if (!server_active)
+ Create_Admin (".", dir, rcsdir, tag, date);
+#else
+ Create_Admin (".", dir, rcsdir, tag, date);
+#endif
+ if (tag)
+ free (tag);
+ if (date)
+ free (date);
+
+ (void) printf ("%s", message);
+out:
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ return (0);
+}
+
+/*
+ * Builds an entry for a new file and sets up "CVS/file",[pt] by
+ * interrogating the user. Returns non-zero on error.
+ */
+static int
+build_entry (repository, user, options, message, entries, tag)
+ char *repository;
+ char *user;
+ char *options;
+ char *message;
+ List *entries;
+ char *tag;
+{
+ char fname[PATH_MAX];
+ char line[MAXLINELEN];
+ FILE *fp;
+
+ if (noexec)
+ return (0);
+
+ /*
+ * The requested log is read directly from the user and stored in the
+ * file user,t. If the "message" argument is set, use it as the
+ * initial creation log (which typically describes the file).
+ */
+ (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_LOG);
+ fp = open_file (fname, "w+");
+ if (message && fputs (message, fp) == EOF)
+ error (1, errno, "cannot write to %s", fname);
+ if (fclose(fp) == EOF)
+ error(1, errno, "cannot close %s", fname);
+
+ /*
+ * Create the entry now, since this allows the user to interrupt us above
+ * without needing to clean anything up (well, we could clean up the
+ * ,t file, but who cares).
+ */
+ (void) sprintf (line, "Initial %s", user);
+ Register (entries, user, "0", line, options, tag, (char *) 0, (char *) 0);
+ return (0);
+}
diff --git a/contrib/cvs/src/admin.c b/contrib/cvs/src/admin.c
new file mode 100644
index 0000000..214318a
--- /dev/null
+++ b/contrib/cvs/src/admin.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Administration
+ *
+ * For now, this is basically a front end for rcs. All options are passed
+ * directly on.
+ */
+
+#include "cvs.h"
+#ifdef CVS_ADMIN_GROUP
+#include <grp.h>
+#endif
+
+static Dtype admin_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int admin_fileproc PROTO((struct file_info *finfo));
+
+static const char *const admin_usage[] =
+{
+ "Usage: %s %s rcs-options files...\n",
+ NULL
+};
+
+static int ac;
+static char **av;
+
+int
+admin (argc, argv)
+ int argc;
+ char **argv;
+{
+ int err;
+#ifdef CVS_ADMIN_GROUP
+ struct group *grp;
+#endif
+ if (argc <= 1)
+ usage (admin_usage);
+
+#ifdef CVS_ADMIN_GROUP
+ grp = getgrnam(CVS_ADMIN_GROUP);
+ /* skip usage right check if group CVS_ADMIN_GROUP does not exist */
+ if (grp != NULL)
+ {
+ char *me = getcaller();
+ char **grnam = grp->gr_mem;
+ int denied = 1;
+
+ while (*grnam)
+ {
+ if (strcmp(*grnam, me) == 0)
+ {
+ denied = 0;
+ break;
+ }
+ grnam++;
+ }
+
+ if (denied)
+ error (1, 0, "usage is restricted to members of the group %s",
+ CVS_ADMIN_GROUP);
+ }
+#endif
+
+ wrap_setup ();
+
+ /* skip all optional arguments to see if we have any file names */
+ for (ac = 1; ac < argc; ac++)
+ if (argv[ac][0] != '-')
+ break;
+ argc -= ac;
+ av = argv + 1;
+ argv += ac;
+ ac--;
+ if (ac == 0 || argc == 0)
+ usage (admin_usage);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int i;
+
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ for (i = 0; i <= ac; ++i) /* XXX send -ko too with i = 0 */
+ send_arg (av[i]);
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, 0, 0);
+ send_to_server ("admin\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ /* start the recursion processor */
+ err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc,
+ (DIRLEAVEPROC) NULL, argc, argv, 0,
+ W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+ return (err);
+}
+
+/*
+ * Called to run "rcs" on a particular file.
+ */
+/* ARGSUSED */
+static int
+admin_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Vers_TS *vers;
+ char *version;
+ char **argv;
+ int argc;
+ int retcode = 0;
+ int status = 0;
+
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 0, 0, finfo->entries, finfo->rcs);
+
+ version = vers->vn_user;
+ if (version == NULL)
+ goto exitfunc;
+ else if (strcmp (version, "0") == 0)
+ {
+ error (0, 0, "cannot admin newly added file `%s'", finfo->file);
+ goto exitfunc;
+ }
+
+ run_setup ("%s%s -x,v/", Rcsbin, RCS);
+ for (argc = ac, argv = av; argc; argc--, argv++)
+ run_arg (*argv);
+ run_arg (vers->srcfile->path);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "%s failed for `%s'", RCS, finfo->file);
+ status = 1;
+ goto exitfunc;
+ }
+ exitfunc:
+ freevers_ts (&vers);
+ return status;
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+admin_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Administrating %s", update_dir);
+ return (R_PROCESS);
+}
diff --git a/contrib/cvs/src/checkin.c b/contrib/cvs/src/checkin.c
new file mode 100644
index 0000000..e1ce9cb
--- /dev/null
+++ b/contrib/cvs/src/checkin.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Check In
+ *
+ * Does a very careful checkin of the file "user", and tries not to spoil its
+ * modification time (to avoid needless recompilations). When RCS ID keywords
+ * get expanded on checkout, however, the modification time is updated and
+ * there is no good way to get around this.
+ *
+ * Returns non-zero on error.
+ */
+
+#include "cvs.h"
+#include "fileattr.h"
+#include "edit.h"
+
+int
+Checkin (type, file, update_dir, repository,
+ rcs, rev, tag, options, message, entries)
+ int type;
+ char *file;
+ char *update_dir;
+ char *repository;
+ char *rcs;
+ char *rev;
+ char *tag;
+ char *options;
+ char *message;
+ List *entries;
+{
+ char fname[PATH_MAX];
+ Vers_TS *vers;
+ int set_time;
+ char *fullname;
+
+ char *tocvsPath = NULL;
+
+ fullname = xmalloc (strlen (update_dir) + strlen (file) + 10);
+ if (update_dir[0] == '\0')
+ strcpy (fullname, file);
+ else
+ sprintf (fullname, "%s/%s", update_dir, file);
+
+ (void) printf ("Checking in %s;\n", fullname);
+ (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
+
+ /*
+ * Move the user file to a backup file, so as to preserve its
+ * modification times, then place a copy back in the original file name
+ * for the checkin and checkout.
+ */
+
+ tocvsPath = wrap_tocvs_process_file (fullname);
+
+ if (!noexec)
+ {
+ if (tocvsPath)
+ {
+ copy_file (tocvsPath, fname);
+ if (unlink_file_dir (file) < 0)
+ if (! existence_error (errno))
+ error (1, errno, "cannot remove %s", file);
+ copy_file (tocvsPath, file);
+ }
+ else
+ {
+ copy_file (file, fname);
+ }
+ }
+
+ switch (RCS_checkin (rcs, NULL, message, rev, 0, 0))
+ {
+ case 0: /* everything normal */
+
+ /*
+ * The checkin succeeded, so now check the new file back out and
+ * see if it matches exactly with the one we checked in. If it
+ * does, just move the original user file back, thus preserving
+ * the modes; otherwise, we have no recourse but to leave the
+ * newly checkout file as the user file and remove the old
+ * original user file.
+ */
+
+ if (strcmp (options, "-V4") == 0) /* upgrade to V5 now */
+ options[0] = '\0';
+
+ /* FIXME: should be checking for errors. */
+ (void) RCS_checkout (rcs, "", rev, options, RUN_TTY, 0, 0);
+
+ xchmod (file, 1);
+ if (xcmp (file, fname) == 0)
+ {
+ rename_file (fname, file);
+ /* the time was correct, so leave it alone */
+ set_time = 0;
+ }
+ else
+ {
+ if (unlink_file (fname) < 0)
+ error (0, errno, "cannot remove %s", fname);
+ /* sync up with the time from the RCS file */
+ set_time = 1;
+ }
+
+ wrap_fromcvs_process_file (file);
+
+ /*
+ * If we want read-only files, muck the permissions here, before
+ * getting the file time-stamp.
+ */
+ if (cvswrite == FALSE || fileattr_get (file, "_watched"))
+ xchmod (file, 0);
+
+ /* re-register with the new data */
+ vers = Version_TS (repository, (char *) NULL, tag, (char *) NULL,
+ file, 1, set_time, entries, (RCSNode *) NULL);
+ if (strcmp (vers->options, "-V4") == 0)
+ vers->options[0] = '\0';
+ Register (entries, file, vers->vn_rcs, vers->ts_user,
+ vers->options, vers->tag, vers->date, (char *) 0);
+ history_write (type, (char *) 0, vers->vn_rcs, file, repository);
+ freevers_ts (&vers);
+
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ break;
+
+ case -1: /* fork failed */
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (!noexec)
+ error (1, errno, "could not check in %s -- fork failed",
+ fullname);
+ return (1);
+
+ default: /* ci failed */
+
+ /*
+ * The checkin failed, for some unknown reason, so we restore the
+ * original user file, print an error, and return an error
+ */
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (!noexec)
+ {
+ rename_file (fname, file);
+ error (0, 0, "could not check in %s", fullname);
+ }
+ return (1);
+ }
+
+ /*
+ * When checking in a specific revision, we may have locked the wrong
+ * branch, so to be sure, we do an extra unlock here before
+ * returning.
+ */
+ if (rev)
+ {
+ (void) RCS_unlock (rcs, NULL, 1);
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ if (set_time)
+ /* Need to update the checked out file on the client side. */
+ server_updated (file, update_dir, repository, SERVER_UPDATED,
+ NULL, NULL);
+ else
+ server_checked_in (file, update_dir, repository);
+ }
+ else
+#endif
+ mark_up_to_date (file);
+
+ return (0);
+}
diff --git a/contrib/cvs/src/checkout.c b/contrib/cvs/src/checkout.c
new file mode 100644
index 0000000..64aa10e
--- /dev/null
+++ b/contrib/cvs/src/checkout.c
@@ -0,0 +1,889 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Create Version
+ *
+ * "checkout" creates a "version" of an RCS repository. This version is owned
+ * totally by the user and is actually an independent copy, to be dealt with
+ * as seen fit. Once "checkout" has been called in a given directory, it
+ * never needs to be called again. The user can keep up-to-date by calling
+ * "update" when he feels like it; this will supply him with a merge of his
+ * own modifications and the changes made in the RCS original. See "update"
+ * for details.
+ *
+ * "checkout" can be given a list of directories or files to be updated and in
+ * the case of a directory, will recursivley create any sub-directories that
+ * exist in the repository.
+ *
+ * When the user is satisfied with his own modifications, the present version
+ * can be committed by "commit"; this keeps the present version in tact,
+ * usually.
+ *
+ * The call is cvs checkout [options] <module-name>...
+ *
+ * "checkout" creates a directory ./CVS, in which it keeps its administration,
+ * in two files, Repository and Entries. The first contains the name of the
+ * repository. The second contains one line for each registered file,
+ * consisting of the version number it derives from, its time stamp at
+ * derivation time and its name. Both files are normal files and can be
+ * edited by the user, if necessary (when the repository is moved, e.g.)
+ */
+
+#include "cvs.h"
+
+static char *findslash PROTO((char *start, char *p));
+static int build_dirs_and_chdir PROTO((char *dir, char *prepath, char *realdir,
+ int sticky));
+static int checkout_proc PROTO((int *pargc, char **argv, char *where,
+ char *mwhere, char *mfile, int shorten,
+ int local_specified, char *omodule,
+ char *msg));
+static int safe_location PROTO((void));
+
+static const char *const checkout_usage[] =
+{
+ "Usage:\n %s %s [-ANPcflnps] [-r rev | -D date] [-d dir] [-k kopt] modules...\n",
+ "\t-A\tReset any sticky tags/date/kopts.\n",
+ "\t-N\tDon't shorten module paths if -d specified.\n",
+ "\t-P\tPrune empty directories.\n",
+ "\t-c\t\"cat\" the module database.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-n\tDo not run module program (if any).\n",
+ "\t-p\tCheck out files to standard output.\n",
+ "\t-s\tLike -c, but include module status.\n",
+ "\t-r rev\tCheck out revision or tag. (implies -P)\n",
+ "\t-D date\tCheck out revisions as of date. (implies -P)\n",
+ "\t-d dir\tCheck out into dir instead of module name.\n",
+ "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+ "\t-j rev\tMerge in changes made between current revision and rev.\n",
+ NULL
+};
+
+static const char *const export_usage[] =
+{
+ "Usage: %s %s [-NPfln] [-r rev | -D date] [-d dir] [-k kopt] module...\n",
+ "\t-N\tDon't shorten module paths if -d specified.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-n\tDo not run module program (if any).\n",
+ "\t-r rev\tCheck out revision or tag.\n",
+ "\t-D date\tCheck out revisions as of date.\n",
+ "\t-d dir\tCheck out into dir instead of module name.\n",
+ "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+ NULL
+};
+
+static int checkout_prune_dirs;
+static int force_tag_match = 1;
+static int pipeout;
+static int aflag;
+static char *options = NULL;
+static char *tag = NULL;
+static int tag_validated = 0;
+static char *date = NULL;
+static char *join_rev1 = NULL;
+static char *join_rev2 = NULL;
+static char *preload_update_dir = NULL;
+
+int
+checkout (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+ int c;
+ DBM *db;
+ int cat = 0, err = 0, status = 0;
+ int run_module_prog = 1;
+ int local = 0;
+ int shorten = -1;
+ char *where = NULL;
+ char *valid_options;
+ const char *const *valid_usage;
+ enum mtype m_type;
+
+ /*
+ * A smaller subset of options are allowed for the export command, which
+ * is essentially like checkout, except that it hard-codes certain
+ * options to be default (like -kv) and takes care to remove the CVS
+ * directory when it has done its duty
+ */
+ if (strcmp (command_name, "export") == 0)
+ {
+ m_type = EXPORT;
+ valid_options = "Nnk:d:flRQqr:D:";
+ valid_usage = export_usage;
+ }
+ else
+ {
+ m_type = CHECKOUT;
+ valid_options = "ANnk:d:flRpQqcsr:D:j:P";
+ valid_usage = checkout_usage;
+ }
+
+ if (argc == -1)
+ usage (valid_usage);
+
+ ign_setup ();
+ wrap_setup ();
+
+ optind = 1;
+ while ((c = getopt (argc, argv, valid_options)) != -1)
+ {
+ switch (c)
+ {
+ case 'A':
+ aflag = 1;
+ break;
+ case 'N':
+ shorten = 0;
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'n':
+ run_module_prog = 0;
+ break;
+ case 'Q':
+ 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 'P':
+ checkout_prune_dirs = 1;
+ break;
+ case 'p':
+ pipeout = 1;
+ run_module_prog = 0; /* don't run module prog when piping */
+ noexec = 1; /* so no locks will be created */
+ break;
+ case 'c':
+ cat = 1;
+ break;
+ case 'd':
+ where = optarg;
+ if (shorten == -1)
+ shorten = 1;
+ break;
+ case 's':
+ status = 1;
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'r':
+ tag = optarg;
+ checkout_prune_dirs = 1;
+ break;
+ case 'D':
+ date = Make_Date (optarg);
+ checkout_prune_dirs = 1;
+ break;
+ case 'j':
+ if (join_rev2)
+ error (1, 0, "only two -j options can be specified");
+ if (join_rev1)
+ join_rev2 = optarg;
+ else
+ join_rev1 = optarg;
+ break;
+ case '?':
+ default:
+ usage (valid_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (shorten == -1)
+ shorten = 0;
+
+ if ((!(cat + status) && argc == 0) || ((cat + status) && argc != 0)
+ || (tag && date))
+ usage (valid_usage);
+
+ if (where && pipeout)
+ error (1, 0, "-d and -p are mutually exclusive");
+
+ if (strcmp (command_name, "export") == 0)
+ {
+ if (!tag && !date)
+ {
+ error (0, 0, "must specify a tag or date");
+ usage (valid_usage);
+ }
+ if (tag && isdigit (tag[0]))
+ error (1, 0, "tag `%s' must be a symbolic tag", tag);
+/*
+ * mhy 950615: -kv doesn't work for binaries with RCS keywords.
+ * Instead use the default provided in the RCS file (-ko for binaries).
+ */
+#if 0
+ if (!options)
+ options = RCS_check_kflag ("v");/* -kv is default */
+#endif
+ }
+
+ if (!safe_location()) {
+ error(1, 0, "Cannot check out files into the repository itself");
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int expand_modules;
+
+ start_server ();
+
+ ign_setup ();
+
+ /* We have to expand names here because the "expand-modules"
+ directive to the server has the side-effect of having the
+ server send the check-in and update programs for the
+ various modules/dirs requested. If we turn this off and
+ simply request the names of the modules and directories (as
+ below in !expand_modules), those files (CVS/Checking.prog
+ or CVS/Update.prog) don't get created. Grrr. */
+
+ expand_modules = (!cat && !status && !pipeout
+ && supported_request ("expand-modules"));
+
+ if (expand_modules)
+ {
+ /* This is done here because we need to read responses
+ from the server before we send the command checkout or
+ export files. */
+
+ client_expand_modules (argc, argv, local);
+ }
+
+ if (!run_module_prog) send_arg ("-n");
+ if (local) send_arg ("-l");
+ if (pipeout) send_arg ("-p");
+ if (!force_tag_match) send_arg ("-f");
+ if (aflag)
+ send_arg("-A");
+ if (!shorten)
+ send_arg("-N");
+ if (checkout_prune_dirs && strcmp (command_name, "export") != 0)
+ send_arg("-P");
+ client_prune_dirs = checkout_prune_dirs;
+ if (cat)
+ send_arg("-c");
+ if (where != NULL)
+ {
+ option_with_arg ("-d", where);
+ }
+ if (status)
+ send_arg("-s");
+ if (strcmp (command_name, "export") != 0
+ && options != NULL
+ && options[0] != '\0')
+ send_arg (options);
+ option_with_arg ("-r", tag);
+ if (date)
+ client_senddate (date);
+ if (join_rev1 != NULL)
+ option_with_arg ("-j", join_rev1);
+ if (join_rev2 != NULL)
+ option_with_arg ("-j", join_rev2);
+
+ if (expand_modules)
+ {
+ client_send_expansions (local);
+ }
+ else
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ client_nonexpanded_setup ();
+ }
+
+ send_to_server (strcmp (command_name, "export") == 0 ?
+ "export\012" : "co\012",
+ 0);
+
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ if (cat || status)
+ {
+ cat_module (status);
+ return (0);
+ }
+ db = open_module ();
+
+ /*
+ * if we have more than one argument and where was specified, we make the
+ * where, cd into it, and try to shorten names as much as possible.
+ * Otherwise, we pass the where as a single argument to do_module.
+ */
+ if (argc > 1 && where != NULL)
+ {
+ char repository[PATH_MAX];
+
+ (void) CVS_MKDIR (where, 0777);
+ if (chdir (where) < 0)
+ error (1, errno, "cannot chdir to %s", where);
+ preload_update_dir = xstrdup (where);
+ where = (char *) NULL;
+ if (!isfile (CVSADM))
+ {
+ (void) sprintf (repository, "%s/%s/%s", CVSroot, CVSROOTADM,
+ CVSNULLREPOS);
+ if (!isfile (repository))
+ {
+ mode_t omask;
+ omask = umask (cvsumask);
+ (void) CVS_MKDIR (repository, 0777);
+ (void) umask (omask);
+ }
+
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+
+ Create_Admin (".", where, repository,
+ (char *) NULL, (char *) NULL);
+ if (!noexec)
+ {
+ FILE *fp;
+
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose(fp) == EOF)
+ error(1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (preload_update_dir, repository);
+#endif
+ }
+ }
+ }
+
+ /*
+ * if where was specified (-d) and we have not taken care of it already
+ * with the multiple arg stuff, and it was not a simple directory name
+ * but rather a path, we strip off everything but the last component and
+ * attempt to cd to the indicated place. where then becomes simply the
+ * last component
+ */
+ if (where != NULL && strchr (where, '/') != NULL)
+ {
+ char *slash;
+
+ slash = strrchr (where, '/');
+ *slash = '\0';
+
+ if (chdir (where) < 0)
+ error (1, errno, "cannot chdir to %s", where);
+
+ preload_update_dir = xstrdup (where);
+
+ where = slash + 1;
+ if (*where == '\0')
+ where = NULL;
+ }
+
+ for (i = 0; i < argc; i++)
+ err += do_module (db, argv[i], m_type, "Updating", checkout_proc,
+ where, shorten, local, run_module_prog,
+ (char *) NULL);
+ close_module (db);
+ return (err);
+}
+
+static int
+safe_location ()
+{
+ char current[PATH_MAX];
+ char hardpath[PATH_MAX+5];
+ int x;
+
+ x = readlink(CVSroot, hardpath, sizeof hardpath - 1);
+ if (x == -1)
+ {
+ strcpy(hardpath, CVSroot);
+ }
+ else
+ {
+ hardpath[x] = '\0';
+ }
+ getwd (current);
+ if (strncmp(current, hardpath, strlen(hardpath)) == 0)
+ {
+ return (0);
+ }
+ return (1);
+}
+
+/*
+ * process_module calls us back here so we do the actual checkout stuff
+ */
+/* ARGSUSED */
+static int
+checkout_proc (pargc, argv, where, mwhere, mfile, shorten,
+ local_specified, omodule, msg)
+ int *pargc;
+ char **argv;
+ char *where;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *omodule;
+ char *msg;
+{
+ int err = 0;
+ int which;
+ char *cp;
+ char *cp2;
+ char repository[PATH_MAX];
+ char xwhere[PATH_MAX];
+ char *oldupdate = NULL;
+ char *prepath;
+ char *realdirs;
+
+ /*
+ * OK, so we're doing the checkout! Our args are as follows:
+ * argc,argv contain either dir or dir followed by a list of files
+ * where contains where to put it (if supplied by checkout)
+ * mwhere contains the module name or -d from module file
+ * mfile says do only that part of the module
+ * shorten = TRUE says shorten as much as possible
+ * omodule is the original arg to do_module()
+ */
+
+ /* set up the repository (maybe) for the bottom directory */
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+
+ /* save the original value of preload_update_dir */
+ if (preload_update_dir != NULL)
+ oldupdate = xstrdup (preload_update_dir);
+
+ /* fix up argv[] for the case of partial modules */
+ if (mfile != NULL)
+ {
+ char file[PATH_MAX];
+
+ /* if mfile is really a path, straighten it out first */
+ if ((cp = strrchr (mfile, '/')) != NULL)
+ {
+ *cp = 0;
+ (void) strcat (repository, "/");
+ (void) strcat (repository, mfile);
+
+ /*
+ * Now we need to fill in the where correctly. if !shorten, tack
+ * the rest of the path onto where if where is filled in
+ * otherwise tack the rest of the path onto mwhere and make that
+ * the where
+ *
+ * If shorten is enabled, we might use mwhere to set where if
+ * nobody set it yet, so we'll need to setup mwhere as the last
+ * component of the path we are tacking onto repository
+ */
+ if (!shorten)
+ {
+ if (where != NULL)
+ (void) sprintf (xwhere, "%s/%s", where, mfile);
+ else
+ (void) sprintf (xwhere, "%s/%s", mwhere, mfile);
+ where = xwhere;
+ }
+ else
+ {
+ char *slash;
+
+ if ((slash = strrchr (mfile, '/')) != NULL)
+ mwhere = slash + 1;
+ else
+ mwhere = mfile;
+ }
+ mfile = cp + 1;
+ }
+
+ (void) sprintf (file, "%s/%s", repository, mfile);
+ if (isdir (file))
+ {
+
+ /*
+ * The portion of a module was a directory, so kludge up where to
+ * be the subdir, and fix up repository
+ */
+ (void) strcpy (repository, file);
+
+ /*
+ * At this point, if shorten is not enabled, we make where either
+ * where with mfile concatenated, or if where hadn't been set we
+ * set it to mwhere with mfile concatenated.
+ *
+ * If shorten is enabled and where hasn't been set yet, then where
+ * becomes mfile
+ */
+ if (!shorten)
+ {
+ if (where != NULL)
+ (void) sprintf (xwhere, "%s/%s", where, mfile);
+ else
+ (void) sprintf (xwhere, "%s/%s", mwhere, mfile);
+ where = xwhere;
+ }
+ else if (where == NULL)
+ where = mfile;
+ }
+ else
+ {
+ int i;
+
+ /*
+ * The portion of a module was a file, so kludge up argv to be
+ * correct
+ */
+ for (i = 1; i < *pargc; i++)/* free the old ones */
+ free (argv[i]);
+ argv[1] = xstrdup (mfile); /* set up the new one */
+ *pargc = 2;
+
+ /* where gets mwhere if where isn't set */
+ if (where == NULL)
+ where = mwhere;
+ }
+ }
+
+ /*
+ * if shorten is enabled and where isn't specified yet, we pluck the last
+ * directory component of argv[0] and make it the where
+ */
+ if (shorten && where == NULL)
+ {
+ if ((cp = strrchr (argv[0], '/')) != NULL)
+ {
+ (void) strcpy (xwhere, cp + 1);
+ where = xwhere;
+ }
+ }
+
+ /* if where is still NULL, use mwhere if set or the argv[0] dir */
+ if (where == NULL)
+ {
+ if (mwhere)
+ where = mwhere;
+ else
+ {
+ (void) strcpy (xwhere, argv[0]);
+ where = xwhere;
+ }
+ }
+
+ if (preload_update_dir != NULL)
+ {
+ char tmp[PATH_MAX];
+
+ (void) sprintf (tmp, "%s/%s", preload_update_dir, where);
+ free (preload_update_dir);
+ preload_update_dir = xstrdup (tmp);
+ }
+ else
+ preload_update_dir = xstrdup (where);
+
+ /*
+ * At this point, where is the directory we want to build, repository is
+ * the repository for the lowest level of the path.
+ */
+
+ /*
+ * If we are sending everything to stdout, we can skip a whole bunch of
+ * work from here
+ */
+ if (!pipeout)
+ {
+
+ /*
+ * We need to tell build_dirs not only the path we want it to build,
+ * but also the repositories we want it to populate the path with. To
+ * accomplish this, we pass build_dirs a ``real path'' with valid
+ * repositories and a string to pre-pend based on how many path
+ * elements exist in where. Big Black Magic
+ */
+ prepath = xstrdup (repository);
+ cp = strrchr (where, '/');
+ cp2 = strrchr (prepath, '/');
+ while (cp != NULL)
+ {
+ cp = findslash (where, cp - 1);
+ cp2 = findslash (prepath, cp2 - 1);
+ }
+ *cp2 = '\0';
+ realdirs = cp2 + 1;
+
+ /*
+ * build dirs on the path if necessary and leave us in the bottom
+ * directory (where if where was specified) doesn't contain a CVS
+ * subdir yet, but all the others contain CVS and Entries.Static
+ * files
+ */
+ if (build_dirs_and_chdir (where, prepath, realdirs, *pargc <= 1) != 0)
+ {
+ error (0, 0, "ignoring module %s", omodule);
+ free (prepath);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (1);
+ }
+
+ /* clean up */
+ free (prepath);
+
+ /* set up the repository (or make sure the old one matches) */
+ if (!isfile (CVSADM))
+ {
+ FILE *fp;
+
+ if (!noexec && *pargc > 1)
+ {
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+
+ Create_Admin (".", where, repository,
+ (char *) NULL, (char *) NULL);
+ 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 (where, repository);
+#endif
+ }
+ else
+ {
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+
+ Create_Admin (".", where, repository, tag, date);
+ }
+ }
+ else
+ {
+ char *repos;
+
+ /* get the contents of the previously existing repository */
+ repos = Name_Repository ((char *) NULL, preload_update_dir);
+ if (fncmp (repository, repos) != 0)
+ {
+ error (0, 0, "existing repository %s does not match %s",
+ repos, repository);
+ error (0, 0, "ignoring module %s", omodule);
+ free (repos);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (1);
+ }
+ free (repos);
+ }
+ }
+
+ /*
+ * If we are going to be updating to stdout, we need to cd to the
+ * repository directory so the recursion processor can use the current
+ * directory as the place to find repository information
+ */
+ if (pipeout)
+ {
+ if (chdir (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (1);
+ }
+ which = W_REPOS;
+ if (tag != NULL && !tag_validated)
+ {
+ tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag, NULL);
+ tag_validated = 1;
+ }
+ }
+ else
+ {
+ which = W_LOCAL | W_REPOS;
+ if (tag != NULL && !tag_validated)
+ {
+ tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag,
+ repository);
+ tag_validated = 1;
+ }
+ }
+
+ if (tag != NULL || date != NULL)
+ which |= W_ATTIC;
+
+ /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2
+ yet (make sure to handle ':' correctly if we do, though). */
+
+ /*
+ * if we are going to be recursive (building dirs), go ahead and call the
+ * update recursion processor. We will be recursive unless either local
+ * only was specified, or we were passed arguments
+ */
+ if (!(local_specified || *pargc > 1))
+ {
+ if (strcmp (command_name, "export") != 0 && !pipeout)
+ history_write ('O', preload_update_dir, tag ? tag : date, where,
+ repository);
+ err += do_update (0, (char **) NULL, options, tag, date,
+ force_tag_match, 0 /* !local */ ,
+ 1 /* update -d */ , aflag, checkout_prune_dirs,
+ pipeout, which, join_rev1, join_rev2,
+ preload_update_dir);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (err);
+ }
+
+ if (!pipeout)
+ {
+ int i;
+ List *entries;
+
+ /* we are only doing files, so register them */
+ entries = Entries_Open (0);
+ for (i = 1; i < *pargc; i++)
+ {
+ char *line;
+ char *user;
+ Vers_TS *vers;
+
+ user = argv[i];
+ vers = Version_TS (repository, options, tag, date, user,
+ force_tag_match, 0, entries, (RCSNode *) NULL);
+ if (vers->ts_user == NULL)
+ {
+ line = xmalloc (strlen (user) + 15);
+ (void) sprintf (line, "Initial %s", user);
+ Register (entries, user, vers->vn_rcs ? vers->vn_rcs : "0",
+ line, vers->options, vers->tag,
+ vers->date, (char *) 0);
+ free (line);
+ }
+ freevers_ts (&vers);
+ }
+
+ Entries_Close (entries);
+ }
+
+ /* Don't log "export", just regular "checkouts" */
+ if (strcmp (command_name, "export") != 0 && !pipeout)
+ history_write ('O', preload_update_dir, (tag ? tag : date), where,
+ repository);
+
+ /* go ahead and call update now that everything is set */
+ err += do_update (*pargc - 1, argv + 1, options, tag, date,
+ force_tag_match, local_specified, 1 /* update -d */,
+ aflag, checkout_prune_dirs, pipeout, which, join_rev1,
+ join_rev2, preload_update_dir);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (err);
+}
+
+static char *
+findslash (start, p)
+ char *start;
+ char *p;
+{
+ while (p >= start && *p != '/')
+ p--;
+ if (p < start)
+ return (NULL);
+ else
+ return (p);
+}
+
+/*
+ * build all the dirs along the path to dir with CVS subdirs with appropriate
+ * repositories and Entries.Static files
+ */
+static int
+build_dirs_and_chdir (dir, prepath, realdir, sticky)
+ char *dir;
+ char *prepath;
+ char *realdir;
+ int sticky;
+{
+ FILE *fp;
+ char repository[PATH_MAX];
+ char path[PATH_MAX];
+ char path2[PATH_MAX];
+ char *slash;
+ char *slash2;
+ char *cp;
+ char *cp2;
+
+ (void) strcpy (path, dir);
+ (void) strcpy (path2, realdir);
+ for (cp = path, cp2 = path2;
+ (slash = strchr (cp, '/')) != NULL && (slash2 = strchr (cp2, '/')) != NULL;
+ cp = slash + 1, cp2 = slash2 + 1)
+ {
+ *slash = '\0';
+ *slash2 = '\0';
+ (void) CVS_MKDIR (cp, 0777);
+ if (chdir (cp) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", cp);
+ return (1);
+ }
+ if (!isfile (CVSADM) && strcmp (command_name, "export") != 0)
+ {
+ (void) sprintf (repository, "%s/%s", prepath, path2);
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+ Create_Admin (".", path, repository, sticky ? (char *) NULL : tag,
+ sticky ? (char *) NULL : date);
+ if (!noexec)
+ {
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose(fp) == EOF)
+ error(1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (path, repository);
+#endif
+ }
+ }
+ *slash = '/';
+ *slash2 = '/';
+ }
+ (void) CVS_MKDIR (cp, 0777);
+ if (chdir (cp) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", cp);
+ return (1);
+ }
+ return (0);
+}
diff --git a/contrib/cvs/src/classify.c b/contrib/cvs/src/classify.c
new file mode 100644
index 0000000..924314b
--- /dev/null
+++ b/contrib/cvs/src/classify.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ */
+
+#include "cvs.h"
+
+#ifdef SERVER_SUPPORT
+static void sticky_ck PROTO((char *file, int aflag, Vers_TS * vers,
+ List * entries,
+ char *repository, char *update_dir));
+#else
+static void sticky_ck PROTO((char *file, int aflag, Vers_TS * vers, List * entries));
+#endif
+
+/*
+ * Classify the state of a file
+ */
+Ctype
+Classify_File (file, tag, date, options, force_tag_match, aflag, repository,
+ entries, rcsnode, versp, update_dir, pipeout)
+ char *file;
+ char *tag;
+ char *date;
+ char *options;
+ int force_tag_match;
+ int aflag;
+ char *repository;
+ List *entries;
+ RCSNode *rcsnode;
+ Vers_TS **versp;
+ char *update_dir;
+ int pipeout;
+{
+ Vers_TS *vers;
+ Ctype ret;
+ char *fullname;
+
+ fullname = xmalloc (strlen (update_dir) + strlen (file) + 10);
+ if (update_dir[0] == '\0')
+ strcpy (fullname, file);
+ else
+ sprintf (fullname, "%s/%s", update_dir, file);
+
+ /* get all kinds of good data about the file */
+ vers = Version_TS (repository, options, tag, date, file,
+ force_tag_match, 0, entries, rcsnode);
+
+ if (vers->vn_user == NULL)
+ {
+ /* No entry available, ts_rcs is invalid */
+ if (vers->vn_rcs == NULL)
+ {
+ /* there is no RCS file either */
+ if (vers->ts_user == NULL)
+ {
+ /* there is no user file */
+ if (!force_tag_match || !(vers->tag || vers->date))
+ if (!really_quiet)
+ error (0, 0, "nothing known about %s", fullname);
+ ret = T_UNKNOWN;
+ }
+ else
+ {
+ /* there is a user file */
+ if (!force_tag_match || !(vers->tag || vers->date))
+ if (!really_quiet)
+ error (0, 0, "use `cvs add' to create an entry for %s",
+ fullname);
+ ret = T_UNKNOWN;
+ }
+ }
+ else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
+ {
+ if (vers->ts_user == NULL)
+ /*
+ * Logically seems to me this should be T_UPTODATE.
+ * But the joining code in update.c seems to expect
+ * T_CHECKOUT, and that is what has traditionally been
+ * returned for this case.
+ */
+ ret = T_CHECKOUT;
+ else
+ {
+ error (0, 0, "use `cvs add' to create an entry for %s",
+ fullname);
+ ret = T_UNKNOWN;
+ }
+ }
+ else
+ {
+ /* there is an rcs file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file; needs checkout */
+ ret = T_CHECKOUT;
+ }
+ else
+ {
+ if (pipeout)
+ {
+ /*
+ * The user file doesn't necessarily have anything
+ * to do with this.
+ */
+ ret = T_CHECKOUT;
+ }
+ /*
+ * There is a user file; print a warning and add it to the
+ * conflict list, only if it is indeed different from what we
+ * plan to extract
+ */
+ else if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ {
+ /* the files were different so it is a conflict */
+ if (!really_quiet)
+ error (0, 0, "move away %s; it is in the way",
+ fullname);
+ ret = T_CONFLICT;
+ }
+ else
+ /* since there was no difference, still needs checkout */
+ ret = T_CHECKOUT;
+ }
+ }
+ }
+ else if (strcmp (vers->vn_user, "0") == 0)
+ {
+ /* An entry for a new-born file; ts_rcs is dummy */
+
+ if (vers->ts_user == NULL)
+ {
+ /*
+ * There is no user file, but there should be one; remove the
+ * entry
+ */
+ if (!really_quiet)
+ error (0, 0, "warning: new-born %s has disappeared", fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ else
+ {
+ /* There is a user file */
+
+ if (vers->vn_rcs == NULL)
+ /* There is no RCS file, added file */
+ ret = T_ADDED;
+ else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
+ /* we are resurrecting. */
+ ret = T_ADDED;
+ else
+ {
+ if (vers->srcfile->flags & INATTIC
+ && vers->srcfile->flags & VALID)
+ {
+ /* This file has been added on some branch other than
+ the one we are looking at. In the branch we are
+ looking at, the file was already valid. */
+ if (!really_quiet)
+ error (0, 0,
+ "\
+conflict: %s has been added, but already exists",
+ fullname);
+ }
+ else
+ {
+ /*
+ * There is an RCS file, so someone else must have checked
+ * one in behind our back; conflict
+ */
+ if (!really_quiet)
+ error (0, 0,
+ "\
+conflict: %s created independently by second party",
+ fullname);
+ }
+ ret = T_CONFLICT;
+ }
+ }
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ /* An entry for a removed file, ts_rcs is invalid */
+
+ if (vers->ts_user == NULL)
+ {
+ char tmp[PATH_MAX];
+
+ /* There is no user file (as it should be) */
+
+ (void) sprintf (tmp, "-%s", vers->vn_rcs ? vers->vn_rcs : "");
+
+ if (vers->vn_rcs == NULL)
+ {
+
+ /*
+ * There is no RCS file; this is all-right, but it has been
+ * removed independently by a second party; remove the entry
+ */
+ ret = T_REMOVE_ENTRY;
+ }
+ else if (strcmp (tmp, vers->vn_user) == 0)
+
+ /*
+ * The RCS file is the same version as the user file was, and
+ * that's OK; remove it
+ */
+ ret = T_REMOVED;
+ else
+ {
+
+ /*
+ * The RCS file is a newer version than the removed user file
+ * and this is definitely not OK; make it a conflict.
+ */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: removed %s was modified by second party",
+ fullname);
+ ret = T_CONFLICT;
+ }
+ }
+ else
+ {
+ /* The user file shouldn't be there */
+ if (!really_quiet)
+ error (0, 0, "%s should be removed and is still there",
+ fullname);
+ ret = T_REMOVED;
+ }
+ }
+ else
+ {
+ /* A normal entry, TS_Rcs is valid */
+ if (vers->vn_rcs == NULL)
+ {
+ /* There is no RCS file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file, so just remove the entry */
+ if (!really_quiet)
+ error (0, 0, "warning: %s is not (any longer) pertinent",
+ fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
+ {
+
+ /*
+ * The user file is still unmodified, so just remove it from
+ * the entry list
+ */
+ if (!really_quiet)
+ error (0, 0, "%s is no longer in the repository",
+ fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ else
+ {
+ /*
+ * The user file has been modified and since it is no longer
+ * in the repository, a conflict is raised
+ */
+ if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ {
+ /* they are different -> conflict */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: %s is modified but no longer in the repository",
+ fullname);
+ ret = T_CONFLICT;
+ }
+ else
+ {
+ /* they weren't really different */
+ if (!really_quiet)
+ error (0, 0,
+ "warning: %s is not (any longer) pertinent",
+ fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ }
+ }
+ else if (strcmp (vers->vn_rcs, vers->vn_user) == 0)
+ {
+ /* The RCS file is the same version as the user file */
+
+ if (vers->ts_user == NULL)
+ {
+
+ /*
+ * There is no user file, so note that it was lost and
+ * extract a new version
+ */
+ if (strcmp (command_name, "update") == 0)
+ if (!really_quiet)
+ error (0, 0, "warning: %s was lost", fullname);
+ ret = T_CHECKOUT;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
+ {
+
+ /*
+ * The user file is still unmodified, so nothing special at
+ * all to do -- no lists updated, unless the sticky -k option
+ * has changed. If the sticky tag has changed, we just need
+ * to re-register the entry
+ */
+ if (vers->entdata->options &&
+ strcmp (vers->entdata->options, vers->options) != 0)
+ ret = T_CHECKOUT;
+ else
+ {
+#ifdef SERVER_SUPPORT
+ sticky_ck (file, aflag, vers, entries,
+ repository, update_dir);
+#else
+ sticky_ck (file, aflag, vers, entries);
+#endif
+ ret = T_UPTODATE;
+ }
+ }
+ else
+ {
+
+ /*
+ * The user file appears to have been modified, but we call
+ * No_Difference to verify that it really has been modified
+ */
+ if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ {
+
+ /*
+ * they really are different; modified if we aren't
+ * changing any sticky -k options, else needs merge
+ */
+#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) == 0)
+ ret = T_MODIFIED;
+ else
+ ret = T_NEEDS_MERGE;
+#else
+ ret = T_MODIFIED;
+#ifdef SERVER_SUPPORT
+ sticky_ck (file, aflag, vers, entries,
+ repository, update_dir);
+#else
+ sticky_ck (file, aflag, vers, entries);
+#endif /* SERVER_SUPPORT */
+#endif
+ }
+ else
+ {
+ /* file has not changed; check out if -k changed */
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) != 0)
+ {
+ ret = T_CHECKOUT;
+ }
+ else
+ {
+
+ /*
+ * else -> note that No_Difference will Register the
+ * file already for us, using the new tag/date. This
+ * is the desired behaviour
+ */
+ ret = T_UPTODATE;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* The RCS file is a newer version than the user file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file, so just get it */
+
+ if (strcmp (command_name, "update") == 0)
+ if (!really_quiet)
+ error (0, 0, "warning: %s was lost", fullname);
+ ret = T_CHECKOUT;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
+ {
+
+ /*
+ * The user file is still unmodified, so just get it as well
+ */
+#ifdef SERVER_SUPPORT
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) != 0
+ || (vers->srcfile != NULL
+ && (vers->srcfile->flags & INATTIC) != 0))
+ ret = T_CHECKOUT;
+ else
+ ret = T_PATCH;
+#else
+ ret = T_CHECKOUT;
+#endif
+ }
+ else
+ {
+ if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ /* really modified, needs to merge */
+ ret = T_NEEDS_MERGE;
+#ifdef SERVER_SUPPORT
+ else if ((strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options)
+ != 0)
+ || (vers->srcfile != NULL
+ && (vers->srcfile->flags & INATTIC) != 0))
+ /* not really modified, check it out */
+ ret = T_CHECKOUT;
+ else
+ ret = T_PATCH;
+#else
+ else
+ /* not really modified, check it out */
+ ret = T_CHECKOUT;
+#endif
+ }
+ }
+ }
+
+ /* free up the vers struct, or just return it */
+ if (versp != (Vers_TS **) NULL)
+ *versp = vers;
+ else
+ freevers_ts (&vers);
+
+ free (fullname);
+
+ /* return the status of the file */
+ return (ret);
+}
+
+static void
+#ifdef SERVER_SUPPORT
+sticky_ck (file, aflag, vers, entries, repository, update_dir)
+#else
+sticky_ck (file, aflag, vers, entries)
+#endif
+ char *file;
+ int aflag;
+ Vers_TS *vers;
+ List *entries;
+#ifdef SERVER_SUPPORT
+ char *repository;
+ char *update_dir;
+#endif
+{
+ if (aflag || vers->tag || vers->date)
+ {
+ char *enttag = vers->entdata->tag;
+ char *entdate = vers->entdata->date;
+
+ if ((enttag && vers->tag && strcmp (enttag, vers->tag)) ||
+ ((enttag && !vers->tag) || (!enttag && vers->tag)) ||
+ (entdate && vers->date && strcmp (entdate, vers->date)) ||
+ ((entdate && !vers->date) || (!entdate && vers->date)))
+ {
+ Register (entries, file, vers->vn_user, vers->ts_rcs,
+ vers->options, vers->tag, vers->date, vers->ts_conflict);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* We need to update the entries line on the client side.
+ It is possible we will later update it again via
+ server_updated or some such, but that is OK. */
+ server_update_entries
+ (file, update_dir, repository,
+ strcmp (vers->ts_rcs, vers->ts_user) == 0 ?
+ SERVER_UPDATED : SERVER_MERGED);
+ }
+#endif
+ }
+ }
+}
diff --git a/contrib/cvs/src/client.c b/contrib/cvs/src/client.c
new file mode 100644
index 0000000..c0557ce
--- /dev/null
+++ b/contrib/cvs/src/client.c
@@ -0,0 +1,4490 @@
+/* CVS client-related stuff. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "cvs.h"
+#include "getline.h"
+#include "edit.h"
+
+#ifdef CLIENT_SUPPORT
+
+#include "md5.h"
+
+#if defined(AUTH_CLIENT_SUPPORT) || HAVE_KERBEROS || USE_DIRECT_TCP
+# ifdef HAVE_WINSOCK_H
+# include <winsock.h>
+# else /* No winsock.h */
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <netdb.h>
+# endif /* No winsock.h */
+#endif /* defined(AUTH_CLIENT_SUPPORT) || HAVE_KERBEROS || USE_DIRECT_TCP */
+
+#ifdef AUTH_CLIENT_SUPPORT
+char *get_cvs_password PROTO((char *user, char *host, char *cvsrooot));
+#endif /* AUTH_CLIENT_SUPPORT */
+
+#if HAVE_KERBEROS || USE_DIRECT_TCP
+#define CVS_PORT 1999
+
+#if HAVE_KERBEROS
+#include <krb.h>
+
+extern char *krb_realmofhost ();
+#ifndef HAVE_KRB_GET_ERR_TEXT
+#define krb_get_err_text(status) krb_err_txt[status]
+#endif /* HAVE_KRB_GET_ERR_TEXT */
+#endif /* HAVE_KERBEROS */
+
+#endif /* HAVE_KERBEROS || USE_DIRECT_TCP */
+
+static void add_prune_candidate PROTO((char *));
+
+/* All the commands. */
+int add PROTO((int argc, char **argv));
+int admin PROTO((int argc, char **argv));
+int checkout PROTO((int argc, char **argv));
+int commit PROTO((int argc, char **argv));
+int diff PROTO((int argc, char **argv));
+int history PROTO((int argc, char **argv));
+int import PROTO((int argc, char **argv));
+int cvslog PROTO((int argc, char **argv));
+int patch PROTO((int argc, char **argv));
+int release PROTO((int argc, char **argv));
+int cvsremove PROTO((int argc, char **argv));
+int rtag PROTO((int argc, char **argv));
+int status PROTO((int argc, char **argv));
+int tag PROTO((int argc, char **argv));
+int update PROTO((int argc, char **argv));
+
+/* All the response handling functions. */
+static void handle_ok PROTO((char *, int));
+static void handle_error PROTO((char *, int));
+static void handle_valid_requests PROTO((char *, int));
+static void handle_checked_in PROTO((char *, int));
+static void handle_new_entry PROTO((char *, int));
+static void handle_checksum PROTO((char *, int));
+static void handle_copy_file PROTO((char *, int));
+static void handle_updated PROTO((char *, int));
+static void handle_merged PROTO((char *, int));
+static void handle_patched PROTO((char *, int));
+static void handle_removed PROTO((char *, int));
+static void handle_remove_entry PROTO((char *, int));
+static void handle_set_static_directory PROTO((char *, int));
+static void handle_clear_static_directory PROTO((char *, int));
+static void handle_set_sticky PROTO((char *, int));
+static void handle_clear_sticky PROTO((char *, int));
+static void handle_set_checkin_prog PROTO((char *, int));
+static void handle_set_update_prog PROTO((char *, int));
+static void handle_module_expansion PROTO((char *, int));
+static void handle_m PROTO((char *, int));
+static void handle_e PROTO((char *, int));
+static void handle_notified PROTO((char *, int));
+
+static size_t try_read_from_server PROTO ((char *, size_t));
+#endif /* CLIENT_SUPPORT */
+
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/* Shared with server. */
+
+/*
+ * Return a malloc'd, '\0'-terminated string
+ * corresponding to the mode in SB.
+ */
+char *
+#ifdef __STDC__
+mode_to_string (mode_t mode)
+#else /* ! __STDC__ */
+mode_to_string (mode)
+ mode_t mode;
+#endif /* __STDC__ */
+{
+ char buf[18], u[4], g[4], o[4];
+ int i;
+
+ i = 0;
+ if (mode & S_IRUSR) u[i++] = 'r';
+ if (mode & S_IWUSR) u[i++] = 'w';
+ if (mode & S_IXUSR) u[i++] = 'x';
+ u[i] = '\0';
+
+ i = 0;
+ if (mode & S_IRGRP) g[i++] = 'r';
+ if (mode & S_IWGRP) g[i++] = 'w';
+ if (mode & S_IXGRP) g[i++] = 'x';
+ g[i] = '\0';
+
+ i = 0;
+ if (mode & S_IROTH) o[i++] = 'r';
+ if (mode & S_IWOTH) o[i++] = 'w';
+ if (mode & S_IXOTH) o[i++] = 'x';
+ o[i] = '\0';
+
+ sprintf(buf, "u=%s,g=%s,o=%s", u, g, o);
+ return xstrdup(buf);
+}
+
+/*
+ * Change mode of FILENAME to MODE_STRING.
+ * Returns 0 for success or errno code.
+ */
+int
+change_mode (filename, mode_string)
+ char *filename;
+ char *mode_string;
+{
+#ifdef CHMOD_BROKEN
+ char *p;
+ int writeable = 0;
+
+ /* We can only distinguish between
+ 1) readable
+ 2) writeable
+ 3) Picasso's "Blue Period"
+ We handle the first two. */
+ p = mode_string;
+ while (*p != '\0')
+ {
+ if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
+ {
+ char *q = p + 2;
+ while (*q != ',' && *q != '\0')
+ {
+ if (*q == 'w')
+ writeable = 1;
+ ++q;
+ }
+ }
+ /* Skip to the next field. */
+ while (*p != ',' && *p != '\0')
+ ++p;
+ if (*p == ',')
+ ++p;
+ }
+
+ xchmod (filename, writeable);
+ return 0;
+
+#else /* ! CHMOD_BROKEN */
+
+ char *p;
+ mode_t mode = 0;
+
+ p = mode_string;
+ while (*p != '\0')
+ {
+ if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
+ {
+ int can_read = 0, can_write = 0, can_execute = 0;
+ char *q = p + 2;
+ while (*q != ',' && *q != '\0')
+ {
+ if (*q == 'r')
+ can_read = 1;
+ else if (*q == 'w')
+ can_write = 1;
+ else if (*q == 'x')
+ can_execute = 1;
+ ++q;
+ }
+ if (p[0] == 'u')
+ {
+ if (can_read)
+ mode |= S_IRUSR;
+ if (can_write)
+ mode |= S_IWUSR;
+ if (can_execute)
+ mode |= S_IXUSR;
+ }
+ else if (p[0] == 'g')
+ {
+ if (can_read)
+ mode |= S_IRGRP;
+ if (can_write)
+ mode |= S_IWGRP;
+ if (can_execute)
+ mode |= S_IXGRP;
+ }
+ else if (p[0] == 'o')
+ {
+ if (can_read)
+ mode |= S_IROTH;
+ if (can_write)
+ mode |= S_IWOTH;
+ if (can_execute)
+ mode |= S_IXOTH;
+ }
+ }
+ /* Skip to the next field. */
+ while (*p != ',' && *p != '\0')
+ ++p;
+ if (*p == ',')
+ ++p;
+ }
+
+ if (chmod (filename, mode) < 0)
+ return errno;
+ return 0;
+#endif /* ! CHMOD_BROKEN */
+}
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+
+#ifdef CLIENT_SUPPORT
+
+/* The host part of CVSROOT. */
+static char *server_host;
+/* The user part of CVSROOT */
+static char *server_user;
+/* The repository part of CVSROOT. */
+static char *server_cvsroot;
+
+int client_active;
+
+int client_prune_dirs;
+
+static int cvsroot_parsed = 0;
+
+static List *ignlist = (List *) NULL;
+
+/* Set server_host and server_cvsroot. */
+static void
+parse_cvsroot ()
+{
+ char *p;
+#ifdef AUTH_CLIENT_SUPPORT
+ static char *access_method;
+#endif /* AUTH_CLIENT_SUPPORT */
+
+ /* Don't go through the trouble twice. */
+ if (cvsroot_parsed)
+ return;
+
+ server_host = xstrdup (CVSroot);
+
+#ifdef AUTH_CLIENT_SUPPORT
+ if ((server_host[0] == ':'))
+ {
+ /* Access method specified, as in
+ * "cvs -d :pserver:user@host:/path".
+ * We need to get past that part of CVSroot before parsing the
+ * rest of it.
+ */
+ access_method = p = &(server_host[1]);
+
+ if (! *access_method)
+ error (1, 0, "bad CVSroot: %s", CVSroot);
+
+ if (! *(p = strchr (access_method, ':')))
+ error (1, 0, "bad CVSroot: %s", CVSroot);
+
+ *p = '\0';
+ p++;
+
+ server_host = p;
+
+ if (! *server_host)
+ error (1, 0, "bad CVSroot: %s", CVSroot);
+
+ if (strcmp (access_method, "pserver") == 0)
+ use_authenticating_server = 1;
+ else
+ error (1, 0, "unknown access method: %s", access_method);
+ }
+#endif /* AUTH_CLIENT_SUPPORT */
+
+ /* First get just the pathname. */
+ server_cvsroot = strchr (server_host, ':');
+ *server_cvsroot = '\0';
+ ++server_cvsroot;
+
+ /* Then deal with host and possible user. */
+ if ( (p = strchr (server_host, '@')) == NULL)
+ {
+ server_user = NULL;
+ }
+ else
+ {
+ server_user = server_host;
+ server_host = p;
+ ++server_host;
+ *p = '\0';
+ }
+
+ client_active = 1;
+ cvsroot_parsed = 1;
+}
+
+#ifdef NO_SOCKET_TO_FD
+/* Under certain circumstances, we must communicate with the server
+ via a socket using send() and recv(). This is because under some
+ operating systems (OS/2 and Windows 95 come to mind), a socket
+ cannot be converted to a file descriptor -- it must be treated as a
+ socket and nothing else. */
+static int use_socket_style = 0;
+static int server_sock;
+#endif /* NO_SOCKET_TO_FD */
+
+/* Stream to write to the server. */
+static FILE *to_server;
+/* Stream to read from the server. */
+static FILE *from_server;
+
+/* We might want to log client/server traffic. */
+static FILE *from_server_logfile;
+static FILE *to_server_logfile;
+
+#if ! RSH_NOT_TRANSPARENT
+/* Process ID of rsh subprocess. */
+static int rsh_pid = -1;
+#endif /* ! RSH_NOT_TRANSPARENT */
+
+
+/*
+ * Read a line from the server. Result does not include the terminating \n.
+ *
+ * Space for the result is malloc'd and should be freed by the caller.
+ *
+ * Returns number of bytes read. If EOF_OK, then return 0 on end of file,
+ * else end of file is an error.
+ */
+static int
+read_line (resultp, eof_ok)
+ char **resultp;
+ int eof_ok;
+{
+ int c;
+ char *result;
+ size_t input_index = 0;
+ size_t result_size = 80;
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ fflush (to_server);
+
+ result = (char *) xmalloc (result_size);
+
+ while (1)
+ {
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ char ch;
+ /* Yes, this sucks performance-wise. Short of implementing
+ our own buffering, I'm not sure how to effect a big
+ improvement. We could at least avoid calling
+ read_from_server() for each character if we were willing
+ to duplicate a lot of its code, but I'm not sure that's
+ worth it. */
+ read_from_server (&ch, 1);
+ c = ch;
+ }
+ else
+#endif /* NO_SOCKET_TO_FD */
+ c = getc (from_server);
+
+ if (c == EOF)
+ {
+ free (result);
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ if (ferror (from_server))
+ error (1, errno, "reading from server");
+
+ /* It's end of file. */
+ if (eof_ok)
+ return 0;
+ else
+ error (1, 0, "end of file from server (consult above messages if any)");
+ }
+
+ if (c == '\n')
+ break;
+
+ result[input_index++] = c;
+ while (input_index + 1 >= result_size)
+ {
+ result_size *= 2;
+ result = (char *) xrealloc (result, result_size);
+ }
+ }
+
+ if (resultp)
+ *resultp = result;
+
+ /* Terminate it just for kicks, but we *can* deal with embedded NULs. */
+ result[input_index] = '\0';
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ {
+ /*
+ * If we're using socket style, then everything has already
+ * been logged because read_from_server() was used to get the
+ * individual chars, and read_from_server() logs already.
+ */
+ if (from_server_logfile)
+ {
+ if (fwrite (result, 1, input_index, from_server_logfile)
+ < input_index)
+ error (0, errno, "writing to from-server logfile");
+ putc ('\n', from_server_logfile);
+ }
+ }
+
+ if (resultp == NULL)
+ free (result);
+ return input_index;
+}
+
+#endif /* CLIENT_SUPPORT */
+
+
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/*
+ * Zero if compression isn't supported or requested; non-zero to indicate
+ * a compression level to request from gzip.
+ */
+int gzip_level;
+
+int filter_through_gzip (fd, dir, level, pidp)
+ int fd, dir, level;
+ pid_t *pidp;
+{
+ static char buf[5] = "-";
+ static char *gzip_argv[3] = { "gzip", buf };
+
+ sprintf (buf+1, "%d", level);
+ return filter_stream_through_program (fd, dir, &gzip_argv[0], pidp);
+}
+
+int filter_through_gunzip (fd, dir, pidp)
+ int fd, dir;
+ pid_t *pidp;
+{
+ static char *gunzip_argv[3] = { "gunzip", "-d" };
+ return filter_stream_through_program (fd, dir, &gunzip_argv[0], pidp);
+}
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+
+#ifdef CLIENT_SUPPORT
+
+/*
+ * The Repository for the top level of this command (not necessarily
+ * the CVSROOT, just the current directory at the time we do it).
+ */
+static char *toplevel_repos;
+
+/* Working directory when we first started. */
+char toplevel_wd[PATH_MAX];
+
+static void
+handle_ok (args, len)
+ char *args;
+ int len;
+{
+ return;
+}
+
+static void
+handle_error (args, len)
+ char *args;
+ int len;
+{
+ int something_printed;
+
+ /*
+ * First there is a symbolic error code followed by a space, which
+ * we ignore.
+ */
+ char *p = strchr (args, ' ');
+ if (p == NULL)
+ {
+ error (0, 0, "invalid data from cvs server");
+ return;
+ }
+ ++p;
+ len -= p - args;
+ something_printed = 0;
+ for (; len > 0; --len)
+ {
+ something_printed = 1;
+ putc (*p++, stderr);
+ }
+ if (something_printed)
+ putc ('\n', stderr);
+}
+
+static void
+handle_valid_requests (args, len)
+ char *args;
+ int len;
+{
+ char *p = args;
+ char *q;
+ struct request *rq;
+ do
+ {
+ q = strchr (p, ' ');
+ if (q != NULL)
+ *q++ = '\0';
+ for (rq = requests; rq->name != NULL; ++rq)
+ {
+ if (strcmp (rq->name, p) == 0)
+ break;
+ }
+ if (rq->name == NULL)
+ /*
+ * It is a request we have never heard of (and thus never
+ * will want to use). So don't worry about it.
+ */
+ ;
+ else
+ {
+ if (rq->status == rq_enableme)
+ {
+ /*
+ * Server wants to know if we have this, to enable the
+ * feature.
+ */
+ send_to_server (rq->name, 0);
+ send_to_server ("\012", 0);
+
+ if (!strcmp("UseUnchanged",rq->name))
+ use_unchanged = 1;
+ }
+ else
+ rq->status = rq_supported;
+ }
+ p = q;
+ } while (q != NULL);
+ for (rq = requests; rq->name != NULL; ++rq)
+ {
+ if (rq->status == rq_essential)
+ error (1, 0, "request `%s' not supported by server", rq->name);
+ else if (rq->status == rq_optional)
+ rq->status = rq_not_supported;
+ }
+}
+
+static int use_directory = -1;
+
+static char *get_short_pathname PROTO((const char *));
+
+static char *
+get_short_pathname (name)
+ const char *name;
+{
+ const char *retval;
+ if (use_directory)
+ return (char *) name;
+ if (strncmp (name, toplevel_repos, strlen (toplevel_repos)) != 0)
+ error (1, 0, "server bug: name `%s' doesn't specify file in `%s'",
+ name, toplevel_repos);
+ retval = name + strlen (toplevel_repos) + 1;
+ if (retval[-1] != '/')
+ error (1, 0, "server bug: name `%s' doesn't specify file in `%s'",
+ name, toplevel_repos);
+ return (char *) retval;
+}
+
+/*
+ * Do all the processing for PATHNAME, where pathname consists of the
+ * repository and the filename. The parameters we pass to FUNC are:
+ * DATA is just the DATA parameter which was passed to
+ * call_in_directory; ENT_LIST is a pointer to an entries list (which
+ * we manage the storage for); SHORT_PATHNAME is the pathname of the
+ * file relative to the (overall) directory in which the command is
+ * taking place; and FILENAME is the filename portion only of
+ * SHORT_PATHNAME. When we call FUNC, the curent directory points to
+ * the directory portion of SHORT_PATHNAME. */
+
+static char *last_dir_name;
+
+static void
+call_in_directory (pathname, func, data)
+ char *pathname;
+ void (*func) PROTO((char *data, List *ent_list, char *short_pathname,
+ char *filename));
+ char *data;
+{
+ static List *last_entries;
+
+ char *dir_name;
+ char *filename;
+ /* Just the part of pathname relative to toplevel_repos. */
+ char *short_pathname = get_short_pathname (pathname);
+ char *p;
+
+ /*
+ * Do the whole descent in parallel for the repositories, so we
+ * know what to put in CVS/Repository files. I'm not sure the
+ * full hair is necessary since the server does a similar
+ * computation; I suspect that we only end up creating one
+ * directory at a time anyway.
+ *
+ * Also note that we must *only* worry about this stuff when we
+ * are creating directories; `cvs co foo/bar; cd foo/bar; cvs co
+ * CVSROOT; cvs update' is legitimate, but in this case
+ * foo/bar/CVSROOT/CVS/Repository is not a subdirectory of
+ * foo/bar/CVS/Repository.
+ */
+ char *reposname;
+ char *short_repos;
+ char *reposdirname;
+ char *rdirp;
+ int reposdirname_absolute;
+
+ reposname = NULL;
+ if (use_directory)
+ read_line (&reposname, 0);
+
+ reposdirname_absolute = 0;
+ if (reposname != NULL)
+ {
+ if (strncmp (reposname, toplevel_repos, strlen (toplevel_repos)) != 0)
+ {
+ reposdirname_absolute = 1;
+ short_repos = reposname;
+ }
+ else
+ {
+ short_repos = reposname + strlen (toplevel_repos) + 1;
+ if (short_repos[-1] != '/')
+ {
+ reposdirname_absolute = 1;
+ short_repos = reposname;
+ }
+ }
+ }
+ else
+ {
+ short_repos = short_pathname;
+ }
+ reposdirname = xstrdup (short_repos);
+ p = strrchr (reposdirname, '/');
+ if (p == NULL)
+ {
+ reposdirname = xrealloc (reposdirname, 2);
+ reposdirname[0] = '.'; reposdirname[1] = '\0';
+ }
+ else
+ *p = '\0';
+
+ dir_name = xstrdup (short_pathname);
+ p = strrchr (dir_name, '/');
+ if (p == NULL)
+ {
+ dir_name = xrealloc (dir_name, 2);
+ dir_name[0] = '.'; dir_name[1] = '\0';
+ }
+ else
+ *p = '\0';
+ if (client_prune_dirs)
+ add_prune_candidate (dir_name);
+
+ filename = strrchr (short_repos, '/');
+ if (filename == NULL)
+ filename = short_repos;
+ else
+ ++filename;
+
+ if (reposname != NULL)
+ {
+ /* This is the use_directory case. */
+
+ short_pathname = xmalloc (strlen (pathname) + strlen (filename) + 5);
+ strcpy (short_pathname, pathname);
+ strcat (short_pathname, filename);
+ }
+
+ if (last_dir_name == NULL
+ || strcmp (last_dir_name, dir_name) != 0)
+ {
+ if (last_dir_name)
+ free (last_dir_name);
+ last_dir_name = dir_name;
+
+ if (toplevel_wd[0] == '\0')
+ if (getwd (toplevel_wd) == NULL)
+ error (1, 0,
+ "could not get working directory: %s", toplevel_wd);
+
+ if (chdir (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ if (chdir (dir_name) < 0)
+ {
+ char *dir;
+ char *dirp;
+
+ if (! existence_error (errno))
+ error (1, errno, "could not chdir to %s", dir_name);
+
+ /* Directory does not exist, we need to create it. */
+ dir = xmalloc (strlen (dir_name) + 1);
+ dirp = dir_name;
+ rdirp = reposdirname;
+
+ /* This algorithm makes nested directories one at a time
+ and create CVS administration files in them. For
+ example, we're checking out foo/bar/baz from the
+ repository:
+
+ 1) create foo, point CVS/Repository to <root>/foo
+ 2) .. foo/bar .. <root>/foo/bar
+ 3) .. foo/bar/baz .. <root>/foo/bar/baz
+
+ As you can see, we're just stepping along DIR_NAME (with
+ DIRP) and REPOSDIRNAME (with RDIRP) respectively.
+
+ We need to be careful when we are checking out a
+ module, however, since DIR_NAME and REPOSDIRNAME are not
+ going to be the same. Since modules will not have any
+ slashes in their names, we should watch the output of
+ STRCHR to decide whether or not we should use STRCHR on
+ the RDIRP. That is, if we're down to a module name,
+ don't keep picking apart the repository directory name. */
+
+ do
+ {
+ dirp = strchr (dirp, '/');
+ if (dirp)
+ {
+ strncpy (dir, dir_name, dirp - dir_name);
+ dir[dirp - dir_name] = '\0';
+ /* Skip the slash. */
+ ++dirp;
+ if (rdirp == NULL)
+ error (0, 0,
+ "internal error: repository string too short.");
+ else
+ rdirp = strchr (rdirp, '/');
+ }
+ else
+ {
+ /* If there are no more slashes in the dir name,
+ we're down to the most nested directory -OR- to
+ the name of a module. In the first case, we
+ should be down to a DIRP that has no slashes,
+ so it won't help/hurt to do another STRCHR call
+ on DIRP. It will definitely hurt, however, if
+ we're down to a module name, since a module
+ name can point to a nested directory (that is,
+ DIRP will still have slashes in it. Therefore,
+ we should set it to NULL so the routine below
+ copies the contents of REMOTEDIRNAME onto the
+ root repository directory (does this if rdirp
+ is set to NULL, because we used to do an extra
+ STRCHR call here). */
+
+ rdirp = NULL;
+ strcpy (dir, dir_name);
+ }
+
+ if (CVS_MKDIR (dir, 0777) < 0)
+ {
+ /* Now, let me get this straight. In IBM C/C++
+ * under OS/2, the error string for EEXIST is:
+ *
+ * "The file already exists",
+ *
+ * and the error string for EACCESS is:
+ *
+ * "The file or directory specified is read-only".
+ *
+ * Nonetheless, mkdir() will set EACCESS if the
+ * directory *exists*, according both to the
+ * documentation and its actual behavior.
+ *
+ * I'm sure that this made sense, to someone,
+ * somewhere, sometime. Just not me, here, now.
+ */
+#ifdef EACCESS
+ if ((errno != EACCESS) && (errno != EEXIST))
+ error (1, errno, "cannot make directory %s", dir);
+#else /* ! defined(EACCESS) */
+ if ((errno != EEXIST))
+ error (1, errno, "cannot make directory %s", dir);
+#endif /* defined(EACCESS) */
+
+ /* It already existed, fine. Just keep going. */
+ }
+ else if (strcmp (command_name, "export") == 0)
+ /* Don't create CVSADM directories if this is export. */
+ ;
+ else
+ {
+ /*
+ * Put repository in CVS/Repository. For historical
+ * (pre-CVS/Root) reasons, this is an absolute pathname,
+ * but what really matters is the part of it which is
+ * relative to cvsroot.
+ */
+ char *repo;
+ char *r;
+
+ repo = xmalloc (strlen (reposdirname)
+ + strlen (toplevel_repos)
+ + 80);
+ if (reposdirname_absolute)
+ r = repo;
+ else
+ {
+ strcpy (repo, toplevel_repos);
+ strcat (repo, "/");
+ r = repo + strlen (repo);
+ }
+
+ if (rdirp)
+ {
+ strncpy (r, reposdirname, rdirp - reposdirname);
+ r[rdirp - reposdirname] = '\0';
+ }
+ else
+ strcpy (r, reposdirname);
+
+ Create_Admin (dir, dir, repo,
+ (char *)NULL, (char *)NULL);
+ free (repo);
+ }
+
+ if (rdirp != NULL)
+ {
+ /* Skip the slash. */
+ ++rdirp;
+ }
+
+ } while (dirp != NULL);
+ free (dir);
+ /* Now it better work. */
+ if (chdir (dir_name) < 0)
+ error (1, errno, "could not chdir to %s", dir_name);
+ }
+
+ if (strcmp (command_name, "export") != 0)
+ {
+ if (last_entries)
+ Entries_Close (last_entries);
+ last_entries = Entries_Open (0);
+ }
+ }
+ else
+ free (dir_name);
+ free (reposdirname);
+ (*func) (data, last_entries, short_pathname, filename);
+ if (reposname != NULL)
+ {
+ free (short_pathname);
+ free (reposname);
+ }
+}
+
+static void
+copy_a_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *newname;
+#ifdef USE_VMS_FILENAMES
+ char *p;
+#endif
+
+ read_line (&newname, 0);
+
+#ifdef USE_VMS_FILENAMES
+ /* Mogrify the filename so VMS is happy with it. */
+ for(p = newname; *p; p++)
+ if(*p == '.' || *p == '#') *p = '_';
+#endif
+
+ copy_file (filename, newname);
+ free (newname);
+}
+
+static void
+handle_copy_file (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, copy_a_file, (char *)NULL);
+}
+
+
+static void read_counted_file PROTO ((char *, char *));
+
+/* Read from the server the count for the length of a file, then read
+ the contents of that file and write them to FILENAME. FULLNAME is
+ the name of the file for use in error messages. FIXME-someday:
+ extend this to deal with compressed files and make update_entries
+ use it. On error, gives a fatal error. */
+static void
+read_counted_file (filename, fullname)
+ char *filename;
+ char *fullname;
+{
+ char *size_string;
+ size_t size;
+ char *buf;
+
+ /* Pointers in buf to the place to put data which will be read,
+ and the data which needs to be written, respectively. */
+ char *pread;
+ char *pwrite;
+ /* Number of bytes left to read and number of bytes in buf waiting to
+ be written, respectively. */
+ size_t nread;
+ size_t nwrite;
+
+ FILE *fp;
+
+ read_line (&size_string, 0);
+ if (size_string[0] == 'z')
+ error (1, 0, "\
+protocol error: compressed files not supported for that operation");
+ /* FIXME: should be doing more error checking, probably. Like using
+ strtoul and making sure we used up the whole line. */
+ size = atoi (size_string);
+ free (size_string);
+
+ /* A more sophisticated implementation would use only a limited amount
+ of buffer space (8K perhaps), and read that much at a time. We allocate
+ a buffer for the whole file only to make it easy to keep track what
+ needs to be read and written. */
+ buf = xmalloc (size);
+
+ /* FIXME-someday: caller should pass in a flag saying whether it
+ is binary or not. I haven't carefully looked into whether
+ CVS/Template files should use local text file conventions or
+ not. */
+ fp = fopen (filename, "wb");
+ if (fp == NULL)
+ error (1, errno, "cannot write %s", fullname);
+ nread = size;
+ nwrite = 0;
+ pread = buf;
+ pwrite = buf;
+ while (nread > 0 || nwrite > 0)
+ {
+ size_t n;
+
+ if (nread > 0)
+ {
+ n = try_read_from_server (pread, nread);
+ nread -= n;
+ pread += n;
+ nwrite += n;
+ }
+
+ if (nwrite > 0)
+ {
+ n = fwrite (pwrite, 1, nwrite, fp);
+ if (ferror (fp))
+ error (1, errno, "cannot write %s", fullname);
+ nwrite -= n;
+ pwrite += n;
+ }
+ }
+ free (buf);
+ if (fclose (fp) < 0)
+ error (1, errno, "cannot close %s", fullname);
+}
+
+/*
+ * The Checksum response gives the checksum for the file transferred
+ * over by the next Updated, Merged or Patch response. We just store
+ * it here, and then check it in update_entries.
+ */
+
+static int stored_checksum_valid;
+static unsigned char stored_checksum[16];
+
+static void
+handle_checksum (args, len)
+ char *args;
+ int len;
+{
+ char *s;
+ char buf[3];
+ int i;
+
+ if (stored_checksum_valid)
+ error (1, 0, "Checksum received before last one was used");
+
+ s = args;
+ buf[2] = '\0';
+ for (i = 0; i < 16; i++)
+ {
+ char *bufend;
+
+ buf[0] = *s++;
+ buf[1] = *s++;
+ stored_checksum[i] = (char) strtol (buf, &bufend, 16);
+ if (bufend != buf + 2)
+ break;
+ }
+
+ if (i < 16 || *s != '\0')
+ error (1, 0, "Invalid Checksum response: `%s'", args);
+
+ stored_checksum_valid = 1;
+}
+
+static int stored_mode_valid;
+static char *stored_mode;
+
+static void handle_mode PROTO ((char *, int));
+
+static void
+handle_mode (args, len)
+ char *args;
+ int len;
+{
+ if (stored_mode_valid)
+ error (1, 0, "protocol error: duplicate Mode");
+ if (stored_mode != NULL)
+ free (stored_mode);
+ stored_mode = xstrdup (args);
+ stored_mode_valid = 1;
+}
+
+/*
+ * If we receive a patch, but the patch program fails to apply it, we
+ * want to request the original file. We keep a list of files whose
+ * patches have failed.
+ */
+
+char **failed_patches;
+int failed_patches_count;
+
+struct update_entries_data
+{
+ enum {
+ /*
+ * We are just getting an Entries line; the local file is
+ * correct.
+ */
+ UPDATE_ENTRIES_CHECKIN,
+ /* We are getting the file contents as well. */
+ UPDATE_ENTRIES_UPDATE,
+ /*
+ * We are getting a patch against the existing local file, not
+ * an entire new file.
+ */
+ UPDATE_ENTRIES_PATCH
+ } contents;
+
+ /*
+ * String to put in the timestamp field or NULL to use the timestamp
+ * of the file.
+ */
+ char *timestamp;
+};
+
+/* Update the Entries line for this file. */
+static void
+update_entries (data_arg, ent_list, short_pathname, filename)
+ char *data_arg;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *entries_line;
+ struct update_entries_data *data = (struct update_entries_data *)data_arg;
+
+ char *cp;
+ char *user;
+ char *vn;
+ /* Timestamp field. Always empty according to the protocol. */
+ char *ts;
+ char *options;
+ char *tag;
+ char *date;
+ char *tag_or_date;
+ char *scratch_entries;
+ int bin;
+
+ read_line (&entries_line, 0);
+
+ /*
+ * Parse the entries line.
+ */
+ if (strcmp (command_name, "export") != 0)
+ {
+ scratch_entries = xstrdup (entries_line);
+
+ if (scratch_entries[0] != '/')
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ user = scratch_entries + 1;
+ if ((cp = strchr (user, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ vn = cp;
+ if ((cp = strchr (vn, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+
+ ts = cp;
+ if ((cp = strchr (ts, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ options = cp;
+ if ((cp = strchr (options, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ tag_or_date = cp;
+
+ /* If a slash ends the tag_or_date, ignore everything after it. */
+ cp = strchr (tag_or_date, '/');
+ if (cp != NULL)
+ *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;
+ }
+ else
+ /* For cvs export, assume it is a text file. FIXME: This is
+ broken behavior--we should be having the server tell us
+ whether it is text or binary and dealing accordingly. I
+ think maybe we can parse the entries line, get the options,
+ and then ignore the entries line otherwise, but I haven't
+ checked to see whether the server sends the entries line
+ correctly in this case. */
+ options = NULL;
+
+ if (data->contents == UPDATE_ENTRIES_UPDATE
+ || data->contents == UPDATE_ENTRIES_PATCH)
+ {
+ char *size_string;
+ char *mode_string;
+ int size;
+ int fd;
+ char *buf;
+ char *temp_filename;
+ int use_gzip, gzip_status;
+ pid_t gzip_pid = 0;
+
+ read_line (&mode_string, 0);
+
+ read_line (&size_string, 0);
+ if (size_string[0] == 'z')
+ {
+ use_gzip = 1;
+ size = atoi (size_string+1);
+ }
+ else
+ {
+ use_gzip = 0;
+ size = atoi (size_string);
+ }
+ free (size_string);
+
+ temp_filename = xmalloc (strlen (filename) + 80);
+#ifdef USE_VMS_FILENAMES
+ /* A VMS rename of "blah.dat" to "foo" to implies a
+ destination of "foo.dat" which is unfortinate for CVS */
+ sprintf (temp_filename, "%s_new_", filename);
+#else
+#ifdef _POSIX_NO_TRUNC
+ sprintf (temp_filename, ".new.%.9s", filename);
+#else /* _POSIX_NO_TRUNC */
+ sprintf (temp_filename, ".new.%s", filename);
+#endif /* _POSIX_NO_TRUNC */
+#endif /* USE_VMS_FILENAMES */
+ buf = xmalloc (size);
+
+ /* Some systems, like OS/2 and Windows NT, end lines with CRLF
+ instead of just LF. Format translation is done in the C
+ library I/O funtions. Here we tell them whether or not to
+ convert -- if this file is marked "binary" with the RCS -kb
+ flag, then we don't want to convert, else we do (because
+ CVS assumes text files by default). */
+
+ if (options)
+ bin = !(strcmp (options, "-kb"));
+ else
+ bin = 0;
+
+ fd = open (temp_filename,
+ O_WRONLY | O_CREAT | O_TRUNC | (bin ? OPEN_BINARY : 0),
+ 0777);
+
+ if (fd < 0)
+ error (1, errno, "writing %s", short_pathname);
+
+ if (use_gzip)
+ fd = filter_through_gunzip (fd, 0, &gzip_pid);
+
+ if (size > 0)
+ {
+ read_from_server (buf, size);
+
+ if (write (fd, buf, size) != size)
+ error (1, errno, "writing %s", short_pathname);
+ }
+
+ if (close (fd) < 0)
+ error (1, errno, "writing %s", short_pathname);
+ if (gzip_pid > 0)
+ {
+ if (waitpid (gzip_pid, &gzip_status, 0) == -1)
+ error (1, errno, "waiting for gzip process %ld",
+ (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, 0, "gzip process exited %d", gzip_status);
+ }
+
+ gzip_pid = -1;
+
+ /* Since gunzip writes files without converting LF to CRLF
+ (a reasonable behavior), we now have a patch file in LF
+ format. Leave the file as is if we're just going to feed
+ it to patch; patch can handle it. However, if it's the
+ final source file, convert it. */
+
+ if (data->contents == UPDATE_ENTRIES_UPDATE)
+ {
+#ifdef LINES_CRLF_TERMINATED
+
+ /* `bin' is non-zero iff `options' contains "-kb", meaning
+ treat this file as binary. */
+
+ if (use_gzip && (! bin))
+ {
+ convert_file (temp_filename, O_RDONLY | OPEN_BINARY,
+ filename, O_WRONLY | O_CREAT | O_TRUNC);
+ if (unlink (temp_filename) < 0)
+ error (0, errno, "warning: couldn't delete %s",
+ temp_filename);
+ }
+ else
+ rename_file (temp_filename, filename);
+
+#else /* ! LINES_CRLF_TERMINATED */
+ rename_file (temp_filename, filename);
+#endif /* LINES_CRLF_TERMINATED */
+ }
+ else
+ {
+ int retcode;
+ char backup[PATH_MAX];
+ struct stat s;
+
+ (void) sprintf (backup, "%s~", filename);
+ (void) unlink_file (backup);
+ if (!isfile (filename))
+ error (1, 0, "patch original file %s does not exist",
+ short_pathname);
+ if (stat (temp_filename, &s) < 0)
+ error (1, 1, "can't stat patch file %s", temp_filename);
+ if (s.st_size == 0)
+ retcode = 0;
+ else
+ {
+ run_setup ("%s -f -s -b ~ %s %s", PATCH_PROGRAM,
+ filename, temp_filename);
+ retcode = run_exec (DEVNULL, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ }
+ /* FIXME: should we really be silently ignoring errors? */
+ (void) unlink_file (temp_filename);
+ if (retcode == 0)
+ {
+ /* FIXME: should we really be silently ignoring errors? */
+ (void) unlink_file (backup);
+ }
+ else
+ {
+ int old_errno = errno;
+ char *path_tmp;
+
+ if (isfile (backup))
+ rename_file (backup, filename);
+
+ /* Get rid of the patch reject file. */
+ path_tmp = xmalloc (strlen (filename) + 10);
+ strcpy (path_tmp, filename);
+ strcat (path_tmp, ".rej");
+ /* FIXME: should we really be silently ignoring errors? */
+ (void) unlink_file (path_tmp);
+ free (path_tmp);
+
+ /* Save this file to retrieve later. */
+ failed_patches =
+ (char **) xrealloc ((char *) failed_patches,
+ ((failed_patches_count + 1)
+ * sizeof (char *)));
+ failed_patches[failed_patches_count] =
+ xstrdup (short_pathname);
+ ++failed_patches_count;
+
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+ "could not patch %s%s", filename,
+ retcode == -1 ? "" : "; will refetch");
+
+ stored_checksum_valid = 0;
+
+ return;
+ }
+ }
+ free (temp_filename);
+
+ if (stored_checksum_valid)
+ {
+ FILE *e;
+ struct MD5Context context;
+ unsigned char buf[8192];
+ unsigned len;
+ unsigned char checksum[16];
+
+ /*
+ * Compute the MD5 checksum. This will normally only be
+ * used when receiving a patch, so we always compute it
+ * here on the final file, rather than on the received
+ * data.
+ *
+ * Note that if the file is a text file, we should read it
+ * here using text mode, so its lines will be terminated the same
+ * way they were transmitted.
+ */
+ e = fopen (filename, "r");
+ if (e == NULL)
+ error (1, errno, "could not open %s", short_pathname);
+
+ MD5Init (&context);
+ while ((len = fread (buf, 1, sizeof buf, e)) != 0)
+ MD5Update (&context, buf, len);
+ if (ferror (e))
+ error (1, errno, "could not read %s", short_pathname);
+ MD5Final (checksum, &context);
+
+ fclose (e);
+
+ stored_checksum_valid = 0;
+
+ if (memcmp (checksum, stored_checksum, 16) != 0)
+ {
+ if (data->contents != UPDATE_ENTRIES_PATCH)
+ error (1, 0, "checksum failure on %s",
+ short_pathname);
+
+ error (0, 0,
+ "checksum failure after patch to %s; will refetch",
+ short_pathname);
+
+ /* Save this file to retrieve later. */
+ failed_patches =
+ (char **) xrealloc ((char *) failed_patches,
+ ((failed_patches_count + 1)
+ * sizeof (char *)));
+ failed_patches[failed_patches_count] =
+ xstrdup (short_pathname);
+ ++failed_patches_count;
+
+ return;
+ }
+ }
+
+ {
+ /* FIXME: we should be respecting the umask. */
+ int status = change_mode (filename, mode_string);
+ if (status != 0)
+ error (0, status, "cannot change mode of %s", short_pathname);
+ }
+
+ free (mode_string);
+ free (buf);
+ }
+
+ if (stored_mode_valid)
+ change_mode (filename, stored_mode);
+ stored_mode_valid = 0;
+
+ /*
+ * Process the entries line. Do this after we've written the file,
+ * since we need the timestamp.
+ */
+ if (strcmp (command_name, "export") != 0)
+ {
+ char *local_timestamp;
+ char *file_timestamp;
+
+ local_timestamp = data->timestamp;
+ if (local_timestamp == NULL || ts[0] == '+')
+ file_timestamp = time_stamp (filename);
+ else
+ file_timestamp = NULL;
+
+ /*
+ * These special version numbers signify that it is not up to
+ * date. Create a dummy timestamp which will never compare
+ * equal to the timestamp of the file.
+ */
+ if (vn[0] == '\0' || vn[0] == '0' || vn[0] == '-')
+ local_timestamp = "dummy timestamp";
+ else if (local_timestamp == NULL)
+ {
+ local_timestamp = file_timestamp;
+ mark_up_to_date (filename);
+ }
+
+ Register (ent_list, filename, vn, local_timestamp,
+ options, tag, date, ts[0] == '+' ? file_timestamp : NULL);
+
+ if (file_timestamp)
+ free (file_timestamp);
+
+ free (scratch_entries);
+ }
+ free (entries_line);
+}
+
+static void
+handle_checked_in (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_CHECKIN;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_new_entry (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_CHECKIN;
+ dat.timestamp = "dummy timestamp from new-entry";
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_updated (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_merged (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ dat.timestamp = "Result of merge";
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_patched (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_PATCH;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+remove_entry (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ Scratch_Entry (ent_list, filename);
+}
+
+static void
+handle_remove_entry (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, remove_entry, (char *)NULL);
+}
+
+static void
+remove_entry_and_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ Scratch_Entry (ent_list, filename);
+ if (unlink_file (filename) < 0)
+ error (0, errno, "unable to remove %s", short_pathname);
+}
+
+static void
+handle_removed (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, remove_entry_and_file, (char *)NULL);
+}
+
+/* Is this the top level (directory containing CVSROOT)? */
+static int
+is_cvsroot_level (pathname)
+ char *pathname;
+{
+ char *short_pathname;
+
+ if (strcmp (toplevel_repos, server_cvsroot) != 0)
+ return 0;
+
+ if (!use_directory)
+ {
+ if (strncmp (pathname, server_cvsroot, strlen (server_cvsroot)) != 0)
+ error (1, 0,
+ "server bug: pathname `%s' doesn't specify file in `%s'",
+ pathname, server_cvsroot);
+ short_pathname = pathname + strlen (server_cvsroot) + 1;
+ if (short_pathname[-1] != '/')
+ error (1, 0,
+ "server bug: pathname `%s' doesn't specify file in `%s'",
+ pathname, server_cvsroot);
+ return strchr (short_pathname, '/') == NULL;
+ }
+ else
+ {
+ return strchr (pathname, '/') == NULL;
+ }
+}
+
+static void
+set_static (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ FILE *fp;
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+}
+
+static void
+handle_set_static_directory (args, len)
+ char *args;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ return;
+ }
+ call_in_directory (args, set_static, (char *)NULL);
+}
+
+static void
+clear_static (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
+}
+
+static void
+handle_clear_static_directory (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ return;
+ }
+
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+ return;
+ }
+ call_in_directory (pathname, clear_static, (char *)NULL);
+}
+
+static void
+set_sticky (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *tagspec;
+ FILE *f;
+
+ read_line (&tagspec, 0);
+ f = open_file (CVSADM_TAG, "w+");
+ if (fprintf (f, "%s\n", tagspec) < 0)
+ error (1, errno, "writing %s", CVSADM_TAG);
+ if (fclose (f) == EOF)
+ error (1, errno, "closing %s", CVSADM_TAG);
+ free (tagspec);
+}
+
+static void
+handle_set_sticky (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ /* Swallow the tag line. */
+ (void) read_line (NULL, 0);
+ return;
+ }
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ /* Swallow the tag line. */
+ (void) read_line (NULL, 0);
+ return;
+ }
+
+ call_in_directory (pathname, set_sticky, (char *)NULL);
+}
+
+static void
+clear_sticky (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ if (unlink_file (CVSADM_TAG) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove %s", CVSADM_TAG);
+}
+
+static void
+handle_clear_sticky (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ return;
+ }
+
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+ return;
+ }
+
+ call_in_directory (pathname, clear_sticky, (char *)NULL);
+}
+
+
+static void template PROTO ((char *, List *, char *, char *));
+
+static void
+template (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ /* FIXME: should be computing second argument from CVSADM_TEMPLATE
+ and short_pathname. */
+ read_counted_file (CVSADM_TEMPLATE, "<CVS/Template file>");
+}
+
+static void handle_template PROTO ((char *, int));
+
+static void
+handle_template (pathname, len)
+ char *pathname;
+ int len;
+{
+ call_in_directory (pathname, template, NULL);
+}
+
+
+struct save_prog {
+ char *name;
+ char *dir;
+ struct save_prog *next;
+};
+
+static struct save_prog *checkin_progs;
+static struct save_prog *update_progs;
+
+/*
+ * Unlike some responses this doesn't include the repository. So we can't
+ * just call call_in_directory and have the right thing happen; we save up
+ * the requests and do them at the end.
+ */
+static void
+handle_set_checkin_prog (args, len)
+ char *args;
+ int len;
+{
+ char *prog;
+ struct save_prog *p;
+ read_line (&prog, 0);
+ p = (struct save_prog *) xmalloc (sizeof (struct save_prog));
+ p->next = checkin_progs;
+ p->dir = xstrdup (args);
+ p->name = prog;
+ checkin_progs = p;
+}
+
+static void
+handle_set_update_prog (args, len)
+ char *args;
+ int len;
+{
+ char *prog;
+ struct save_prog *p;
+ read_line (&prog, 0);
+ p = (struct save_prog *) xmalloc (sizeof (struct save_prog));
+ p->next = update_progs;
+ p->dir = xstrdup (args);
+ p->name = prog;
+ update_progs = p;
+}
+
+static void do_deferred_progs PROTO((void));
+
+static void
+do_deferred_progs ()
+{
+ struct save_prog *p;
+ struct save_prog *q;
+
+ char fname[PATH_MAX];
+ FILE *f;
+ if (toplevel_wd[0] != '\0')
+ {
+ if (chdir (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+ for (p = checkin_progs; p != NULL; )
+ {
+ sprintf (fname, "%s/%s", p->dir, CVSADM_CIPROG);
+ f = open_file (fname, "w");
+ if (fprintf (f, "%s\n", p->name) < 0)
+ error (1, errno, "writing %s", fname);
+ if (fclose (f) == EOF)
+ error (1, errno, "closing %s", fname);
+ free (p->name);
+ free (p->dir);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+ checkin_progs = NULL;
+ for (p = update_progs; p != NULL; p = p->next)
+ {
+ sprintf (fname, "%s/%s", p->dir, CVSADM_UPROG);
+ f = open_file (fname, "w");
+ if (fprintf (f, "%s\n", p->name) < 0)
+ error (1, errno, "writing %s", fname);
+ if (fclose (f) == EOF)
+ error (1, errno, "closing %s", fname);
+ free (p->name);
+ free (p->dir);
+ free (p);
+ }
+ update_progs = NULL;
+}
+
+static int client_isemptydir PROTO((char *));
+
+/*
+ * Returns 1 if the argument directory exists and is completely empty,
+ * other than the existence of the CVS directory entry. Zero otherwise.
+ */
+static int
+client_isemptydir (dir)
+ char *dir;
+{
+ DIR *dirp;
+ struct dirent *dp;
+
+ if ((dirp = opendir (dir)) == NULL)
+ {
+ if (! existence_error (errno))
+ 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 &&
+ strcmp (dp->d_name, CVSADM) != 0)
+ {
+ (void) closedir (dirp);
+ return (0);
+ }
+ }
+ if (errno != 0)
+ {
+ error (0, errno, "cannot read directory %s", dir);
+ (void) closedir (dirp);
+ return (0);
+ }
+ (void) closedir (dirp);
+ return (1);
+}
+
+struct save_dir {
+ char *dir;
+ struct save_dir *next;
+};
+
+struct save_dir *prune_candidates;
+
+static void
+add_prune_candidate (dir)
+ char *dir;
+{
+ struct save_dir *p;
+
+ if (dir[0] == '.' && dir[1] == '\0')
+ return;
+ p = (struct save_dir *) xmalloc (sizeof (struct save_dir));
+ p->dir = xstrdup (dir);
+ p->next = prune_candidates;
+ prune_candidates = p;
+}
+
+static void process_prune_candidates PROTO((void));
+
+static void
+process_prune_candidates ()
+{
+ struct save_dir *p;
+ struct save_dir *q;
+
+ if (toplevel_wd[0] != '\0')
+ {
+ if (chdir (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+ for (p = prune_candidates; p != NULL; )
+ {
+ if (client_isemptydir (p->dir))
+ {
+ unlink_file_dir (p->dir);
+ }
+ free (p->dir);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+}
+
+/* Send a Repository line. */
+
+static char *last_repos;
+static char *last_update_dir;
+
+static void send_repository PROTO((char *, char *, char *));
+
+static void
+send_repository (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ char *adm_name;
+
+ /* FIXME: this is probably not the best place to check; I wish I
+ * knew where in here's callers to really trap this bug. To
+ * reproduce the bug, just do this:
+ *
+ * mkdir junk
+ * cd junk
+ * cvs -d some_repos update foo
+ *
+ * Poof, CVS seg faults and dies! It's because it's trying to
+ * send a NULL string to the server but dies in send_to_server.
+ * That string was supposed to be the repository, but it doesn't
+ * get set because there's no CVSADM dir, and somehow it's not
+ * getting set from the -d argument either... ?
+ */
+ if (repos == NULL)
+ {
+ /* Lame error. I want a real fix but can't stay up to track
+ this down right now. */
+ error (1, 0, "no repository");
+ }
+
+ if (update_dir == NULL || update_dir[0] == '\0')
+ update_dir = ".";
+
+ if (last_repos != NULL
+ && strcmp (repos, last_repos) == 0
+ && last_update_dir != NULL
+ && strcmp (update_dir, last_update_dir) == 0)
+ /* We've already sent it. */
+ return;
+
+ if (client_prune_dirs)
+ add_prune_candidate (update_dir);
+
+ /* 80 is large enough for any of CVSADM_*. */
+ adm_name = xmalloc (strlen (dir) + 80);
+
+ if (use_directory == -1)
+ use_directory = supported_request ("Directory");
+
+ if (use_directory)
+ {
+ send_to_server ("Directory ", 0);
+ send_to_server (update_dir, 0);
+ send_to_server ("\012", 1);
+ send_to_server (repos, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ {
+ send_to_server ("Repository ", 0);
+ send_to_server (repos, 0);
+ send_to_server ("\012", 1);
+ }
+ if (supported_request ("Static-directory"))
+ {
+ adm_name[0] = '\0';
+ if (dir[0] != '\0')
+ {
+ strcat (adm_name, dir);
+ strcat (adm_name, "/");
+ }
+ strcat (adm_name, CVSADM_ENTSTAT);
+ if (isreadable (adm_name))
+ {
+ send_to_server ("Static-directory\012", 0);
+ }
+ }
+ if (supported_request ("Sticky"))
+ {
+ FILE *f;
+ if (dir[0] == '\0')
+ strcpy (adm_name, CVSADM_TAG);
+ else
+ sprintf (adm_name, "%s/%s", dir, CVSADM_TAG);
+
+ f = fopen (adm_name, "r");
+ if (f == NULL)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "reading %s", adm_name);
+ }
+ else
+ {
+ char line[80];
+ char *nl;
+ send_to_server ("Sticky ", 0);
+ while (fgets (line, sizeof (line), f) != NULL)
+ {
+ send_to_server (line, 0);
+ nl = strchr (line, '\n');
+ if (nl != NULL)
+ break;
+ }
+ if (nl == NULL)
+ send_to_server ("\012", 1);
+ if (fclose (f) == EOF)
+ error (0, errno, "closing %s", adm_name);
+ }
+ }
+ if (supported_request ("Checkin-prog"))
+ {
+ FILE *f;
+ if (dir[0] == '\0')
+ strcpy (adm_name, CVSADM_CIPROG);
+ else
+ sprintf (adm_name, "%s/%s", dir, CVSADM_CIPROG);
+
+ f = fopen (adm_name, "r");
+ if (f == NULL)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "reading %s", adm_name);
+ }
+ else
+ {
+ char line[80];
+ char *nl;
+
+ send_to_server ("Checkin-prog ", 0);
+
+ while (fgets (line, sizeof (line), f) != NULL)
+ {
+ send_to_server (line, 0);
+
+ nl = strchr (line, '\n');
+ if (nl != NULL)
+ break;
+ }
+ if (nl == NULL)
+ send_to_server ("\012", 1);
+ if (fclose (f) == EOF)
+ error (0, errno, "closing %s", adm_name);
+ }
+ }
+ if (supported_request ("Update-prog"))
+ {
+ FILE *f;
+ if (dir[0] == '\0')
+ strcpy (adm_name, CVSADM_UPROG);
+ else
+ sprintf (adm_name, "%s/%s", dir, CVSADM_UPROG);
+
+ f = fopen (adm_name, "r");
+ if (f == NULL)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "reading %s", adm_name);
+ }
+ else
+ {
+ char line[80];
+ char *nl;
+
+ send_to_server ("Update-prog ", 0);
+
+ while (fgets (line, sizeof (line), f) != NULL)
+ {
+ send_to_server (line, 0);
+
+ nl = strchr (line, '\n');
+ if (nl != NULL)
+ break;
+ }
+ if (nl == NULL)
+ send_to_server ("\012", 1);
+ if (fclose (f) == EOF)
+ error (0, errno, "closing %s", adm_name);
+ }
+ }
+ free (adm_name);
+ if (last_repos != NULL)
+ free (last_repos);
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_repos = xstrdup (repos);
+ last_update_dir = xstrdup (update_dir);
+}
+
+/* Send a Repository line and set toplevel_repos. */
+static void send_a_repository PROTO((char *, char *, char *));
+
+static void
+send_a_repository (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ if (toplevel_repos == NULL && repository != NULL)
+ {
+ if (update_dir[0] == '\0'
+ || (update_dir[0] == '.' && update_dir[1] == '\0'))
+ toplevel_repos = xstrdup (repository);
+ else
+ {
+ /*
+ * Get the repository from a CVS/Repository file if update_dir
+ * is absolute. This is not correct in general, because
+ * the CVS/Repository file might not be the top-level one.
+ * This is for cases like "cvs update /foo/bar" (I'm not
+ * sure it matters what toplevel_repos we get, but it does
+ * matter that we don't hit the "internal error" code below).
+ */
+ if (update_dir[0] == '/')
+ toplevel_repos = Name_Repository (update_dir, update_dir);
+ else
+ {
+ /*
+ * Guess the repository of that directory by looking at a
+ * subdirectory and removing as many pathname components
+ * as are in update_dir. I think that will always (or at
+ * least almost always) be 1.
+ *
+ * So this deals with directories which have been
+ * renamed, though it doesn't necessarily deal with
+ * directories which have been put inside other
+ * directories (and cvs invoked on the containing
+ * directory). I'm not sure the latter case needs to
+ * work.
+ */
+ /*
+ * This gets toplevel_repos wrong for "cvs update ../foo"
+ * but I'm not sure toplevel_repos matters in that case.
+ */
+ int slashes_in_update_dir;
+ int slashes_skipped;
+ char *p;
+
+ slashes_in_update_dir = 0;
+ for (p = update_dir; *p != '\0'; ++p)
+ if (*p == '/')
+ ++slashes_in_update_dir;
+
+ slashes_skipped = 0;
+ p = repository + strlen (repository);
+ while (1)
+ {
+ if (p == repository)
+ error (1, 0,
+ "internal error: not enough slashes in %s",
+ repository);
+ if (*p == '/')
+ ++slashes_skipped;
+ if (slashes_skipped < slashes_in_update_dir + 1)
+ --p;
+ else
+ break;
+ }
+ toplevel_repos = xmalloc (p - repository + 1);
+ /* Note that we don't copy the trailing '/'. */
+ strncpy (toplevel_repos, repository, p - repository);
+ toplevel_repos[p - repository] = '\0';
+ }
+ }
+ }
+
+ send_repository (dir, repository, update_dir);
+}
+
+/* The "expanded" modules. */
+static int modules_count;
+static int modules_allocated;
+static char **modules_vector;
+
+static void
+handle_module_expansion (args, len)
+ char *args;
+ int len;
+{
+ if (modules_vector == NULL)
+ {
+ modules_allocated = 1; /* Small for testing */
+ modules_vector = (char **) xmalloc
+ (modules_allocated * sizeof (modules_vector[0]));
+ }
+ else if (modules_count >= modules_allocated)
+ {
+ modules_allocated *= 2;
+ modules_vector = (char **) xrealloc
+ ((char *) modules_vector,
+ modules_allocated * sizeof (modules_vector[0]));
+ }
+ modules_vector[modules_count] = xmalloc (strlen (args) + 1);
+ strcpy (modules_vector[modules_count], args);
+ ++modules_count;
+}
+
+/* Original, not "expanded" modules. */
+static int module_argc;
+static char **module_argv;
+
+void
+client_expand_modules (argc, argv, local)
+ int argc;
+ char **argv;
+ int local;
+{
+ int errs;
+ int i;
+
+ module_argc = argc;
+ module_argv = (char **) xmalloc ((argc + 1) * sizeof (module_argv[0]));
+ for (i = 0; i < argc; ++i)
+ module_argv[i] = xstrdup (argv[i]);
+ module_argv[argc] = NULL;
+
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ send_a_repository ("", server_cvsroot, "");
+
+ send_to_server ("expand-modules\012", 0);
+
+ errs = get_server_responses ();
+ if (last_repos != NULL)
+ free (last_repos);
+ last_repos = NULL;
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_update_dir = NULL;
+ if (errs)
+ error (errs, 0, "cannot expand modules");
+}
+
+void
+client_send_expansions (local)
+ int local;
+{
+ int i;
+ char *argv[1];
+
+ /* Send the original module names. The "expanded" module name might
+ not be suitable as an argument to a co request (e.g. it might be
+ the result of a -d argument in the modules file). It might be
+ cleaner if we genuinely expanded module names, all the way to a
+ local directory and repository, but that isn't the way it works
+ now. */
+ send_file_names (module_argc, module_argv, 0);
+
+ for (i = 0; i < modules_count; ++i)
+ {
+ argv[0] = modules_vector[i];
+ if (isfile (argv[0]))
+ send_files (1, argv, local, 0);
+ }
+ send_a_repository ("", server_cvsroot, "");
+}
+
+void
+client_nonexpanded_setup ()
+{
+ send_a_repository ("", server_cvsroot, "");
+}
+
+static void
+handle_m (args, len)
+ char *args;
+ int len;
+{
+ fwrite (args, len, sizeof (*args), stdout);
+ putc ('\n', stdout);
+}
+
+static void
+handle_e (args, len)
+ char *args;
+ int len;
+{
+ fwrite (args, len, sizeof (*args), stderr);
+ putc ('\n', stderr);
+}
+
+#endif /* CLIENT_SUPPORT */
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/* This table must be writeable if the server code is included. */
+struct response responses[] =
+{
+#ifdef CLIENT_SUPPORT
+#define RSP_LINE(n, f, t, s) {n, f, t, s}
+#else /* ! CLIENT_SUPPORT */
+#define RSP_LINE(n, f, t, s) {n, s}
+#endif /* CLIENT_SUPPORT */
+
+ RSP_LINE("ok", handle_ok, response_type_ok, rs_essential),
+ RSP_LINE("error", handle_error, response_type_error, rs_essential),
+ RSP_LINE("Valid-requests", handle_valid_requests, response_type_normal,
+ rs_essential),
+ RSP_LINE("Checked-in", handle_checked_in, response_type_normal,
+ rs_essential),
+ RSP_LINE("New-entry", handle_new_entry, response_type_normal, rs_optional),
+ RSP_LINE("Checksum", handle_checksum, response_type_normal, rs_optional),
+ RSP_LINE("Copy-file", handle_copy_file, response_type_normal, rs_optional),
+ RSP_LINE("Updated", handle_updated, response_type_normal, rs_essential),
+ RSP_LINE("Merged", handle_merged, response_type_normal, rs_essential),
+ RSP_LINE("Patched", handle_patched, response_type_normal, rs_optional),
+ RSP_LINE("Mode", handle_mode, response_type_normal, rs_optional),
+ RSP_LINE("Removed", handle_removed, response_type_normal, rs_essential),
+ RSP_LINE("Remove-entry", handle_remove_entry, response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-static-directory", handle_set_static_directory,
+ response_type_normal,
+ rs_optional),
+ RSP_LINE("Clear-static-directory", handle_clear_static_directory,
+ response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-sticky", handle_set_sticky, response_type_normal,
+ rs_optional),
+ RSP_LINE("Clear-sticky", handle_clear_sticky, response_type_normal,
+ rs_optional),
+ RSP_LINE("Template", handle_template, response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-checkin-prog", handle_set_checkin_prog, response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-update-prog", handle_set_update_prog, response_type_normal,
+ rs_optional),
+ RSP_LINE("Notified", handle_notified, response_type_normal, rs_optional),
+ RSP_LINE("Module-expansion", handle_module_expansion, response_type_normal,
+ rs_optional),
+ RSP_LINE("M", handle_m, response_type_normal, rs_essential),
+ RSP_LINE("E", handle_e, response_type_normal, rs_essential),
+ /* Possibly should be response_type_error. */
+ RSP_LINE(NULL, NULL, response_type_normal, rs_essential)
+
+#undef RSP_LINE
+};
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+#ifdef CLIENT_SUPPORT
+
+/*
+ * If LEN is 0, then send_to_server() computes string's length itself.
+ *
+ * Therefore, pass the real length when transmitting data that might
+ * contain 0's.
+ */
+void
+send_to_server (str, len)
+ char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ int just_wrtn = 0;
+ size_t wrtn = 0;
+
+#ifdef VMS
+ /* send() blocks under VMS */
+ if (send (server_sock, str + wrtn, len - wrtn, 0) < 0)
+ error (1, errno, "writing to server socket");
+#else /* VMS */
+ while (wrtn < len)
+ {
+ just_wrtn = send (server_sock, str + wrtn, len - wrtn, 0);
+
+ if (just_wrtn == -1)
+ error (1, errno, "writing to server socket");
+
+ wrtn += just_wrtn;
+ if (wrtn == len)
+ break;
+ }
+#endif /* VMS */
+ }
+ else
+#endif /* NO_SOCKET_TO_FD */
+ {
+ size_t wrtn = 0;
+
+ while (wrtn < len)
+ {
+ wrtn += fwrite (str + wrtn, 1, len - wrtn, to_server);
+
+ if (wrtn == len)
+ break;
+
+ if (ferror (to_server))
+ error (1, errno, "writing to server");
+ if (feof (to_server))
+ error (1, 0, "premature end-of-file on server");
+ }
+ }
+
+ if (to_server_logfile)
+ if (fwrite (str, 1, len, to_server_logfile) < len)
+ error (0, errno, "writing to to-server logfile");
+}
+
+/* Read up to LEN bytes from the server. Returns actual number of bytes
+ read. Gives a fatal error on EOF or error. */
+static size_t
+try_read_from_server (buf, len)
+ char *buf;
+ size_t len;
+{
+ int nread;
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ nread = recv (server_sock, buf, len, 0);
+ if (nread == -1)
+ error (1, errno, "reading from server");
+ }
+ else
+#endif
+ {
+ nread = fread (buf, 1, len, from_server);
+ if (ferror (from_server))
+ error (1, errno, "reading from server");
+ if (feof (from_server))
+ error (1, 0,
+ "end of file from server (consult above messages if any)");
+ }
+
+ /* Log, if that's what we're doing. */
+ if (from_server_logfile != NULL && nread > 0)
+ {
+ size_t towrite = nread;
+ if (fwrite (buf, 1, towrite, from_server_logfile) < towrite)
+ error (0, errno, "writing to from-server logfile");
+ }
+
+ return nread;
+}
+
+/*
+ * Read LEN bytes from the server or die trying.
+ */
+void
+read_from_server (buf, len)
+ char *buf;
+ size_t len;
+{
+ size_t red = 0;
+ while (red < len)
+ {
+ red += try_read_from_server (buf + red, len - red);
+ if (red == len)
+ break;
+ }
+}
+
+/*
+ * Get some server responses and process them. Returns nonzero for
+ * error, 0 for success. */
+int
+get_server_responses ()
+{
+ struct response *rs;
+ do
+ {
+ char *cmd;
+ int len;
+
+ len = read_line (&cmd, 0);
+ for (rs = responses; rs->name != NULL; ++rs)
+ if (strncmp (cmd, rs->name, strlen (rs->name)) == 0)
+ {
+ int cmdlen = strlen (rs->name);
+ if (cmd[cmdlen] == '\0')
+ ;
+ else if (cmd[cmdlen] == ' ')
+ ++cmdlen;
+ else
+ /*
+ * The first len characters match, but it's a different
+ * response. e.g. the response is "oklahoma" but we
+ * matched "ok".
+ */
+ continue;
+ (*rs->func) (cmd + cmdlen, len - cmdlen);
+ break;
+ }
+ if (rs->name == NULL)
+ /* It's OK to print just to the first '\0'. */
+ error (0, 0,
+ "warning: unrecognized response `%s' from cvs server",
+ cmd);
+ free (cmd);
+ } while (rs->type == response_type_normal);
+ return rs->type == response_type_error ? 1 : 0;
+}
+
+/* Get the responses and then close the connection. */
+int server_fd = -1;
+
+/*
+ * Flag var; we'll set it in start_server() and not one of its
+ * callees, such as start_rsh_server(). This means that there might
+ * be a small window between the starting of the server and the
+ * setting of this var, but all the code in that window shouldn't care
+ * because it's busy checking return values to see if the server got
+ * started successfully anyway.
+ */
+int server_started = 0;
+
+int
+get_responses_and_close ()
+{
+ int errs = get_server_responses ();
+
+ do_deferred_progs ();
+
+ if (client_prune_dirs)
+ process_prune_candidates ();
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ if (shutdown (server_sock, 2) < 0)
+ error (1, errno, "shutting down server socket");
+ }
+ else
+#endif /* NO_SOCKET_TO_FD */
+ {
+#if defined(HAVE_KERBEROS) || defined(USE_DIRECT_TCP) || defined(AUTH_CLIENT_SUPPORT)
+ if (server_fd != -1)
+ {
+ if (shutdown (server_fd, 1) < 0)
+ error (1, errno, "shutting down connection to %s", server_host);
+ /*
+ * This test will always be true because we dup the descriptor
+ */
+ if (fileno (from_server) != fileno (to_server))
+ {
+ if (fclose (to_server) != 0)
+ error (1, errno,
+ "closing down connection to %s",
+ server_host);
+ }
+ }
+ else
+#endif /* HAVE_KERBEROS || USE_DIRECT_TCP || AUTH_CLIENT_SUPPORT */
+
+#ifdef SHUTDOWN_SERVER
+ SHUTDOWN_SERVER (fileno (to_server));
+#else /* ! SHUTDOWN_SERVER */
+ {
+
+#ifdef START_RSH_WITH_POPEN_RW
+ if (pclose (to_server) == EOF)
+#else /* ! START_RSH_WITH_POPEN_RW */
+ if (fclose (to_server) == EOF)
+#endif /* START_RSH_WITH_POPEN_RW */
+ {
+ error (1, errno, "closing connection to %s", server_host);
+ }
+ }
+
+ if (getc (from_server) != EOF)
+ error (0, 0, "dying gasps from %s unexpected", server_host);
+ else if (ferror (from_server))
+ error (0, errno, "reading from %s", server_host);
+
+ fclose (from_server);
+#endif /* SHUTDOWN_SERVER */
+ }
+
+#if ! RSH_NOT_TRANSPARENT
+ if (rsh_pid != -1
+ && waitpid (rsh_pid, (int *) 0, 0) == -1)
+ error (1, errno, "waiting for process %d", rsh_pid);
+#endif /* ! RSH_NOT_TRANSPARENT */
+
+ server_started = 0;
+
+ return errs;
+}
+
+#ifndef RSH_NOT_TRANSPARENT
+static void start_rsh_server PROTO((int *, int *));
+#endif /* RSH_NOT_TRANSPARENT */
+
+int
+supported_request (name)
+ char *name;
+{
+ struct request *rq;
+
+ for (rq = requests; rq->name; rq++)
+ if (!strcmp (rq->name, name))
+ return rq->status == rq_supported;
+ error (1, 0, "internal error: testing support for unknown option?");
+ /* NOTREACHED */
+ return 0;
+}
+
+
+#ifdef AUTH_CLIENT_SUPPORT
+void
+init_sockaddr (name, hostname, port)
+ struct sockaddr_in *name;
+ const char *hostname;
+ unsigned short int port;
+{
+ struct hostent *hostinfo;
+
+ memset (name, 0, sizeof (*name));
+ name->sin_family = AF_INET;
+ name->sin_port = htons (port);
+ hostinfo = gethostbyname (hostname);
+ if (hostinfo == NULL)
+ {
+ fprintf (stderr, "Unknown host %s.\n", hostname);
+ exit (EXIT_FAILURE);
+ }
+ name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
+}
+
+
+int
+auth_server_port_number ()
+{
+ return CVS_AUTH_PORT;
+}
+
+
+/*
+ * Connect to the authenticating server.
+ *
+ * If VERIFY_ONLY is non-zero, then just verify that the password is
+ * correct and then shutdown the connection. In this case, the return
+ * values is 1 if the password was correct, 0 if not.
+ *
+ * If VERIFY_ONLY is 0, then really connect to the server. In this
+ * case the return value is 1 on succees, but is probably ignored. If
+ * fail to connect, then die with error.
+ */
+int
+connect_to_pserver (tofdp, fromfdp, verify_only)
+ int *tofdp, *fromfdp;
+ int verify_only;
+{
+ int sock;
+#ifndef NO_SOCKET_TO_FD
+ int tofd, fromfd;
+#endif
+ int port_number;
+ struct sockaddr_in client_sai;
+
+ /* Does nothing if already called before now. */
+ parse_cvsroot ();
+
+ sock = socket (AF_INET, SOCK_STREAM, 0);
+ if (sock == -1)
+ {
+ fprintf (stderr, "socket() failed\n");
+ exit (EXIT_FAILURE);
+ }
+ port_number = auth_server_port_number ();
+ init_sockaddr (&client_sai, server_host, port_number);
+ if (connect (sock, (struct sockaddr *) &client_sai, sizeof (client_sai))
+ < 0)
+ error (1, errno, "connect to %s:%d failed", server_host,
+ CVS_AUTH_PORT);
+
+ /* Run the authorization mini-protocol before anything else. */
+ {
+ int i;
+ char ch, read_buf[PATH_MAX];
+ char *begin = NULL;
+ char *repository = server_cvsroot;
+ char *username = server_user;
+ char *password = NULL;
+ char *end = NULL;
+
+ if (verify_only)
+ {
+ begin = "BEGIN VERIFICATION REQUEST\n";
+ end = "END VERIFICATION REQUEST\n";
+ }
+ else
+ {
+ begin = "BEGIN AUTH REQUEST\n";
+ end = "END AUTH REQUEST\n";
+ }
+
+ /* Get the password, probably from ~/.cvspass. */
+ password = get_cvs_password (server_user, server_host, server_cvsroot);
+
+ /* Announce that we're starting the authorization protocol. */
+ send (sock, begin, strlen (begin), 0);
+
+ /* Send the data the server needs. */
+ send (sock, repository, strlen (repository), 0);
+ send (sock, "\n", 1, 0);
+ send (sock, username, strlen (username), 0);
+ send (sock, "\n", 1, 0);
+ send (sock, password, strlen (password), 0);
+ send (sock, "\n", 1, 0);
+
+ /* Announce that we're ending the authorization protocol. */
+ send (sock, end, strlen (end), 0);
+
+ /* Paranoia. */
+ memset (password, 0, strlen (password));
+
+ /* Get ACK or NACK from the server.
+ *
+ * We could avoid this careful read-char loop by having the ACK
+ * and NACK cookies be of the same length, so we'd simply read
+ * that length and see what we got. But then there'd be Yet
+ * Another Protocol Requirement floating around, and someday
+ * someone would make a change that breaks it and spend a hellish
+ * day tracking it down. Therefore, we use "\n" to mark off the
+ * end of both ACK and NACK, and we loop, reading until "\n".
+ */
+ ch = 0;
+ memset (read_buf, 0, PATH_MAX);
+ for (i = 0; (i < (PATH_MAX - 1)) && (ch != '\n'); i++)
+ {
+ if (recv (sock, &ch, 1, 0) < 0)
+ error (1, errno, "recv() from server %s", server_host);
+
+ read_buf[i] = ch;
+ }
+
+ if (strcmp (read_buf, "I HATE YOU\n") == 0)
+ {
+ /* Authorization not granted. */
+ if (shutdown (sock, 2) < 0)
+ {
+ error (0, 0,
+ "authorization failed: server %s rejected access",
+ server_host);
+ error (1, errno,
+ "shutdown() failed (server %s)", server_host);
+ }
+
+ if (verify_only)
+ return 0;
+ else
+ error (1, 0,
+ "authorization failed: server %s rejected access",
+ server_host);
+ }
+ else if (strcmp (read_buf, "I LOVE YOU\n") != 0)
+ {
+ /* Unrecognized response from server. */
+ if (shutdown (sock, 2) < 0)
+ {
+ error (0, 0,
+ "unrecognized auth response from %s: %s",
+ server_host, read_buf);
+ error (1, errno, "shutdown() failed, server %s", server_host);
+ }
+ error (1, 0,
+ "unrecognized auth response from %s: %s",
+ server_host, read_buf);
+ }
+ }
+
+ if (verify_only)
+ {
+ if (shutdown (sock, 2) < 0)
+ error (0, errno, "shutdown() failed, server %s", server_host);
+ return 1;
+ }
+ else
+ {
+#ifdef NO_SOCKET_TO_FD
+ use_socket_style = 1;
+ server_sock = sock;
+ /* Try to break mistaken callers: */
+ *tofdp = 0;
+ *fromfdp = 0;
+#else /* ! NO_SOCKET_TO_FD */
+ server_fd = sock;
+ close_on_exec (server_fd);
+ tofd = fromfd = sock;
+ /* Hand them back to the caller. */
+ *tofdp = tofd;
+ *fromfdp = fromfd;
+#endif /* NO_SOCKET_TO_FD */
+ }
+
+ return 1;
+}
+#endif /* AUTH_CLIENT_SUPPORT */
+
+
+#if HAVE_KERBEROS || USE_DIRECT_TCP
+
+/*
+ * FIXME: this function has not been changed to deal with
+ * NO_SOCKET_TO_FD (i.e., systems on which sockets cannot be converted
+ * to file descriptors. The first person to try building a kerberos
+ * client on such a system (OS/2, Windows 95, and maybe others) will
+ * have to make take care of this.
+ */
+void
+start_tcp_server (tofdp, fromfdp)
+ int *tofdp, *fromfdp;
+{
+ int tofd, fromfd;
+
+ struct hostent *hp;
+ char *hname;
+ const char *portenv;
+ int port;
+ struct sockaddr_in sin;
+ int s;
+
+
+#if HAVE_KERBEROS
+ KTEXT_ST ticket;
+ const char *realm;
+#endif /* HAVE_KERBEROS */
+
+ int status;
+
+ /*
+ * We look up the host to give a better error message if it
+ * does not exist. However, we then pass server_host to
+ * krb_sendauth, rather than the canonical name, because
+ * krb_sendauth is going to do its own canonicalization anyhow
+ * and that lets us not worry about the static storage used by
+ * gethostbyname.
+ */
+ hp = gethostbyname (server_host);
+ if (hp == NULL)
+ error (1, 0, "%s: unknown host", server_host);
+ hname = xmalloc (strlen (hp->h_name) + 1);
+ strcpy (hname, hp->h_name);
+
+#if HAVE_KERBEROS
+ realm = krb_realmofhost (hname);
+#endif /* HAVE_KERBEROS */
+
+ /* Get CVS_CLIENT_PORT or look up cvs/tcp with CVS_PORT as default */
+ portenv = getenv ("CVS_CLIENT_PORT");
+ if (portenv != NULL)
+ {
+ port = atoi (portenv);
+ if (port <= 0)
+ goto try_rsh_no_message;
+ if (trace)
+ fprintf(stderr, "Using TCP port %d to contact server.\n", port);
+ port = htons (port);
+ }
+ else
+ {
+ struct servent *sp;
+
+ sp = getservbyname ("cvs", "tcp");
+ if (sp == NULL)
+ port = htons (CVS_PORT);
+ else
+ port = sp->s_port;
+ }
+
+ s = socket (AF_INET, SOCK_STREAM, 0);
+ if (s < 0)
+ error (1, errno, "socket");
+
+ memset (&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ sin.sin_port = 0;
+
+ if (bind (s, (struct sockaddr *) &sin, sizeof sin) < 0)
+ error (1, errno, "bind");
+
+ memcpy (&sin.sin_addr, hp->h_addr, hp->h_length);
+ sin.sin_port = port;
+
+ tofd = -1;
+ if (connect (s, (struct sockaddr *) &sin, sizeof sin) < 0)
+ {
+ error (0, errno, "connect");
+ close (s);
+ }
+ else
+ {
+#ifdef HAVE_KERBEROS
+ struct sockaddr_in laddr;
+ int laddrlen;
+ MSG_DAT msg_data;
+ CREDENTIALS cred;
+ Key_schedule sched;
+
+ laddrlen = sizeof (laddr);
+ if (getsockname (s, (struct sockaddr *) &laddr, &laddrlen) < 0)
+ error (1, errno, "getsockname");
+
+ /* We don't care about the checksum, and pass it as zero. */
+ status = krb_sendauth (KOPT_DO_MUTUAL, s, &ticket, "rcmd",
+ hname, realm, (unsigned long) 0, &msg_data,
+ &cred, sched, &laddr, &sin, "KCVSV1.0");
+ if (status != KSUCCESS)
+ {
+ error (0, 0, "kerberos: %s", krb_get_err_text(status));
+ close (s);
+ }
+ else
+ {
+#endif /* HAVE_KERBEROS */
+
+ server_fd = s;
+ close_on_exec (server_fd);
+ tofd = fromfd = s;
+
+#ifdef HAVE_KERBEROS
+ }
+#endif /* HAVE_KERBEROS */
+ }
+
+ if (tofd == -1)
+ {
+ /* FIXME: Falling back like this is slow and we should probably
+ just make it a fatal error (so that people use the right
+ environment variables or, when we get around to implementing
+ the right ones, access methods). */
+ error (0, 0, "trying to start server using rsh");
+ try_rsh_no_message:
+ server_fd = -1;
+#if ! RSH_NOT_TRANSPARENT
+ start_rsh_server (&tofd, &fromfd);
+#else /* RSH_NOT_TRANSPARENT */
+#if defined (START_SERVER)
+ START_SERVER (&tofd, &fromfd, getcaller (),
+ server_user, server_host, server_cvsroot);
+#endif /* defined (START_SERVER) */
+#endif /* ! RSH_NOT_TRANSPARENT */
+ }
+ free (hname);
+
+ /* Give caller the values it wants. */
+ *tofdp = tofd;
+ *fromfdp = fromfd;
+}
+
+#endif /* HAVE_KERBEROS || USE_DIRECT_TCP */
+
+static int send_variable_proc PROTO ((Node *, void *));
+
+static int
+send_variable_proc (node, closure)
+ Node *node;
+ void *closure;
+{
+ send_to_server ("Set ", 0);
+ send_to_server (node->key, 0);
+ send_to_server ("=", 1);
+ send_to_server (node->data, 0);
+ send_to_server ("\012", 1);
+ return 0;
+}
+
+/* Contact the server. */
+void
+start_server ()
+{
+ int tofd, fromfd;
+ char *log = getenv ("CVS_CLIENT_LOG");
+
+ /* Note that generally speaking we do *not* fall back to a different
+ way of connecting if the first one does not work. This is slow
+ (*really* slow on a 14.4kbps link); the clean way to have a CVS
+ which supports several ways of connecting is with access methods. */
+
+ /* Init these to NULL. They will be set later if logging is on. */
+ from_server_logfile = (FILE *) NULL;
+ to_server_logfile = (FILE *) NULL;
+
+#ifdef AUTH_CLIENT_SUPPORT
+ if (use_authenticating_server)
+ {
+ /* Toss the return value. It will die with error if anything
+ goes wrong anyway. */
+ connect_to_pserver (&tofd, &fromfd, 0);
+ }
+ else
+#endif /* AUTH_CLIENT_SUPPORT */
+ {
+#if HAVE_KERBEROS || USE_DIRECT_TCP
+ start_tcp_server (&tofd, &fromfd);
+#else
+
+# if ! RSH_NOT_TRANSPARENT
+ start_rsh_server (&tofd, &fromfd);
+# else
+
+# if defined(START_SERVER)
+ START_SERVER (&tofd, &fromfd, getcaller (),
+ server_user, server_host, server_cvsroot);
+# endif
+# endif
+#endif
+ }
+
+#if defined(VMS) && defined(NO_SOCKET_TO_FD)
+ /* Avoid mixing sockets with stdio */
+ use_socket_style = 1;
+ server_sock = tofd;
+#endif /* VMS && NO_SOCKET_TO_FD */
+
+ /* "Hi, I'm Darlene and I'll be your server tonight..." */
+ server_started = 1;
+
+ /* Set up logfiles, if any. */
+ if (log)
+ {
+ int len = strlen (log);
+ char *buf = xmalloc (len + 5);
+ char *p;
+
+ strcpy (buf, log);
+ p = buf + len;
+
+ strcpy (p, ".in");
+ to_server_logfile = open_file (buf, "w");
+ if (to_server_logfile == NULL)
+ error (0, errno, "opening to-server logfile %s", buf);
+
+ strcpy (p, ".out");
+ from_server_logfile = open_file (buf, "w");
+ if (from_server_logfile == NULL)
+ error (0, errno, "opening from-server logfile %s", buf);
+
+ free (buf);
+ }
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ {
+ /* todo: some OS's don't need these calls... */
+ close_on_exec (tofd);
+ close_on_exec (fromfd);
+
+ /* SCO 3 and AIX have a nasty bug in the I/O libraries which precludes
+ fdopening the same file descriptor twice, so dup it if it is the
+ same. */
+ if (tofd == fromfd)
+ {
+ fromfd = dup (tofd);
+ if (fromfd < 0)
+ error (1, errno, "cannot dup net connection");
+ }
+
+ /* These will use binary mode on systems which have it. */
+ to_server = fdopen (tofd, FOPEN_BINARY_WRITE);
+ if (to_server == NULL)
+ error (1, errno, "cannot fdopen %d for write", tofd);
+ from_server = fdopen (fromfd, FOPEN_BINARY_READ);
+ if (from_server == NULL)
+ error (1, errno, "cannot fdopen %d for read", fromfd);
+ }
+
+ /* Clear static variables. */
+ if (toplevel_repos != NULL)
+ free (toplevel_repos);
+ toplevel_repos = NULL;
+ if (last_dir_name != NULL)
+ free (last_dir_name);
+ last_dir_name = NULL;
+ if (last_repos != NULL)
+ free (last_repos);
+ last_repos = NULL;
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_update_dir = NULL;
+ stored_checksum_valid = 0;
+ stored_mode_valid = 0;
+
+ if (strcmp (command_name, "init") != 0)
+ {
+ send_to_server ("Root ", 0);
+ send_to_server (server_cvsroot, 0);
+ send_to_server ("\012", 1);
+ }
+
+ {
+ struct response *rs;
+
+ send_to_server ("Valid-responses", 0);
+
+ for (rs = responses; rs->name != NULL; ++rs)
+ {
+ send_to_server (" ", 0);
+ send_to_server (rs->name, 0);
+ }
+ send_to_server ("\012", 1);
+ }
+ send_to_server ("valid-requests\012", 0);
+
+ if (get_server_responses ())
+ exit (EXIT_FAILURE);
+
+ /*
+ * Now handle global options.
+ *
+ * -H, -f, -d, -e should be handled OK locally.
+ *
+ * -b we ignore (treating it as a server installation issue).
+ * FIXME: should be an error message.
+ *
+ * -v we print local version info; FIXME: Add a protocol request to get
+ * the version from the server so we can print that too.
+ *
+ * -l -t -r -w -q -n and -Q need to go to the server.
+ */
+
+ {
+ int have_global = supported_request ("Global_option");
+
+ if (noexec)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -n\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -n option.");
+ }
+ if (quiet)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -q\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -q option.");
+ }
+ if (really_quiet)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -Q\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -Q option.");
+ }
+ if (!cvswrite)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -r\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -r option.");
+ }
+ if (trace)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -t\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -t option.");
+ }
+ if (logoff)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -l\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -l option.");
+ }
+ }
+ if (gzip_level)
+ {
+ if (supported_request ("gzip-file-contents"))
+ {
+ char gzip_level_buf[5];
+ send_to_server ("gzip-file-contents ", 0);
+ sprintf (gzip_level_buf, "%d", gzip_level);
+ send_to_server (gzip_level_buf, 0);
+
+ send_to_server ("\012", 1);
+ }
+ else
+ {
+ fprintf (stderr, "server doesn't support gzip-file-contents\n");
+ gzip_level = 0;
+ }
+ }
+
+#ifdef FILENAMES_CASE_INSENSITIVE
+ if (supported_request ("Case"))
+ send_to_server ("Case\012", 0);
+#endif
+
+ /* If "Set" is not supported, just silently fail to send the variables.
+ Users with an old server should get a useful error message when it
+ fails to recognize the ${=foo} syntax. This way if someone uses
+ several servers, some of which are new and some old, they can still
+ set user variables in their .cvsrc without trouble. */
+ if (supported_request ("Set"))
+ walklist (variable_list, send_variable_proc, NULL);
+}
+
+#ifndef RSH_NOT_TRANSPARENT
+/* Contact the server by starting it with rsh. */
+
+/* Right now, we have two different definitions for this function,
+ depending on whether we start the rsh server using popenRW or not.
+ This isn't ideal, and the best thing would probably be to change
+ the OS/2 port to be more like the regular Unix client (i.e., by
+ implementing piped_child)... but I'm doing something else at the
+ moment, and wish to make only one change at a time. -Karl */
+
+#ifdef START_RSH_WITH_POPEN_RW
+
+/* This is actually a crock -- it's OS/2-specific, for no one else
+ uses it. If I get time, I want to make piped_child and all the
+ other stuff in os2/run.c work right. In the meantime, this gets us
+ up and running, and that's most important. */
+
+static void
+start_rsh_server (tofdp, fromfdp)
+ int *tofdp, *fromfdp;
+{
+ int pipes[2];
+
+ /* If you're working through firewalls, you can set the
+ CVS_RSH environment variable to a script which uses rsh to
+ invoke another rsh on a proxy machine. */
+ char *cvs_rsh = getenv ("CVS_RSH");
+ char *cvs_server = getenv ("CVS_SERVER");
+ char command[PATH_MAX];
+ int i = 0;
+ /* This needs to fit "rsh", "-b", "-l", "USER", "host",
+ "cmd (w/ args)", and NULL. We leave some room to grow. */
+ char *rsh_argv[10];
+
+ if (!cvs_rsh)
+ cvs_rsh = "rsh";
+ if (!cvs_server)
+ cvs_server = "cvs";
+
+ /* If you are running a very old (Nov 3, 1994, before 1.5)
+ * version of the server, you need to make sure that your .bashrc
+ * on the server machine does not set CVSROOT to something
+ * containing a colon (or better yet, upgrade the server). */
+
+ /* The command line starts out with rsh. */
+ rsh_argv[i++] = cvs_rsh;
+
+#ifdef RSH_NEEDS_BINARY_FLAG
+ /* "-b" for binary, under OS/2. */
+ rsh_argv[i++] = "-b";
+#endif /* RSH_NEEDS_BINARY_FLAG */
+
+ /* Then we strcat more things on the end one by one. */
+ if (server_user != NULL)
+ {
+ rsh_argv[i++] = "-l";
+ rsh_argv[i++] = server_user;
+ }
+
+ rsh_argv[i++] = server_host;
+ rsh_argv[i++] = cvs_server;
+ rsh_argv[i++] = "server";
+
+ /* Mark the end of the arg list. */
+ rsh_argv[i] = (char *) NULL;
+
+ if (trace)
+ {
+ fprintf (stderr, " -> Starting server: ");
+ fprintf (stderr, "%s", command);
+ putc ('\n', stderr);
+ }
+
+ /* Do the deed. */
+ rsh_pid = popenRW (rsh_argv, pipes);
+ if (rsh_pid < 0)
+ error (1, errno, "cannot start server via rsh");
+
+ /* Give caller the file descriptors. */
+ *tofdp = pipes[0];
+ *fromfdp = pipes[1];
+}
+
+#else /* ! START_RSH_WITH_POPEN_RW */
+
+static void
+start_rsh_server (tofdp, fromfdp)
+ int *tofdp;
+ int *fromfdp;
+{
+ /* If you're working through firewalls, you can set the
+ CVS_RSH environment variable to a script which uses rsh to
+ invoke another rsh on a proxy machine. */
+ char *cvs_rsh = getenv ("CVS_RSH");
+ char *cvs_server = getenv ("CVS_SERVER");
+ char *command;
+
+ if (!cvs_rsh)
+ cvs_rsh = "rsh";
+ if (!cvs_server)
+ cvs_server = "cvs";
+
+ /* Pass the command to rsh as a single string. This shouldn't
+ affect most rsh servers at all, and will pacify some buggy
+ versions of rsh that grab switches out of the middle of the
+ command (they're calling the GNU getopt routines incorrectly). */
+ command = xmalloc (strlen (cvs_server)
+ + strlen (server_cvsroot)
+ + 50);
+
+ /* If you are running a very old (Nov 3, 1994, before 1.5)
+ * version of the server, you need to make sure that your .bashrc
+ * on the server machine does not set CVSROOT to something
+ * containing a colon (or better yet, upgrade the server). */
+ sprintf (command, "%s server", cvs_server);
+
+ {
+ char *argv[10];
+ char **p = argv;
+
+ *p++ = cvs_rsh;
+ *p++ = server_host;
+
+ /* If the login names differ between client and server
+ * pass it on to rsh.
+ */
+ if (server_user != NULL)
+ {
+ *p++ = "-l";
+ *p++ = server_user;
+ }
+
+ *p++ = command;
+ *p++ = NULL;
+
+ if (trace)
+ {
+ int i;
+
+ fprintf (stderr, " -> Starting server: ");
+ for (i = 0; argv[i]; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ putc ('\n', stderr);
+ }
+ rsh_pid = piped_child (argv, tofdp, fromfdp);
+
+ if (rsh_pid < 0)
+ error (1, errno, "cannot start server via rsh");
+ }
+}
+
+#endif /* START_RSH_WITH_POPEN_RW */
+#endif /* ! RSH_NOT_TRANSPARENT */
+
+
+
+/* Send an argument STRING. */
+void
+send_arg (string)
+ char *string;
+{
+ char buf[1];
+ char *p = string;
+
+ send_to_server ("Argument ", 0);
+
+ while (*p)
+ {
+ if (*p == '\n')
+ {
+ send_to_server ("\012Argumentx ", 0);
+ }
+ else
+ {
+ buf[0] = *p;
+ send_to_server (buf, 1);
+ }
+ ++p;
+ }
+ send_to_server ("\012", 1);
+}
+
+static void send_modified PROTO ((char *, char *, Vers_TS *));
+
+static void
+send_modified (file, short_pathname, vers)
+ char *file;
+ char *short_pathname;
+ Vers_TS *vers;
+{
+ /* File was modified, send it. */
+ struct stat sb;
+ int fd;
+ char *buf;
+ char *mode_string;
+ int bufsize;
+ int bin;
+
+ /* Don't think we can assume fstat exists. */
+ if (stat (file, &sb) < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ mode_string = mode_to_string (sb.st_mode);
+
+ /* Beware: on systems using CRLF line termination conventions,
+ the read and write functions will convert CRLF to LF, so the
+ number of characters read is not the same as sb.st_size. Text
+ files should always be transmitted using the LF convention, so
+ we don't want to disable this conversion. */
+ bufsize = sb.st_size;
+ buf = xmalloc (bufsize);
+
+ /* Is the file marked as containing binary data by the "-kb" flag?
+ If so, make sure to open it in binary mode: */
+
+ if (vers && vers->options)
+ bin = !(strcmp (vers->options, "-kb"));
+ else
+ bin = 0;
+
+ fd = open (file, O_RDONLY | (bin ? OPEN_BINARY : 0));
+
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ if (gzip_level && sb.st_size > 100)
+ {
+ int nread, newsize = 0, gzip_status;
+ pid_t gzip_pid;
+ char *bufp = buf;
+ int readsize = 8192;
+#ifdef LINES_CRLF_TERMINATED
+ char tempfile[L_tmpnam];
+ int converting;
+#endif /* LINES_CRLF_TERMINATED */
+
+#ifdef LINES_CRLF_TERMINATED
+ /* Assume everything in a "cvs import" is text. */
+ if (vers == NULL)
+ converting = 1;
+ else
+ /* Otherwise, we convert things unless they're binary. */
+ converting = (! bin);
+
+ if (converting)
+ {
+ /* gzip reads and writes files without munging CRLF
+ sequences, as it should, but files should be
+ transmitted in LF form. Convert CRLF to LF before
+ gzipping, on systems where this is necessary.
+
+ If Windows NT supported fork, we could do this by
+ pushing another filter on in front of gzip. But it
+ doesn't. I'd have to write a trivial little program to
+ do the conversion and have CVS spawn it off. But
+ little executables like that always get lost.
+
+ Alternatively, this cruft could go away if we switched
+ to a gzip library instead of a subprocess; then we
+ could tell gzip to open the file with CRLF translation
+ enabled. */
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ tmpnam (tempfile);
+ convert_file (file, O_RDONLY,
+ tempfile,
+ O_WRONLY | O_CREAT | O_TRUNC | OPEN_BINARY);
+
+ /* This OPEN_BINARY doesn't make any difference, I think, because
+ gzip will deal with the inherited handle as it pleases. But I
+ do remember something obscure in the manuals about propagating
+ the translation mode to created processes via environment
+ variables, ick. */
+ fd = open (tempfile, O_RDONLY | OPEN_BINARY);
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+ }
+#endif /* LINES_CRLF_TERMINATED */
+
+ fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid);
+
+ /* FIXME: is there any reason to go through all this realloc'ing
+ when we could just be writing the data to the network as we read
+ it from gzip? */
+ while (1)
+ {
+ if ((bufp - buf) + readsize >= bufsize)
+ {
+ /*
+ * We need to expand the buffer if gzip ends up expanding
+ * the file.
+ */
+ newsize = bufp - buf;
+ while (newsize + readsize >= bufsize)
+ bufsize *= 2;
+ buf = xrealloc (buf, bufsize);
+ bufp = buf + newsize;
+ }
+ nread = read (fd, bufp, readsize);
+ if (nread < 0)
+ error (1, errno, "reading from gzip pipe");
+ else if (nread == 0)
+ /* eof */
+ break;
+ bufp += nread;
+ }
+ newsize = bufp - buf;
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid)
+ error (1, errno, "waiting for gzip proc %ld", (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, errno, "gzip exited %d", gzip_status);
+
+#if LINES_CRLF_TERMINATED
+ if (converting)
+ {
+ if (unlink (tempfile) < 0)
+ error (0, errno,
+ "warning: can't remove temp file %s", tempfile);
+ }
+#endif /* LINES_CRLF_TERMINATED */
+
+ {
+ char tmp[80];
+
+ send_to_server ("Modified ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ send_to_server (mode_string, 0);
+ send_to_server ("\012z", 2);
+ sprintf (tmp, "%lu\n", (unsigned long) newsize);
+ send_to_server (tmp, 0);
+
+ send_to_server (buf, newsize);
+ }
+ }
+ else
+ {
+ int newsize;
+
+ {
+ char *bufp = buf;
+ int len;
+
+ /* FIXME: This is gross. It assumes that we might read
+ less than st_size bytes (true on NT), but not more.
+ Instead of this we should just be reading a block of
+ data (e.g. 8192 bytes), writing it to the network, and
+ so on until EOF. */
+ while ((len = read (fd, bufp, (buf + sb.st_size) - bufp)) > 0)
+ bufp += len;
+
+ if (len < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ newsize = bufp - buf;
+ }
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ {
+ char tmp[80];
+
+ send_to_server ("Modified ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ send_to_server (mode_string, 0);
+ send_to_server ("\012", 1);
+ sprintf (tmp, "%lu\012", (unsigned long) newsize);
+ send_to_server (tmp, 0);
+ }
+
+ /*
+ * Note that this only ends with a newline if the file ended with
+ * one.
+ */
+ if (newsize > 0)
+ send_to_server (buf, newsize);
+ }
+ free (buf);
+ free (mode_string);
+}
+
+static int send_fileproc PROTO ((struct file_info *finfo));
+
+/* Deal with one file. */
+static int
+send_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Vers_TS *vers;
+
+ send_a_repository ("", finfo->repository, finfo->update_dir);
+
+ vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL,
+ (char *)NULL,
+ finfo->file, 0, 0, finfo->entries, (RCSNode *)NULL);
+
+ if (vers->vn_user != NULL)
+ {
+ char *tmp;
+
+ tmp = xmalloc (strlen (finfo->file) + strlen (vers->vn_user)
+ + strlen (vers->options) + 200);
+ sprintf (tmp, "Entry /%s/%s/%s%s/%s/",
+ finfo->file, vers->vn_user,
+ vers->ts_conflict == NULL ? "" : "+",
+ (vers->ts_conflict == NULL ? ""
+ : (vers->ts_user != NULL &&
+ strcmp (vers->ts_conflict, vers->ts_user) == 0
+ ? "="
+ : "modified")),
+ vers->options);
+
+ /* The Entries request. */
+ /* Not sure about whether this deals with -k and stuff right. */
+ send_to_server (tmp, 0);
+ free (tmp);
+ if (vers->entdata != NULL && vers->entdata->tag)
+ {
+ send_to_server ("T", 0);
+ send_to_server (vers->entdata->tag, 0);
+ }
+ else if (vers->entdata != NULL && vers->entdata->date)
+ {
+ send_to_server ("D", 0);
+ send_to_server (vers->entdata->date, 0);
+ }
+ send_to_server ("\012", 1);
+ }
+
+ if (vers->ts_user == NULL)
+ {
+ /*
+ * Do we want to print "file was lost" like normal CVS?
+ * Would it always be appropriate?
+ */
+ /* File no longer exists. */
+ if (!use_unchanged)
+ {
+ /* if the server is old, use the old request... */
+ send_to_server ("Lost ", 0);
+ send_to_server (finfo->file, 0);
+ send_to_server ("\012", 1);
+ /*
+ * Otherwise, don't do anything for missing files,
+ * they just happen.
+ */
+ }
+ }
+ else if (vers->ts_rcs == NULL
+ || strcmp (vers->ts_user, vers->ts_rcs) != 0)
+ {
+ send_modified (finfo->file, finfo->fullname, vers);
+ }
+ else
+ {
+ /* Only use this request if the server supports it... */
+ if (use_unchanged)
+ {
+ send_to_server ("Unchanged ", 0);
+ send_to_server (finfo->file, 0);
+ send_to_server ("\012", 1);
+ }
+ }
+
+ /* 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);
+ (void) addnode (ignlist, p);
+ }
+
+ freevers_ts (&vers);
+ return 0;
+}
+
+static void send_ignproc PROTO ((char *, char *));
+
+static void
+send_ignproc (file, dir)
+ char *file;
+ char *dir;
+{
+ if (ign_inhibit_server || !supported_request ("Questionable"))
+ {
+ if (dir[0] != '\0')
+ (void) printf ("? %s/%s\n", dir, file);
+ else
+ (void) printf ("? %s\n", file);
+ }
+ else
+ {
+ send_to_server ("Questionable ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ }
+}
+
+static int send_filesdoneproc PROTO ((int, char *, char *));
+
+static int
+send_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ /* if this directory has an ignore list, process it then free it */
+ if (ignlist)
+ {
+ ignore_files (ignlist, update_dir, send_ignproc);
+ dellist (&ignlist);
+ }
+
+ return (err);
+}
+
+static Dtype send_dirent_proc PROTO ((char *, char *, char *));
+
+/*
+ * send_dirent_proc () is called back by the recursion processor before a
+ * sub-directory is processed for update.
+ * 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
+send_dirent_proc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ int dir_exists;
+ char *cvsadm_repos_name;
+
+ /*
+ * If the directory does not exist yet (e.g. "cvs update -d
+ * foo"), no need to send any files from it.
+ */
+ dir_exists = isdir (dir);
+
+ if (ignore_directory (update_dir))
+ {
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Ignoring %s", update_dir);
+ return (R_SKIP_ALL);
+ }
+
+ /* initialize the ignore list for this directory */
+ ignlist = getlist ();
+
+ /*
+ * If there is an empty directory (e.g. we are doing `cvs add' on a
+ * newly-created directory), the server still needs to know about it.
+ */
+
+ cvsadm_repos_name = xmalloc (strlen (dir) + sizeof (CVSADM_REP) + 80);
+ sprintf (cvsadm_repos_name, "%s/%s", dir, CVSADM_REP);
+ if (dir_exists && isreadable (cvsadm_repos_name))
+ {
+ /*
+ * Get the repository from a CVS/Repository file whenever possible.
+ * The repository variable is wrong if the names in the local
+ * directory don't match the names in the repository.
+ */
+ char *repos = Name_Repository (dir, update_dir);
+ send_a_repository (dir, repos, update_dir);
+ free (repos);
+ }
+ else
+ send_a_repository (dir, repository, update_dir);
+ free (cvsadm_repos_name);
+
+ return (dir_exists ? R_PROCESS : R_SKIP_ALL);
+}
+
+/*
+ * Send each option in a string to the server, one by one.
+ * This assumes that the options are single characters. For
+ * more complex parsing, do it yourself.
+ */
+
+void
+send_option_string (string)
+ char *string;
+{
+ char *p;
+ char it[3];
+
+ for (p = string; p[0]; p++) {
+ if (p[0] == ' ')
+ continue;
+ if (p[0] == '-')
+ continue;
+ it[0] = '-';
+ it[1] = p[0];
+ it[2] = '\0';
+ send_arg (it);
+ }
+}
+
+
+/* Send the names of all the argument files to the server. */
+
+void
+send_file_names (argc, argv, flags)
+ int argc;
+ char **argv;
+ unsigned int flags;
+{
+ int i;
+ char *p;
+ char *q;
+ int level;
+ int max_level;
+
+ /* The fact that we do this here as well as start_recursion is a bit
+ of a performance hit. Perhaps worth cleaning up someday. */
+ if (flags & SEND_EXPAND_WILD)
+ expand_wild (argc, argv, &argc, &argv);
+
+ /* Send Max-dotdot if needed. */
+ max_level = 0;
+ for (i = 0; i < argc; ++i)
+ {
+ p = argv[i];
+ 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);
+ }
+ if (max_level > 0)
+ {
+ if (supported_request ("Max-dotdot"))
+ {
+ char buf[10];
+ sprintf (buf, "%d", max_level);
+
+ send_to_server ("Max-dotdot ", 0);
+ send_to_server (buf, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ /*
+ * "leading .." is not strictly correct, as this also includes
+ * cases like "foo/../..". But trying to explain that in the
+ * error message would probably just confuse users.
+ */
+ error (1, 0,
+ "leading .. not supported by old (pre-Max-dotdot) servers");
+ }
+
+ for (i = 0; i < argc; ++i)
+ {
+ char buf[1];
+ char *p = argv[i];
+
+ send_to_server ("Argument ", 0);
+
+ while (*p)
+ {
+ if (*p == '\n')
+ {
+ send_to_server ("\012Argumentx ", 0);
+ }
+ else if (ISDIRSEP (*p))
+ {
+ buf[0] = '/';
+ send_to_server (buf, 1);
+ }
+ else
+ {
+ buf[0] = *p;
+ send_to_server (buf, 1);
+ }
+ ++p;
+ }
+ send_to_server ("\012", 1);
+ }
+
+ if (flags & SEND_EXPAND_WILD)
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ free (argv[i]);
+ free (argv);
+ }
+}
+
+
+/*
+ * Send Repository, Modified and Entry. argc and argv contain only
+ * the files to operate on (or empty for everything), not options.
+ * local is nonzero if we should not recurse (-l option). Also sends
+ * Argument lines for argc and argv, so should be called after options
+ * are sent.
+ */
+void
+send_files (argc, argv, local, aflag)
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+{
+ int err;
+
+ /*
+ * aflag controls whether the tag/date is copied into the vers_ts.
+ * But we don't actually use it, so I don't think it matters what we pass
+ * for aflag here.
+ */
+ err = start_recursion
+ (send_fileproc, send_filesdoneproc,
+ send_dirent_proc, (DIRLEAVEPROC)NULL,
+ argc, argv, local, W_LOCAL, aflag, 0, (char *)NULL, 0, 0);
+ if (err)
+ exit (EXIT_FAILURE);
+ if (toplevel_repos == NULL)
+ /*
+ * This happens if we are not processing any files,
+ * or for checkouts in directories without any existing stuff
+ * checked out. The following assignment is correct for the
+ * latter case; I don't think toplevel_repos matters for the
+ * former.
+ */
+ toplevel_repos = xstrdup (server_cvsroot);
+ send_repository ("", toplevel_repos, ".");
+}
+
+void
+client_import_setup (repository)
+ char *repository;
+{
+ if (toplevel_repos == NULL) /* should always be true */
+ send_a_repository ("", repository, "");
+}
+
+/*
+ * Process the argument import file.
+ */
+int
+client_process_import_file (message, vfile, vtag, targc, targv, repository)
+ char *message;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+ char *repository;
+{
+ char *short_pathname;
+ int first_time;
+
+ /* FIXME: I think this is always false now that we call
+ client_import_setup at the start. */
+
+ first_time = toplevel_repos == NULL;
+
+ if (first_time)
+ send_a_repository ("", repository, "");
+
+ if (strncmp (repository, toplevel_repos, strlen (toplevel_repos)) != 0)
+ error (1, 0,
+ "internal error: pathname `%s' doesn't specify file in `%s'",
+ repository, toplevel_repos);
+ short_pathname = repository + strlen (toplevel_repos) + 1;
+
+ if (!first_time)
+ {
+ send_a_repository ("", repository, short_pathname);
+ }
+ send_modified (vfile, short_pathname, NULL);
+ return 0;
+}
+
+void
+client_import_done ()
+{
+ if (toplevel_repos == NULL)
+ /*
+ * This happens if we are not processing any files,
+ * or for checkouts in directories without any existing stuff
+ * checked out. The following assignment is correct for the
+ * latter case; I don't think toplevel_repos matters for the
+ * former.
+ */
+ /* FIXME: "can't happen" now that we call client_import_setup
+ at the beginning. */
+ toplevel_repos = xstrdup (server_cvsroot);
+ send_repository ("", toplevel_repos, ".");
+}
+
+static void
+notified_a_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ FILE *fp;
+ FILE *newf;
+ size_t line_len = 8192;
+ char *line = xmalloc (line_len);
+ char *cp;
+ int nread;
+ int nwritten;
+ char *p;
+
+ fp = open_file (CVSADM_NOTIFY, "r");
+ if (getline (&line, &line_len, fp) < 0)
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ cp = strchr (line, '\t');
+ if (cp == NULL)
+ {
+ error (0, 0, "malformed %s file", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ *cp = '\0';
+ if (strcmp (filename, line + 1) != 0)
+ {
+ error (0, 0, "protocol error: notified %s, expected %s", filename,
+ line + 1);
+ }
+
+ if (getline (&line, &line_len, fp) < 0)
+ {
+ if (feof (fp))
+ {
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ if (unlink (CVSADM_NOTIFY) < 0)
+ error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
+ return;
+ }
+ else
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ }
+ newf = open_file (CVSADM_NOTIFYTMP, "w");
+ if (fputs (line, newf) < 0)
+ {
+ error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
+ goto error2;
+ }
+ while ((nread = fread (line, 1, line_len, fp)) > 0)
+ {
+ p = line;
+ while ((nwritten = fwrite (p, 1, nread, newf)) > 0)
+ {
+ nread -= nwritten;
+ p += nwritten;
+ }
+ if (ferror (newf))
+ {
+ error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
+ goto error2;
+ }
+ }
+ if (ferror (fp))
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error2;
+ }
+ if (fclose (newf) < 0)
+ {
+ error (0, errno, "cannot close %s", CVSADM_NOTIFYTMP);
+ goto error_exit;
+ }
+ if (fclose (fp) < 0)
+ {
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ return;
+ }
+
+ {
+ /* In this case, we want rename_file() to ignore noexec. */
+ int saved_noexec = noexec;
+ noexec = 0;
+ rename_file (CVSADM_NOTIFYTMP, CVSADM_NOTIFY);
+ noexec = saved_noexec;
+ }
+
+ return;
+ error2:
+ (void) fclose (newf);
+ error_exit:
+ (void) fclose (fp);
+}
+
+static void
+handle_notified (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, notified_a_file, NULL);
+}
+
+void
+client_notify (repository, update_dir, filename, notif_type, val)
+ char *repository;
+ char *update_dir;
+ char *filename;
+ int notif_type;
+ char *val;
+{
+ char buf[2];
+
+ send_a_repository ("", repository, update_dir);
+ send_to_server ("Notify ", 0);
+ send_to_server (filename, 0);
+ send_to_server ("\012", 1);
+ buf[0] = notif_type;
+ buf[1] = '\0';
+ send_to_server (buf, 1);
+ send_to_server ("\t", 1);
+ send_to_server (val, 0);
+}
+
+/*
+ * Send an option with an argument, dealing correctly with newlines in
+ * the argument. If ARG is NULL, forget the whole thing.
+ */
+void
+option_with_arg (option, arg)
+ char *option;
+ char *arg;
+{
+ if (arg == NULL)
+ return;
+
+ send_to_server ("Argument ", 0);
+ send_to_server (option, 0);
+ send_to_server ("\012", 1);
+
+ send_arg (arg);
+}
+
+/*
+ * Send a date to the server. This will passed a string which is the
+ * result of Make_Date, and looks like YY.MM.DD.HH.MM.SS, where all
+ * the letters are single digits. The time will be GMT. getdate on
+ * the server can't parse that, so we turn it back into something
+ * which it can parse.
+ */
+
+void
+client_senddate (date)
+ const char *date;
+{
+ int year, month, day, hour, minute, second;
+ char buf[100];
+
+ if (sscanf (date, DATEFORM, &year, &month, &day, &hour, &minute, &second)
+ != 6)
+ {
+ error (1, 0, "diff_client_senddate: sscanf failed on date");
+ }
+
+#ifndef HAVE_RCS5
+ /* We need to fix the timezone in this case; see Make_Date. */
+ abort ();
+#endif /* HAVE_RCS5 */
+
+ sprintf (buf, "%d/%d/%d %d:%d:%d GMT", month, day, year,
+ hour, minute, second);
+ option_with_arg ("-D", buf);
+}
+
+int
+client_commit (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return commit (argc, argv);
+}
+
+int
+client_update (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return update (argc, argv);
+}
+
+int
+client_checkout (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return checkout (argc, argv);
+}
+
+int
+client_diff (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return diff (argc, argv); /* Call real code */
+}
+
+int
+client_status (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+ return status (argc, argv);
+}
+
+int
+client_log (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return cvslog (argc, argv); /* Call real code */
+}
+
+int
+client_add (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return add (argc, argv); /* Call real code */
+}
+
+int
+client_remove (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return cvsremove (argc, argv); /* Call real code */
+}
+
+int
+client_rdiff (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return patch (argc, argv); /* Call real code */
+}
+
+int
+client_tag (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return tag (argc, argv); /* Call real code */
+}
+
+int
+client_rtag (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return rtag (argc, argv); /* Call real code */
+}
+
+int
+client_import (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return import (argc, argv); /* Call real code */
+}
+
+int
+client_admin (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return admin (argc, argv); /* Call real code */
+}
+
+int
+client_export (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return checkout (argc, argv); /* Call real code */
+}
+
+int
+client_history (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return history (argc, argv); /* Call real code */
+}
+
+int
+client_release (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return release (argc, argv); /* Call real code */
+}
+
+int
+client_watch (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return watch (argc, argv); /* Call real code */
+}
+
+int
+client_watchers (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return watchers (argc, argv); /* Call real code */
+}
+
+int
+client_editors (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return editors (argc, argv); /* Call real code */
+}
+
+int
+client_edit (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return edit (argc, argv); /* Call real code */
+}
+
+int
+client_unedit (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return unedit (argc, argv); /* Call real code */
+}
+
+void
+send_init_command ()
+{
+ /* This is here because we need the server_cvsroot variable. */
+ send_to_server ("init ", 0);
+ send_to_server (server_cvsroot, 0);
+ send_to_server ("\012", 0);
+}
+
+int
+client_init (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return init (argc, argv); /* Call real code */
+}
+
+int
+client_annotate (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return annotate (argc, argv); /* Call real code */
+}
+#endif /* CLIENT_SUPPORT */
diff --git a/contrib/cvs/src/client.h b/contrib/cvs/src/client.h
new file mode 100644
index 0000000..b238c32
--- /dev/null
+++ b/contrib/cvs/src/client.h
@@ -0,0 +1,191 @@
+/* Interface between the client and the rest of CVS. */
+
+/* Stuff shared with the server. */
+extern char *mode_to_string PROTO((mode_t));
+extern int change_mode PROTO((char *, char *));
+
+extern int gzip_level;
+extern int filter_through_gzip PROTO((int, int, int, pid_t *));
+extern int filter_through_gunzip PROTO((int, int, pid_t *));
+
+#ifdef CLIENT_SUPPORT
+/*
+ * Functions to perform CVS commands via the protocol. argc and argv
+ * are the arguments and the return value is the exit status (zero success
+ * nonzero failure).
+ */
+extern int client_commit PROTO((int argc, char **argv));
+extern int client_update PROTO((int argc, char **argv));
+extern int client_checkout PROTO((int argc, char **argv));
+extern int client_diff PROTO((int argc, char **argv));
+extern int client_log PROTO((int argc, char **argv));
+extern int client_add PROTO((int argc, char **argv));
+extern int client_remove PROTO((int argc, char **argv));
+extern int client_status PROTO((int argc, char **argv));
+extern int client_rdiff PROTO((int argc, char **argv));
+extern int client_tag PROTO((int argc, char **argv));
+extern int client_rtag PROTO((int argc, char **argv));
+extern int client_import PROTO((int argc, char **argv));
+extern int client_admin PROTO((int argc, char **argv));
+extern int client_export PROTO((int argc, char **argv));
+extern int client_history PROTO((int argc, char **argv));
+extern int client_release PROTO((int argc, char **argv));
+extern int client_watch PROTO((int argc, char **argv));
+extern int client_watchers PROTO((int argc, char **argv));
+extern int client_editors PROTO((int argc, char **argv));
+extern int client_edit PROTO((int argc, char **argv));
+extern int client_unedit PROTO((int argc, char **argv));
+extern int client_init PROTO ((int argc, char **argv));
+extern int client_annotate PROTO ((int argc, char **argv));
+
+/*
+ * Flag variable for seeing whether common code is running as a client
+ * or to do a local operation.
+ */
+extern int client_active;
+
+/*
+ * Flag variable for seeing whether the server has been started yet.
+ * As of this writing, only edit.c:notify_check() uses it.
+ */
+extern int server_started;
+
+/* Is the -P option to checkout or update specified? */
+extern int client_prune_dirs;
+
+#ifdef AUTH_CLIENT_SUPPORT
+extern int use_authenticating_server;
+int connect_to_pserver PROTO((int *tofdp, int* fromfdp, int verify_only));
+# ifndef CVS_AUTH_PORT
+# define CVS_AUTH_PORT 2401
+# endif /* CVS_AUTH_PORT */
+#endif /* AUTH_CLIENT_SUPPORT */
+
+#ifdef AUTH_SERVER_SUPPORT
+extern void authenticate_connection PROTO ((void));
+#endif
+
+/* Talking to the server. */
+void send_to_server PROTO((char *str, size_t len));
+void read_from_server PROTO((char *buf, size_t len));
+
+/* Internal functions that handle client communication to server, etc. */
+int supported_request PROTO ((char *));
+void option_with_arg PROTO((char *option, char *arg));
+
+/* Get the responses and then close the connection. */
+extern int get_responses_and_close PROTO((void));
+
+extern int get_server_responses PROTO((void));
+
+/* Start up the connection to the server on the other end. */
+void
+start_server PROTO((void));
+
+/* Send the names of all the argument files to the server. */
+void
+send_file_names PROTO((int argc, char **argv, unsigned int flags));
+
+/* Flags for send_file_names. */
+/* Expand wild cards? */
+#define SEND_EXPAND_WILD 1
+
+/*
+ * Send Repository, Modified and Entry. argc and argv contain only
+ * the files to operate on (or empty for everything), not options.
+ * local is nonzero if we should not recurse (-l option).
+ */
+void
+send_files PROTO((int argc, char **argv, int local, int aflag));
+
+/*
+ * Like send_files but never send "Unchanged"--just send the contents of the
+ * file in that case. This is used to fix it if you import a directory which
+ * happens to have CVS directories (yes it is obscure but the testsuite tests
+ * it).
+ */
+void
+send_files_contents PROTO((int argc, char **argv, int local, int aflag));
+
+/* Send an argument to the remote server. */
+void
+send_arg PROTO((char *string));
+
+/* Send a string of single-char options to the remote server, one by one. */
+void
+send_option_string PROTO((char *string));
+
+#endif /* CLIENT_SUPPORT */
+
+/*
+ * This structure is used to catalog the responses the client is
+ * prepared to see from the server.
+ */
+
+struct response
+{
+ /* Name of the response. */
+ char *name;
+
+#ifdef CLIENT_SUPPORT
+ /*
+ * Function to carry out the response. ARGS is the text of the
+ * command after name and, if present, a single space, have been
+ * stripped off. The function can scribble into ARGS if it wants.
+ */
+ void (*func) PROTO((char *args, int len));
+
+ /*
+ * ok and error are special; they indicate we are at the end of the
+ * responses, and error indicates we should exit with nonzero
+ * exitstatus.
+ */
+ enum {response_type_normal, response_type_ok, response_type_error} type;
+#endif
+
+ /* Used by the server to indicate whether response is supported by
+ the client, as set by the Valid-responses request. */
+ enum {
+ /*
+ * Failure to implement this response can imply a fatal
+ * error. This should be set only for responses which were in the
+ * original version of the protocol; it should not be set for new
+ * responses.
+ */
+ rs_essential,
+
+ /* Some clients might not understand this response. */
+ rs_optional,
+
+ /*
+ * Set by the server to one of the following based on what this
+ * client actually supports.
+ */
+ rs_supported,
+ rs_not_supported
+ } status;
+};
+
+/* Table of responses ending in an entry with a NULL name. */
+
+extern struct response responses[];
+
+#ifdef CLIENT_SUPPORT
+
+extern void client_senddate PROTO((const char *date));
+extern void client_expand_modules PROTO((int argc, char **argv, int local));
+extern void client_send_expansions PROTO((int local));
+extern void client_nonexpanded_setup PROTO((void));
+
+extern void send_init_command PROTO ((void));
+
+extern char **failed_patches;
+extern int failed_patches_count;
+extern char toplevel_wd[];
+extern void client_import_setup PROTO((char *repository));
+extern int client_process_import_file
+ PROTO((char *message, char *vfile, char *vtag,
+ int targc, char *targv[], char *repository));
+extern void client_import_done PROTO((void));
+extern void client_notify PROTO((char *, char *, char *, int, char *));
+#endif /* CLIENT_SUPPORT */
diff --git a/contrib/cvs/src/commit.c b/contrib/cvs/src/commit.c
new file mode 100644
index 0000000..ac81790
--- /dev/null
+++ b/contrib/cvs/src/commit.c
@@ -0,0 +1,1824 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Commit Files
+ *
+ * "commit" commits the present version to the RCS repository, AFTER
+ * having done a test on conflicts.
+ *
+ * The call is: cvs commit [options] files...
+ *
+ */
+
+#include "cvs.h"
+#include "getline.h"
+#include "edit.h"
+#include "fileattr.h"
+
+static Dtype check_direntproc PROTO((char *dir, char *repos, char *update_dir));
+static int check_fileproc PROTO((struct file_info *finfo));
+static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+static int checkaddfile PROTO((char *file, char *repository, char *tag,
+ char *options, RCSNode **rcsnode));
+static Dtype commit_direntproc PROTO((char *dir, char *repos, char *update_dir));
+static int commit_dirleaveproc PROTO((char *dir, int err, char *update_dir));
+static int commit_fileproc PROTO((struct file_info *finfo));
+static int commit_filesdoneproc PROTO((int err, char *repository, char *update_dir));
+static int finaladd PROTO((char *file, char *revision, char *tag,
+ char *options, char *update_dir,
+ char *repository, List *entries));
+static int findmaxrev PROTO((Node * p, void *closure));
+static int lock_RCS PROTO((char *user, char *rcs, char *rev, char *repository));
+static int lockrcsfile PROTO((char *file, char *repository, char *rev));
+static int precommit_list_proc PROTO((Node * p, void *closure));
+static int precommit_proc PROTO((char *repository, char *filter));
+static int remove_file PROTO((char *file, char *repository, char *tag,
+ char *message, List *entries, RCSNode *rcsnode));
+static void fix_rcs_modes PROTO((char *rcs, char *user));
+static void fixaddfile PROTO((char *file, char *repository));
+static void fixbranch PROTO((char *file, char *repository, char *branch));
+static void unlockrcs PROTO((char *file, char *repository));
+static void ci_delproc PROTO((Node *p));
+static void masterlist_delproc PROTO((Node *p));
+static void locate_rcs PROTO((char *file, char *repository, char *rcs));
+
+struct commit_info
+{
+ Ctype status; /* as returned from Classify_File() */
+ char *rev; /* a numeric rev, if we know it */
+ char *tag; /* any sticky tag, or -r option */
+ char *options; /* Any sticky -k option */
+};
+struct master_lists
+{
+ List *ulist; /* list for Update_Logfile */
+ List *cilist; /* list with commit_info structs */
+};
+
+static int force_ci = 0;
+static int got_message;
+static int run_module_prog = 1;
+static int aflag;
+static char *tag;
+static char *write_dirtag;
+static char *logfile;
+static List *mulist;
+static char *message;
+static time_t last_register_time;
+
+
+static const char *const commit_usage[] =
+{
+ "Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
+ "\t-n\tDo not run the module program (if any).\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-l\tLocal directory only (not recursive).\n",
+ "\t-f\tForce the file to be committed; disables recursion.\n",
+ "\t-F file\tRead the log message from file.\n",
+ "\t-m msg\tLog message.\n",
+ "\t-r rev\tCommit to this branch or trunk revision.\n",
+ NULL
+};
+
+#ifdef CLIENT_SUPPORT
+struct find_data {
+ List *ulist;
+ int argc;
+ char **argv;
+};
+
+/* Pass as a static until we get around to fixing start_recursion to
+ pass along a void * where we can stash it. */
+struct find_data *find_data_static;
+
+static int find_fileproc PROTO ((struct file_info *finfo));
+
+/* Machinery to find out what is modified, added, and removed. It is
+ possible this should be broken out into a new client_classify function;
+ merging it with classify_file is almost sure to be a mess, though,
+ because classify_file has all kinds of repository processing. */
+static int
+find_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Vers_TS *vers;
+ enum classify_type status;
+ Node *node;
+ struct find_data *args = find_data_static;
+
+ vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL,
+ (char *)NULL,
+ finfo->file, 0, 0, finfo->entries, (RCSNode *)NULL);
+ if (vers->ts_user == NULL
+ && vers->vn_user != NULL
+ && vers->vn_user[0] == '-')
+ status = T_REMOVED;
+ else if (vers->vn_user == NULL)
+ {
+ if (vers->ts_user == NULL)
+ error (0, 0, "nothing known about `%s'", finfo->fullname);
+ else
+ error (0, 0, "use `cvs add' to create an entry for %s",
+ finfo->fullname);
+ return 1;
+ }
+ else if (vers->ts_user != NULL
+ && vers->vn_user != NULL
+ && vers->vn_user[0] == '0')
+ status = T_ADDED;
+ else if (vers->ts_user != NULL
+ && vers->ts_rcs != NULL
+ && strcmp (vers->ts_user, vers->ts_rcs) != 0)
+ status = T_MODIFIED;
+ else
+ {
+ /* This covers unmodified files, as well as a variety of other
+ cases. FIXME: we probably should be printing a message and
+ returning 1 for many of those cases (but I'm not sure
+ exactly which ones). */
+ return 0;
+ }
+
+ node = getnode ();
+ node->key = xstrdup (finfo->fullname);
+
+ node->type = UPDATE;
+ node->delproc = update_delproc;
+ node->data = (char *) status;
+ (void)addnode (args->ulist, node);
+
+ ++args->argc;
+
+ return 0;
+}
+
+static int copy_ulist PROTO ((Node *, void *));
+
+static int
+copy_ulist (node, data)
+ Node *node;
+ void *data;
+{
+ struct find_data *args = (struct find_data *)data;
+ args->argv[args->argc++] = node->key;
+ return 0;
+}
+#endif /* CLIENT_SUPPORT */
+
+int
+commit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+ int local = 0;
+
+ if (argc == -1)
+ usage (commit_usage);
+
+#ifdef CVS_BADROOT
+ /*
+ * For log purposes, do not allow "root" to commit files. If you look
+ * like root, but are really logged in as a non-root user, it's OK.
+ */
+ if (geteuid () == (uid_t) 0)
+ {
+ struct passwd *pw;
+
+ if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
+ error (1, 0, "you are unknown to this system");
+ if (pw->pw_uid == (uid_t) 0)
+ error (1, 0, "cannot commit files as 'root'");
+ }
+#endif /* CVS_BADROOT */
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "nlRm:fF:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 'n':
+ run_module_prog = 0;
+ break;
+ case 'm':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#endif
+ if (message)
+ {
+ free (message);
+ message = NULL;
+ }
+
+ message = xstrdup(optarg);
+ break;
+ case 'r':
+ if (tag)
+ free (tag);
+ tag = xstrdup (optarg);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'f':
+ force_ci = 1;
+ local = 1; /* also disable recursion */
+ break;
+ case 'F':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#endif
+ logfile = optarg;
+ break;
+ case '?':
+ default:
+ usage (commit_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* numeric specified revision means we ignore sticky tags... */
+ if (tag && isdigit (*tag))
+ {
+ aflag = 1;
+ /* strip trailing dots */
+ while (tag[strlen (tag) - 1] == '.')
+ tag[strlen (tag) - 1] = '\0';
+ }
+
+ /* some checks related to the "-F logfile" option */
+ if (logfile)
+ {
+ int n, logfd;
+ struct stat statbuf;
+
+ if (message)
+ error (1, 0, "cannot specify both a message and a log file");
+
+ /* FIXME: Why is this binary? Needs more investigation. */
+ if ((logfd = open (logfile, O_RDONLY | OPEN_BINARY)) < 0)
+ error (1, errno, "cannot open log file %s", logfile);
+
+ if (fstat(logfd, &statbuf) < 0)
+ error (1, errno, "cannot find size of log file %s", logfile);
+
+ message = xmalloc (statbuf.st_size + 1);
+
+ /* FIXME: Should keep reading until EOF, rather than assuming the
+ first read gets the whole thing. */
+ if ((n = read (logfd, message, statbuf.st_size + 1)) < 0)
+ error (1, errno, "cannot read log message from %s", logfile);
+
+ (void) close (logfd);
+ message[n] = '\0';
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ struct find_data find_args;
+
+ ign_setup ();
+
+ /* Note that we don't do ignore file processing here, and we
+ don't call ignore_files. This means that we won't print "?
+ foo" for stray files. Sounds OK, the doc only promises
+ that update does that. */
+ find_args.ulist = getlist ();
+ find_args.argc = 0;
+ find_data_static = &find_args;
+ err = start_recursion (find_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0,
+ (char *)NULL, 0, 0);
+ if (err)
+ error (1, 0, "correct above errors first!");
+
+ if (find_args.argc == 0)
+ return 0;
+
+ /* Now we keep track of which files we actually are going to
+ operate on, and only work with those files in the future.
+ This saves time--we don't want to search the file system
+ of the working directory twice. */
+ find_args.argv = (char **) xmalloc (find_args.argc * sizeof (char **));
+ find_args.argc = 0;
+ walklist (find_args.ulist, copy_ulist, &find_args);
+
+ /*
+ * Do this before calling do_editor; don't ask for a log
+ * message if we can't talk to the server. But do it after we
+ * have made the checks that we can locally (to more quickly
+ * catch syntax errors, the case where no files are modified,
+ * added or removed, etc.). */
+ start_server ();
+
+ /*
+ * We do this once, not once for each directory as in normal CVS.
+ * The protocol is designed this way. This is a feature.
+ */
+ if (use_editor)
+ do_editor (".", &message, (char *)NULL, find_args.ulist);
+
+ /* We always send some sort of message, even if empty. */
+ option_with_arg ("-m", message);
+
+ if (local)
+ send_arg("-l");
+ if (force_ci)
+ send_arg("-f");
+ if (!run_module_prog)
+ send_arg("-n");
+ option_with_arg ("-r", tag);
+
+ /* Sending only the names of the files which were modified, added,
+ or removed means that the server will only do an up-to-date
+ check on those files. This is different from local CVS and
+ previous versions of client/server CVS, but it probably is a Good
+ Thing, or at least Not Such A Bad Thing. */
+ send_file_names (find_args.argc, find_args.argv, 0);
+ send_files (find_args.argc, find_args.argv, local, 0);
+
+ send_to_server ("ci\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ if (tag != NULL)
+ tag_check_valid (tag, argc, argv, local, aflag, "");
+
+ /* XXX - this is not the perfect check for this */
+ if (argc <= 0)
+ write_dirtag = tag;
+
+ wrap_setup ();
+
+ lock_tree_for_write (argc, argv, local, aflag);
+
+ /*
+ * Set up the master update list
+ */
+ mulist = getlist ();
+
+ /*
+ * Run the recursion processor to verify the files are all up-to-date
+ */
+ err = start_recursion (check_fileproc, check_filesdoneproc,
+ check_direntproc, (DIRLEAVEPROC) NULL, argc,
+ argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1,
+ 0);
+ if (err)
+ {
+ lock_tree_cleanup ();
+ error (1, 0, "correct above errors first!");
+ }
+
+ /*
+ * Run the recursion processor to commit the files
+ */
+ if (noexec == 0)
+ err = start_recursion (commit_fileproc, commit_filesdoneproc,
+ commit_direntproc, commit_dirleaveproc,
+ argc, argv, local, W_LOCAL, aflag, 0,
+ (char *) NULL, 1, 0);
+
+ /*
+ * Unlock all the dirs and clean up
+ */
+ lock_tree_cleanup ();
+ dellist (&mulist);
+
+ if (last_register_time)
+ {
+ time_t now;
+
+ (void) time (&now);
+ if (now == last_register_time)
+ {
+ sleep (1); /* to avoid time-stamp races */
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * Check to see if a file is ok to commit and make sure all files are
+ * up-to-date
+ */
+/* ARGSUSED */
+static int
+check_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Ctype status;
+ char *xdir;
+ Node *p;
+ List *ulist, *cilist;
+ Vers_TS *vers;
+ struct commit_info *ci;
+ int save_noexec, save_quiet, save_really_quiet;
+
+ save_noexec = noexec;
+ save_quiet = quiet;
+ save_really_quiet = really_quiet;
+ noexec = quiet = really_quiet = 1;
+
+ /* handle specified numeric revision specially */
+ if (tag && isdigit (*tag))
+ {
+ /* If the tag is for the trunk, make sure we're at the head */
+ if (numdots (tag) < 2)
+ {
+ status = Classify_File (finfo->file, (char *) NULL, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir, 0);
+ if (status == T_UPTODATE || status == T_MODIFIED ||
+ status == T_ADDED)
+ {
+ Ctype xstatus;
+
+ freevers_ts (&vers);
+ xstatus = Classify_File (finfo->file, tag, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir,
+ 0);
+ if (xstatus == T_REMOVE_ENTRY)
+ status = T_MODIFIED;
+ else if (status == T_MODIFIED && xstatus == T_CONFLICT)
+ status = T_MODIFIED;
+ else
+ status = xstatus;
+ }
+ }
+ else
+ {
+ char *xtag, *cp;
+
+ /*
+ * The revision is off the main trunk; make sure we're
+ * up-to-date with the head of the specified branch.
+ */
+ xtag = xstrdup (tag);
+ if ((numdots (xtag) & 1) != 0)
+ {
+ cp = strrchr (xtag, '.');
+ *cp = '\0';
+ }
+ status = Classify_File (finfo->file, xtag, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir, 0);
+ if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
+ && (cp = strrchr (xtag, '.')) != NULL)
+ {
+ /* pluck one more dot off the revision */
+ *cp = '\0';
+ freevers_ts (&vers);
+ status = Classify_File (finfo->file, xtag, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir,
+ 0);
+ if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
+ status = T_MODIFIED;
+ }
+ /* now, muck with vers to make the tag correct */
+ free (vers->tag);
+ vers->tag = xstrdup (tag);
+ free (xtag);
+ }
+ }
+ else
+ status = Classify_File (finfo->file, tag, (char *) NULL, (char *) NULL,
+ 1, 0, finfo->repository, finfo->entries, finfo->rcs, &vers,
+ finfo->update_dir, 0);
+ noexec = save_noexec;
+ quiet = save_quiet;
+ really_quiet = save_really_quiet;
+
+ /*
+ * If the force-commit option is enabled, and the file in question
+ * appears to be up-to-date, just make it look modified so that
+ * it will be committed.
+ */
+ if (force_ci && status == T_UPTODATE)
+ status = T_MODIFIED;
+
+ switch (status)
+ {
+ case T_CHECKOUT:
+#ifdef SERVER_SUPPORT
+ case T_PATCH:
+#endif
+ case T_NEEDS_MERGE:
+ case T_CONFLICT:
+ case T_REMOVE_ENTRY:
+ error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ case T_MODIFIED:
+ case T_ADDED:
+ case T_REMOVED:
+ /*
+ * some quick sanity checks; if no numeric -r option specified:
+ * - can't have a sticky date
+ * - can't have a sticky tag that is not a branch
+ * Also,
+ * - if status is T_REMOVED, can't have a numeric tag
+ * - if status is T_ADDED, rcs file must not exist
+ * - if status is T_ADDED, can't have a non-trunk numeric rev
+ * - if status is T_MODIFIED and a Conflict marker exists, don't
+ * allow the commit if timestamp is identical or if we find
+ * an RCS_MERGE_PAT in the file.
+ */
+ if (!tag || !isdigit (*tag))
+ {
+ if (vers->date)
+ {
+ error (0, 0,
+ "cannot commit with sticky date for file `%s'",
+ finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (status == T_MODIFIED && vers->tag &&
+ !RCS_isbranch (finfo->rcs, vers->tag))
+ {
+ error (0, 0,
+ "sticky tag `%s' for file `%s' is not a branch",
+ vers->tag, finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+ if (status == T_MODIFIED && !force_ci && vers->ts_conflict)
+ {
+ char *filestamp;
+ int retcode;
+
+ /*
+ * We found a "conflict" marker.
+ *
+ * If the timestamp on the file is the same as the
+ * timestamp stored in the Entries file, we block the commit.
+ */
+#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 == 0)
+ {
+ error (0, 0,
+ "file `%s' had a conflict and has not been modified",
+ finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ /*
+ * If the timestamps differ, look for Conflict indicators
+ * in the file to see if we should block the commit anyway
+ */
+ run_setup ("%s", GREP);
+ run_arg (RCS_MERGE_PAT);
+ run_arg (finfo->file);
+ retcode = run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_REALLY);
+
+ if (retcode == -1)
+ {
+ error (1, errno,
+ "fork failed while examining conflict in `%s'",
+ finfo->fullname);
+ }
+ else if (retcode == 0)
+ {
+ error (0, 0,
+ "file `%s' still contains conflict indicators",
+ finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+
+ if (status == T_REMOVED && vers->tag && isdigit (*vers->tag))
+ {
+ error (0, 0,
+ "cannot remove file `%s' which has a numeric sticky tag of `%s'",
+ finfo->fullname, vers->tag);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (status == T_ADDED)
+ {
+ char rcs[PATH_MAX];
+
+ /* Don't look in the attic; if it exists there we will
+ move it back out in checkaddfile. */
+ sprintf(rcs, "%s/%s%s", finfo->repository, finfo->file, RCSEXT);
+ if (isreadable (rcs))
+ {
+ error (0, 0,
+ "cannot add file `%s' when RCS file `%s' already exists",
+ finfo->fullname, rcs);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (vers->tag && isdigit (*vers->tag) &&
+ numdots (vers->tag) > 1)
+ {
+ error (0, 0,
+ "cannot add file `%s' with revision `%s'; must be on trunk",
+ finfo->fullname, vers->tag);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+
+ /* done with consistency checks; now, to get on with the commit */
+ if (finfo->update_dir[0] == '\0')
+ xdir = ".";
+ else
+ xdir = finfo->update_dir;
+ if ((p = findnode (mulist, xdir)) != NULL)
+ {
+ ulist = ((struct master_lists *) p->data)->ulist;
+ cilist = ((struct master_lists *) p->data)->cilist;
+ }
+ else
+ {
+ struct master_lists *ml;
+
+ ulist = getlist ();
+ cilist = getlist ();
+ p = getnode ();
+ p->key = xstrdup (xdir);
+ p->type = UPDATE;
+ ml = (struct master_lists *)
+ xmalloc (sizeof (struct master_lists));
+ ml->ulist = ulist;
+ ml->cilist = cilist;
+ p->data = (char *) ml;
+ p->delproc = masterlist_delproc;
+ (void) addnode (mulist, p);
+ }
+
+ /* first do ulist, then cilist */
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = update_delproc;
+ p->data = (char *) status;
+ (void) addnode (ulist, p);
+
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = ci_delproc;
+ ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
+ ci->status = status;
+ if (vers->tag)
+ if (isdigit (*vers->tag))
+ ci->rev = xstrdup (vers->tag);
+ else
+ ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
+ else
+ ci->rev = (char *) NULL;
+ ci->tag = xstrdup (vers->tag);
+ ci->options = xstrdup(vers->options);
+ p->data = (char *) ci;
+ (void) addnode (cilist, p);
+ break;
+ case T_UNKNOWN:
+ error (0, 0, "nothing known about `%s'", finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ case T_UPTODATE:
+ break;
+ default:
+ error (0, 0, "CVS internal error: unknown status %d", status);
+ break;
+ }
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print warm fuzzies while examining the dirs
+ */
+/* ARGSUSED */
+static Dtype
+check_direntproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Examining %s", update_dir);
+
+ return (R_PROCESS);
+}
+
+/*
+ * Walklist proc to run pre-commit checks
+ */
+static int
+precommit_list_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) T_ADDED || p->data == (char *) T_MODIFIED ||
+ p->data == (char *) T_REMOVED)
+ {
+ run_arg (p->key);
+ }
+ return (0);
+}
+
+/*
+ * Callback proc for pre-commit checking
+ */
+static List *ulist;
+static int
+precommit_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ /* see if the filter is there, only if it's a full path */
+ if (isabsolute (filter))
+ {
+ char *s, *cp;
+
+ s = xstrdup (filter);
+ for (cp = s; *cp; cp++)
+ if (isspace (*cp))
+ {
+ *cp = '\0';
+ break;
+ }
+ if (!isfile (s))
+ {
+ error (0, errno, "cannot find pre-commit filter `%s'", s);
+ free (s);
+ return (1); /* so it fails! */
+ }
+ free (s);
+ }
+
+ run_setup ("%s %s", filter, repository);
+ (void) walklist (ulist, precommit_list_proc, NULL);
+ return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
+}
+
+/*
+ * Run the pre-commit checks for the dir
+ */
+/* ARGSUSED */
+static int
+check_filesdoneproc (err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ int n;
+ Node *p;
+
+ /* find the update list for this dir */
+ p = findnode (mulist, update_dir);
+ if (p != NULL)
+ ulist = ((struct master_lists *) p->data)->ulist;
+ else
+ ulist = (List *) NULL;
+
+ /* skip the checks if there's nothing to do */
+ if (ulist == NULL || ulist->list->next == ulist->list)
+ return (err);
+
+ /* run any pre-commit checks */
+ if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0)
+ {
+ error (0, 0, "Pre-commit check failed");
+ err += n;
+ }
+
+ return (err);
+}
+
+/*
+ * Do the work of committing a file
+ */
+static int maxrev;
+static char sbranch[PATH_MAX];
+
+/* ARGSUSED */
+static int
+commit_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Node *p;
+ int err = 0;
+ List *ulist, *cilist;
+ struct commit_info *ci;
+ char rcs[PATH_MAX];
+
+ if (finfo->update_dir[0] == '\0')
+ p = findnode (mulist, ".");
+ else
+ p = findnode (mulist, finfo->update_dir);
+
+ /*
+ * if p is null, there were file type command line args which were
+ * all up-to-date so nothing really needs to be done
+ */
+ if (p == NULL)
+ return (0);
+ ulist = ((struct master_lists *) p->data)->ulist;
+ cilist = ((struct master_lists *) p->data)->cilist;
+
+ /*
+ * At this point, we should have the commit message unless we were called
+ * with files as args from the command line. In that latter case, we
+ * need to get the commit message ourselves
+ */
+ if (use_editor && !got_message)
+ {
+ got_message = 1;
+ do_editor (finfo->update_dir, &message, finfo->repository, ulist);
+ }
+
+ p = findnode (cilist, finfo->file);
+ if (p == NULL)
+ return (0);
+
+ ci = (struct commit_info *) p->data;
+ if (ci->status == T_MODIFIED)
+ {
+ if (lockrcsfile (finfo->file, finfo->repository, ci->rev) != 0)
+ {
+ unlockrcs (finfo->file, finfo->repository);
+ err = 1;
+ goto out;
+ }
+ }
+ else if (ci->status == T_ADDED)
+ {
+ if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options,
+ &finfo->rcs) != 0)
+ {
+ fixaddfile (finfo->file, finfo->repository);
+ err = 1;
+ goto out;
+ }
+
+ /* adding files with a tag, now means adding them on a branch.
+ Since the branch test was done in check_fileproc for
+ modified files, we need to stub it in again here. */
+
+ if (ci->tag) {
+ locate_rcs (finfo->file, finfo->repository, rcs);
+ ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
+ err = Checkin ('A', finfo->file, finfo->update_dir, finfo->repository, rcs, ci->rev,
+ ci->tag, ci->options, message, finfo->entries);
+ if (err != 0)
+ {
+ unlockrcs (finfo->file, finfo->repository);
+ fixbranch (finfo->file, finfo->repository, sbranch);
+ }
+
+ (void) time (&last_register_time);
+
+ ci->status = T_UPTODATE;
+ }
+ }
+
+ /*
+ * Add the file for real
+ */
+ if (ci->status == T_ADDED)
+ {
+ char *xrev = (char *) NULL;
+
+ if (ci->rev == NULL)
+ {
+ /* find the max major rev number in this directory */
+ maxrev = 0;
+ (void) walklist (finfo->entries, findmaxrev, NULL);
+ if (maxrev == 0)
+ maxrev = 1;
+ xrev = xmalloc (20);
+ (void) sprintf (xrev, "%d", maxrev);
+ }
+
+ /* XXX - an added file with symbolic -r should add tag as well */
+ err = finaladd (finfo->file, ci->rev ? ci->rev : xrev, ci->tag, ci->options,
+ finfo->update_dir, finfo->repository, finfo->entries);
+ if (xrev)
+ free (xrev);
+ }
+ else if (ci->status == T_MODIFIED)
+ {
+ locate_rcs (finfo->file, finfo->repository, rcs);
+ err = Checkin ('M', finfo->file, finfo->update_dir, finfo->repository,
+ rcs, ci->rev, ci->tag,
+ ci->options, message, finfo->entries);
+
+ (void) time (&last_register_time);
+
+ if (err != 0)
+ {
+ unlockrcs (finfo->file, finfo->repository);
+ fixbranch (finfo->file, finfo->repository, sbranch);
+ }
+ }
+ else if (ci->status == T_REMOVED)
+ {
+ err = remove_file (finfo->file, finfo->repository, ci->tag, message,
+ finfo->entries, finfo->rcs);
+#ifdef SERVER_SUPPORT
+ if (server_active) {
+ server_scratch_entry_only ();
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ /* Doesn't matter, it won't get checked. */
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+ }
+#endif
+ }
+
+ /* Clearly this is right for T_MODIFIED. I haven't thought so much
+ about T_ADDED or T_REMOVED. */
+ notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository);
+
+out:
+ if (err != 0)
+ {
+ /* on failure, remove the file from ulist */
+ p = findnode (ulist, finfo->file);
+ if (p)
+ delnode (p);
+ }
+
+ return (err);
+}
+
+/*
+ * Log the commit and clean up the update list
+ */
+/* ARGSUSED */
+static int
+commit_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ char *xtag = (char *) NULL;
+ Node *p;
+ List *ulist;
+
+ p = findnode (mulist, update_dir);
+ if (p == NULL)
+ return (err);
+
+ ulist = ((struct master_lists *) p->data)->ulist;
+
+ got_message = 0;
+
+ /* see if we need to specify a per-directory or -r option tag */
+ if (tag == NULL)
+ ParseTag (&xtag, (char **) NULL);
+
+ Update_Logfile (repository, message, tag ? tag : xtag, (FILE *) 0, ulist);
+ if (xtag)
+ free (xtag);
+
+ /* Build the administrative files if necessary. */
+ {
+ char *p;
+
+ if (strncmp (CVSroot, repository, strlen (CVSroot)) != 0)
+ error (0, 0, "internal error: repository doesn't begin with root");
+ p = repository + strlen (CVSroot);
+ if (*p == '/')
+ ++p;
+ if (strcmp ("CVSROOT", p) == 0)
+ {
+ /* "Database" might a little bit grandiose and/or vague,
+ but "checked-out copies of administrative files, unless
+ in the case of modules and you are using ndbm in which
+ case modules.{pag,dir,db}" is verbose and excessively
+ focused on how the database is implemented. */
+
+ cvs_output (program_name, 0);
+ cvs_output (" ", 1);
+ cvs_output (command_name, 0);
+ cvs_output (": Rebuilding administrative file database\n", 0);
+ mkmodules (repository);
+ }
+ }
+
+ if (err == 0 && run_module_prog)
+ {
+ FILE *fp;
+
+ if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL)
+ {
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+ char *repository;
+
+ line = NULL;
+ line_chars_allocated = 0;
+ line_length = getline (&line, &line_chars_allocated, fp);
+ if (line_length > 0)
+ {
+ /* Remove any trailing newline. */
+ if (line[line_length - 1] == '\n')
+ line[--line_length] = '\0';
+ repository = Name_Repository ((char *) NULL, update_dir);
+ run_setup ("%s %s", line, repository);
+ (void) printf ("%s %s: Executing '", program_name,
+ command_name);
+ run_print (stdout);
+ (void) printf ("'\n");
+ (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ free (repository);
+ }
+ else
+ {
+ if (ferror (fp))
+ error (0, errno, "warning: error reading %s",
+ CVSADM_CIPROG);
+ }
+ if (line != NULL)
+ free (line);
+ if (fclose (fp) < 0)
+ error (0, errno, "warning: cannot close %s", CVSADM_CIPROG);
+ }
+ else
+ {
+ if (! existence_error (errno))
+ error (0, errno, "warning: cannot open %s", CVSADM_CIPROG);
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * Get the log message for a dir and print a warm fuzzy
+ */
+/* ARGSUSED */
+static Dtype
+commit_direntproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ Node *p;
+ List *ulist;
+ char *real_repos;
+
+ /* find the update list for this dir */
+ p = findnode (mulist, update_dir);
+ if (p != NULL)
+ ulist = ((struct master_lists *) p->data)->ulist;
+ else
+ ulist = (List *) NULL;
+
+ /* skip the files as an optimization */
+ if (ulist == NULL || ulist->list->next == ulist->list)
+ return (R_SKIP_FILES);
+
+ /* print the warm fuzzy */
+ if (!quiet)
+ error (0, 0, "Committing %s", update_dir);
+
+ /* get commit message */
+ if (use_editor)
+ {
+ got_message = 1;
+ real_repos = Name_Repository (dir, update_dir);
+ do_editor (update_dir, &message, real_repos, ulist);
+ free (real_repos);
+ }
+ return (R_PROCESS);
+}
+
+/*
+ * Process the post-commit proc if necessary
+ */
+/* ARGSUSED */
+static int
+commit_dirleaveproc (dir, err, update_dir)
+ char *dir;
+ int err;
+ char *update_dir;
+{
+ /* update the per-directory tag info */
+ if (err == 0 && write_dirtag != NULL)
+ {
+ WriteTag ((char *) NULL, write_dirtag, (char *) NULL);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (update_dir, Name_Repository (dir, update_dir),
+ write_dirtag, (char *) NULL);
+#endif
+ }
+
+ return (err);
+}
+
+/*
+ * find the maximum major rev number in an entries file
+ */
+static int
+findmaxrev (p, closure)
+ Node *p;
+ void *closure;
+{
+ char *cp;
+ int thisrev;
+ Entnode *entdata;
+
+ entdata = (Entnode *) p->data;
+ cp = strchr (entdata->version, '.');
+ if (cp != NULL)
+ *cp = '\0';
+ thisrev = atoi (entdata->version);
+ if (cp != NULL)
+ *cp = '.';
+ if (thisrev > maxrev)
+ maxrev = thisrev;
+ return (0);
+}
+
+/*
+ * Actually remove a file by moving it to the attic
+ * XXX - if removing a ,v file that is a relative symbolic link to
+ * another ,v file, we probably should add a ".." component to the
+ * link to keep it relative after we move it into the attic.
+ */
+static int
+remove_file (file, repository, tag, message, entries, rcsnode)
+ char *file;
+ char *repository;
+ char *tag;
+ char *message;
+ List *entries;
+ RCSNode *rcsnode;
+{
+ mode_t omask;
+ int retcode;
+ char rcs[PATH_MAX];
+ char *tmp;
+
+ int branch;
+ int lockflag;
+ char *corev;
+ char *rev;
+ char *prev_rev;
+
+ corev = NULL;
+ rev = NULL;
+ prev_rev = NULL;
+
+ retcode = 0;
+
+ locate_rcs (file, repository, rcs);
+
+ branch = 0;
+ if (tag && !(branch = RCS_isbranch (rcsnode, tag)))
+ {
+ /* a symbolic tag is specified; just remove the tag from the file */
+ if ((retcode = RCS_deltag (rcs, tag, 1)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag `%s' from `%s'", tag, rcs);
+ return (1);
+ }
+ Scratch_Entry (entries, file);
+ return (0);
+ }
+
+ /* we are removing the file from either the head or a branch */
+ /* commit a new, dead revision. */
+
+ /* Print message indicating that file is going to be removed. */
+ (void) printf ("Removing %s;\n", file);
+
+ rev = NULL;
+ lockflag = RCS_FLAGS_LOCK;
+ if (branch)
+ {
+ char *branchname;
+
+ rev = RCS_whatbranch (rcsnode, tag);
+ if (rev == NULL)
+ {
+ error (0, 0, "cannot find branch \"%s\".", tag);
+ return (1);
+ }
+
+ if (rcsnode == NULL)
+ {
+ error (0, 0, "boy, I'm confused.");
+ return (1);
+ }
+ branchname = RCS_getbranch (rcsnode, rev, 1);
+ if (branchname == NULL)
+ {
+ /* no revision exists on this branch. use the previous
+ revision but do not lock. */
+ corev = RCS_gettag (rcsnode, tag, 1, 0);
+ prev_rev = xstrdup(rev);
+ lockflag = 0;
+ } else
+ {
+ corev = xstrdup (rev);
+ prev_rev = xstrdup(branchname);
+ free (branchname);
+ }
+
+ } else /* Not a branch */
+ {
+
+ /* Get current head revision of file. */
+ if (rcsnode == NULL)
+ {
+ error (0, 0, "could not find parsed rcsfile %s", file);
+ return (1);
+ }
+ prev_rev = RCS_head (rcsnode);
+ }
+
+ /* if removing without a tag or a branch, then make sure the default
+ branch is the trunk. */
+ if (!tag && !branch)
+ {
+ if (RCS_setbranch (rcs, NULL) != 0)
+ {
+ error (0, 0, "cannot change branch to default for %s",
+ rcs);
+ return (1);
+ }
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active) {
+ /* If this is the server, there will be a file sitting in the
+ temp directory which is the kludgy way in which server.c
+ tells time_stamp that the file is no longer around. Remove
+ it so we can create temp files with that name (ignore errors). */
+ unlink_file (file);
+ }
+#endif
+
+ /* check something out. Generally this is the head. If we have a
+ particular rev, then name it. except when creating a branch,
+ lock the rev we're checking out. */
+ retcode = RCS_checkout (rcs, "", rev ? corev : NULL, NULL, RUN_TTY,
+ lockflag, 1);
+ if (retcode != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to check out `%s'", rcs);
+ return (1);
+ }
+
+ if (corev != NULL)
+ free (corev);
+
+ retcode = RCS_checkin (rcs, NULL, message, rev, RCS_FLAGS_DEAD, 1);
+ if (retcode != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to commit dead revision for `%s'", rcs);
+ return (1);
+ }
+
+ if (rev != NULL)
+ free (rev);
+
+ if (!branch)
+ {
+ /* this was the head; really move it into the Attic */
+ tmp = xmalloc(strlen(repository) +
+ sizeof('/') +
+ sizeof(CVSATTIC) +
+ sizeof('/') +
+ strlen(file) +
+ sizeof(RCSEXT) + 1);
+ (void) sprintf (tmp, "%s/%s", repository, CVSATTIC);
+ omask = umask (cvsumask);
+ (void) CVS_MKDIR (tmp, 0777);
+ (void) umask (omask);
+ (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+
+ if (strcmp (rcs, tmp) != 0
+ && rename (rcs, tmp) == -1
+ && (isreadable (rcs) || !isreadable (tmp)))
+ {
+ free(tmp);
+ return (1);
+ }
+ free(tmp);
+ }
+
+ /* Print message that file was removed. */
+ (void) printf ("%s <-- %s\n", rcs, file);
+ (void) printf ("new revision: delete; ");
+ (void) printf ("previous revision: %s\n", prev_rev);
+ (void) printf ("done\n");
+ free(prev_rev);
+
+ Scratch_Entry (entries, file);
+ return (0);
+}
+
+/*
+ * Do the actual checkin for added files
+ */
+static int
+finaladd (file, rev, tag, options, update_dir, repository, entries)
+ char *file;
+ char *rev;
+ char *tag;
+ char *options;
+ char *update_dir;
+ char *repository;
+ List *entries;
+{
+ int ret;
+ char tmp[PATH_MAX];
+ char rcs[PATH_MAX];
+
+ locate_rcs (file, repository, rcs);
+ ret = Checkin ('A', file, update_dir, repository, rcs, rev, tag, options,
+ message, entries);
+ if (ret == 0)
+ {
+ (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
+ (void) unlink_file (tmp);
+ }
+ else
+ fixaddfile (file, repository);
+
+ (void) time (&last_register_time);
+
+ return (ret);
+}
+
+/*
+ * Unlock an rcs file
+ */
+static void
+unlockrcs (file, repository)
+ char *file;
+ char *repository;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ locate_rcs (file, repository, rcs);
+
+ if ((retcode = RCS_unlock (rcs, NULL, 0)) != 0)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not unlock %s", rcs);
+}
+
+/*
+ * remove a partially added file. if we can parse it, leave it alone.
+ */
+static void
+fixaddfile (file, repository)
+ char *file;
+ char *repository;
+{
+ RCSNode *rcsfile;
+ char rcs[PATH_MAX];
+ int save_really_quiet;
+
+ locate_rcs (file, repository, rcs);
+ save_really_quiet = really_quiet;
+ really_quiet = 1;
+ if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
+ (void) unlink_file (rcs);
+ else
+ freercsnode (&rcsfile);
+ really_quiet = save_really_quiet;
+}
+
+/*
+ * put the branch back on an rcs file
+ */
+static void
+fixbranch (file, repository, branch)
+ char *file;
+ char *repository;
+ char *branch;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ if (branch != NULL && branch[0] != '\0')
+ {
+ locate_rcs (file, repository, rcs);
+ if ((retcode = RCS_setbranch (rcs, branch)) != 0)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "cannot restore branch to %s for %s", branch, rcs);
+ }
+}
+
+/*
+ * do the initial part of a file add for the named file. if adding
+ * with a tag, put the file in the Attic and point the symbolic tag
+ * at the committed revision.
+ */
+
+static int
+checkaddfile (file, repository, tag, options, rcsnode)
+ char *file;
+ char *repository;
+ char *tag;
+ char *options;
+ RCSNode **rcsnode;
+{
+ char rcs[PATH_MAX];
+ char fname[PATH_MAX];
+ mode_t omask;
+ int retcode = 0;
+ int newfile = 0;
+
+ if (tag)
+ {
+ (void) sprintf(rcs, "%s/%s", repository, CVSATTIC);
+ omask = umask (cvsumask);
+ if (CVS_MKDIR (rcs, 0777) != 0 && errno != EEXIST)
+ error (1, errno, "cannot make directory `%s'", rcs);;
+ (void) umask (omask);
+ (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+ }
+ else
+ locate_rcs (file, repository, rcs);
+
+ if (isreadable(rcs))
+ {
+ /* file has existed in the past. Prepare to resurrect. */
+ char oldfile[PATH_MAX];
+ char *rev;
+ RCSNode *rcsfile;
+
+ if (tag == NULL)
+ {
+ /* we are adding on the trunk, so move the file out of the
+ Attic. */
+ strcpy (oldfile, rcs);
+ sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+
+ if (strcmp (oldfile, rcs) == 0
+ || rename (oldfile, rcs) != 0
+ || isreadable (oldfile)
+ || !isreadable (rcs))
+ {
+ error (0, 0, "failed to move `%s' out of the attic.",
+ file);
+ return (1);
+ }
+ }
+
+ if ((rcsfile = *rcsnode) == NULL)
+ {
+ error (0, 0, "could not find parsed rcsfile %s", file);
+ return (1);
+ }
+
+ rev = RCS_getversion (rcsfile, tag, NULL, 1, 0);
+ /* and lock it */
+ if (lock_RCS (file, rcs, rev, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ free (rev);
+ return (1);
+ }
+
+ free (rev);
+ } else {
+ /* this is the first time we have ever seen this file; create
+ an rcs file. */
+ run_setup ("%s%s -x,v/ -i", Rcsbin, RCS);
+
+ (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
+ /* If the file does not exist, no big deal. In particular, the
+ server does not (yet at least) create CVSEXT_LOG files. */
+ if (isfile (fname))
+ run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG);
+
+ /* Set RCS keyword expansion options. */
+ if (options && options[0] == '-' && options[1] == 'k')
+ run_arg (options);
+ run_arg (rcs);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not create %s", rcs);
+ return (1);
+ }
+ newfile = 1;
+ }
+
+ /* when adding a file for the first time, and using a tag, we need
+ to create a dead revision on the trunk. */
+ if (tag && newfile)
+ {
+ char *tmp;
+
+ /* move the new file out of the way. */
+ (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
+ rename_file (file, fname);
+ copy_file (DEVNULL, file);
+
+ tmp = xmalloc (strlen (file) + strlen (tag) + 80);
+ /* commit a dead revision. */
+ (void) sprintf (tmp, "file %s was initially added on branch %s.",
+ file, tag);
+ retcode = RCS_checkin (rcs, NULL, tmp, NULL,
+ RCS_FLAGS_DEAD | RCS_FLAGS_QUIET, 0);
+ free (tmp);
+ if (retcode != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not create initial dead revision %s", rcs);
+ return (1);
+ }
+
+ /* put the new file back where it was */
+ rename_file (fname, file);
+
+ /* and lock it once again. */
+ if (lock_RCS (file, rcs, NULL, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ return (1);
+ }
+ }
+
+ if (tag != NULL)
+ {
+ /* when adding with a tag, we need to stub a branch, if it
+ doesn't already exist. */
+ RCSNode *rcsfile;
+
+ rcsfile = RCS_parse (file, repository);
+ if (rcsfile == NULL)
+ {
+ error (0, 0, "could not read %s", rcs);
+ return (1);
+ }
+
+ if (!RCS_nodeisbranch (rcsfile, tag)) {
+ /* branch does not exist. Stub it. */
+ char *head;
+ char *magicrev;
+
+ head = RCS_getversion (rcsfile, NULL, NULL, 0, 0);
+ magicrev = RCS_magicrev (rcsfile, head);
+ if ((retcode = RCS_settag(rcs, tag, magicrev)) != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not stub branch %s for %s", tag, rcs);
+ return (1);
+ }
+
+ freercsnode (&rcsfile);
+
+ /* reparse the file, then add it to our list. */
+ rcsfile = RCS_parse (file, repository);
+ if (rcsfile == NULL)
+ {
+ error (0, 0, "could not reparse %s", rcs);
+ return (1);
+ }
+
+ free (head);
+ free (magicrev);
+ }
+ else
+ {
+ /* lock the branch. (stubbed branches need not be locked.) */
+ if (lock_RCS (file, rcs, NULL, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ return (1);
+ }
+ }
+
+ if (rcsnode)
+ freercsnode(rcsnode);
+ *rcsnode = rcsfile;
+ }
+
+ fileattr_newfile (file);
+
+ fix_rcs_modes (rcs, file);
+ return (0);
+}
+
+/*
+ * Lock the rcs file ``file''
+ */
+static int
+lockrcsfile (file, repository, rev)
+ char *file;
+ char *repository;
+ char *rev;
+{
+ char rcs[PATH_MAX];
+
+ locate_rcs (file, repository, rcs);
+ if (lock_RCS (file, rcs, rev, repository) != 0)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it
+ * couldn't. If the RCS file currently has a branch as the head, we must
+ * move the head back to the trunk before locking the file, and be sure to
+ * put the branch back as the head if there are any errors.
+ */
+static int
+lock_RCS (user, rcs, rev, repository)
+ char *user;
+ char *rcs;
+ char *rev;
+ char *repository;
+{
+ RCSNode *rcsfile;
+ char *branch = NULL;
+ int err = 0;
+
+ /*
+ * For a specified, numeric revision of the form "1" or "1.1", (or when
+ * no revision is specified ""), definitely move the branch to the trunk
+ * before locking the RCS file.
+ *
+ * The assumption is that if there is more than one revision on the trunk,
+ * the head points to the trunk, not a branch... and as such, it's not
+ * necessary to move the head in this case.
+ */
+ if (rev == NULL || (rev && isdigit (*rev) && numdots (rev) < 2))
+ {
+ if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
+ {
+ /* invalid rcs file? */
+ err = 1;
+ }
+ else
+ {
+ /* rcsfile is valid */
+ branch = xstrdup (rcsfile->branch);
+ freercsnode (&rcsfile);
+ if (branch != NULL)
+ {
+ if (RCS_setbranch (rcs, NULL) != 0)
+ {
+ error (0, 0, "cannot change branch to default for %s",
+ rcs);
+ if (branch)
+ free (branch);
+ return (1);
+ }
+ }
+ err = RCS_lock(rcs, NULL, 0);
+ }
+ }
+ else
+ {
+ (void) RCS_lock(rcs, rev, 1);
+ }
+
+ if (err == 0)
+ {
+ if (branch)
+ {
+ (void) strcpy (sbranch, branch);
+ free (branch);
+ }
+ else
+ sbranch[0] = '\0';
+ return (0);
+ }
+
+ /* try to restore the branch if we can on error */
+ if (branch != NULL)
+ fixbranch (user, repository, branch);
+
+ if (branch)
+ free (branch);
+ return (1);
+}
+
+/*
+ * Called when "add"ing files to the RCS respository, as it is necessary to
+ * preserve the file modes in the same fashion that RCS does. This would be
+ * automatic except that we are placing the RCS ,v file very far away from
+ * the user file, and I can't seem to convince RCS of the location of the
+ * user file. So we munge it here, after the ,v file has been successfully
+ * initialized with "rcs -i".
+ */
+static void
+fix_rcs_modes (rcs, user)
+ char *rcs;
+ char *user;
+{
+ struct stat sb;
+
+ if (stat (user, &sb) != -1)
+ (void) chmod (rcs, (int) sb.st_mode & ~0222);
+}
+
+/*
+ * free an UPDATE node's data (really nothing to do)
+ */
+void
+update_delproc (p)
+ Node *p;
+{
+ p->data = (char *) NULL;
+}
+
+/*
+ * Free the commit_info structure in p.
+ */
+static void
+ci_delproc (p)
+ Node *p;
+{
+ struct commit_info *ci;
+
+ ci = (struct commit_info *) p->data;
+ if (ci->rev)
+ free (ci->rev);
+ if (ci->tag)
+ free (ci->tag);
+ if (ci->options)
+ free (ci->options);
+ free (ci);
+}
+
+/*
+ * Free the commit_info structure in p.
+ */
+static void
+masterlist_delproc (p)
+ Node *p;
+{
+ struct master_lists *ml;
+
+ ml = (struct master_lists *) p->data;
+ dellist (&ml->ulist);
+ dellist (&ml->cilist);
+ free (ml);
+}
+
+/*
+ * Find an RCS file in the repository.
+ */
+static void
+locate_rcs (file, repository, rcs)
+ char *file;
+ char *repository;
+ char *rcs;
+{
+ (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+ if (!isreadable (rcs))
+ {
+ (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+ if (!isreadable (rcs))
+ (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+ }
+}
diff --git a/contrib/cvs/src/create_adm.c b/contrib/cvs/src/create_adm.c
new file mode 100644
index 0000000..fd7fd4d
--- /dev/null
+++ b/contrib/cvs/src/create_adm.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Create Administration.
+ *
+ * Creates a CVS administration directory based on the argument repository; the
+ * "Entries" file is prefilled from the "initrecord" argument.
+ */
+
+#include "cvs.h"
+
+/* update_dir includes dir as its last component. */
+
+void
+Create_Admin (dir, update_dir, repository, tag, date)
+ char *dir;
+ char *update_dir;
+ char *repository;
+ char *tag;
+ char *date;
+{
+ FILE *fout;
+ char *cp;
+ char tmp[PATH_MAX];
+
+#ifdef SERVER_SUPPORT
+ if (trace)
+ {
+ char wd[PATH_MAX];
+ getwd (wd);
+ fprintf (stderr, "%c-> Create_Admin (%s, %s, %s, %s, %s) in %s\n",
+ (server_active) ? 'S' : ' ',
+ dir, update_dir, repository, tag ? tag : "",
+ date ? date : "", wd);
+ }
+#endif
+
+ if (noexec)
+ return;
+
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM);
+ else
+ (void) strcpy (tmp, CVSADM);
+ if (isfile (tmp))
+ error (1, 0, "there is a version in %s already", update_dir);
+
+ make_directory (tmp);
+
+ /* record the current cvs root for later use */
+
+ Create_Root (dir, CVSroot);
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP);
+ else
+ (void) strcpy (tmp, CVSADM_REP);
+ fout = fopen (tmp, "w+");
+ if (fout == NULL)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot open %s", tmp);
+ else
+ error (1, errno, "cannot open %s/%s", update_dir, CVSADM_REP);
+ }
+ cp = repository;
+ strip_path (cp);
+
+#ifdef RELATIVE_REPOS
+ /*
+ * If the Repository file is to hold a relative path, try to strip off
+ * the leading CVSroot argument.
+ */
+ if (CVSroot != NULL)
+ {
+ char path[PATH_MAX];
+
+ (void) sprintf (path, "%s/", CVSroot);
+ if (strncmp (repository, path, strlen (path)) == 0)
+ cp = repository + strlen (path);
+ }
+#endif
+
+ if (fprintf (fout, "%s\n", cp) < 0)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "write to %s failed", tmp);
+ else
+ error (1, errno, "write to %s/%s failed", update_dir, CVSADM_REP);
+ }
+ if (fclose (fout) == EOF)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot close %s", tmp);
+ else
+ error (1, errno, "cannot close %s/%s", update_dir, CVSADM_REP);
+ }
+
+ /* now, do the Entries file */
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT);
+ else
+ (void) strcpy (tmp, CVSADM_ENT);
+ fout = fopen (tmp, "w+");
+ if (fout == NULL)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot open %s", tmp);
+ else
+ error (1, errno, "cannot open %s/%s", update_dir, CVSADM_ENT);
+ }
+ if (fclose (fout) == EOF)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot close %s", tmp);
+ else
+ error (1, errno, "cannot close %s/%s", update_dir, CVSADM_ENT);
+ }
+
+ /* Create a new CVS/Tag file */
+ WriteTag (dir, tag, date);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_set_sticky (update_dir, repository, tag, date);
+ server_template (update_dir, repository);
+ }
+
+ if (trace)
+ {
+ fprintf (stderr, "%c<- Create_Admin\n",
+ (server_active) ? 'S' : ' ');
+ }
+#endif
+
+}
diff --git a/contrib/cvs/src/cvs.h b/contrib/cvs/src/cvs.h
new file mode 100644
index 0000000..8e50f5d
--- /dev/null
+++ b/contrib/cvs/src/cvs.h
@@ -0,0 +1,690 @@
+/* $CVSid: @(#)cvs.h 1.86 94/10/22 $ */
+
+/*
+ * basic information used in all source files
+ *
+ */
+
+
+#include "config.h" /* this is stuff found via autoconf */
+#include "options.h" /* these are some larger questions which
+ can't easily be automatically checked
+ for */
+
+/* Changed from if __STDC__ to ifdef __STDC__ because of Sun's acc compiler */
+
+#ifdef __STDC__
+#define PTR void *
+#else
+#define PTR char *
+#endif
+
+/* Add prototype support. */
+#ifndef PROTO
+#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
+#define PROTO(ARGS) ARGS
+#else
+#define PROTO(ARGS) ()
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Under OS/2, <stdio.h> doesn't define popen()/pclose(). */
+#ifdef USE_OWN_POPEN
+#include "popen.h"
+#endif
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+extern void exit ();
+extern char *getenv();
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#ifdef SERVER_SUPPORT
+/* If the system doesn't provide strerror, it won't be declared in
+ string.h. */
+char *strerror ();
+#endif
+
+#include <fnmatch.h> /* This is supposed to be available on Posix systems */
+
+#include <ctype.h>
+#include <pwd.h>
+#include <signal.h>
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#else
+#ifndef errno
+extern int errno;
+#endif /* !errno */
+#endif /* HAVE_ERRNO_H */
+
+#include "system.h"
+
+#include "hash.h"
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+#include "server.h"
+#include "client.h"
+#endif
+
+#ifdef MY_NDBM
+#include "myndbm.h"
+#else
+#include <ndbm.h>
+#endif /* MY_NDBM */
+
+#include "regex.h"
+#include "getopt.h"
+#include "wait.h"
+
+#include "rcs.h"
+
+
+/* XXX - for now this is static */
+#ifndef PATH_MAX
+#ifdef MAXPATHLEN
+#define PATH_MAX MAXPATHLEN+2
+#else
+#define PATH_MAX 1024+2
+#endif
+#endif /* PATH_MAX */
+
+/* just in case this implementation does not define this */
+#ifndef L_tmpnam
+#define L_tmpnam 50
+#endif
+
+
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Definitions for the CVS Administrative directory and the files it contains.
+ * Here as #define's to make changing the names a simple task.
+ */
+
+#ifdef USE_VMS_FILENAMES
+#define CVSADM "CVS"
+#define CVSADM_ENT "CVS/Entries."
+#define CVSADM_ENTBAK "CVS/Entries.Backup"
+#define CVSADM_ENTLOG "CVS/Entries.Log"
+#define CVSADM_ENTSTAT "CVS/Entries.Static"
+#define CVSADM_REP "CVS/Repository."
+#define CVSADM_ROOT "CVS/Root."
+#define CVSADM_CIPROG "CVS/Checkin.prog"
+#define CVSADM_UPROG "CVS/Update.prog"
+#define CVSADM_TAG "CVS/Tag."
+#define CVSADM_NOTIFY "CVS/Notify."
+#define CVSADM_NOTIFYTMP "CVS/Notify.tmp"
+#define CVSADM_BASE "CVS/Base"
+#define CVSADM_TEMPLATE "CVS/Template."
+#else /* USE_VMS_FILENAMES */
+#define CVSADM "CVS"
+#define CVSADM_ENT "CVS/Entries"
+#define CVSADM_ENTBAK "CVS/Entries.Backup"
+#define CVSADM_ENTLOG "CVS/Entries.Log"
+#define CVSADM_ENTSTAT "CVS/Entries.Static"
+#define CVSADM_REP "CVS/Repository"
+#define CVSADM_ROOT "CVS/Root"
+#define CVSADM_CIPROG "CVS/Checkin.prog"
+#define CVSADM_UPROG "CVS/Update.prog"
+#define CVSADM_TAG "CVS/Tag"
+#define CVSADM_NOTIFY "CVS/Notify"
+#define CVSADM_NOTIFYTMP "CVS/Notify.tmp"
+/* A directory in which we store base versions of files we currently are
+ editing with "cvs edit". */
+#define CVSADM_BASE "CVS/Base"
+/* File which contains the template for use in log messages. */
+#define CVSADM_TEMPLATE "CVS/Template"
+#endif /* USE_VMS_FILENAMES */
+
+/* This is the special directory which we use to store various extra
+ per-directory information in the repository. It must be the same as
+ CVSADM to avoid creating a new reserved directory name which users cannot
+ use, but is a separate #define because if anyone changes it (which I don't
+ recommend), one needs to deal with old, unconverted, repositories.
+
+ See fileattr.h for details about file attributes, the only thing stored
+ in CVSREP currently. */
+#define CVSREP "CVS"
+
+/*
+ * Definitions for the CVSROOT Administrative directory and the files it
+ * contains. This directory is created as a sub-directory of the $CVSROOT
+ * environment variable, and holds global administration information for the
+ * entire source repository beginning at $CVSROOT.
+ */
+#define CVSROOTADM "CVSROOT"
+#define CVSROOTADM_MODULES "modules"
+#define CVSROOTADM_LOGINFO "loginfo"
+#define CVSROOTADM_RCSINFO "rcsinfo"
+#define CVSROOTADM_COMMITINFO "commitinfo"
+#define CVSROOTADM_TAGINFO "taginfo"
+#define CVSROOTADM_EDITINFO "editinfo"
+#define CVSROOTADM_HISTORY "history"
+#define CVSROOTADM_VALTAGS "val-tags"
+#define CVSROOTADM_IGNORE "cvsignore"
+#define CVSROOTADM_CHECKOUTLIST "checkoutlist"
+#define CVSROOTADM_WRAPPER "cvswrappers"
+#define CVSROOTADM_NOTIFY "notify"
+#define CVSROOTADM_USERS "users"
+
+#define CVSNULLREPOS "Emptydir" /* an empty directory */
+
+/* Other CVS file names */
+
+/* Files go in the attic if the head main branch revision is dead,
+ otherwise they go in the regular repository directories. The whole
+ concept of having an attic is sort of a relic from before death
+ support but on the other hand, it probably does help the speed of
+ some operations (such as main branch checkouts and updates). */
+#define CVSATTIC "Attic"
+
+#define CVSLCK "#cvs.lock"
+#define CVSRFL "#cvs.rfl"
+#define CVSWFL "#cvs.wfl"
+#define CVSRFLPAT "#cvs.rfl.*" /* wildcard expr to match read locks */
+#define CVSEXT_LOG ",t"
+#define CVSPREFIX ",,"
+#define CVSDOTIGNORE ".cvsignore"
+#define CVSDOTWRAPPER ".cvswrappers"
+
+/* miscellaneous CVS defines */
+#define CVSEDITPREFIX "CVS: "
+#define CVSLCKAGE (60*60) /* 1-hour old lock files cleaned up */
+#define CVSLCKSLEEP 30 /* wait 30 seconds before retrying */
+#define CVSBRANCH "1.1.1" /* RCS branch used for vendor srcs */
+
+#ifdef USE_VMS_FILENAMES
+#define BAKPREFIX "_$"
+#define DEVNULL "NLA0:"
+#else /* USE_VMS_FILENAMES */
+#define BAKPREFIX ".#" /* when rcsmerge'ing */
+#ifndef DEVNULL
+#define DEVNULL "/dev/null"
+#endif
+#endif /* USE_VMS_FILENAMES */
+
+#define FALSE 0
+#define TRUE 1
+
+/*
+ * Special tags. -rHEAD refers to the head of an RCS file, regardless of any
+ * sticky tags. -rBASE refers to the current revision the user has checked
+ * out This mimics the behaviour of RCS.
+ */
+#define TAG_HEAD "HEAD"
+#define TAG_BASE "BASE"
+
+/* Environment variable used by CVS */
+#define CVSREAD_ENV "CVSREAD" /* make files read-only */
+#define CVSREAD_DFLT FALSE /* writable files by default */
+
+#define RCSBIN_ENV "RCSBIN" /* RCS binary directory */
+/* #define RCSBIN_DFLT Set by config.h */
+
+#define EDITOR1_ENV "CVSEDITOR" /* which editor to use */
+#define EDITOR2_ENV "VISUAL" /* which editor to use */
+#define EDITOR3_ENV "EDITOR" /* which editor to use */
+/* #define EDITOR_DFLT Set by config.h */
+
+#define CVSROOT_ENV "CVSROOT" /* source directory root */
+#define CVSROOT_DFLT NULL /* No dflt; must set for checkout */
+
+#define IGNORE_ENV "CVSIGNORE" /* More files to ignore */
+#define WRAPPER_ENV "CVSWRAPPERS" /* name of the wrapper file */
+
+#define CVSUMASK_ENV "CVSUMASK" /* Effective umask for repository */
+/* #define CVSUMASK_DFLT Set by config.h */
+
+/*
+ * If the beginning of the Repository matches the following string, strip it
+ * so that the output to the logfile does not contain a full pathname.
+ *
+ * If the CVSROOT environment variable is set, it overrides this define.
+ */
+#define REPOS_STRIP "/master/"
+
+/*
+ * The maximum number of files per each CVS directory. This is mainly for
+ * sizing arrays statically rather than dynamically. 3000 seems plenty for
+ * now.
+ */
+#define MAXFILEPERDIR 3000
+#define MAXLINELEN 5000 /* max input line from a file */
+#define MAXPROGLEN 30000 /* max program length to system() */
+#define MAXLISTLEN 40000 /* For [A-Z]list holders */
+#define MAXDATELEN 50 /* max length for a date */
+
+/* structure of a entry record */
+struct entnode
+{
+ char *user;
+ char *version;
+ char *timestamp;
+ char *options;
+ char *tag;
+ char *date;
+ char *conflict;
+};
+typedef struct entnode Entnode;
+
+/* The type of request that is being done in do_module() */
+enum mtype
+{
+ CHECKOUT, TAG, PATCH, EXPORT
+};
+
+/*
+ * defines for Classify_File() to determine the current state of a file.
+ * These are also used as types in the data field for the list we make for
+ * Update_Logfile in commit, import, and add.
+ */
+enum classify_type
+{
+ T_UNKNOWN = 1, /* no old-style analog existed */
+ T_CONFLICT, /* C (conflict) list */
+ T_NEEDS_MERGE, /* G (needs merging) list */
+ T_MODIFIED, /* M (needs checked in) list */
+ T_CHECKOUT, /* O (needs checkout) list */
+ T_ADDED, /* A (added file) list */
+ T_REMOVED, /* R (removed file) list */
+ T_REMOVE_ENTRY, /* W (removed entry) list */
+ T_UPTODATE, /* File is up-to-date */
+#ifdef SERVER_SUPPORT
+ T_PATCH, /* P Like C, but can patch */
+#endif
+ T_TITLE /* title for node type */
+};
+typedef enum classify_type Ctype;
+
+/*
+ * a struct vers_ts contains all the information about a file including the
+ * user and rcs file names, and the version checked out and the head.
+ *
+ * this is usually obtained from a call to Version_TS which takes a tag argument
+ * for the RCS file if desired
+ */
+struct vers_ts
+{
+ /* rcs version user file derives from, from CVS/Entries.
+ * it can have the following special values:
+ * empty = no user file
+ * 0 = user file is new
+ * -vers = user file to be removed. */
+ char *vn_user;
+
+ /* Numeric revision number corresponding to ->vn_tag (->vn_tag
+ will often be symbolic). */
+ char *vn_rcs;
+ /* If ->tag corresponds to a tag which really exists in this file,
+ this is just a copy of ->tag. If not, this is either NULL or
+ the head revision. (Or something like that, see RCS_getversion
+ and friends). */
+ char *vn_tag;
+
+ /* This is the timestamp from stating the file in the working directory.
+ It is NULL if there is no file in the working directory. */
+ char *ts_user;
+ /* Timestamp from CVS/Entries. For the server, ts_user and ts_rcs
+ are computed in a slightly different way, but the fact remains that
+ if they are equal the file in the working directory is unmodified
+ and if they differ it is modified. */
+ char *ts_rcs;
+
+ /* Options from CVS/Entries (keyword expansion). */
+ char *options;
+
+ /* If non-NULL, there was a conflict (or merely a merge? See merge_file)
+ and the time stamp in this field is the time stamp of the working
+ directory file which was created with the conflict markers in it.
+ This is from CVS/Entries. */
+ char *ts_conflict;
+
+ /* Tag specified on the command line, or if none, tag stored in
+ CVS/Entries. */
+ char *tag;
+ /* Date specified on the command line, or if none, date stored in
+ CVS/Entries. */
+ char *date;
+
+ /* Pointer to entries file node */
+ Entnode *entdata;
+
+ /* Pointer to parsed src file info */
+ RCSNode *srcfile;
+};
+typedef struct vers_ts Vers_TS;
+
+/*
+ * structure used for list-private storage by Entries_Open() and
+ * Version_TS().
+ */
+struct stickydirtag
+{
+ int aflag;
+ char *tag;
+ char *date;
+ char *options;
+};
+
+/* Flags for find_{names,dirs} routines */
+#define W_LOCAL 0x01 /* look for files locally */
+#define W_REPOS 0x02 /* look for files in the repository */
+#define W_ATTIC 0x04 /* look for files in the attic */
+
+/* Flags for return values of direnter procs for the recursion processor */
+enum direnter_type
+{
+ R_PROCESS = 1, /* process files and maybe dirs */
+ R_SKIP_FILES, /* don't process files in this dir */
+ R_SKIP_DIRS, /* don't process sub-dirs */
+ R_SKIP_ALL /* don't process files or dirs */
+};
+typedef enum direnter_type Dtype;
+
+extern char *program_name, *program_path, *command_name;
+extern char *Rcsbin, *Editor, *CVSroot;
+extern char *CVSADM_Root;
+extern int cvsadmin_root;
+extern char *CurDir;
+extern int really_quiet, quiet;
+extern int use_editor;
+extern int cvswrite;
+extern mode_t cvsumask;
+
+extern int trace; /* Show all commands */
+extern int noexec; /* Don't modify disk anywhere */
+extern int logoff; /* Don't write history entry */
+
+extern char hostname[];
+
+/* Externs that are included directly in the CVS sources */
+
+int RCS_settag PROTO((const char *, const char *, const char *));
+int RCS_deltag PROTO((const char *, const char *, int));
+int RCS_setbranch PROTO((const char *, const char *));
+int RCS_lock PROTO((const char *, const char *, int));
+int RCS_unlock PROTO((const char *, const char *, int));
+int RCS_merge PROTO((const char *, const char *, const char *, const char *));
+int RCS_checkout PROTO ((char *rcsfile, char *workfile, char *tag,
+ char *options,
+ char *sout, int flags, int noerr));
+/* Flags used by RCS_* functions. See the description of the individual
+ functions for which flags mean what for each function. */
+#define RCS_FLAGS_LOCK 1
+#define RCS_FLAGS_FORCE 2
+#define RCS_FLAGS_DEAD 4
+#define RCS_FLAGS_QUIET 8
+#define RCS_FLAGS_MODTIME 16
+int RCS_checkin PROTO ((char *rcsfile, char *workfile, char *message,
+ char *rev, int flags, int noerr));
+
+
+
+#include "error.h"
+
+DBM *open_module PROTO((void));
+FILE *open_file PROTO((const char *, const char *));
+List *Find_Directories PROTO((char *repository, int which));
+void Entries_Close PROTO((List *entries));
+List *Entries_Open PROTO((int aflag));
+char *Make_Date PROTO((char *rawdate));
+char *Name_Repository PROTO((char *dir, char *update_dir));
+char *Name_Root PROTO((char *dir, char *update_dir));
+void Create_Root PROTO((char *dir, char *rootdir));
+int same_directories PROTO((char *dir1, char *dir2));
+char *Short_Repository PROTO((char *repository));
+char *gca PROTO((char *rev1, char *rev2));
+char *getcaller PROTO((void));
+char *time_stamp PROTO((char *file));
+char *xmalloc PROTO((size_t bytes));
+void *xrealloc PROTO((void *ptr, size_t bytes));
+char *xstrdup PROTO((const char *str));
+void strip_trailing_newlines PROTO((char *str));
+int No_Difference PROTO((char *file, Vers_TS * vers, List * entries,
+ char *repository, char *update_dir));
+typedef int (*CALLPROC) PROTO((char *repository, char *value));
+int Parse_Info PROTO((char *infofile, char *repository, CALLPROC callproc, int all));
+int Reader_Lock PROTO((char *xrepository));
+typedef RETSIGTYPE (*SIGCLEANUPPROC) PROTO(());
+int SIG_register PROTO((int sig, SIGCLEANUPPROC sigcleanup));
+int Writer_Lock PROTO((List * list));
+int isdir PROTO((const char *file));
+int isfile PROTO((const char *file));
+int islink PROTO((const char *file));
+int isreadable PROTO((const char *file));
+int iswritable PROTO((const char *file));
+int isaccessible PROTO((const char *file, const int mode));
+int isabsolute PROTO((const char *filename));
+char *last_component PROTO((char *path));
+char *get_homedir PROTO ((void));
+
+int numdots PROTO((const char *s));
+int unlink_file PROTO((const char *f));
+int link_file PROTO ((const char *from, const char *to));
+int unlink_file_dir PROTO((const char *f));
+int update PROTO((int argc, char *argv[]));
+int xcmp PROTO((const char *file1, const char *file2));
+int yesno PROTO((void));
+void *valloc PROTO((size_t bytes));
+time_t get_date PROTO((char *date, struct timeb *now));
+void Create_Admin PROTO((char *dir, char *update_dir,
+ char *repository, char *tag, char *date));
+
+void Lock_Cleanup PROTO((void));
+
+/* Writelock an entire subtree, well the part specified by ARGC, ARGV, LOCAL,
+ and AFLAG, anyway. */
+void lock_tree_for_write PROTO ((int argc, char **argv, int local, int aflag));
+
+/* Remove locks set by lock_tree_for_write. Currently removes readlocks
+ too. */
+void lock_tree_cleanup PROTO ((void));
+
+void ParseTag PROTO((char **tagp, char **datep));
+void Scratch_Entry PROTO((List * list, char *fname));
+void WriteTag PROTO((char *dir, char *tag, char *date));
+void cat_module PROTO((int status));
+void check_entries PROTO((char *dir));
+void close_module PROTO((DBM * db));
+void copy_file PROTO((const char *from, const char *to));
+void (*error_set_cleanup PROTO((void (*) (void)))) PROTO ((void));
+void fperror PROTO((FILE * fp, int status, int errnum, char *message,...));
+void free_names PROTO((int *pargc, char *argv[]));
+void freevers_ts PROTO((Vers_TS ** versp));
+
+extern int ign_name PROTO ((char *name));
+void ign_add PROTO((char *ign, int hold));
+void ign_add_file PROTO((char *file, int hold));
+void ign_setup PROTO((void));
+void ign_dir_add PROTO((char *name));
+int ignore_directory PROTO((char *name));
+typedef void (*Ignore_proc) PROTO ((char *, char *));
+extern void ignore_files PROTO ((List *, char *, Ignore_proc));
+extern int ign_inhibit_server;
+extern int ign_case;
+
+#include "update.h"
+
+void line2argv PROTO((int *pargc, char *argv[], char *line));
+void make_directories PROTO((const char *name));
+void make_directory PROTO((const char *name));
+void rename_file PROTO((const char *from, const char *to));
+/* Expand wildcards in each element of (ARGC,ARGV). This is according to the
+ files which exist in the current directory, and accordingly to OS-specific
+ conventions regarding wildcard syntax. It might be desirable to change the
+ former in the future (e.g. "cvs status *.h" including files which don't exist
+ in the working directory). The result is placed in *PARGC and *PARGV;
+ the *PARGV array itself and all the strings it contains are newly
+ malloc'd. It is OK to call it with PARGC == &ARGC or PARGV == &ARGV. */
+extern void expand_wild PROTO ((int argc, char **argv,
+ int *pargc, char ***pargv));
+
+void strip_path PROTO((char *path));
+void strip_trailing_slashes PROTO((char *path));
+void update_delproc PROTO((Node * p));
+void usage PROTO((const char *const *cpp));
+void xchmod PROTO((char *fname, int writable));
+char *xgetwd PROTO((void));
+int Checkin PROTO((int type, char *file, char *update_dir,
+ char *repository, char *rcs, char *rev,
+ char *tag, char *options, char *message, List *entries));
+Ctype Classify_File PROTO((char *file, char *tag, char *date, char *options,
+ int force_tag_match, int aflag, char *repository,
+ List *entries, RCSNode *rcsnode, Vers_TS **versp,
+ char *update_dir, int pipeout));
+List *Find_Names PROTO((char *repository, int which, int aflag,
+ List ** optentries));
+void Register PROTO((List * list, char *fname, char *vn, char *ts,
+ char *options, char *tag, char *date, char *ts_conflict));
+void Update_Logfile PROTO((char *repository, char *xmessage, char *xrevision,
+ FILE * xlogfp, List * xchanges));
+Vers_TS *Version_TS PROTO((char *repository, char *options, char *tag,
+ char *date, char *user, int force_tag_match,
+ int set_time, List * entries, RCSNode * rcs));
+void do_editor PROTO((char *dir, char **messagep,
+ char *repository, List * changes));
+
+typedef int (*CALLBACKPROC) PROTO((int *pargc, char *argv[], char *where,
+ char *mwhere, char *mfile, int horten, int local_specified,
+ char *omodule, char *msg));
+
+/* This is the structure that the recursion processor passes to the
+ fileproc to tell it about a particular file. */
+struct file_info
+{
+ /* Name of the file, without any directory component. */
+ char *file;
+
+ /* Name of the directory we are in, relative to the directory in
+ which this command was issued. We have cd'd to this directory
+ (either in the working directory or in the repository, depending
+ on which sort of recursion we are doing). If we are in the directory
+ in which the command was issued, this is "". */
+ char *update_dir;
+
+ /* update_dir and file put together, with a slash between them as
+ necessary. This is the proper way to refer to the file in user
+ messages. */
+ char *fullname;
+
+ /* Name of the directory corresponding to the repository which contains
+ this file. */
+ char *repository;
+
+ /* The pre-parsed entries for this directory. */
+ List *entries;
+
+ RCSNode *rcs;
+};
+
+typedef int (*FILEPROC) PROTO((struct file_info *finfo));
+typedef int (*FILESDONEPROC) PROTO((int err, char *repository, char *update_dir));
+typedef Dtype (*DIRENTPROC) PROTO((char *dir, char *repos, char *update_dir));
+typedef int (*DIRLEAVEPROC) PROTO((char *dir, int err, char *update_dir));
+
+extern int mkmodules PROTO ((char *dir));
+extern int init PROTO ((int argc, char **argv));
+
+int do_module PROTO((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));
+int do_recursion PROTO((FILEPROC xfileproc, FILESDONEPROC xfilesdoneproc,
+ DIRENTPROC xdirentproc, DIRLEAVEPROC xdirleaveproc,
+ Dtype xflags, int xwhich, int xaflag, int xreadlock,
+ int xdosrcs));
+void history_write PROTO((int type, char *update_dir, char *revs, char *name,
+ char *repository));
+int start_recursion PROTO((FILEPROC fileproc, FILESDONEPROC filesdoneproc,
+ DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
+ int argc, char *argv[], int local, int which,
+ int aflag, int readlock, char *update_preload,
+ int dosrcs, int wd_is_repos));
+void SIG_beginCrSect PROTO((void));
+void SIG_endCrSect PROTO((void));
+void read_cvsrc PROTO((int *argc, char ***argv, char *cmdname));
+
+char *make_message_rcslegal PROTO((char *message));
+
+/* flags for run_exec(), the fast system() for CVS */
+#define RUN_NORMAL 0x0000 /* no special behaviour */
+#define RUN_COMBINED 0x0001 /* stdout is duped to stderr */
+#define RUN_REALLY 0x0002 /* do the exec, even if noexec is on */
+#define RUN_STDOUT_APPEND 0x0004 /* append to stdout, don't truncate */
+#define RUN_STDERR_APPEND 0x0008 /* append to stderr, don't truncate */
+#define RUN_SIGIGNORE 0x0010 /* ignore interrupts for command */
+#define RUN_TTY (char *)0 /* for the benefit of lint */
+
+void run_arg PROTO((const char *s));
+void run_print PROTO((FILE * fp));
+#ifdef HAVE_VPRINTF
+void run_setup PROTO((const char *fmt,...));
+void run_args PROTO((const char *fmt,...));
+#else
+void run_setup ();
+void run_args ();
+#endif
+int run_exec PROTO((char *stin, char *stout, char *sterr, int flags));
+
+/* other similar-minded stuff from run.c. */
+FILE *run_popen PROTO((const char *, const char *));
+int piped_child PROTO((char **, int *, int *));
+void close_on_exec PROTO((int));
+int filter_stream_through_program PROTO((int, int, char **, pid_t *));
+
+pid_t waitpid PROTO((pid_t, int *, int));
+
+/* Wrappers. */
+
+typedef enum { WRAP_MERGE, WRAP_COPY } WrapMergeMethod;
+typedef enum { WRAP_TOCVS, WRAP_FROMCVS, WRAP_CONFLICT } WrapMergeHas;
+
+void wrap_setup PROTO((void));
+int wrap_name_has PROTO((const char *name,WrapMergeHas has));
+char *wrap_tocvs_process_file PROTO((const char *fileName));
+int wrap_merge_is_copy PROTO((const char *fileName));
+char *wrap_fromcvs_process_file PROTO((const char *fileName));
+void wrap_add_file PROTO((const char *file,int temp));
+void wrap_add PROTO((char *line,int temp));
+
+/* Pathname expansion */
+char *expand_path PROTO((char *name, char *file, int line));
+
+/* User variables. */
+extern List *variable_list;
+
+extern void variable_set PROTO ((char *nameval));
+
+int watch PROTO ((int argc, char **argv));
+int edit PROTO ((int argc, char **argv));
+int unedit PROTO ((int argc, char **argv));
+int editors PROTO ((int argc, char **argv));
+int watchers PROTO ((int argc, char **argv));
+extern int annotate PROTO ((int argc, char **argv));
+
+#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT)
+char *scramble PROTO ((char *str));
+char *descramble PROTO ((char *str));
+#endif /* AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT */
+
+extern void tag_check_valid PROTO ((char *, int, char **, int, int, char *));
+
+extern void cvs_output PROTO ((char *, size_t));
+extern void cvs_outerr PROTO ((char *, size_t));
diff --git a/contrib/cvs/src/cvsbug.sh b/contrib/cvs/src/cvsbug.sh
new file mode 100755
index 0000000..ab26cfc
--- /dev/null
+++ b/contrib/cvs/src/cvsbug.sh
@@ -0,0 +1,528 @@
+#! /bin/sh
+# Submit a problem report to a GNATS site.
+# Copyright (C) 1993 Free Software Foundation, Inc.
+# Contributed by Brendan Kehoe (brendan@cygnus.com), based on a
+# version written by Heinz G. Seidl (hgs@ide.com).
+#
+# This file is part of GNU GNATS.
+# Modified by Berliner for CVS.
+#
+#ident "@(#)cvs/src:$Name: $:$Id: cvsbug.sh,v 1.10 1995/11/15 00:18:00 woods Exp $"
+#
+# GNU GNATS 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.
+#
+# GNU GNATS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU GNATS; see the file COPYING. If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# The version of this send-pr.
+VERSION=3.2
+
+# The submitter-id for your site.
+SUBMITTER=net
+
+## # Where the GNATS directory lives, if at all.
+## [ -z "$GNATS_ROOT" ] &&
+## GNATS_ROOT=/usr/local/lib/gnats/gnats-db
+
+# The default mail address for PR submissions.
+GNATS_ADDR=bug-cvs@prep.ai.mit.edu
+
+## # Where the gnats category tree lives.
+## DATADIR=/usr/local/lib
+
+## # If we've been moved around, try using GCC_EXEC_PREFIX.
+## [ ! -d $DATADIR/gnats -a -d "$GCC_EXEC_PREFIX" ] && DATADIR=${GCC_EXEC_PREFIX}..
+
+# The default release for this host.
+DEFAULT_RELEASE="xVERSIONx"
+
+# The default organization.
+DEFAULT_ORGANIZATION="net"
+
+## # The default site to look for.
+## GNATS_SITE=unknown
+
+## # Newer config information?
+## [ -f ${GNATS_ROOT}/gnats-adm/config ] && . ${GNATS_ROOT}/gnats-adm/config
+
+# What mailer to use. This must come after the config file, since it is
+# host-dependent.
+if [ -f /usr/sbin/sendmail ]; then
+ MAIL_AGENT="/usr/sbin/sendmail -oi -t"
+else
+ MAIL_AGENT="/usr/lib/sendmail -oi -t"
+fi
+MAILER=`echo $MAIL_AGENT | sed -e 's, .*,,'`
+if [ ! -f "$MAILER" ] ; then
+ echo "$COMMAND: Cannot file mail program \"$MAILER\"."
+ echo "$COMMAND: Please fix the MAIL_AGENT entry in the $COMMAND file."
+ exit 1
+fi
+
+if test "`echo -n foo`" = foo ; then
+ ECHON=bsd
+elif test "`echo 'foo\c'`" = foo ; then
+ ECHON=sysv
+else
+ ECHON=none
+fi
+
+if [ $ECHON = bsd ] ; then
+ ECHON1="echo -n"
+ ECHON2=
+elif [ $ECHON = sysv ] ; then
+ ECHON1=echo
+ ECHON2='\c'
+else
+ ECHON1=echo
+ ECHON2=
+fi
+
+#
+
+[ -z "$TMPDIR" ] && TMPDIR=/tmp
+
+TEMP=$TMPDIR/p$$
+BAD=$TMPDIR/pbad$$
+REF=$TMPDIR/pf$$
+
+if [ -z "$LOGNAME" -a -n "$USER" ]; then
+ LOGNAME=$USER
+fi
+
+FROM="$LOGNAME"
+REPLY_TO="$LOGNAME"
+
+# Find out the name of the originator of this PR.
+if [ -n "$NAME" ]; then
+ ORIGINATOR="$NAME"
+elif [ -f $HOME/.fullname ]; then
+ ORIGINATOR="`sed -e '1q' $HOME/.fullname`"
+elif [ -f /bin/domainname ]; then
+ if [ "`/bin/domainname`" != "" -a -f /usr/bin/ypcat ]; then
+ # Must use temp file due to incompatibilities in quoting behavior
+ # and to protect shell metacharacters in the expansion of $LOGNAME
+ /usr/bin/ypcat passwd 2>/dev/null | cat - /etc/passwd | grep "^$LOGNAME:" |
+ cut -f5 -d':' | sed -e 's/,.*//' > $TEMP
+ ORIGINATOR="`cat $TEMP`"
+ rm -f $TEMP
+ fi
+fi
+
+if [ "$ORIGINATOR" = "" ]; then
+ grep "^$LOGNAME:" /etc/passwd | cut -f5 -d':' | sed -e 's/,.*//' > $TEMP
+ ORIGINATOR="`cat $TEMP`"
+ rm -f $TEMP
+fi
+
+if [ -n "$ORGANIZATION" ]; then
+ if [ -f "$ORGANIZATION" ]; then
+ ORGANIZATION="`cat $ORGANIZATION`"
+ fi
+else
+ if [ -n "$DEFAULT_ORGANIZATION" ]; then
+ ORGANIZATION="$DEFAULT_ORGANIZATION"
+ elif [ -f $HOME/.organization ]; then
+ ORGANIZATION="`cat $HOME/.organization`"
+ elif [ -f $HOME/.signature ]; then
+ ORGANIZATION="`cat $HOME/.signature`"
+ fi
+fi
+
+# If they don't have a preferred editor set, then use
+if [ -z "$VISUAL" ]; then
+ if [ -z "$EDITOR" ]; then
+ EDIT=vi
+ else
+ EDIT="$EDITOR"
+ fi
+else
+ EDIT="$VISUAL"
+fi
+
+# Find out some information.
+SYSTEM=`( [ -f /bin/uname ] && /bin/uname -a ) || \
+ ( [ -f /usr/bin/uname ] && /usr/bin/uname -a ) || echo ""`
+ARCH=`[ -f /bin/arch ] && /bin/arch`
+MACHINE=`[ -f /bin/machine ] && /bin/machine`
+
+COMMAND=`echo $0 | sed -e 's,.*/,,'`
+## USAGE="Usage: $COMMAND [-PVL] [-t address] [-f filename] [--request-id]
+USAGE="Usage: $COMMAND [-PVL]
+[--version]"
+REMOVE=
+BATCH=
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -r) ;; # Ignore for backward compat.
+## -t | --to) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi
+## shift ; GNATS_ADDR="$1"
+## EXPLICIT_GNATS_ADDR=true
+## ;;
+## -f | --file) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi
+## shift ; IN_FILE="$1"
+## if [ "$IN_FILE" != "-" -a ! -r "$IN_FILE" ]; then
+## echo "$COMMAND: cannot read $IN_FILE"
+## exit 1
+## fi
+## ;;
+ -b | --batch) BATCH=true ;;
+ -p | -P | --print) PRINT=true ;;
+ -L | --list) FORMAT=norm ;;
+ -l | -CL | --lisp) FORMAT=lisp ;;
+## --request-id) REQUEST_ID=true ;;
+ -h | --help) echo "$USAGE"; exit 0 ;;
+ -V | --version) echo "$VERSION"; exit 0 ;;
+ -*) echo "$USAGE" ; exit 1 ;;
+ *) echo "$USAGE" ; exit 1
+## if [ -z "$USER_GNATS_SITE" ]; then
+## if [ ! -r "$DATADIR/gnats/$1" ]; then
+## echo "$COMMAND: the GNATS site $1 does not have a categories list."
+## exit 1
+## else
+## # The site name is the alias they'll have to have created.
+## USER_GNATS_SITE=$1
+## fi
+## else
+## echo "$USAGE" ; exit 1
+## fi
+ ;;
+ esac
+ shift
+done
+
+if [ -n "$USER_GNATS_SITE" ]; then
+ GNATS_SITE=$USER_GNATS_SITE
+ GNATS_ADDR=$USER_GNATS_SITE-gnats
+fi
+
+if [ "$SUBMITTER" = "unknown" -a -z "$REQUEST_ID" -a -z "$IN_FILE" ]; then
+ cat << '__EOF__'
+It seems that send-pr is not installed with your unique submitter-id.
+You need to run
+
+ install-sid YOUR-SID
+
+where YOUR-SID is the identification code you received with `send-pr'.
+`send-pr' will automatically insert this value into the template field
+`>Submitter-Id'. If you've downloaded `send-pr' from the Net, use `net'
+for this value. If you do not know your id, run `send-pr --request-id' to
+get one from your support site.
+__EOF__
+ exit 1
+fi
+
+## if [ -r "$DATADIR/gnats/$GNATS_SITE" ]; then
+## CATEGORIES=`grep -v '^#' $DATADIR/gnats/$GNATS_SITE | sort`
+## else
+## echo "$COMMAND: could not read $DATADIR/gnats/$GNATS_SITE for categories list."
+## exit 1
+## fi
+CATEGORIES="contrib cvs doc pcl-cvs portability"
+
+if [ -z "$CATEGORIES" ]; then
+ echo "$COMMAND: the categories list for $GNATS_SITE was empty!"
+ exit 1
+fi
+
+case "$FORMAT" in
+ lisp) echo "$CATEGORIES" | \
+ awk 'BEGIN {printf "( "} {printf "(\"%s\") ",$0} END {printf ")\n"}'
+ exit 0
+ ;;
+ norm) l=`echo "$CATEGORIES" | \
+ awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } }
+ END {print max + 1;}'`
+ c=`expr 70 / $l`
+ if [ $c -eq 0 ]; then c=1; fi
+ echo "$CATEGORIES" | \
+ awk 'BEGIN {print "Known categories:"; i = 0 }
+ { printf ("%-'$l'.'$l's", $0); if ((++i % '$c') == 0) { print "" } }
+ END { print ""; }'
+ exit 0
+ ;;
+esac
+
+ORIGINATOR_C='<name of the PR author (one line)>'
+ORGANIZATION_C='<organization of PR author (multiple lines)>'
+CONFIDENTIAL_C='<[ yes | no ] (one line)>'
+SYNOPSIS_C='<synopsis of the problem (one line)>'
+SEVERITY_C='<[ non-critical | serious | critical ] (one line)>'
+PRIORITY_C='<[ low | medium | high ] (one line)>'
+CATEGORY_C='<name of the product (one line)>'
+CLASS_C='<[ sw-bug | doc-bug | change-request | support ] (one line)>'
+RELEASE_C='<release number or tag (one line)>'
+ENVIRONMENT_C='<machine, os, target, libraries (multiple lines)>'
+DESCRIPTION_C='<precise description of the problem (multiple lines)>'
+HOW_TO_REPEAT_C='<code/input/activities to reproduce the problem (multiple lines)>'
+FIX_C='<how to correct or work around the problem, if known (multiple lines)>'
+
+# Catch some signals. ($xs kludge needed by Sun /bin/sh)
+xs=0
+trap 'rm -f $REF $TEMP; exit $xs' 0
+trap 'echo "$COMMAND: Aborting ..."; rm -f $REF $TEMP; xs=1; exit' 1 2 3 13 15
+
+# If they told us to use a specific file, then do so.
+if [ -n "$IN_FILE" ]; then
+ if [ "$IN_FILE" = "-" ]; then
+ # The PR is coming from the standard input.
+ if [ -n "$EXPLICIT_GNATS_ADDR" ]; then
+ sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" > $TEMP
+ else
+ cat > $TEMP
+ fi
+ else
+ # Use the file they named.
+ if [ -n "$EXPLICIT_GNATS_ADDR" ]; then
+ sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" $IN_FILE > $TEMP
+ else
+ cat $IN_FILE > $TEMP
+ fi
+ fi
+else
+
+ if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then
+ # If their PR_FORM points to a bogus entry, then bail.
+ if [ ! -f "$PR_FORM" -o ! -r "$PR_FORM" -o ! -s "$PR_FORM" ]; then
+ echo "$COMMAND: can't seem to read your template file (\`$PR_FORM'), ignoring PR_FORM"
+ sleep 1
+ PRINT_INTERN=bad_prform
+ fi
+ fi
+
+ if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then
+ cp $PR_FORM $TEMP ||
+ ( echo "$COMMAND: could not copy $PR_FORM" ; xs=1; exit )
+ else
+ for file in $TEMP $REF ; do
+ cat > $file << '__EOF__'
+SEND-PR: -*- send-pr -*-
+SEND-PR: Lines starting with `SEND-PR' will be removed automatically, as
+SEND-PR: will all comments (text enclosed in `<' and `>').
+SEND-PR:
+SEND-PR: Choose from the following categories:
+SEND-PR:
+__EOF__
+
+ # Format the categories so they fit onto lines.
+ l=`echo "$CATEGORIES" | \
+ awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } }
+ END {print max + 1;}'`
+ c=`expr 61 / $l`
+ if [ $c -eq 0 ]; then c=1; fi
+ echo "$CATEGORIES" | \
+ awk 'BEGIN {printf "SEND-PR: "; i = 0 }
+ { printf ("%-'$l'.'$l's", $0);
+ if ((++i % '$c') == 0) { printf "\nSEND-PR: " } }
+ END { printf "\nSEND-PR:\n"; }' >> $file
+
+ cat >> $file << __EOF__
+To: $GNATS_ADDR
+Subject:
+From: $FROM
+Reply-To: $REPLY_TO
+X-send-pr-version: $VERSION
+
+
+>Submitter-Id: $SUBMITTER
+>Originator: $ORIGINATOR
+>Organization:
+${ORGANIZATION-$ORGANIZATION_C}
+>Confidential: $CONFIDENTIAL_C
+>Synopsis: $SYNOPSIS_C
+>Severity: $SEVERITY_C
+>Priority: $PRIORITY_C
+>Category: $CATEGORY_C
+>Class: $CLASS_C
+>Release: ${DEFAULT_RELEASE-$RELEASE_C}
+>Environment:
+ $ENVIRONMENT_C
+`[ -n "$SYSTEM" ] && echo System: $SYSTEM`
+`[ -n "$ARCH" ] && echo Architecture: $ARCH`
+`[ -n "$MACHINE" ] && echo Machine: $MACHINE`
+>Description:
+ $DESCRIPTION_C
+>How-To-Repeat:
+ $HOW_TO_REPEAT_C
+>Fix:
+ $FIX_C
+__EOF__
+ done
+ fi
+
+ if [ "$PRINT" = true -o "$PRINT_INTERN" = true ]; then
+ cat $TEMP
+ xs=0; exit
+ fi
+
+ chmod u+w $TEMP
+ if [ -z "$REQUEST_ID" ]; then
+ eval $EDIT $TEMP
+ else
+ ed -s $TEMP << '__EOF__'
+/^Subject/s/^Subject:.*/Subject: request for a customer id/
+/^>Category/s/^>Category:.*/>Category: send-pr/
+w
+q
+__EOF__
+ fi
+
+ if cmp -s $REF $TEMP ; then
+ echo "$COMMAND: problem report not filled out, therefore not sent"
+ xs=1; exit
+ fi
+fi
+
+#
+# Check the enumeration fields
+
+# This is a "sed-subroutine" with one keyword parameter
+# (with workaround for Sun sed bug)
+#
+SED_CMD='
+/$PATTERN/{
+s|||
+s|<.*>||
+s|^[ ]*||
+s|[ ]*$||
+p
+q
+}'
+
+
+while [ -z "$REQUEST_ID" ]; do
+ CNT=0
+
+ # 1) Confidential
+ #
+ PATTERN=">Confidential:"
+ CONFIDENTIAL=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$CONFIDENTIAL" in
+ ""|yes|no) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$CONFIDENTIAL' is not a valid value for \`Confidential'." ;;
+ esac
+ #
+ # 2) Severity
+ #
+ PATTERN=">Severity:"
+ SEVERITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$SEVERITY" in
+ ""|non-critical|serious|critical) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$SEVERITY' is not a valid value for \`Severity'."
+ esac
+ #
+ # 3) Priority
+ #
+ PATTERN=">Priority:"
+ PRIORITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$PRIORITY" in
+ ""|low|medium|high) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$PRIORITY' is not a valid value for \`Priority'."
+ esac
+ #
+ # 4) Category
+ #
+ PATTERN=">Category:"
+ CATEGORY=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ FOUND=
+ for C in $CATEGORIES
+ do
+ if [ "$C" = "$CATEGORY" ]; then FOUND=true ; break ; fi
+ done
+ if [ -n "$FOUND" ]; then
+ CNT=`expr $CNT + 1`
+ else
+ if [ -z "$CATEGORY" ]; then
+ echo "$COMMAND: you must include a Category: field in your report."
+ else
+ echo "$COMMAND: \`$CATEGORY' is not a known category."
+ fi
+ fi
+ #
+ # 5) Class
+ #
+ PATTERN=">Class:"
+ CLASS=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$CLASS" in
+ ""|sw-bug|doc-bug|change-request|support) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$CLASS' is not a valid value for \`Class'."
+ esac
+
+ [ $CNT -lt 5 -a -z "$BATCH" ] &&
+ echo "Errors were found with the problem report."
+
+ while true; do
+ if [ -z "$BATCH" ]; then
+ $ECHON1 "a)bort, e)dit or s)end? $ECHON2"
+ read input
+ else
+ if [ $CNT -eq 5 ]; then
+ input=s
+ else
+ input=a
+ fi
+ fi
+ case "$input" in
+ a*)
+ if [ -z "$BATCH" ]; then
+ echo "$COMMAND: the problem report remains in $BAD and is not sent."
+ mv $TEMP $BAD
+ else
+ echo "$COMMAND: the problem report is not sent."
+ fi
+ xs=1; exit
+ ;;
+ e*)
+ eval $EDIT $TEMP
+ continue 2
+ ;;
+ s*)
+ break 2
+ ;;
+ esac
+ done
+done
+#
+# Remove comments and send the problem report
+# (we have to use patterns, where the comment contains regex chars)
+#
+# /^>Originator:/s;$ORIGINATOR;;
+sed -e "
+/^SEND-PR:/d
+/^>Organization:/,/^>[A-Za-z-]*:/s;$ORGANIZATION_C;;
+/^>Confidential:/s;<.*>;;
+/^>Synopsis:/s;$SYNOPSIS_C;;
+/^>Severity:/s;<.*>;;
+/^>Priority:/s;<.*>;;
+/^>Category:/s;$CATEGORY_C;;
+/^>Class:/s;<.*>;;
+/^>Release:/,/^>[A-Za-z-]*:/s;$RELEASE_C;;
+/^>Environment:/,/^>[A-Za-z-]*:/s;$ENVIRONMENT_C;;
+/^>Description:/,/^>[A-Za-z-]*:/s;$DESCRIPTION_C;;
+/^>How-To-Repeat:/,/^>[A-Za-z-]*:/s;$HOW_TO_REPEAT_C;;
+/^>Fix:/,/^>[A-Za-z-]*:/s;$FIX_C;;
+" $TEMP > $REF
+
+if $MAIL_AGENT < $REF; then
+ echo "$COMMAND: problem report sent"
+ xs=0; exit
+else
+ echo "$COMMAND: mysterious mail failure."
+ if [ -z "$BATCH" ]; then
+ echo "$COMMAND: the problem report remains in $BAD and is not sent."
+ mv $REF $BAD
+ else
+ echo "$COMMAND: the problem report is not sent."
+ fi
+ xs=1; exit
+fi
diff --git a/contrib/cvs/src/cvsrc.c b/contrib/cvs/src/cvsrc.c
new file mode 100644
index 0000000..140ce1c
--- /dev/null
+++ b/contrib/cvs/src/cvsrc.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 1993 david d zuhn
+ *
+ * written by david d `zoo' zuhn while at Cygnus Support
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS 1.4 kit.
+ *
+ */
+
+
+#include "cvs.h"
+#include "getline.h"
+
+/* this file is to be found in the user's home directory */
+
+#ifndef CVSRC_FILENAME
+#define CVSRC_FILENAME ".cvsrc"
+#endif
+char cvsrc[] = CVSRC_FILENAME;
+
+#define GROW 10
+
+extern char *strtok ();
+
+/* Read cvsrc, processing options matching CMDNAME ("cvs" for global
+ options, and update *ARGC and *ARGV accordingly. */
+
+void
+read_cvsrc (argc, argv, cmdname)
+ int *argc;
+ char ***argv;
+ char *cmdname;
+{
+ char *homedir;
+ char *homeinit;
+ FILE *cvsrcfile;
+
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+
+ char *optstart;
+
+ int command_len;
+ int found = 0;
+
+ int i;
+
+ int new_argc;
+ int max_new_argv;
+ char **new_argv;
+
+ /* don't do anything if argc is -1, since that implies "help" mode */
+ if (*argc == -1)
+ return;
+
+ /* setup the new options list */
+
+ new_argc = 1;
+ max_new_argv = (*argc) + GROW;
+ new_argv = (char **) xmalloc (max_new_argv * sizeof (char*));
+ new_argv[0] = xstrdup ((*argv)[0]);
+
+ /* determine filename for ~/.cvsrc */
+
+ homedir = get_homedir ();
+ if (!homedir)
+ return;
+
+ homeinit = (char *) xmalloc (strlen (homedir) + strlen (cvsrc) + 10);
+ strcpy (homeinit, homedir);
+ strcat (homeinit, "/");
+ strcat (homeinit, cvsrc);
+
+ /* if it can't be read, there's no point to continuing */
+
+ if (!isreadable (homeinit))
+ {
+ free (homeinit);
+ return;
+ }
+
+ /* now scan the file until we find the line for the command in question */
+
+ line = NULL;
+ line_chars_allocated = 0;
+ command_len = strlen (cmdname);
+ cvsrcfile = open_file (homeinit, "r");
+ while ((line_length = getline (&line, &line_chars_allocated, cvsrcfile))
+ >= 0)
+ {
+ /* skip over comment lines */
+ if (line[0] == '#')
+ continue;
+
+ /* stop if we match the current command */
+ if (!strncmp (line, cmdname, command_len)
+ && isspace (*(line + command_len)))
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ fclose (cvsrcfile);
+
+ if (found)
+ {
+ /* skip over command in the options line */
+ optstart = strtok (line + command_len, "\t \n");
+
+ do
+ {
+ new_argv [new_argc] = xstrdup (optstart);
+ new_argv [new_argc+1] = NULL;
+ new_argc += 1;
+
+ if (new_argc >= max_new_argv)
+ {
+ char **tmp_argv;
+ max_new_argv += GROW;
+ tmp_argv = (char **) xmalloc (max_new_argv * sizeof (char*));
+ for (i = 0; i <= new_argc; i++)
+ tmp_argv[i] = new_argv[i];
+ free(new_argv);
+ new_argv = tmp_argv;
+ }
+
+ }
+ while ((optstart = strtok (NULL, "\t \n")) != NULL);
+ }
+
+ if (line != NULL)
+ free (line);
+
+ /* now copy the remaining arguments */
+
+ for (i=1; i < *argc; i++)
+ {
+ new_argv [new_argc] = (*argv)[i];
+ new_argc += 1;
+ }
+
+ *argc = new_argc;
+ *argv = new_argv;
+
+ free (homeinit);
+ return;
+}
diff --git a/contrib/cvs/src/diff.c b/contrib/cvs/src/diff.c
new file mode 100644
index 0000000..7520cec
--- /dev/null
+++ b/contrib/cvs/src/diff.c
@@ -0,0 +1,623 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Difference
+ *
+ * Run diff against versions in the repository. Options that are specified are
+ * passed on directly to "rcsdiff".
+ *
+ * Without any file arguments, runs diff against all the currently modified
+ * files.
+ */
+
+#include "cvs.h"
+
+static Dtype diff_dirproc PROTO((char *dir, char *pos_repos, char *update_dir));
+static int diff_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+static int diff_dirleaveproc PROTO((char *dir, int err, char *update_dir));
+static int diff_file_nodiff PROTO((char *file, char *repository, List *entries,
+ RCSNode *rcs, Vers_TS *vers));
+static int diff_fileproc PROTO((struct file_info *finfo));
+static void diff_mark_errors PROTO((int err));
+
+static char *diff_rev1, *diff_rev2;
+static char *diff_date1, *diff_date2;
+static char *use_rev1, *use_rev2;
+
+#ifdef SERVER_SUPPORT
+/* 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;
+#endif
+
+static char *options;
+static char opts[PATH_MAX];
+static int diff_errors;
+static int empty_files = 0;
+
+static const char *const diff_usage[] =
+{
+ "Usage: %s %s [-lN] [rcsdiff-options]\n",
+#ifdef CVS_DIFFDATE
+ " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
+#else
+ " [-r rev1 [-r rev2]] [files...] \n",
+#endif
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-D d1\tDiff revision for date against working file.\n",
+ "\t-D d2\tDiff rev1/date1 against date2.\n",
+ "\t-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",
+ NULL
+};
+
+int
+diff (argc, argv)
+ int argc;
+ char **argv;
+{
+ char tmp[50];
+ int c, err = 0;
+ int local = 0;
+ int which;
+
+ if (argc == -1)
+ usage (diff_usage);
+
+ /*
+ * Note that we catch all the valid arguments here, so that we can
+ * intercept the -r arguments for doing revision diffs; and -l/-R for a
+ * non-recursive/recursive diff.
+ */
+#ifdef SERVER_SUPPORT
+ /* Need to be able to do this command more than once (according to
+ the protocol spec, even if the current client doesn't use it). */
+ opts[0] = '\0';
+#endif
+ optind = 1;
+ while ((c = getopt (argc, argv,
+ "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'h': case 'i': case 'n': case 'p': case 't': case 'u':
+ case 'w': case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9': case 'B':
+ case 'H': case 'T': case 'Q':
+ (void) sprintf (tmp, " -%c", (char) c);
+ (void) strcat (opts, tmp);
+ if (c == 'Q')
+ {
+ quiet = 1;
+ really_quiet = 1;
+ c = 'q';
+ }
+ break;
+ case 'C': case 'F': case 'I': case 'L': case 'V':
+#ifndef CVS_DIFFDATE
+ case 'D':
+#endif
+ (void) sprintf (tmp, " -%c%s", (char) c, optarg);
+ (void) strcat (opts, tmp);
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'r':
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (diff_rev1 != NULL || diff_date1 != NULL)
+ diff_rev2 = optarg;
+ else
+ diff_rev1 = optarg;
+ break;
+#ifdef CVS_DIFFDATE
+ case 'D':
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (diff_rev1 != NULL || diff_date1 != NULL)
+ diff_date2 = Make_Date (optarg);
+ else
+ diff_date1 = Make_Date (optarg);
+ break;
+#endif
+ case '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 (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_file_names (argc, argv, SEND_EXPAND_WILD);
+#if 0
+ /* FIXME: We shouldn't have to send current files to diff two
+ revs, but it doesn't work yet and I haven't debugged it.
+ So send the files -- it's slower but it works.
+ gnu@cygnus.com Apr94 */
+ /* Send the current files unless diffing two revs from the archive */
+ if (diff_rev2 == NULL && diff_date2 == NULL)
+#endif
+ send_files (argc, argv, local, 0);
+
+ send_to_server ("diff\012", 0);
+ err = get_responses_and_close ();
+ free (options);
+ 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_rev2 != NULL || diff_date2 != NULL)
+ which |= W_REPOS | W_ATTIC;
+
+ wrap_setup ();
+
+ /* start the recursion processor */
+ err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
+ diff_dirleaveproc, argc, argv, local,
+ which, 0, 1, (char *) NULL, 1, 0);
+
+ /* clean up */
+ free (options);
+ return (err);
+}
+
+/*
+ * Do a file diff
+ */
+/* ARGSUSED */
+static int
+diff_fileproc (finfo)
+ struct file_info *finfo;
+{
+ int status, err = 2; /* 2 == trouble, like rcsdiff */
+ Vers_TS *vers;
+ enum {
+ DIFF_ERROR,
+ DIFF_ADDED,
+ DIFF_REMOVED,
+ DIFF_NEITHER
+ } empty_file = DIFF_NEITHER;
+ char tmp[L_tmpnam+1];
+ char *tocvsPath;
+ char fname[PATH_MAX];
+
+#ifdef SERVER_SUPPORT
+ user_file_rev = 0;
+#endif
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 1, 0, finfo->entries, finfo->rcs);
+
+ 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)
+ {
+ 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);
+ }
+#ifdef SERVER_SUPPORT
+ 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;
+ }
+#endif
+ }
+ }
+
+ if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers))
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal
+ accordingly. */
+
+ /* Output an "Index:" line for patch to use */
+ (void) fflush (stdout);
+ (void) printf ("Index: %s\n", finfo->fullname);
+ (void) fflush (stdout);
+
+ tocvsPath = wrap_tocvs_process_file(finfo->file);
+ if (tocvsPath)
+ {
+ /* Backup the current version of the file to CVS/,,filename */
+ 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", finfo->file);
+ 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. */
+ (void) printf ("===================================================================\nRCS file: %s\n",
+ finfo->file);
+ (void) printf ("diff -N %s\n", finfo->file);
+
+ if (empty_file == DIFF_ADDED)
+ {
+ run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file);
+ }
+ else
+ {
+ int retcode;
+
+ /*
+ * FIXME: Should be setting use_rev1 using the logic in
+ * diff_file_nodiff, and using that revision. This code
+ * is broken for "cvs diff -N -r foo".
+ */
+ retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
+ *options ? options : vers->options, tmpnam (tmp),
+ 0, 0);
+ if (retcode == -1)
+ {
+ (void) unlink (tmp);
+ error (1, errno, "fork failed during checkout of %s",
+ vers->srcfile->path);
+ }
+ /* FIXME: what if retcode > 0? */
+
+ run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
+ }
+ }
+ else
+ {
+ if (use_rev2)
+ {
+ run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF,
+ opts, *options ? options : vers->options,
+ use_rev1, use_rev2);
+ }
+ else
+ {
+ run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts,
+ *options ? options : vers->options, use_rev1);
+ }
+ run_arg (vers->srcfile->path);
+ }
+
+ switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+ RUN_REALLY|RUN_COMBINED)))
+ {
+ case -1: /* fork failed */
+ error (1, errno, "fork failed during rcsdiff of %s",
+ vers->srcfile->path);
+ case 0: /* everything ok */
+ err = 0;
+ break;
+ default: /* other error */
+ err = status;
+ break;
+ }
+
+ 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", finfo->file);
+ }
+
+ if (empty_file == DIFF_REMOVED)
+ (void) unlink (tmp);
+
+ (void) fflush (stdout);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+}
+
+/*
+ * Remember the exit status for each file.
+ */
+static void
+diff_mark_errors (err)
+ int err;
+{
+ if (err > diff_errors)
+ diff_errors = err;
+}
+
+/*
+ * Print a warm fuzzy message when we enter a dir
+ *
+ * Don't try to diff directories that don't exist! -- DW
+ */
+/* ARGSUSED */
+static Dtype
+diff_dirproc (dir, pos_repos, update_dir)
+ char *dir;
+ char *pos_repos;
+ char *update_dir;
+{
+ /* XXX - check for dirs we don't want to process??? */
+
+ /* 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 (err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ return (diff_errors);
+}
+
+/*
+ * Concoct the proper exit status - leaving directories
+ */
+/* ARGSUSED */
+static int
+diff_dirleaveproc (dir, err, update_dir)
+ char *dir;
+ int err;
+ char *update_dir;
+{
+ return (diff_errors);
+}
+
+/*
+ * verify that a file is different 0=same 1=different
+ */
+static int
+diff_file_nodiff (file, repository, entries, rcs, vers)
+ char *file;
+ char *repository;
+ List *entries;
+ RCSNode *rcs;
+ Vers_TS *vers;
+{
+ Vers_TS *xvers;
+ char tmp[L_tmpnam+1];
+ 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 = xstrdup (vers->vn_rcs);
+ else
+ {
+ xvers = Version_TS (repository, (char *) NULL, diff_rev1,
+ diff_date1, file, 1, 0, entries, rcs);
+ if (xvers->vn_rcs == NULL)
+ {
+ /* Don't gripe if it doesn't exist, just ignore! */
+ if (! isfile (file))
+ /* null statement */ ;
+ else if (diff_rev1)
+ error (0, 0, "tag %s is not in file %s", diff_rev1, file);
+ else
+ error (0, 0, "no revision for date %s in file %s",
+ diff_date1, file);
+
+ freevers_ts (&xvers);
+ return (1);
+ }
+ use_rev1 = xstrdup (xvers->vn_rcs);
+ freevers_ts (&xvers);
+ }
+ }
+ if (diff_rev2 || diff_date2)
+ {
+ /* special handling for TAG_HEAD */
+ if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
+ use_rev2 = xstrdup (vers->vn_rcs);
+ else
+ {
+ xvers = Version_TS (repository, (char *) NULL, diff_rev2,
+ diff_date2, file, 1, 0, entries, rcs);
+ if (xvers->vn_rcs == NULL)
+ {
+ /* Don't gripe if it doesn't exist, just ignore! */
+ if (! isfile (file))
+ /* null statement */ ;
+ else if (diff_rev1)
+ error (0, 0, "tag %s is not in file %s", diff_rev2, file);
+ else
+ error (0, 0, "no revision for date %s in file %s",
+ diff_date2, file);
+
+ freevers_ts (&xvers);
+ return (1);
+ }
+ use_rev2 = xstrdup (xvers->vn_rcs);
+ freevers_ts (&xvers);
+ }
+
+ /* now, see if we really need to do the diff */
+ if (use_rev1 && use_rev2) {
+ return (strcmp (use_rev1, use_rev2) == 0);
+ } else {
+ error(0, 0, "No HEAD revision for file %s", file);
+ return (1);
+ }
+ }
+#ifdef SERVER_SUPPORT
+ 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)
+ {
+ return (strcmp (use_rev1, use_rev2) == 0);
+ }
+#endif /* SERVER_SUPPORT */
+ if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0)
+ {
+ if (strcmp (vers->ts_rcs, vers->ts_user) == 0 &&
+ (!(*options) || strcmp (options, vers->options) == 0))
+ {
+ return (1);
+ }
+ if (use_rev1 == NULL)
+ use_rev1 = xstrdup (vers->vn_user);
+ }
+
+ /*
+ * with 0 or 1 -r option specified, run a quick diff to see if we
+ * should bother with it at all.
+ */
+ retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1,
+ *options ? options : vers->options, tmpnam (tmp), 0, 0);
+ switch (retcode)
+ {
+ case 0: /* everything ok */
+ if (xcmp (file, tmp) == 0)
+ {
+ (void) unlink (tmp);
+ return (1);
+ }
+ break;
+ case -1: /* fork failed */
+ (void) unlink (tmp);
+ error (1, errno, "fork failed during checkout of %s",
+ vers->srcfile->path);
+ default:
+ break;
+ }
+ (void) unlink (tmp);
+ return (0);
+}
diff --git a/contrib/cvs/src/edit.c b/contrib/cvs/src/edit.c
new file mode 100644
index 0000000..0473a03
--- /dev/null
+++ b/contrib/cvs/src/edit.c
@@ -0,0 +1,1020 @@
+/* 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "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 ((struct file_info *finfo));
+
+static int
+onoff_fileproc (finfo)
+ struct file_info *finfo;
+{
+ fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
+ return 0;
+}
+
+static int onoff_filesdoneproc PROTO ((int, char *, char *));
+
+static int
+onoff_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ 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 = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ 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_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ 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,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+
+ lock_tree_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 ((struct file_info *finfo));
+
+static int
+dummy_fileproc (finfo)
+ 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 ((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 (finfo)
+ 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 = 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 (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,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 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,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+ lock_tree_cleanup ();
+ }
+ return err;
+}
+
+static int edit_fileproc PROTO ((struct file_info *finfo));
+
+static int
+edit_fileproc (finfo)
+ struct file_info *finfo;
+{
+ FILE *fp;
+ time_t now;
+ char *ascnow;
+ char *basefilename;
+
+ if (noexec)
+ 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 trying to create
+ the output file fails. But copy_file isn't set up to facilitate
+ that. */
+ if (CVS_MKDIR (CVSADM_BASE, 0777) < 0)
+ {
+ if (errno != EEXIST
+#ifdef EACCESS
+ /* OS/2; see longer comment in client.c. */
+ && errno != EACCESS
+#endif
+ )
+ error (1, errno, "cannot mkdir %s", 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);
+
+ return 0;
+}
+
+static const char *const edit_usage[] =
+{
+ "Usage: %s %s [-l] [files...]\n",
+ "-l: Local directory only, not recursive\n",
+ "-a: Specify what actions for temporary watch, one of\n",
+ " edit,unedit,commit.all,none\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 = 1;
+ while ((c = getopt (argc, argv, "la:")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ 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,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+
+ err += send_notifications (argc, argv, local);
+
+ return err;
+}
+
+static int unedit_fileproc PROTO ((struct file_info *finfo));
+
+static int
+unedit_fileproc (finfo)
+ 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);
+ }
+
+ xchmod (finfo->file, 0);
+ return 0;
+}
+
+int
+unedit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+ int err;
+
+ if (argc == -1)
+ usage (edit_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case '?':
+ default:
+ usage (edit_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,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 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 (edlist != NULL)
+ free (edlist);
+ /* 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 (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));
+}
+
+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':
+ 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)
+ + sizeof CVSROOTADM
+ + sizeof CVSROOTADM_USERS
+ + 20);
+ strcpy (usersname, CVSroot);
+ strcat (usersname, "/");
+ strcat (usersname, CVSROOTADM);
+ strcat (usersname, "/");
+ strcat (usersname, CVSROOTADM_USERS);
+ fp = 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);
+ cp = strchr (args.notifyee, ':');
+ 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);
+ 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 = 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 (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 [files...]\n",
+ NULL
+};
+
+static int editors_fileproc PROTO ((struct file_info *finfo));
+
+static int
+editors_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char *them;
+ char *p;
+
+ them = fileattr_get0 (finfo->file, "_editors");
+ if (them == NULL)
+ return 0;
+
+ fputs (finfo->fullname, stdout);
+
+ p = them;
+ while (1)
+ {
+ putc ('\t', stdout);
+ while (*p != '>' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ /* Only happens if attribute is misformed. */
+ putc ('\n', stdout);
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ while (1)
+ {
+ while (*p != '+' && *p != ',' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ putc ('\n', stdout);
+ goto out;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ }
+ putc ('\n', stdout);
+ }
+ out:;
+ return 0;
+}
+
+int
+editors (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (editors_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ 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_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ 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,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 0, 0);
+}
diff --git a/contrib/cvs/src/edit.h b/contrib/cvs/src/edit.h
new file mode 100644
index 0000000..0a823ad
--- /dev/null
+++ b/contrib/cvs/src/edit.h
@@ -0,0 +1,42 @@
+/* Interface to "cvs edit", "cvs watch on", and related 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+extern int watch_on PROTO ((int argc, char **argv));
+extern int watch_off PROTO ((int argc, char **argv));
+
+#ifdef CLIENT_SUPPORT
+/* Check to see if any notifications are sitting around in need of being
+ sent. These are the notifications stored in CVSADM_NOTIFY (edit,unedit);
+ commit calls notify_do directly. */
+extern void notify_check PROTO ((char *repository, char *update_dir));
+#endif /* CLIENT_SUPPORT */
+
+/* Issue a notification for file FILENAME. TYPE is 'E' for edit, 'U'
+ for unedit, and 'C' for commit. WHO is the user currently running.
+ For TYPE 'E', VAL is the time+host+directory data which goes in
+ _editors, and WATCHES is zero or more of E,U,C, in that order, to specify
+ what kinds of temporary watches to set. */
+extern void notify_do PROTO ((int type, char *filename, char *who,
+ char *val, char *watches, char *repository));
+
+/* Set attributes to reflect the fact that EDITOR is editing FILENAME.
+ VAL is time+host+directory, or NULL if we are to say that EDITOR is
+ *not* editing FILENAME. */
+extern void editor_set PROTO ((char *filename, char *editor, char *val));
+
+/* Take note of the fact that FILE is up to date (this munges CVS/Base;
+ processing of CVS/Entries is done separately). */
+extern void mark_up_to_date PROTO ((char *file));
diff --git a/contrib/cvs/src/entries.c b/contrib/cvs/src/entries.c
new file mode 100644
index 0000000..350f7f8
--- /dev/null
+++ b/contrib/cvs/src/entries.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * 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 *));
+static int fputentent PROTO((FILE *, Entnode *));
+
+static FILE *entfile;
+static char *entfilename; /* for error messages */
+
+/*
+ * Construct an Entnode
+ */
+Entnode *
+Entnode_Create(user, vn, ts, options, tag, date, ts_conflict)
+ 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->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
+ */
+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;
+{
+ if (fputentent(entfile, (Entnode *) node->data))
+ error (1, errno, "cannot write %s", entfilename);
+
+ return (0);
+}
+
+/*
+ * write out the current entries file given a list, making a backup copy
+ * first of course
+ */
+static void
+write_entries (list)
+ List *list;
+{
+ /* open the new one and walk the list writing entries */
+ entfilename = CVSADM_ENTBAK;
+ entfile = open_file (entfilename, "w+");
+ (void) walklist (list, write_ent_proc, NULL);
+ 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 */
+ unlink_file (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)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> Scratch_Entry(%s)\n",
+ (server_active) ? 'S' : ' ', fname);
+#else
+ (void) fprintf (stderr, "-> Scratch_Entry(%s)\n", fname);
+#endif
+
+ /* hashlookup to see if it is there */
+ if ((node = findnode_fn (list, fname)) != NULL)
+ {
+ delnode (node); /* delete the node */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_scratch (fname);
+#endif
+ if (!noexec)
+ write_entries (list); /* re-write the file */
+ }
+}
+
+/*
+ * Enters the given file name/version/time-stamp into the Entries file,
+ * removing the old entry first, if necessary.
+ */
+void
+Register (list, fname, vn, ts, options, tag, date, 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)
+ {
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> Register(%s, %s, %s%s%s, %s, %s %s)\n",
+ (server_active) ? 'S' : ' ',
+ fname, vn, ts ? ts : "",
+ ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "",
+ options, tag ? tag : "", date ? date : "");
+#else
+ (void) fprintf (stderr, "-> Register(%s, %s, %s%s%s, %s, %s %s)\n",
+ fname, vn, ts ? ts : "",
+ ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "",
+ options, tag ? tag : "", date ? date : "");
+#endif
+ }
+
+ entnode = Entnode_Create(fname, vn, ts, options, tag, date, ts_conflict);
+ node = AddEntryNode (list, entnode);
+
+ if (!noexec)
+ {
+ entfile = open_file (CVSADM_ENTLOG, "a");
+
+ write_ent_proc (node, NULL);
+
+ if (fclose (entfile) == EOF)
+ error (1, errno, "error closing %s", CVSADM_ENTLOG);
+ }
+}
+
+/*
+ * Node delete procedure for list-private sticky dir tag/date info
+ */
+static void
+freesdt (p)
+ Node *p;
+{
+ struct stickydirtag *sdtp;
+
+ sdtp = (struct stickydirtag *) p->data;
+ if (sdtp->tag)
+ free (sdtp->tag);
+ if (sdtp->date)
+ free (sdtp->date);
+ if (sdtp->options)
+ free (sdtp->options);
+ free ((char *) sdtp);
+}
+
+static Entnode *
+fgetentent(fpin)
+ FILE *fpin;
+{
+ Entnode *ent;
+ char *line;
+ size_t line_chars_allocated;
+ register char *cp;
+ char *user, *vn, *ts, *options;
+ char *tag_or_date, *tag, *date, *ts_conflict;
+
+ line = NULL;
+ line_chars_allocated = 0;
+
+ ent = NULL;
+ while (getline (&line, &line_chars_allocated, fpin) > 0)
+ {
+ if (line[0] != '/')
+ continue;
+
+ user = line + 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 && 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(user, vn, ts, options, tag, date, ts_conflict);
+ break;
+ }
+
+ free (line);
+ return ent;
+}
+
+static int
+fputentent(fp, p)
+ FILE *fp;
+ Entnode *p;
+{
+ 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.
+ */
+List *
+Entries_Open (aflag)
+ int aflag;
+{
+ List *entries;
+ Entnode *ent;
+ char *dirtag, *dirdate;
+ int do_rewrite = 0;
+ FILE *fpin;
+
+ /* get a fresh list... */
+ entries = getlist ();
+
+ /*
+ * Parse the CVS/Tag file, to get any default tag/date settings. Use
+ * list-private storage to tuck them away for Version_TS().
+ */
+ ParseTag (&dirtag, &dirdate);
+ if (aflag || dirtag || dirdate)
+ {
+ struct stickydirtag *sdtp;
+
+ sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
+ memset ((char *) sdtp, 0, sizeof (*sdtp));
+ sdtp->aflag = aflag;
+ sdtp->tag = xstrdup (dirtag);
+ sdtp->date = xstrdup (dirdate);
+
+ /* feed it into the list-private area */
+ entries->list->data = (char *) sdtp;
+ entries->list->delproc = freesdt;
+ }
+
+ fpin = fopen (CVSADM_ENT, "r");
+ if (fpin == NULL)
+ error (0, errno, "cannot open %s for reading", CVSADM_ENT);
+ else
+ {
+ while ((ent = fgetentent (fpin)) != NULL)
+ {
+ (void) AddEntryNode (entries, ent);
+ }
+
+ fclose (fpin);
+ }
+
+ fpin = fopen (CVSADM_ENTLOG, "r");
+ if (fpin != NULL)
+ {
+ while ((ent = fgetentent (fpin)) != NULL)
+ {
+ (void) AddEntryNode (entries, ent);
+ }
+ do_rewrite = 1;
+ fclose (fpin);
+ }
+
+ 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)
+ char *dir;
+ char *tag;
+ char *date;
+{
+ FILE *fout;
+ char tmp[PATH_MAX];
+
+ if (noexec)
+ return;
+
+ if (dir == NULL)
+ (void) strcpy (tmp, CVSADM_TAG);
+ else
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG);
+
+ if (tag || date)
+ {
+ fout = open_file (tmp, "w+");
+ if (tag)
+ {
+ if (fprintf (fout, "T%s\n", tag) < 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);
+}
+
+/*
+ * Parse the CVS/Tag file for the current directory.
+ */
+void
+ParseTag (tagp, datep)
+ char **tagp;
+ char **datep;
+{
+ FILE *fp;
+
+ if (tagp)
+ *tagp = (char *) NULL;
+ if (datep)
+ *datep = (char *) NULL;
+ fp = 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';
+ if (*line == 'T' && tagp)
+ *tagp = xstrdup (line + 1);
+ else if (*line == 'D' && datep)
+ *datep = xstrdup (line + 1);
+ }
+ (void) fclose (fp);
+ free (line);
+ }
+}
diff --git a/contrib/cvs/src/error.c b/contrib/cvs/src/error.c
new file mode 100644
index 0000000..8a10cc7
--- /dev/null
+++ b/contrib/cvs/src/error.c
@@ -0,0 +1,256 @@
+/* error.c -- error handler for noninteractive utilities
+ Copyright (C) 1990-1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* David MacKenzie */
+/* Brian Berliner added support for CVS */
+
+#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
+
+#if __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 */
+#if __STDC__
+void exit(int status);
+#else /* ! __STDC__ */
+void exit ();
+#endif /* __STDC__ */
+#endif /* STDC_HEADERS */
+
+extern char *strerror ();
+
+extern int vasprintf ();
+
+typedef void (*fn_returning_void) PROTO((void));
+
+/* Function to call before exiting. */
+static fn_returning_void cleanup_fn;
+
+fn_returning_void
+error_set_cleanup (arg)
+ fn_returning_void arg;
+{
+ fn_returning_void retval = cleanup_fn;
+ cleanup_fn = arg;
+ return retval;
+}
+
+/* Print the program name and error message MESSAGE, which is a printf-style
+ format string with optional args.
+ If ERRNUM is nonzero, print its corresponding system error message.
+ Exit with status EXIT_FAILURE if STATUS is nonzero. */
+/* VARARGS */
+void
+#if defined (HAVE_VPRINTF) && __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
+{
+ FILE *out = stderr;
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+
+ if (error_use_protocol)
+ {
+ out = stdout;
+ printf ("E ");
+ }
+
+#ifdef HAVE_VPRINTF
+ {
+ char *mess = NULL;
+ char *entire;
+ size_t len;
+
+ VA_START (args, message);
+ vasprintf (&mess, message, args);
+ va_end (args);
+
+ if (mess == NULL)
+ {
+ entire = NULL;
+ status = 1;
+ }
+ else
+ {
+ len = strlen (mess) + strlen (program_name) + 80;
+ if (command_name != NULL)
+ len += strlen (command_name);
+ if (errnum != 0)
+ len += strlen (strerror (errnum));
+ entire = malloc (len);
+ if (entire == NULL)
+ {
+ free (mess);
+ status = 1;
+ }
+ else
+ {
+ strcpy (entire, program_name);
+ if (command_name != NULL && command_name[0] != '\0')
+ {
+ strcat (entire, " ");
+ if (status != 0)
+ strcat (entire, "[");
+ strcat (entire, command_name);
+ if (status != 0)
+ strcat (entire, " aborted]");
+ }
+ strcat (entire, ": ");
+ strcat (entire, mess);
+ if (errnum != 0)
+ {
+ strcat (entire, ": ");
+ strcat (entire, strerror (errnum));
+ }
+ strcat (entire, "\n");
+ free (mess);
+ }
+ }
+ if (error_use_protocol)
+ fputs (entire ? entire : "out of memory", out);
+ else
+ cvs_outerr (entire ? entire : "out of memory", 0);
+ if (entire != NULL)
+ free (entire);
+ }
+
+#else /* No HAVE_VPRINTF */
+ /* I think that all relevant systems have vprintf these days. But
+ just in case, I'm leaving this code here. */
+
+ if (command_name && *command_name)
+ {
+ if (status)
+ fprintf (out, "%s [%s aborted]: ", program_name, command_name);
+ else
+ fprintf (out, "%s %s: ", program_name, command_name);
+ }
+ else
+ fprintf (out, "%s: ", program_name);
+
+#ifdef HAVE_VPRINTF
+ VA_START (args, message);
+ vfprintf (out, message, args);
+ va_end (args);
+#else
+#ifdef HAVE_DOPRNT
+ _doprnt (message, &args, out);
+#else
+ fprintf (out, message, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+#endif
+ if (errnum)
+ fprintf (out, ": %s", strerror (errnum));
+ putc ('\n', out);
+
+#endif /* No HAVE_VPRINTF */
+
+ /* In the error_use_protocol case, this probably does something useful.
+ In most other cases, I suspect it is a noop (either stderr is line
+ buffered or we haven't written anything to stderr) or unnecessary
+ (if stderr is not line buffered, maybe there is a reason....). */
+ fflush (out);
+
+ if (status)
+ {
+ if (cleanup_fn)
+ (*cleanup_fn) ();
+ exit (EXIT_FAILURE);
+ }
+}
+
+/* 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) && __STDC__
+fperror (FILE *fp, int status, int errnum, char *message, ...)
+#else
+fperror (fp, status, errnum, message, va_alist)
+ FILE *fp;
+ int status;
+ int errnum;
+ char *message;
+ va_dcl
+#endif
+{
+#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)
+ {
+ if (cleanup_fn)
+ (*cleanup_fn) ();
+ exit (EXIT_FAILURE);
+ }
+}
diff --git a/contrib/cvs/src/error.h b/contrib/cvs/src/error.h
new file mode 100644
index 0000000..7d4f535
--- /dev/null
+++ b/contrib/cvs/src/error.h
@@ -0,0 +1,47 @@
+/* error.h -- declaration for error-reporting function
+ Copyright (C) 1995 Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef _error_h_
+#define _error_h_
+
+#ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__
+# define __attribute__(Spec) /* empty */
+# endif
+/* The __-protected variants of `format' and `printf' attributes
+ are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __format__ format
+# define __printf__ printf
+# endif
+#endif
+
+#if __STDC__
+void error (int, int, const char *, ...) \
+ __attribute__ ((__format__ (__printf__, 3, 4)));
+#else
+void error ();
+#endif
+
+/* If non-zero, error will use the CVS protocol 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. */
+extern int error_use_protocol;
+
+#endif /* _error_h_ */
diff --git a/contrib/cvs/src/expand_path.c b/contrib/cvs/src/expand_path.c
new file mode 100644
index 0000000..9898051
--- /dev/null
+++ b/contrib/cvs/src/expand_path.c
@@ -0,0 +1,241 @@
+/* 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 (*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. 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. */
+char *
+expand_path (name, file, line)
+ char *name;
+ char *file;
+ int line;
+{
+ char *s;
+ char *d;
+ /* FIXME: arbitrary limit. */
+ char mybuf[PATH_MAX];
+ char buf[PATH_MAX];
+ char *result;
+ s = name;
+ d = mybuf;
+ while ((*d++ = *s))
+ if (*s++ == '$')
+ {
+ char *p = d;
+ char *e;
+ int flag = (*s == '{');
+
+ for (; (*d++ = *s); s++)
+ if (flag
+ ? *s =='}'
+ : isalnum (*s) == 0 && *s != '_')
+ break;
+ *--d = 0;
+ e = expand_variable (&p[flag], file, line);
+
+ if (e)
+ {
+ for (d = &p[-1]; (*d++ = *e++);)
+ ;
+ --d;
+ if (flag && *s)
+ s++;
+ }
+ else
+ /* expand_variable has already printed an error message. */
+ return NULL;
+ }
+ *d = 0;
+ 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
+ {
+ 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;
+ }
+ while ((*d++ = *t++))
+ ;
+ --d;
+ if (*p == 0)
+ *p = '/'; /* always add / */
+ s=p;
+ }
+ else
+ --s;
+ /* Kill up to here */
+ while ((*d++ = *s++))
+ ;
+ *d=0;
+ result = xmalloc (sizeof(char) * strlen(buf)+1);
+ strcpy (result, buf);
+ return result;
+}
+
+static char *
+expand_variable (name, file, line)
+ char *name;
+ char *file;
+ int line;
+{
+ if (strcmp (name, CVSROOT_ENV) == 0)
+ return CVSroot;
+ else if (strcmp (name, RCSBIN_ENV) == 0)
+ return Rcsbin;
+ 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 (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 varaible syntax %s",
+ file, line, name);
+ else
+ error (0, 0, "%s: unrecognized varaible syntax %s",
+ file, name);
+ return NULL;
+ }
+}
diff --git a/contrib/cvs/src/fileattr.c b/contrib/cvs/src/fileattr.c
new file mode 100644
index 0000000..827c69c
--- /dev/null
+++ b/contrib/cvs/src/fileattr.c
@@ -0,0 +1,517 @@
+/* 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "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;
+
+/* 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;
+}
+
+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 = 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');
+ *p++ = '\0';
+ newnode = getnode ();
+ newnode->type = FILEATTR;
+ newnode->delproc = fileattr_delproc;
+ newnode->key = xstrdup (line + 1);
+ newnode->data = xstrdup (p);
+ addnode (attrlist, 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');
+ ++p;
+ fileattr_default_attrs = xstrdup (p);
+ }
+ /* else just ignore the line, for future expansion. */
+ }
+ 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)
+ char *filename;
+ 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)
+ char *filename;
+ 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;
+ char *attrname;
+ 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)
+ char *filename;
+ char *attrname;
+ char *attrval;
+{
+ Node *node;
+ char *p;
+
+ attrs_modified = 1;
+
+ if (filename == NULL)
+ {
+ p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
+ '=', ';');
+ if (fileattr_default_attrs != NULL)
+ free (fileattr_default_attrs);
+ fileattr_default_attrs = p;
+ 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, '=', ';');
+ free (node->data);
+ node->data = NULL;
+ if (p == NULL)
+ delnode (node);
+ else
+ node->data = p;
+}
+
+void
+fileattr_newfile (filename)
+ 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)
+ {
+ /* 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 (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 = 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 = fopen (fname, FOPEN_BINARY_WRITE);
+ }
+ if (fp == NULL)
+ {
+ error (0, errno, "cannot write %s", fname);
+ (void) umask (omask);
+ return;
+ }
+ }
+ (void) umask (omask);
+ walklist (attrlist, writeattr_proc, fp);
+ if (fileattr_default_attrs != NULL)
+ {
+ fputs ("D\t", fp);
+ fputs (fileattr_default_attrs, fp);
+ fputs ("\012", fp);
+ }
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ attrs_modified = 0;
+ free (fname);
+}
+
+void
+fileattr_free ()
+{
+ 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/contrib/cvs/src/fileattr.h b/contrib/cvs/src/fileattr.h
new file mode 100644
index 0000000..c24c035
--- /dev/null
+++ b/contrib/cvs/src/fileattr.h
@@ -0,0 +1,125 @@
+/* Declarations 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef FILEATTR_H
+
+/* File containing per-file attributes. Format is a series of entries:
+
+ ENT-TYPE FILENAME <tab> ATTRNAME = ATTRVAL
+ {; ATTRNAME = ATTRVAL} <linefeed>
+
+ ENT-TYPE is 'F' for a file, in which case the entry specifies the
+ attributes for that file.
+
+ ENT-TYPE is 'D', and FILENAME empty, to specify default attributes
+ to be used for newly added files.
+
+ There is currently no way of quoting tabs or linefeeds in the
+ filename, '=' in ATTRNAME, ';' in ATTRVAL, etc. I'm not sure
+ whether I think we need one. Note: the current implementation also
+ doesn't handle '\0' in any of the fields.
+
+ By convention, ATTRNAME starting with '_' is for an attribute given
+ special meaning by CVS; other ATTRNAMEs are for user-defined attributes
+ (or will be, once we add commands to manipulate user-defined attributes).
+
+ Builtin attributes:
+
+ _watched: Present means the file is watched and should be checked out
+ read-only.
+
+ _watchers: Users with watches for this file. Value is
+ WATCHER > TYPE { , WATCHER > TYPE }
+ where WATCHER is a username, and TYPE is edit,unedit,commit separated by
+ + (or nothing if none; there is no "none" or "all" keyword).
+
+ _editors: Users editing this file. Value is
+ EDITOR > VAL { , EDITOR > VAL }
+ where EDITOR is a username, and VAL is TIME+HOSTNAME+PATHNAME, where
+ TIME is when the "cvs edit" command happened,
+ and HOSTNAME and PATHNAME are for the working directory. */
+
+#define CVSREP_FILEATTR "CVS/fileattr"
+
+/* Prepare for a new directory with repository REPOS. If REPOS is NULL,
+ then prepare for a "non-directory"; the caller can call fileattr_write
+ and fileattr_free, but must not call fileattr_get or fileattr_set. */
+extern void fileattr_startdir PROTO ((char *repos));
+
+/* Get the attribute ATTRNAME for file FILENAME. The return value
+ points into memory managed by the fileattr_* routines, should not
+ be altered by the caller, and is only good until the next call to
+ fileattr_clear or fileattr_set. It points to the value, terminated
+ by '\0' or ';'. Return NULL if said file lacks said attribute.
+ If FILENAME is NULL, return default attributes (attributes for
+ files created in the future). */
+extern char *fileattr_get PROTO ((char *filename, char *attrname));
+
+/* Like fileattr_get, but return a pointer to a newly malloc'd string
+ terminated by '\0' (or NULL if said file lacks said attribute). */
+extern char *fileattr_get0 PROTO ((char *filename, char *attrname));
+
+/* This is just a string manipulation function; it does not manipulate
+ file attributes as such.
+
+ LIST is in the format
+
+ ATTRNAME NAMEVALSEP ATTRVAL {ENTSEP ATTRNAME NAMEVALSEP ATTRVAL}
+
+ And we want to put in an attribute with name NAME and value VAL,
+ replacing the already-present attribute with name NAME if there is
+ one. Or if VAL is NULL remove attribute NAME. Return a new
+ malloc'd list; don't muck with the one passed in. If we are removing
+ the last attribute return NULL. LIST can be NULL to mean that we
+ started out without any attributes.
+
+ Examples:
+
+ fileattr_modify ("abc=def", "xxx", "val", '=', ';')) => "abc=def;xxx=val"
+ fileattr_modify ("abc=def", "abc", "val", '=', ';')) => "abc=val"
+ fileattr_modify ("abc=v1;def=v2", "abc", "val", '=', ';'))
+ => "abc=val;def=v2"
+ fileattr_modify ("abc=v1;def=v2", "def", "val", '=', ';'))
+ => "abc=v1;def=val"
+ fileattr_modify ("abc=v1;def=v2", "xxx", "val"))
+ => "abc=v1;def=v2;xxx=val"
+ fileattr_modify ("abc=v1;def=v2;ghi=v3", "def", "val", '=', ';'))
+ => "abc=v1;def=val;ghi=v3"
+*/
+
+extern char *fileattr_modify PROTO ((char *list, char *attrname,
+ char *attrval, int namevalsep,
+ int entsep));
+
+/* Set attribute ATTRNAME for file FILENAME to ATTRVAL. If ATTRVAL is NULL,
+ the attribute is removed. Changes are not written to disk until the
+ next call to fileattr_write. If FILENAME is NULL, set attributes for
+ files created in the future. If ATTRVAL is NULL, remove that attribute. */
+extern void fileattr_set PROTO ((char *filename, char *attrname,
+ char *attrval));
+
+/* Set the attributes for file FILENAME in whatever manner is appropriate
+ for a newly created file. */
+extern void fileattr_newfile PROTO ((char *filename));
+
+/* Write out all modified attributes. */
+extern void fileattr_write PROTO ((void));
+
+/* Free all memory allocated by fileattr_*. */
+extern void fileattr_free PROTO ((void));
+
+#define FILEATTR_H 1
+#endif /* fileattr.h */
diff --git a/contrib/cvs/src/filesubr.c b/contrib/cvs/src/filesubr.c
new file mode 100644
index 0000000..086da83
--- /dev/null
+++ b/contrib/cvs/src/filesubr.c
@@ -0,0 +1,662 @@
+/* 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* 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"
+
+/*
+ * I don't know of a convenient way to test this at configure time, or else
+ * I'd certainly do it there.
+ */
+#if defined(NeXT)
+#define LOSING_TMPNAM_FUNCTION
+#endif
+
+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)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> copy(%s,%s)\n",
+ (server_active) ? 'S' : ' ', from, to);
+#else
+ (void) fprintf (stderr, "-> copy(%s,%s)\n", from, to);
+#endif
+ if (noexec)
+ return;
+
+ if ((fdin = open (from, O_RDONLY)) < 0)
+ error (1, errno, "cannot open %s for copying", from);
+ if (fstat (fdin, &sb) < 0)
+ error (1, errno, "cannot fstat %s", from);
+ if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0)
+ error (1, errno, "cannot create %s for copying", to);
+ if (sb.st_size > 0)
+ {
+ 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 (lstat (file, &sb) < 0)
+ return (0);
+ return (S_ISLNK (sb.st_mode));
+#else
+ return (0);
+#endif
+}
+
+/*
+ * Returns non-zero if the argument file exists.
+ */
+int
+isfile (file)
+ 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);
+}
+
+/*
+ * Change the mode of a file, either adding write permissions, or removing
+ * all write permissions. Either change honors the current umask setting.
+ */
+void
+xchmod (fname, writable)
+ char *fname;
+ int writable;
+{
+ struct stat sb;
+ mode_t mode, oumask;
+
+ 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)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> chmod(%s,%o)\n",
+ (server_active) ? 'S' : ' ', fname,
+ (unsigned int) mode);
+#else
+ (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname,
+ (unsigned int) mode);
+#endif
+ 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)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> rename(%s,%s)\n",
+ (server_active) ? 'S' : ' ', from, to);
+#else
+ (void) fprintf (stderr, "-> rename(%s,%s)\n", from, to);
+#endif
+ if (noexec)
+ return;
+
+ if (rename (from, to) < 0)
+ error (1, errno, "cannot rename file %s to %s", from, to);
+}
+
+/*
+ * link a file, if possible. Warning: the Windows NT version of this
+ * function just copies the file, so only use this function in ways
+ * that can deal with either a link or a copy.
+ */
+int
+link_file (from, to)
+ const char *from;
+ const char *to;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> link(%s,%s)\n",
+ (server_active) ? 'S' : ' ', from, to);
+#else
+ (void) fprintf (stderr, "-> link(%s,%s)\n", from, to);
+#endif
+ if (noexec)
+ return (0);
+
+ return (link (from, to));
+}
+
+/*
+ * unlink a file, if possible.
+ */
+int
+unlink_file (f)
+ const char *f;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> unlink(%s)\n",
+ (server_active) ? 'S' : ' ', f);
+#else
+ (void) fprintf (stderr, "-> unlink(%s)\n", f);
+#endif
+ 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;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n",
+ (server_active) ? 'S' : ' ', f);
+#else
+ (void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
+#endif
+ 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 isdir() to see if it is OK to call unlink(). This
+ doesn't quite work--if someone creates a directory between the
+ call to isdir() and the call to unlink(), we'll still corrupt
+ the filesystem. Where is the Unix Haters Handbook when you need
+ it? */
+ if (isdir(f))
+ return deep_remove_dir(f);
+ else
+ {
+ if (unlink (f) != 0)
+ return -1;
+ }
+ /* We were able to remove the file from the disk */
+ return 0;
+}
+
+/* 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;
+ char buf[PATH_MAX];
+
+ if (rmdir (path) != 0 && (errno == ENOTEMPTY || errno == EEXIST))
+ {
+ if ((dirp = opendir (path)) == NULL)
+ /* If unable to open the directory return
+ * an error
+ */
+ return -1;
+
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 ||
+ strcmp (dp->d_name, "..") == 0)
+ continue;
+
+ 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);
+ return -1;
+ }
+ }
+ else
+ {
+ if (unlink (buf) != 0)
+ {
+ closedir(dirp);
+ return -1;
+ }
+ }
+ }
+ closedir (dirp);
+ return rmdir (path);
+ }
+
+ /* 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.
+ */
+int
+xcmp (file1, file2)
+ const char *file1;
+ const char *file2;
+{
+ char *buf1, *buf2;
+ struct stat sb1, sb2;
+ int fd1, fd2;
+ int ret;
+
+ if ((fd1 = open (file1, O_RDONLY)) < 0)
+ error (1, errno, "cannot open file %s for comparing", file1);
+ if ((fd2 = open (file2, O_RDONLY)) < 0)
+ error (1, errno, "cannot open file %s for comparing", file2);
+ if (fstat (fd1, &sb1) < 0)
+ error (1, errno, "cannot fstat %s", file1);
+ if (fstat (fd2, &sb2) < 0)
+ error (1, errno, "cannot fstat %s", 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);
+}
+
+#ifdef LOSING_TMPNAM_FUNCTION
+char *tmpnam(char *s)
+{
+ static char value[L_tmpnam+1];
+
+ if (s){
+ strcpy(s,"/tmp/cvsXXXXXX");
+ mktemp(s);
+ return s;
+ }else{
+ strcpy(value,"/tmp/cvsXXXXXX");
+ mktemp(s);
+ return value;
+ }
+}
+#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 pointer into PATH's last component. */
+char *
+last_component (path)
+ char *path;
+{
+ char *last = strrchr (path, '/');
+
+ if (last)
+ return last + 1;
+ else
+ return path;
+}
+
+/* Return the home directory. Returns a pointer to storage
+ managed by this function or its callees (currently getenv). */
+char *
+get_homedir ()
+{
+ return getenv ("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]);
+}
diff --git a/contrib/cvs/src/find_names.c b/contrib/cvs/src/find_names.c
new file mode 100644
index 0000000..4885437
--- /dev/null
+++ b/contrib/cvs/src/find_names.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Find Names
+ *
+ * Finds all the pertinent file names, both from the administration and from the
+ * repository
+ *
+ * Find Dirs
+ *
+ * Finds all pertinent sub-directories of the checked out instantiation and the
+ * repository (and optionally the attic)
+ */
+
+#include "cvs.h"
+
+static int find_dirs PROTO((char *dir, List * list, int checkadm));
+static int find_rcs PROTO((char *dir, List * list));
+
+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;
+{
+ Node *fnode;
+
+ fnode = getnode ();
+ fnode->type = FILES;
+ fnode->key = xstrdup (node->key);
+ if (addnode (filelist, fnode) != 0)
+ freenode (fnode);
+ return (0);
+}
+
+/*
+ * compare two files list node (for sort)
+ */
+static int fsortcmp PROTO ((const Node *, const Node *));
+static int
+fsortcmp (p, q)
+ const Node *p;
+ const Node *q;
+{
+ return (strcmp (p->key, q->key));
+}
+
+List *
+Find_Names (repository, which, aflag, optentries)
+ char *repository;
+ int which;
+ int aflag;
+ List **optentries;
+{
+ List *entries;
+ List *files;
+ char dir[PATH_MAX];
+
+ /* make a list for the files */
+ files = filelist = getlist ();
+
+ /* look at entries (if necessary) */
+ if (which & W_LOCAL)
+ {
+ /* parse the entries file (if it exists) */
+ entries = Entries_Open (aflag);
+ 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 (1, errno, "cannot open directory %s", repository);
+
+ /* search the attic too */
+ if (which & W_ATTIC)
+ {
+ (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
+ (void) find_rcs (dir, files);
+ }
+ }
+
+ /* sort the list into alphabetical order and return it */
+ sortlist (files, fsortcmp);
+ return (files);
+}
+
+/*
+ * create a list of directories to traverse from the current directory
+ */
+List *
+Find_Directories (repository, which)
+ char *repository;
+ int which;
+{
+ List *dirlist;
+
+ /* make a list for the directories */
+ dirlist = getlist ();
+
+ /* find the local ones */
+ if (which & W_LOCAL)
+ {
+ /* look only for CVS controlled sub-directories */
+ if (find_dirs (".", dirlist, 1) != 0)
+ error (1, errno, "cannot open current directory");
+ }
+
+ /* look for sub-dirs in the repository */
+ if ((which & W_REPOS) && repository)
+ {
+ /* search the repository */
+ if (find_dirs (repository, dirlist, 0) != 0)
+ error (1, errno, "cannot open directory %s", repository);
+
+#ifdef ATTIC_DIR_SUPPORT /* XXX - FIXME */
+ /* search the attic too */
+ if (which & W_ATTIC)
+ {
+ char dir[PATH_MAX];
+
+ (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
+ (void) find_dirs (dir, dirlist, 0);
+ }
+#endif
+ }
+
+ /* sort the list into alphabetical order and return it */
+ sortlist (dirlist, fsortcmp);
+ return (dirlist);
+}
+
+/*
+ * Finds all the ,v files in the argument directory, and adds them to the
+ * files list. Returns 0 for success and non-zero if the argument directory
+ * cannot be opened.
+ */
+static int
+find_rcs (dir, list)
+ char *dir;
+ List *list;
+{
+ Node *p;
+ struct dirent *dp;
+ DIR *dirp;
+
+ /* set up to read the dir */
+ if ((dirp = opendir (dir)) == NULL)
+ return (1);
+
+ /* read the dir, grabbing the ,v files */
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (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);
+ }
+ }
+ (void) closedir (dirp);
+ return (0);
+}
+
+/*
+ * Finds all the subdirectories of the argument dir and adds them to the
+ * specified list. Sub-directories without a CVS administration directory
+ * are optionally ignored Returns 0 for success or 1 on error.
+ */
+static int
+find_dirs (dir, list, checkadm)
+ char *dir;
+ List *list;
+ int checkadm;
+{
+ Node *p;
+ char tmp[PATH_MAX];
+ struct dirent *dp;
+ DIR *dirp;
+
+ /* set up to read the dir */
+ if ((dirp = opendir (dir)) == NULL)
+ return (1);
+
+ /* read the dir, grabbing sub-dirs */
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 ||
+ strcmp (dp->d_name, "..") == 0 ||
+ strcmp (dp->d_name, CVSATTIC) == 0 ||
+ strcmp (dp->d_name, CVSLCK) == 0 ||
+ strcmp (dp->d_name, CVSREP) == 0)
+ continue;
+
+#ifdef DT_DIR
+ if (dp->d_type != DT_DIR)
+ {
+ if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK)
+ continue;
+#endif
+ /* don't bother stating ,v files */
+ if (fnmatch (RCSPAT, dp->d_name, 0) == 0)
+ continue;
+
+ sprintf (tmp, "%s/%s", dir, dp->d_name);
+ if (!isdir (tmp))
+ continue;
+
+#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)
+ continue;
+#endif
+ if (islink (tmp))
+ continue;
+#ifdef DT_DIR
+ }
+#endif
+
+ /* check for new style */
+ (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM);
+ if (!isdir (tmp))
+ continue;
+ }
+
+ /* put it in the list */
+ p = getnode ();
+ p->type = DIRS;
+ p->key = xstrdup (dp->d_name);
+ if (addnode (list, p) != 0)
+ freenode (p);
+ }
+ (void) closedir (dirp);
+ return (0);
+}
diff --git a/contrib/cvs/src/hash.c b/contrib/cvs/src/hash.c
new file mode 100644
index 0000000..2197db0
--- /dev/null
+++ b/contrib/cvs/src/hash.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Polk's hash list manager. So cool.
+ */
+
+#include "cvs.h"
+#include <assert.h>
+
+/* global caches */
+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 */
+ p->type = UNKNOWN;
+ p->next = nodecache;
+ nodecache = p;
+ }
+ }
+
+ /* put it on the cache */
+ (*listp)->next = listcache;
+ listcache = *listp;
+ *listp = (List *) NULL;
+}
+
+/*
+ * get a new list node
+ */
+Node *
+getnode ()
+{
+ Node *p;
+
+ if (nodecache != (Node *) NULL)
+ {
+ /* get one from the cache */
+ p = nodecache;
+ nodecache = p->next;
+ }
+ else
+ {
+ /* make a new one */
+ p = (Node *) xmalloc (sizeof (Node));
+ }
+
+ /* always make it clean */
+ memset ((char *) p, 0, sizeof (Node));
+ p->type = UNKNOWN;
+
+ return (p);
+}
+
+/*
+ * remove a node from it's list (maybe hash list too) and free it
+ */
+void
+delnode (p)
+ Node *p;
+{
+ if (p == (Node *) NULL)
+ return;
+
+ /* take it out of the list */
+ p->next->prev = p->prev;
+ p->prev->next = p->next;
+
+ /* if it was hashed, remove it from there too */
+ if (p->hashnext != (Node *) NULL)
+ {
+ p->hashnext->hashprev = p->hashprev;
+ p->hashprev->hashnext = p->hashnext;
+ }
+
+ /* free up the storage */
+ freenode (p);
+}
+
+/*
+ * free up the storage associated with a node
+ */
+static void
+freenode_mem (p)
+ Node *p;
+{
+ if (p->delproc != (void (*) ()) NULL)
+ p->delproc (p); /* call the specified delproc */
+ else
+ {
+ if (p->data != NULL) /* otherwise free() it if necessary */
+ free (p->data);
+ }
+ if (p->key != NULL) /* free the key if necessary */
+ free (p->key);
+
+ /* to be safe, re-initialize these */
+ p->key = p->data = (char *) NULL;
+ p->delproc = (void (*) ()) NULL;
+}
+
+/*
+ * free up the storage associated with a node and recycle it
+ */
+void
+freenode (p)
+ Node *p;
+{
+ /* first free the memory */
+ freenode_mem (p);
+
+ /* then put it in the cache */
+ p->type = UNKNOWN;
+ p->next = nodecache;
+ nodecache = p;
+}
+
+/*
+ * insert item p at end of list "list" (maybe hash it too) if hashing and it
+ * already exists, return -1 and don't actually put it in the list
+ *
+ * return 0 on success
+ */
+int
+addnode (list, p)
+ List *list;
+ Node *p;
+{
+ int hashval;
+ Node *q;
+
+ if (p->key != NULL) /* hash it too? */
+ {
+ hashval = hashp (p->key);
+ if (list->hasharray[hashval] == NULL) /* make a header for list? */
+ {
+ q = getnode ();
+ q->type = HEADER;
+ list->hasharray[hashval] = q->hashnext = q->hashprev = q;
+ }
+
+ /* put it into the hash list if it's not already there */
+ for (q = list->hasharray[hashval]->hashnext;
+ q != list->hasharray[hashval]; q = q->hashnext)
+ {
+ if (strcmp (p->key, q->key) == 0)
+ return (-1);
+ }
+ q = list->hasharray[hashval];
+ p->hashprev = q->hashprev;
+ p->hashnext = q;
+ p->hashprev->hashnext = p;
+ q->hashprev = p;
+ }
+
+ /* put it into the regular list */
+ p->prev = list->list->prev;
+ p->next = list->list;
+ list->list->prev->next = p;
+ list->list->prev = p;
+
+ return (0);
+}
+
+/* Look up an entry in hash list table 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;
+}
+
+/*
+ * 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, *q;
+
+ /* save the old first element of the list */
+ head = list->list;
+ remain = head->next;
+
+ /* make the header node into a null list of it's own */
+ head->next = head->prev = head;
+
+ /* while there are nodes remaining, do insert sort */
+ while (remain != head)
+ {
+ /* take one from the list */
+ p = remain;
+ remain = remain->next;
+
+ /* traverse the sorted list looking for the place to insert it */
+ for (q = head->next; q != head; q = q->next)
+ {
+ if (comp (p, q) < 0)
+ {
+ /* p comes before q */
+ p->next = q;
+ p->prev = q->prev;
+ p->prev->next = p;
+ q->prev = p;
+ break;
+ }
+ }
+ if (q == head)
+ {
+ /* it belongs at the end of the list */
+ p->next = head;
+ p->prev = head->prev;
+ p->prev->next = p;
+ head->prev = p;
+ }
+ }
+}
+
+/* Debugging functions. Quite useful to call from within gdb. */
+
+char *
+nodetypestring (type)
+ Ntype type;
+{
+ switch (type) {
+ case 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");
+ }
+
+ 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);
+}
+
+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/contrib/cvs/src/hash.h b/contrib/cvs/src/hash.h
new file mode 100644
index 0000000..dd83665
--- /dev/null
+++ b/contrib/cvs/src/hash.h
@@ -0,0 +1,58 @@
+/* $CVSid: @(#)hash.h 1.23 94/10/07 $ */
+
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ */
+
+/*
+ * The number of buckets for the hash table contained in each list. This
+ * should probably be prime.
+ */
+#define HASHSIZE 151
+
+/*
+ * Types of nodes
+ */
+enum ntype
+{
+ UNKNOWN, HEADER, ENTRIES, FILES, LIST, RCSNODE,
+ RCSVERS, DIRS, UPDATE, LOCK, NDBMNODE, FILEATTR,
+ VARIABLE
+};
+typedef enum ntype Ntype;
+
+struct node
+{
+ Ntype type;
+ struct node *next;
+ struct node *prev;
+ struct node *hashnext;
+ struct node *hashprev;
+ char *key;
+ char *data;
+ void (*delproc) ();
+};
+typedef struct node Node;
+
+struct list
+{
+ Node *list;
+ Node *hasharray[HASHSIZE];
+ struct list *next;
+};
+typedef struct list List;
+
+List *getlist PROTO((void));
+Node *findnode PROTO((List * list, const char *key));
+Node *findnode_fn PROTO((List * list, const char *key));
+Node *getnode PROTO((void));
+int addnode PROTO((List * list, Node * p));
+int walklist PROTO((List * list, int (*)(Node *n, void *closure), void *closure));
+int list_isempty PROTO ((List *list));
+void dellist PROTO((List ** listp));
+void delnode PROTO((Node * p));
+void freenode PROTO((Node * p));
+void sortlist PROTO((List * list, int (*)(const Node *, const Node *)));
diff --git a/contrib/cvs/src/history.c b/contrib/cvs/src/history.c
new file mode 100644
index 0000000..81c71ff
--- /dev/null
+++ b/contrib/cvs/src/history.c
@@ -0,0 +1,1484 @@
+/*
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS 1.0 kit.
+ *
+ * **************** History of Users and Module ****************
+ *
+ * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
+ *
+ * On For each Tag, Add, Checkout, Commit, Update or Release command,
+ * one line of text is written to a History log.
+ *
+ * X date | user | CurDir | special | rev(s) | argument '\n'
+ *
+ * where: [The spaces in the example line above are not in the history file.]
+ *
+ * X is a single character showing the type of event:
+ * T "Tag" cmd.
+ * O "Checkout" cmd.
+ * F "Release" cmd.
+ * W "Update" cmd - No User file, Remove from Entries file.
+ * U "Update" cmd - File was checked out over User file.
+ * G "Update" cmd - File was merged successfully.
+ * C "Update" cmd - File was merged and shows overlaps.
+ * M "Commit" cmd - "Modified" file.
+ * A "Commit" cmd - "Added" file.
+ * R "Commit" cmd - "Removed" file.
+ *
+ * date is a fixed length 8-char hex representation of a Unix time_t.
+ * [Starting here, variable fields are delimited by '|' chars.]
+ *
+ * user is the username of the person who typed the command.
+ *
+ * CurDir The directory where the action occurred. This should be the
+ * absolute path of the directory which is at the same level as
+ * the "Repository" field (for W,U,G,C & M,A,R).
+ *
+ * Repository For record types [W,U,G,C,M,A,R] this field holds the
+ * repository read from the administrative data where the
+ * command was typed.
+ * T "A" --> New Tag, "D" --> Delete Tag
+ * Otherwise it is the Tag or Date to modify.
+ * O,F A "" (null field)
+ *
+ * rev(s) Revision number or tag.
+ * T The Tag to apply.
+ * O The Tag or Date, if specified, else "" (null field).
+ * F "" (null field)
+ * W The Tag or Date, if specified, else "" (null field).
+ * U The Revision checked out over the User file.
+ * G,C The Revision(s) involved in merge.
+ * M,A,R RCS Revision affected.
+ *
+ * argument The module (for [TOUF]) or file (for [WUGCMAR]) affected.
+ *
+ *
+ *** Report categories: "User" and "Since" modifiers apply to all reports.
+ * [For "sort" ordering see the "sort_order" routine.]
+ *
+ * Extract list of record types
+ *
+ * -e, -x [TOFWUGCMAR]
+ *
+ * Extracted records are simply printed, No analysis is performed.
+ * All "field" modifiers apply. -e chooses all types.
+ *
+ * Checked 'O'ut modules
+ *
+ * -o, -w
+ * Checked out modules. 'F' and 'O' records are examined and if
+ * the last record for a repository/file is an 'O', a line is
+ * printed. "-w" forces the "working dir" to be used in the
+ * comparison instead of the repository.
+ *
+ * Committed (Modified) files
+ *
+ * -c, -l, -w
+ * All 'M'odified, 'A'dded and 'R'emoved records are examined.
+ * "Field" modifiers apply. -l forces a sort by file within user
+ * and shows only the last modifier. -w works as in Checkout.
+ *
+ * Warning: Be careful with what you infer from the output of
+ * "cvs hi -c -l". It means the last time *you*
+ * changed the file, not the list of files for which
+ * you were the last changer!!!
+ *
+ * Module history for named modules.
+ * -m module, -l
+ *
+ * This is special. If one or more modules are specified, the
+ * module names are remembered and the files making up the
+ * modules are remembered. Only records matching exactly those
+ * files and repositories are shown. Sorting by "module", then
+ * filename, is implied. If -l ("last modified") is specified,
+ * then "update" records (types WUCG), tag and release records
+ * are ignored and the last (by date) "modified" record.
+ *
+ * TAG history
+ *
+ * -T All Tag records are displayed.
+ *
+ *** Modifiers.
+ *
+ * Since ... [All records contain a timestamp, so any report
+ * category can be limited by date.]
+ *
+ * -D date - The "date" is parsed into a Unix "time_t" and
+ * records with an earlier time stamp are ignored.
+ * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
+ * you use this option, every file is searched for the
+ * indicated rev/tag.
+ * -t tag - The "tag" is searched for in the history file and no
+ * record is displayed before the tag is found. An
+ * error is printed if the tag is never found.
+ * -b string - Records are printed only back to the last reference
+ * to the string in the "module", "file" or
+ * "repository" fields.
+ *
+ * Field Selections [Simple comparisons on existing fields. All field
+ * selections are repeatable.]
+ *
+ * -a - All users.
+ * -u user - If no user is given and '-a' is not given, only
+ * records for the user typing the command are shown.
+ * ==> If -a or -u is not specified, just use "self".
+ *
+ * -f filematch - Only records in which the "file" field contains the
+ * string "filematch" are considered.
+ *
+ * -p repository - Only records in which the "repository" string is a
+ * prefix of the "repos" field are considered.
+ *
+ * -m modulename - Only records which contain "modulename" in the
+ * "module" field are considered.
+ *
+ *
+ * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
+ *
+ *** Checked out files for username. (default self, e.g. "dgg")
+ * cvs hi [equivalent to: "cvs hi -o -u dgg"]
+ * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
+ * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
+ *
+ *** Committed (modified) files from the beginning of the file.
+ * cvs hi -c [-u user]
+ *
+ *** Committed (modified) files since Midnight, January 1, 1990:
+ * cvs hi -c -D 'Jan 1 1990' [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was stored in the history file:
+ * cvs hi -c -t TAG [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was placed on the files:
+ * cvs hi -c -r TAG [-u user]
+ *
+ *** Who last committed file/repository X?
+ * cvs hi -c -l -[fp] X
+ *
+ *** Modified files since tag/date/file/repos?
+ * cvs hi -c {-r TAG | -D Date | -b string}
+ *
+ *** Tag history
+ * cvs hi -T
+ *
+ *** History of file/repository/module X.
+ * cvs hi -[fpn] X
+ *
+ *** History of user "user".
+ * cvs hi -e -u user
+ *
+ *** Dump (eXtract) specified record types
+ * cvs hi -x [TOFWUGCMAR]
+ *
+ *
+ * FUTURE: J[Join], I[Import] (Not currently implemented.)
+ *
+ */
+
+#include "cvs.h"
+
+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 char *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 time_t date_and_time PROTO((char *date_str));
+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 "TOFWUCGMAR"
+#define USER_INCREMENT 2
+#define FILE_INCREMENT 128
+#define MODULE_INCREMENT 5
+#define HREC_INCREMENT 128
+
+static short report_count;
+
+static short extract;
+static short v_checkout;
+static short modified;
+static short tag_report;
+static short module_report;
+static short working;
+static short last_entry;
+static short all_users;
+
+static short user_sort;
+static short repos_sort;
+static short file_sort;
+static short module_sort;
+
+#ifdef HAVE_RCS5
+static short tz_local;
+static time_t tz_seconds_east_of_GMT;
+static char *tz_name = "+0000";
+#else
+static char tz_name[] = "LT";
+#endif
+
+static time_t since_date;
+static char since_rev[20]; /* Maxrev ~= 99.99.99.999 */
+static char since_tag[64];
+static struct hrec *last_since_tag;
+static char backto[128];
+static struct hrec *last_backto;
+static char rec_types[20];
+
+static int hrec_count;
+static int hrec_max;
+
+static char **user_list; /* Ptr to array of ptrs to user names */
+static int user_max; /* Number of elements allocated */
+static int user_count; /* Number of elements used */
+
+static struct file_list_str
+{
+ char *l_file;
+ char *l_module;
+} *file_list; /* Ptr to array file name structs */
+static int file_max; /* Number of elements allocated */
+static int file_count; /* Number of elements used */
+
+static char **mod_list; /* Ptr to array of ptrs to module names */
+static int mod_max; /* Number of elements allocated */
+static int mod_count; /* Number of elements used */
+
+static char *histfile; /* Ptr to the history file name */
+
+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 [TOFWUCGMAR] Extract by record type\n",
+ " Flags:\n",
+ " -a All users (Default is self)\n",
+ " -e Everything (same as -x, but all record types)\n",
+ " -l Last modified (committed or modified report)\n",
+ " -w Working directory must match\n",
+ " Options:\n",
+ " -D <date> Since date (Many formats)\n",
+ " -b <str> Back to record with str in module/file/repos field\n",
+ " -f <file> Specified file (same as command line) (repeatable)\n",
+ " -n <modulename> In module (repeatable)\n",
+ " -p <repos> In repository (repeatable)\n",
+ " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
+ " -t <tag> Since tag record placed in history file (by anyone).\n",
+ " -u <user> For user name (repeatable)\n",
+ " -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);
+}
+
+static time_t
+date_and_time (date_str)
+ char *date_str;
+{
+ time_t t;
+
+ t = get_date (date_str, (struct timeb *) NULL);
+ if (t == (time_t) - 1)
+ error (1, 0, "Can't parse date/time: %s", date_str);
+ return (t);
+}
+
+int
+history (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i, c;
+ char fname[PATH_MAX];
+
+ if (argc == -1)
+ usage (history_usg);
+
+ optind = 1;
+ while ((c = 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++;
+ (void) strcpy (rec_types, ALL_REC_TYPES);
+ break;
+ case 'l': /* Find Last file record */
+ last_entry = 1;
+ break;
+ case 'o':
+ report_count++;
+ v_checkout = 1;
+ break;
+ case 'w': /* Match Working Dir (CurDir) fields */
+ working = 1;
+ break;
+ case 'X': /* Undocumented debugging flag */
+ histfile = optarg;
+ break;
+ case 'D': /* Since specified date */
+ if (*since_rev || *since_tag || *backto)
+ {
+ error (0, 0, "date overriding rev/tag/backto");
+ *since_rev = *since_tag = *backto = '\0';
+ }
+ since_date = date_and_time (optarg);
+ break;
+ case 'b': /* Since specified file/Repos */
+ if (since_date || *since_rev || *since_tag)
+ {
+ error (0, 0, "backto overriding date/rev/tag");
+ *since_rev = *since_tag = '\0';
+ since_date = 0;
+ }
+ if (strlen (optarg) >= sizeof (backto))
+ {
+ error (0, 0, "backto truncated to %d bytes",
+ sizeof (backto) - 1);
+ optarg[sizeof (backto) - 1] = '\0';
+ }
+ (void) strcpy (backto, optarg);
+ break;
+ case 'f': /* For specified file */
+ save_file ("", optarg, (char *) NULL);
+ break;
+ case 'm': /* Full module report */
+ report_count++;
+ module_report++;
+ case 'n': /* Look for specified module */
+ save_module (optarg);
+ break;
+ case 'p': /* For specified directory */
+ save_file (optarg, "", (char *) NULL);
+ break;
+ case 'r': /* Since specified Tag/Rev */
+ if (since_date || *since_tag || *backto)
+ {
+ error (0, 0, "rev overriding date/tag/backto");
+ *since_tag = *backto = '\0';
+ since_date = 0;
+ }
+ (void) strcpy (since_rev, optarg);
+ break;
+ case 't': /* Since specified Tag/Rev */
+ if (since_date || *since_rev || *backto)
+ {
+ error (0, 0, "tag overriding date/marker/file/repos");
+ *since_rev = *backto = '\0';
+ since_date = 0;
+ }
+ (void) strcpy (since_tag, optarg); /* tag */
+ break;
+ case 'u': /* For specified username */
+ save_user (optarg);
+ break;
+ case 'x':
+ report_count++;
+ extract++;
+ {
+ char *cp;
+
+ for (cp = optarg; *cp; cp++)
+ if (!strchr (ALL_REC_TYPES, *cp))
+ error (1, 0, "%c is not a valid report type", *cp);
+ }
+ (void) strcpy (rec_types, optarg);
+ break;
+ case 'z':
+#ifndef HAVE_RCS5
+ error (0, 0, "-z not supported with RCS 4");
+#else
+ 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;
+ }
+ }
+#endif
+ break;
+ case '?':
+ default:
+ usage (history_usg);
+ break;
+ }
+ }
+ c = optind; /* Save the handled option count */
+
+ /* ================ Now analyze the arguments a bit */
+ if (!report_count)
+ v_checkout++;
+ else if (report_count > 1)
+ error (1, 0, "Only one report type allowed from: \"-Tcomx\".");
+
+#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)
+ option_with_arg ("-D", asctime (gmtime (&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 != NULL)
+ option_with_arg ("-r", since_rev);
+ if (since_tag != NULL)
+ 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'))
+ (void) strcat (rec_types, "T");
+ }
+ else if (extract)
+ {
+ if (user_list)
+ user_sort++;
+ }
+ else if (modified)
+ {
+ (void) strcpy (rec_types, "MAR");
+ /*
+ * If the user has not specified a date oriented flag ("Since"), sort
+ * by Repository/file before date. Default is "just" date.
+ */
+ if (!since_date && !*since_rev && !*since_tag && !*backto)
+ {
+ repos_sort++;
+ file_sort++;
+ /*
+ * If we are not looking for last_modified and the user specified
+ * one or more users to look at, sort by user before filename.
+ */
+ if (!last_entry && user_list)
+ user_sort++;
+ }
+ }
+ else if (module_report)
+ {
+ (void) strcpy (rec_types, last_entry ? "OMAR" : ALL_REC_TYPES);
+ module_sort++;
+ repos_sort++;
+ file_sort++;
+ working = 0; /* User's workdir doesn't count here */
+ }
+ else
+ /* Must be "checkout" or default */
+ {
+ (void) strcpy (rec_types, "OF");
+ /* See comments in "modified" above */
+ if (!last_entry && user_list)
+ user_sort++;
+ if (!since_date && !*since_rev && !*since_tag && !*backto)
+ file_sort++;
+ }
+
+ /* If no users were specified, use self (-a saves a universal ("") user) */
+ if (!user_list)
+ save_user (getcaller ());
+
+ /* If we're looking back to a Tag value, must consider "Tag" records */
+ if (*since_tag && !strchr (rec_types, 'T'))
+ (void) strcat (rec_types, "T");
+
+ argc -= c;
+ argv += c;
+ for (i = 0; i < argc; i++)
+ save_file ("", argv[i], (char *) NULL);
+
+ if (histfile)
+ (void) strcpy (fname, histfile);
+ else
+ (void) sprintf (fname, "%s/%s/%s", CVSroot,
+ CVSROOTADM, CVSROOTADM_HISTORY);
+
+ read_hrecs (fname);
+ qsort ((PTR) hrec_head, hrec_count, sizeof (struct hrec), sort_order);
+ report_hrecs ();
+
+ return (0);
+}
+
+void
+history_write (type, update_dir, revs, name, repository)
+ int type;
+ char *update_dir;
+ char *revs;
+ char *name;
+ char *repository;
+{
+ char fname[PATH_MAX], workdir[PATH_MAX], homedir[PATH_MAX];
+ 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;
+ (void) sprintf (fname, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_HISTORY);
+
+ /* turn off history logging if the history file does not exist */
+ if (!isfile (fname))
+ {
+ logoff = 1;
+ return;
+ }
+
+ if (trace)
+#ifdef SERVER_SUPPORT
+ fprintf (stderr, "%c-> fopen(%s,a)\n",
+ (server_active) ? 'S' : ' ', fname);
+#else
+ fprintf (stderr, "-> fopen(%s,a)\n", fname);
+#endif
+ if (noexec)
+ return;
+ fd = 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" */
+ if (!getwd (workdir))
+ error (1, errno, "can't getwd in history");
+ if (chdir (pwdir) < 0)
+ error (1, errno, "can't chdir(%s)", pwdir);
+ if (!getwd (homedir))
+ error (1, errno, "can't getwd in %s", pwdir);
+ (void) chdir (workdir);
+
+ i = strlen (homedir);
+ if (!strncmp (CurDir, homedir, i))
+ {
+ PrCurDir += i; /* Point to '/' separator */
+ tilde = "~";
+ }
+ }
+ }
+ }
+
+ if (type == 'T')
+ {
+ repos = update_dir;
+ update_dir = "";
+ }
+ else if (update_dir && *update_dir)
+ slash = "/";
+ else
+ update_dir = "";
+
+ (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
+
+ /*
+ * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
+ * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
+ *
+ * "$workdir/$name" is the working file name.
+ * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
+ *
+ * First, note that the history format was intended to save space, not
+ * to be human readable.
+ *
+ * The working file directory ("workdir") and the Repository ("repos")
+ * usually end with the same one or more directory elements. To avoid
+ * duplication (and save space), the "workdir" field ends with
+ * an integer offset into the "repos" field. This offset indicates the
+ * beginning of the "tail" of "repos", after which all characters are
+ * duplicates.
+ *
+ * In other words, if the "workdir" field has a '*' (a very stupid thing
+ * to put in a filename) in it, then every thing following the last '*'
+ * is a hex offset into "repos" of the first character from "repos" to
+ * append to "workdir" to finish the pathname.
+ *
+ * It might be easier to look at an example:
+ *
+ * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+ *
+ * Indicates that the workdir is really "~/work/cvs/examples", saving
+ * 10 characters, where "~/work*d" would save 6 characters and mean that
+ * the workdir is really "~/work/examples". It will mean more on
+ * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
+ *
+ * "workdir" is always an absolute pathname (~/xxx is an absolute path)
+ * "repos" is always a relative pathname. So we can assume that we will
+ * never run into the top of "workdir" -- there will always be a '/' or
+ * a '~' at the head of "workdir" that is not matched by anything in
+ * "repos". On the other hand, we *can* run off the top of "repos".
+ *
+ * Only "compress" if we save characters.
+ */
+
+ if (!repos)
+ repos = "";
+
+ cp = workdir + strlen (workdir) - 1;
+ cp2 = repos + strlen (repos) - 1;
+ for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
+ i++;
+
+ if (i > 2)
+ {
+ i = strlen (repos) - i;
+ (void) sprintf ((cp + 1), "*%x", i);
+ }
+
+ if (!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);
+}
+
+/*
+ * save_user() adds a user name to the user list to select. Zero-length
+ * username ("") matches any user.
+ */
+static void
+save_user (name)
+ char *name;
+{
+ if (user_count == user_max)
+ {
+ user_max += USER_INCREMENT;
+ user_list = (char **) xrealloc ((char *) user_list,
+ (int) user_max * sizeof (char *));
+ }
+ user_list[user_count++] = xstrdup (name);
+}
+
+/*
+ * save_file() adds file name and associated module to the file list to select.
+ *
+ * If "dir" is null, store a file name as is.
+ * If "name" is null, store a directory name with a '*' on the front.
+ * Else, store concatenated "dir/name".
+ *
+ * Later, in the "select" stage:
+ * - if it starts with '*', it is prefix-matched against the repository.
+ * - if it has a '/' in it, it is matched against the repository/file.
+ * - else it is matched against the file name.
+ */
+static void
+save_file (dir, name, module)
+ char *dir;
+ char *name;
+ char *module;
+{
+ char *cp;
+ struct file_list_str *fl;
+
+ if (file_count == file_max)
+ {
+ file_max += FILE_INCREMENT;
+ file_list = (struct file_list_str *) xrealloc ((char *) file_list,
+ file_max * sizeof (*fl));
+ }
+ fl = &file_list[file_count++];
+ fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
+ fl->l_module = module;
+
+ if (dir && *dir)
+ {
+ if (name && *name)
+ {
+ (void) strcpy (cp, dir);
+ (void) strcat (cp, "/");
+ (void) strcat (cp, name);
+ }
+ else
+ {
+ *cp++ = '*';
+ (void) strcpy (cp, dir);
+ }
+ }
+ else
+ {
+ if (name && *name)
+ {
+ (void) strcpy (cp, name);
+ }
+ else
+ {
+ error (0, 0, "save_file: null dir and file name");
+ }
+ }
+}
+
+static void
+save_module (module)
+ char *module;
+{
+ if (mod_count == mod_max)
+ {
+ mod_max += MODULE_INCREMENT;
+ mod_list = (char **) xrealloc ((char *) mod_list,
+ mod_max * sizeof (char *));
+ }
+ mod_list[mod_count++] = xstrdup (module);
+}
+
+static void
+expand_modules ()
+{
+}
+
+/* fill_hrec
+ *
+ * Take a ptr to 7-part history line, ending with a newline, for example:
+ *
+ * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+ *
+ * Split it into 7 parts and drop the parts into a "struct hrec".
+ * Return a pointer to the character following the newline.
+ */
+
+#define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return(rtn); *(line - 1) = '\0'; } while (0)
+
+static char *
+fill_hrec (line, hr)
+ char *line;
+ struct hrec *hr;
+{
+ char *cp, *rtn;
+ int c;
+ int off;
+ static int idx = 0;
+ unsigned long date;
+
+ memset ((char *) hr, 0, sizeof (*hr));
+ while (isspace (*line))
+ line++;
+ if (!(rtn = strchr (line, '\n')))
+ return ("");
+ *rtn++ = '\0';
+
+ hr->type = line++;
+ (void) sscanf (line, "%lx", &date);
+ hr->date = date;
+ while (*line && strchr ("0123456789abcdefABCDEF", *line))
+ line++;
+ if (*line == '\0')
+ return (rtn);
+
+ 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 ("FOT", *(hr->type)))
+ hr->mod = line;
+
+ NEXT_BAR (file); /* This returns ptr to next line or final '\0' */
+ return (rtn); /* If it falls through, go on to next record */
+}
+
+/* read_hrecs's job is to read the history file and fill in all the "hrec"
+ * (history record) array elements with the ones we need to print.
+ *
+ * Logic:
+ * - Read the whole history file into a single buffer.
+ * - Walk through the buffer, parsing lines out of the buffer.
+ * 1. Split line into pointer and integer fields in the "next" hrec.
+ * 2. Apply tests to the hrec to see if it is wanted.
+ * 3. If it *is* wanted, bump the hrec pointer down by one.
+ */
+static void
+read_hrecs (fname)
+ char *fname;
+{
+ char *cp, *cp2;
+ int i, fd;
+ struct hrec *hr;
+ struct stat st_buf;
+
+ if ((fd = open (fname, O_RDONLY | 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");
+
+ /* Exactly enough space for lines data */
+ if (!(i = st_buf.st_size))
+ error (1, 0, "history file is empty");
+ cp = xmalloc (i + 2);
+
+ if (read (fd, cp, i) != i)
+ error (1, errno, "cannot read log file");
+ (void) close (fd);
+
+ if (*(cp + i - 1) != '\n')
+ {
+ *(cp + i) = '\n'; /* Make sure last line ends in '\n' */
+ i++;
+ }
+ *(cp + i) = '\0';
+ for (cp2 = cp; cp2 - cp < i; cp2++)
+ {
+ if (*cp2 != '\n' && !isprint (*cp2))
+ *cp2 = ' ';
+ }
+
+ hrec_max = HREC_INCREMENT;
+ hrec_head = (struct hrec *) xmalloc (hrec_max * sizeof (struct hrec));
+
+ while (*cp)
+ {
+ if (hrec_count == hrec_max)
+ {
+ struct hrec *old_head = hrec_head;
+
+ hrec_max += HREC_INCREMENT;
+ hrec_head = (struct hrec *) xrealloc ((char *) hrec_head,
+ hrec_max * sizeof (struct hrec));
+ if (hrec_head != old_head)
+ {
+ if (last_since_tag)
+ last_since_tag = hrec_head + (last_since_tag - old_head);
+ if (last_backto)
+ last_backto = hrec_head + (last_backto - old_head);
+ }
+ }
+
+ hr = hrec_head + hrec_count;
+ cp = fill_hrec (cp, hr); /* cp == next line or '\0' at end of buffer */
+
+ if (select_hrec (hr))
+ hrec_count++;
+ }
+
+ /* Special selection problem: If "since_tag" is set, we have saved every
+ * record from the 1st occurrence of "since_tag", when we want to save
+ * records since the *last* occurrence of "since_tag". So what we have
+ * to do is bump hrec_head forward and reduce hrec_count accordingly.
+ */
+ if (last_since_tag)
+ {
+ hrec_count -= (last_since_tag - hrec_head);
+ hrec_head = last_since_tag;
+ }
+
+ /* Much the same thing is necessary for the "backto" option. */
+ if (last_backto)
+ {
+ hrec_count -= (last_backto - hrec_head);
+ hrec_head = last_backto;
+ }
+}
+
+/* Utility program for determining whether "find" is inside "string" */
+static int
+within (find, string)
+ char *find, *string;
+{
+ int c, len;
+
+ if (!find || !string)
+ return (0);
+
+ c = *find++;
+ len = strlen (find);
+
+ while (*string)
+ {
+ if (!(string = 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 a Unix time_t specified on the
+ * command line. hr->date fields earlier than "since_date" are ignored.
+ * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
+ * number (which is of limited use) or a symbolic TAG. Each RCS file
+ * is examined and the date on the specified revision (or the revision
+ * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
+ * compared against hr->date as in 1. above.
+ * 3. If "since_tag" is set, matching tag records are saved. The field
+ * "last_since_tag" is set to the last one of these. Since we don't
+ * know where the last one will be, all records are saved from the
+ * first occurrence of the TAG. Later, at the end of "select_hrec"
+ * records before the last occurrence of "since_tag" are skipped.
+ * 4. If "backto" is set, all records with a module name or file name
+ * matching "backto" are saved. In addition, all records with a
+ * repository field with a *prefix* matching "backto" are saved.
+ * The field "last_backto" is set to the last one of these. As in
+ * 3. above, "select_hrec" adjusts to include the last one later on.
+ */
+ if (since_date)
+ {
+ if (hr->date < since_date)
+ return (0);
+ }
+ else if (*since_rev)
+ {
+ Vers_TS *vers;
+ time_t t;
+
+ vers = Version_TS (hr->repos, (char *) NULL, since_rev, (char *) NULL,
+ hr->file, 1, 0, (List *) NULL, (RCSNode *) NULL);
+ if (vers->vn_rcs)
+ {
+ if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
+ != (time_t) 0)
+ {
+ if (hr->date < t)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ }
+ }
+ freevers_ts (&vers);
+ }
+ else if (*since_tag)
+ {
+ if (*(hr->type) == 'T')
+ {
+ /*
+ * A 'T'ag record, the "rev" field holds the tag to be set,
+ * while the "repos" field holds "D"elete, "A"dd or a rev.
+ */
+ if (within (since_tag, hr->rev))
+ {
+ last_since_tag = hr;
+ return (1);
+ }
+ else
+ return (0);
+ }
+ if (!last_since_tag)
+ return (0);
+ }
+ else if (*backto)
+ {
+ if (within (backto, hr->file) || within (backto, hr->mod) ||
+ within (backto, hr->repos))
+ last_backto = hr;
+ else
+ return (0);
+ }
+
+ /* User checking:
+ *
+ * Run down "user_list", match username ("" matches anything)
+ * If "" is not there and actual username is not there, return failure.
+ */
+ if (user_list && hr->user)
+ {
+ for (cpp = user_list, count = user_count; count; cpp++, count--)
+ {
+ if (!**cpp)
+ break; /* null user == accept */
+ if (!strcmp (hr->user, *cpp)) /* found listed user */
+ break;
+ }
+ if (!count)
+ return (0); /* Not this user */
+ }
+
+ /* Record type checking:
+ *
+ * 1. If Record type is not in rec_types field, skip it.
+ * 2. If mod_list is null, keep everything. Otherwise keep only modules
+ * on mod_list.
+ * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
+ * file_list is null, keep everything. Otherwise, keep only files on
+ * file_list, matched appropriately.
+ */
+ if (!strchr (rec_types, *(hr->type)))
+ return (0);
+ if (!strchr ("TFO", *(hr->type))) /* Don't bother with "file" if "TFO" */
+ {
+ if (file_list) /* If file_list is null, accept all */
+ {
+ for (fl = file_list, count = file_count; count; fl++, count--)
+ {
+ /* 1. If file_list entry starts with '*', skip the '*' and
+ * compare it against the repository in the hrec.
+ * 2. If file_list entry has a '/' in it, compare it against
+ * the concatenation of the repository and file from hrec.
+ * 3. Else compare the file_list entry against the hrec file.
+ */
+ char cmpfile[PATH_MAX];
+
+ if (*(cp = fl->l_file) == '*')
+ {
+ cp++;
+ /* if argument to -p is a prefix of repository */
+ if (!strncmp (cp, hr->repos, strlen (cp)))
+ {
+ hr->mod = fl->l_module;
+ break;
+ }
+ }
+ else
+ {
+ if (strchr (cp, '/'))
+ {
+ (void) sprintf (cp2 = cmpfile, "%s/%s",
+ hr->repos, hr->file);
+ }
+ else
+ {
+ cp2 = hr->file;
+ }
+
+ /* if requested file is found within {repos}/file fields */
+ if (within (cp, cp2))
+ {
+ hr->mod = fl->l_module;
+ break;
+ }
+ }
+ }
+ if (!count)
+ return (0); /* String specified and no match */
+ }
+ }
+ if (mod_list)
+ {
+ for (cpp = mod_list, count = mod_count; count; cpp++, count--)
+ {
+ if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
+ break;
+ }
+ if (!count)
+ return (0); /* Module specified & this record is not one of them. */
+ }
+
+ return (1); /* Select this record unless rejected above. */
+}
+
+/* The "sort_order" routine (when handed to qsort) has arranged for the
+ * hrecs files to be in the right order for the report.
+ *
+ * Most of the "selections" are done in the select_hrec routine, but some
+ * selections are more easily done after the qsort by "accept_hrec".
+ */
+static void
+report_hrecs ()
+{
+ struct hrec *hr, *lr;
+ struct tm *tm;
+ int i, count, ty;
+ char *cp;
+ int user_len, file_len, rev_len, mod_len, repos_len;
+
+ if (*since_tag && !last_since_tag)
+ {
+ (void) printf ("No tag found: %s\n", since_tag);
+ return;
+ }
+ else if (*backto && !last_backto)
+ {
+ (void) printf ("No module, file or repository with: %s\n", backto);
+ return;
+ }
+ else if (hrec_count < 1)
+ {
+ (void) printf ("No records selected.\n");
+ return;
+ }
+
+ user_len = file_len = rev_len = mod_len = repos_len = 0;
+
+ /* Run through lists and find maximum field widths */
+ hr = lr = hrec_head;
+ hr++;
+ for (count = hrec_count; count--; lr = hr, hr++)
+ {
+ char repos[PATH_MAX];
+
+ if (!count)
+ hr = NULL;
+ if (!accept_hrec (lr, hr))
+ continue;
+
+ ty = *(lr->type);
+ (void) strcpy (repos, lr->repos);
+ if ((cp = 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;
+ }
+
+ /* Walk through hrec array setting "lr" (Last Record) to each element.
+ * "hr" points to the record following "lr" -- It is NULL in the last
+ * pass.
+ *
+ * There are two sections in the loop below:
+ * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
+ * decide whether the record should be printed.
+ * 2. Based on the record type, format and print the data.
+ */
+ for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
+ {
+ char workdir[PATH_MAX], repos[PATH_MAX];
+
+ if (!hrec_count)
+ hr = NULL;
+ if (!accept_hrec (lr, hr))
+ continue;
+
+ ty = *(lr->type);
+#ifdef HAVE_RCS5
+ if (!tz_local)
+ {
+ time_t t = lr->date + tz_seconds_east_of_GMT;
+ tm = gmtime (&t);
+ }
+ else
+#endif
+ 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);
+
+ (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
+ if ((cp = strrchr (workdir, '/')) != NULL)
+ {
+ if (lr->mod && !strcmp (++cp, lr->mod))
+ {
+ (void) strcpy (cp, "*");
+ }
+ }
+ (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 'O':
+ if (lr->rev && *(lr->rev))
+ (void) printf (" [%s]", lr->rev);
+ (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
+ mod_len + 1 - strlen (lr->mod), "=", workdir);
+ break;
+ case 'W':
+ case 'U':
+ case 'C':
+ case 'G':
+ case 'M':
+ case 'A':
+ case 'R':
+ (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
+ file_len, lr->file, repos_len, repos,
+ lr->mod ? lr->mod : "", workdir);
+ break;
+ default:
+ (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
+ break;
+ }
+ (void) putchar ('\n');
+ }
+}
+
+static int
+accept_hrec (lr, hr)
+ struct hrec *hr, *lr;
+{
+ int ty;
+
+ ty = *(lr->type);
+
+ if (last_since_tag && ty == 'T')
+ return (1);
+
+ if (v_checkout)
+ {
+ if (ty != 'O')
+ return (0); /* Only interested in 'O' records */
+
+ /* We want to identify all the states that cause the next record
+ * ("hr") to be different from the current one ("lr") and only
+ * print a line at the allowed boundaries.
+ */
+
+ if (!hr || /* The last record */
+ strcmp (hr->user, lr->user) || /* User has changed */
+ strcmp (hr->mod, lr->mod) ||/* Module has changed */
+ (working && /* If must match "workdir" */
+ (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
+ strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
+
+ return (1);
+ }
+ else if (modified)
+ {
+ if (!last_entry || /* Don't want only last rec */
+ !hr || /* Last entry is a "last entry" */
+ strcmp (hr->repos, lr->repos) || /* Repository has changed */
+ strcmp (hr->file, lr->file))/* File has changed */
+ return (1);
+
+ if (working)
+ { /* If must match "workdir" */
+ if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
+ strcmp (hr->end, lr->end)) /* the 2nd parts differ */
+ return (1);
+ }
+ }
+ else if (module_report)
+ {
+ if (!last_entry || /* Don't want only last rec */
+ !hr || /* Last entry is a "last entry" */
+ strcmp (hr->mod, lr->mod) ||/* Module has changed */
+ strcmp (hr->repos, lr->repos) || /* Repository has changed */
+ strcmp (hr->file, lr->file))/* File has changed */
+ return (1);
+ }
+ else
+ {
+ /* "extract" and "tag_report" always print selected records. */
+ return (1);
+ }
+
+ return (0);
+}
diff --git a/contrib/cvs/src/ignore.c b/contrib/cvs/src/ignore.c
new file mode 100644
index 0000000..25c2269
--- /dev/null
+++ b/contrib/cvs/src/ignore.c
@@ -0,0 +1,405 @@
+/*
+ * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com>
+ */
+
+#include "cvs.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; /* 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 ()
+{
+ struct passwd *pw;
+ char file[PATH_MAX];
+ 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
+ {
+ /* Then add entries found in repository, if it exists */
+ (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM,
+ CVSROOTADM_IGNORE);
+ ign_add_file (file, 0);
+ }
+
+ /* Then add entries found in home dir, (if user has one) and file exists */
+ if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir)
+ {
+ (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTIGNORE);
+ ign_add_file (file, 0);
+ }
+
+ /* Then add entries found in CVSIGNORE environment variable. */
+ ign_add (getenv (IGNORE_ENV), 0);
+
+ /* Later, add ignore entries found in -I arguments */
+}
+
+/*
+ * Open a file and read lines, feeding each line to a line parser. Arrange
+ * for keeping a temporary list of wildcards at the end, if the "hold"
+ * argument is set.
+ */
+void
+ign_add_file (file, hold)
+ char *file;
+ int hold;
+{
+ FILE *fp;
+ char line[1024];
+
+ /* restore the saved list (if any) */
+ if (s_ign_list != NULL)
+ {
+ int i;
+
+ for (i = 0; i < s_ign_count; i++)
+ ign_list[i] = s_ign_list[i];
+ ign_count = s_ign_count;
+ ign_list[ign_count] = NULL;
+
+ s_ign_count = 0;
+ free (s_ign_list);
+ s_ign_list = NULL;
+ }
+
+ /* is this a temporary ignore file? */
+ if (hold)
+ {
+ /* re-set if we had already done a temporary file */
+ if (ign_hold)
+ {
+ int i;
+
+ for (i = ign_hold; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_count = ign_hold;
+ ign_list[ign_count] = NULL;
+ }
+ else
+ {
+ ign_hold = ign_count;
+ }
+ }
+
+ /* load the file */
+ fp = fopen (file, "r");
+ if (fp == NULL)
+ {
+ if (! existence_error (errno))
+ error (0, errno, "cannot open %s", file);
+ return;
+ }
+ while (fgets (line, sizeof (line), fp))
+ ign_add (line, hold);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", file);
+}
+
+/* Parse a line of space-separated wildcards and add them to the list. */
+void
+ign_add (ign, hold)
+ char *ign;
+ int hold;
+{
+ if (!ign || !*ign)
+ return;
+
+ for (; *ign; ign++)
+ {
+ char *mark;
+ char save;
+
+ /* ignore whitespace before the token */
+ if (isspace (*ign))
+ continue;
+
+ /*
+ * if we find a single character !, we must re-set the ignore list
+ * (saving it if necessary). We also catch * as a special case in a
+ * global ignore file as an optimization
+ */
+ if ((!*(ign+1) || isspace (*(ign+1))) && (*ign == '!' || *ign == '*'))
+ {
+ if (!hold)
+ {
+ /* permanently reset the ignore list */
+ int i;
+
+ for (i = 0; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_count = 0;
+ ign_list[0] = NULL;
+
+ /* if we are doing a '!', continue; otherwise add the '*' */
+ if (*ign == '!')
+ {
+ ign_inhibit_server = 1;
+ continue;
+ }
+ }
+ else if (*ign == '!')
+ {
+ /* temporarily reset the ignore list */
+ int i;
+
+ if (ign_hold)
+ {
+ for (i = ign_hold; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_hold = 0;
+ }
+ s_ign_list = (char **) xmalloc (ign_count * sizeof (char *));
+ for (i = 0; i < ign_count; i++)
+ s_ign_list[i] = ign_list[i];
+ s_ign_count = ign_count;
+ ign_count = 0;
+ ign_list[0] = NULL;
+ continue;
+ }
+ }
+
+ /* If we have used up all the space, add some more */
+ if (ign_count >= ign_size)
+ {
+ ign_size += IGN_GROW;
+ ign_list = (char **) xrealloc ((char *) ign_list,
+ (ign_size + 1) * sizeof (char *));
+ }
+
+ /* find the end of this token */
+ for (mark = ign; *mark && !isspace (*mark); mark++)
+ /* do nothing */ ;
+
+ save = *mark;
+ *mark = '\0';
+
+ ign_list[ign_count++] = xstrdup (ign);
+ ign_list[ign_count] = NULL;
+
+ *mark = save;
+ if (save)
+ ign = mark;
+ else
+ ign = mark - 1;
+ }
+}
+
+/* Set to 1 if ignore file patterns should be matched in a case-insensitive
+ fashion. */
+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. */
+ 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 (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 (fnmatch (*cpp++, name, 0) == 0)
+ return 1;
+ return 0;
+ }
+}
+
+/* FIXME: This list of dirs to ignore stuff seems not to be used. */
+
+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 ((char *) dir_ign_list, (dir_ign_max+1) * sizeof(char*));
+ }
+
+ dir_ign_list[dir_ign_current] = name;
+
+ dir_ign_current += 1 ;
+}
+
+
+/* this function returns 1 (true) if the given directory 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.
+ */
+void
+ignore_files (ilist, update_dir, proc)
+ List *ilist;
+ char *update_dir;
+ Ignore_proc proc;
+{
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ char *file;
+ char *xdir;
+
+ /* we get called with update_dir set to "." sometimes... strip it */
+ if (strcmp (update_dir, ".") == 0)
+ xdir = "";
+ else
+ xdir = update_dir;
+
+ dirp = opendir (".");
+ if (dirp == NULL)
+ return;
+
+ ign_add_file (CVSDOTIGNORE, 1);
+ wrap_add_file (CVSDOTWRAPPER, 1);
+
+ 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 (
+#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 &&
+#endif
+ S_ISDIR(sb.st_mode))
+ {
+ char temp[PATH_MAX];
+
+ (void) sprintf (temp, "%s/%s", file, CVSADM);
+ if (isdir (temp))
+ continue;
+ }
+#ifdef S_ISLNK
+ else if (
+#ifdef DT_DIR
+ dp->d_type == DT_LNK || dp->d_type == DT_UNKNOWN &&
+#endif
+ S_ISLNK(sb.st_mode))
+ {
+ continue;
+ }
+#endif
+ }
+
+ /* We could be ignoring FIFOs and other files which are neither
+ regular files nor directories here. */
+ if (ign_name (file))
+ continue;
+ (*proc) (file, xdir);
+ }
+ (void) closedir (dirp);
+}
diff --git a/contrib/cvs/src/import.c b/contrib/cvs/src/import.c
new file mode 100644
index 0000000..98c1635
--- /dev/null
+++ b/contrib/cvs/src/import.c
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * "import" checks in the vendor release located in the current directory into
+ * the CVS source repository. The CVS vendor branch support is utilized.
+ *
+ * At least three arguments are expected to follow the options:
+ * repository Where the source belongs relative to the CVSROOT
+ * VendorTag Vendor's major tag
+ * VendorReleTag Tag for this particular release
+ *
+ * Additional arguments specify more Vendor Release Tags.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+#define FILE_HOLDER ".#cvsxxx"
+
+static char *get_comment PROTO((char *user));
+static int add_rcs_file PROTO((char *message, char *rcs, char *user, char *vtag,
+ int targc, char *targv[]));
+static int expand_at_signs PROTO((char *buf, off_t size, FILE *fp));
+static int add_rev PROTO((char *message, char *rcs, char *vfile, char *vers));
+static int add_tags PROTO((char *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[50];
+static char vbranch[50];
+static FILE *logfp;
+static char repository[PATH_MAX];
+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",
+ NULL
+};
+
+int
+import (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *message = NULL;
+ char tmpfile[L_tmpnam+1];
+ char *cp;
+ int i, c, msglen, err;
+ List *ulist;
+ Node *p;
+
+ if (argc == -1)
+ usage (import_usage);
+
+ ign_setup ();
+ wrap_setup ();
+
+ (void) strcpy (vbranch, CVSBRANCH);
+ optind = 1;
+ 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':
+ use_file_modtime = 1;
+ break;
+ case 'b':
+ (void) strcpy (vbranch, optarg);
+ break;
+ case 'm':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#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);
+
+ for (i = 1; i < argc; i++) /* check the tags for validity */
+ RCS_check_tag (argv[i]);
+
+ /* XXX - this should be a module, not just a pathname */
+ if (! isabsolute (argv[0]))
+ {
+ if (CVSroot == NULL)
+ {
+ error (0, 0, "missing CVSROOT environment variable\n");
+ error (1, 0, "Set it or specify the '-d' option to %s.",
+ program_name);
+ }
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+ repos_len = strlen (CVSroot);
+ }
+ else
+ {
+ (void) strcpy (repository, argv[0]);
+ repos_len = 0;
+ }
+
+ /*
+ * Consistency checks on the specified vendor branch. It must be
+ * composed of only numbers and dots ('.'). Also, for now we only
+ * support branching to a single level, so the specified vendor branch
+ * must only have two dots in it (like "1.1.1").
+ */
+ for (cp = vbranch; *cp != '\0'; cp++)
+ if (!isdigit (*cp) && *cp != '.')
+ error (1, 0, "%s is not a numeric branch", vbranch);
+ if (numdots (vbranch) != 2)
+ error (1, 0, "Only branches with two dots are supported: %s", vbranch);
+ (void) strcpy (vhead, vbranch);
+ cp = strrchr (vhead, '.');
+ *cp = '\0';
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* Do this now; don't ask for a log message if we can't talk to the
+ server. But if there is a syntax error in the options, give
+ an error message without connecting. */
+ start_server ();
+ }
+#endif
+
+ if (use_editor)
+ {
+ do_editor ((char *) NULL, &message, repository,
+ (List *) NULL);
+ }
+
+ msglen = message == NULL ? 0 : strlen (message);
+ if (msglen == 0 || message[msglen - 1] != '\n')
+ {
+ char *nm = xmalloc (msglen + 2);
+ 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 (use_file_modtime)
+ send_arg("-d");
+
+ 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 ("!");
+ }
+
+ {
+ 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 */
+ if ((logfp = fopen (tmpnam (tmpfile), "w+")) == NULL)
+ error (1, errno, "cannot create temporary file `%s'", tmpfile);
+ (void) unlink (tmpfile); /* to be sure it goes away */
+ (void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]);
+ (void) fprintf (logfp, "Release Tags:\t");
+ for (i = 2; i < argc; i++)
+ (void) fprintf (logfp, "%s\n\t\t", argv[i]);
+ (void) fprintf (logfp, "\n");
+
+ /* Just Do It. */
+ err = import_descend (message, argv[1], argc - 2, argv + 2);
+ if (conflicts)
+ {
+ if (!really_quiet)
+ {
+ char buf[80];
+ sprintf (buf, "\n%d conflicts created by this import.\n",
+ conflicts);
+ cvs_output (buf, 0);
+ cvs_output ("Use the following command to help the merge:\n\n",
+ 0);
+ cvs_output ("\t", 1);
+ cvs_output (program_name, 0);
+ cvs_output (" checkout -j", 0);
+ cvs_output (argv[1], 0);
+ cvs_output (":yesterday -j", 0);
+ cvs_output (argv[1], 0);
+ cvs_output (" ", 1);
+ cvs_output (argv[0], 0);
+ cvs_output ("\n\n", 0);
+ }
+
+ (void) fprintf (logfp, "\n%d conflicts created by this import.\n",
+ conflicts);
+ (void) fprintf (logfp,
+ "Use the following command to help the merge:\n\n");
+ (void) fprintf (logfp, "\t%s checkout -j%s:yesterday -j%s %s\n\n",
+ program_name, argv[1], argv[1], argv[0]);
+ }
+ else
+ {
+ if (!really_quiet)
+ 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");
+ p->data = (char *) T_TITLE;
+ (void) addnode (ulist, p);
+ Update_Logfile (repository, message, vbranch, logfp, ulist);
+ dellist (&ulist);
+ (void) fclose (logfp);
+
+ /* Make sure the temporary file goes away, even on systems that don't let
+ you delete a file that's in use. */
+ unlink (tmpfile);
+
+ if (message)
+ free (message);
+
+ return (err);
+}
+
+/*
+ * process all the files in ".", then descend into other directories.
+ */
+static int
+import_descend (message, vtag, targc, targv)
+ char *message;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ DIR *dirp;
+ struct 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 = opendir (".")) == NULL)
+ {
+ err++;
+ }
+ else
+ {
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0)
+ continue;
+#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)
+ continue;
+#endif
+ if (ign_name (dp->d_name))
+ {
+ add_log ('I', dp->d_name);
+ continue;
+ }
+
+ 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 &&
+#endif
+ islink (dp->d_name))
+ {
+ 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);
+ else
+#endif
+ err += process_import_file (message, dp->d_name,
+ vtag, targc, targv);
+ }
+ }
+ (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 attic_name[PATH_MAX];
+ char rcs[PATH_MAX];
+ int inattic = 0;
+
+ (void) sprintf (rcs, "%s/%s%s", repository, vfile, RCSEXT);
+ if (!isfile (rcs))
+ {
+ (void) sprintf (attic_name, "%s/%s/%s%s", repository, CVSATTIC,
+ vfile, RCSEXT);
+ if (!isfile (attic_name))
+ {
+
+ /*
+ * A new import source file; it doesn't exist as a ,v within the
+ * repository nor in the Attic -- create it anew.
+ */
+ add_log ('N', vfile);
+ return (add_rcs_file (message, rcs, vfile, vtag, targc, targv));
+ }
+ inattic = 1;
+ }
+
+ /*
+ * 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;
+ int ierrno;
+ char *tmpdir;
+ char *tocvsPath;
+
+ vers = Version_TS (repository, (char *) NULL, vbranch, (char *) NULL, vfile,
+ 1, 0, (List *) NULL, (RCSNode *) NULL);
+ if (vers->vn_rcs != NULL
+ && !RCS_isdead(vers->srcfile, vers->vn_rcs))
+ {
+ char xtmpfile[PATH_MAX];
+ int different;
+ int retcode = 0;
+
+ tmpdir = getenv ("TMPDIR");
+ if (tmpdir == NULL || tmpdir[0] == '\0')
+ tmpdir = "/tmp";
+
+ (void) sprintf (xtmpfile, "%s/cvs-imp%ld", tmpdir, (long) getpid());
+
+ /*
+ * 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.
+ */
+ /* Why is RCS_FLAGS_FORCE here? I wouldn't think that it would have any
+ effect in conjunction with passing NULL for workfile (i.e. to stdout). */
+ retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
+#ifdef HAVE_RCS5
+ "-ko",
+#else
+ NULL,
+#endif
+ xtmpfile, RCS_FLAGS_FORCE, 0);
+ if (retcode != 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, retcode == -1 ? ierrno : 0,
+ "ERROR: cannot co revision %s of file %s", vers->vn_rcs,
+ vers->srcfile->path);
+ error (0, retcode == -1 ? ierrno : 0,
+ "ERROR: cannot co revision %s of file %s", vers->vn_rcs,
+ vers->srcfile->path);
+ (void) unlink_file (xtmpfile);
+ return (1);
+ }
+
+ tocvsPath = wrap_tocvs_process_file (vfile);
+ different = xcmp (xtmpfile, vfile);
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ (void) unlink_file (xtmpfile);
+ if (!different)
+ {
+ int retval = 0;
+
+ /*
+ * The two files are identical. Just update the tags, print the
+ * "U", signifying that the file has changed, but needs no
+ * attention, and we're done.
+ */
+ if (add_tags (vers->srcfile->path, vfile, vtag, targc, targv))
+ retval = 1;
+ add_log ('U', vfile);
+ freevers_ts (&vers);
+ return (retval);
+ }
+ }
+
+ /* We may have failed to parse the RCS file; check just in case */
+ if (vers->srcfile == NULL ||
+ add_rev (message, vers->srcfile->path, vfile, vers->vn_rcs) ||
+ add_tags (vers->srcfile->path, vfile, vtag, targc, targv))
+ {
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ if (vers->srcfile->branch == NULL || 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;
+ char *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. */
+ if (RCS_lock (rcs, vbranch, 1) != 0)
+ {
+ error (0, errno, "fork failed");
+ return (1);
+ }
+ locked = 1;
+ }
+ tocvsPath = wrap_tocvs_process_file (vfile);
+ if (tocvsPath == NULL)
+ {
+ /* We play with hard links rather than passing -u to ci to avoid
+ expanding RCS keywords (see test 106.5 in sanity.sh). */
+ if (link_file (vfile, FILE_HOLDER) < 0)
+ {
+ if (errno == EEXIST)
+ {
+ (void) unlink_file (FILE_HOLDER);
+ (void) link_file (vfile, FILE_HOLDER);
+ }
+ else
+ {
+ ierrno = errno;
+ fperror (logfp, 0, ierrno,
+ "ERROR: cannot create link to %s", vfile);
+ error (0, ierrno, "ERROR: cannot create link to %s", vfile);
+ return (1);
+ }
+ }
+ }
+
+ status = RCS_checkin (rcs, tocvsPath == NULL ? vfile : tocvsPath,
+ message, vbranch,
+ (RCS_FLAGS_QUIET
+ | (use_file_modtime ? RCS_FLAGS_MODTIME : 0)),
+ 0);
+ ierrno = errno;
+
+ if (tocvsPath == NULL)
+ rename_file (FILE_HOLDER, vfile);
+ else
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (status)
+ {
+ if (!noexec)
+ {
+ fperror (logfp, 0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs);
+ error (0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs);
+ }
+ if (locked)
+ {
+ (void) RCS_unlock(rcs, vbranch, 0);
+ }
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Add the vendor branch tag and all the specified import release tags to the
+ * RCS file. The vendor branch tag goes on the branch root (1.1.1) while the
+ * vendor release tags go on the newly added leaf of the branch (1.1.1.1,
+ * 1.1.1.2, ...).
+ */
+static int
+add_tags (rcs, vfile, vtag, targc, targv)
+ char *rcs;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ int i, ierrno;
+ Vers_TS *vers;
+ int retcode = 0;
+
+ if (noexec)
+ return (0);
+
+ if ((retcode = RCS_settag(rcs, vtag, vbranch)) != 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, retcode == -1 ? ierrno : 0,
+ "ERROR: Failed to set tag %s in %s", vtag, rcs);
+ error (0, retcode == -1 ? ierrno : 0,
+ "ERROR: Failed to set tag %s in %s", vtag, rcs);
+ return (1);
+ }
+ vers = Version_TS (repository, (char *) NULL, vtag, (char *) NULL, vfile,
+ 1, 0, (List *) NULL, (RCSNode *) NULL);
+ for (i = 0; i < targc; i++)
+ {
+ if ((retcode = RCS_settag (rcs, targv[i], vers->vn_rcs)) != 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, retcode == -1 ? ierrno : 0,
+ "WARNING: Couldn't add tag %s to %s", targv[i], rcs);
+ error (0, retcode == -1 ? ierrno : 0,
+ "WARNING: Couldn't add tag %s to %s", targv[i], rcs);
+ }
+ }
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Stolen from rcs/src/rcsfnms.c, and adapted/extended.
+ */
+struct compair
+{
+ char *suffix, *comlead;
+};
+
+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.
+ */
+ {"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[PATH_MAX];
+ int i;
+
+ 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 (*cp))
+ *cp = tolower (*cp);
+ suffix = suffix_path;
+ }
+ else
+ suffix = ""; /* will use the default */
+ for (i = 0;; i++)
+ {
+ if (comtable[i].suffix == NULL) /* default */
+ return (comtable[i].comlead);
+ if (strcmp (suffix, comtable[i].suffix) == 0)
+ return (comtable[i].comlead);
+ }
+}
+
+static int
+add_rcs_file (message, rcs, user, vtag, targc, targv)
+ char *message;
+ char *rcs;
+ char *user;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ FILE *fprcs, *fpuser;
+ struct stat sb;
+ struct tm *ftm;
+ time_t now;
+ char altdate1[50];
+#ifndef HAVE_RCS5
+ char altdate2[50];
+#endif
+ char *author;
+ int i, ierrno, err = 0;
+ mode_t mode;
+ char *tocvsPath;
+ char *userfile;
+
+ if (noexec)
+ return (0);
+
+ /* FIXME? We always import files as text files (note that means
+ that files get stored with straight linefeeds). There isn't an
+ obvious, clean, way to let people specify which files are binary.
+ Maybe based on the file name.... */
+ tocvsPath = wrap_tocvs_process_file (user);
+ userfile = (tocvsPath == NULL ? user : tocvsPath);
+ fpuser = fopen (userfile, "r");
+ if (fpuser == NULL)
+ {
+ /* not fatal, continue import */
+ fperror (logfp, 0, errno, "ERROR: cannot read file %s", userfile);
+ error (0, errno, "ERROR: cannot read file %s", userfile);
+ goto read_error;
+ }
+ fprcs = fopen (rcs, "w+b");
+ if (fprcs == NULL)
+ {
+ ierrno = errno;
+ goto write_error_noclose;
+ }
+
+ /*
+ * putadmin()
+ */
+ if (fprintf (fprcs, "head %s;\012", vhead) < 0 ||
+ fprintf (fprcs, "branch %s;\012", vbranch) < 0 ||
+ fprintf (fprcs, "access ;\012") < 0 ||
+ fprintf (fprcs, "symbols ") < 0)
+ {
+ goto write_error;
+ }
+
+ for (i = targc - 1; i >= 0; i--) /* RCS writes the symbols backwards */
+ if (fprintf (fprcs, "%s:%s.1 ", targv[i], vbranch) < 0)
+ goto write_error;
+
+ if (fprintf (fprcs, "%s:%s;\012", vtag, vbranch) < 0 ||
+ 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 (keyword_opt != NULL)
+ if (fprintf (fprcs, "expand @%s@;\012", keyword_opt) < 0)
+ {
+ goto write_error;
+ }
+
+ if (fprintf (fprcs, "\012") < 0)
+ goto write_error;
+
+ /*
+ * puttree()
+ */
+ if (fstat (fileno (fpuser), &sb) < 0)
+ error (1, errno, "cannot fstat %s", user);
+ if (use_file_modtime)
+ now = sb.st_mtime;
+ else
+ (void) time (&now);
+#ifdef HAVE_RCS5
+ ftm = gmtime (&now);
+#else
+ ftm = localtime (&now);
+#endif
+ (void) sprintf (altdate1, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+#ifdef HAVE_RCS5
+#define altdate2 altdate1
+#else
+ /*
+ * If you don't have RCS V5 or later, you need to lie about the ci
+ * time, since RCS V4 and earlier insist that the times differ.
+ */
+ now++;
+ ftm = localtime (&now);
+ (void) sprintf (altdate2, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+#endif
+ author = getcaller ();
+
+ if (fprintf (fprcs, "\012%s\012", vhead) < 0 ||
+ fprintf (fprcs, "date %s; author %s; state Exp;\012",
+ altdate1, author) < 0 ||
+ fprintf (fprcs, "branches %s.1;\012", vbranch) < 0 ||
+ fprintf (fprcs, "next ;\012") < 0 ||
+ fprintf (fprcs, "\012%s.1\012", vbranch) < 0 ||
+ fprintf (fprcs, "date %s; author %s; state Exp;\012",
+ altdate2, author) < 0 ||
+ fprintf (fprcs, "branches ;\012") < 0 ||
+ fprintf (fprcs, "next ;\012\012") < 0 ||
+ /*
+ * putdesc()
+ */
+ fprintf (fprcs, "\012desc\012") < 0 ||
+ fprintf (fprcs, "@@\012\012\012") < 0 ||
+ /*
+ * putdelta()
+ */
+ fprintf (fprcs, "\012%s\012", vhead) < 0 ||
+ fprintf (fprcs, "log\012") < 0 ||
+ fprintf (fprcs, "@Initial revision\012@\012") < 0 ||
+ fprintf (fprcs, "text\012@") < 0)
+ {
+ goto write_error;
+ }
+
+ /* Now copy over the contents of the file, expanding at signs. */
+ {
+ unsigned 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 ||
+ fprintf (fprcs, "\012%s.1\012", 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;
+ }
+ (void) fclose (fpuser);
+
+ /*
+ * 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;
+ fperror (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);
+ return (err);
+
+write_error:
+ ierrno = errno;
+ (void) fclose (fprcs);
+write_error_noclose:
+ (void) fclose (fpuser);
+ fperror (logfp, 0, ierrno, "ERROR: cannot write file %s", rcs);
+ error (0, ierrno, "ERROR: cannot write file %s", rcs);
+ if (ierrno == ENOSPC)
+ {
+ (void) unlink (rcs);
+ fperror (logfp, 0, 0, "ERROR: out of space - aborting");
+ error (1, 0, "ERROR: out of space - aborting");
+ }
+read_error:
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ 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.
+ */
+static int
+expand_at_signs (buf, size, fp)
+ char *buf;
+ off_t size;
+ FILE *fp;
+{
+ char *cp, *end;
+
+ errno = 0;
+ for (cp = buf, end = buf + size; cp < end; cp++)
+ {
+ if (*cp == '@')
+ {
+ if (putc ('@', fp) == EOF && errno != 0)
+ return EOF;
+ }
+ if (putc (*cp, fp) == EOF && errno != 0)
+ 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;
+
+ if (islink (dir))
+ return (0);
+ if (save_cwd (&cwd))
+ {
+ fperror (logfp, 0, 0, "ERROR: cannot get working directory");
+ return (1);
+ }
+ if (repository[0] == '\0')
+ (void) strcpy (repository, dir);
+ else
+ {
+ (void) strcat (repository, "/");
+ (void) strcat (repository, dir);
+ }
+#ifdef CLIENT_SUPPORT
+ if (!quiet && !client_active)
+#else
+ if (!quiet)
+#endif
+ error (0, 0, "Importing %s", repository);
+
+ if (chdir (dir) < 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, ierrno, "ERROR: cannot chdir to %s", repository);
+ error (0, ierrno, "ERROR: cannot chdir to %s", repository);
+ err = 1;
+ goto out;
+ }
+#ifdef CLIENT_SUPPORT
+ if (!client_active && !isdir (repository))
+#else
+ if (!isdir (repository))
+#endif
+ {
+ char rcs[PATH_MAX];
+
+ (void) sprintf (rcs, "%s%s", repository, RCSEXT);
+ if (isfile (repository) || isfile(rcs))
+ {
+ fperror (logfp, 0, 0, "ERROR: %s is a file, should be a directory!",
+ repository);
+ error (0, 0, "ERROR: %s is a file, should be a directory!",
+ repository);
+ err = 1;
+ goto out;
+ }
+ if (noexec == 0 && CVS_MKDIR (repository, 0777) < 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, ierrno,
+ "ERROR: cannot mkdir %s -- not added", repository);
+ error (0, ierrno,
+ "ERROR: cannot mkdir %s -- not added", repository);
+ err = 1;
+ goto out;
+ }
+ }
+ err = import_descend (message, vtag, targc, targv);
+ out:
+ if ((cp = strrchr (repository, '/')) != NULL)
+ *cp = '\0';
+ else
+ repository[0] = '\0';
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ return (err);
+}
diff --git a/contrib/cvs/src/lock.c b/contrib/cvs/src/lock.c
new file mode 100644
index 0000000..7e35aed
--- /dev/null
+++ b/contrib/cvs/src/lock.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Set Lock
+ *
+ * Lock file support for CVS.
+ */
+
+#include "cvs.h"
+
+static int readers_exist PROTO((char *repository));
+static int set_lock PROTO((char *repository, int will_wait));
+static void clear_lock PROTO((void));
+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((char *repository));
+static void lock_simple_remove PROTO((char *repository));
+static void lock_wait PROTO((char *repository));
+static int Check_Owner PROTO((char *lockdir));
+
+static char lockers_name[20];
+static char *repository;
+static char readlock[PATH_MAX], writelock[PATH_MAX], masterlock[PATH_MAX];
+static int cleanup_lckdir;
+static List *locklist;
+
+#define L_OK 0 /* success */
+#define L_ERROR 1 /* error condition */
+#define L_LOCKED 2 /* lock owned by someone else */
+
+/*
+ * Clean up all outstanding locks
+ */
+void
+Lock_Cleanup ()
+{
+ /* clean up simple locks (if any) */
+ if (repository != NULL)
+ {
+ lock_simple_remove (repository);
+ repository = (char *) 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 (p->key);
+ return (0);
+}
+
+/*
+ * Remove the lock files (without complaining if they are not there),
+ */
+static void
+lock_simple_remove (repository)
+ char *repository;
+{
+ char tmp[PATH_MAX];
+
+ if (readlock[0] != '\0')
+ {
+ (void) sprintf (tmp, "%s/%s", repository, readlock);
+ if (unlink (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+ }
+
+ if (writelock[0] != '\0')
+ {
+ (void) sprintf (tmp, "%s/%s", repository, writelock);
+ if (unlink (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+ }
+
+ /*
+ * Only remove the lock directory if it is ours, note that this does
+ * lead to the limitation that one user ID should not be committing
+ * files into the same Repository directory at the same time. Oh well.
+ */
+ if (writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir))
+ {
+ (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
+ if (Check_Owner(tmp))
+ {
+#ifdef AFSCVS
+ char rmuidlock[PATH_MAX];
+ sprintf(rmuidlock, "rm -f %s/uidlock%d", tmp, geteuid() );
+ system(rmuidlock);
+#endif
+ (void) rmdir (tmp);
+ }
+ }
+ cleanup_lckdir = 0;
+}
+
+/*
+ * Check the owner of a lock. Returns 1 if we own it, 0 otherwise.
+ */
+static int
+Check_Owner(lockdir)
+ char *lockdir;
+{
+ struct stat sb;
+
+#ifdef AFSCVS
+ /* In the Andrew File System (AFS), user ids from stat don't match
+ those from geteuid(). The AFSCVS code can deal with either AFS or
+ non-AFS repositories; the non-AFSCVS code is faster. */
+ char uidlock[PATH_MAX];
+
+ /* Check if the uidlock is in the lock directory */
+ sprintf(uidlock, "%s/uidlock%d", lockdir, geteuid() );
+ if( stat(uidlock, &sb) != -1)
+ return 1; /* The file exists, therefore we own the lock */
+ else
+ return 0; /* The file didn't exist or some other error.
+ * Assume that we don't own it.
+ */
+#else
+ if (stat (lockdir, &sb) != -1 && sb.st_uid == geteuid ())
+ return 1;
+ else
+ return 0;
+#endif
+} /* end Check_Owner() */
+
+
+/*
+ * Create a lock file for readers
+ */
+int
+Reader_Lock (xrepository)
+ char *xrepository;
+{
+ int err = 0;
+ FILE *fp;
+ char tmp[PATH_MAX];
+
+ if (noexec)
+ return (0);
+
+ /* we only do one directory at a time for read locks! */
+ if (repository != NULL)
+ {
+ error (0, 0, "Reader_Lock called while read locks set - Help!");
+ return (1);
+ }
+
+ if (readlock[0] == '\0')
+ (void) sprintf (readlock,
+#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) */
+ repository = xrepository;
+
+ /* get the lock dir for our own */
+ if (set_lock (xrepository, 1) != L_OK)
+ {
+ error (0, 0, "failed to obtain dir lock in repository `%s'",
+ xrepository);
+ readlock[0] = '\0';
+ return (1);
+ }
+
+ /* write a read-lock */
+ (void) sprintf (tmp, "%s/%s", xrepository, readlock);
+ if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+ {
+ error (0, errno, "cannot create read lock in repository `%s'",
+ xrepository);
+ readlock[0] = '\0';
+ err = 1;
+ }
+
+ /* free the lock dir */
+ clear_lock();
+
+ return (err);
+}
+
+/*
+ * Lock a list of directories for writing
+ */
+static char *lock_error_repos;
+static int lock_error;
+int
+Writer_Lock (list)
+ List *list;
+{
+ if (noexec)
+ return (0);
+
+ /* We only know how to do one list at a time */
+ if (locklist != (List *) NULL)
+ {
+ error (0, 0, "Writer_Lock called while write locks set - Help!");
+ return (1);
+ }
+
+ for (;;)
+ {
+ /* try to lock everything on the list */
+ lock_error = L_OK; /* init for set_writelock_proc */
+ lock_error_repos = (char *) NULL; /* init for set_writelock_proc */
+ locklist = list; /* init for Lock_Cleanup */
+ (void) strcpy (lockers_name, "unknown");
+
+ (void) walklist (list, set_writelock_proc, NULL);
+
+ switch (lock_error)
+ {
+ case L_ERROR: /* Real Error */
+ Lock_Cleanup (); /* clean up any locks we set */
+ error (0, 0, "lock failed - giving up");
+ return (1);
+
+ case L_LOCKED: /* Someone already had a lock */
+ Lock_Cleanup (); /* clean up any locks we set */
+ lock_wait (lock_error_repos); /* sleep a while and try again */
+ continue;
+
+ case L_OK: /* we got the locks set */
+ return (0);
+
+ default:
+ error (0, 0, "unknown lock status %d in Writer_Lock",
+ lock_error);
+ return (1);
+ }
+ }
+}
+
+/*
+ * walklist proc for setting write locks
+ */
+static int
+set_writelock_proc (p, 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 (p->key);
+ return (0);
+}
+
+/*
+ * Create a lock file for writers returns L_OK if lock set ok, L_LOCKED if
+ * lock held by someone else or L_ERROR if an error occurred
+ */
+static int
+write_lock (repository)
+ char *repository;
+{
+ int status;
+ FILE *fp;
+ char tmp[PATH_MAX];
+
+ if (writelock[0] == '\0')
+ (void) sprintf (writelock,
+#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 (repository, 0);
+ if (status == L_OK)
+ {
+ /* we now own a writer - make sure there are no readers */
+ if (readers_exist (repository))
+ {
+ /* clean up the lock dir if we created it */
+ if (status == L_OK)
+ {
+ clear_lock();
+ }
+
+ /* indicate we failed due to read locks instead of error */
+ return (L_LOCKED);
+ }
+
+ /* write the write-lock file */
+ (void) sprintf (tmp, "%s/%s", repository, writelock);
+ if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+ {
+ int xerrno = errno;
+
+ if (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();
+ }
+
+ /* return the error */
+ error (0, xerrno, "cannot create write lock in repository `%s'",
+ repository);
+ return (L_ERROR);
+ }
+ return (L_OK);
+ }
+ else
+ return (status);
+}
+
+/*
+ * readers_exist() returns 0 if there are no reader lock files remaining in
+ * the repository; else 1 is returned, to indicate that the caller should
+ * sleep a while and try again.
+ */
+static int
+readers_exist (repository)
+ char *repository;
+{
+ char *line;
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ int ret = 0;
+
+#ifdef CVS_FUDGELOCKS
+again:
+#endif
+
+ if ((dirp = opendir (repository)) == NULL)
+ error (1, 0, "cannot open directory %s", repository);
+
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (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 (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) && 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 ((pw = (struct passwd *) getpwuid (statp->st_uid)) !=
+ (struct passwd *) NULL)
+ {
+ (void) strcpy (lockers_name, pw->pw_name);
+ }
+ else
+ (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 (repository, will_wait)
+ char *repository;
+ int will_wait;
+{
+ struct stat sb;
+ mode_t omask;
+#ifdef CVS_FUDGELOCKS
+ time_t now;
+#endif
+
+ (void) sprintf (masterlock, "%s/%s", 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.
+ */
+ cleanup_lckdir = 0;
+ for (;;)
+ {
+ int status = -1;
+ omask = umask (cvsumask);
+ SIG_beginCrSect ();
+ if (CVS_MKDIR (masterlock, 0777) == 0)
+ {
+#ifdef AFSCVS
+ char uidlock[PATH_MAX];
+ FILE *fp;
+
+ sprintf(uidlock, "%s/uidlock%d", masterlock, geteuid() );
+ if ((fp = fopen(uidlock, "w+")) == NULL)
+ {
+ /* We failed to create the uidlock,
+ so rm masterlock and leave */
+ rmdir(masterlock);
+ SIG_endCrSect ();
+ status = L_ERROR;
+ goto out;
+ }
+
+ /* We successfully created the uid lock, so close the file */
+ fclose(fp);
+#endif
+ cleanup_lckdir = 1;
+ SIG_endCrSect ();
+ status = L_OK;
+ 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'",
+ repository);
+ return (L_ERROR);
+ }
+
+ /*
+ * stat the dir - if it is non-existent, re-try the loop since
+ * someone probably just removed it (thus releasing the lock)
+ */
+ if (stat (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))
+ {
+#ifdef AFSCVS
+ /* Remove the uidlock first */
+ char rmuidlock[PATH_MAX];
+ sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() );
+ system(rmuidlock);
+#endif
+ if (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 (repository);
+ }
+}
+
+/*
+ * 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()
+{
+#ifdef AFSCVS
+ /* Remove the uidlock first */
+ char rmuidlock[PATH_MAX];
+ sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() );
+ system(rmuidlock);
+#endif
+ if (rmdir (masterlock) < 0)
+ error (0, errno, "failed to remove lock dir `%s'", masterlock);
+ cleanup_lckdir = 0;
+}
+
+/*
+ * Print out a message that the lock is still held, then sleep a while.
+ */
+static void
+lock_wait (repos)
+ char *repos;
+{
+ time_t now;
+
+ (void) time (&now);
+ error (0, 0, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11,
+ lockers_name, repos);
+ (void) sleep (CVSLCKSLEEP);
+}
+
+static int lock_filesdoneproc PROTO ((int err, char *repository,
+ char *update_dir));
+static int fsortcmp PROTO((const Node * p, const Node * q));
+
+static List *lock_tree_list;
+
+/*
+ * Create a list of repositories to lock
+ */
+/* ARGSUSED */
+static int
+lock_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ Node *p;
+
+ p = getnode ();
+ p->type = LOCK;
+ p->key = xstrdup (repository);
+ /* 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);
+}
+
+/*
+ * compare two lock list nodes (for sort)
+ */
+static int
+fsortcmp (p, q)
+ const Node *p;
+ const Node *q;
+{
+ return (strcmp (p->key, q->key));
+}
+
+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, argc,
+ argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0,
+ 0);
+ sortlist (lock_tree_list, fsortcmp);
+ if (Writer_Lock (lock_tree_list) != 0)
+ error (1, 0, "lock failed - giving up");
+}
+
+void
+lock_tree_cleanup ()
+{
+ Lock_Cleanup ();
+ dellist (&lock_tree_list);
+}
diff --git a/contrib/cvs/src/log.c b/contrib/cvs/src/log.c
new file mode 100644
index 0000000..f167d92
--- /dev/null
+++ b/contrib/cvs/src/log.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Print Log Information
+ *
+ * This line exists solely to test some pcl-cvs/ChangeLog stuff. You
+ * can delete it, if indeed it's still here when you read it. -Karl
+ *
+ * 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"
+
+static Dtype log_dirproc PROTO((char *dir, char *repository, char *update_dir));
+static int log_fileproc PROTO((struct file_info *finfo));
+
+static const char *const log_usage[] =
+{
+ "Usage: %s %s [-l] [rlog-options] [files...]\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ NULL
+};
+
+static int ac;
+static char **av;
+
+int
+cvslog (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+ int err = 0;
+ int local = 0;
+
+ if (argc == -1)
+ usage (log_usage);
+
+ /*
+ * All 'log' command options except -l are passed directly on to 'rlog'
+ */
+ for (i = 1; i < argc && argv[i][0] == '-'; i++)
+ if (argv[i][1] == 'l')
+ local = 1;
+
+ wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+ if (client_active) {
+ /* We're the local client. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ for (i = 1; i < argc && argv[i][0] == '-'; i++)
+ send_arg (argv[i]);
+
+ send_file_names (argc - i, argv + i, SEND_EXPAND_WILD);
+/* FIXME: We shouldn't have to send current files to get log entries, but it
+ doesn't work yet and I haven't debugged it. So send the files --
+ it's slower but it works. gnu@cygnus.com Apr94 */
+ send_files (argc - i, argv + i, local, 0);
+
+ send_to_server ("log\012", 0);
+ err = get_responses_and_close ();
+ return err;
+ }
+
+ ac = argc;
+ av = argv;
+#endif
+
+ err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
+ (DIRLEAVEPROC) NULL, argc - i, argv + i, local,
+ W_LOCAL | W_REPOS | W_ATTIC, 0, 1,
+ (char *) NULL, 1, 0);
+ return (err);
+}
+
+
+/*
+ * Do an rlog on a file
+ */
+/* ARGSUSED */
+static int
+log_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Node *p;
+ RCSNode *rcsfile;
+ int retcode = 0;
+
+ 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);
+ }
+
+ run_setup ("%s%s -x,v/", Rcsbin, RCS_RLOG);
+ {
+ int i;
+ for (i = 1; i < ac && av[i][0] == '-'; i++)
+ if (av[i][1] != 'l')
+ run_arg (av[i]);
+ }
+ run_arg (rcsfile->path);
+
+ if (*finfo->update_dir)
+ {
+ char *workfile = xmalloc (strlen (finfo->update_dir) + strlen (finfo->file) + 2);
+ sprintf (workfile, "%s/%s", finfo->update_dir, finfo->file);
+ run_arg (workfile);
+ free (workfile);
+ }
+
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_REALLY)) == -1)
+ {
+ error (1, errno, "fork failed for rlog on %s", finfo->file);
+ }
+ return (retcode);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+log_dirproc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ if (!isdir (dir))
+ return (R_SKIP_ALL);
+
+ if (!quiet)
+ error (0, 0, "Logging %s", update_dir);
+ return (R_PROCESS);
+}
diff --git a/contrib/cvs/src/login.c b/contrib/cvs/src/login.c
new file mode 100644
index 0000000..fc3a178
--- /dev/null
+++ b/contrib/cvs/src/login.c
@@ -0,0 +1,392 @@
+/*
+ * 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. */
+
+extern char *getpass ();
+
+#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;
+
+/* The return value will need to be freed. */
+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)
+ {
+ error (1, errno, "could not find out home directory");
+ return (char *) NULL;
+ }
+
+ passfile =
+ (char *) xmalloc (strlen (homedir) + strlen (CVS_PASSWORD_FILE) + 3);
+ strcpy (passfile, homedir);
+ strcat (passfile, "/");
+ strcat (passfile, CVS_PASSWORD_FILE);
+
+ /* Safety first and last, Scouts. */
+ if (isfile (passfile))
+ /* xchmod() is too polite. */
+ chmod (passfile, 0600);
+
+ return passfile;
+}
+
+
+/* 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;
+
+ /* Make this a "fully-qualified" CVSroot if necessary. */
+ if (! strchr (CVSroot, '@'))
+ {
+ /* We need to prepend "user@host:". */
+ char *tmp;
+
+ printf ("Repository \"%s\" not fully-qualified.\n", CVSroot);
+ printf ("Please enter \"user@host:/path\": ");
+ fflush (stdout);
+ getline (&linebuf, &linebuf_len, stdin);
+
+ tmp = xmalloc (strlen (linebuf) + 1);
+
+ /* Give it some permanent storage. */
+ strcpy (tmp, linebuf);
+ tmp[strlen (linebuf) - 1] = '\0';
+ CVSroot = tmp;
+
+ /* Reset. */
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+
+ if (CVSroot[0] != ':')
+ {
+ /* Then we need to prepend ":pserver:". */
+ char *tmp;
+
+ tmp = xmalloc (strlen (":pserver:") + strlen (CVSroot) + 1);
+ strcpy (tmp, ":pserver:");
+ strcat (tmp, CVSroot);
+ CVSroot = tmp;
+ }
+
+ /* Check to make sure it's fully-qualified before going on.
+ * Fully qualified in this context means it has both a user and a
+ * host:repos portion.
+ */
+ {
+ char *r;
+
+ /* After confirming that CVSroot is non-NULL, we skip past the
+ initial ":pserver:" to test the rest of it. */
+
+ if (! CVSroot)
+ error (1, 0, "CVSroot is NULL");
+ else if (! strchr ((r = (CVSroot + strlen (":pserver:"))), '@'))
+ goto not_fqrn;
+ else if (! strchr (r, ':'))
+ goto not_fqrn;
+
+ if (0) /* Lovely. */
+ {
+ not_fqrn:
+ error (0, 0, "CVSroot not fully-qualified: %s", CVSroot);
+ error (1, 0, "should be format user@host:/path/to/repository");
+ }
+ }
+
+ /* CVSroot is now fully qualified and has ":pserver:" prepended.
+ We'll print out most of it so user knows exactly what is being
+ dealt with here. */
+ {
+ char *s;
+ s = strchr (CVSroot, ':');
+ s++;
+ s = strchr (s, ':');
+ s++;
+
+ if (s == NULL)
+ error (1, 0, "NULL CVSroot");
+
+ printf ("(Logging in to %s)\n", s);
+ 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);
+
+ if (connect_to_pserver (NULL, NULL, 1) == 0)
+ {
+ /* The password is wrong, according to the server. */
+ error (1, 0, "incorrect password");
+ }
+
+ /* 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);
+
+ /* 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 = 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 (getline (&linebuf, &linebuf_len, fp) >= 0)
+ {
+ if (strncmp (CVSroot, linebuf, root_len) == 0)
+ {
+ already_entered = 1;
+ break;
+ }
+ else
+ {
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+ }
+ fclose (fp);
+ }
+
+ 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 = tmpnam (NULL);
+ if ((tmp_fp = fopen (tmp_name, "w")) == NULL)
+ {
+ error (1, errno, "unable to open temp file %s", tmp_name);
+ return 1;
+ }
+ chmod (tmp_name, 0600);
+
+ fp = fopen (passfile, "r");
+ if (fp == NULL)
+ {
+ error (1, errno, "unable to open %s", passfile);
+ return 1;
+ }
+ /* I'm not paranoid, they really ARE out to get me: */
+ chmod (passfile, 0600);
+
+ free (linebuf);
+ linebuf = (char *) NULL;
+ while (getline (&linebuf, &linebuf_len, fp) >= 0)
+ {
+ if (strncmp (CVSroot, linebuf, root_len))
+ fprintf (tmp_fp, "%s", linebuf);
+ else
+ fprintf (tmp_fp, "%s %s\n", CVSroot, typed_password);
+
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+ fclose (tmp_fp);
+ fclose (fp);
+ rename_file (tmp_name, passfile);
+ chmod (passfile, 0600);
+ }
+ }
+ else
+ {
+ if ((fp = fopen (passfile, "a")) == NULL)
+ {
+ error (1, errno, "could not open %s", passfile);
+ free (passfile);
+ return 1;
+ }
+
+ fprintf (fp, "%s %s\n", CVSroot, typed_password);
+ fclose (fp);
+ }
+
+ /* 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;
+}
+
+/* todo: "cvs logout" could erase an entry from the file.
+ * But to what purpose?
+ */
+
+/* Returns the _scrambled_ password. The server must descramble
+ before hashing and comparing. */
+char *
+get_cvs_password ()
+{
+ int found_it = 0;
+ int root_len;
+ char *password;
+ char *linebuf = (char *) NULL;
+ size_t linebuf_len;
+ FILE *fp;
+ char *passfile;
+
+ /* 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;
+
+ /* Environment should override file. */
+ if ((password = getenv ("CVS_PASSWORD")) != NULL)
+ {
+ char *p;
+ p = xstrdup (password);
+ /* If we got it from the environment, then it wasn't properly
+ scrambled. Since unscrambling is done on the server side, we
+ need to transmit it scrambled. */
+ p = scramble (p);
+ return p;
+ }
+
+ /* Else get it from the file. */
+ passfile = construct_cvspass_filename ();
+ fp = fopen (passfile, "r");
+ if (fp == NULL)
+ {
+ error (0, errno, "could not open %s", passfile);
+ free (passfile);
+ error (1, 0, "use \"cvs login\" to log in first");
+ }
+
+ root_len = strlen (CVSroot);
+
+ /* Check each line to see if we have this entry already. */
+ while (getline (&linebuf, &linebuf_len, fp) >= 0)
+ {
+ if (strncmp (CVSroot, linebuf, root_len) == 0)
+ {
+ /* This is it! So break out and deal with linebuf. */
+ found_it = 1;
+ break;
+ }
+ else
+ {
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+ }
+
+ if (found_it)
+ {
+ /* linebuf now contains the line with the password. */
+ char *tmp;
+
+ strtok (linebuf, " ");
+ password = strtok (NULL, "\n");
+
+ /* Give it permanent storage. */
+ tmp = xmalloc (strlen (password) + 1);
+ strcpy (tmp, password);
+ tmp[strlen (password)] = '\0';
+ memset (password, 0, strlen (password));
+ free (linebuf);
+ return tmp;
+ }
+ else
+ {
+ error (0, 0, "cannot find password");
+ error (0, 0, "use \"cvs login\" to log in first");
+ error (1, 0, "or set the CVS_PASSWORD environment variable");
+ }
+ /* NOTREACHED */
+ return NULL;
+}
+
+#endif /* AUTH_CLIENT_SUPPORT from beginning of file. */
+
diff --git a/contrib/cvs/src/logmsg.c b/contrib/cvs/src/logmsg.c
new file mode 100644
index 0000000..370ceab
--- /dev/null
+++ b/contrib/cvs/src/logmsg.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ */
+
+#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 *title,
+ char *message, char *revision, 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 FILE *fp;
+static char *str_list;
+static char *editinfo_editor;
+static Ctype type;
+
+/*
+ * Puts a standard header on the output which is either being prepared for an
+ * editor session, or being sent to a logfile program. The modified, added,
+ * and removed files are included (if any) and formatted to look pretty. */
+static char *prefix;
+static int col;
+static void
+setup_tmpfile (xfp, xprefix, changes)
+ FILE *xfp;
+ char *xprefix;
+ List *changes;
+{
+ /* set up statics */
+ fp = xfp;
+ prefix = xprefix;
+
+ type = T_MODIFIED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sModified Files:\n", prefix);
+ (void) fprintf (fp, "%s\t", prefix);
+ col = 8;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ }
+ type = T_ADDED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sAdded Files:\n", prefix);
+ (void) fprintf (fp, "%s\t", prefix);
+ col = 8;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ }
+ type = T_REMOVED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sRemoved Files:\n", prefix);
+ (void) fprintf (fp, "%s\t", prefix);
+ col = 8;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ }
+}
+
+/*
+ * Looks for nodes of a specified type and returns 1 if found
+ */
+static int
+find_type (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) type)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Breaks the files list into reasonable sized lines to avoid line wrap...
+ * all in the name of pretty output. It only works on nodes whose types
+ * match the one we're looking for
+ */
+static int
+fmt_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) type)
+ {
+ if ((col + (int) strlen (p->key)) > 70)
+ {
+ (void) fprintf (fp, "\n%s\t", prefix);
+ col = 8;
+ }
+ (void) fprintf (fp, "%s ", p->key);
+ col += strlen (p->key) + 1;
+ }
+ return (0);
+}
+
+/*
+ * Builds a temporary file using setup_tmpfile() and invokes the user's
+ * editor on the file. The header garbage in the resultant file is then
+ * stripped and the log message is stored in the "message" argument.
+ *
+ * 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[L_tmpnam+1];
+ 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 */
+ (void) tmpnam (fname);
+ again:
+ if ((fp = fopen (fname, "w+")) == NULL)
+ error (1, 0, "cannot create temporary file %s", fname);
+
+ if (*messagep)
+ {
+ (void) fprintf (fp, "%s", *messagep);
+
+ if ((*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 = 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, 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 (stat (fname, &pre_stbuf) == -1)
+ pre_stbuf.st_mtime = 0;
+
+ if (editinfo_editor)
+ free (editinfo_editor);
+ editinfo_editor = (char *) NULL;
+ if (repository != NULL)
+ (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
+
+ /* run the editor */
+ run_setup ("%s", editinfo_editor ? editinfo_editor : Editor);
+ run_arg (fname);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+ RUN_NORMAL | RUN_SIGIGNORE)) != 0)
+ error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
+ editinfo_editor ? "Logfile verification failed" :
+ "warning: editor session failed");
+
+ /* put the entire message back into the *messagep variable */
+
+ fp = open_file (fname, "r");
+
+ if (*messagep)
+ free (*messagep);
+
+ if (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, sizeof (CVSEDITPREFIX) - 1) == 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
+ || *line == '\n' || *line == 'c' || *line == 'C')
+ break;
+ if (*line == 'a' || *line == 'A')
+ error (1, 0, "aborted by user");
+ if (*line == 'e' || *line == 'E')
+ goto again;
+ if (*line == '!')
+ {
+ reuse_log_message = 1;
+ break;
+ }
+ (void) printf ("Unknown input\n");
+ }
+ }
+ if (line)
+ free (line);
+ if (unlink_file (fname) < 0)
+ error (0, errno, "warning: cannot remove temp file %s", 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 = 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 char *title;
+static FILE *logfp;
+static char *message;
+static char *revision;
+static List *changes;
+
+void
+Update_Logfile (repository, xmessage, xrevision, xlogfp, xchanges)
+ char *repository;
+ char *xmessage;
+ char *xrevision;
+ FILE *xlogfp;
+ List *xchanges;
+{
+ char *srepos;
+
+ /* 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;
+ revision = xrevision;
+ logfp = xlogfp;
+ changes = xchanges;
+
+ /* figure out a good title string */
+ srepos = Short_Repository (repository);
+
+ /* allocate a chunk of memory to hold the title string */
+ if (!str_list)
+ str_list = xmalloc (MAXLISTLEN);
+ str_list[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);
+ title = xmalloc (strlen (srepos) + strlen (str_list) + 1 + 2); /* for 's */
+ (void) sprintf (title, "'%s%s'", srepos, str_list);
+
+ /* to be nice, free up this chunk of memory */
+ free (str_list);
+ str_list = (char *) NULL;
+
+ /* call Parse_Info to do the actual logfile updates */
+ (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
+
+ /* clean up */
+ free (title);
+}
+
+/*
+ * callback proc to actually do the logfile write from Update_Logfile
+ */
+static int
+update_logfile_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ return (logfile_write (repository, filter, title, message, revision,
+ logfp, changes));
+}
+
+/*
+ * concatenate each name onto str_list
+ */
+static int
+title_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) type)
+ {
+ (void) strcat (str_list, " ");
+ (void) strcat (str_list, p->key);
+ }
+ return (0);
+}
+
+/*
+ * Since some systems don't define this...
+ */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+/*
+ * Writes some stuff to the logfile "filter" and returns the status of the
+ * filter program.
+ */
+static int
+logfile_write (repository, filter, title, message, revision, logfp, changes)
+ char *repository;
+ char *filter;
+ char *title;
+ char *message;
+ char *revision;
+ FILE *logfp;
+ List *changes;
+{
+ char cwd[PATH_MAX];
+ FILE *pipefp;
+ char *prog = xmalloc (MAXPROGLEN);
+ char *cp;
+ int c;
+ int pipestatus;
+
+ /*
+ * Only 1 %s argument is supported in the filter
+ */
+ (void) sprintf (prog, filter, title);
+ 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:%s\n\n", hostname,
+ ((cp = getwd (cwd)) != NULL) ? cp : cwd);
+ if (revision && *revision)
+ (void) fprintf (pipefp, "Revision/Branch: %s\n\n", revision);
+ setup_tmpfile (pipefp, "", changes);
+ (void) fprintf (pipefp, "Log Message:\n%s\n", message);
+ if (logfp != (FILE *) 0)
+ {
+ (void) fprintf (pipefp, "Status:\n");
+ 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);
+}
diff --git a/contrib/cvs/src/main.c b/contrib/cvs/src/main.c
new file mode 100644
index 0000000..daa0230
--- /dev/null
+++ b/contrib/cvs/src/main.c
@@ -0,0 +1,814 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * This is the main C driver for the CVS system.
+ *
+ * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing
+ * the shell-script CVS system that this is based on.
+ *
+ * Usage:
+ * cvs [options] command [options] [files/modules...]
+ *
+ * Where "command" is composed of:
+ * admin RCS command
+ * checkout Check out a module/dir/file
+ * export Like checkout, but used for exporting sources
+ * update Brings work tree in sync with repository
+ * commit Checks files into the repository
+ * diff Runs diffs between revisions
+ * log Prints "rlog" information for files
+ * login Record user, host, repos, password
+ * add Adds an entry to the repository
+ * remove Removes an entry from the repository
+ * status Status info on the revisions
+ * rdiff "patch" format diff listing between releases
+ * tag Add/delete a symbolic tag to the RCS file
+ * rtag Add/delete a symbolic tag to the RCS file
+ * import Import sources into CVS, using vendor branches
+ * release Indicate that Module is no longer in use.
+ * history Display history of Users and Modules.
+ */
+
+#include "cvs.h"
+
+#ifdef HAVE_WINSOCK_H
+#include <winsock.h>
+#else
+extern int gethostname ();
+#endif
+
+#if HAVE_KERBEROS
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <krb.h>
+#ifndef HAVE_KRB_GET_ERR_TEXT
+#define krb_get_err_text(status) krb_err_txt[status]
+#endif
+#endif
+
+char *program_name;
+char *program_path;
+/*
+ * Initialize comamnd_name to "cvs" so that the first call to
+ * read_cvsrc tries to find global cvs options.
+ */
+char *command_name = "";
+
+/*
+ * Since some systems don't define this...
+ */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+char hostname[MAXHOSTNAMELEN];
+
+#ifdef AUTH_CLIENT_SUPPORT
+int use_authenticating_server = FALSE;
+#endif /* AUTH_CLIENT_SUPPORT */
+int use_editor = TRUE;
+int use_cvsrc = TRUE;
+int cvswrite = !CVSREAD_DFLT;
+int really_quiet = FALSE;
+int quiet = FALSE;
+int trace = FALSE;
+int noexec = FALSE;
+int logoff = FALSE;
+mode_t cvsumask = UMASK_DFLT;
+
+char *CurDir;
+
+/*
+ * Defaults, for the environment variables that are not set
+ */
+char *Rcsbin = RCSBIN_DFLT;
+char *Editor = EDITOR_DFLT;
+char *CVSroot = CVSROOT_DFLT;
+/*
+ * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root'
+ */
+char *CVSADM_Root = CVSROOT_DFLT;
+
+int add PROTO((int argc, char **argv));
+int admin PROTO((int argc, char **argv));
+int checkout PROTO((int argc, char **argv));
+int commit PROTO((int argc, char **argv));
+int diff PROTO((int argc, char **argv));
+int history PROTO((int argc, char **argv));
+int import PROTO((int argc, char **argv));
+int cvslog PROTO((int argc, char **argv));
+#ifdef AUTH_CLIENT_SUPPORT
+int login PROTO((int argc, char **argv));
+#endif /* AUTH_CLIENT_SUPPORT */
+int patch PROTO((int argc, char **argv));
+int release PROTO((int argc, char **argv));
+int cvsremove PROTO((int argc, char **argv));
+int rtag PROTO((int argc, char **argv));
+int status PROTO((int argc, char **argv));
+int tag PROTO((int argc, char **argv));
+int update PROTO((int argc, char **argv));
+
+const struct cmd
+{
+ char *fullname; /* Full name of the function (e.g. "commit") */
+ char *nick1; /* alternate name (e.g. "ci") */
+ char *nick2; /* another alternate names (e.g. "ci") */
+ int (*func) (); /* Function takes (argc, argv) arguments. */
+#ifdef CLIENT_SUPPORT
+ int (*client_func) (); /* Function to do it via the protocol. */
+#endif
+} cmds[] =
+
+{
+#ifdef CLIENT_SUPPORT
+#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1, f2 }
+#else
+#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1 }
+#endif
+
+ CMD_ENTRY("add", "ad", "new", add, client_add),
+ CMD_ENTRY("admin", "adm", "rcs", admin, client_admin),
+ CMD_ENTRY("annotate", NULL, NULL, annotate, client_annotate),
+ CMD_ENTRY("checkout", "co", "get", checkout, client_checkout),
+ CMD_ENTRY("commit", "ci", "com", commit, client_commit),
+ CMD_ENTRY("diff", "di", "dif", diff, client_diff),
+ CMD_ENTRY("edit", "edit", "edit", edit, client_edit),
+ CMD_ENTRY("editors", "editors","editors",editors, client_editors),
+ CMD_ENTRY("export", "exp", "ex", checkout, client_export),
+ CMD_ENTRY("history", "hi", "his", history, client_history),
+ CMD_ENTRY("import", "im", "imp", import, client_import),
+ CMD_ENTRY("init", NULL, NULL, init, client_init),
+ CMD_ENTRY("log", "lo", "rlog", cvslog, client_log),
+#ifdef AUTH_CLIENT_SUPPORT
+ CMD_ENTRY("login", "logon", "lgn", login, login),
+#endif /* AUTH_CLIENT_SUPPORT */
+ CMD_ENTRY("rdiff", "patch", "pa", patch, client_rdiff),
+ CMD_ENTRY("release", "re", "rel", release, client_release),
+ CMD_ENTRY("remove", "rm", "delete", cvsremove, client_remove),
+ CMD_ENTRY("status", "st", "stat", status, client_status),
+ CMD_ENTRY("rtag", "rt", "rfreeze", rtag, client_rtag),
+ CMD_ENTRY("tag", "ta", "freeze", tag, client_tag),
+ CMD_ENTRY("unedit", "unedit","unedit", unedit, client_unedit),
+ CMD_ENTRY("update", "up", "upd", update, client_update),
+ CMD_ENTRY("watch", "watch", "watch", watch, client_watch),
+ CMD_ENTRY("watchers", "watchers","watchers",watchers,client_watchers),
+#ifdef SERVER_SUPPORT
+ /*
+ * The client_func is also server because we might have picked up a
+ * CVSROOT environment variable containing a colon. The client will send
+ * the real root later.
+ */
+ CMD_ENTRY("server", "server", "server", server, server),
+#endif
+ CMD_ENTRY(NULL, NULL, NULL, NULL, NULL),
+
+#undef CMD_ENTRY
+};
+
+static const char *const usg[] =
+{
+ "Usage: %s [cvs-options] command [command-options] [files...]\n",
+ " Where 'cvs-options' are:\n",
+ " -H Displays Usage information for command\n",
+ " -Q Cause CVS to be really quiet.\n",
+ " -q Cause CVS to be somewhat quiet.\n",
+ " -r Make checked-out files read-only\n",
+ " -w Make checked-out files read-write (default)\n",
+ " -l Turn History logging off\n",
+ " -n Do not execute anything that will change the disk\n",
+ " -t Show trace of program execution -- Try with -n\n",
+ " -v CVS version and copyright\n",
+ " -b bindir Find RCS programs in 'bindir'\n",
+ " -e editor Use 'editor' for editing log information\n",
+ " -d CVS_root Overrides $CVSROOT as the root of the CVS tree\n",
+ " -f Do not use the ~/.cvsrc file\n",
+#ifdef CLIENT_SUPPORT
+ " -z # Use 'gzip -#' for net traffic if possible.\n",
+#endif
+ " -s VAR=VAL Set CVS user variable.\n",
+ "\n",
+ " and where 'command' is: add, admin, etc. (use the --help-commands\n",
+ " option for a list of commands)\n",
+ NULL,
+};
+
+static const char *const cmd_usage[] =
+{
+ "CVS commands are:\n",
+ " add Adds a new file/directory to the repository\n",
+ " admin Administration front end for rcs\n",
+ " annotate Show revision where each line was modified\n",
+ " checkout Checkout sources for editing\n",
+ " commit Checks files into the repository\n",
+ " diff Runs diffs between revisions\n",
+ " edit Get ready to edit a watched file\n",
+ " editors See who is editing a watched file\n",
+ " history Shows status of files and users\n",
+ " import Import sources into CVS, using vendor branches\n",
+ " export Export sources from CVS, similar to checkout\n",
+ " log Prints out 'rlog' information for files\n",
+#ifdef AUTH_CLIENT_SUPPORT
+ " login Prompt for password for authenticating server.\n",
+#endif /* AUTH_CLIENT_SUPPORT */
+ " rdiff 'patch' format diffs between releases\n",
+ " release Indicate that a Module is no longer in use\n",
+ " remove Removes an entry from the repository\n",
+ " status Status info on the revisions\n",
+ " tag Add a symbolic tag to checked out version of RCS file\n",
+ " unedit Undo an edit command\n",
+ " rtag Add a symbolic tag to the RCS file\n",
+ " update Brings work tree in sync with repository\n",
+ " watch Set watches\n",
+ " watchers See who is watching a file\n",
+ NULL,
+};
+
+static RETSIGTYPE
+main_cleanup ()
+{
+ exit (EXIT_FAILURE);
+}
+
+static void
+error_cleanup PROTO((void))
+{
+ Lock_Cleanup();
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_cleanup (0);
+#endif
+}
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ extern char *version_string;
+ extern char *config_string;
+ char *cp, *end;
+ const struct cmd *cm;
+ int c, err = 0;
+ static int help = FALSE;
+ static int version_flag = FALSE;
+ static int help_commands = FALSE;
+ int rcsbin_update_env, cvs_update_env = 0;
+ static struct option long_options[] =
+ {
+ {"help", 0, &help, TRUE},
+ {"version", 0, &version_flag, TRUE},
+ {"help-commands", 0, &help_commands, TRUE},
+ {0, 0, 0, 0}
+ };
+ /* `getopt_long' stores the option index here, but right now we
+ don't use it. */
+ int option_index = 0;
+
+ error_set_cleanup (error_cleanup);
+
+/* The socket subsystems on NT and OS2 must be initialized before use */
+#ifdef INITIALIZE_SOCKET_SUBSYSTEM
+ INITIALIZE_SOCKET_SUBSYSTEM();
+#endif /* INITIALIZE_SOCKET_SUBSYSTEM */
+
+ /*
+ * Just save the last component of the path for error messages
+ */
+ program_path = xstrdup (argv[0]);
+ program_name = last_component (argv[0]);
+
+ CurDir = xmalloc (PATH_MAX);
+#ifndef SERVER_SUPPORT
+ if (!getwd (CurDir))
+ error (1, 0, "cannot get working directory: %s", CurDir);
+#endif
+
+ /*
+ * Query the environment variables up-front, so that
+ * they can be overridden by command line arguments
+ */
+ rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */
+ cvs_update_env = 0;
+ if ((cp = getenv (RCSBIN_ENV)) != NULL)
+ {
+ Rcsbin = cp;
+ rcsbin_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 = FALSE;
+ 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);
+ }
+
+ /* This has the effect of setting getopt's ordering to REQUIRE_ORDER,
+ which is what we need to distinguish between global options and
+ command options. FIXME: It would appear to be possible to do this
+ much less kludgily by passing "+" as the first character to the
+ option string we pass to getopt_long. */
+ optind = 1;
+
+
+ /* 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 = FALSE;
+ }
+
+ /*
+ * Scan cvsrc file for global options.
+ */
+ if (use_cvsrc)
+ read_cvsrc (&argc, &argv, "cvs");
+
+ optind = 1;
+ opterr = 1;
+
+ while ((c = getopt_long
+ (argc, argv, "Qqrwtnlvb:e:d:Hfz:s:", long_options, &option_index))
+ != EOF)
+ {
+ switch (c)
+ {
+ case 0:
+ /* getopt_long took care of setting the flag. */
+ break;
+ case 'Q':
+ really_quiet = TRUE;
+ /* FALL THROUGH */
+ case 'q':
+ quiet = TRUE;
+ break;
+ case 'r':
+ cvswrite = FALSE;
+ break;
+ case 'w':
+ cvswrite = TRUE;
+ break;
+ case 't':
+ trace = TRUE;
+ break;
+ case 'n':
+ noexec = TRUE;
+ case 'l': /* Fall through */
+ logoff = TRUE;
+ break;
+ case 'v':
+ version_flag = TRUE;
+ break;
+ case 'b':
+ Rcsbin = optarg;
+ rcsbin_update_env = 1; /* need to update environment */
+ break;
+ case 'e':
+ Editor = optarg;
+ break;
+ case 'd':
+ CVSroot = optarg;
+ cvs_update_env = 1; /* need to update environment */
+ break;
+ case 'H':
+ use_cvsrc = FALSE; /* this ensure that cvs -H works */
+ help = TRUE;
+ break;
+ case 'f':
+ use_cvsrc = FALSE;
+ 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 '?':
+ default:
+ usage (usg);
+ }
+ }
+
+ if (version_flag == TRUE)
+ {
+ (void) fputs (version_string, stdout);
+ (void) fputs (config_string, stdout);
+ (void) fputs ("\n", stdout);
+ (void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout);
+ (void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout);
+ (void) fputs ("Copyright (c) 1992, Brian Berliner and Jeff Polk\n", stdout);
+ (void) fputs ("Copyright (c) 1989-1992, Brian Berliner\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);
+ exit (0);
+ }
+ else if (help_commands)
+ usage (cmd_usage);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ usage (usg);
+
+#ifdef HAVE_KERBEROS
+ /* 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 (argv[0], "kserver") == 0)
+ {
+ 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];
+ Key_schedule sched;
+ char user[ANAME_SZ];
+ struct passwd *pw;
+
+ 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));
+ exit (EXIT_FAILURE);
+ }
+
+ 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));
+ exit (EXIT_FAILURE);
+ }
+
+ /* 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));
+ exit (EXIT_FAILURE);
+ }
+
+ pw = getpwnam (user);
+ if (pw == NULL)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 %s: no such user\n", user);
+ exit (EXIT_FAILURE);
+ }
+
+ initgroups (pw->pw_name, pw->pw_gid);
+ setgid (pw->pw_gid);
+ setuid (pw->pw_uid);
+ /* Inhibit access by randoms. Don't want people randomly
+ changing our temporary tree before we check things in. */
+ umask (077);
+
+#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 (user));
+ (void) sprintf (env, "LOGNAME=%s", user);
+ (void) putenv (env);
+
+ env = xmalloc (sizeof "USER=" + strlen (user));
+ (void) sprintf (env, "USER=%s", user);
+ (void) putenv (env);
+ }
+#endif
+
+ /* Pretend we were invoked as a plain server. */
+ argv[0] = "server";
+ }
+#endif /* HAVE_KERBEROS */
+
+
+#if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT)
+ if (strcmp (argv[0], "pserver") == 0)
+ {
+ /* Gets username and password from client, authenticates, then
+ switches to run as that user and sends an ACK back to the
+ client. */
+ authenticate_connection ();
+
+ /* Pretend we were invoked as a plain server. */
+ argv[0] = "server";
+ }
+#endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */
+
+
+ /*
+ * See if we are able to find a 'better' value for CVSroot in the
+ * CVSADM_ROOT directory.
+ */
+#ifdef SERVER_SUPPORT
+ if (strcmp (argv[0], "server") == 0 && CVSroot == NULL)
+ CVSADM_Root = NULL;
+ else
+ CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
+#else /* No SERVER_SUPPORT */
+ CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
+#endif /* No SERVER_SUPPORT */
+ if (CVSADM_Root != NULL)
+ {
+ if (CVSroot == NULL || !cvs_update_env)
+ {
+ CVSroot = CVSADM_Root;
+ cvs_update_env = 1; /* need to update environment */
+ }
+#ifdef CLIENT_SUPPORT
+ else if (!getenv ("CVS_IGNORE_REMOTE_ROOT"))
+#else /* ! CLIENT_SUPPORT */
+ else
+#endif /* CLIENT_SUPPORT */
+ {
+ /*
+ * Now for the hard part, compare the two directories. If they
+ * are not identical, then abort this command.
+ */
+ if ((fncmp (CVSroot, CVSADM_Root) != 0) &&
+ !same_directories(CVSroot, CVSADM_Root))
+ {
+ error (0, 0, "%s value for CVS Root found in %s",
+ CVSADM_Root, CVSADM_ROOT);
+ error (0, 0, "does not match command line -d %s setting",
+ CVSroot);
+ error (1, 0,
+ "you may wish to try the cvs command again without the -d option ");
+ }
+ }
+ }
+
+ /* CVSroot may need fixing up, if an access-method was specified,
+ * but not a user. Later code assumes that if CVSroot contains an
+ * access-method, then it also has a user. We print a warning and
+ * die if we can't guarantee that.
+ */
+ if (CVSroot
+ && *CVSroot
+ && (CVSroot[0] == ':')
+ && (strchr (CVSroot, '@') == NULL))
+ {
+ error (1, 0,
+ "must also give a username if specifying access method");
+ }
+
+ /*
+ * Specifying just the '-H' flag to the sub-command causes a Usage
+ * message to be displayed.
+ */
+ command_name = cp = argv[0];
+ if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0))
+ argc = -1;
+ else
+ {
+ /*
+ * Check to see if we can write into the history file. If not,
+ * we assume that we can't work in the repository.
+ * BUT, only if the history file exists.
+ */
+#ifdef SERVER_SUPPORT
+ if (strcmp (command_name, "server") != 0 || CVSroot != NULL)
+#endif
+ {
+ char path[PATH_MAX];
+ int save_errno;
+
+ if (!CVSroot || !*CVSroot)
+ error (1, 0, "You don't have a %s environment variable",
+ CVSROOT_ENV);
+ (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM);
+ if (!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
+#ifdef CLIENT_SUPPORT
+ /* If we are a remote client, the root need not exist
+ on the client machine (FIXME: we should also skip
+ the check for CVSROOTADM_HISTORY being writable;
+ it shouldn't matter if there is a read-only file
+ which happens to have the same name on the client
+ machine). */
+ && strchr (CVSroot, ':') == NULL)
+#endif
+ {
+ error (0, 0,
+ "Sorry, you don't have sufficient access to %s", CVSroot);
+ error (1, save_errno, "%s", path);
+ }
+ }
+ (void) strcat (path, "/");
+ (void) strcat (path, CVSROOTADM_HISTORY);
+ if (isfile (path) && !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);
+ }
+ }
+ }
+
+#ifdef SERVER_SUPPORT
+ if (strcmp (command_name, "server") == 0)
+ /* This is only used for writing into the history file. Might
+ be nice to have hostname and/or remote path, on the other hand
+ I'm not sure whether it is worth the trouble. */
+ strcpy (CurDir, "<remote>");
+ else if (!getwd (CurDir))
+ error (1, 0, "cannot get working directory: %s", CurDir);
+#endif
+
+#ifdef HAVE_PUTENV
+ /* Now, see if we should update the environment with the Rcsbin value */
+ if (cvs_update_env)
+ {
+ char *env;
+
+ env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
+ (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+ }
+ if (rcsbin_update_env)
+ {
+ char *env;
+
+ env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1);
+ (void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+ }
+#endif
+
+ /*
+ * If Rcsbin is set to something, make sure it is terminated with
+ * a slash character. If not, add one.
+ */
+ if (*Rcsbin)
+ {
+ int len = strlen (Rcsbin);
+ char *rcsbin;
+
+ if (Rcsbin[len - 1] != '/')
+ {
+ rcsbin = Rcsbin;
+ Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */
+ (void) strcpy (Rcsbin, rcsbin);
+ (void) strcat (Rcsbin, "/");
+ }
+ }
+
+ for (cm = cmds; cm->fullname; cm++)
+ {
+ if (cm->nick1 && !strcmp (cp, cm->nick1))
+ break;
+ if (cm->nick2 && !strcmp (cp, cm->nick2))
+ break;
+ if (!strcmp (cp, cm->fullname))
+ break;
+ }
+
+ if (!cm->fullname)
+ usage (usg); /* no match */
+ else
+ {
+ command_name = cm->fullname; /* Global pointer for later use */
+
+ /* 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
+
+ gethostname(hostname, sizeof (hostname));
+
+#ifdef HAVE_SETVBUF
+ /*
+ * Make stdout line buffered, so 'tail -f' can monitor progress.
+ * Patch creates too much output to monitor and it runs slowly.
+ */
+ if (strcmp (cm->fullname, "patch"))
+ (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0);
+#endif
+
+ if (use_cvsrc)
+ read_cvsrc (&argc, &argv, command_name);
+
+#ifdef CLIENT_SUPPORT
+ /* If cvsroot contains a colon, try to do it via the protocol. */
+ {
+ char *p = CVSroot == NULL ? NULL : strchr (CVSroot, ':');
+ if (p)
+ err = (*(cm->client_func)) (argc, argv);
+ else
+ err = (*(cm->func)) (argc, argv);
+ }
+#else /* No CLIENT_SUPPORT */
+ err = (*(cm->func)) (argc, argv);
+
+#endif /* No CLIENT_SUPPORT */
+ }
+ Lock_Cleanup ();
+ if (err)
+ return (EXIT_FAILURE);
+ return 0;
+}
+
+char *
+Make_Date (rawdate)
+ char *rawdate;
+{
+ struct tm *ftm;
+ time_t unixtime;
+ char date[256]; /* XXX bigger than we'll ever need? */
+ char *ret;
+
+ unixtime = get_date (rawdate, (struct timeb *) NULL);
+ if (unixtime == (time_t) - 1)
+ error (1, 0, "Can't parse date/time: %s", rawdate);
+#ifdef HAVE_RCS5
+ ftm = gmtime (&unixtime);
+#else
+ ftm = localtime (&unixtime);
+#endif
+ (void) sprintf (date, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+ ret = xstrdup (date);
+ return (ret);
+}
+
+void
+usage (cpp)
+ register const char *const *cpp;
+{
+ (void) fprintf (stderr, *cpp++, program_name, command_name);
+ for (; *cpp; cpp++)
+ (void) fprintf (stderr, *cpp);
+ exit (EXIT_FAILURE);
+}
diff --git a/contrib/cvs/src/mkmodules.c b/contrib/cvs/src/mkmodules.c
new file mode 100644
index 0000000..bdd27eb
--- /dev/null
+++ b/contrib/cvs/src/mkmodules.c
@@ -0,0 +1,742 @@
+/*
+ * 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"
+
+#ifndef DBLKSIZ
+#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */
+#endif
+
+static int checkout_file PROTO((char *file, char *temp));
+static void make_tempfile PROTO((char *temp));
+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. */
+ const char * const *contents;
+};
+
+static const char *const loginfo_contents[] = {
+ "# The \"loginfo\" file is used to control where \"cvs commit\" log information\n",
+ "# is sent. 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 a filter program that should expect log information on its standard\n",
+ "# input.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in the\n",
+ "# first field of this 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",
+ "# The filter program may use one and only one \"%s\" modifier (ala printf). If\n",
+ "# such a \"%s\" is specified in the filter program, a brief title is included\n",
+ "# (as one argument, enclosed in single quotes) showing the relative directory\n",
+ "# name and listing the modified file names.\n",
+ "#\n",
+ "# For example:\n",
+ "#DEFAULT (echo \"\"; who am i; 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 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 describes wrappers and other binary files to CVS.\n",
+ "#\n",
+ "# Wrappers are the concept where directories of files are to be\n",
+ "# treated as a single file. The intended use is to wrap up a wrapper\n",
+ "# into a single tar such that the tar archive can be treated as a\n",
+ "# single binary file in CVS.\n",
+ "#\n",
+ "# To solve the problem effectively, it was also necessary to be able to\n",
+ "# prevent rcsmerge from merging these files.\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",
+ "#\n",
+ "# and value is a single-quote delimited value.\n",
+ "#\n",
+ "# For example:\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",
+ "# 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 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_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},
+ {NULL, NULL}
+};
+
+/* Rebuild the checked out administrative files in directory DIR. */
+int
+mkmodules (dir)
+ char *dir;
+{
+ struct saved_cwd cwd;
+ /* FIXME: arbitrary limit */
+ char temp[PATH_MAX];
+ char *cp, *last, *fname;
+#ifdef MY_NDBM
+ DBM *db;
+#endif
+ FILE *fp;
+ /* FIXME: arbitrary limit */
+ char line[512];
+ const struct admin_file *fileptr;
+
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+
+ if (chdir (dir) < 0)
+ error (1, errno, "cannot chdir to %s", dir);
+
+ /*
+ * First, do the work necessary to update the "modules" database.
+ */
+ make_tempfile (temp);
+ switch (checkout_file (CVSROOTADM_MODULES, temp))
+ {
+
+ case 0: /* everything ok */
+#ifdef MY_NDBM
+ /* open it, to generate any duplicate errors */
+ if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
+ dbm_close (db);
+#else
+ write_dbmfile (temp);
+ rename_dbmfile (temp);
+#endif
+ rename_rcsfile (temp, CVSROOTADM_MODULES);
+ break;
+
+ case -1: /* fork failed */
+ (void) unlink_file (temp);
+ exit (EXIT_FAILURE);
+ /* NOTREACHED */
+
+ default:
+ error (0, 0,
+ "'cvs checkout' is less functional without a %s file",
+ CVSROOTADM_MODULES);
+ break;
+ } /* switch on checkout_file() */
+
+ (void) unlink_file (temp);
+
+ /* Checkout the files that need it in CVSROOT dir */
+ for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
+ if (fileptr->errormsg == NULL)
+ continue;
+ make_tempfile (temp);
+ 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
+ (void) unlink_file (temp);
+ }
+
+ /* Use 'fopen' instead of 'open_file' because we want to ignore error */
+ fp = fopen (CVSROOTADM_CHECKOUTLIST, "r");
+ if (fp)
+ {
+ /*
+ * File format:
+ * [<whitespace>]<filename><whitespace><error message><end-of-line>
+ *
+ * comment lines begin with '#'
+ */
+ while (fgets (line, sizeof (line), fp) != NULL)
+ {
+ /* 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(*fname); fname++)
+ ;
+
+ /* Find end of filename. */
+ for (cp = fname; *cp && !isspace(*cp); cp++)
+ ;
+ *cp = '\0';
+
+ make_tempfile (temp);
+ if (checkout_file (fname, temp) == 0)
+ {
+ rename_rcsfile (temp, fname);
+ }
+ else
+ {
+ for (cp++; cp < last && *last && isspace(*last); cp++)
+ ;
+ if (cp < last && *cp)
+ error (0, 0, cp, fname);
+ }
+ }
+ (void) fclose (fp);
+ }
+
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+
+ return (0);
+}
+
+/*
+ * Yeah, I know, there are NFS race conditions here.
+ */
+static void
+make_tempfile (temp)
+ char *temp;
+{
+ static int seed = 0;
+ int fd;
+
+ if (seed == 0)
+ seed = getpid ();
+ while (1)
+ {
+ (void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
+ if ((fd = open (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
+ break;
+ if (errno != EEXIST)
+ error (1, errno, "cannot create temporary file %s", temp);
+ }
+ if (close(fd) < 0)
+ error(1, errno, "cannot close temporary file %s", temp);
+}
+
+static int
+checkout_file (file, temp)
+ char *file;
+ char *temp;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ (void) sprintf (rcs, "%s%s", file, RCSEXT);
+ if (!isfile (rcs))
+ return (1);
+ run_setup ("%s%s -x,v/ -q -p", Rcsbin, RCS_CO);
+ run_arg (rcs);
+ if ((retcode = run_exec (RUN_TTY, temp, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file);
+ }
+ return (retcode);
+}
+
+#ifndef MY_NDBM
+
+static void
+write_dbmfile (temp)
+ char *temp;
+{
+ char line[DBLKSIZ], value[DBLKSIZ];
+ FILE *fp;
+ DBM *db;
+ char *cp, *vp;
+ datum key, val;
+ int len, cont, err = 0;
+
+ fp = open_file (temp, "r");
+ if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
+ error (1, errno, "cannot open dbm file %s for creation", temp);
+ for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
+ {
+ if ((cp = 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 (*vp))
+ vp++;
+ if (*vp == '\0')
+ continue; /* empty line */
+
+ /*
+ * If this was not a continuation line, add the entry to the database
+ */
+ if (!cont)
+ {
+ key.dptr = vp;
+ while (*vp && !isspace (*vp))
+ vp++;
+ key.dsize = vp - key.dptr;
+ *vp++ = '\0'; /* NULL terminate the key */
+ while (*vp && isspace (*vp))
+ vp++; /* skip whitespace to value */
+ if (*vp == '\0')
+ {
+ error (0, 0, "warning: NULL value for key `%s'", key.dptr);
+ continue;
+ }
+ val.dptr = vp;
+ val.dsize = strlen (vp);
+ if (dbm_store (db, key, val, DBM_INSERT) == 1)
+ {
+ error (0, 0, "duplicate key found for `%s'", key.dptr);
+ err++;
+ }
+ }
+ }
+ dbm_close (db);
+ (void) fclose (fp);
+ if (err)
+ {
+ char dotdir[50], dotpag[50], dotdb[50];
+
+ (void) sprintf (dotdir, "%s.dir", temp);
+ (void) sprintf (dotpag, "%s.pag", temp);
+ (void) sprintf (dotdb, "%s.db", temp);
+ (void) unlink_file (dotdir);
+ (void) unlink_file (dotpag);
+ (void) unlink_file (dotdb);
+ error (1, 0, "DBM creation failed; correct above errors");
+ }
+}
+
+static void
+rename_dbmfile (temp)
+ char *temp;
+{
+ char newdir[50], newpag[50], newdb[50];
+ char dotdir[50], dotpag[50], dotdb[50];
+ char bakdir[50], bakpag[50], bakdb[50];
+
+ (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 ();
+
+ (void) unlink_file (bakdir); /* rm .#modules.dir .#modules.pag */
+ (void) unlink_file (bakpag);
+ (void) unlink_file (bakdb);
+ (void) rename (dotdir, bakdir); /* mv modules.dir .#modules.dir */
+ (void) rename (dotpag, bakpag); /* mv modules.pag .#modules.pag */
+ (void) rename (dotdb, bakdb); /* mv modules.db .#modules.db */
+ (void) rename (newdir, dotdir); /* mv "temp".dir modules.dir */
+ (void) rename (newpag, dotpag); /* mv "temp".pag modules.pag */
+ (void) rename (newdb, dotdb); /* mv "temp".db modules.db */
+
+ /* OK -- make my day */
+ SIG_endCrSect ();
+}
+
+#endif /* !MY_NDBM */
+
+static void
+rename_rcsfile (temp, real)
+ char *temp;
+ char *real;
+{
+ char bak[50];
+ struct stat statbuf;
+ char rcs[PATH_MAX];
+
+ /* Set "x" bits if set in original. */
+ (void) sprintf (rcs, "%s%s", real, RCSEXT);
+ statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
+ (void) stat (rcs, &statbuf);
+
+ if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
+ error (0, errno, "warning: cannot chmod %s", temp);
+ (void) sprintf (bak, "%s%s", BAKPREFIX, real);
+ (void) unlink_file (bak); /* rm .#loginfo */
+ (void) rename (real, bak); /* mv loginfo .#loginfo */
+ (void) rename (temp, real); /* mv "temp" loginfo */
+}
+
+const char *const init_usage[] = {
+ "Usage: %s %s\n",
+ NULL
+};
+
+/* Create directory NAME if it does not already exist; fatal error for
+ other errors. FIXME: This should be in filesubr.c or thereabouts,
+ probably. Perhaps it should be further abstracted, though (for example
+ to handle CVSUMASK where appropriate?). */
+static void
+mkdir_if_needed (name)
+ char *name;
+{
+ if (CVS_MKDIR (name, 0777) < 0)
+ {
+ if (errno != EEXIST
+#ifdef EACCESS
+ /* OS/2; see longer comment in client.c. */
+ && errno != EACCESS
+#endif
+ )
+ error (1, errno, "cannot mkdir %s", name);
+ }
+}
+
+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;
+
+ const struct admin_file *fileptr;
+
+ umask (cvsumask);
+
+ if (argc > 1)
+ usage (init_usage);
+
+ if (client_active)
+ {
+ start_server ();
+
+ ign_setup ();
+ send_init_command ();
+ return get_responses_and_close ();
+ }
+
+ /* 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);
+
+ adm = xmalloc (strlen (CVSroot) + sizeof (CVSROOTADM) + 10);
+ strcpy (adm, CVSroot);
+ strcat (adm, "/");
+ strcat (adm, CVSROOTADM);
+ mkdir_if_needed (adm);
+
+ /* This is needed by the call to "ci" below. */
+ if (chdir (adm) < 0)
+ error (1, errno, "cannot change to directory %s", adm);
+
+ /* 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);
+ }
+ /* Now check the file in. FIXME: we could be using
+ add_rcs_file from import.c which is faster (if it were
+ tweaked slightly). */
+ run_setup ("%s%s -x,v/ -q -u -t-", Rcsbin, RCS_CI);
+ run_args ("-minitial checkin of %s", fileptr->filename);
+ run_arg (fileptr->filename);
+ retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ if (retcode != 0)
+ error (1, retcode == -1 ? errno : 0,
+ "failed to check in %s", info);
+ }
+ }
+
+ /* 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);
+ }
+
+ free (info);
+ free (info_v);
+
+ mkmodules (adm);
+
+ free (adm);
+ return 0;
+}
diff --git a/contrib/cvs/src/modules.c b/contrib/cvs/src/modules.c
new file mode 100644
index 0000000..0e07c0b
--- /dev/null
+++ b/contrib/cvs/src/modules.c
@@ -0,0 +1,900 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Modules
+ *
+ * Functions for accessing the modules file.
+ *
+ * The modules file supports basically three formats of lines:
+ * key [options] directory files... [ -x directory [files] ] ...
+ * key [options] directory [ -x directory [files] ] ...
+ * key -a aliases...
+ *
+ * The -a option allows an aliasing step in the parsing of the modules
+ * file. The "aliases" listed on a line following the -a are
+ * processed one-by-one, as if they were specified as arguments on the
+ * command line.
+ */
+
+#include "cvs.h"
+#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
+{
+ char *modname;
+ char *status;
+ 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[PATH_MAX];
+
+ if (CVSroot == NULL)
+ {
+ (void) fprintf (stderr,
+ "%s: must set the CVSROOT environment variable\n",
+ program_name);
+ error (1, 0, "or specify the '-d' option to %s", program_name);
+ }
+ (void) sprintf (mfile, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_MODULES);
+ return (dbm_open (mfile, O_RDONLY, 0666));
+}
+
+/*
+ * Close the modules file, if the open succeeded, that is
+ */
+void
+close_module (db)
+ DBM *db;
+{
+ if (db != NULL)
+ dbm_close (db);
+}
+
+/*
+ * This is the recursive function that processes a module name.
+ * It calls back the passed routine for each directory of a module
+ * It runs the post checkout or post tag proc from the modules file
+ */
+int
+do_module (db, mname, m_type, msg, callback_proc, where,
+ shorten, local_specified, run_module_prog, extra_arg)
+ DBM *db;
+ char *mname;
+ enum mtype m_type;
+ char *msg;
+ 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;
+ char *line;
+ int modargc;
+ int xmodargc;
+ char **modargv;
+ char *xmodargv[MAXFILEPERDIR];
+ char *value;
+ char *zvalue;
+ char *mwhere = NULL;
+ char *mfile = NULL;
+ char *spec_opt = NULL;
+ char xvalue[PATH_MAX];
+ int alias = 0;
+ datum key, val;
+ char *cp;
+ int c, err = 0;
+
+#ifdef SERVER_SUPPORT
+ if (trace)
+ {
+ fprintf (stderr, "%s%c-> do_module (%s, %s, %s, %s)\n",
+ error_use_protocol ? "E " : "",
+ (server_active) ? 'S' : ' ',
+ mname, msg, where ? where : "",
+ extra_arg ? extra_arg : "");
+ }
+#endif
+
+ /* if this is a directory to ignore, add it to that list */
+ if (mname[0] == '!' && mname[1] != '\0')
+ {
+ ign_dir_add (mname+1);
+ return(err);
+ }
+
+ /* strip extra stuff from the module name */
+ strip_path (mname);
+
+ /*
+ * Look up the module using the following scheme:
+ * 1) look for mname as a module name
+ * 2) look for mname as a directory
+ * 3) look for mname as a file
+ * 4) take mname up to the first slash and look it up as a module name
+ * (this is for checking out only part of a module)
+ */
+
+ /* look it up as a module name */
+ key.dptr = mname;
+ key.dsize = strlen (key.dptr);
+ if (db != NULL)
+ val = dbm_fetch (db, key);
+ else
+ val.dptr = NULL;
+ if (val.dptr != NULL)
+ {
+ /* null terminate the value XXX - is this space ours? */
+ val.dptr[val.dsize] = '\0';
+
+ /* If the line ends in a comment, strip it off */
+ if ((cp = strchr (val.dptr, '#')) != NULL)
+ {
+ do
+ *cp-- = '\0';
+ while (isspace (*cp));
+ }
+ else
+ {
+ /* Always strip trailing spaces */
+ cp = strchr (val.dptr, '\0');
+ while (cp > val.dptr && isspace(*--cp))
+ *cp = '\0';
+ }
+
+ value = val.dptr;
+ mwhere = xstrdup (mname);
+ goto found;
+ }
+ else
+ {
+ char file[PATH_MAX];
+ char attic_file[PATH_MAX];
+ char *acp;
+
+ /* check to see if mname is a directory or file */
+
+ (void) sprintf (file, "%s/%s", CVSroot, mname);
+ if ((acp = strrchr (mname, '/')) != NULL)
+ {
+ *acp = '\0';
+ (void) sprintf (attic_file, "%s/%s/%s/%s%s", CVSroot, mname,
+ CVSATTIC, acp + 1, RCSEXT);
+ *acp = '/';
+ }
+ else
+ (void) sprintf (attic_file, "%s/%s/%s%s", CVSroot, CVSATTIC,
+ mname, RCSEXT);
+
+ if (isdir (file))
+ {
+ value = mname;
+ goto found;
+ }
+ else
+ {
+ (void) strcat (file, RCSEXT);
+ if (isfile (file) || isfile (attic_file))
+ {
+ /* if mname was a file, we have to split it into "dir file" */
+ if ((cp = strrchr (mname, '/')) != NULL && cp != mname)
+ {
+ char *slashp;
+
+ /* put the ' ' in a copy so we don't mess up the original */
+ value = strcpy (xvalue, mname);
+ slashp = strrchr (value, '/');
+ *slashp = ' ';
+ }
+ else
+ {
+ /*
+ * the only '/' at the beginning or no '/' at all
+ * means the file we are interested in is in CVSROOT
+ * itself so the directory should be '.'
+ */
+ if (cp == mname)
+ {
+ /* drop the leading / if specified */
+ value = strcpy (xvalue, ". ");
+ (void) strcat (xvalue, mname + 1);
+ }
+ else
+ {
+ /* otherwise just copy it */
+ value = strcpy (xvalue, ". ");
+ (void) strcat (xvalue, mname);
+ }
+ }
+ goto found;
+ }
+ }
+ }
+
+ /* look up everything to the first / as a module */
+ if (mname[0] != '/' && (cp = 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)
+ {
+ do
+ *cp2-- = '\0';
+ while (isspace (*cp2));
+ }
+ value = val.dptr;
+
+ /* mwhere gets just the module name */
+ mwhere = xstrdup (mname);
+ mfile = cp + 1;
+
+ /* put the / back in mname */
+ *cp = '/';
+
+ goto found;
+ }
+
+ /* put the / back in mname */
+ *cp = '/';
+ }
+
+ /* if we got here, we couldn't find it using our search, so give up */
+ error (0, 0, "cannot find module `%s' - ignored", mname);
+ err++;
+ if (mwhere)
+ free (mwhere);
+ return (err);
+
+
+ /*
+ * At this point, we found what we were looking for in one
+ * of the many different forms.
+ */
+ found:
+
+ /* remember where we start */
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+
+ /* copy value to our own string since if we go recursive we'll be
+ really screwed if we do another dbm lookup */
+ zvalue = xstrdup (value);
+ value = zvalue;
+
+ /* search the value for the special delimiter and save for later */
+ if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL)
+ {
+ *cp = '\0'; /* null out the special char */
+ spec_opt = cp + 1; /* save the options for later */
+
+ if (cp != value) /* strip whitespace if necessary */
+ while (isspace (*--cp))
+ *cp = '\0';
+
+ if (cp == value)
+ {
+ /*
+ * we had nothing but special options, so skip arg
+ * parsing and regular stuff entirely
+ *
+ * If there were only special ones though, we must
+ * make the appropriate directory and cd to it
+ */
+ char *dir;
+
+ /* XXX - XXX - MAJOR HACK - DO NOT SHIP - this needs to
+ be !pipeout, but we don't know that here yet */
+ if (!run_module_prog)
+ goto out;
+
+ dir = where ? where : mname;
+ /* XXX - think about making null repositories at each dir here
+ instead of just at the bottom */
+ make_directories (dir);
+ if (chdir (dir) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", dir);
+ spec_opt = NULL;
+ err++;
+ goto out;
+ }
+ if (!isfile (CVSADM))
+ {
+ char nullrepos[PATH_MAX];
+
+ (void) sprintf (nullrepos, "%s/%s/%s", CVSroot,
+ CVSROOTADM, CVSNULLREPOS);
+ if (!isfile (nullrepos))
+ {
+ mode_t omask;
+ omask = umask (cvsumask);
+ (void) CVS_MKDIR (nullrepos, 0777);
+ (void) umask (omask);
+ }
+ if (!isdir (nullrepos))
+ error (1, 0, "there is no repository %s", nullrepos);
+
+ Create_Admin (".", dir,
+ nullrepos, (char *) NULL, (char *) NULL);
+ if (!noexec)
+ {
+ FILE *fp;
+
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (dir, nullrepos);
+#endif
+ }
+ }
+ out:
+ goto do_special;
+ }
+ }
+
+ /* don't do special options only part of a module was specified */
+ if (mfile != NULL)
+ spec_opt = NULL;
+
+ /*
+ * value now contains one of the following:
+ * 1) dir
+ * 2) dir file
+ * 3) the value from modules without any special args
+ * [ args ] dir [file] [file] ...
+ * or -a module [ module ] ...
+ */
+
+ /* Put the value on a line with XXX prepended for getopt to eat */
+ line = xmalloc (strlen (value) + 10);
+ (void) sprintf (line, "%s %s", "XXX", value);
+
+ /* turn the line into an argv[] array */
+ line2argv (&xmodargc, xmodargv, line);
+ free (line);
+ modargc = xmodargc;
+ modargv = xmodargv;
+
+ /* parse the args */
+ optind = 1;
+ while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1)
+ {
+ switch (c)
+ {
+ case 'a':
+ alias = 1;
+ break;
+ case 'd':
+ if (mwhere)
+ free (mwhere);
+ mwhere = xstrdup (optarg);
+ break;
+ case 'i':
+ checkin_prog = optarg;
+ break;
+ case 'l':
+ local_specified = 1;
+ break;
+ case 'o':
+ checkout_prog = optarg;
+ break;
+ case 'e':
+ export_prog = optarg;
+ break;
+ case 't':
+ tag_prog = optarg;
+ break;
+ case 'u':
+ update_prog = optarg;
+ break;
+ case '?':
+ error (0, 0,
+ "modules file has invalid option for key %s value %s",
+ key.dptr, val.dptr);
+ err++;
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+ free_cwd (&cwd);
+ return (err);
+ }
+ }
+ modargc -= optind;
+ modargv += optind;
+ if (modargc == 0)
+ {
+ error (0, 0, "modules file missing directory for module %s", mname);
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+ free_cwd (&cwd);
+ return (++err);
+ }
+
+ /* 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);
+ }
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+ free_cwd (&cwd);
+ return (err);
+ }
+
+ /* otherwise, process this module */
+ err += callback_proc (&modargc, modargv, where, mwhere, mfile, shorten,
+ local_specified, mname, msg);
+
+#if 0
+ /* FIXME: I've fixed this so that the correct arguments are called,
+ but now this fails because there is code below this point that
+ uses optarg values extracted from the arg vector. */
+ free_names (&xmodargc, xmodargv);
+#endif
+
+ /* if there were special include args, process them now */
+
+ do_special:
+
+ /* blow off special options if -l was specified */
+ if (local_specified)
+ spec_opt = NULL;
+
+ while (spec_opt != NULL)
+ {
+ char *next_opt;
+
+ cp = 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 (isspace (*--cp));
+ }
+ else
+ next_opt = NULL;
+
+ /* strip whitespace from front */
+ while (isspace (*spec_opt))
+ spec_opt++;
+
+ if (*spec_opt == '\0')
+ error (0, 0, "Mal-formed %c option for module %s - ignored",
+ CVSMODULE_SPEC, mname);
+ else
+ err += do_module (db, spec_opt, m_type, msg, callback_proc,
+ (char *) NULL, 0, local_specified,
+ run_module_prog, extra_arg);
+ spec_opt = next_opt;
+ }
+
+ /* write out the checkin/update prog files if necessary */
+#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))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+
+ /* run checkout or tag prog if appropriate */
+ if (err == 0 && run_module_prog)
+ {
+ if ((m_type == TAG && tag_prog != NULL) ||
+ (m_type == CHECKOUT && checkout_prog != NULL) ||
+ (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[PATH_MAX];
+ char *prog = (m_type == TAG ? tag_prog :
+ (m_type == CHECKOUT ? checkout_prog : export_prog));
+ char *real_where = (where != NULL ? where : mwhere);
+
+ if ((*prog != '/') && (*prog != '.'))
+ {
+ (void) sprintf (real_prog, "%s/%s", real_where, prog);
+ if (isfile (real_prog))
+ prog = real_prog;
+ }
+
+ run_setup ("%s %s", prog, real_where);
+ if (extra_arg)
+ run_arg (extra_arg);
+
+ if (!quiet)
+ {
+ (void) printf ("%s %s: Executing '", program_name,
+ command_name);
+ run_print (stdout);
+ (void) printf ("'\n");
+ }
+ err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ }
+ }
+
+ /* clean up */
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+
+ return (err);
+}
+
+/* - Read all the records from the modules database into an array.
+ - Sort the array depending on what format is desired.
+ - Print the array in the format desired.
+
+ Currently, there are only two "desires":
+
+ 1. Sort by module name and format the whole entry including switches,
+ files and the comment field: (Including aliases)
+
+ modulename -s switches, one per line, even if
+ -i it has many switches.
+ Directories and files involved, formatted
+ to cover multiple lines if necessary.
+ # Comment, also formatted to cover multiple
+ # lines if necessary.
+
+ 2. Sort by status field string and print: (*not* including aliases)
+
+ modulename STATUS Directories and files involved, formatted
+ to cover multiple lines if necessary.
+ # Comment, also formatted to cover multiple
+ # lines if necessary.
+*/
+
+static struct sortrec *s_head;
+
+static int s_max = 0; /* Number of elements allocated */
+static int s_count = 0; /* Number of elements used */
+
+static int Status; /* 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 (*cp))
+ cp++;
+ /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
+ while (*cp)
+ {
+ if (isspace (*cp))
+ {
+ *cp2++ = ' ';
+ while (isspace (*cp))
+ cp++;
+ }
+ else
+ *cp2++ = *cp++;
+ }
+ *cp2 = '\0';
+
+ /* Look for the "-s statusvalue" text */
+ if (Status)
+ {
+ s_rec->status = def_status;
+
+ /* Minor kluge, but general enough to maintain */
+ for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2)
+ {
+ if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ')
+ {
+ s_rec->status = (cp2 += 3);
+ while (*cp2 != ' ')
+ cp2++;
+ *cp2++ = '\0';
+ cp = cp2;
+ break;
+ }
+ }
+ }
+ else
+ cp = s_rec->rest;
+
+ /* Find comment field, clean up on all three sides & compress blanks */
+ if ((cp2 = cp = 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 *line;
+ char *moduleargv[MAXFILEPERDIR];
+
+#ifdef sun
+#ifdef TIOCGSIZE
+ struct ttysize ts;
+
+ (void) ioctl (0, TIOCGSIZE, &ts);
+ cols = ts.ts_cols;
+#endif
+#else
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+
+ (void) ioctl (0, TIOCGWINSZ, &ws);
+ cols = ws.ws_col;
+#endif
+#endif
+
+ Status = status;
+
+ /* Read the whole modules file into allocated records */
+ if (!(db = open_module ()))
+ error (1, 0, "failed to open the modules file");
+
+ for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db))
+ {
+ val = dbm_fetch (db, key);
+ if (val.dptr != NULL)
+ save_d (key.dptr, key.dsize, val.dptr, val.dsize);
+ }
+
+ /* Sort the list as requested */
+ qsort ((PTR) s_head, s_count, sizeof (struct sortrec), sort_order);
+
+ /*
+ * Run through the sorted array and format the entries
+ * indent = space for modulename + space for status field
+ */
+ indent = 12 + (status * 12);
+ fill = cols - (indent + 2);
+ for (s_h = s_head, i = 0; i < s_count; i++, s_h++)
+ {
+ /* Print module name (and status, if wanted) */
+ (void) printf ("%-12s", s_h->modname);
+ if (status)
+ {
+ (void) printf (" %-11s", s_h->status);
+ if (s_h->status != def_status)
+ *(s_h->status + strlen (s_h->status)) = ' ';
+ }
+
+ /* Parse module file entry as command line and print options */
+ line = xmalloc (strlen (s_h->modname) + strlen (s_h->rest) + 10);
+ (void) sprintf (line, "%s %s", s_h->modname, s_h->rest);
+ line2argv (&moduleargc, moduleargv, line);
+ 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')
+ {
+ (void) printf (" -%c", c);
+ wid += 3; /* Could just set it to 3 */
+ }
+ else
+ {
+ if (strlen (optarg) + 4 + wid > (unsigned) fill)
+ {
+ (void) printf ("\n%*s", indent, "");
+ wid = 0;
+ }
+ (void) printf (" -%c %s", c, optarg);
+ wid += strlen (optarg) + 4;
+ }
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Format and Print all the files and directories */
+ for (; argc--; argv++)
+ {
+ if (strlen (*argv) + wid > (unsigned) fill)
+ {
+ (void) printf ("\n%*s", indent, "");
+ wid = 0;
+ }
+ (void) printf (" %s", *argv);
+ wid += strlen (*argv) + 1;
+ }
+ (void) printf ("\n");
+
+ /* Format the comment field -- save_d (), compressed spaces */
+ for (cp2 = cp = s_h->comment; *cp; cp2 = cp)
+ {
+ (void) printf ("%*s # ", indent, "");
+ if (strlen (cp2) < (unsigned) (fill - 2))
+ {
+ (void) printf ("%s\n", cp2);
+ break;
+ }
+ cp += fill - 2;
+ while (*cp != ' ' && cp > cp2)
+ cp--;
+ if (cp == cp2)
+ {
+ (void) printf ("%s\n", cp2);
+ break;
+ }
+
+ *cp++ = '\0';
+ (void) printf ("%s\n", cp2);
+ }
+
+ free_names(&moduleargc, moduleargv);
+ }
+}
diff --git a/contrib/cvs/src/myndbm.c b/contrib/cvs/src/myndbm.c
new file mode 100644
index 0000000..527f7ee
--- /dev/null
+++ b/contrib/cvs/src/myndbm.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * A simple ndbm-emulator for CVS. It parses a text file of the format:
+ *
+ * key value
+ *
+ * at dbm_open time, and loads the entire file into memory. As such, it is
+ * probably only good for fairly small modules files. Ours is about 30K in
+ * size, and this code works fine.
+ */
+
+#include <assert.h>
+#include "cvs.h"
+#include "getline.h"
+
+#ifdef MY_NDBM
+
+static void mydbm_load_file ();
+
+/* ARGSUSED */
+DBM *
+mydbm_open (file, flags, mode)
+ char *file;
+ int flags;
+ int mode;
+{
+ FILE *fp;
+ DBM *db;
+
+ fp = 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 = 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_len;
+ /* FIXME: arbitrary limit. */
+ char value[MAXLINELEN];
+ char *cp, *vp;
+ int len, cont;
+
+ for (cont = 0; getline (&line, &line_len, fp) >= 0;)
+ {
+ if ((cp = strrchr (line, '\012')) != NULL)
+ *cp = '\0'; /* strip the newline */
+ cp = line + strlen (line);
+ if (cp > line && cp[-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). */
+ cp[-1] = '\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.
+ */
+ vp = value;
+ if (cont)
+ vp += strlen (value);
+
+ /*
+ * See if the line we read is a continuation line, and strip the
+ * backslash if so.
+ */
+ len = strlen (line);
+ if (len > 0)
+ cp = &line[len - 1];
+ else
+ cp = line;
+ if (*cp == '\\')
+ {
+ cont = 1;
+ *cp = '\0';
+ }
+ else
+ {
+ cont = 0;
+ }
+ (void) strcpy (vp, line);
+ if (value[0] == '#')
+ continue; /* comment line */
+ vp = value;
+ while (*vp && isspace (*vp))
+ vp++;
+ if (*vp == '\0')
+ continue; /* empty line */
+
+ /*
+ * If this was not a continuation line, add the entry to the database
+ */
+ if (!cont)
+ {
+ Node *p = getnode ();
+ char *kp;
+
+ kp = vp;
+ while (*vp && !isspace (*vp))
+ vp++;
+ *vp++ = '\0'; /* NULL terminate the key */
+ p->type = NDBMNODE;
+ p->key = xstrdup (kp);
+ while (*vp && isspace (*vp))
+ vp++; /* skip whitespace to value */
+ if (*vp == '\0')
+ {
+ error (0, 0, "warning: NULL value for key `%s'", p->key);
+ freenode (p);
+ continue;
+ }
+ p->data = xstrdup (vp);
+ if (addnode (list, p) == -1)
+ {
+ error (0, 0, "duplicate key found for `%s'", p->key);
+ freenode (p);
+ }
+ }
+ }
+ free (line);
+}
+
+#endif /* MY_NDBM */
diff --git a/contrib/cvs/src/myndbm.h b/contrib/cvs/src/myndbm.h
new file mode 100644
index 0000000..0431e15
--- /dev/null
+++ b/contrib/cvs/src/myndbm.h
@@ -0,0 +1,47 @@
+/* $CVSid: @(#)myndbm.h 1.4 94/09/21 $ */
+
+#ifdef MY_NDBM
+
+#define DBLKSIZ 4096
+
+typedef struct
+{
+ List *dbm_list; /* cached database */
+ Node *dbm_next; /* next key to return for nextkey() */
+
+ /* Name of the file to write to if modified is set. malloc'd. */
+ char *name;
+
+ /* Nonzero if the database has been modified and dbm_close needs to
+ write it out to disk. */
+ int modified;
+} DBM;
+
+typedef struct
+{
+ char *dptr;
+ int dsize;
+} datum;
+
+/*
+ * So as not to conflict with other dbm_open, etc., routines that may
+ * be included by someone's libc, all of my emulation routines are prefixed
+ * by "my" and we define the "standard" ones to be "my" ones here.
+ */
+#define dbm_open mydbm_open
+#define dbm_close mydbm_close
+#define dbm_fetch mydbm_fetch
+#define dbm_firstkey mydbm_firstkey
+#define dbm_nextkey mydbm_nextkey
+#define dbm_store mydbm_store
+#define DBM_INSERT 0
+#define DBM_REPLACE 1
+
+DBM *mydbm_open PROTO((char *file, int flags, int mode));
+void mydbm_close PROTO((DBM * db));
+datum mydbm_fetch PROTO((DBM * db, datum key));
+datum mydbm_firstkey PROTO((DBM * db));
+datum mydbm_nextkey PROTO((DBM * db));
+extern int mydbm_store PROTO ((DBM *, datum, datum, int));
+
+#endif /* MY_NDBM */
diff --git a/contrib/cvs/src/no_diff.c b/contrib/cvs/src/no_diff.c
new file mode 100644
index 0000000..a0d00f5
--- /dev/null
+++ b/contrib/cvs/src/no_diff.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * No Difference
+ *
+ * The user file looks modified judging from its time stamp; however it needn't
+ * be. No_difference() finds out whether it is or not. If it is not, it
+ * updates the administration.
+ *
+ * returns 0 if no differences are found and non-zero otherwise
+ */
+
+#include "cvs.h"
+
+int
+No_Difference (file, vers, entries, repository, update_dir)
+ char *file;
+ Vers_TS *vers;
+ List *entries;
+ char *repository;
+ char *update_dir;
+{
+ Node *p;
+ char tmp[L_tmpnam+1];
+ int ret;
+ char *ts, *options;
+ int retcode = 0;
+ char *tocvsPath;
+
+ if (!vers->srcfile || !vers->srcfile->path)
+ return (-1); /* different since we couldn't tell */
+
+ if (vers->entdata && vers->entdata->options)
+ options = xstrdup (vers->entdata->options);
+ else
+ options = xstrdup ("");
+
+ retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_user, options,
+ tmpnam (tmp), 0, 0);
+ if (retcode == 0)
+ {
+#if 0
+ /* Why would we want to munge the modes? And only if the timestamps
+ are different? And even for commands like "cvs status"???? */
+ if (!iswritable (file)) /* fix the modes as a side effect */
+ xchmod (file, 1);
+#endif
+
+ tocvsPath = wrap_tocvs_process_file (file);
+
+ /* do the byte by byte compare */
+ if (xcmp (tocvsPath == NULL ? file : tocvsPath, tmp) == 0)
+ {
+#if 0
+ /* Why would we want to munge the modes? And only if the
+ timestamps are different? And even for commands like
+ "cvs status"???? */
+ if (cvswrite == FALSE) /* fix the modes as a side effect */
+ xchmod (file, 0);
+#endif
+
+ /* no difference was found, so fix the entries file */
+ ts = time_stamp (file);
+ Register (entries, file,
+ vers->vn_user ? vers->vn_user : vers->vn_rcs, ts,
+ options, vers->tag, vers->date, (char *) 0);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* We need to update the entries line on the client side. */
+ server_update_entries
+ (file, update_dir, repository, SERVER_UPDATED);
+ }
+#endif
+ free (ts);
+
+ /* update the entdata pointer in the vers_ts structure */
+ p = findnode (entries, file);
+ vers->entdata = (Entnode *) p->data;
+
+ ret = 0;
+ }
+ else
+ ret = 1; /* files were really different */
+ if (tocvsPath)
+ {
+ /* Need to call unlink myself because the noexec variable
+ * has been set to 1. */
+ if (trace)
+ (void) fprintf (stderr, "%c-> unlink (%s)\n",
+#ifdef SERVER_SUPPORT
+ (server_active) ? 'S' : ' ',
+#else
+ ' ',
+#endif
+ tocvsPath);
+ if (unlink (tocvsPath) < 0)
+ error (0, errno, "could not remove %s", tocvsPath);
+ }
+ }
+ else
+ {
+ if (update_dir[0] == '\0')
+ error (0, retcode == -1 ? errno : 0,
+ "could not check out revision %s of %s",
+ vers->vn_user, file);
+ else
+ error (0, retcode == -1 ? errno : 0,
+ "could not check out revision %s of %s/%s",
+ vers->vn_user, update_dir, file);
+ ret = -1; /* different since we couldn't tell */
+ }
+
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> unlink2 (%s)\n",
+ (server_active) ? 'S' : ' ', tmp);
+#else
+ (void) fprintf (stderr, "-> unlink (%s)\n", tmp);
+#endif
+ if (unlink (tmp) < 0)
+ error (0, errno, "could not remove %s", tmp);
+ free (options);
+ return (ret);
+}
diff --git a/contrib/cvs/src/options.h.in b/contrib/cvs/src/options.h.in
new file mode 100644
index 0000000..7cb58dc
--- /dev/null
+++ b/contrib/cvs/src/options.h.in
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * This file holds (most of) the configuration tweaks that can be made to
+ * customize CVS for your site. CVS comes configured for a typical SunOS 4.x
+ * environment. The comments for each configurable item are intended to be
+ * self-explanatory. All #defines are tested first to see if an over-riding
+ * option was specified on the "make" command line.
+ *
+ * If special libraries are needed, you will have to edit the Makefile.in file
+ * or the configure script directly. Sorry.
+ */
+
+/*
+ * CVS provides the most features when used in conjunction with the Version-5
+ * release of RCS. Thus, it is the default. This also assumes that GNU diff
+ * Version-1.15 is being used as well -- you will have to configure your RCS
+ * V5 release separately to make this the case. If you do not have RCS V5 and
+ * GNU diff V1.15, comment out this define. You should not try mixing and
+ * matching other combinations of these tools.
+ */
+#ifndef HAVE_RCS5
+#define HAVE_RCS5
+#endif
+
+/*
+ * If, before installing this version of CVS, you were running RCS V4 AND you
+ * are installing this CVS and RCS V5 and GNU diff 1.15 all at the same time,
+ * you should turn on the following define. It only exists to try to do
+ * reasonable things with your existing checked out files when you upgrade to
+ * RCS V5, since the keyword expansion formats have changed with RCS V5.
+ *
+ * If you already have been running with RCS5, or haven't been running with CVS
+ * yet at all, or are sticking with RCS V4 for now, leave the commented out.
+ */
+#ifndef HAD_RCS4
+/* #define HAD_RCS4 */
+#endif
+
+/*
+ * For portability and heterogeneity reasons, CVS is shipped by default using
+ * my own text-file version of the ndbm database library in the src/myndbm.c
+ * file. If you want better performance and are not concerned about
+ * heterogeneous hosts accessing your modules file, turn this option off.
+ */
+#ifndef MY_NDBM
+#define MY_NDBM
+#endif
+
+/*
+ * The "diff" program to execute when creating patch output. This "diff"
+ * must support the "-c" option for context diffing. Specify a full
+ * pathname if your site wants to use a particular diff. Note that unlike
+ * the diff used with RCS, you *must not* supply -a here (doing so will cause
+ * the server to generate patches which patch cannot handle in some cases).
+ *
+ * NOTE: this program is only used for the ``patch'' sub-command (and
+ * for ``update'' if you are using the server). The other commands
+ * use rcsdiff which will use whatever version of diff was specified
+ * when rcsdiff was built on your system.
+ */
+
+#ifndef DIFF
+#define DIFF "diff"
+#endif
+
+/*
+ * The "grep" program to execute when checking to see if a merged file had
+ * any conflicts. This "grep" must support a standard basic
+ * regular expression as an argument. Specify a full pathname if your site
+ * wants to use a particular grep.
+ */
+
+#ifndef GREP
+#define GREP "grep"
+#endif
+
+/*
+ * The "patch" program to run when using the CVS server and accepting
+ * patches across the network. Specify a full pathname if your site
+ * wants to use a particular patch.
+ */
+#ifndef PATCH_PROGRAM
+#define PATCH_PROGRAM "patch"
+#endif
+
+/*
+ * By default, RCS programs are executed with the shell or through execlp(),
+ * so the user's PATH environment variable is searched. If you'd like to
+ * bind all RCS programs to a certain directory (perhaps one not in most
+ * people's PATH) then set the default in RCSBIN_DFLT. Note that setting
+ * this here will cause all RCS programs to be executed from this directory,
+ * unless the user overrides the default with the RCSBIN environment variable
+ * or the "-b" option to CVS.
+ *
+ * If you use the password-authenticating server, then you need to
+ * make sure that the server can find the RCS programs to invoke them.
+ * The authenticating server starts out running as root, and then
+ * switches to run as the appropriate user once authentication is
+ * complete. But no actual shell is ever started by that user, so the
+ * PATH environment variable may not contain the directory with the
+ * RCS binaries, even though if that user logged in normally, PATH
+ * would include the directory.
+ *
+ * One way to solve this problem is to set RCSBIN_DFLT here. An
+ * alternative is to make sure that root has the right directory in
+ * its path already. Another, probably better alternative is to
+ * specify -b in /etc/inetd.conf.
+ *
+ * This define should be either the empty string ("") or a full
+ * pathname to the directory containing all the installed programs
+ * from the RCS distribution.
+ */
+#ifndef RCSBIN_DFLT
+#define RCSBIN_DFLT ""
+#endif
+
+/*
+ * The default editor to use, if one does not specify the "-e" option to cvs,
+ * or does not have an EDITOR environment variable. I set this to just "vi",
+ * and use the shell to find where "vi" actually is. This allows sites with
+ * /usr/bin/vi or /usr/ucb/vi to work equally well (assuming that your PATH
+ * is reasonable).
+ */
+#ifndef EDITOR_DFLT
+#define EDITOR_DFLT "vi"
+#endif
+
+/*
+ * The default umask to use when creating or otherwise setting file or
+ * directory permissions in the repository. Must be a value in the
+ * range of 0 through 0777. For example, a value of 002 allows group
+ * rwx access and world rx access; a value of 007 allows group rwx
+ * access but no world access. This value is overridden by the value
+ * of the CVSUMASK environment variable, which is interpreted as an
+ * octal number.
+ */
+#ifndef UMASK_DFLT
+#define UMASK_DFLT 002
+#endif
+
+/*
+ * The cvs admin command is restricted to the members of the group
+ * CVS_ADMIN_GROUP. If this group does not exist, all users are
+ * allowed to run cvs admin. To disable the cvs admin for all users,
+ * create an empty group CVS_ADMIN_GROUP. To disable access control for
+ * cvs admin, comment out the define below.
+ */
+#ifndef CVS_ADMIN_GROUP
+#define CVS_ADMIN_GROUP "cvsadmin"
+#endif
+
+/*
+ * The Repository file holds the path to the directory within the source
+ * repository that contains the RCS ,v files for each CVS working directory.
+ * This path is either a full-path or a path relative to CVSROOT.
+ *
+ * The only advantage that I can see to having a relative path is that One can
+ * change the physical location of the master source repository, change one's
+ * CVSROOT environment variable, and CVS will work without problems. I
+ * recommend using full-paths.
+ */
+#ifndef RELATIVE_REPOS
+/* #define RELATIVE_REPOS */
+#endif
+
+/*
+ * When committing or importing files, you must enter a log message.
+ * Normally, you can do this either via the -m flag on the command line or an
+ * editor will be started for you. If you like to use logging templates (the
+ * rcsinfo file within the $CVSROOT/CVSROOT directory), you might want to
+ * force people to use the editor even if they specify a message with -m.
+ * Enabling FORCE_USE_EDITOR will cause the -m message to be appended to the
+ * temp file when the editor is started.
+ */
+#ifndef FORCE_USE_EDITOR
+/* #define FORCE_USE_EDITOR */
+#endif
+
+/*
+ * When locking the repository, some sites like to remove locks and assume
+ * the program that created them went away if the lock has existed for a long
+ * time. This used to be the default for previous versions of CVS. CVS now
+ * attempts to be much more robust, so lock files should not be left around
+ * by mistake. The new behaviour will never remove old locks (they must now
+ * be removed by hand). Enabling CVS_FUDGELOCKS will cause CVS to remove
+ * locks that are older than CVSLCKAGE seconds.
+ * Use of this option is NOT recommended.
+ */
+#ifndef CVS_FUDGELOCKS
+/* #define CVS_FUDGELOCKS */
+#endif
+
+/*
+ * When committing a permanent change, CVS and RCS make a log entry of
+ * who committed the change. If you are committing the change logged in
+ * as "root" (not under "su" or other root-priv giving program), CVS/RCS
+ * cannot determine who is actually making the change.
+ *
+ * As such, by default, CVS disallows changes to be committed by users
+ * logged in as "root". You can disable this option by commenting
+ * out the lines below.
+ */
+#ifndef CVS_BADROOT
+#define CVS_BADROOT
+#endif
+
+/*
+ * The "cvs diff" command accepts all the single-character options that GNU
+ * diff (1.15) accepts. Except -D. GNU diff uses -D as a way to put
+ * cpp-style #define's around the output differences. CVS, by default, uses
+ * -D to specify a free-form date (like "cvs diff -D '1 week ago'"). If
+ * you would prefer that the -D option of "cvs diff" work like the GNU diff
+ * option, then comment out this define.
+ */
+#ifndef CVS_DIFFDATE
+#define CVS_DIFFDATE
+#endif
+
+/* Define this to enable the SETXID support. The way to use this is
+ to create a group with no users in it (except perhaps cvs
+ administrators), set the cvs executable to setgid that group, chown
+ all the repository files to that group, and change all directory
+ permissions in the repository to 770. The last person to modify a
+ file will own it, but as long as directory permissions are set
+ right that won't matter. You'll need a system which inherits file
+ groups from the parent directory. I don't know how carefully this
+ has been inspected for security holes. */
+
+#ifndef SETXID_SUPPORT
+/* #define SETXID_SUPPORT */
+#endif
+
+/* Should we build the password-authenticating client? Whether to
+ include the password-authenticating _server_, on the other hand, is
+ set in config.h. */
+#define AUTH_CLIENT_SUPPORT 1
+
+/*
+ * If you are working with a large remote repository and a 'cvs checkout' is
+ * swamping your network and memory, define these to enable flow control.
+ * You will end up with even less guarantees of a consistant checkout,
+ * but that may be better than no checkout at all. The master server process
+ * will monitor how far it is getting behind, if it reaches the high water
+ * mark, it will signal the child process to stop generating data when
+ * convenient (ie: no locks are held, currently at the beginning of a
+ * new directory). Once the buffer has drained sufficiently to reach the
+ * low water mark, it will be signalled to start again.
+ * -- EXPERIMENTAL! -- A better solution may be in the works.
+ * You may override the default hi/low watermarks here too.
+ */
+#ifndef SERVER_FLOWCONTROL
+/* #define SERVER_FLOWCONTROL */
+/* #define SERVER_HI_WATER (2 * 1024 * 1024) */
+/* #define SERVER_LO_WATER (1 * 1024 * 1024) */
+#endif
+
+/* End of CVS configuration section */
+
+/*
+ * Externs that are included in libc, but are used frequently enough to
+ * warrant defining here.
+ */
+#ifndef STDC_HEADERS
+extern void exit ();
+#endif
+
+#ifndef getwd
+extern char *getwd ();
+#endif
+
diff --git a/contrib/cvs/src/parseinfo.c b/contrib/cvs/src/parseinfo.c
new file mode 100644
index 0000000..c567ef8
--- /dev/null
+++ b/contrib/cvs/src/parseinfo.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ */
+
+#include "cvs.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[PATH_MAX];
+ char line[MAXLINELEN];
+ char *default_value = NULL;
+ char *expanded_value= NULL;
+ int callback_done, line_number;
+ char *cp, *exp, *value, *srepos;
+ const char *regex_err;
+
+ if (CVSroot == NULL)
+ {
+ /* XXX - should be error maybe? */
+ error (0, 0, "CVSROOT variable not set");
+ return (1);
+ }
+
+ /* find the info file and open it */
+ (void) sprintf (infopath, "%s/%s/%s", CVSroot,
+ CVSROOTADM, infofile);
+ if ((fp_info = fopen (infopath, "r")) == NULL)
+ return (0); /* no file -> nothing special done */
+
+ /* strip off the CVSROOT if repository was absolute */
+ srepos = Short_Repository (repository);
+
+ 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 (fgets (line, sizeof (line), fp_info) != NULL)
+ {
+ line_number++;
+
+ /* skip lines starting with # */
+ if (line[0] == '#')
+ continue;
+
+ /* skip whitespace at beginning of line */
+ for (cp = line; *cp && isspace (*cp); cp++)
+ ;
+
+ /* if *cp is null, the whole line was blank */
+ if (*cp == '\0')
+ continue;
+
+ /* the regular expression is everything up to the first space */
+ for (exp = cp; *cp && !isspace (*cp); cp++)
+ ;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* skip whitespace up to the start of the matching value */
+ while (*cp && isspace (*cp))
+ cp++;
+
+ /* no value to match with the regular expression is an error */
+ if (*cp == '\0')
+ {
+ error (0, 0, "syntax error at line %d file %s; ignored",
+ line_number, infofile);
+ continue;
+ }
+ value = cp;
+
+ /* strip the newline off the end of the value */
+ if ((cp = strrchr (value, '\n')) != NULL)
+ *cp = '\0';
+
+ 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)
+ {
+ 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;
+ }
+ (void) fclose (fp_info);
+
+ /* if we fell through and didn't callback at all, do the default */
+ if (callback_done == 0 && default_value != NULL)
+ err += callproc (repository, default_value);
+
+ /* free up space if necessary */
+ if (default_value != NULL)
+ free (default_value);
+ if (expanded_value != NULL)
+ free (expanded_value);
+
+ return (err);
+}
diff --git a/contrib/cvs/src/patch.c b/contrib/cvs/src/patch.c
new file mode 100644
index 0000000..39b4e64
--- /dev/null
+++ b/contrib/cvs/src/patch.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Patch
+ *
+ * Create a Larry Wall format "patch" file between a previous release and the
+ * current head of a module, or between two releases. Can specify the
+ * release as either a date or a revision number.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static RETSIGTYPE patch_cleanup PROTO((void));
+static Dtype patch_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int patch_fileproc PROTO((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 = 1;
+static char *rev2 = NULL;
+static int rev2_validated = 1;
+static char *date1 = NULL;
+static char *date2 = NULL;
+static char tmpfile1[L_tmpnam+1], tmpfile2[L_tmpnam+1], tmpfile3[L_tmpnam+1];
+static int unidiff = 0;
+
+static const char *const patch_usage[] =
+{
+ "Usage: %s %s [-fl] [-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-c\tContext diffs (default)\n",
+ "\t-u\tUnidiff format.\n",
+ "\t-s\tShort patch - one liner per file.\n",
+ "\t-t\tTop two diffs - last change made to the file.\n",
+ "\t-D date\tDate.\n",
+ "\t-r rev\tRevision - symbolic or numeric.\n",
+ "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
+ NULL
+};
+
+int
+patch (argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ int c;
+ int err = 0;
+ DBM *db;
+
+ if (argc == -1)
+ usage (patch_usage);
+
+ optind = 1;
+ while ((c = 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':
+ if (atoi (optarg) <= 0)
+ error (1, 0, "must specify a version number to -V");
+ if (options)
+ free (options);
+ options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
+ (void) sprintf (options, "-V%s", optarg);
+ break;
+ case 'u':
+ unidiff = 1; /* Unidiff */
+ break;
+ case 'c': /* Context diff */
+ unidiff = 0;
+ break;
+ case '?':
+ default:
+ usage (patch_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Sanity checks */
+ if (argc < 1)
+ usage (patch_usage);
+
+ if (toptwo_diffs && patch_short)
+ error (1, 0, "-t and -s options are mutually exclusive");
+ if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
+ rev1 != NULL || rev2 != NULL))
+ error (1, 0, "must not specify revisions/dates with -t option!");
+
+ if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
+ rev1 == NULL && rev2 == NULL))
+ error (1, 0, "must specify at least one revision/date!");
+ if (date1 != NULL && date2 != NULL)
+ if (RCS_datecmp (date1, date2) >= 0)
+ error (1, 0, "second date must come after first date!");
+
+ /* if options is NULL, make it a NULL string */
+ if (options == NULL)
+ options = xstrdup ("");
+
+#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 char where[PATH_MAX];
+static int
+patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
+ mname, msg)
+ int *pargc;
+ char **argv;
+ char *xwhere;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *mname;
+ char *msg;
+{
+ int err = 0;
+ int which;
+ char repository[PATH_MAX];
+
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+ (void) strcpy (where, argv[0]);
+
+ /* if mfile isn't null, we need to set up to do only part of the module */
+ if (mfile != NULL)
+ {
+ char *cp;
+ char path[PATH_MAX];
+
+ /* if the portion of the module is a path, put the dir part on repos */
+ if ((cp = 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 */
+ (void) sprintf (path, "%s/%s", repository, mfile);
+ if (isdir (path))
+ {
+ /* directory means repository gets the dir tacked on */
+ (void) strcpy (repository, path);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ }
+ else
+ {
+ int i;
+
+ /* a file means muck argv */
+ for (i = 1; i < *pargc; i++)
+ free (argv[i]);
+ argv[1] = xstrdup (mfile);
+ (*pargc) = 2;
+ }
+ }
+
+ /* cd to the starting repository */
+ if (chdir (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ return (1);
+ }
+
+ if (force_tag_match)
+ which = W_REPOS | W_ATTIC;
+ else
+ which = W_REPOS;
+
+ 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, *pargc - 1, argv + 1, local,
+ which, 0, 1, where, 1, 1);
+
+ return (err);
+}
+
+/*
+ * Called to examine a particular RCS file, as appropriate with the options
+ * that were set above.
+ */
+/* ARGSUSED */
+static int
+patch_fileproc (finfo)
+ struct file_info *finfo;
+{
+ struct utimbuf t;
+ char *vers_tag, *vers_head;
+ char rcsspace[1][PATH_MAX];
+ char *rcs = rcsspace[0];
+ RCSNode *rcsfile;
+ FILE *fp1, *fp2, *fp3;
+ int ret = 0;
+ int isattic = 0;
+ int retcode = 0;
+ char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX];
+ char *line1, *line2;
+ size_t line1_chars_allocated;
+ size_t line2_chars_allocated;
+ char *cp1, *cp2;
+ FILE *fp;
+
+ /* find the parsed rcs file */
+ if ((rcsfile = finfo->rcs) == NULL)
+ return (1);
+ if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
+ isattic = 1;
+
+ (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, 0);
+
+ if (toptwo_diffs)
+ {
+ if (vers_head == NULL)
+ return (1);
+
+ if (!date1)
+ date1 = xmalloc (50); /* plenty big :-) */
+ *date1 = '\0';
+ if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1)
+ {
+ if (!really_quiet)
+ error (0, 0, "cannot find date in rcs file %s revision %s",
+ rcs, vers_head);
+ return (1);
+ }
+ }
+ vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, 0);
+
+ if (vers_tag == NULL && (vers_head == NULL || isattic))
+ return (0); /* nothing known about specified revs */
+
+ if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0)
+ return (0); /* not changed between releases */
+
+ if (patch_short)
+ {
+ (void) printf ("File %s ", finfo->fullname);
+ if (vers_tag == NULL)
+ (void) printf ("is new; current revision %s\n", vers_head);
+ else if (vers_head == NULL)
+ {
+ (void) printf ("is removed; not included in ");
+ if (rev2 != NULL)
+ (void) printf ("release tag %s", rev2);
+ else if (date2 != NULL)
+ (void) printf ("release date %s", date2);
+ else
+ (void) printf ("current release");
+ (void) printf ("\n");
+ }
+ else
+ (void) printf ("changed from revision %s to %s\n",
+ vers_tag, vers_head);
+ return (0);
+ }
+ if ((fp1 = fopen (tmpnam (tmpfile1), "w+")) != NULL)
+ (void) fclose (fp1);
+ if ((fp2 = fopen (tmpnam (tmpfile2), "w+")) != NULL)
+ (void) fclose (fp2);
+ if ((fp3 = fopen (tmpnam (tmpfile3), "w+")) != NULL)
+ (void) fclose (fp3);
+ if (fp1 == NULL || fp2 == NULL || fp3 == NULL)
+ {
+ error (0, 0, "cannot create temporary files");
+ ret = 1;
+ goto out;
+ }
+ if (vers_tag != NULL)
+ {
+ retcode = RCS_checkout (rcsfile->path, NULL, vers_tag, options, tmpfile1,
+ 0, 0);
+ if (retcode != 0)
+ {
+ if (!really_quiet)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "co of revision %s in %s failed", vers_tag, rcs);
+ ret = 1;
+ goto out;
+ }
+ memset ((char *) &t, 0, sizeof (t));
+ if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
+ (char *) 0, 0)) != -1)
+ (void) utime (tmpfile1, &t);
+ }
+ else if (toptwo_diffs)
+ {
+ ret = 1;
+ goto out;
+ }
+ if (vers_head != NULL)
+ {
+ retcode = RCS_checkout (rcsfile->path, NULL, vers_head, options, tmpfile2, 0, 0);
+ if (retcode != 0)
+ {
+ if (!really_quiet)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "co of revision %s in %s failed", vers_head, rcs);
+ ret = 1;
+ goto out;
+ }
+ if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
+ (char *) 0, 0)) != -1)
+ (void) utime (tmpfile2, &t);
+ }
+ run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c');
+ run_arg (tmpfile1);
+ run_arg (tmpfile2);
+
+ line1 = NULL;
+ line1_chars_allocated = 0;
+ line2 = NULL;
+ line2_chars_allocated = 0;
+
+ switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_REALLY))
+ {
+ 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 */
+ (void) fflush (stdout);
+ (void) printf ("Index: %s\n", finfo->fullname);
+ (void) fflush (stdout);
+
+ fp = open_file (tmpfile3, "r");
+ if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
+ getline (&line2, &line2_chars_allocated, fp) < 0)
+ {
+ error (0, errno, "failed to read diff file header %s for %s",
+ tmpfile3, rcs);
+ ret = 1;
+ (void) fclose (fp);
+ goto out;
+ }
+ if (!unidiff)
+ {
+ if (strncmp (line1, "*** ", 4) != 0 ||
+ strncmp (line2, "--- ", 4) != 0 ||
+ (cp1 = strchr (line1, '\t')) == NULL ||
+ (cp2 = strchr (line2, '\t')) == NULL)
+ {
+ error (0, 0, "invalid diff header for %s", rcs);
+ ret = 1;
+ (void) fclose (fp);
+ goto out;
+ }
+ }
+ else
+ {
+ if (strncmp (line1, "--- ", 4) != 0 ||
+ strncmp (line2, "+++ ", 4) != 0 ||
+ (cp1 = strchr (line1, '\t')) == NULL ||
+ (cp2 = strchr (line2, '\t')) == NULL)
+ {
+ error (0, 0, "invalid unidiff header for %s", rcs);
+ ret = 1;
+ (void) fclose (fp);
+ goto out;
+ }
+ }
+ if (CVSroot != NULL)
+ (void) sprintf (strippath, "%s/", CVSroot);
+ else
+ (void) strcpy (strippath, REPOS_STRIP);
+ if (strncmp (rcs, strippath, strlen (strippath)) == 0)
+ rcs += strlen (strippath);
+ if (vers_tag != NULL)
+ {
+ (void) sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
+ }
+ else
+ {
+ (void) strcpy (file1, DEVNULL);
+ }
+ (void) sprintf (file2, "%s:%s", finfo->fullname,
+ vers_head ? vers_head : "removed");
+
+ /* Note that this prints "diff" not DIFF. The format of a diff
+ does not depend on the name of the program which happens to
+ have produced it. */
+ if (unidiff)
+ {
+ (void) printf ("diff -u %s %s\n", file1, file2);
+ (void) printf ("--- %s%s+++ ", file1, cp1);
+ }
+ else
+ {
+ (void) printf ("diff -c %s %s\n", file1, file2);
+ (void) printf ("*** %s%s--- ", file1, cp1);
+ }
+
+ (void) printf ("%s%s", finfo->fullname, cp2);
+ /* spew the rest of the diff out */
+ while (getline (&line1, &line1_chars_allocated, fp) >= 0)
+ (void) fputs (line1, stdout);
+ (void) fclose (fp);
+ break;
+ default:
+ error (0, 0, "diff failed for %s", finfo->fullname);
+ }
+ out:
+ if (line1)
+ free (line1);
+ if (line2)
+ free (line2);
+ /* FIXME: should be checking for errors. */
+ (void) unlink (tmpfile1);
+ (void) unlink (tmpfile2);
+ (void) unlink (tmpfile3);
+ return (ret);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+patch_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Diffing %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Clean up temporary files
+ */
+static RETSIGTYPE
+patch_cleanup ()
+{
+ if (tmpfile1[0] != '\0')
+ (void) unlink_file (tmpfile1);
+ if (tmpfile2[0] != '\0')
+ (void) unlink_file (tmpfile2);
+ if (tmpfile3[0] != '\0')
+ (void) unlink_file (tmpfile3);
+}
diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c
new file mode 100644
index 0000000..c68c255
--- /dev/null
+++ b/contrib/cvs/src/rcs.c
@@ -0,0 +1,2262 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * The routines contained in this file do all the rcs file parsing and
+ * manipulation
+ */
+
+#include <assert.h>
+#include "cvs.h"
+
+static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile));
+static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch));
+static int getrcskey PROTO((FILE * fp, char **keyp, char **valp));
+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 rcsvers_delproc PROTO((Node * p));
+
+/*
+ * 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)
+
+
+/*
+ * Parse an rcsfile given a user file name and a repository
+ */
+RCSNode *
+RCS_parse (file, repos)
+ const char *file;
+ const char *repos;
+{
+ RCSNode *rcs;
+ FILE *fp;
+ char rcsfile[PATH_MAX];
+
+ (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
+ if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL)
+ {
+ rcs = RCS_parsercsfile_i(fp, rcsfile);
+ if (rcs != NULL)
+ rcs->flags |= VALID;
+
+ fclose (fp);
+ return (rcs);
+ }
+ else if (! existence_error (errno))
+ {
+ error (0, errno, "cannot open %s", rcsfile);
+ return NULL;
+ }
+
+ (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT);
+ if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL)
+ {
+ rcs = RCS_parsercsfile_i(fp, rcsfile);
+ if (rcs != NULL)
+ {
+ rcs->flags |= INATTIC;
+ rcs->flags |= VALID;
+ }
+
+ fclose (fp);
+ return (rcs);
+ }
+ else if (! existence_error (errno))
+ {
+ error (0, errno, "cannot open %s", rcsfile);
+ return NULL;
+ }
+
+ return (NULL);
+}
+
+/*
+ * Parse a specific rcsfile.
+ */
+RCSNode *
+RCS_parsercsfile (rcsfile)
+ char *rcsfile;
+{
+ FILE *fp;
+ RCSNode *rcs;
+
+ /* open the rcsfile */
+ if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) == NULL)
+ {
+ error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
+ return (NULL);
+ }
+
+ rcs = RCS_parsercsfile_i (fp, rcsfile);
+
+ fclose (fp);
+ return (rcs);
+}
+
+
+/*
+ */
+static RCSNode *
+RCS_parsercsfile_i (fp, rcsfile)
+ FILE *fp;
+ const char *rcsfile;
+{
+ RCSNode *rdata;
+ 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 and BRANCH keywords from the RCS header.
+ *
+ * Most cvs operatations on the main branch don't need any more
+ * information. Those that do call XXX to completely parse the
+ * RCS file. */
+
+ if (getrcskey (fp, &key, &value) == -1 || key == NULL)
+ goto l_error;
+ if (strcmp (key, RCSDESC) == 0)
+ goto l_error;
+
+ if (strcmp (RCSHEAD, key) == 0 && value != NULL)
+ rdata->head = xstrdup (value);
+
+ if (getrcskey (fp, &key, &value) == -1 || key == NULL)
+ goto l_error;
+ if (strcmp (key, RCSDESC) == 0)
+ goto l_error;
+
+ if (strcmp (RCSBRANCH, key) == 0 && value != NULL)
+ {
+ char *cp;
+
+ rdata->branch = xstrdup (value);
+ if ((numdots (rdata->branch) & 1) != 0)
+ {
+ /* turn it into a branch if it's a revision */
+ cp = strrchr (rdata->branch, '.');
+ *cp = '\0';
+ }
+ }
+
+ rdata->flags |= PARTIAL;
+ return rdata;
+
+l_error:
+ if (!really_quiet)
+ {
+ if (ferror(fp))
+ {
+ error (1, 0, "error reading `%s'", rcsfile);
+ }
+ else
+ {
+ error (0, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ }
+ }
+ freercsnode (&rdata);
+ 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. */
+static void
+RCS_reparsercsfile (rdata, pfp)
+ RCSNode *rdata;
+ FILE **pfp;
+{
+ FILE *fp;
+ char *rcsfile;
+
+ Node *q;
+ RCSVers *vnode;
+ int n;
+ char *cp;
+ char *key, *value;
+
+ assert (rdata != NULL);
+ rcsfile = rdata->path;
+
+ fp = fopen(rcsfile, FOPEN_BINARY_READ);
+ if (fp == NULL)
+ error (1, 0, "unable to reopen `%s'", rcsfile);
+
+ /* make a node */
+ rdata->versions = getlist ();
+
+ /*
+ * process all the special header information, break out when we get to
+ * the first revision delta
+ */
+ for (;;)
+ {
+ /* get the next key/value pair */
+
+ /* if key is NULL here, then the file is missing some headers
+ or we had trouble reading the file. */
+ if (getrcskey (fp, &key, &value) == -1 || key == NULL
+ || strcmp (key, RCSDESC) == 0)
+ {
+ if (ferror(fp))
+ {
+ error (1, 0, "error reading `%s'", rcsfile);
+ }
+ else
+ {
+ error (1, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ }
+ }
+
+ if (strcmp (RCSSYMBOLS, key) == 0)
+ {
+ if (value != NULL)
+ {
+ rdata->symbols_data = xstrdup(value);
+ continue;
+ }
+ }
+
+ if (strcmp (RCSEXPAND, key) == 0)
+ {
+ rdata->expand = xstrdup (value);
+ continue;
+ }
+
+ /*
+ * check key for '.''s and digits (probably a rev) if it is a
+ * revision, we are done with the headers and are down to the
+ * revision deltas, so we break out of the loop
+ */
+ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
+ /* do nothing */ ;
+ if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+ break;
+
+ /* if we haven't grabbed it yet, we didn't want it */
+ }
+
+ /*
+ * we got out of the loop, so we have the first part of the first
+ * revision delta in our hand key=the revision and value=the date key and
+ * its value
+ */
+ for (;;)
+ {
+ char *valp;
+
+ vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
+ memset (vnode, 0, sizeof (RCSVers));
+
+ /* fill in the version before we forget it */
+ vnode->version = xstrdup (key);
+
+ /* grab the value of the date from value */
+ valp = value + strlen (RCSDATE);/* skip the "date" keyword */
+ while (whitespace (*valp)) /* take space off front of value */
+ valp++;
+
+ vnode->date = xstrdup (valp);
+
+ /* Get author field. */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be using errno in case of ferror. */
+ if (key == NULL || strcmp (key, "author") != 0)
+ error (1, 0, "\
+unable to parse rcs file; `author' not in the expected place");
+ vnode->author = xstrdup (value);
+
+ /* Get state field. */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be using errno in case of ferror. */
+ if (key == NULL || strcmp (key, "state") != 0)
+ error (1, 0, "\
+unable to parse rcs file; `state' not in the expected place");
+ if (strcmp (value, "dead") == 0)
+ {
+ vnode->dead = 1;
+ }
+
+ /* fill in the branch list (if any branches exist) */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be handling various error conditions better. */
+ if (key != NULL && strcmp (key, RCSDESC) == 0)
+ value = NULL;
+ if (value != (char *) NULL)
+ {
+ vnode->branches = getlist ();
+ do_branches (vnode->branches, value);
+ }
+
+ /* fill in the next field if there is a next revision */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be handling various error conditions better. */
+ if (key != NULL && strcmp (key, RCSDESC) == 0)
+ value = NULL;
+ if (value != (char *) NULL)
+ vnode->next = xstrdup (value);
+
+ /*
+ * at this point, we skip any user defined fields XXX - this is where
+ * we put the symbolic link stuff???
+ */
+ /* FIXME: Does not correctly handle errors, e.g. from stdio. */
+ while ((n = getrcskey (fp, &key, &value)) >= 0)
+ {
+ assert (key != NULL);
+
+ if (strcmp (key, RCSDESC) == 0)
+ {
+ n = -1;
+ break;
+ }
+
+ /* 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 (strcmp(key, RCSDEAD) == 0)
+ {
+ vnode->dead = 1;
+ continue;
+ }
+ /* if we have a revision, break and do it */
+ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
+ /* do nothing */ ;
+ if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+ break;
+ }
+
+ /* 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
+ }
+
+ /*
+ * if we left the loop because there were no more keys, we break out
+ * of the revision processing loop
+ */
+ if (n < 0)
+ break;
+ }
+
+ if (pfp == NULL)
+ {
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", rcsfile);
+ }
+ else
+ {
+ *pfp = fp;
+ }
+ rdata->flags &= ~PARTIAL;
+}
+
+/*
+ * freercsnode - free up the info for an RCSNode
+ */
+void
+freercsnode (rnodep)
+ RCSNode **rnodep;
+{
+ if (rnodep == NULL || *rnodep == NULL)
+ return;
+
+ ((*rnodep)->refcount)--;
+ if ((*rnodep)->refcount != 0)
+ {
+ *rnodep = (RCSNode *) NULL;
+ return;
+ }
+ free ((*rnodep)->path);
+ dellist (&(*rnodep)->versions);
+ if ((*rnodep)->symbols != (List *) NULL)
+ dellist (&(*rnodep)->symbols);
+ if ((*rnodep)->symbols_data != (char *) NULL)
+ free ((*rnodep)->symbols_data);
+ if ((*rnodep)->expand != NULL)
+ free ((*rnodep)->expand);
+ if ((*rnodep)->head != (char *) NULL)
+ free ((*rnodep)->head);
+ if ((*rnodep)->branch != (char *) NULL)
+ free ((*rnodep)->branch);
+ free ((char *) *rnodep);
+ *rnodep = (RCSNode *) NULL;
+}
+
+/*
+ * rcsvers_delproc - free up an RCSVers type node
+ */
+static void
+rcsvers_delproc (p)
+ Node *p;
+{
+ RCSVers *rnode;
+
+ rnode = (RCSVers *) p->data;
+
+ if (rnode->branches != (List *) NULL)
+ dellist (&rnode->branches);
+ if (rnode->date != (char *) NULL)
+ free (rnode->date);
+ if (rnode->next != (char *) NULL)
+ free (rnode->next);
+ free ((char *) rnode);
+}
+
+/*
+ * getrcskey - fill in the key and value from the rcs file the algorithm is
+ * as follows
+ *
+ * o skip whitespace o fill in key with everything up to next white
+ * space or semicolon
+ * o if key == "desc" then key and data are NULL and return -1
+ * o if key wasn't terminated by a semicolon, skip white space and fill
+ * in value with everything up to a semicolon
+ * o compress all whitespace down to a single space
+ * o if a word starts with @, do funky rcs processing
+ * o strip whitespace off end of value or set value to NULL if it empty
+ * o return 0 since we found something besides "desc"
+ *
+ * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey
+ * function; the contents are only valid until the next call to getrcskey
+ * or getrcsrev.
+ */
+
+static char *key = NULL;
+static char *value = NULL;
+static size_t keysize = 0;
+static size_t valsize = 0;
+
+#define ALLOCINCR 1024
+
+static int
+getrcskey (fp, keyp, valp)
+ FILE *fp;
+ char **keyp;
+ char **valp;
+{
+ char *cur, *max;
+ int c;
+
+ /* skip leading whitespace */
+ do
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ } while (whitespace (c));
+
+ /* fill in key */
+ cur = key;
+ max = key + keysize;
+ while (!whitespace (c) && c != ';')
+ {
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur++ = c;
+
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ }
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur = '\0';
+
+ /* skip whitespace between key and val */
+ while (whitespace (c))
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ }
+
+ /* if we ended key with a semicolon, there is no value */
+ if (c == ';')
+ {
+ *keyp = key;
+ *valp = (char *) NULL;
+ return (0);
+ }
+
+ /* otherwise, there might be a value, so fill it in */
+ cur = value;
+ max = value + valsize;
+
+ /* process the value */
+ for (;;)
+ {
+ /* handle RCS "strings" */
+ if (c == '@')
+ {
+ for (;;)
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+
+ if (c == '@')
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+
+ if (c != '@')
+ break;
+ }
+
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur++ = c;
+ }
+ }
+
+ /* The syntax for some key-value pairs is different; they
+ don't end with a semicolon. */
+ if (strcmp (key, RCSDESC) == 0
+ || strcmp (key, "text") == 0
+ || strcmp (key, "log") == 0)
+ break;
+
+ /* compress whitespace down to a single space */
+ if (whitespace (c))
+ {
+ do {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ } while (whitespace (c));
+
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur++ = ' ';
+ }
+
+ /* if we got a semi-colon we are done with the entire value */
+ if (c == ';')
+ break;
+
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur++ = c;
+
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ }
+
+ /* terminate the string */
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur = '\0';
+
+ /* if the string is empty, make it null */
+ if (value && *value != '\0')
+ *valp = value;
+ else
+ *valp = NULL;
+ *keyp = key;
+ return (0);
+}
+
+static void getrcsrev PROTO ((FILE *fp, char **revp));
+
+/* Read an RCS revision number from FP. Put a pointer to it in *REVP;
+ it points to space managed by getrcsrev which is only good until
+ the next call to getrcskey or getrcsrev. */
+static void
+getrcsrev (fp, revp)
+ FILE *fp;
+ char **revp;
+{
+ char *cur;
+ char *max;
+ int c;
+
+ do {
+ c = getc (fp);
+ if (c == EOF)
+ /* FIXME: should be including filename in error message. */
+ error (1, errno, "cannot read rcs file");
+ } while (whitespace (c));
+
+ if (!(isdigit (c) || c == '.'))
+ /* FIXME: should be including filename in error message. */
+ error (1, 0, "error reading rcs file; revision number expected");
+
+ cur = key;
+ max = key + keysize;
+ while (isdigit (c) || c == '.')
+ {
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur++ = c;
+
+ c = getc (fp);
+ if (c == EOF)
+ {
+ /* FIXME: should be including filename in error message. */
+ error (1, errno, "cannot read rcs file");
+ }
+ }
+
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur = '\0';
+ *revp = key;
+}
+
+/*
+ * 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 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, return_both)
+ RCSNode *rcs;
+ char *tag;
+ char *date;
+ int force_tag_match;
+ int return_both;
+{
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (tag && date)
+ {
+ char *cp, *rev, *tagrev;
+
+ /*
+ * first lookup the tag; if that works, turn the revision into
+ * a branch and lookup the date.
+ */
+ tagrev = RCS_gettag (rcs, tag, force_tag_match, 0);
+ if (tagrev == NULL)
+ return ((char *) NULL);
+
+ if ((cp = strrchr (tagrev, '.')) != NULL)
+ *cp = '\0';
+ rev = RCS_getdatebranch (rcs, date, tagrev);
+ free (tagrev);
+ return (rev);
+ }
+ else if (tag)
+ return (RCS_gettag (rcs, tag, force_tag_match, return_both));
+ else if (date)
+ return (RCS_getdate (rcs, date, force_tag_match));
+ else
+ return (RCS_head (rcs));
+
+}
+
+/*
+ * Find the revision for a specific tag.
+ * If force_tag_match is set, return NULL if an exact match is not
+ * possible otherwise return RCS_head (). We are careful to look for
+ * and handle "magic" revisions specially.
+ *
+ * If the matched tag is a branch tag, find the head of the branch.
+ */
+char *
+RCS_gettag (rcs, symtag, force_tag_match, return_both)
+ RCSNode *rcs;
+ char *symtag;
+ int force_tag_match;
+ int return_both;
+{
+ Node *p;
+ char *tag = symtag;
+
+ /* 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, NULL);
+
+ /* If tag is "HEAD", special case to get head RCS revision */
+ if (tag && (strcmp (tag, TAG_HEAD) == 0 || *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 (tag[0]))
+ {
+ /* If we got a symbolic tag, resolve it to a numeric */
+ if (rcs == NULL)
+ p = NULL;
+ else {
+ p = findnode (RCS_symbols(rcs), tag);
+ }
+ if (p != NULL)
+ {
+ int dots;
+ char *magic, *branch, *cp;
+
+ tag = p->data;
+
+ /*
+ * If this is a magic revision, we turn it into either its
+ * physical branch equivalent (if one exists) or into
+ * its base revision, which we assume exists.
+ */
+ dots = numdots (tag);
+ if (dots > 2 && (dots & 1) != 0)
+ {
+ branch = 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)
+ {
+ char *xtag;
+
+ /* it's magic. See if the branch exists */
+ *cp = '\0'; /* turn it into a revision */
+ xtag = xstrdup (tag);
+ *cp = '.'; /* and back again */
+ (void) sprintf (magic, "%s.%s", xtag, branch);
+ branch = RCS_getbranch (rcs, magic, 1);
+ free (magic);
+ if (branch != NULL)
+ {
+ free (xtag);
+ return (branch);
+ }
+ return (xtag);
+ }
+ free (magic);
+ }
+ }
+ else
+ {
+ /* The tag wasn't there, so return the head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ }
+
+ /*
+ * numeric tag processing:
+ * 1) revision number - just return it
+ * 2) branch number - find head of branch
+ */
+
+ /* strip trailing dots */
+ while (tag[strlen (tag) - 1] == '.')
+ tag[strlen (tag) - 1] = '\0';
+
+ if ((numdots (tag) & 1) == 0)
+ {
+ /* we have a branch tag, so we need to walk the branch */
+ return (RCS_getbranch (rcs, tag, force_tag_match));
+ }
+ else
+ {
+ /* we have a revision tag, so make sure it exists */
+ if (rcs == NULL)
+ p = NULL;
+ else
+ p = findnode (rcs->versions, tag);
+ if (p != NULL)
+ {
+ /*
+ * we have found a numeric revision for the revision tag.
+ * To support expanding the RCS keyword Name, return both
+ * the numeric tag and the supplied tag (which might be
+ * symbolic). They are separated with a ':' which is not
+ * a valid tag char. The variable return_both is only set
+ * if this function is called through Version_TS ->
+ * RCS_getversion.
+ */
+ if (return_both)
+ {
+ char *both = xmalloc(strlen(tag) + 2 + strlen(symtag));
+ sprintf(both, "%s:%s", tag, symtag);
+ return both;
+ }
+ else
+ return (xstrdup (tag));
+ }
+ else
+ {
+ /* The revision wasn't there, so return the head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ }
+}
+
+/*
+ * Return a "magic" revision as a virtual branch off of REV for the RCS file.
+ * A "magic" revision is one which is unique in the RCS file. By unique, I
+ * mean we return a revision which:
+ * - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH)
+ * - has a revision component which is not an existing branch off REV
+ * - has a revision component which is not an existing magic revision
+ * - is an even-numbered revision, to avoid conflicts with vendor branches
+ * The first point is what makes it "magic".
+ *
+ * As an example, if we pass in 1.37 as REV, we will look for an existing
+ * branch called 1.37.2. If it did not exist, we would look for an
+ * existing symbolic tag with a numeric part equal to 1.37.0.2. If that
+ * didn't exist, then we know that the 1.37.2 branch can be reserved by
+ * creating a symbolic tag with 1.37.0.2 as the numeric part.
+ *
+ * This allows us to fork development with very little overhead -- just a
+ * symbolic tag is used in the RCS file. When a commit is done, a physical
+ * branch is dynamically created to hold the new revision.
+ *
+ * Note: We assume that REV is an RCS revision and not a branch number.
+ */
+static char *check_rev;
+char *
+RCS_magicrev (rcs, rev)
+ RCSNode *rcs;
+ char *rev;
+{
+ int rev_num;
+ char *xrev, *test_branch;
+
+ xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */
+ check_rev = xrev;
+
+ /* only look at even numbered branches */
+ for (rev_num = 2; ; rev_num += 2)
+ {
+ /* see if the physical branch exists */
+ (void) sprintf (xrev, "%s.%d", rev, rev_num);
+ test_branch = RCS_getbranch (rcs, xrev, 1);
+ if (test_branch != NULL) /* it did, so keep looking */
+ {
+ free (test_branch);
+ continue;
+ }
+
+ /* now, create a "magic" revision */
+ (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num);
+
+ /* walk the symbols list to see if a magic one already exists */
+ if (walklist (RCS_symbols(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 (strcmp (check_rev, p->data) == 0)
+ 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 (*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;
+ Node *p;
+
+ /* numeric revisions are easy -- even number of dots is a branch */
+ if (isdigit (*rev))
+ return ((numdots (rev) & 1) == 0);
+
+ p = findnode (RCS_symbols(rcs), rev);
+ if (p == NULL)
+ return (0);
+ dots = numdots (p->data);
+ if ((dots & 1) == 0)
+ return (1);
+
+ /* got a symbolic tag match, but it's not a branch; see if it's magic */
+ if (dots > 2)
+ {
+ char *magic;
+ char *branch = strrchr (p->data, '.');
+ char *cp = branch - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (p->data) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ free (magic);
+ return (1);
+ }
+ free (magic);
+ }
+ return (0);
+}
+
+/*
+ * Returns a pointer to malloc'ed memory which contains the branch
+ * for the specified *symbolic* tag. Magic branches are handled correctly.
+ */
+char *
+RCS_whatbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ Node *p;
+ 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 */
+ p = findnode (RCS_symbols(rcs), rev);
+ if (p == NULL)
+ return ((char *) NULL);
+ dots = numdots (p->data);
+ if ((dots & 1) == 0)
+ return (xstrdup (p->data));
+
+ /* got a symbolic tag match, but it's not a branch; see if it's magic */
+ if (dots > 2)
+ {
+ char *magic;
+ char *branch = strrchr (p->data, '.');
+ char *cp = branch++ - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (p->data) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ /* yep. it's magic. now, construct the real branch */
+ *cp = '\0'; /* turn it into a revision */
+ (void) sprintf (magic, "%s.%s", p->data, branch);
+ *cp = '.'; /* and turn it back */
+ return (magic);
+ }
+ free (magic);
+ }
+ return ((char *) NULL);
+}
+
+/*
+ * Get the head of the specified branch. If the branch does not exist,
+ * return NULL or RCS_head depending on force_tag_match
+ */
+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, 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));
+}
+
+/*
+ * Get the head of the RCS file. If branch is set, this is the head of the
+ * branch, otherwise the real head
+ */
+char *
+RCS_head (rcs)
+ RCSNode *rcs;
+{
+ /* make sure we have something to look at... */
+ 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, NULL);
+
+ /* if the head is on a branch, try the branch first */
+ if (rcs->branch != NULL)
+ retval = RCS_getdatebranch (rcs, date, rcs->branch);
+
+ /* if we found a match, we are done */
+ if (retval != NULL)
+ return (retval);
+
+ /* otherwise if we have a trunk, try it */
+ if (rcs->head)
+ {
+ p = findnode (rcs->versions, rcs->head);
+ while (p != NULL)
+ {
+ /* if the date of this one is before date, take it */
+ vers = (RCSVers *) p->data;
+ if (RCS_datecmp (vers->date, date) <= 0)
+ {
+ cur_rev = vers->version;
+ break;
+ }
+
+ /* if there is a next version, find the node */
+ if (vers->next != NULL)
+ p = findnode (rcs->versions, vers->next);
+ else
+ p = (Node *) NULL;
+ }
+ }
+
+ /*
+ * at this point, either we have the revision we want, or we have the
+ * first revision on the trunk (1.1?) in our hands
+ */
+
+ /* if we found what we're looking for, and it's not 1.1 return it */
+ if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0)
+ return (xstrdup (cur_rev));
+
+ /* look on the vendor branch */
+ retval = RCS_getdatebranch (rcs, date, CVSBRANCH);
+
+ /*
+ * if we found a match, return it; otherwise, we return the first
+ * revision on the trunk or NULL depending on force_tag_match and the
+ * date of the first rev
+ */
+ if (retval != NULL)
+ return (retval);
+
+ if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0)
+ return (xstrdup (vers->version));
+ else
+ return (NULL);
+}
+
+/*
+ * Look up the last element on a branch that was put in before the specified
+ * date (return the rev or NULL)
+ */
+static char *
+RCS_getdatebranch (rcs, date, branch)
+ RCSNode *rcs;
+ char *date;
+ char *branch;
+{
+ char *cur_rev = NULL;
+ char *cp;
+ char *xbranch, *xrev;
+ Node *p;
+ RCSVers *vers;
+
+ /* look up the first revision on the branch */
+ xrev = xstrdup (branch);
+ cp = 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, NULL);
+
+ p = findnode (rcs->versions, xrev);
+ free (xrev);
+ if (p == NULL)
+ return (NULL);
+ vers = (RCSVers *) p->data;
+
+ /* if no branches list, return NULL */
+ if (vers->branches == NULL)
+ return (NULL);
+
+ /* walk the branches list looking for the branch number */
+ xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */
+ (void) strcpy (xbranch, branch);
+ (void) strcat (xbranch, ".");
+ for (p = vers->branches->list->next; p != vers->branches->list; p = p->next)
+ if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
+ break;
+ free (xbranch);
+ if (p == vers->branches->list)
+ return (NULL);
+
+ p = findnode (rcs->versions, p->key);
+
+ /* walk the next pointers until you find the end, or the date is too late */
+ while (p != NULL)
+ {
+ vers = (RCSVers *) p->data;
+ if (RCS_datecmp (vers->date, date) <= 0)
+ cur_rev = vers->version;
+ else
+ break;
+
+ /* if there is a next version, find the node */
+ if (vers->next != NULL)
+ p = findnode (rcs->versions, vers->next);
+ else
+ p = (Node *) NULL;
+ }
+
+ /* if we found something acceptable, return it - otherwise NULL */
+ if (cur_rev != NULL)
+ return (xstrdup (cur_rev));
+ else
+ return (NULL);
+}
+
+/*
+ * Compare two dates in RCS format. Beware the change in format on January 1,
+ * 2000, when years go from 2-digit to full format.
+ */
+int
+RCS_datecmp (date1, date2)
+ char *date1, *date2;
+{
+ int length_diff = strlen (date1) - strlen (date2);
+
+ return (length_diff ? length_diff : strcmp (date1, date2));
+}
+
+/*
+ * Lookup the specified revision in the ,v file and return, in the date
+ * argument, the date specified for the revision *minus one second*, so that
+ * the logically previous revision will be found later.
+ *
+ * Returns zero on failure, RCS revision time as a Unix "time_t" on success.
+ */
+time_t
+RCS_getrevtime (rcs, rev, date, fudge)
+ RCSNode *rcs;
+ char *rev;
+ char *date;
+ int fudge;
+{
+ char tdate[MAXDATELEN];
+ struct tm xtm, *ftm;
+ time_t revdate = 0;
+ Node *p;
+ RCSVers *vers;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, 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 */
+#ifdef HAVE_RCS5
+ (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon,
+ ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+#else
+ (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon,
+ ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+#endif
+
+ /* turn it into seconds since the epoch */
+ revdate = get_date (tdate, (struct timeb *) NULL);
+ if (revdate != (time_t) -1)
+ {
+ revdate -= fudge; /* remove "fudge" seconds */
+ if (date)
+ {
+ /* put an appropriate string into ``date'' if we were given one */
+#ifdef HAVE_RCS5
+ ftm = gmtime (&revdate);
+#else
+ ftm = localtime (&revdate);
+#endif
+ (void) sprintf (date, DATEFORM,
+ ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
+ ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+ }
+ }
+ return (revdate);
+}
+
+List *
+RCS_symbols(rcs)
+ RCSNode *rcs;
+{
+ assert(rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, 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;
+}
+
+/*
+ * 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 kflags[] =
+ {"kv", "kvl", "k", "v", "o", "b", (char *) NULL};
+ 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",
+ NULL,
+ };
+ char karg[10];
+ char const *const *cpp = NULL;
+
+#ifndef HAVE_RCS5
+ error (1, 0, "%s %s: your version of RCS does not support the -k option",
+ program_name, command_name);
+#endif
+
+ if (arg)
+ {
+ for (cpp = kflags; *cpp != NULL; cpp++)
+ {
+ if (strcmp (arg, *cpp) == 0)
+ break;
+ }
+ }
+
+ if (arg == NULL || *cpp == NULL)
+ {
+ 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 (*tag))
+ {
+ for (cp = tag; *cp; cp++)
+ {
+ if (!isgraph (*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);
+}
+
+/*
+ * 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, 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;
+{
+ assert (rcs != NULL);
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+ return rcs->expand;
+}
+
+/* Stuff related to annotate command. This should perhaps be split
+ into the stuff which knows about the guts of RCS files, and the
+ command parsing type stuff. */
+
+/* Linked list of allocated blocks. Seems kind of silly to
+ reinvent the obstack wheel, and this isn't as nice as obstacks
+ in some ways, but obstacks are pretty baroque. */
+struct allocblock
+{
+ char *text;
+ struct allocblock *next;
+};
+struct allocblock *blocks;
+
+static void *block_alloc PROTO ((size_t));
+
+static void *
+block_alloc (n)
+ size_t n;
+{
+ struct allocblock *blk;
+ blk = (struct allocblock *) xmalloc (sizeof (struct allocblock));
+ blk->text = xmalloc (n);
+ blk->next = blocks;
+ blocks = blk;
+ return blk->text;
+}
+
+static void block_free PROTO ((void));
+
+static void
+block_free ()
+{
+ struct allocblock *p;
+ struct allocblock *q;
+
+ p = blocks;
+ while (p != NULL)
+ {
+ free (p->text);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+ blocks = NULL;
+}
+
+struct line
+{
+ /* Text of this line, terminated by \n or \0. */
+ char *text;
+ /* 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;
+};
+
+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 = 10;
+ vec->nlines = 0;
+ vec->vector = (struct line **)
+ xmalloc (vec->lines_alloced * sizeof (*vec->vector));
+}
+
+static void linevector_add PROTO ((struct linevector *vec, char *text,
+ 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. All \n in TEXT are changed to \0. Set the
+ version for each of the new lines to VERS. */
+static void
+linevector_add (vec, text, vers, pos)
+ struct linevector *vec;
+ char *text;
+ RCSVers *vers;
+ unsigned int pos;
+{
+ unsigned int i;
+ unsigned int nnew;
+ char *p;
+ struct line *lines;
+
+ assert (vec->lines_alloced > 0);
+
+ /* Count the number of lines we will need to add. */
+ nnew = 1;
+ for (p = text; *p != '\0'; ++p)
+ if (*p == '\n' && p[1] != '\0')
+ ++nnew;
+ /* Allocate the struct line's. */
+ lines = block_alloc (nnew * sizeof (struct line));
+
+ /* Expand VEC->VECTOR if needed. */
+ if (vec->nlines + nnew >= vec->lines_alloced)
+ {
+ 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)
+ error (1, 0, "invalid rcs file: line to add out of range");
+
+ /* Actually add the lines, to LINES and VEC->VECTOR. */
+ i = pos;
+ lines[0].text = text;
+ lines[0].vers = vers;
+ lines[0].has_newline = 0;
+ vec->vector[i++] = &lines[0];
+ for (p = text; *p != '\0'; ++p)
+ if (*p == '\n')
+ {
+ *p = '\0';
+ lines[i - pos - 1].has_newline = 1;
+ if (p[1] == '\0')
+ /* If there are no characters beyond the last newline, we
+ don't consider it another line. */
+ break;
+ lines[i - pos].text = p + 1;
+ lines[i - pos].vers = vers;
+ lines[i - pos].has_newline = 0;
+ vec->vector[i] = &lines[i - pos];
+ ++i;
+ }
+ vec->nlines += nnew;
+}
+
+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 < 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;
+{
+ if (from->nlines > to->lines_alloced)
+ {
+ 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;
+}
+
+static void linevector_free PROTO ((struct linevector *));
+
+/* Free storage associated with linevector (that is, the vector but
+ not the lines pointed to). */
+static void
+linevector_free (vec)
+ struct linevector *vec;
+{
+ 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 annotate_fileproc PROTO ((struct file_info *));
+
+static int
+annotate_fileproc (finfo)
+ struct file_info *finfo;
+{
+ FILE *fp;
+ char *key;
+ char *value;
+ RCSVers *vers;
+ RCSVers *prev_vers;
+ int n;
+ int ishead;
+ Node *node;
+ struct linevector headlines;
+ struct linevector curlines;
+
+ if (finfo->rcs == NULL)
+ return (1);
+
+ /* 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);
+
+ if (!(finfo->rcs->flags & PARTIAL))
+ /* We are leaking memory by calling RCS_reparsefile again. */
+ error (0, 0, "internal warning: non-partial rcs in annotate_fileproc");
+ RCS_reparsercsfile (finfo->rcs, &fp);
+
+ ishead = 1;
+ vers = NULL;
+
+ do {
+ getrcsrev (fp, &key);
+
+ /* Stash the previous version. */
+ prev_vers = vers;
+
+ /* look up the revision */
+ node = findnode (finfo->rcs->versions, key);
+ if (node == NULL)
+ error (1, 0, "mismatch in rcs file %s between deltas and deltatexts",
+ finfo->rcs->path);
+ vers = (RCSVers *) node->data;
+
+ while ((n = getrcskey (fp, &key, &value)) >= 0)
+ {
+ if (strcmp (key, "text") == 0)
+ {
+ if (ishead)
+ {
+ char *p;
+
+ p = block_alloc (strlen (value) + 1);
+ strcpy (p, value);
+
+ linevector_init (&headlines);
+ linevector_init (&curlines);
+ linevector_add (&headlines, p, NULL, 0);
+ linevector_copy (&curlines, &headlines);
+ ishead = 0;
+ }
+ else
+ {
+ char *p;
+ 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 {ADD, DELETE} type;
+ unsigned long pos;
+ unsigned long nlines;
+ char *new_lines;
+ struct deltafrag *next;
+ };
+ struct deltafrag *dfhead;
+ struct deltafrag *df;
+
+ dfhead = NULL;
+ for (p = value; p != NULL && *p != '\0'; )
+ {
+ 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 '%c' in %s",
+ op, finfo->rcs->path);
+ df = (struct deltafrag *)
+ xmalloc (sizeof (struct deltafrag));
+ df->next = dfhead;
+ dfhead = df;
+ df->pos = strtoul (p, &q, 10);
+
+ if (p == q)
+ error (1, 0, "number expected in %s",
+ finfo->rcs->path);
+ p = q;
+ if (*p++ != ' ')
+ error (1, 0, "space expected in %s",
+ finfo->rcs->path);
+ df->nlines = strtoul (p, &q, 10);
+ if (p == q)
+ error (1, 0, "number expected in %s",
+ finfo->rcs->path);
+ p = q;
+ if (*p++ != '\012')
+ error (1, 0, "linefeed expected in %s",
+ finfo->rcs->path);
+
+ if (op == 'a')
+ {
+ unsigned int i;
+
+ df->type = 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 == '\0')
+ {
+ if (i != 1)
+ error (1, 0, "\
+invalid rcs file %s: premature end of value",
+ finfo->rcs->path);
+ else
+ break;
+ }
+
+ /* Copy the text we are adding into allocated
+ space. */
+ df->new_lines = block_alloc (q - p + 1);
+ strncpy (df->new_lines, p, q - p);
+ df->new_lines[q - p] = '\0';
+
+ p = q;
+ }
+ else
+ {
+ /* Correct for the fact that line numbers in RCS
+ files start with 1. */
+ --df->pos;
+
+ assert (op == 'd');
+ df->type = DELETE;
+ }
+ }
+ for (df = dfhead; df != NULL;)
+ {
+ unsigned int ln;
+
+ switch (df->type)
+ {
+ case ADD:
+ linevector_add (&curlines, df->new_lines,
+ NULL, df->pos);
+ break;
+ case DELETE:
+ if (df->pos > curlines.nlines
+ || df->pos + df->nlines > curlines.nlines)
+ error (1, 0, "\
+invalid rcs file %s (`d' operand out of range)",
+ finfo->rcs->path);
+ for (ln = df->pos; ln < df->pos + df->nlines; ++ln)
+ curlines.vector[ln]->vers = prev_vers;
+ linevector_delete (&curlines, df->pos, df->nlines);
+ break;
+ }
+ df = df->next;
+ free (dfhead);
+ dfhead = df;
+ }
+ }
+ break;
+ }
+ }
+ if (n < 0)
+ goto l_error;
+ } while (vers->next != NULL);
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", finfo->rcs->path);
+
+ /* Now print out the data we have just computed. */
+ {
+ 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)
+ 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);
+ cvs_output (headlines.vector[ln]->text, 0);
+ cvs_output ("\n", 1);
+ }
+ }
+
+ if (!ishead)
+ {
+ linevector_free (&curlines);
+ linevector_free (&headlines);
+ }
+ block_free ();
+ return 0;
+
+ l_error:
+ if (ferror (fp))
+ error (1, errno, "cannot read %s", finfo->rcs->path);
+ else
+ error (1, 0, "%s does not appear to be a valid rcs file",
+ finfo->rcs->path);
+ /* Shut up gcc -Wall. */
+ return 0;
+}
+
+static const char *const annotate_usage[] =
+{
+ "Usage: %s %s [-l] [files...]\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ NULL
+};
+
+/* Command to show the revision, date, and author where each line of a
+ file was modified. Currently it will only show the trunk, all the
+ way to the head, but it would be useful to enhance it to (a) allow
+ one to specify a revision, and display only as far as that (easy;
+ just have annotate_fileproc set all the ->vers fields to NULL when
+ you hit that revision), and (b) handle branches (not as easy, but
+ doable). The user interface for both (a) and (b) could be a -r
+ option. */
+
+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, "+l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ 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");
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ send_to_server ("annotate\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ return start_recursion (annotate_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 1, 0);
+}
diff --git a/contrib/cvs/src/rcs.h b/contrib/cvs/src/rcs.h
new file mode 100644
index 0000000..698a3b1
--- /dev/null
+++ b/contrib/cvs/src/rcs.h
@@ -0,0 +1,104 @@
+/* $CVSid: @(#)rcs.h 1.18 94/09/23 $ */
+
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * RCS source control definitions needed by rcs.c and friends
+ */
+
+#define RCS "rcs"
+#define RCS_CI "ci"
+#define RCS_CO "co"
+#define RCS_RLOG "rlog"
+#define RCS_DIFF "rcsdiff"
+#define RCS_RCSMERGE "rcsmerge"
+#define RCS_MERGE_PAT "^>>>>>>> " /* runs "grep" with this pattern */
+#define RCSEXT ",v"
+#define RCSPAT "*,v"
+#define RCSHEAD "head"
+#define RCSBRANCH "branch"
+#define RCSSYMBOLS "symbols"
+#define RCSDATE "date"
+#define RCSDESC "desc"
+#define RCSEXPAND "expand"
+
+/* Used by the version of death support which resulted from old
+ versions of CVS (e.g. 1.5 if you define DEATH_SUPPORT and not
+ DEATH_STATE). Only a hacked up RCS (used by those old versions of
+ CVS) will put this into RCS files. Considered obsolete. */
+#define RCSDEAD "dead"
+
+#define DATEFORM "%02d.%02d.%02d.%02d.%02d.%02d"
+#define SDATEFORM "%d.%d.%d.%d.%d.%d"
+
+/*
+ * Opaque structure definitions used by RCS specific lookup routines
+ */
+#define VALID 0x1 /* flags field contains valid data */
+#define INATTIC 0x2 /* RCS file is located in the Attic */
+#define PARTIAL 0x4 /* RCS file not completly parsed */
+
+struct rcsnode
+{
+ int refcount;
+ int flags;
+ char *path;
+ char *head;
+ char *branch;
+ char *symbols_data;
+ char *expand;
+ List *symbols;
+ List *versions;
+};
+
+typedef struct rcsnode RCSNode;
+
+struct rcsversnode
+{
+ char *version;
+ char *date;
+ char *author;
+ char *next;
+ int dead;
+ List *branches;
+};
+typedef struct rcsversnode RCSVers;
+
+/*
+ * CVS reserves all even-numbered branches for its own use. "magic" branches
+ * (see rcs.c) are contained as virtual revision numbers (within symbolic
+ * tags only) off the RCS_MAGIC_BRANCH, which is 0. CVS also reserves the
+ * ".1" branch for vendor revisions. So, if you do your own branching, you
+ * should limit your use to odd branch numbers starting at 3.
+ */
+#define RCS_MAGIC_BRANCH 0
+
+/*
+ * exported interfaces
+ */
+RCSNode *RCS_parse PROTO((const char *file, const char *repos));
+RCSNode *RCS_parsercsfile PROTO((char *rcsfile));
+char *RCS_check_kflag PROTO((const char *arg));
+char *RCS_getdate PROTO((RCSNode * rcs, char *date, int force_tag_match));
+char *RCS_gettag PROTO((RCSNode * rcs, char *symtag, int force_tag_match,
+ int return_both));
+char *RCS_getversion PROTO((RCSNode * rcs, char *tag, char *date,
+ int force_tag_match, int return_both));
+char *RCS_magicrev PROTO((RCSNode *rcs, char *rev));
+int RCS_isbranch PROTO((RCSNode *rcs, const char *rev));
+int RCS_nodeisbranch PROTO((RCSNode *rcs, const char *tag));
+char *RCS_whatbranch PROTO((RCSNode *rcs, const char *tag));
+char *RCS_head PROTO((RCSNode * rcs));
+int RCS_datecmp PROTO((char *date1, char *date2));
+time_t RCS_getrevtime PROTO((RCSNode * rcs, char *rev, char *date, int fudge));
+List *RCS_symbols PROTO((RCSNode *rcs));
+void RCS_check_tag PROTO((const char *tag));
+void freercsnode PROTO((RCSNode ** rnodep));
+char *RCS_getbranch PROTO((RCSNode * rcs, char *tag, int force_tag_match));
+
+int RCS_isdead PROTO((RCSNode *, const char *));
+char *RCS_getexpand PROTO ((RCSNode *));
diff --git a/contrib/cvs/src/rcscmds.c b/contrib/cvs/src/rcscmds.c
new file mode 100644
index 0000000..66aea57
--- /dev/null
+++ b/contrib/cvs/src/rcscmds.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * The functions in this file provide an interface for performing
+ * operations directly on RCS files.
+ */
+
+#include "cvs.h"
+#include <assert.h>
+
+/* For RCS file PATH, 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(path, tag, rev)
+ const char *path;
+ const char *tag;
+ const char *rev;
+{
+ if (strcmp (tag, TAG_BASE) == 0
+ || strcmp (tag, TAG_HEAD) == 0)
+ {
+ /* 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;
+ }
+
+ run_setup ("%s%s -x,v/ -q -N%s:%s", Rcsbin, RCS, tag, rev);
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+}
+
+/* NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. */
+int
+RCS_deltag(path, tag, noerr)
+ const char *path;
+ const char *tag;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q -N%s", Rcsbin, RCS, tag);
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
+
+/* set RCS branch to REV */
+int
+RCS_setbranch(path, rev)
+ const char *path;
+ const char *rev;
+{
+ run_setup ("%s%s -x,v/ -q -b%s", Rcsbin, RCS, rev ? rev : "");
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+}
+
+/* Lock revision REV. NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. */
+int
+RCS_lock(path, rev, noerr)
+ const char *path;
+ const char *rev;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q -l%s", Rcsbin, RCS, rev ? rev : "");
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
+
+/* Unlock revision REV. NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. */
+int
+RCS_unlock(path, rev, noerr)
+ const char *path;
+ const char *rev;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q -u%s", Rcsbin, RCS, rev ? rev : "");
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
+
+/* Merge revisions REV1 and REV2. */
+int
+RCS_merge(path, options, rev1, rev2)
+ const char *path;
+ const char *options;
+ const char *rev1;
+ const char *rev2;
+{
+ int status;
+
+ /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */
+
+ run_setup ("%s%s -x,v/ %s -r%s -r%s %s", Rcsbin, RCS_RCSMERGE,
+ options, rev1, rev2, path);
+ status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+#ifndef HAVE_RCS5
+ if (status == 0)
+ {
+ /* Run GREP to see if there appear to be conflicts in the file */
+ run_setup ("%s", GREP);
+ run_arg (RCS_MERGE_PAT);
+ run_arg (path);
+ status = (run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_NORMAL) == 0);
+
+ }
+#endif
+ return status;
+}
+
+/* Check out a revision from RCSFILE into WORKFILE, or to standard output
+ if WORKFILE is NULL. If WORKFILE is "", let RCS pick the working file
+ name. TAG is the tag to check out, or NULL if one should check out
+ the head of the default branch. OPTIONS is a string such as
+ -kb or -kkv, for keyword expansion options, or NULL if there are none.
+ If WORKFILE is NULL, run regardless of noexec; if non-NULL, noexec
+ inhibits execution. SOUT is what to do with standard output
+ (typically RUN_TTY). If FLAGS & RCS_FLAGS_LOCK, lock it. If
+ FLAGS & RCS_FLAGS_FORCE, check out even on top of an existing file.
+ If NOERR is nonzero, suppress errors. */
+int
+RCS_checkout (rcsfile, workfile, tag, options, sout, flags, noerr)
+ char *rcsfile;
+ char *workfile;
+ char *tag;
+ char *options;
+ char *sout;
+ int flags;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q %s%s", Rcsbin, RCS_CO,
+ tag ? "-r" : "", tag ? tag : "");
+ if (options != NULL && options[0] != '\0')
+ run_arg (options);
+ if (workfile == NULL)
+ run_arg ("-p");
+ if (flags & RCS_FLAGS_LOCK)
+ run_arg ("-l");
+ if (flags & RCS_FLAGS_FORCE)
+ run_arg ("-f");
+ run_arg (rcsfile);
+ if (workfile != NULL && workfile[0] != '\0')
+ run_arg (workfile);
+ return run_exec (RUN_TTY, sout, noerr ? DEVNULL : RUN_TTY,
+ workfile == NULL ? (RUN_NORMAL | RUN_REALLY) : RUN_NORMAL);
+}
+
+/* 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 NOERR, do not
+ report errors. If FLAGS & RCS_FLAGS_QUIET suppress errors somewhat more
+ selectively. 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. */
+int
+RCS_checkin (rcsfile, workfile, message, rev, flags, noerr)
+ char *rcsfile;
+ char *workfile;
+ char *message;
+ char *rev;
+ int flags;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -f %s%s", Rcsbin, RCS_CI,
+ rev ? "-r" : "", rev ? rev : "");
+ if (flags & RCS_FLAGS_DEAD)
+ run_arg ("-sdead");
+ if (flags & RCS_FLAGS_QUIET)
+ run_arg ("-q");
+ if (flags & RCS_FLAGS_MODTIME)
+ run_arg ("-d");
+ run_args ("-m%s", make_message_rcslegal (message));
+ if (workfile != NULL)
+ run_arg (workfile);
+ run_arg (rcsfile);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
diff --git a/contrib/cvs/src/recurse.c b/contrib/cvs/src/recurse.c
new file mode 100644
index 0000000..ec51a98
--- /dev/null
+++ b/contrib/cvs/src/recurse.c
@@ -0,0 +1,714 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * 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));
+
+
+/*
+ * Local static versions eliminates the need for globals
+ */
+static FILEPROC fileproc;
+static FILESDONEPROC filesdoneproc;
+static DIRENTPROC direntproc;
+static DIRLEAVEPROC dirleaveproc;
+static int which;
+static Dtype flags;
+static int aflag;
+static int readlock;
+static int dosrcs;
+static char update_dir[PATH_MAX];
+static char *repository = NULL;
+static List *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;
+ Dtype flags;
+ int which;
+ int aflag;
+ int readlock;
+ int dosrcs;
+};
+
+/*
+ * Called to start a recursive command.
+ *
+ * Command line arguments dictate the directories and files on which
+ * we operate. In the special case of no arguments, we default to
+ * ".".
+ *
+ * The general algorithm is as follows.
+ */
+int
+start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc,
+ argc, argv, local, which, aflag, readlock,
+ update_preload, dosrcs, wd_is_repos)
+ FILEPROC fileproc;
+ FILESDONEPROC filesdoneproc;
+ DIRENTPROC direntproc;
+ DIRLEAVEPROC dirleaveproc;
+ int argc;
+ char **argv;
+ int local;
+ int which;
+ int aflag;
+ int readlock;
+ char *update_preload;
+ int dosrcs;
+ int wd_is_repos; /* Set if caller has already cd'd to the repository */
+{
+ int i, err = 0;
+ Dtype flags;
+ List *files_by_dir = NULL;
+ struct recursion_frame frame;
+
+ expand_wild (argc, argv, &argc, &argv);
+
+ if (update_preload == NULL)
+ update_dir[0] = '\0';
+ else
+ (void) strcpy (update_dir, update_preload);
+
+ if (local)
+ flags = R_SKIP_DIRS;
+ else
+ flags = R_PROCESS;
+
+ /* clean up from any previous calls to start_recursion */
+ if (repository)
+ {
+ free (repository);
+ repository = (char *) NULL;
+ }
+ if (filelist)
+ dellist (&filelist); /* FIXME-krp: no longer correct. */
+/* FIXME-krp: clean up files_by_dir */
+ if (dirlist)
+ dellist (&dirlist);
+
+ if (argc == 0)
+ {
+
+ /*
+ * There were no arguments, so we'll probably just recurse. The
+ * exception to the rule is when we are called from a directory
+ * without any CVS administration files. That has always meant to
+ * process each of the sub-directories, so we pretend like we were
+ * called with the list of sub-dirs of the current dir as args
+ */
+ if ((which & W_LOCAL) && !isdir (CVSADM))
+ dirlist = Find_Directories ((char *) NULL, W_LOCAL);
+ else
+ addlist (&dirlist, ".");
+
+ err += do_recursion (fileproc, filesdoneproc, direntproc,
+ dirleaveproc, flags, which, aflag,
+ readlock, dosrcs);
+ return(err);
+ }
+
+
+ /*
+ * 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 tmp[PATH_MAX];
+ 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 (wd_is_repos)
+ {
+ /* If doing rtag, we've done a chdir to the repository. */
+ sprintf (tmp, "%s%s", argv[i], RCSEXT);
+ file_to_try = tmp;
+ }
+ else
+ file_to_try = argv[i];
+
+ if(isfile(file_to_try))
+ addfile (&files_by_dir, dir, comp);
+ else if (isdir (dir))
+ {
+ if (isdir (CVSADM))
+ {
+ /* otherwise, look for it in the repository. */
+ char *save_update_dir;
+ char *repos;
+
+ /* save & set (aka push) update_dir */
+ save_update_dir = xstrdup (update_dir);
+
+ if (*update_dir != '\0')
+ (void) strcat (update_dir, "/");
+
+ (void) strcat (update_dir, dir);
+
+ /* look for it in the repository. */
+ repos = Name_Repository (dir, update_dir);
+ (void) sprintf (tmp, "%s/%s", repos, comp);
+ free (repos);
+
+ if (!wrap_name_has (comp, WRAP_TOCVS) && isdir(tmp))
+ addlist (&dirlist, argv[i]);
+ else
+ addfile (&files_by_dir, dir, comp);
+
+ (void) sprintf (update_dir, "%s", save_update_dir);
+ free (save_update_dir);
+ }
+ else
+ addfile (&files_by_dir, dir, comp);
+ }
+ else
+ error (1, 0, "no such directory `%s'", dir);
+
+ 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. */
+
+ frame.fileproc = fileproc;
+ frame.filesdoneproc = filesdoneproc;
+ frame.direntproc = direntproc;
+ frame.dirleaveproc = dirleaveproc;
+ frame.flags = flags;
+ frame.which = which;
+ frame.aflag = aflag;
+ frame.readlock = readlock;
+ frame.dosrcs = dosrcs;
+ err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
+
+ /* then do_recursion on the dirlist. */
+ if (dirlist != NULL)
+ err += do_recursion (frame.fileproc, frame.filesdoneproc,
+ frame.direntproc, frame.dirleaveproc,
+ frame.flags, frame.which, frame.aflag,
+ frame.readlock, frame.dosrcs);
+
+ /* Free the data which expand_wild allocated. */
+ for (i = 0; i < argc; ++i)
+ free (argv[i]);
+ free (argv);
+
+ return (err);
+}
+
+/*
+ * Implement the recursive policies on the local directory. This may be
+ * called directly, or may be called by start_recursion
+ */
+int
+do_recursion (xfileproc, xfilesdoneproc, xdirentproc, xdirleaveproc,
+ xflags, xwhich, xaflag, xreadlock, xdosrcs)
+ FILEPROC xfileproc;
+ FILESDONEPROC xfilesdoneproc;
+ DIRENTPROC xdirentproc;
+ DIRLEAVEPROC xdirleaveproc;
+ Dtype xflags;
+ int xwhich;
+ int xaflag;
+ int xreadlock;
+ int xdosrcs;
+{
+ int err = 0;
+ int dodoneproc = 1;
+ char *srepository;
+ List *entries = NULL;
+
+ /* do nothing if told */
+ if (xflags == R_SKIP_ALL)
+ return (0);
+
+ /* set up the static vars */
+ fileproc = xfileproc;
+ filesdoneproc = xfilesdoneproc;
+ direntproc = xdirentproc;
+ dirleaveproc = xdirleaveproc;
+ flags = xflags;
+ which = xwhich;
+ aflag = xaflag;
+ readlock = noexec ? 0 : xreadlock;
+ dosrcs = xdosrcs;
+
+ /* 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. */
+ && (readlock || noexec))
+ server_pause_check();
+#endif
+
+ /*
+ * Fill in repository with the current repository
+ */
+ if (which & W_LOCAL)
+ {
+ if (isdir (CVSADM))
+ repository = Name_Repository ((char *) NULL, update_dir);
+ else
+ repository = NULL;
+ }
+ else
+ {
+ repository = xmalloc (PATH_MAX);
+ (void) getwd (repository);
+ }
+ 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 (fileproc != NULL && flags != R_SKIP_FILES)
+ {
+ int lwhich = which;
+
+ /* be sure to look in the attic if we have sticky tags/date */
+ if ((lwhich & W_ATTIC) == 0)
+ if (isreadable (CVSADM_TAG))
+ lwhich |= W_ATTIC;
+
+ /* find the files and fill in entries if appropriate */
+ filelist = Find_Names (repository, lwhich, aflag, &entries);
+ }
+
+ /* find sub-directories if we will recurse */
+ if (flags != R_SKIP_DIRS)
+ dirlist = Find_Directories (repository, which);
+ }
+ else
+ {
+ /* something was passed on the command line */
+ if (filelist != NULL && fileproc != NULL)
+ {
+ /* we will process files, so pre-parse entries */
+ if (which & W_LOCAL)
+ entries = Entries_Open (aflag);
+ }
+ }
+
+ /* process the files (if any) */
+ if (filelist != NULL && fileproc)
+ {
+ struct file_info finfo_struct;
+
+ /* read lock it if necessary */
+ if (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. */
+
+ /* process the files */
+ err += walklist (filelist, do_file_proc, &finfo_struct);
+
+ /* unlock it */
+ if (readlock)
+ Lock_Cleanup ();
+
+ /* clean up */
+ dellist (&filelist);
+ }
+
+ if (entries)
+ {
+ Entries_Close (entries);
+ entries = NULL;
+ }
+
+ /* call-back files done proc (if any) */
+ if (dodoneproc && filesdoneproc != NULL)
+ err = filesdoneproc (err, repository, update_dir[0] ? update_dir : ".");
+
+ fileattr_write ();
+ fileattr_free ();
+
+ /* process the directories (if necessary) */
+ if (dirlist != NULL)
+ err += walklist (dirlist, do_dir_proc, NULL);
+#ifdef notdef
+ else if (dirleaveproc != NULL)
+ err += dirleaveproc(".", err, ".");
+#endif
+ dellist (&dirlist);
+
+ /* 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 file_info *finfo = (struct file_info *)closure;
+ 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 (dosrcs && repository)
+ finfo->rcs = RCS_parse (finfo->file, repository);
+ else
+ finfo->rcs = (RCSNode *) NULL;
+ ret = fileproc (finfo);
+
+ freercsnode(&finfo->rcs);
+ free (finfo->fullname);
+
+ 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;
+{
+ char *dir = p->key;
+ char newrepos[PATH_MAX];
+ List *sdirlist;
+ char *srepository;
+ char *cp;
+ Dtype dir_return = R_PROCESS;
+ int stripped_dot = 0;
+ int err = 0;
+ struct saved_cwd cwd;
+
+ /* set up update_dir - skip dots if not at start */
+ if (strcmp (dir, ".") != 0)
+ {
+ if (update_dir[0] != '\0')
+ {
+ (void) strcat (update_dir, "/");
+ (void) strcat (update_dir, dir);
+ }
+ else
+ (void) strcpy (update_dir, dir);
+
+ /*
+ * Here we need a plausible repository name for the sub-directory. We
+ * create one by concatenating the new directory name onto the
+ * previous repository name. The only case where the name should be
+ * used is in the case where we are creating a new sub-directory for
+ * update -d and in that case the generated name will be correct.
+ */
+ if (repository == NULL)
+ newrepos[0] = '\0';
+ else
+ (void) sprintf (newrepos, "%s/%s", repository, dir);
+ }
+ else
+ {
+ if (update_dir[0] == '\0')
+ (void) strcpy (update_dir, dir);
+
+ if (repository == NULL)
+ newrepos[0] = '\0';
+ else
+ (void) strcpy (newrepos, repository);
+ }
+
+ /* call-back dir entry proc (if any) */
+ if (direntproc != NULL)
+ dir_return = direntproc (dir, newrepos, update_dir);
+
+ /* only process the dir if the return code was 0 */
+ if (dir_return != R_SKIP_ALL)
+ {
+ /* save our current directory and static vars */
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+ sdirlist = dirlist;
+ srepository = repository;
+ dirlist = NULL;
+
+ /* cd to the sub-directory */
+ if (chdir (dir) < 0)
+ error (1, errno, "could not chdir to %s", dir);
+
+ /* honor the global SKIP_DIRS (a.k.a. local) */
+ if (flags == R_SKIP_DIRS)
+ dir_return = R_SKIP_DIRS;
+
+ /* remember if the `.' will be stripped for subsequent dirs */
+ if (strcmp (update_dir, ".") == 0)
+ {
+ update_dir[0] = '\0';
+ stripped_dot = 1;
+ }
+
+ /* make the recursive call */
+ err += do_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc,
+ dir_return, which, aflag, readlock, dosrcs);
+
+ /* put the `.' back if necessary */
+ if (stripped_dot)
+ (void) strcpy (update_dir, ".");
+
+ /* call-back dir leave proc (if any) */
+ if (dirleaveproc != NULL)
+ err = dirleaveproc (dir, err, update_dir);
+
+ /* get back to where we started and restore state vars */
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ dirlist = sdirlist;
+ repository = srepository;
+ }
+
+ /* put back update_dir */
+ cp = last_component (update_dir);
+ if (cp > update_dir)
+ cp[-1] = '\0';
+ else
+ update_dir[0] = '\0';
+
+ return (err);
+}
+
+/*
+ * Add a node to a list allocating the list if necessary.
+ */
+static void
+addlist (listp, key)
+ List **listp;
+ char *key;
+{
+ Node *p;
+
+ if (*listp == NULL)
+ *listp = getlist ();
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (key);
+ 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;
+ save_dirlist = dirlist;
+ dirlist = NULL;
+
+ if (strcmp(p->key, ".") != 0)
+ {
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+ if (chdir (p->key) < 0)
+ error (1, errno, "could not chdir to %s", p->key);
+
+ save_update_dir = xstrdup (update_dir);
+
+ if (*update_dir != '\0')
+ (void) strcat (update_dir, "/");
+
+ (void) strcat (update_dir, p->key);
+ }
+
+ err += do_recursion (frame->fileproc, frame->filesdoneproc,
+ frame->direntproc, frame->dirleaveproc,
+ frame->flags, frame->which, frame->aflag,
+ frame->readlock, frame->dosrcs);
+
+ if (save_update_dir != NULL)
+ {
+ (void) strcpy (update_dir, save_update_dir);
+ free (save_update_dir);
+
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ }
+
+ dirlist = save_dirlist;
+ filelist = NULL;
+ return(err);
+}
diff --git a/contrib/cvs/src/release.c b/contrib/cvs/src/release.c
new file mode 100644
index 0000000..b3ebb2b
--- /dev/null
+++ b/contrib/cvs/src/release.c
@@ -0,0 +1,286 @@
+/*
+ * Release: "cancel" a checkout in the history log.
+ *
+ * - Don't allow release if anything is active - Don't allow release if not
+ * above or inside repository. - Don't allow release if ./CVS/Repository is
+ * not the same as the directory specified in the module database.
+ *
+ * - Enter a line in the history log indicating the "release". - If asked to,
+ * delete the local working directory.
+ */
+
+#include "cvs.h"
+
+static void release_delete PROTO((char *dir));
+
+static const char *const release_usage[] =
+{
+ "Usage: %s %s [-d] modules...\n",
+ "\t-d\tDelete the given directory.\n",
+ NULL
+};
+
+static short delete_flag;
+
+/* FIXME: This implementation is cheezy in quite a few ways:
+
+ 1. The whole "cvs update" junk could be checked locally with a
+ fairly simple start_recursion/classify_file loop--a win for
+ portability, performance, and cleanliness.
+
+ 2. Should be like edit/unedit in terms of working well if disconnected
+ from the network, and then sending a delayed notification.
+
+ 3. Way too many network turnarounds. More than one for each argument.
+ Puh-leeze.
+
+ 4. Oh, and as a purely stylistic nit, break this out into separate
+ functions for client/local and for server. Those #ifdefs are a mess. */
+
+int
+release (argc, argv)
+ int argc;
+ char **argv;
+{
+ FILE *fp;
+ register int i, c;
+ char *repository, *srepos;
+ char line[PATH_MAX], update_cmd[PATH_MAX];
+ char *thisarg;
+ int arg_start_idx;
+ int err = 0;
+
+#ifdef SERVER_SUPPORT
+ if (!server_active)
+ {
+#endif /* SERVER_SUPPORT */
+ if (argc == -1)
+ usage (release_usage);
+ optind = 1;
+ while ((c = getopt (argc, argv, "Qdq")) != -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':
+ delete_flag++;
+ break;
+ case '?':
+ default:
+ usage (release_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+#ifdef SERVER_SUPPORT
+ }
+#endif /* SERVER_SUPPORT */
+
+ /* 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.
+ */
+ /* Construct the update command. */
+ sprintf (update_cmd, "%s -n -q -d %s update",
+ program_path, CVSroot);
+
+#ifdef CLIENT_SUPPORT
+ /* Start the server; we'll close it after looping. */
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ /* If !server_active, we already skipped over argv[0] in the "argc
+ -= optind;" statement above. But if server_active, we need to
+ skip it now. */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ arg_start_idx = 1;
+ else
+#endif /* SERVER_SUPPORT */
+ arg_start_idx = 0;
+
+ for (i = arg_start_idx; i < argc; i++)
+ {
+ thisarg = argv[i];
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* Just log the release -- all the interesting stuff happened
+ * on the client.
+ */
+ history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
+ }
+ else
+ {
+#endif /* SERVER_SUPPORT */
+
+ /*
+ * If we are in a repository, do it. Else if we are in the parent of
+ * a directory with the same name as the module, "cd" into it and
+ * look for a repository there.
+ */
+ if (isdir (thisarg))
+ {
+ if (chdir (thisarg) < 0)
+ {
+ if (!really_quiet)
+ error (0, 0, "can't chdir to: %s", thisarg);
+ continue;
+ }
+ if (!isdir (CVSADM))
+ {
+ if (!really_quiet)
+ error (0, 0, "no repository module: %s", thisarg);
+ continue;
+ }
+ }
+ else
+ {
+ if (!really_quiet)
+ error (0, 0, "no such directory: %s", thisarg);
+ continue;
+ }
+
+ repository = Name_Repository ((char *) NULL, (char *) NULL);
+ srepos = Short_Repository (repository);
+
+ if (!really_quiet)
+ {
+ /* 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");
+ c = 0;
+
+ while (fgets (line, sizeof (line), fp))
+ {
+ if (strchr ("MARCZ", *line))
+ c++;
+ (void) printf (line);
+ }
+
+ /* 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);
+ continue;
+ }
+
+ (void) printf ("You have [%d] altered files in this repository.\n",
+ c);
+ (void) printf ("Are you sure you want to release %smodule `%s': ",
+ delete_flag ? "(and delete) " : "", thisarg);
+ c = !yesno ();
+ if (c) /* "No" */
+ {
+ (void) fprintf (stderr, "** `%s' aborted by user choice.\n",
+ command_name);
+ free (repository);
+ continue;
+ }
+ }
+
+ if (1
+#ifdef SERVER_SUPPORT
+ && !server_active
+#endif
+#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 */
+#ifdef CLIENT_SUPPORT
+ } /* else client not active */
+#endif /* CLIENT_SUPPORT */
+
+ free (repository);
+ if (delete_flag) release_delete (thisarg);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ return get_responses_and_close ();
+ else
+#endif /* CLIENT_SUPPORT */
+ return (0);
+
+#ifdef SERVER_SUPPORT
+ } /* else server not active */
+#endif /* SERVER_SUPPORT */
+ } /* `for' loop */
+ return err;
+}
+
+
+/* We want to "rm -r" the working directory, but let us be a little
+ paranoid. */
+static void
+release_delete (dir)
+ char *dir;
+{
+ struct stat st;
+ ino_t ino;
+
+ (void) stat (".", &st);
+ ino = st.st_ino;
+ (void) chdir ("..");
+ (void) stat (dir, &st);
+ if (ino != st.st_ino)
+ {
+ error (0, 0,
+ "Parent dir on a different disk, delete of %s aborted", dir);
+ return;
+ }
+ /*
+ * XXX - 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 (dir) < 0)
+ error (0, errno, "deletion of directory %s failed", dir);
+}
diff --git a/contrib/cvs/src/remove.c b/contrib/cvs/src/remove.c
new file mode 100644
index 0000000..2911bf4
--- /dev/null
+++ b/contrib/cvs/src/remove.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Remove a File
+ *
+ * Removes entries from the present version. The entries will be removed from
+ * the RCS repository upon the next "commit".
+ *
+ * "remove" accepts no options, only file names that are to be removed. The
+ * file must not exist in the current directory for "remove" to work
+ * correctly.
+ */
+
+#include "cvs.h"
+
+static int remove_fileproc PROTO((struct file_info *finfo));
+static Dtype remove_dirproc PROTO((char *dir, char *repos, char *update_dir));
+
+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",
+ NULL
+};
+
+int
+cvsremove (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c, err;
+
+ if (argc == -1)
+ usage (remove_usage);
+
+ optind = 1;
+ 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) {
+ start_server ();
+ ign_setup ();
+ if (local)
+ send_arg("-l");
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_files (argc, argv, local, 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, argc, argv,
+ local, W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+
+ 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);
+}
+
+/*
+ * remove the file, only if it has already been physically removed
+ */
+/* ARGSUSED */
+static int
+remove_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char fname[PATH_MAX];
+ Vers_TS *vers;
+
+ if (force)
+ {
+ if (!noexec)
+ {
+ if (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->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 0, 0, finfo->entries, finfo->rcs);
+
+ 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')
+ {
+ /*
+ * 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);
+ (void) sprintf (fname, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
+ (void) unlink_file (fname);
+ 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
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ if (!quiet)
+ error (0, 0, "file `%s' already scheduled for removal",
+ finfo->fullname);
+ }
+ else
+ {
+ /* Re-register it with a negative version number. */
+ (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
+ }
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+remove_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Removing %s", update_dir);
+ return (R_PROCESS);
+}
diff --git a/contrib/cvs/src/repos.c b/contrib/cvs/src/repos.c
new file mode 100644
index 0000000..7beaaba
--- /dev/null
+++ b/contrib/cvs/src/repos.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ */
+
+#include "cvs.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 *ret, *xupdate_dir;
+ char repos[PATH_MAX];
+ char path[PATH_MAX];
+ char tmp[PATH_MAX];
+ char cvsadm[PATH_MAX];
+ char *cp;
+
+ if (update_dir && *update_dir)
+ xupdate_dir = update_dir;
+ else
+ xupdate_dir = ".";
+
+ if (dir != NULL)
+ (void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
+ else
+ (void) strcpy (cvsadm, CVSADM);
+
+ /* sanity checks */
+ 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);
+ }
+
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT);
+ else
+ (void) strcpy (tmp, CVSADM_ENT);
+
+ if (!isreadable (tmp))
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "*PANIC* administration files missing");
+ }
+
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP);
+ else
+ (void) strcpy (tmp, CVSADM_REP);
+
+ if (!isreadable (tmp))
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "*PANIC* administration files missing");
+ }
+
+ /*
+ * The assumption here is that the repository is always contained in the
+ * first line of the "Repository" file.
+ */
+ fpin = open_file (tmp, "r");
+
+ if (fgets (repos, PATH_MAX, fpin) == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, errno, "cannot read %s", CVSADM_REP);
+ }
+ (void) fclose (fpin);
+ if ((cp = 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))
+ {
+ if (CVSroot == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0, "must set the CVSROOT environment variable\n");
+ error (0, 0, "or specify the '-d' option to %s.", program_name);
+ error (1, 0, "illegal repository setting");
+ }
+ (void) strcpy (path, repos);
+ (void) sprintf (repos, "%s/%s", CVSroot, path);
+ }
+#ifdef CLIENT_SUPPORT
+ if (!client_active && !isdir (repos))
+#else
+ if (!isdir (repos))
+#endif
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "there is no repository %s", repos);
+ }
+
+ /* allocate space to return and fill it in */
+ strip_path (repos);
+ ret = xstrdup (repos);
+ return (ret);
+}
+
+/*
+ * Return a pointer to the repository name relative to CVSROOT from a
+ * possibly fully qualified repository
+ */
+char *
+Short_Repository (repository)
+ char *repository;
+{
+ if (repository == NULL)
+ return (NULL);
+
+ /* If repository matches CVSroot at the beginning, strip off CVSroot */
+ /* And skip leading '/' in rep, in case CVSroot ended with '/'. */
+ if (strncmp (CVSroot, repository, strlen (CVSroot)) == 0)
+ {
+ char *rep = repository + strlen (CVSroot);
+ return (*rep == '/') ? rep+1 : rep;
+ }
+ else
+ return (repository);
+}
diff --git a/contrib/cvs/src/root.c b/contrib/cvs/src/root.c
new file mode 100644
index 0000000..0742cf0
--- /dev/null
+++ b/contrib/cvs/src/root.c
@@ -0,0 +1,177 @@
+/*
+ * 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 1.4 kit.
+ *
+ * Name of Root
+ *
+ * Determine the path to the CVSROOT and set "Root" accordingly.
+ * If this looks like of modified clone of Name_Repository() in
+ * repos.c, it is...
+ */
+
+#include "cvs.h"
+
+char *
+Name_Root(dir, update_dir)
+ char *dir;
+ char *update_dir;
+{
+ FILE *fpin;
+ char *ret, *xupdate_dir;
+ char root[PATH_MAX];
+ char tmp[PATH_MAX];
+ char cvsadm[PATH_MAX];
+ char *cp;
+
+ if (update_dir && *update_dir)
+ xupdate_dir = update_dir;
+ else
+ xupdate_dir = ".";
+
+ if (dir != NULL)
+ {
+ (void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
+ }
+ else
+ {
+ (void) strcpy (cvsadm, CVSADM);
+ (void) strcpy (tmp, 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)))
+ {
+ if (CVSroot == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0, "must set the CVSROOT environment variable");
+ error (0, 0, "or specify the '-d' option to %s.", program_name);
+ }
+ return (NULL);
+ }
+
+ /*
+ * 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 (fgets (root, PATH_MAX, fpin) == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, errno, "cannot read %s", CVSADM_ROOT);
+ error (0, 0, "please correct this problem");
+ return (NULL);
+ }
+ (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
+ */
+
+#ifdef CLIENT_SUPPORT
+ /* It must specify a server via remote CVS or be an absolute pathname. */
+ if ((strchr (root, ':') == NULL)
+ && ! isabsolute (root))
+#else /* ! CLIENT_SUPPORT */
+ if (root[0] != '/')
+#endif /* CLIENT_SUPPORT */
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0,
+ "ignoring %s because it does not contain an absolute pathname.",
+ CVSADM_ROOT);
+ return (NULL);
+ }
+
+#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);
+ return (NULL);
+ }
+
+ /* allocate space to return and fill it in */
+ strip_path (root);
+ ret = xstrdup (root);
+ return (ret);
+}
+
+/*
+ * Returns non-zero if the two directories have the same stat values
+ * which indicates that they are really the same directories.
+ */
+int
+same_directories (dir1, dir2)
+ char *dir1;
+ char *dir2;
+{
+ struct stat sb1;
+ struct stat sb2;
+ int ret;
+
+ if (stat (dir1, &sb1) < 0)
+ return (0);
+ if (stat (dir2, &sb2) < 0)
+ return (0);
+
+ ret = 0;
+ if ( (memcmp( &sb1.st_dev, &sb2.st_dev, sizeof(dev_t) ) == 0) &&
+ (memcmp( &sb1.st_ino, &sb2.st_ino, sizeof(ino_t) ) == 0))
+ ret = 1;
+
+ 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[PATH_MAX];
+
+ if (noexec)
+ return;
+
+ /* record the current cvs root */
+
+ if (rootdir != NULL)
+ {
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
+ else
+ (void) strcpy (tmp, 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);
+ }
+}
diff --git a/contrib/cvs/src/rtag.c b/contrib/cvs/src/rtag.c
new file mode 100644
index 0000000..8609647
--- /dev/null
+++ b/contrib/cvs/src/rtag.c
@@ -0,0 +1,682 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Rtag
+ *
+ * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
+ * Uses the modules database, if necessary.
+ */
+
+#include "cvs.h"
+
+static int check_fileproc PROTO((struct file_info *finfo));
+static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+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((char *dir, char *repos, char *update_dir));
+static int rtag_fileproc PROTO((struct file_info *finfo));
+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 tag|-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-[rD]\tExisting tag or Date.\n",
+ "\t-F\tMove tag if it already exists\n",
+ NULL
+};
+
+int
+rtag (argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ int c;
+ DBM *db;
+ int run_module_prog = 1;
+ int err = 0;
+
+ if (argc == -1)
+ usage (rtag_usage);
+
+ optind = 1;
+ while ((c = 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 (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;
+{
+ int err = 0;
+ int which;
+ char repository[PATH_MAX];
+ char where[PATH_MAX];
+
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+ (void) strcpy (where, argv[0]);
+
+ /* if mfile isn't null, we need to set up to do only part of the module */
+ if (mfile != NULL)
+ {
+ char *cp;
+ char path[PATH_MAX];
+
+ /* if the portion of the module is a path, put the dir part on repos */
+ if ((cp = 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 */
+ (void) sprintf (path, "%s/%s", repository, mfile);
+ if (isdir (path))
+ {
+ /* directory means repository gets the dir tacked on */
+ (void) strcpy (repository, path);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ }
+ else
+ {
+ int i;
+
+ /* a file means muck argv */
+ for (i = 1; i < *pargc; i++)
+ free (argv[i]);
+ argv[1] = xstrdup (mfile);
+ (*pargc) = 2;
+ }
+ }
+
+ /* chdir to the starting directory */
+ if (chdir (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ return (1);
+ }
+
+ if (delete_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,
+ *pargc - 1, argv + 1, local, which, 0, 1,
+ where, 1, 1);
+
+ if (err)
+ {
+ error (1, 0, "correct the above errors first!");
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (rtag_fileproc, (FILESDONEPROC) NULL, rtag_dirproc,
+ (DIRLEAVEPROC) NULL, *pargc - 1, argv + 1, local,
+ which, 0, 1, where, 1, 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 (finfo)
+ 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->repository, (char *) NULL, (char *) NULL,
+ (char *) NULL, finfo->file, 0, 0, finfo->entries, finfo->rcs);
+ p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match, 0);
+ if (p->data != NULL)
+ {
+ int addit = 1;
+ char *oversion;
+
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
+ if (oversion == NULL)
+ {
+ if (delete_flag)
+ {
+ addit = 0;
+ }
+ }
+ 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(err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ 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(*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("%s %s %s %s",
+ filter,
+ symtag,
+ delete_flag ? "del" : force_tag_move ? "mov" : "add",
+ repository);
+ walklist(tlist, pretag_list_proc, NULL);
+ return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
+}
+
+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 (finfo)
+ struct file_info *finfo;
+{
+ RCSNode *rcsfile;
+ char *version, *rev;
+ int retcode = 0;
+
+ /* 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, 0);
+ if (version == NULL)
+ {
+ /* If -a specified, clean up any old tags */
+ if (attic_too)
+ (void) rtag_delete (rcsfile);
+
+ if (!quiet && !force_tag_match)
+ {
+ error (0, 0, "cannot find tag `%s' in `%s'",
+ numtag ? numtag : "head", rcsfile->path);
+ return (1);
+ }
+ return (0);
+ }
+ if (numtag && isdigit (*numtag) && strcmp (numtag, version) != 0)
+ {
+
+ /*
+ * We didn't find a match for the numeric tag that was specified, but
+ * that's OK. just pass the numeric tag on to rcs, to be tagged as
+ * specified. Could get here if one tried to tag "1.1.1" and there
+ * was a 1.1.1 branch with some head revision. In this case, we want
+ * the tag to reference "1.1.1" and not the revision at the head of
+ * the branch. Use a symbolic tag for that.
+ */
+ rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
+ retcode = RCS_settag(rcsfile->path, symtag, numtag);
+ }
+ else
+ {
+ char *oversion;
+
+ /*
+ * As an enhancement for the case where a tag is being re-applied to
+ * a large body of a module, make one extra call to 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, 0);
+ if (oversion != NULL)
+ {
+ int isbranch = RCS_isbranch (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->path, symtag, rev);
+ }
+
+ 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, 0);
+ if (version == NULL)
+ return (0);
+ free (version);
+ }
+
+ version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1, 0);
+ if (version == NULL)
+ return (0);
+ free (version);
+
+ if ((retcode = RCS_deltag(rcsfile->path, symtag, 1)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag `%s' from `%s'", symtag,
+ rcsfile->path);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+rtag_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
+ return (R_PROCESS);
+}
+
+
+
diff --git a/contrib/cvs/src/run.c b/contrib/cvs/src/run.c
new file mode 100644
index 0000000..036821e
--- /dev/null
+++ b/contrib/cvs/src/run.c
@@ -0,0 +1,541 @@
+/* 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "cvs.h"
+
+#ifdef HAVE_VPRINTF
+#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
+#include <stdarg.h>
+#define VA_START(args, lastarg) va_start(args, lastarg)
+#else
+#include <varargs.h>
+#define VA_START(args, lastarg) va_start(args)
+#endif
+#else
+#define va_alist a1, a2, a3, a4, a5, a6, a7, a8
+#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
+#endif
+
+static void run_add_arg PROTO((const char *s));
+static void run_init_prog PROTO((void));
+
+extern char *strtok ();
+
+/*
+ * To exec a program under CVS, first call run_setup() to setup any initial
+ * arguments. The options to run_setup are essentially like printf(). The
+ * arguments will be parsed into whitespace separated words and added to the
+ * global run_argv list.
+ *
+ * Then, optionally call run_arg() for each additional argument that you'd like
+ * to pass to the executed program.
+ *
+ * Finally, call run_exec() to execute the program with the specified arguments.
+ * The execvp() syscall will be used, so that the PATH is searched correctly.
+ * File redirections can be performed in the call to run_exec().
+ */
+static char *run_prog;
+static char **run_argv;
+static int run_argc;
+static int run_argc_allocated;
+
+/* VARARGS */
+#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__))
+void
+run_setup (const char *fmt,...)
+#else
+void
+run_setup (fmt, va_alist)
+ char *fmt;
+ va_dcl
+#endif
+{
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+ char *cp;
+ int i;
+
+ run_init_prog ();
+
+ /* clean out any malloc'ed values from run_argv */
+ for (i = 0; i < run_argc; i++)
+ {
+ if (run_argv[i])
+ {
+ free (run_argv[i]);
+ run_argv[i] = (char *) 0;
+ }
+ }
+ run_argc = 0;
+
+ /* process the varargs into run_prog */
+#ifdef HAVE_VPRINTF
+ VA_START (args, fmt);
+ (void) vsprintf (run_prog, fmt, args);
+ va_end (args);
+#else
+ (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+
+ /* put each word into run_argv, allocating it as we go */
+ for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t"))
+ run_add_arg (cp);
+}
+
+void
+run_arg (s)
+ const char *s;
+{
+ run_add_arg (s);
+}
+
+/* VARARGS */
+#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__))
+void
+run_args (const char *fmt,...)
+#else
+void
+run_args (fmt, va_alist)
+ char *fmt;
+ va_dcl
+#endif
+{
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+
+ run_init_prog ();
+
+ /* process the varargs into run_prog */
+#ifdef HAVE_VPRINTF
+ VA_START (args, fmt);
+ (void) vsprintf (run_prog, fmt, args);
+ va_end (args);
+#else
+ (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+
+ /* and add the (single) argument to the run_argv list */
+ run_add_arg (run_prog);
+}
+
+static void
+run_add_arg (s)
+ 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! */
+}
+
+static void
+run_init_prog ()
+{
+ /* make sure that run_prog is allocated once */
+ if (run_prog == (char *) 0)
+ run_prog = xmalloc (10 * 1024); /* 10K of args for _setup and _arg */
+}
+
+int
+run_exec (stin, stout, sterr, flags)
+ char *stin;
+ char *stout;
+ char *sterr;
+ int flags;
+{
+ int shin, shout, sherr;
+ int mode_out, mode_err;
+ int status;
+ 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
+ (void) fprintf (stderr, "%c-> system(", (server_active) ? 'S' : ' ');
+#else
+ (void) fprintf (stderr, "-> system(");
+#endif
+ run_print (stderr);
+ (void) fprintf (stderr, ")\n");
+ }
+ if (noexec && (flags & RUN_REALLY) == 0)
+ return (0);
+
+ /* make sure that we are null terminated, since we didn't calloc */
+ run_add_arg ((char *) 0);
+
+ /* setup default file descriptor numbers */
+ shin = 0;
+ shout = 1;
+ sherr = 2;
+
+ /* set the file modes for stdout and stderr */
+ mode_out = mode_err = O_WRONLY | O_CREAT;
+ mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC);
+ mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC);
+
+ if (stin && (shin = open (stin, O_RDONLY)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for reading (prog %s)",
+ stin, run_argv[0]);
+ goto out0;
+ }
+ if (stout && (shout = open (stout, mode_out, 0666)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for writing (prog %s)",
+ stout, run_argv[0]);
+ goto out1;
+ }
+ if (sterr && (flags & RUN_COMBINED) == 0)
+ {
+ if ((sherr = open (sterr, mode_err, 0666)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for writing (prog %s)",
+ sterr, run_argv[0]);
+ goto out2;
+ }
+ }
+
+ /* 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 */
+#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;
+
+ for (i = 0; i < run_argc; i++)
+ {
+ (void) fprintf (fp, "'%s'", run_argv[i]);
+ if (i != run_argc - 1)
+ (void) fprintf (fp, " ");
+ }
+}
+
+FILE *
+run_popen (cmd, mode)
+ const char *cmd;
+ const char *mode;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> run_popen(%s,%s)\n",
+ (server_active) ? 'S' : ' ', cmd, mode);
+#else
+ (void) fprintf (stderr, "-> run_popen(%s,%s)\n", cmd, mode);
+#endif
+ if (noexec)
+ return (NULL);
+
+ return (popen (cmd, mode));
+}
+
+extern int evecvp PROTO((char *file, char **argv));
+
+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");
+
+ pid = fork ();
+ 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");
+ if (close (to_child_pipe[1]) < 0)
+ error (1, errno, "cannot close");
+ if (close (from_child_pipe[0]) < 0)
+ error (1, errno, "cannot close");
+ if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0)
+ error (1, errno, "cannot dup2");
+
+ execvp (command[0], command);
+ error (1, errno, "cannot exec");
+ }
+ if (close (to_child_pipe[0]) < 0)
+ error (1, errno, "cannot close");
+ if (close (from_child_pipe[1]) < 0)
+ error (1, errno, "cannot close");
+
+ *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
+}
+
+/*
+ * dir = 0 : main proc writes to new proc, which writes to oldfd
+ * dir = 1 : main proc reads from new proc, which reads from oldfd
+ *
+ * Returns: a file descriptor. On failure (i.e., the exec fails),
+ * then filter_stream_through_program() complains and dies.
+ */
+
+int
+filter_stream_through_program (oldfd, dir, prog, pidp)
+ int oldfd, dir;
+ char **prog;
+ pid_t *pidp;
+{
+ int p[2], newfd;
+ pid_t newpid;
+
+ if (pipe (p))
+ error (1, errno, "cannot create pipe");
+ newpid = fork ();
+ if (pidp)
+ *pidp = newpid;
+ switch (newpid)
+ {
+ case -1:
+ error (1, errno, "cannot fork");
+ case 0:
+ /* child */
+ if (dir)
+ {
+ /* write to new pipe */
+ close (p[0]);
+ dup2 (oldfd, 0);
+ dup2 (p[1], 1);
+ }
+ else
+ {
+ /* read from new pipe */
+ close (p[1]);
+ dup2 (p[0], 0);
+ dup2 (oldfd, 1);
+ }
+ /* Should I be blocking some signals here? */
+ execvp (prog[0], prog);
+ error (1, errno, "couldn't exec %s", prog[0]);
+ default:
+ /* parent */
+ close (oldfd);
+ if (dir)
+ {
+ /* read from new pipe */
+ close (p[1]);
+ newfd = p[0];
+ }
+ else
+ {
+ /* write to new pipe */
+ close (p[0]);
+ newfd = p[1];
+ }
+ close_on_exec (newfd);
+ return newfd;
+ }
+}
diff --git a/contrib/cvs/src/sanity.sh b/contrib/cvs/src/sanity.sh
new file mode 100755
index 0000000..4f80f90
--- /dev/null
+++ b/contrib/cvs/src/sanity.sh
@@ -0,0 +1,2869 @@
+#! /bin/sh
+:
+# sanity.sh -- a growing testsuite for cvs.
+#
+# Copyright (C) 1992, 1993 Cygnus Support
+#
+# Original Author: K. Richard Pixley
+
+# usage: sanity.sh [-r] @var{cvs-to-test} @var{tests-to-run}
+# -r means to test remote instead of local cvs.
+# @var{tests-to-run} are the names of the tests to run; if omitted run all
+# tests.
+
+# See TODO list at end of file.
+
+# required to make this script work properly.
+unset CVSREAD
+
+TESTDIR=/tmp/cvs-sanity
+
+# "debugger"
+#set -x
+
+echo 'This test should produce no other output than this line, and a final "OK".'
+
+if test x"$1" = x"-r"; then
+ shift
+ remote=yes
+else
+ remote=no
+fi
+
+# The --keep option will eventually cause all the tests to leave around the
+# contents of the /tmp directory; right now only some implement it. Not
+# useful if you are running more than one test.
+# FIXME: need some real option parsing so this doesn't depend on the order
+# in which they are specified.
+if test x"$1" = x"--keep"; then
+ shift
+ keep=yes
+else
+ keep=no
+fi
+
+# Use full path for CVS executable, so that CVS_SERVER gets set properly
+# for remote.
+case $1 in
+/*)
+ testcvs=$1
+ ;;
+*)
+ testcvs=`pwd`/$1
+ ;;
+esac
+
+shift
+
+# Regexp to match what CVS will call itself in output that it prints.
+# FIXME: we don't properly quote this--if the name contains . we'll
+# just spuriously match a few things; if the name contains other regexp
+# special characters we are probably in big trouble.
+PROG=`basename ${testcvs}`
+
+# FIXME: try things (what things? checkins?) without -m.
+#
+# Some of these tests are written to expect -Q. But testing with
+# -Q is kind of bogus, it is not the way users actually use CVS (usually).
+# So new tests probably should invoke ${testcvs} directly, rather than ${CVS}.
+# and then they've obviously got to do something with the output....
+#
+CVS="${testcvs} -Q"
+
+LOGFILE=`pwd`/check.log
+
+# Save the previous log in case the person running the tests decides
+# they want to look at it. The extension ".plog" is chosen for consistency
+# with dejagnu.
+if test -f check.log; then
+ mv check.log check.plog
+fi
+
+# That we should have to do this is total bogosity, but GNU expr
+# version 1.9.4 uses the emacs definition of "$" instead of the unix
+# (e.g. SunOS 4.1.3 expr) one. Rumor has it this will be fixed in the
+# next release of GNU expr after 1.12 (but we still have to cater to the old
+# ones for some time because they are in many linux distributions).
+ENDANCHOR="$"
+if expr 'abc
+def' : 'abc$' >/dev/null; then
+ ENDANCHOR='\'\'
+fi
+
+# Work around another GNU expr (version 1.10) bug/incompatibility.
+# "." doesn't appear to match a newline (it does with SunOS 4.1.3 expr).
+# Note that the workaround is not a complete equivalent of .* because
+# the first parenthesized expression in the regexp must match something
+# in order for expr to return a successful exit status.
+# Rumor has it this will be fixed in the
+# next release of GNU expr after 1.12 (but we still have to cater to the old
+# ones for some time because they are in many linux distributions).
+DOTSTAR='.*'
+if expr 'abc
+def' : "a${DOTSTAR}f" >/dev/null; then
+ : good, it works
+else
+ DOTSTAR='\(.\|
+\)*'
+fi
+
+# Work around yet another GNU expr (version 1.10) bug/incompatibility.
+# "+" is a special character, yet for unix expr (e.g. SunOS 4.1.3)
+# it is not. I doubt that POSIX allows us to use \+ and assume it means
+# (non-special) +, so here is another workaround
+# Rumor has it this will be fixed in the
+# next release of GNU expr after 1.12 (but we still have to cater to the old
+# ones for some time because they are in many linux distributions).
+PLUS='+'
+if expr 'a +b' : "a ${PLUS}b" >/dev/null; then
+ : good, it works
+else
+ PLUS='\+'
+fi
+
+# Likewise, for ?
+QUESTION='?'
+if expr 'a?b' : "a${QUESTION}b" >/dev/null; then
+ : good, it works
+else
+ QUESTION='\?'
+fi
+
+# Cause NextStep 3.3 users to lose in a more graceful fashion.
+if expr 'abc
+def' : 'abc
+def' >/dev/null; then
+ : good, it works
+else
+ echo 'Running these tests requires an "expr" program that can handle'
+ echo 'multi-line patterns. Make sure that such an expr (GNU, or many but'
+ echo 'not all vendor-supplied versions) is in your path.'
+ exit 1
+fi
+
+# Warn SunOS, SysVr3.2, etc., users that they may be partially losing
+if expr 'a
+b' : 'a
+c' >/dev/null; then
+ echo 'Warning: you are using a version of expr which does not correctly'
+ echo 'match multi-line patterns. Some tests may spuriously pass.'
+ echo 'You may wish to make sure GNU expr is in your path.'
+else
+ : good, it works
+fi
+
+pass ()
+{
+ echo "PASS: $1" >>${LOGFILE}
+}
+
+fail ()
+{
+ echo "FAIL: $1" | tee -a ${LOGFILE}
+ # This way the tester can go and see what remnants were left
+ exit 1
+}
+
+# See dotest and dotest_fail for explanation (this is the parts
+# of the implementation common to the two).
+dotest_internal ()
+{
+ # expr can't distinguish between "zero characters matched" and "no match",
+ # so special-case it.
+ if test -z "$3"; then
+ if test -s ${TESTDIR}/dotest.tmp; then
+ echo "** expected: " >>${LOGFILE}
+ echo "$3" >>${LOGFILE}
+ echo "** got: " >>${LOGFILE}
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ fail "$1"
+ else
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ pass "$1"
+ fi
+ else
+ if expr "`cat ${TESTDIR}/dotest.tmp`" : \
+ "$3"${ENDANCHOR} >/dev/null; then
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ pass "$1"
+ else
+ if test x"$4" != x; then
+ if expr "`cat ${TESTDIR}/dotest.tmp`" : \
+ "$4"${ENDANCHOR} >/dev/null; then
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ pass "$1"
+ else
+ echo "** expected: " >>${LOGFILE}
+ echo "$3" >>${LOGFILE}
+ echo "** or: " >>${LOGFILE}
+ echo "$4" >>${LOGFILE}
+ echo "** got: " >>${LOGFILE}
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ fail "$1"
+ fi
+ else
+ echo "** expected: " >>${LOGFILE}
+ echo "$3" >>${LOGFILE}
+ echo "** got: " >>${LOGFILE}
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ fail "$1"
+ fi
+ fi
+ fi
+}
+
+# Usage:
+# dotest TESTNAME COMMAND OUTPUT [OUTPUT2]
+# TESTNAME is the name used in the log to identify the test.
+# COMMAND is the command to run; for the test to pass, it exits with
+# exitstatus zero.
+# OUTPUT is a regexp which is compared against the output (stdout and
+# stderr combined) from the test. It is anchored to the start and end
+# of the output, so should start or end with ".*" if that is what is desired.
+# Trailing newlines are stripped from the command's actual output before
+# matching against OUTPUT.
+# If OUTPUT2 is specified and the output matches it, then it is also
+# a pass (partial workaround for the fact that some versions of expr
+# lack \|).
+dotest ()
+{
+ if $2 >${TESTDIR}/dotest.tmp 2>&1; then
+ : so far so good
+ else
+ status=$?
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ echo "exit status was $status" >>${LOGFILE}
+ fail "$1"
+ fi
+ dotest_internal "$@"
+}
+
+# Like dotest except exitstatus should be nonzero.
+dotest_fail ()
+{
+ if $2 >${TESTDIR}/dotest.tmp 2>&1; then
+ status=$?
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ echo "exit status was $status" >>${LOGFILE}
+ fail "$1"
+ else
+ : so far so good
+ fi
+ dotest_internal "$@"
+}
+
+# Like dotest except second argument is the required exitstatus.
+dotest_status ()
+{
+ $3 >${TESTDIR}/dotest.tmp 2>&1
+ status=$?
+ if test "$status" = "$2"; then
+ : so far so good
+ else
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ echo "exit status was $status; expected $2" >>${LOGFILE}
+ fail "$1"
+ fi
+ dotest_internal "$1" "$3" "$4" "$5"
+}
+
+# clean any old remnants
+rm -rf ${TESTDIR}
+mkdir ${TESTDIR}
+cd ${TESTDIR}
+
+# Avoid picking up any stray .cvsrc, etc., from the user running the tests
+mkdir home
+HOME=${TESTDIR}/home; export HOME
+
+# Remaining arguments are the names of tests to run.
+#
+# The testsuite is broken up into (hopefully manageably-sized)
+# independently runnable tests, so that one can quickly get a result
+# from a cvs or testsuite change, and to facilitate understanding the
+# tests.
+
+if test x"$*" = x; then
+ tests="basica basic1 deep basic2 death branches import new conflicts modules mflag errmsg1 devcom ignore binfiles info"
+else
+ tests="$*"
+fi
+
+# this should die
+if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then
+ echo "FAIL: test 1" | tee -a ${LOGFILE}
+ exit 1
+else
+ echo "PASS: test 1" >>${LOGFILE}
+fi
+
+# this should still die
+mkdir cvsroot
+if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then
+ echo "FAIL: test 2" | tee -a ${LOGFILE}
+ exit 1
+else
+ echo "PASS: test 2" >>${LOGFILE}
+fi
+
+# this should still die
+mkdir cvsroot/CVSROOT
+if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then
+ echo "FAIL: test 3" | tee -a ${LOGFILE}
+ exit 1
+else
+ echo "PASS: test 3" >>${LOGFILE}
+fi
+
+# This one should work, although it should spit a warning.
+mkdir tmp ; cd tmp
+${CVS} -d `pwd`/../cvsroot co CVSROOT 2>> ${LOGFILE}
+cd .. ; rm -rf tmp
+
+# set up a minimal modules file...
+# (now that mkmodules is gone, this doesn't test -i the way it
+# used to. In fact, it looks like a noop to me).
+echo "CVSROOT CVSROOT" > cvsroot/CVSROOT/modules
+# The following line stolen from cvsinit.sh. FIXME: create our
+# repository via cvsinit.sh; that way we test it too.
+(cd cvsroot/CVSROOT; ci -q -u -t/dev/null \
+ -m'initial checkin of modules' modules)
+
+# This one should succeed. No warnings.
+mkdir tmp ; cd tmp
+if ${CVS} -d `pwd`/../cvsroot co CVSROOT ; then
+ echo "PASS: test 4" >>${LOGFILE}
+else
+ echo "FAIL: test 4" | tee -a ${LOGFILE}
+ exit 1
+fi
+
+if echo "yes" | ${CVS} -d `pwd`/../cvsroot release -d CVSROOT ; then
+ echo "PASS: test 4.5" >>${LOGFILE}
+else
+ echo "FAIL: test 4.5" | tee -a ${LOGFILE}
+ exit 1
+fi
+# this had better be empty
+cd ..; rmdir tmp
+dotest_fail 4.75 "test -d tmp" ''
+
+# a simple function to compare directory contents
+#
+# BTW, I don't care any more -- if you don't have a /bin/sh that handles
+# shell functions, well get one.
+#
+# Returns: ISDIFF := true|false
+#
+directory_cmp ()
+{
+ OLDPWD=`pwd`
+ DIR_1=$1
+ DIR_2=$2
+ ISDIFF=false
+
+ cd $DIR_1
+ find . -print | fgrep -v /CVS | sort > /tmp/dc$$d1
+
+ # go back where we were to avoid symlink hell...
+ cd $OLDPWD
+ cd $DIR_2
+ find . -print | fgrep -v /CVS | sort > /tmp/dc$$d2
+
+ if diff /tmp/dc$$d1 /tmp/dc$$d2 >/dev/null 2>&1
+ then
+ :
+ else
+ ISDIFF=true
+ return
+ fi
+ cd $OLDPWD
+ while read a
+ do
+ if [ -f $DIR_1/"$a" ] ; then
+ cmp -s $DIR_1/"$a" $DIR_2/"$a"
+ if [ $? -ne 0 ] ; then
+ ISDIFF=true
+ fi
+ fi
+ done < /tmp/dc$$d1
+### FIXME:
+### rm -f /tmp/dc$$*
+}
+
+# so much for the setup. Let's try something harder.
+
+# Try setting CVSROOT so we don't have to worry about it anymore. (now that
+# we've tested -d cvsroot.)
+CVSROOT_DIRNAME=${TESTDIR}/cvsroot
+CVSROOT=${CVSROOT_DIRNAME} ; export CVSROOT
+if test "x$remote" = xyes; then
+ CVSROOT=`hostname`:${CVSROOT_DIRNAME} ; export CVSROOT
+ # Use rsh so we can test it without having to muck with inetd or anything
+ # like that. Also needed to get CVS_SERVER to work.
+ CVS_CLIENT_PORT=-1; export CVS_CLIENT_PORT
+ CVS_SERVER=${testcvs}; export CVS_SERVER
+fi
+
+# start keeping history
+touch ${CVSROOT_DIRNAME}/CVSROOT/history
+
+### The big loop
+for what in $tests; do
+ case $what in
+ basica)
+ # Similar in spirit to some of the basic1, and basic2
+ # tests, but hopefully a lot faster. Also tests operating on
+ # files two directories down *without* operating on the parent dirs.
+
+ # Using mkdir in the repository is used throughout these
+ # tests to create a top-level directory. I think instead it
+ # should be:
+ # cvs co -l .
+ # mkdir first-dir
+ # cvs add first-dir
+ # but currently that works only for local CVS, not remote.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest basica-1 "${testcvs} -q co first-dir" ''
+ cd first-dir
+
+ # Test a few operations, to ensure they gracefully do
+ # nothing in an empty directory.
+ dotest basica-1a0 "${testcvs} -q update" ''
+ dotest basica-1a1 "${testcvs} -q diff -c" ''
+ dotest basica-1a2 "${testcvs} -q status" ''
+
+ mkdir sdir
+ dotest basica-2 "${testcvs} add sdir" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/sdir added to the repository'
+ cd sdir
+ mkdir ssdir
+ dotest basica-3 "${testcvs} add ssdir" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir added to the repository'
+ cd ssdir
+ echo ssfile >ssfile
+
+ # Trying to commit it without a "cvs add" should be an error.
+ # The "use `cvs add' to create an entry" message is the one
+ # that I consider to be more correct, but local cvs prints the
+ # "nothing known" message and noone has gotten around to fixing it.
+ dotest_fail basica-notadded "${testcvs} -q ci ssfile" \
+"${PROG} [a-z]*: use "'`cvs add'\'' to create an entry for ssfile
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!' \
+"${PROG}"' [a-z]*: nothing known about `ssfile'\''
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!'
+
+ dotest basica-4 "${testcvs} add ssfile" \
+"${PROG}"' [a-z]*: scheduling file `ssfile'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest_fail basica-4a "${testcvs} tag tag0 ssfile" \
+"${PROG} [a-z]*: nothing known about ssfile
+${PROG} "'\[[a-z]* aborted\]: correct the above errors first!'
+ cd ../..
+ dotest basica-5 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v
+done
+Checking in sdir/ssdir/ssfile;
+/tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v <-- ssfile
+initial revision: 1.1
+done'
+ dotest_fail basica-5a \
+ "${testcvs} -q tag BASE sdir/ssdir/ssfile" \
+"${PROG} [a-z]*: Attempt to add reserved tag name BASE
+${PROG} \[[a-z]* aborted\]: failed to set tag BASE to revision 1.1 in /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v"
+ dotest basica-5b "${testcvs} -q tag NOT_RESERVED" \
+'T sdir/ssdir/ssfile'
+
+ dotest basica-6 "${testcvs} -q update" ''
+ echo "ssfile line 2" >>sdir/ssdir/ssfile
+ dotest_status basica-6.2 1 "${testcvs} -q diff -c" \
+'Index: sdir/ssdir/ssfile
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v
+retrieving revision 1\.1
+diff -c -r1\.1 ssfile
+\*\*\* ssfile [0-9/]* [0-9:]* 1\.1
+--- ssfile [0-9/]* [0-9:]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+ ssfile
+'"${PLUS} ssfile line 2"
+ dotest basica-7 "${testcvs} -q ci -m modify-it" \
+'Checking in sdir/ssdir/ssfile;
+/tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v <-- ssfile
+new revision: 1.2; previous revision: 1.1
+done'
+ dotest_fail basica-nonexist "${testcvs} -q ci nonexist" \
+"${PROG}"' [a-z]*: nothing known about `nonexist'\''
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!'
+ dotest basica-8 "${testcvs} -q update" ''
+ dotest_fail basica-9 \
+ "${testcvs} -q -d /tmp/cvs-sanity/nonexist update" \
+"${PROG}: .*/tmp/cvs-sanity/cvsroot value for CVS Root found in CVS/Root
+${PROG}"': does not match command line -d /tmp/cvs-sanity/nonexist setting
+'"${PROG}"': you may wish to try the cvs command again without the -d option '
+
+ dotest basica-10 "${testcvs} annotate" \
+'Annotations for sdir/ssdir/ssfile
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1.1 .[a-z@][a-z@ ]* [0-9a-zA-Z-]*.: ssfile
+1.2 .[a-z@][a-z@ ]* [0-9a-zA-Z-]*.: ssfile line 2'
+ cd ..
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -r first-dir
+ ;;
+
+ basic1) # first dive - add a files, first singly, then in a group.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ # check out an empty directory
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 13a" >>${LOGFILE}
+ else
+ echo "FAIL: test 13a" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ cd first-dir
+ files=first-file
+ for i in a b ; do
+ for j in ${files} ; do
+ echo $j > $j
+ done
+
+ for do in add rm ; do
+ for j in ${do} "commit -m test" ; do
+ # ${do}
+ if ${CVS} $j ${files} >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 14-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 14-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # update it.
+ if [ "${do}" = "rm" -a "$j" != "commit -m test" ] || ${CVS} update ${files} ; then
+ echo "PASS: test 15-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 15-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # update all.
+ if ${CVS} update ; then
+ echo "PASS: test 16-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 16-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # status all.
+ if ${CVS} status >> ${LOGFILE}; then
+ echo "PASS: test 17-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 17-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # FIXME: this one doesn't work yet for added files.
+ # log all.
+ if ${CVS} log >> ${LOGFILE}; then
+ echo "PASS: test 18-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 18-${do}-$j" | tee -a ${LOGFILE}
+ fi
+
+ cd ..
+ # update all.
+ if ${CVS} update ; then
+ echo "PASS: test 21-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 21-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # log all.
+ # FIXME: doesn't work right for added files.
+ if ${CVS} log first-dir >> ${LOGFILE}; then
+ echo "PASS: test 22-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 22-${do}-$j" | tee -a ${LOGFILE}
+ fi
+
+ # status all.
+ if ${CVS} status first-dir >> ${LOGFILE}; then
+ echo "PASS: test 23-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 23-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # update all.
+ if ${CVS} update first-dir ; then
+ echo "PASS: test 24-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 24-${do}-$j" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # update all.
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 27-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 27-${do}-$j" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+ done # j
+ rm -f ${files}
+ done # do
+
+ files="file2 file3 file4 file5"
+ done
+ if ${CVS} tag first-dive ; then
+ echo "PASS: test 28" >>${LOGFILE}
+ else
+ echo "FAIL: test 28" | tee -a ${LOGFILE} ; exit 1
+ fi
+ cd ..
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -rf first-dir
+ ;;
+
+ deep)
+ # Test the ability to operate on directories nested rather deeply.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest deep-1 "${testcvs} -q co first-dir" ''
+ cd first-dir
+ for i in dir1 dir2 dir3 dir4 dir5 dir6 dir7 dir8; do
+ mkdir $i
+ dotest deep-2-$i "${testcvs} add $i" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/dir1[/dir0-9]* added to the repository'
+ cd $i
+ echo file1 >file1
+ dotest deep-3-$i "${testcvs} add file1" \
+"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ done
+ cd ../../../../../../../../..
+ dotest deep-4 "${testcvs} -q ci -m add-them first-dir" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/file1,v
+done
+Checking in first-dir/dir1/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/file1,v
+done
+Checking in first-dir/dir1/dir2/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v <-- file1
+initial revision: 1.1
+done'
+
+ if echo "yes" | ${testcvs} release -d first-dir >>${LOGFILE}; then
+ pass deep-5
+ else
+ fail deep-5
+ fi
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+
+ basic2)
+ # Test rtag, import, history, various miscellaneous operations
+
+ # First empty the history file
+ rm ${CVSROOT_DIRNAME}/CVSROOT/history
+ touch ${CVSROOT_DIRNAME}/CVSROOT/history
+
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest basic2-1 "${testcvs} -q co first-dir" ''
+ for i in first-dir dir1 dir2 ; do
+ if [ ! -d $i ] ; then
+ mkdir $i
+ if ${CVS} add $i >> ${LOGFILE}; then
+ echo "PASS: test 29-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 29-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ fi
+
+ cd $i
+
+ for j in file6 file7; do
+ echo $j > $j
+ done
+
+ if ${CVS} add file6 file7 2>> ${LOGFILE}; then
+ echo "PASS: test 30-$i-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 30-$i-$j" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+ cd ../../..
+ if ${CVS} update first-dir ; then
+ echo "PASS: test 31" >>${LOGFILE}
+ else
+ echo "FAIL: test 31" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # fixme: doesn't work right for added files.
+ if ${CVS} log first-dir >> ${LOGFILE}; then
+ echo "PASS: test 32" >>${LOGFILE}
+ else
+ echo "FAIL: test 32" | tee -a ${LOGFILE} # ; exit 1
+ fi
+
+ if ${CVS} status first-dir >> ${LOGFILE}; then
+ echo "PASS: test 33" >>${LOGFILE}
+ else
+ echo "FAIL: test 33" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then
+# echo "PASS: test 34" >>${LOGFILE}
+# else
+# echo "FAIL: test 34" | tee -a ${LOGFILE} # ; exit 1
+# fi
+
+ if ${CVS} ci -m "second dive" first-dir >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 35" >>${LOGFILE}
+ else
+ echo "FAIL: test 35" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} tag second-dive first-dir ; then
+ echo "PASS: test 36" >>${LOGFILE}
+ else
+ echo "FAIL: test 36" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # third dive - in bunch o' directories, add bunch o' files,
+ # delete some, change some.
+
+ for i in first-dir dir1 dir2 ; do
+ cd $i
+
+ # modify a file
+ echo file6 >>file6
+
+ # delete a file
+ rm file7
+
+ if ${CVS} rm file7 2>> ${LOGFILE}; then
+ echo "PASS: test 37-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 37-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # and add a new file
+ echo file14 >file14
+
+ if ${CVS} add file14 2>> ${LOGFILE}; then
+ echo "PASS: test 38-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 38-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+ cd ../../..
+ if ${CVS} update first-dir ; then
+ echo "PASS: test 39" >>${LOGFILE}
+ else
+ echo "FAIL: test 39" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # fixme: doesn't work right for added files
+ if ${CVS} log first-dir >> ${LOGFILE}; then
+ echo "PASS: test 40" >>${LOGFILE}
+ else
+ echo "FAIL: test 40" | tee -a ${LOGFILE} # ; exit 1
+ fi
+
+ if ${CVS} status first-dir >> ${LOGFILE}; then
+ echo "PASS: test 41" >>${LOGFILE}
+ else
+ echo "FAIL: test 41" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then
+# echo "PASS: test 42" >>${LOGFILE}
+# else
+# echo "FAIL: test 42" | tee -a ${LOGFILE} # ; exit 1
+# fi
+
+ if ${CVS} ci -m "third dive" first-dir >>${LOGFILE} 2>&1; then
+ echo "PASS: test 43" >>${LOGFILE}
+ else
+ echo "FAIL: test 43" | tee -a ${LOGFILE} ; exit 1
+ fi
+ dotest 43.5 "${testcvs} -q update first-dir" ''
+
+ if ${CVS} tag third-dive first-dir ; then
+ echo "PASS: test 44" >>${LOGFILE}
+ else
+ echo "FAIL: test 44" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if echo "yes" | ${CVS} release -d first-dir ; then
+ echo "PASS: test 45" >>${LOGFILE}
+ else
+ echo "FAIL: test 45" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # end of third dive
+ if [ -d first-dir ] ; then
+ echo "FAIL: test 45.5" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 45.5" >>${LOGFILE}
+ fi
+
+ # now try some rtags
+
+ # rtag HEADS
+ if ${CVS} rtag rtagged-by-head first-dir ; then
+ echo "PASS: test 46" >>${LOGFILE}
+ else
+ echo "FAIL: test 46" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # tag by tag
+ if ${CVS} rtag -r rtagged-by-head rtagged-by-tag first-dir ; then
+ echo "PASS: test 47" >>${LOGFILE}
+ else
+ echo "FAIL: test 47" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # tag by revision
+ if ${CVS} rtag -r1.1 rtagged-by-revision first-dir ; then
+ echo "PASS: test 48" >>${LOGFILE}
+ else
+ echo "FAIL: test 48" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # rdiff by revision
+ if ${CVS} rdiff -r1.1 -rrtagged-by-head first-dir >> ${LOGFILE} || [ $? = 1 ] ; then
+ echo "PASS: test 49" >>${LOGFILE}
+ else
+ echo "FAIL: test 49" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # now export by rtagged-by-head and rtagged-by-tag and compare.
+ rm -rf first-dir
+ if ${CVS} export -r rtagged-by-head first-dir ; then
+ echo "PASS: test 50" >>${LOGFILE}
+ else
+ echo "FAIL: test 50" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ mv first-dir 1dir
+ if ${CVS} export -r rtagged-by-tag first-dir ; then
+ echo "PASS: test 51" >>${LOGFILE}
+ else
+ echo "FAIL: test 51" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ directory_cmp 1dir first-dir
+
+ if $ISDIFF ; then
+ echo "FAIL: test 52" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 52" >>${LOGFILE}
+ fi
+ rm -rf 1dir first-dir
+
+ # checkout by revision vs export by rtagged-by-revision and compare.
+ if ${CVS} export -rrtagged-by-revision -d export-dir first-dir ; then
+ echo "PASS: test 53" >>${LOGFILE}
+ else
+ echo "FAIL: test 53" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} co -r1.1 first-dir ; then
+ echo "PASS: test 54" >>${LOGFILE}
+ else
+ echo "FAIL: test 54" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # directory copies are done in an oblique way in order to avoid a bug in sun's tmp filesystem.
+ mkdir first-dir.cpy ; (cd first-dir ; tar cf - * | (cd ../first-dir.cpy ; tar xf -))
+
+ directory_cmp first-dir export-dir
+
+ if $ISDIFF ; then
+ echo "FAIL: test 55" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 55" >>${LOGFILE}
+ fi
+
+ # interrupt, while we've got a clean 1.1 here, let's import it into another tree.
+ cd export-dir
+ dotest 56 "${testcvs} import -m first-import second-dir first-immigration immigration1 immigration1_0" \
+'N second-dir/file14
+N second-dir/file6
+N second-dir/file7
+'"${PROG}"' [a-z]*: Importing /tmp/cvs-sanity/cvsroot/second-dir/dir1
+N second-dir/dir1/file14
+N second-dir/dir1/file6
+N second-dir/dir1/file7
+'"${PROG}"' [a-z]*: Importing /tmp/cvs-sanity/cvsroot/second-dir/dir1/dir2
+N second-dir/dir1/dir2/file14
+N second-dir/dir1/dir2/file6
+N second-dir/dir1/dir2/file7
+
+No conflicts created by this import'
+
+ cd ..
+
+ if ${CVS} export -r HEAD second-dir ; then
+ echo "PASS: test 57" >>${LOGFILE}
+ else
+ echo "FAIL: test 57" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ directory_cmp first-dir second-dir
+
+ if $ISDIFF ; then
+ echo "FAIL: test 58" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 58" >>${LOGFILE}
+ fi
+
+ rm -rf second-dir
+ rm -rf export-dir first-dir
+ mkdir first-dir
+ (cd first-dir.cpy ; tar cf - * | (cd ../first-dir ; tar xf -))
+
+ # update the top, cancelling sticky tags, retag, update other copy, compare.
+ cd first-dir
+ if ${CVS} update -A -l *file* 2>> ${LOGFILE}; then
+ echo "PASS: test 59" >>${LOGFILE}
+ else
+ echo "FAIL: test 59" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # If we don't delete the tag first, cvs won't retag it.
+ # This would appear to be a feature.
+ if ${CVS} tag -l -d rtagged-by-revision ; then
+ echo "PASS: test 60a" >>${LOGFILE}
+ else
+ echo "FAIL: test 60a" | tee -a ${LOGFILE} ; exit 1
+ fi
+ if ${CVS} tag -l rtagged-by-revision ; then
+ echo "PASS: test 60b" >>${LOGFILE}
+ else
+ echo "FAIL: test 60b" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd ..
+ mv first-dir 1dir
+ mv first-dir.cpy first-dir
+ cd first-dir
+
+ dotest 61 "${testcvs} -q diff -u" ''
+
+ if ${CVS} update ; then
+ echo "PASS: test 62" >>${LOGFILE}
+ else
+ echo "FAIL: test 62" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd ..
+
+ #### FIXME: is this expected to work??? Need to investigate
+ #### and fix or remove the test.
+# directory_cmp 1dir first-dir
+#
+# if $ISDIFF ; then
+# echo "FAIL: test 63" | tee -a ${LOGFILE} # ; exit 1
+# else
+# echo "PASS: test 63" >>${LOGFILE}
+# fi
+ rm -rf 1dir first-dir
+
+ # Test the cvs history command.
+
+ # The reason that there are two patterns rather than using
+ # \(/tmp/cvs-sanity\|<remote>\) is that we are trying to
+ # make this portable. Perhaps at some point we should
+ # ditch that notion and require GNU expr (or dejagnu or....)
+ # since it seems to be so painful.
+
+ # why are there two lines at the end of the local output
+ # which don't exist in the remote output? would seem to be
+ # a CVS bug.
+ dotest basic2-64 "${testcvs} his -e -a" \
+'O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir =first-dir= /tmp/cvs-sanity/\*
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1/dir2 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1/dir2 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir == /tmp/cvs-sanity
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1 == /tmp/cvs-sanity
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1/dir2 == /tmp/cvs-sanity
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1/dir2 == /tmp/cvs-sanity
+F [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* =first-dir= /tmp/cvs-sanity/\*
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-head:A\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-tag:rtagged-by-head\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-revision:1.1\]
+O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* \[1.1\] first-dir =first-dir= /tmp/cvs-sanity/\*
+U [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == /tmp/cvs-sanity/first-dir
+U [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file7 first-dir == /tmp/cvs-sanity/first-dir' \
+'O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir =first-dir= <remote>/\*
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1/dir2 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1/dir2 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir == <remote>
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1 == <remote>
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1/dir2 == <remote>
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1/dir2 == <remote>
+F [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* =first-dir= <remote>/\*
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-head:A\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-tag:rtagged-by-head\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-revision:1.1\]
+O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* \[1.1\] first-dir =first-dir= <remote>/\*'
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -rf ${CVSROOT_DIRNAME}/second-dir
+ ;;
+
+ death) # next dive. test death support.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 65" >>${LOGFILE}
+ else
+ echo "FAIL: test 65" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+
+ # Create a directory with only dead files, to make sure CVS
+ # doesn't get confused by it.
+ mkdir subdir
+ dotest 65a0 "${testcvs} add subdir" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/subdir added to the repository'
+ cd subdir
+ echo file in subdir >sfile
+ dotest 65a1 "${testcvs} add sfile" \
+"${PROG}"' [a-z]*: scheduling file `sfile'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest 65a2 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v
+done
+Checking in sfile;
+/tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v <-- sfile
+initial revision: 1.1
+done'
+ rm sfile
+ dotest 65a3 "${testcvs} rm sfile" \
+"${PROG}"' [a-z]*: scheduling `sfile'\'' for removal
+'"${PROG}"' [a-z]*: use '\'"${PROG}"' commit'\'' to remove this file permanently'
+ dotest 65a4 "${testcvs} -q ci -m remove-it" \
+'Removing sfile;
+/tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v <-- sfile
+new revision: delete; previous revision: 1.1
+done'
+ cd ..
+ dotest 65a5 "${testcvs} -q update -P" ''
+ dotest_fail 65a6 "test -d subdir" ''
+
+ # add a file.
+ touch file1
+ if ${CVS} add file1 2>> ${LOGFILE}; then
+ echo "PASS: test 66" >>${LOGFILE}
+ else
+ echo "FAIL: test 66" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 67" >>${LOGFILE}
+ else
+ echo "FAIL: test 67" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove
+ rm file1
+ if ${CVS} rm file1 2>> ${LOGFILE}; then
+ echo "PASS: test 68" >>${LOGFILE}
+ else
+ echo "FAIL: test 68" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE} ; then
+ echo "PASS: test 69" >>${LOGFILE}
+ else
+ echo "FAIL: test 69" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail 69a0 "test -f file1" ''
+ # get the old contents of file1 back
+ if ${testcvs} update -p -r 1.1 file1 >file1 2>>${LOGFILE}; then
+ pass 69a1
+ else
+ fail 69a1
+ fi
+ dotest 69a2 "cat file1" ''
+
+ # create second file
+ touch file2
+ if ${CVS} add file1 file2 2>> ${LOGFILE}; then
+ echo "PASS: test 70" >>${LOGFILE}
+ else
+ echo "FAIL: test 70" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 71" >>${LOGFILE}
+ else
+ echo "FAIL: test 71" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # log
+ if ${CVS} log file1 >> ${LOGFILE}; then
+ echo "PASS: test 72" >>${LOGFILE}
+ else
+ echo "FAIL: test 72" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # file4 will be dead at the time of branching and stay dead.
+ echo file4 > file4
+ dotest death-file4-add "${testcvs} add file4" \
+"${PROG}"' [a-z]*: scheduling file `file4'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest death-file4-ciadd "${testcvs} -q ci -m add file4" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+initial revision: 1.1
+done'
+ rm file4
+ dotest death-file4-rm "${testcvs} remove file4" \
+"${PROG}"' [a-z]*: scheduling `file4'\'' for removal
+'"${PROG}"' [a-z]*: use '\'"${PROG}"' commit'\'' to remove this file permanently'
+ dotest death-file4-cirm "${testcvs} -q ci -m remove file4" \
+'Removing file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: delete; previous revision: 1.1
+done'
+
+ # branch1
+ if ${CVS} tag -b branch1 ; then
+ echo "PASS: test 73" >>${LOGFILE}
+ else
+ echo "FAIL: test 73" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # and move to the branch.
+ if ${CVS} update -r branch1 ; then
+ echo "PASS: test 74" >>${LOGFILE}
+ else
+ echo "FAIL: test 74" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-3 "test -f file4" ''
+
+ # add a file in the branch
+ echo line1 from branch1 >> file3
+ if ${CVS} add file3 2>> ${LOGFILE}; then
+ echo "PASS: test 75" >>${LOGFILE}
+ else
+ echo "FAIL: test 75" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 76" >>${LOGFILE}
+ else
+ echo "FAIL: test 76" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove
+ rm file3
+ if ${CVS} rm file3 2>> ${LOGFILE}; then
+ echo "PASS: test 77" >>${LOGFILE}
+ else
+ echo "FAIL: test 77" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE} ; then
+ echo "PASS: test 78" >>${LOGFILE}
+ else
+ echo "FAIL: test 78" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # add again
+ echo line1 from branch1 >> file3
+ if ${CVS} add file3 2>> ${LOGFILE}; then
+ echo "PASS: test 79" >>${LOGFILE}
+ else
+ echo "FAIL: test 79" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 80" >>${LOGFILE}
+ else
+ echo "FAIL: test 80" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # change the first file
+ echo line2 from branch1 >> file1
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 81" >>${LOGFILE}
+ else
+ echo "FAIL: test 81" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove the second
+ rm file2
+ if ${CVS} rm file2 2>> ${LOGFILE}; then
+ echo "PASS: test 82" >>${LOGFILE}
+ else
+ echo "FAIL: test 82" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE}; then
+ echo "PASS: test 83" >>${LOGFILE}
+ else
+ echo "FAIL: test 83" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # back to the trunk.
+ if ${CVS} update -A 2>> ${LOGFILE}; then
+ echo "PASS: test 84" >>${LOGFILE}
+ else
+ echo "FAIL: test 84" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-4 "test -f file4" ''
+
+ if [ -f file3 ] ; then
+ echo "FAIL: test 85" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 85" >>${LOGFILE}
+ fi
+
+ # join
+ if ${CVS} update -j branch1 >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 86" >>${LOGFILE}
+ else
+ echo "FAIL: test 86" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-5 "test -f file4" ''
+
+ if [ -f file3 ] ; then
+ echo "PASS: test 87" >>${LOGFILE}
+ else
+ echo "FAIL: test 87" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # Make sure that we joined the correct change to file1
+ if echo line2 from branch1 | cmp - file1 >/dev/null; then
+ echo 'PASS: test 87a' >>${LOGFILE}
+ else
+ echo 'FAIL: test 87a' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # update
+ if ${CVS} update ; then
+ echo "PASS: test 88" >>${LOGFILE}
+ else
+ echo "FAIL: test 88" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE} 2>&1; then
+ echo "PASS: test 89" >>${LOGFILE}
+ else
+ echo "FAIL: test 89" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove first file.
+ rm file1
+ if ${CVS} rm file1 2>> ${LOGFILE}; then
+ echo "PASS: test 90" >>${LOGFILE}
+ else
+ echo "FAIL: test 90" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE}; then
+ echo "PASS: test 91" >>${LOGFILE}
+ else
+ echo "FAIL: test 91" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if [ -f file1 ] ; then
+ echo "FAIL: test 92" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 92" >>${LOGFILE}
+ fi
+
+ # typo; try to get to the branch and fail
+ dotest_fail 92.1a "${testcvs} update -r brnach1" \
+ "${PROG}"' \[[a-z]* aborted\]: no such tag brnach1'
+ # Make sure we are still on the trunk
+ if test -f file1 ; then
+ echo "FAIL: 92.1b" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: 92.1b" >>${LOGFILE}
+ fi
+ if test -f file2 ; then
+ echo "PASS: 92.1c" >>${LOGFILE}
+ else
+ echo "FAIL: 92.1c" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # back to branch1
+ if ${CVS} update -r branch1 2>> ${LOGFILE}; then
+ echo "PASS: test 93" >>${LOGFILE}
+ else
+ echo "FAIL: test 93" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-6 "test -f file4" ''
+
+ if [ -f file1 ] ; then
+ echo "PASS: test 94" >>${LOGFILE}
+ else
+ echo "FAIL: test 94" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # and join
+ if ${CVS} update -j HEAD >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 95" >>${LOGFILE}
+ else
+ echo "FAIL: test 95" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-7 "test -f file4" ''
+
+ cd .. ; rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ branches)
+ # More branch tests, including branches off of branches
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest branches-1 "${testcvs} -q co first-dir" ''
+ cd first-dir
+ echo 1:ancest >file1
+ echo 2:ancest >file2
+ echo 3:ancest >file3
+ echo 4:trunk-1 >file4
+ dotest branches-2 "${testcvs} add file1 file2 file3 file4" \
+"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition
+'"${PROG}"' [a-z]*: scheduling file `file2'\'' for addition
+'"${PROG}"' [a-z]*: scheduling file `file3'\'' for addition
+'"${PROG}"' [a-z]*: scheduling file `file4'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add these files permanently'
+ dotest branches-3 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v
+done
+Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file2,v
+done
+Checking in file2;
+/tmp/cvs-sanity/cvsroot/first-dir/file2,v <-- file2
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file3,v
+done
+Checking in file3;
+/tmp/cvs-sanity/cvsroot/first-dir/file3,v <-- file3
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+initial revision: 1.1
+done'
+ echo 4:trunk-2 >file4
+ dotest branches-3.2 "${testcvs} -q ci -m trunk-before-branch" \
+'Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2; previous revision: 1.1
+done'
+ dotest branches-4 "${testcvs} tag -b br1" "${PROG}"' [a-z]*: Tagging \.
+T file1
+T file2
+T file3
+T file4'
+ dotest branches-5 "${testcvs} update -r br1" \
+"${PROG}"' [a-z]*: Updating \.'
+ echo 1:br1 >file1
+ echo 2:br1 >file2
+ echo 4:br1 >file4
+ dotest branches-6 "${testcvs} -q ci -m modify" \
+'Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+new revision: 1.1.2.1; previous revision: 1.1
+done
+Checking in file2;
+/tmp/cvs-sanity/cvsroot/first-dir/file2,v <-- file2
+new revision: 1.1.2.1; previous revision: 1.1
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2.2.1; previous revision: 1.2
+done'
+ dotest branches-7 "${testcvs} -q tag -b brbr" 'T file1
+T file2
+T file3
+T file4'
+ dotest branches-8 "${testcvs} -q update -r brbr" ''
+ echo 1:brbr >file1
+ echo 4:brbr >file4
+ dotest branches-9 "${testcvs} -q ci -m modify" \
+'Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+new revision: 1.1.2.1.2.1; previous revision: 1.1.2.1
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2.2.1.2.1; previous revision: 1.2.2.1
+done'
+ dotest branches-10 "cat file1 file2 file3 file4" '1:brbr
+2:br1
+3:ancest
+4:brbr'
+ dotest branches-11 "${testcvs} -q update -r br1" \
+'[UP] file1
+[UP] file4'
+ dotest branches-12 "cat file1 file2 file3 file4" '1:br1
+2:br1
+3:ancest
+4:br1'
+ echo 4:br1-2 >file4
+ dotest branches-12.2 "${testcvs} -q ci -m change-on-br1" \
+'Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2.2.2; previous revision: 1.2.2.1
+done'
+ dotest branches-13 "${testcvs} -q update -A" '[UP] file1
+[UP] file2
+[UP] file4'
+ dotest branches-14 "cat file1 file2 file3 file4" '1:ancest
+2:ancest
+3:ancest
+4:trunk-2'
+ echo 4:trunk-3 >file4
+ dotest branches-14.2 \
+ "${testcvs} -q ci -m trunk-change-after-branch" \
+'Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.3; previous revision: 1.2
+done'
+ dotest branches-14.3 "${testcvs} log file4" \
+'
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+Working file: file4
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+ brbr: 1\.2\.2\.1\.0\.2
+ br1: 1\.2\.0\.2
+keyword substitution: kv
+total revisions: 6; selected revisions: 6
+description:
+----------------------------
+revision 1\.3
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+trunk-change-after-branch
+----------------------------
+revision 1\.2
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+branches: 1\.2\.2;
+trunk-before-branch
+----------------------------
+revision 1\.1
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp;
+add-it
+----------------------------
+revision 1\.2\.2\.2
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+change-on-br1
+----------------------------
+revision 1\.2\.2\.1
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+branches: 1\.2\.2\.1\.2;
+modify
+----------------------------
+revision 1\.2\.2\.1\.2\.1
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+modify
+============================================================================='
+ dotest_status branches-14.4 1 \
+ "${testcvs} diff -c -r 1.1 -r 1.3 file4" \
+'Index: file4
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+retrieving revision 1\.1
+retrieving revision 1\.3
+diff -c -r1\.1 -r1\.3
+\*\*\* file4 [0-9/]* [0-9:]* 1\.1
+--- file4 [0-9/]* [0-9:]* 1\.3
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! 4:trunk-1
+--- 1 ----
+! 4:trunk-3'
+ dotest_status branches-14.5 1 \
+ "${testcvs} diff -c -r 1.1 -r 1.2.2.1 file4" \
+'Index: file4
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+retrieving revision 1\.1
+retrieving revision 1\.2\.2\.1
+diff -c -r1\.1 -r1\.2\.2\.1
+\*\*\* file4 [0-9/]* [0-9:]* 1\.1
+--- file4 [0-9/]* [0-9:]* 1\.2\.2\.1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! 4:trunk-1
+--- 1 ----
+! 4:br1'
+ dotest branches-15 \
+ "${testcvs} update -j 1.1.2.1 -j 1.1.2.1.2.1 file1" \
+ 'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v
+retrieving revision 1.1.2.1
+retrieving revision 1.1.2.1.2.1
+Merging differences between 1.1.2.1 and 1.1.2.1.2.1 into file1
+rcsmerge: warning: conflicts during merge'
+ dotest branches-16 "cat file1" '<<<<<<< file1
+1:ancest
+=======
+1:brbr
+>>>>>>> 1.1.2.1.2.1'
+ cd ..
+
+ if test "$keep" = yes; then
+ echo Keeping /tmp/cvs-sanity and exiting due to --keep
+ exit 0
+ fi
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -r first-dir
+ ;;
+
+ import) # test death after import
+ # import
+ mkdir import-dir ; cd import-dir
+
+ for i in 1 2 3 4 ; do
+ echo imported file"$i" > imported-file"$i"
+ done
+
+ # This directory should be on the default ignore list,
+ # so it shouldn't get imported.
+ mkdir RCS
+ echo ignore.me >RCS/ignore.me
+
+ echo 'import should not expand $''Id$' >>imported-file2
+ cp imported-file2 ../imported-file2-orig.tmp
+
+ if ${CVS} import -m first-import first-dir vendor-branch junk-1_0 ; then
+ echo "PASS: test 96" >>${LOGFILE}
+ else
+ echo "FAIL: test 96" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if cmp ../imported-file2-orig.tmp imported-file2; then
+ pass 96.5
+ else
+ fail 96.5
+ fi
+ cd ..
+
+ # co
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 97" >>${LOGFILE}
+ else
+ echo "FAIL: test 97" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+ for i in 1 2 3 4 ; do
+ if [ -f imported-file"$i" ] ; then
+ echo "PASS: test 98-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 98-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+ if test -d RCS; then
+ echo "FAIL: test 98.5" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 98.5" >>${LOGFILE}
+ fi
+
+ # remove
+ rm imported-file1
+ if ${CVS} rm imported-file1 2>> ${LOGFILE}; then
+ echo "PASS: test 99" >>${LOGFILE}
+ else
+ echo "FAIL: test 99" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # change
+ # this sleep is significant. Otherwise, on some machines, things happen so
+ # fast that the file mod times do not differ.
+ sleep 1
+ echo local-change >> imported-file2
+
+ # commit
+ if ${CVS} ci -m local-changes >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 100" >>${LOGFILE}
+ else
+ echo "FAIL: test 100" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # log
+ if ${CVS} log imported-file1 | grep '1.1.1.2 (dead)' ; then
+ echo "FAIL: test 101" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 101" >>${LOGFILE}
+ fi
+
+ # update into the vendor branch.
+ if ${CVS} update -rvendor-branch ; then
+ echo "PASS: test 102" >>${LOGFILE}
+ else
+ echo "FAIL: test 102" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove file4 on the vendor branch
+ rm imported-file4
+
+ if ${CVS} rm imported-file4 2>> ${LOGFILE}; then
+ echo "PASS: test 103" >>${LOGFILE}
+ else
+ echo "FAIL: test 103" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m vendor-removed imported-file4 >>${LOGFILE}; then
+ echo "PASS: test 104" >>${LOGFILE}
+ else
+ echo "FAIL: test 104" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # update to main line
+ if ${CVS} update -A 2>> ${LOGFILE}; then
+ echo "PASS: test 105" >>${LOGFILE}
+ else
+ echo "FAIL: test 105" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # second import - file4 deliberately unchanged
+ cd ../import-dir
+ for i in 1 2 3 ; do
+ echo rev 2 of file $i >> imported-file"$i"
+ done
+ cp imported-file2 ../imported-file2-orig.tmp
+
+ if ${CVS} import -m second-import first-dir vendor-branch junk-2_0 ; then
+ echo "PASS: test 106" >>${LOGFILE}
+ else
+ echo "FAIL: test 106" | tee -a ${LOGFILE} ; exit 1
+ fi
+ if cmp ../imported-file2-orig.tmp imported-file2; then
+ pass 106.5
+ else
+ fail 106.5
+ fi
+ cd ..
+
+ # co
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 107" >>${LOGFILE}
+ else
+ echo "FAIL: test 107" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+
+ if [ -f imported-file1 ] ; then
+ echo "FAIL: test 108" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 108" >>${LOGFILE}
+ fi
+
+ for i in 2 3 ; do
+ if [ -f imported-file"$i" ] ; then
+ echo "PASS: test 109-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 109-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+
+ # check vendor branch for file4
+ if ${CVS} update -rvendor-branch ; then
+ echo "PASS: test 110" >>${LOGFILE}
+ else
+ echo "FAIL: test 110" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if [ -f imported-file4 ] ; then
+ echo "PASS: test 111" >>${LOGFILE}
+ else
+ echo "FAIL: test 111" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # update to main line
+ if ${CVS} update -A 2>> ${LOGFILE}; then
+ echo "PASS: test 112" >>${LOGFILE}
+ else
+ echo "FAIL: test 112" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd ..
+
+ if ${CVS} co -jjunk-1_0 -jjunk-2_0 first-dir >>${LOGFILE} 2>&1; then
+ echo "PASS: test 113" >>${LOGFILE}
+ else
+ echo "FAIL: test 113" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+
+ if [ -f imported-file1 ] ; then
+ echo "FAIL: test 114" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 114" >>${LOGFILE}
+ fi
+
+ for i in 2 3 ; do
+ if [ -f imported-file"$i" ] ; then
+ echo "PASS: test 115-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 115-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+
+ if cat imported-file2 | grep '====' >> ${LOGFILE}; then
+ echo "PASS: test 116" >>${LOGFILE}
+ else
+ echo "FAIL: test 116" | tee -a ${LOGFILE} ; exit 1
+ fi
+ cd .. ; rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ rm -rf import-dir
+ ;;
+
+ new) # look for stray "no longer pertinent" messages.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 117" >>${LOGFILE}
+ else
+ echo "FAIL: test 117" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+ touch a
+
+ if ${CVS} add a 2>>${LOGFILE}; then
+ echo "PASS: test 118" >>${LOGFILE}
+ else
+ echo "FAIL: test 118" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} ci -m added >>${LOGFILE} 2>&1; then
+ echo "PASS: test 119" >>${LOGFILE}
+ else
+ echo "FAIL: test 119" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ rm a
+
+ if ${CVS} rm a 2>>${LOGFILE}; then
+ echo "PASS: test 120" >>${LOGFILE}
+ else
+ echo "FAIL: test 120" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} ci -m removed >>${LOGFILE} ; then
+ echo "PASS: test 121" >>${LOGFILE}
+ else
+ echo "FAIL: test 121" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} update -A 2>&1 | grep longer ; then
+ echo "FAIL: test 122" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 122" >>${LOGFILE}
+ fi
+
+ if ${CVS} update -rHEAD 2>&1 | grep longer ; then
+ echo "FAIL: test 123" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 123" >>${LOGFILE}
+ fi
+
+ cd .. ; rm -rf first-dir ; rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+
+ conflicts)
+ rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+
+ mkdir 1
+ cd 1
+
+ if ${CVS} co first-dir ; then
+ echo 'PASS: test 124' >>${LOGFILE}
+ else
+ echo 'FAIL: test 124' | tee -a ${LOGFILE}
+ fi
+
+ cd first-dir
+ touch a
+
+ if ${CVS} add a 2>>${LOGFILE} ; then
+ echo 'PASS: test 125' >>${LOGFILE}
+ else
+ echo 'FAIL: test 125' | tee -a ${LOGFILE}
+ fi
+
+ if ${CVS} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 126' >>${LOGFILE}
+ else
+ echo 'FAIL: test 126' | tee -a ${LOGFILE}
+ fi
+
+ cd ../..
+ mkdir 2
+ cd 2
+
+ if ${CVS} co first-dir ; then
+ echo 'PASS: test 127' >>${LOGFILE}
+ else
+ echo 'FAIL: test 127' | tee -a ${LOGFILE}
+ fi
+ cd first-dir
+ if test -f a; then
+ echo 'PASS: test 127a' >>${LOGFILE}
+ else
+ echo 'FAIL: test 127a' | tee -a ${LOGFILE}
+ fi
+
+ cd ../../1/first-dir
+ echo add a line >>a
+ mkdir dir1
+ dotest conflicts-127b "${testcvs} add dir1" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/dir1 added to the repository'
+ dotest conflicts-128 "${testcvs} -q ci -m changed" \
+'Checking in a;
+/tmp/cvs-sanity/cvsroot/first-dir/a,v <-- a
+new revision: 1.2; previous revision: 1.1
+done'
+ cd ../../2/first-dir
+ echo add a conflicting line >>a
+ dotest_fail conflicts-129 "${testcvs} -q ci -m changed" \
+"${PROG}"' [a-z]*: Up-to-date check failed for `a'\''
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!'
+ mkdir dir1
+ mkdir sdir
+ dotest conflicts-130 "${testcvs} -q update" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/a,v
+retrieving revision 1.1
+retrieving revision 1.2
+Merging differences between 1.1 and 1.2 into a
+rcsmerge: warning: conflicts during merge
+'"${PROG}"' [a-z]*: conflicts found in a
+C a
+'"${QUESTION}"' dir1
+'"${QUESTION}"' sdir' \
+''"${QUESTION}"' dir1
+'"${QUESTION}"' sdir
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/a,v
+retrieving revision 1.1
+retrieving revision 1.2
+Merging differences between 1.1 and 1.2 into a
+rcsmerge: warning: conflicts during merge
+'"${PROG}"' [a-z]*: conflicts found in a
+C a'
+
+ # Try to check in the file with the conflict markers in it.
+ if ${CVS} ci -m try 2>>${LOGFILE}; then
+ echo 'FAIL: test 131' | tee -a ${LOGFILE}
+ else
+ # Should tell us to resolve conflict first
+ echo 'PASS: test 131' >>${LOGFILE}
+ fi
+
+ echo lame attempt at resolving it >>a
+ # Try to check in the file with the conflict markers in it.
+ if ${CVS} ci -m try >>${LOGFILE} 2>&1; then
+ echo 'FAIL: test 132' | tee -a ${LOGFILE}
+ else
+ # Should tell us to resolve conflict first
+ echo 'PASS: test 132' >>${LOGFILE}
+ fi
+
+ echo resolve conflict >a
+ if ${CVS} ci -m resolved >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 133' >>${LOGFILE}
+ else
+ echo 'FAIL: test 133' | tee -a ${LOGFILE}
+ fi
+
+ # Now test that we can add a file in one working directory
+ # and have an update in another get it.
+ cd ../../1/first-dir
+ echo abc >abc
+ if ${testcvs} add abc >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 134' >>${LOGFILE}
+ else
+ echo 'FAIL: test 134' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} ci -m 'add abc' abc >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 135' >>${LOGFILE}
+ else
+ echo 'FAIL: test 135' | tee -a ${LOGFILE}
+ fi
+ cd ../../2
+ dotest conflicts-136 "${testcvs} -q update" \
+'[UP] first-dir/abc
+'"${QUESTION}"' first-dir/dir1
+'"${QUESTION}"' first-dir/sdir' \
+''"${QUESTION}"' first-dir/dir1
+'"${QUESTION}"' first-dir/sdir
+[UP] first-dir/abc'
+ dotest conflicts-137 'test -f first-dir/abc' ''
+ rmdir first-dir/dir1 first-dir/sdir
+
+ # Now test something similar, but in which the parent directory
+ # (not the directory in question) has the Entries.Static flag
+ # set.
+ cd ../1/first-dir
+ mkdir subdir
+ if ${testcvs} add subdir >>${LOGFILE}; then
+ echo 'PASS: test 138' >>${LOGFILE}
+ else
+ echo 'FAIL: test 138' | tee -a ${LOGFILE}
+ fi
+ cd ../..
+ mkdir 3
+ cd 3
+ if ${testcvs} -q co first-dir/abc first-dir/subdir \
+ >>${LOGFILE}; then
+ echo 'PASS: test 139' >>${LOGFILE}
+ else
+ echo 'FAIL: test 139' | tee -a ${LOGFILE}
+ fi
+ cd ../1/first-dir/subdir
+ echo sss >sss
+ if ${testcvs} add sss >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 140' >>${LOGFILE}
+ else
+ echo 'FAIL: test 140' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} ci -m adding sss >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 140' >>${LOGFILE}
+ else
+ echo 'FAIL: test 140' | tee -a ${LOGFILE}
+ fi
+ cd ../../../3/first-dir
+ if ${testcvs} -q update >>${LOGFILE}; then
+ echo 'PASS: test 141' >>${LOGFILE}
+ else
+ echo 'FAIL: test 141' | tee -a ${LOGFILE}
+ fi
+ if test -f subdir/sss; then
+ echo 'PASS: test 142' >>${LOGFILE}
+ else
+ echo 'FAIL: test 142' | tee -a ${LOGFILE}
+ fi
+
+ cd ../..
+ rm -rf 1 2 3 ; rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ modules)
+ rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+
+ mkdir 1
+ cd 1
+
+ if ${testcvs} -q co first-dir; then
+ echo 'PASS: test 143' >>${LOGFILE}
+ else
+ echo 'FAIL: test 143' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd first-dir
+ mkdir subdir
+ ${testcvs} add subdir >>${LOGFILE}
+ cd subdir
+
+ mkdir ssdir
+ ${testcvs} add ssdir >>${LOGFILE}
+
+ touch a b
+
+ if ${testcvs} add a b 2>>${LOGFILE} ; then
+ echo 'PASS: test 144' >>${LOGFILE}
+ else
+ echo 'FAIL: test 144' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 145' >>${LOGFILE}
+ else
+ echo 'FAIL: test 145' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ..
+ if ${testcvs} -q co CVSROOT >>${LOGFILE}; then
+ echo 'PASS: test 146' >>${LOGFILE}
+ else
+ echo 'FAIL: test 146' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # Here we test that CVS can deal with CVSROOT (whose repository
+ # is at top level) in the same directory as subdir (whose repository
+ # is a subdirectory of first-dir). TODO: Might want to check that
+ # files can actually get updated in this state.
+ if ${testcvs} -q update; then
+ echo 'PASS: test 147' >>${LOGFILE}
+ else
+ echo 'FAIL: test 147' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ echo realmodule first-dir/subdir a >>CVSROOT/modules
+ echo dirmodule first-dir/subdir >>CVSROOT/modules
+ echo namedmodule -d nameddir first-dir/subdir >>CVSROOT/modules
+ echo aliasmodule -a first-dir/subdir/a >>CVSROOT/modules
+ echo aliasnested -a first-dir/subdir/ssdir >>CVSROOT/modules
+
+ # Options must come before arguments. It is possible this should
+ # be relaxed at some point (though the result would be bizarre for
+ # -a); for now test the current behavior.
+ echo bogusalias first-dir/subdir/a -a >>CVSROOT/modules
+ if ${testcvs} ci -m 'add modules' CVSROOT/modules \
+ >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 148' >>${LOGFILE}
+ else
+ echo 'FAIL: test 148' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd ..
+ dotest 148a0 "${testcvs} co -c" 'CVSROOT CVSROOT
+aliasmodule -a first-dir/subdir/a
+aliasnested -a first-dir/subdir/ssdir
+bogusalias first-dir/subdir/a -a
+dirmodule first-dir/subdir
+namedmodule -d nameddir first-dir/subdir
+realmodule first-dir/subdir a'
+ # I don't know why aliasmodule isn't printed (I would have thought
+ # that it gets printed without the -a; although I'm not sure that
+ # printing expansions without options is useful).
+ dotest 148a1 "${testcvs} co -s" 'CVSROOT NONE CVSROOT
+bogusalias NONE first-dir/subdir/a -a
+dirmodule NONE first-dir/subdir
+namedmodule NONE first-dir/subdir
+realmodule NONE first-dir/subdir a'
+
+ # Test that real modules check out to realmodule/a, not subdir/a.
+ if ${testcvs} co realmodule >>${LOGFILE}; then
+ echo 'PASS: test 149a1' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a1' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -d realmodule && test -f realmodule/a; then
+ echo 'PASS: test 149a2' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a2' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -f realmodule/b; then
+ echo 'FAIL: test 149a3' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 149a3' >>${LOGFILE}
+ fi
+ if ${testcvs} -q co realmodule; then
+ echo 'PASS: test 149a4' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a4' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if echo "yes" | ${testcvs} release -d realmodule >>${LOGFILE} ; then
+ echo 'PASS: test 149a5' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a5' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # Now test the ability to check out a single file from a directory
+ if ${testcvs} co dirmodule/a >>${LOGFILE}; then
+ echo 'PASS: test 150c' >>${LOGFILE}
+ else
+ echo 'FAIL: test 150c' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -d dirmodule && test -f dirmodule/a; then
+ echo 'PASS: test 150d' >>${LOGFILE}
+ else
+ echo 'FAIL: test 150d' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -f dirmodule/b; then
+ echo 'FAIL: test 150e' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 150e' >>${LOGFILE}
+ fi
+ if echo "yes" | ${testcvs} release -d dirmodule >>${LOGFILE} ; then
+ echo 'PASS: test 150f' >>${LOGFILE}
+ else
+ echo 'FAIL: test 150f' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Now test the ability to correctly reject a non-existent filename.
+ # For maximum studliness we would check that an error message is
+ # being output.
+ if ${testcvs} co dirmodule/nonexist >>${LOGFILE} 2>&1; then
+ # We accept a zero exit status because it is what CVS does
+ # (Dec 95). Probably the exit status should be nonzero,
+ # however.
+ echo 'PASS: test 150g1' >>${LOGFILE}
+ else
+ echo 'PASS: test 150g1' >>${LOGFILE}
+ fi
+ # We tolerate the creation of the dirmodule directory, since that
+ # is what CVS does, not because we view that as preferable to not
+ # creating it.
+ if test -f dirmodule/a || test -f dirmodule/b; then
+ echo 'FAIL: test 150g2' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 150g2' >>${LOGFILE}
+ fi
+ rm -rf dirmodule
+
+ # Now test that a module using -d checks out to the specified
+ # directory.
+ dotest 150h1 "${testcvs} -q co namedmodule" 'U nameddir/a
+U nameddir/b'
+ if test -f nameddir/a && test -f nameddir/b; then
+ pass 150h2
+ else
+ fail 150h2
+ fi
+ echo add line >>nameddir/a
+ dotest 150h3 "${testcvs} -q co namedmodule" 'M nameddir/a'
+ rm nameddir/a
+ dotest 150h4 "${testcvs} -q co namedmodule" 'U nameddir/a'
+ if echo "yes" | ${testcvs} release -d nameddir >>${LOGFILE} ; then
+ pass 150h99
+ else
+ fail 150h99
+ fi
+
+ # Now test that alias modules check out to subdir/a, not
+ # aliasmodule/a.
+ if ${testcvs} co aliasmodule >>${LOGFILE}; then
+ echo 'PASS: test 151' >>${LOGFILE}
+ else
+ echo 'FAIL: test 151' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -d aliasmodule; then
+ echo 'FAIL: test 152' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 152' >>${LOGFILE}
+ fi
+ echo abc >>first-dir/subdir/a
+ if (${testcvs} -q co aliasmodule | tee test153.tmp) \
+ >>${LOGFILE}; then
+ echo 'PASS: test 153' >>${LOGFILE}
+ else
+ echo 'FAIL: test 153' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ echo 'M first-dir/subdir/a' >ans153.tmp
+ if cmp test153.tmp ans153.tmp; then
+ echo 'PASS: test 154' >>${LOGFILE}
+ else
+ echo 'FAIL: test 154' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ..
+ rm -rf 1
+
+ mkdir 2
+ cd 2
+ dotest modules-155a0 "${testcvs} co aliasnested" \
+"${PROG} [a-z]*: Updating first-dir/subdir/ssdir"
+ dotest modules-155a1 "test -d first-dir" ''
+ dotest modules-155a2 "test -d first-dir/subdir" ''
+ dotest modules-155a3 "test -d first-dir/subdir/ssdir" ''
+ # Test that nothing extraneous got created.
+ dotest modules-155a4 "ls -1" "first-dir"
+ cd ..
+ rm -rf 2
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ mflag)
+ for message in '' ' ' '
+ ' ' test' ; do
+ # Set up
+ mkdir a-dir; cd a-dir
+ # Test handling of -m during import
+ echo testa >>test
+ if ${testcvs} import -m "$message" a-dir A A1 >>${LOGFILE} 2>&1;then
+ echo 'PASS: test 156' >>${LOGFILE}
+ else
+ echo 'FAIL: test 156' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Must import twice since the first time uses inline code that
+ # avoids RCS call.
+ echo testb >>test
+ if ${testcvs} import -m "$message" a-dir A A2 >>${LOGFILE} 2>&1;then
+ echo 'PASS: test 157' >>${LOGFILE}
+ else
+ echo 'FAIL: test 157' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Test handling of -m during ci
+ cd ..; rm -rf a-dir;
+ if ${testcvs} co a-dir >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 158' >>${LOGFILE}
+ else
+ echo 'FAIL: test 158' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd a-dir
+ echo testc >>test
+ if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 159' >>${LOGFILE}
+ else
+ echo 'FAIL: test 159' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Test handling of -m during rm/ci
+ rm test;
+ if ${testcvs} rm test >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 160' >>${LOGFILE}
+ else
+ echo 'FAIL: test 160' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 161' >>${LOGFILE}
+ else
+ echo 'FAIL: test 161' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Clean up
+ cd ..; rm -rf a-dir ${CVSROOT_DIRNAME}/a-dir
+ done
+ ;;
+ errmsg1)
+ mkdir ${CVSROOT_DIRNAME}/1dir
+ mkdir 1
+ cd 1
+ if ${testcvs} -q co 1dir; then
+ echo 'PASS: test 162' >>${LOGFILE}
+ else
+ echo 'FAIL: test 162' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd 1dir
+ touch foo
+ if ${testcvs} add foo 2>>${LOGFILE}; then
+ echo 'PASS: test 163' >>${LOGFILE}
+ else
+ echo 'FAIL: test 163' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 164' >>${LOGFILE}
+ else
+ echo 'FAIL: test 164' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd ../..
+ mkdir 2
+ cd 2
+ if ${testcvs} -q co 1dir >>${LOGFILE}; then
+ echo 'PASS: test 165' >>${LOGFILE}
+ else
+ echo 'FAIL: test 165' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ chmod a-w 1dir
+ cd ../1/1dir
+ rm foo;
+ if ${testcvs} rm foo >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 166' >>${LOGFILE}
+ else
+ echo 'FAIL: test 166' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m removed >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 167' >>${LOGFILE}
+ else
+ echo 'FAIL: test 167' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ../../2/1dir
+ # FIXME: should be using dotest and PROG.
+ ${testcvs} -q update 2>../tst167.err
+ CVSBASE=`basename $testcvs` # Get basename of CVS executable.
+ cat <<EOF >../tst167.ans
+$CVSBASE server: warning: foo is not (any longer) pertinent
+$CVSBASE update: unable to remove ./foo: Permission denied
+EOF
+ if cmp ../tst167.ans ../tst167.err >/dev/null ||
+ ( echo "$CVSBASE [update aborted]: cannot rename file foo to CVS/,,foo: Permission denied" | cmp - ../tst167.err >/dev/null )
+ then
+ echo 'PASS: test 168' >>${LOGFILE}
+ else
+ echo 'FAIL: test 168' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ..
+ chmod u+w 1dir
+ cd ..
+ rm -rf 1 2 ${CVSROOT_DIRNAME}/1dir
+ ;;
+
+ devcom)
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ mkdir 1
+ cd 1
+ if ${testcvs} -q co first-dir >>${LOGFILE} ; then
+ echo 'PASS: test 169' >>${LOGFILE}
+ else
+ echo 'FAIL: test 169' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd first-dir
+ echo abb >abb
+ if ${testcvs} add abb 2>>${LOGFILE}; then
+ echo 'PASS: test 170' >>${LOGFILE}
+ else
+ echo 'FAIL: test 170' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 171' >>${LOGFILE}
+ else
+ echo 'FAIL: test 171' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ dotest_fail 171a0 "${testcvs} watch" "Usage${DOTSTAR}"
+ if ${testcvs} watch on; then
+ echo 'PASS: test 172' >>${LOGFILE}
+ else
+ echo 'FAIL: test 172' | tee -a ${LOGFILE}
+ fi
+ echo abc >abc
+ if ${testcvs} add abc 2>>${LOGFILE}; then
+ echo 'PASS: test 173' >>${LOGFILE}
+ else
+ echo 'FAIL: test 173' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 174' >>${LOGFILE}
+ else
+ echo 'FAIL: test 174' | tee -a ${LOGFILE}
+ fi
+
+ cd ../..
+ mkdir 2
+ cd 2
+
+ if ${testcvs} -q co first-dir >>${LOGFILE}; then
+ echo 'PASS: test 175' >>${LOGFILE}
+ else
+ echo 'FAIL: test 175' | tee -a ${LOGFILE}
+ fi
+ cd first-dir
+ if test -w abb; then
+ echo 'FAIL: test 176' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 176' >>${LOGFILE}
+ fi
+ if test -w abc; then
+ echo 'FAIL: test 177' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 177' >>${LOGFILE}
+ fi
+
+ if ${testcvs} editors >../ans178.tmp; then
+ echo 'PASS: test 178' >>${LOGFILE}
+ else
+ echo 'FAIL: test 178' | tee -a ${LOGFILE}
+ fi
+ cat ../ans178.tmp >>${LOGFILE}
+ if test -s ../ans178.tmp; then
+ echo 'FAIL: test 178a' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 178a' >>${LOGFILE}
+ fi
+
+ if ${testcvs} edit abb; then
+ echo 'PASS: test 179' >>${LOGFILE}
+ else
+ echo 'FAIL: test 179' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ if ${testcvs} editors >../ans180.tmp; then
+ echo 'PASS: test 180' >>${LOGFILE}
+ else
+ echo 'FAIL: test 180' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cat ../ans180.tmp >>${LOGFILE}
+ if test -s ../ans180.tmp; then
+ echo 'PASS: test 181' >>${LOGFILE}
+ else
+ echo 'FAIL: test 181' | tee -a ${LOGFILE}
+ fi
+
+ echo aaaa >>abb
+ if ${testcvs} ci -m modify abb >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 182' >>${LOGFILE}
+ else
+ echo 'FAIL: test 182' | tee -a ${LOGFILE}
+ fi
+ # Unedit of a file not being edited should be a noop.
+ dotest 182.5 "${testcvs} unedit abb" ''
+
+ if ${testcvs} editors >../ans183.tmp; then
+ echo 'PASS: test 183' >>${LOGFILE}
+ else
+ echo 'FAIL: test 183' | tee -a ${LOGFILE}
+ fi
+ cat ../ans183.tmp >>${LOGFILE}
+ if test -s ../ans183.tmp; then
+ echo 'FAIL: test 184' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 184' >>${LOGFILE}
+ fi
+
+ if test -w abb; then
+ echo 'FAIL: test 185' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 185' >>${LOGFILE}
+ fi
+
+ if ${testcvs} edit abc; then
+ echo 'PASS: test 186a1' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a1' | tee -a ${LOGFILE}
+ fi
+ # Unedit of an unmodified file.
+ if ${testcvs} unedit abc; then
+ echo 'PASS: test 186a2' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a2' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} edit abc; then
+ echo 'PASS: test 186a3' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a3' | tee -a ${LOGFILE}
+ fi
+ echo changedabc >abc
+ # Try to unedit a modified file; cvs should ask for confirmation
+ if (echo no | ${testcvs} unedit abc) >>${LOGFILE}; then
+ echo 'PASS: test 186a4' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a4' | tee -a ${LOGFILE}
+ fi
+ if echo changedabc | cmp - abc; then
+ echo 'PASS: test 186a5' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a5' | tee -a ${LOGFILE}
+ fi
+ # OK, now confirm the unedit
+ if (echo yes | ${testcvs} unedit abc) >>${LOGFILE}; then
+ echo 'PASS: test 186a6' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a6' | tee -a ${LOGFILE}
+ fi
+ if echo abc | cmp - abc; then
+ echo 'PASS: test 186a7' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a7' | tee -a ${LOGFILE}
+ fi
+
+ dotest devcom-a0 "${testcvs} watchers" ''
+ dotest devcom-a1 "${testcvs} watch add" ''
+ dotest devcom-a2 "${testcvs} watchers" \
+'abb [a-z0-9]* edit unedit commit
+abc [a-z0-9]* edit unedit commit'
+ dotest devcom-a3 "${testcvs} watch remove -a unedit abb" ''
+ dotest devcom-a4 "${testcvs} watchers abb" \
+'abb [a-z0-9]* edit commit'
+
+ cd ../..
+ rm -rf 1 2 ${CVSROOT_DIRNAME}/first-dir
+ ;;
+
+ ignore)
+ dotest 187a1 "${testcvs} -q co CVSROOT" 'U CVSROOT/modules'
+ cd CVSROOT
+ echo rootig.c >cvsignore
+ dotest 187a2 "${testcvs} add cvsignore" "${PROG}"' [a-z]*: scheduling file `cvsignore'"'"' for addition
+'"${PROG}"' [a-z]*: use '"'"'cvs commit'"'"' to add this file permanently'
+
+ # As of Jan 96, local CVS prints "Examining ." and remote doesn't.
+ # Accept either.
+ dotest 187a3 " ${testcvs} ci -m added" \
+"${DOTSTAR}"'CS file: /tmp/cvs-sanity/cvsroot/CVSROOT/cvsignore,v
+done
+Checking in cvsignore;
+/tmp/cvs-sanity/cvsroot/CVSROOT/cvsignore,v <-- cvsignore
+initial revision: 1.1
+done
+'"${PROG}"' [a-z]*: Rebuilding administrative file database'
+
+ cd ..
+ if echo "yes" | ${testcvs} release -d CVSROOT >>${LOGFILE} ; then
+ echo 'PASS: test 187a4' >>${LOGFILE}
+ else
+ echo 'FAIL: test 187a4' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # CVS looks at the home dir from getpwuid, not HOME (is that correct
+ # behavior?), so this is hard to test and we won't try.
+ # echo foobar.c >${HOME}/.cvsignore
+ CVSIGNORE=envig.c; export CVSIGNORE
+ mkdir dir-to-import
+ cd dir-to-import
+ touch foobar.c bar.c rootig.c defig.o envig.c optig.c
+ # We really should allow the files to be listed in any order.
+ # But we (kludgily) just list the orders which have been observed.
+ dotest 188a "${testcvs} import -m m -I optig.c first-dir tag1 tag2" \
+ 'N first-dir/foobar.c
+N first-dir/bar.c
+I first-dir/rootig.c
+I first-dir/defig.o
+I first-dir/envig.c
+I first-dir/optig.c
+
+No conflicts created by this import' 'I first-dir/defig.o
+I first-dir/envig.c
+I first-dir/optig.c
+N first-dir/foobar.c
+N first-dir/bar.c
+I first-dir/rootig.c
+
+No conflicts created by this import'
+ dotest 188b "${testcvs} import -m m -I ! second-dir tag3 tag4" \
+ 'N second-dir/foobar.c
+N second-dir/bar.c
+N second-dir/rootig.c
+N second-dir/defig.o
+N second-dir/envig.c
+N second-dir/optig.c
+
+No conflicts created by this import'
+ cd ..
+ rm -rf dir-to-import
+
+ dotest 189a "${testcvs} -q co second-dir" \
+'U second-dir/bar.c
+U second-dir/defig.o
+U second-dir/envig.c
+U second-dir/foobar.c
+U second-dir/optig.c
+U second-dir/rootig.c'
+ rm -rf second-dir
+ dotest 189b "${testcvs} -q co first-dir" 'U first-dir/bar.c
+U first-dir/foobar.c'
+ cd first-dir
+ touch rootig.c defig.o envig.c optig.c notig.c
+ dotest 189c "${testcvs} -q update -I optig.c" "${QUESTION} notig.c"
+ # The fact that CVS requires us to specify -I CVS here strikes me
+ # as a bug.
+ dotest 189d "${testcvs} -q update -I ! -I CVS" "${QUESTION} rootig.c
+${QUESTION} defig.o
+${QUESTION} envig.c
+${QUESTION} optig.c
+${QUESTION} notig.c"
+ cd ..
+ rm -rf first-dir
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir ${CVSROOT_DIRNAME}/second-dir
+ ;;
+
+ binfiles)
+ # Test cvs's ability to handle binary files.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ mkdir 1; cd 1
+ dotest binfiles-1 "${testcvs} -q co first-dir" ''
+ awk 'BEGIN { printf "%c%c%c%c%c%c", 2, 10, 137, 0, 13, 10 }' \
+ </dev/null >binfile.dat
+ cat binfile.dat binfile.dat >binfile2.dat
+ cd first-dir
+ cp ../binfile.dat binfile
+ dotest binfiles-2 "${testcvs} add -kb binfile" \
+"${PROG}"' [a-z]*: scheduling file `binfile'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest binfiles-3 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+done
+Checking in binfile;
+/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile
+initial revision: 1.1
+done'
+ cd ../..
+ mkdir 2; cd 2
+ dotest binfiles-4 "${testcvs} -q co first-dir" 'U first-dir/binfile'
+ cd first-dir
+ dotest binfiles-5 "cmp ../../1/binfile.dat binfile" ''
+ # Testing that sticky options is -kb is the closest thing we have
+ # to testing that binary files work right on non-unix machines
+ # (until there is automated testing for such machines, of course).
+ dotest binfiles-5.5 "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.1.*
+ Repository revision: 1\.1 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kb'
+ cp ../../1/binfile2.dat binfile
+ dotest binfiles-6 "${testcvs} -q ci -m modify-it" \
+'Checking in binfile;
+/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile
+new revision: 1.2; previous revision: 1.1
+done'
+ cd ../../1/first-dir
+ dotest binfiles-7 "${testcvs} -q update" '[UP] binfile'
+ dotest binfiles-8 "cmp ../binfile2.dat binfile" ''
+
+ # The bugs which these test for are apparently not fixed for remote.
+ if test "$remote" = no; then
+ dotest binfiles-9 "${testcvs} -q update -A" ''
+ dotest binfiles-10 "${testcvs} -q update -kk" '[UP] binfile'
+ dotest binfiles-11 "${testcvs} -q update" ''
+ dotest binfiles-12 "${testcvs} -q update -A" '[UP] binfile'
+ dotest binfiles-13 "${testcvs} -q update -A" ''
+ fi
+
+ cd ../../2/first-dir
+ echo 'this file is $''RCSfile$' >binfile
+ dotest binfiles-14a "${testcvs} -q ci -m modify-it" \
+'Checking in binfile;
+/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile
+new revision: 1.3; previous revision: 1.2
+done'
+ dotest binfiles-14b "cat binfile" 'this file is $''RCSfile$'
+ # See binfiles-5.5 for discussion of -kb.
+ dotest binfiles-14c "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.3.*
+ Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kb'
+ dotest binfiles-14d "${testcvs} admin -kv binfile" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+done'
+ # cvs admin doesn't change the checked-out file or its sticky
+ # kopts. There probably should be a way which does (but
+ # what if the file is modified? And do we try to version
+ # control the kopt setting?)
+ dotest binfiles-14e "cat binfile" 'this file is $''RCSfile$'
+ dotest binfiles-14f "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.3.*
+ Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kb'
+ dotest binfiles-14g "${testcvs} -q update -A" '[UP] binfile'
+ dotest binfiles-14h "cat binfile" 'this file is binfile,v'
+ dotest binfiles-14i "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.3.*
+ Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kv'
+
+ cd ../..
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -r 1 2
+ ;;
+ info)
+ # Test CVS's ability to handle *info files.
+ dotest info-1 "${testcvs} -q co CVSROOT" "[UP] CVSROOT${DOTSTAR}"
+ cd CVSROOT
+ echo "ALL sh -c \"echo x\${=MYENV}\${=OTHER}y\${=ZEE}=\$USER=\$CVSROOT= >>$TESTDIR/testlog; cat >/dev/null\"" > loginfo
+ dotest info-2 "${testcvs} add loginfo" \
+"${PROG}"' [a-z]*: scheduling file `loginfo'"'"' for addition
+'"${PROG}"' [a-z]*: use '"'"'cvs commit'"'"' to add this file permanently'
+ dotest info-3 "${testcvs} -q ci -m new-loginfo" \
+'RCS file: /tmp/cvs-sanity/cvsroot/CVSROOT/loginfo,v
+done
+Checking in loginfo;
+/tmp/cvs-sanity/cvsroot/CVSROOT/loginfo,v <-- loginfo
+initial revision: 1.1
+done
+'"${PROG}"' [a-z]*: Rebuilding administrative file database'
+ cd ..
+ if echo "yes" | ${testcvs} release -d CVSROOT >>${LOGFILE} ; then
+ pass info-4
+ else
+ fail info-4
+ fi
+
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest info-5 "${testcvs} -q co first-dir" ''
+ cd first-dir
+ touch file1
+ dotest info-6 "${testcvs} add file1" \
+"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ echo "cvs -s OTHER=not-this -s MYENV=env-" >>$HOME/.cvsrc
+ dotest info-6a "${testcvs} -q -s OTHER=value ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v
+done
+Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+initial revision: 1.1
+done
+'"${PROG}"' [a-z]*: loginfo:1: no such user variable ${=ZEE}'
+ echo line1 >>file1
+ dotest info-7 "${testcvs} -q -s OTHER=value -s ZEE=z ci -m mod-it" \
+'Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+new revision: 1.2; previous revision: 1.1
+done'
+ cd ..
+ if echo "yes" | ${testcvs} release -d first-dir >>${LOGFILE} ; then
+ pass info-8
+ else
+ fail info-8
+ fi
+ dotest info-9 "cat $TESTDIR/testlog" 'xenv-valueyz=[a-z@][a-z@]*=/tmp/cvs-sanity/cvsroot='
+
+ # I think this might be doable with cvs remove, or at least
+ # checking in a version with only comments, but I'm too lazy
+ # at the moment. Blow it away.
+ rm -f ${CVSROOT_DIRNAME}/CVSROOT/loginfo*
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ *)
+ echo $what is not the name of a test -- ignored
+ ;;
+ esac
+done
+
+echo "OK, all tests completed."
+
+# TODO:
+# * Test `cvs admin'.
+# * Test `cvs update -d foo' (where foo does not exist).
+# * Test `cvs update foo bar' (where foo and bar are both from the same
+# repository). Suppose one is a branch--make sure that both directories
+# get updated with the respective correct thing.
+# * `cvs update ../foo'. Also ../../foo ./../foo foo/../../bar /foo/bar
+# foo/.././../bar foo/../bar etc.
+# * Test all flags in modules file.
+# Test that ciprog gets run both on checkin in that directory, or a
+# higher-level checkin which recurses into it.
+# * Test that $ followed by "Header" followed by $ gets expanded on checkin.
+# * Test operations on a directory that contains other directories but has
+# no files of its own.
+# * -t global option
+# * cvs rm followed by cvs add or vice versa (with no checkin in between).
+# * cvs rm twice (should be a nice error message).
+# * -P option to checkout--(a) refrains from checking out new empty dirs,
+# (b) prunes empty dirs already there.
+# * Test that cvs -d `hostname`:/tmp/cvs-sanity/non/existent co foo
+# gives an appropriate error (e.g.
+# Cannot access /tmp/cvs-sanity/non-existent/CVSROOT
+# No such file or directory).
+# * Test ability to send notifications in response to watches. (currently
+# hard to test because CVS doesn't send notifications if username is the
+# same).
+# * Test that remote edit and/or unedit works when disconnected from
+# server (e.g. set CVS_SERVER to "foobar").
+# End of TODO list.
+
+# Remove the test directory, but first change out of it.
+cd /tmp
+rm -rf ${TESTDIR}
+
+# end of sanity.sh
diff --git a/contrib/cvs/src/scramble.c b/contrib/cvs/src/scramble.c
new file mode 100644
index 0000000..07094a6
--- /dev/null
+++ b/contrib/cvs/src/scramble.c
@@ -0,0 +1,246 @@
+/*
+ * 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);
+
+ s[0] = 'A'; /* Scramble (TM) version prefix. */
+ 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/contrib/cvs/src/server.c b/contrib/cvs/src/server.c
new file mode 100644
index 0000000..e92445b
--- /dev/null
+++ b/contrib/cvs/src/server.c
@@ -0,0 +1,4642 @@
+#include <assert.h>
+#include "cvs.h"
+#include "watch.h"
+#include "edit.h"
+#include "fileattr.h"
+
+#ifdef SERVER_SUPPORT
+
+/* for select */
+#include <sys/types.h>
+#ifdef HAVE_SYS_BSDTYPES_H
+#include <sys/bsdtypes.h>
+#endif
+#include <sys/time.h>
+
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifndef O_NONBLOCK
+#define O_NONBLOCK O_NDELAY
+#endif
+
+#ifdef AUTH_SERVER_SUPPORT
+/* For initgroups(). */
+#if HAVE_INITGROUPS
+#include <grp.h>
+#endif /* HAVE_INITGROUPS */
+#endif /* AUTH_SERVER_SUPPORT */
+
+
+/* Functions which the server calls. */
+int add PROTO((int argc, char **argv));
+int admin PROTO((int argc, char **argv));
+int checkout PROTO((int argc, char **argv));
+int commit PROTO((int argc, char **argv));
+int diff PROTO((int argc, char **argv));
+int history PROTO((int argc, char **argv));
+int import PROTO((int argc, char **argv));
+int cvslog PROTO((int argc, char **argv));
+int patch PROTO((int argc, char **argv));
+int release PROTO((int argc, char **argv));
+int cvsremove PROTO((int argc, char **argv));
+int rtag PROTO((int argc, char **argv));
+int status PROTO((int argc, char **argv));
+int tag PROTO((int argc, char **argv));
+int update PROTO((int argc, char **argv));
+
+
+/*
+ * 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;
+
+/* Nonzero if we should keep the temp directory around after we exit. */
+static int dont_delete_temp;
+
+static char no_mem_error;
+#define NO_MEM_ERROR (&no_mem_error)
+
+static void server_write_entries PROTO((void));
+
+/*
+ * Read a line from the stream "instream" without command line editing.
+ *
+ * Action is compatible with "readline", e.g. space for the result is
+ * malloc'd and should be freed by the caller.
+ *
+ * A NULL return means end of file. A return of NO_MEM_ERROR means
+ * that we are out of memory.
+ */
+static char *read_line PROTO((FILE *));
+
+static char *
+read_line (stream)
+ FILE *stream;
+{
+ int c;
+ char *result;
+ int input_index = 0;
+ int result_size = 80;
+
+ fflush (stdout);
+ result = (char *) malloc (result_size);
+ if (result == NULL)
+ return NO_MEM_ERROR;
+
+ while (1)
+ {
+ c = fgetc (stream);
+
+ if (c == EOF)
+ {
+ free (result);
+ return NULL;
+ }
+
+ if (c == '\n')
+ break;
+
+ result[input_index++] = c;
+ while (input_index >= result_size)
+ {
+ result_size *= 2;
+ result = (char *) realloc (result, result_size);
+ if (result == NULL)
+ return NO_MEM_ERROR;
+ }
+ }
+
+ result[input_index++] = '\0';
+ return result;
+}
+
+/*
+ * 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;
+
+ /*
+ * 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 (CVS_MKDIR (q, 0777) < 0)
+ {
+ if (errno != EEXIST
+ && (errno != EACCES || !isdir(q)))
+ {
+ retval = errno;
+ goto done;
+ }
+ }
+ ++p;
+ }
+ else
+ {
+ if (CVS_MKDIR (dir, 0777) < 0)
+ retval = errno;
+ else
+ retval = 0;
+ 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.
+ */
+static void
+print_error (status)
+ int status;
+{
+ char *msg;
+ printf ("error ");
+ msg = strerror (status);
+ if (msg)
+ printf ("%s", msg);
+ printf ("\n");
+}
+
+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. */
+static int
+print_pending_error ()
+{
+ if (pending_error_text)
+ {
+ printf ("%s\n", pending_error_text);
+ if (pending_error)
+ print_error (pending_error);
+ else
+ printf ("error \n");
+ 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)
+
+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)
+ {
+ printf ("E response `%s' not supported by client\nerror \n",
+ rs->name);
+ exit (EXIT_FAILURE);
+ }
+ else if (rs->status == rs_optional)
+ rs->status = rs_not_supported;
+ }
+}
+
+static int use_dir_and_repos = 0;
+
+static void
+serve_root (arg)
+ char *arg;
+{
+ char *env;
+ extern char *CVSroot;
+ char path[PATH_MAX];
+ int save_errno;
+
+ if (error_pending()) return;
+
+ (void) sprintf (path, "%s/%s", arg, 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;
+ }
+
+ CVSroot = malloc (strlen (arg) + 1);
+ if (CVSroot == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (CVSroot, arg);
+#ifdef HAVE_PUTENV
+ env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
+ if (env == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ (void) sprintf (env, "%s=%s", CVSROOT_ENV, arg);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+#endif
+}
+
+/*
+ * 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");
+ free (server_temp_dir);
+ server_temp_dir = p;
+}
+
+static char *dir_name;
+
+static void
+dirswitch (dir, repos)
+ char *dir;
+ char *repos;
+{
+ int status;
+ FILE *f;
+
+ server_write_entries ();
+
+ if (error_pending()) return;
+
+ if (dir_name != NULL)
+ free (dir_name);
+
+ dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 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;
+ pending_error_text = malloc (80 + strlen(dir_name));
+ sprintf(pending_error_text, "E cannot mkdir %s", dir_name);
+ return;
+ }
+ if (chdir (dir_name) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (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)
+ {
+ if (errno == EEXIST)
+ /* Don't create the files again. */
+ return;
+ pending_error = errno;
+ return;
+ }
+ f = fopen (CVSADM_REP, "w");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ return;
+ }
+ if (fprintf (f, "%s\n", repos) < 0)
+ {
+ pending_error = errno;
+ fclose (f);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ return;
+ }
+ f = fopen (CVSADM_ENT, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENT));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENT));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
+ return;
+ }
+}
+
+static void
+serve_repository (arg)
+ char *arg;
+{
+ dirswitch (arg + 1, arg);
+}
+
+static void
+serve_directory (arg)
+ char *arg;
+{
+ char *repos;
+ use_dir_and_repos = 1;
+ repos = read_line (stdin);
+ if (repos == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ 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 = errno;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ }
+ else if (repos == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ }
+ else
+ {
+ dirswitch (arg, repos);
+ free (repos);
+ }
+}
+
+static void
+serve_static_directory (arg)
+ char *arg;
+{
+ FILE *f;
+ f = fopen (CVSADM_ENTSTAT, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
+ return;
+ }
+}
+
+static void
+serve_sticky (arg)
+ char *arg;
+{
+ FILE *f;
+ f = fopen (CVSADM_TAG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (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;
+ pending_error_text = malloc (80 + strlen(CVSADM_TAG));
+ sprintf(pending_error_text, "E cannot write to %s", CVSADM_TAG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_TAG));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_TAG);
+ return;
+ }
+}
+
+/*
+ * Read SIZE bytes from stdin, 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;
+{
+ char buf[16*1024], *bufp;
+ int toread, nread, nwrote;
+ while (size > 0)
+ {
+ toread = sizeof (buf);
+ if (toread > size)
+ toread = size;
+
+ nread = fread (buf, 1, toread, stdin);
+ if (nread <= 0)
+ {
+ if (feof (stdin))
+ {
+ pending_error_text = malloc (80);
+ if (pending_error_text)
+ {
+ sprintf (pending_error_text,
+ "E premature end of file from client");
+ pending_error = 0;
+ }
+ else
+ pending_error = ENOMEM;
+ }
+ else if (ferror (stdin))
+ {
+ pending_error_text = malloc (40);
+ if (pending_error_text)
+ sprintf (pending_error_text,
+ "E error reading from client");
+ pending_error = errno;
+ }
+ else
+ {
+ pending_error_text = malloc (40);
+ if (pending_error_text)
+ sprintf (pending_error_text,
+ "E short read from client");
+ pending_error = 0;
+ }
+ return;
+ }
+ size -= nread;
+ bufp = buf;
+ while (nread)
+ {
+ nwrote = write (file, bufp, nread);
+ if (nwrote < 0)
+ {
+ pending_error_text = malloc (40);
+ if (pending_error_text)
+ sprintf (pending_error_text, "E unable to write");
+ pending_error = errno;
+ return;
+ }
+ nread -= nwrote;
+ bufp += 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;
+ pid_t gzip_pid = 0;
+ int gzip_status;
+
+ /* Write the file. */
+ fd = 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;
+ }
+
+ /*
+ * FIXME: This doesn't do anything reasonable with gunzip's stderr, which
+ * means that if gunzip writes to stderr, it will cause all manner of
+ * protocol violations.
+ */
+ if (gzipped)
+ fd = filter_through_gunzip (fd, 0, &gzip_pid);
+
+ 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 */
+ }
+
+ 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;
+ if (gzip_pid)
+ waitpid (gzip_pid, (int *) 0, 0);
+ return;
+ }
+
+ if (gzip_pid)
+ {
+ if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid)
+ error (1, errno, "waiting for gunzip process %ld",
+ (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, 0, "gunzip exited %d", gzip_status);
+ }
+}
+
+static void
+serve_modified (arg)
+ char *arg;
+{
+ int size;
+ char *size_text;
+ char *mode_text;
+
+ int gzipped = 0;
+
+ if (error_pending ()) return;
+
+ mode_text = read_line (stdin);
+ if (mode_text == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ 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 = errno;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ return;
+ }
+ else if (mode_text == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ size_text = read_line (stdin);
+ if (size_text == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ 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;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ return;
+ }
+ else if (size_text == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ if (size_text[0] == 'z')
+ {
+ gzipped = 1;
+ size = atoi (size_text + 1);
+ }
+ else
+ size = atoi (size_text);
+ free (size_text);
+
+ if (size >= 0)
+ {
+ receive_file (size, arg, gzipped);
+ if (error_pending ()) return;
+ }
+
+ {
+ int status = change_mode (arg, mode_text);
+ 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;
+ }
+ }
+}
+
+#endif /* SERVER_SUPPORT */
+
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+
+int use_unchanged = 0;
+
+#endif
+#ifdef SERVER_SUPPORT
+
+static void
+serve_enable_unchanged (arg)
+ char *arg;
+{
+ use_unchanged = 1;
+}
+
+static void
+serve_lost (arg)
+ char *arg;
+{
+ if (use_unchanged)
+ {
+ /* A missing file already indicates it is nonexistent. */
+ return;
+ }
+ else
+ {
+ struct utimbuf ut;
+ int fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0 || close (fd) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(arg));
+ sprintf(pending_error_text, "E cannot open %s", arg);
+ return;
+ }
+ /*
+ * Set the times to the beginning of the epoch to tell time_stamp()
+ * that the file was lost.
+ */
+ ut.actime = 0;
+ ut.modtime = 0;
+ if (utime (arg, &ut) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(arg));
+ sprintf(pending_error_text, "E cannot utime %s", arg);
+ return;
+ }
+ }
+}
+
+struct an_entry {
+ struct an_entry *next;
+ char *entry;
+};
+
+static struct an_entry *entries;
+
+static void
+serve_unchanged (arg)
+ char *arg;
+{
+ if (error_pending ())
+ return;
+ if (!use_unchanged)
+ {
+ /* A missing file already indicates it is unchanged. */
+ return;
+ }
+ else
+ {
+ struct an_entry *p;
+ char *name;
+ char *cp;
+ char *timefield;
+
+ /* 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_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
+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 ())
+ {
+ f = fopen (CVSADM_ENT, "w");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (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;
+ pending_error_text = malloc (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;
+ pending_error_text = malloc (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;
+
+ if (error_pending ()) 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);
+
+ data = read_line (stdin);
+ if (data == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ 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 = errno;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ }
+ else if (data == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ }
+ else
+ {
+ char *cp;
+
+ 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 (40);
+ 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;
+ List *list;
+ Node *node;
+ int status;
+
+ while (notify_list != NULL)
+ {
+ if (chdir (notify_list->dir) < 0)
+ {
+ error (0, errno, "cannot change to %s", notify_list->dir);
+ return -1;
+ }
+ repos = Name_Repository (NULL, NULL);
+
+ /* Now writelock. */
+ list = getlist ();
+ node = getnode ();
+ node->type = LOCK;
+ node->key = xstrdup (repos);
+ status = addnode (list, node);
+ assert (status == 0);
+ Writer_Lock (list);
+
+ fileattr_startdir (repos);
+
+ notify_do (*notify_list->type, notify_list->filename, getcaller(),
+ notify_list->val, notify_list->watches, repos);
+
+ printf ("Notified ");
+ if (use_dir_and_repos)
+ {
+ char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
+ if (dir[0] == '\0')
+ fputs (".", stdout);
+ else
+ fputs (dir, stdout);
+ fputs ("/\n", stdout);
+ }
+ fputs (repos, stdout);
+ fputs ("/", stdout);
+ fputs (notify_list->filename, stdout);
+ fputs ("\n", stdout);
+
+ 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 ();
+
+ /* Remove the writelock. */
+ Lock_Cleanup ();
+ dellist (&list);
+ }
+ /* do_cvs_command writes to stdout via write(), not stdio, so better
+ flush out the buffer. */
+ fflush (stdout);
+ 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:
+ pending_error_text = malloc (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);
+}
+
+/*
+ * We must read data from a child process and send it across the
+ * network. We do not want to block on writing to the network, so we
+ * store the data from the child process in memory. A BUFFER
+ * structure holds the status of one communication, and uses a linked
+ * list of buffer_data structures to hold data.
+ */
+
+struct buffer
+{
+ /* Data. */
+ struct buffer_data *data;
+
+ /* Last buffer on data chain. */
+ struct buffer_data *last;
+
+ /* File descriptor to write to or read from. */
+ int fd;
+
+ /* Nonzero if this is an output buffer (sanity check). */
+ int output;
+
+ /* Nonzero if the file descriptor is in nonblocking mode. */
+ int nonblocking;
+
+ /* Function to call if we can't allocate memory. */
+ void (*memory_error) PROTO((struct buffer *));
+};
+
+/* Data is stored in lists of these structures. */
+
+struct buffer_data
+{
+ /* Next buffer in linked list. */
+ struct buffer_data *next;
+
+ /*
+ * A pointer into the data area pointed to by the text field. This
+ * is where to find data that has not yet been written out.
+ */
+ char *bufp;
+
+ /* The number of data bytes found at BUFP. */
+ int size;
+
+ /*
+ * Actual buffer. This never changes after the structure is
+ * allocated. The buffer is BUFFER_DATA_SIZE bytes.
+ */
+ char *text;
+};
+
+/* The size we allocate for each buffer_data structure. */
+#define BUFFER_DATA_SIZE (4096)
+
+#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 */
+#endif /* SERVER_FLOWCONTROL */
+
+/* Linked list of available buffer_data structures. */
+static struct buffer_data *free_buffer_data;
+
+static void allocate_buffer_datas PROTO((void));
+static inline struct buffer_data *get_buffer_data PROTO((void));
+static int buf_empty_p PROTO((struct buffer *));
+static void buf_output PROTO((struct buffer *, const char *, int));
+static void buf_output0 PROTO((struct buffer *, const char *));
+static inline void buf_append_char PROTO((struct buffer *, int));
+static int buf_send_output PROTO((struct buffer *));
+static int set_nonblock PROTO((struct buffer *));
+static int set_block PROTO((struct buffer *));
+static int buf_send_counted PROTO((struct buffer *));
+static inline void buf_append_data PROTO((struct buffer *,
+ struct buffer_data *,
+ struct buffer_data *));
+static int buf_read_file PROTO((FILE *, long, struct buffer_data **,
+ struct buffer_data **));
+static int buf_input_data PROTO((struct buffer *, int *));
+static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int));
+static int buf_copy_counted PROTO((struct buffer *, struct buffer *));
+
+#ifdef SERVER_FLOWCONTROL
+static int buf_count_mem PROTO((struct buffer *));
+static int set_nonblock_fd PROTO((int));
+#endif /* SERVER_FLOWCONTROL */
+
+/* Allocate more buffer_data structures. */
+
+static void
+allocate_buffer_datas ()
+{
+ struct buffer_data *alc;
+ char *space;
+ int i;
+
+ /* Allocate buffer_data structures in blocks of 16. */
+#define ALLOC_COUNT (16)
+
+ alc = ((struct buffer_data *)
+ malloc (ALLOC_COUNT * sizeof (struct buffer_data)));
+ space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE);
+ if (alc == NULL || space == NULL)
+ return;
+ for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE)
+ {
+ alc->next = free_buffer_data;
+ free_buffer_data = alc;
+ alc->text = space;
+ }
+}
+
+/* Get a new buffer_data structure. */
+
+static inline struct buffer_data *
+get_buffer_data ()
+{
+ struct buffer_data *ret;
+
+ if (free_buffer_data == NULL)
+ {
+ allocate_buffer_datas ();
+ if (free_buffer_data == NULL)
+ return NULL;
+ }
+
+ ret = free_buffer_data;
+ free_buffer_data = ret->next;
+ return ret;
+}
+
+/* See whether a buffer is empty. */
+
+static int
+buf_empty_p (buf)
+ struct buffer *buf;
+{
+ struct buffer_data *data;
+
+ for (data = buf->data; data != NULL; data = data->next)
+ if (data->size > 0)
+ return 0;
+ return 1;
+}
+
+#ifdef SERVER_FLOWCONTROL
+/*
+ * Count how much data is stored in the buffer..
+ * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE.
+ */
+
+static int
+buf_count_mem (buf)
+ struct buffer *buf;
+{
+ struct buffer_data *data;
+ int mem = 0;
+
+ for (data = buf->data; data != NULL; data = data->next)
+ mem += BUFFER_DATA_SIZE;
+
+ return mem;
+}
+#endif /* SERVER_FLOWCONTROL */
+
+/* Add data DATA of length LEN to BUF. */
+
+static void
+buf_output (buf, data, len)
+ struct buffer *buf;
+ const char *data;
+ int len;
+{
+ if (buf->data != NULL
+ && (((buf->last->text + BUFFER_DATA_SIZE)
+ - (buf->last->bufp + buf->last->size))
+ >= len))
+ {
+ memcpy (buf->last->bufp + buf->last->size, data, len);
+ buf->last->size += len;
+ return;
+ }
+
+ while (1)
+ {
+ struct buffer_data *newdata;
+
+ newdata = get_buffer_data ();
+ if (newdata == NULL)
+ {
+ (*buf->memory_error) (buf);
+ return;
+ }
+
+ if (buf->data == NULL)
+ buf->data = newdata;
+ else
+ buf->last->next = newdata;
+ newdata->next = NULL;
+ buf->last = newdata;
+
+ newdata->bufp = newdata->text;
+
+ if (len <= BUFFER_DATA_SIZE)
+ {
+ newdata->size = len;
+ memcpy (newdata->text, data, len);
+ return;
+ }
+
+ newdata->size = BUFFER_DATA_SIZE;
+ memcpy (newdata->text, data, BUFFER_DATA_SIZE);
+
+ data += BUFFER_DATA_SIZE;
+ len -= BUFFER_DATA_SIZE;
+ }
+
+ /*NOTREACHED*/
+}
+
+/* Add a '\0' terminated string to BUF. */
+
+static void
+buf_output0 (buf, string)
+ struct buffer *buf;
+ const char *string;
+{
+ buf_output (buf, string, strlen (string));
+}
+
+/* Add a single character to BUF. */
+
+static inline void
+buf_append_char (buf, ch)
+ struct buffer *buf;
+ int ch;
+{
+ if (buf->data != NULL
+ && (buf->last->text + BUFFER_DATA_SIZE
+ != buf->last->bufp + buf->last->size))
+ {
+ *(buf->last->bufp + buf->last->size) = ch;
+ ++buf->last->size;
+ }
+ else
+ {
+ char b;
+
+ b = ch;
+ buf_output (buf, &b, 1);
+ }
+}
+
+/*
+ * Send all the output we've been saving up. Returns 0 for success or
+ * errno code. If the buffer has been set to be nonblocking, this
+ * will just write until the write would block.
+ */
+
+static int
+buf_send_output (buf)
+ struct buffer *buf;
+{
+ if (! buf->output)
+ abort ();
+
+ while (buf->data != NULL)
+ {
+ struct buffer_data *data;
+
+ data = buf->data;
+ while (data->size > 0)
+ {
+ int nbytes;
+
+ nbytes = write (buf->fd, data->bufp, data->size);
+ if (nbytes <= 0)
+ {
+ int status;
+
+ if (buf->nonblocking
+ && (nbytes == 0
+#ifdef EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ || errno == EAGAIN))
+ {
+ /*
+ * A nonblocking write failed to write any data.
+ * Just return.
+ */
+ return 0;
+ }
+
+ /*
+ * An error, or EOF. Throw away all the data and
+ * return.
+ */
+ if (nbytes == 0)
+ status = EIO;
+ else
+ status = errno;
+
+ buf->last->next = free_buffer_data;
+ free_buffer_data = buf->data;
+ buf->data = NULL;
+ buf->last = NULL;
+
+ return status;
+ }
+
+ data->size -= nbytes;
+ data->bufp += nbytes;
+ }
+
+ buf->data = data->next;
+ data->next = free_buffer_data;
+ free_buffer_data = data;
+ }
+
+ buf->last = NULL;
+
+ return 0;
+}
+
+#ifdef SERVER_FLOWCONTROL
+/*
+ * 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 int
+set_nonblock (buf)
+ struct buffer *buf;
+{
+ int flags;
+
+ if (buf->nonblocking)
+ return 0;
+ flags = fcntl (buf->fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+ if (fcntl (buf->fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ return errno;
+ buf->nonblocking = 1;
+ return 0;
+}
+
+/*
+ * Set buffer BUF to blocking I/O. Returns 0 for success or errno
+ * code.
+ */
+
+static int
+set_block (buf)
+ struct buffer *buf;
+{
+ int flags;
+
+ if (! buf->nonblocking)
+ return 0;
+ flags = fcntl (buf->fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+ if (fcntl (buf->fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
+ return errno;
+ buf->nonblocking = 0;
+ return 0;
+}
+
+/*
+ * Send a character count and some output. Returns errno code or 0 for
+ * success.
+ *
+ * Sending the count in binary is OK since this is only used on a pipe
+ * within the same system.
+ */
+
+static int
+buf_send_counted (buf)
+ struct buffer *buf;
+{
+ int size;
+ struct buffer_data *data;
+
+ if (! buf->output)
+ abort ();
+
+ size = 0;
+ for (data = buf->data; data != NULL; data = data->next)
+ size += data->size;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ (*buf->memory_error) (buf);
+ return ENOMEM;
+ }
+
+ data->next = buf->data;
+ buf->data = data;
+ if (buf->last == NULL)
+ buf->last = data;
+
+ data->bufp = data->text;
+ data->size = sizeof (int);
+
+ *((int *) data->text) = size;
+
+ return buf_send_output (buf);
+}
+
+/* Append a list of buffer_data structures to an buffer. */
+
+static inline void
+buf_append_data (buf, data, last)
+ struct buffer *buf;
+ struct buffer_data *data;
+ struct buffer_data *last;
+{
+ if (data != NULL)
+ {
+ if (buf->data == NULL)
+ buf->data = data;
+ else
+ buf->last->next = data;
+ buf->last = last;
+ }
+}
+
+/*
+ * Copy the contents of file F into buffer_data structures. We can't
+ * copy directly into an buffer, because we want to handle failure and
+ * succeess differently. Returns 0 on success, or -2 if out of
+ * memory, or a status code on error. Since the caller happens to
+ * know the size of the file, it is passed in as SIZE. On success,
+ * this function sets *RETP and *LASTP, which may be passed to
+ * buf_append_data.
+ */
+
+static int
+buf_read_file (f, size, retp, lastp)
+ FILE *f;
+ long size;
+ struct buffer_data **retp;
+ struct buffer_data **lastp;
+{
+ int status;
+
+ *retp = NULL;
+ *lastp = NULL;
+
+ while (size > 0)
+ {
+ struct buffer_data *data;
+ int get;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ status = -2;
+ goto error_return;
+ }
+
+ if (*retp == NULL)
+ *retp = data;
+ else
+ (*lastp)->next = data;
+ data->next = NULL;
+ *lastp = data;
+
+ data->bufp = data->text;
+ data->size = 0;
+
+ if (size > BUFFER_DATA_SIZE)
+ get = BUFFER_DATA_SIZE;
+ else
+ get = size;
+
+ errno = EIO;
+ if (fread (data->text, get, 1, f) != 1)
+ {
+ status = errno;
+ goto error_return;
+ }
+
+ data->size += get;
+ size -= get;
+ }
+
+ return 0;
+
+ error_return:
+ if (*retp != NULL)
+ {
+ (*lastp)->next = free_buffer_data;
+ free_buffer_data = *retp;
+ }
+ return status;
+}
+
+static int
+buf_read_file_to_eof (f, retp, lastp)
+ FILE *f;
+ struct buffer_data **retp;
+ struct buffer_data **lastp;
+{
+ int status;
+
+ *retp = NULL;
+ *lastp = NULL;
+
+ while (!feof (f))
+ {
+ struct buffer_data *data;
+ int get, nread;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ status = -2;
+ goto error_return;
+ }
+
+ if (*retp == NULL)
+ *retp = data;
+ else
+ (*lastp)->next = data;
+ data->next = NULL;
+ *lastp = data;
+
+ data->bufp = data->text;
+ data->size = 0;
+
+ get = BUFFER_DATA_SIZE;
+
+ errno = EIO;
+ nread = fread (data->text, 1, get, f);
+ if (nread == 0 && !feof (f))
+ {
+ status = errno;
+ goto error_return;
+ }
+
+ data->size = nread;
+ }
+
+ return 0;
+
+ error_return:
+ if (*retp != NULL)
+ {
+ (*lastp)->next = free_buffer_data;
+ free_buffer_data = *retp;
+ }
+ return status;
+}
+
+static int
+buf_chain_length (buf)
+ struct buffer_data *buf;
+{
+ int size = 0;
+ while (buf)
+ {
+ size += buf->size;
+ buf = buf->next;
+ }
+ return size;
+}
+
+/*
+ * Read an arbitrary amount of data from a file descriptor into an
+ * input buffer. The file descriptor will be in nonblocking mode, and
+ * we just grab what we can. Return 0 on success, or -1 on end of
+ * file, or -2 if out of memory, or an error code. If COUNTP is not
+ * NULL, *COUNTP is set to the number of bytes read.
+ */
+
+static int
+buf_input_data (buf, countp)
+ struct buffer *buf;
+ int *countp;
+{
+ if (buf->output)
+ abort ();
+
+ if (countp != NULL)
+ *countp = 0;
+
+ while (1)
+ {
+ int get;
+ int nbytes;
+
+ if (buf->data == NULL
+ || (buf->last->bufp + buf->last->size
+ == buf->last->text + BUFFER_DATA_SIZE))
+ {
+ struct buffer_data *data;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ (*buf->memory_error) (buf);
+ return -2;
+ }
+
+ if (buf->data == NULL)
+ buf->data = data;
+ else
+ buf->last->next = data;
+ data->next = NULL;
+ buf->last = data;
+
+ data->bufp = data->text;
+ data->size = 0;
+ }
+
+ get = ((buf->last->text + BUFFER_DATA_SIZE)
+ - (buf->last->bufp + buf->last->size));
+ nbytes = read (buf->fd, buf->last->bufp + buf->last->size, get);
+ if (nbytes <= 0)
+ {
+ if (nbytes == 0)
+ {
+ /*
+ * 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;
+ }
+
+ if (errno == EAGAIN
+#ifdef EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ )
+ return 0;
+
+ return errno;
+ }
+
+ buf->last->size += nbytes;
+ if (countp != NULL)
+ *countp += nbytes;
+ }
+
+ /*NOTREACHED*/
+}
+
+/*
+ * Copy lines from an input buffer to an output buffer. This copies
+ * all complete lines (characters up to a newline) from INBUF to
+ * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND
+ * and a space.
+ */
+
+static void
+buf_copy_lines (outbuf, inbuf, command)
+ struct buffer *outbuf;
+ struct buffer *inbuf;
+ int command;
+{
+ if (! outbuf->output || inbuf->output)
+ abort ();
+
+ while (1)
+ {
+ struct buffer_data *data;
+ struct buffer_data *nldata;
+ char *nl;
+ int len;
+
+ /* See if there is a newline in INBUF. */
+ nldata = NULL;
+ nl = NULL;
+ for (data = inbuf->data; data != NULL; data = data->next)
+ {
+ nl = memchr (data->bufp, '\n', data->size);
+ if (nl != NULL)
+ {
+ nldata = data;
+ break;
+ }
+ }
+
+ if (nldata == NULL)
+ {
+ /* There are no more lines in INBUF. */
+ return;
+ }
+
+ /* Put in the command. */
+ buf_append_char (outbuf, command);
+ buf_append_char (outbuf, ' ');
+
+ if (inbuf->data != nldata)
+ {
+ /*
+ * Simply move over all the buffers up to the one containing
+ * the newline.
+ */
+ for (data = inbuf->data; data->next != nldata; data = data->next)
+ ;
+ data->next = NULL;
+ buf_append_data (outbuf, inbuf->data, data);
+ inbuf->data = nldata;
+ }
+
+ /*
+ * If the newline is at the very end of the buffer, just move
+ * the buffer onto OUTBUF. Otherwise we must copy the data.
+ */
+ len = nl + 1 - nldata->bufp;
+ if (len == nldata->size)
+ {
+ inbuf->data = nldata->next;
+ if (inbuf->data == NULL)
+ inbuf->last = NULL;
+
+ nldata->next = NULL;
+ buf_append_data (outbuf, nldata, nldata);
+ }
+ else
+ {
+ buf_output (outbuf, nldata->bufp, len);
+ nldata->bufp += len;
+ nldata->size -= len;
+ }
+ }
+}
+
+/*
+ * Copy counted data from one buffer to another. The count is an
+ * integer, host size, host byte order (it is only used across a
+ * pipe). If there is enough data, it should be moved over. If there
+ * is not enough data, it should remain on the original buffer. This
+ * returns the number of bytes it needs to see in order to actually
+ * copy something over.
+ */
+
+static int
+buf_copy_counted (outbuf, inbuf)
+ struct buffer *outbuf;
+ struct buffer *inbuf;
+{
+ if (! outbuf->output || inbuf->output)
+ abort ();
+
+ while (1)
+ {
+ struct buffer_data *data;
+ int need;
+ union
+ {
+ char intbuf[sizeof (int)];
+ int i;
+ } u;
+ char *intp;
+ int count;
+ struct buffer_data *start;
+ int startoff;
+ struct buffer_data *stop;
+ int stopwant;
+
+ /* See if we have enough bytes to figure out the count. */
+ need = sizeof (int);
+ intp = u.intbuf;
+ for (data = inbuf->data; data != NULL; data = data->next)
+ {
+ if (data->size >= need)
+ {
+ memcpy (intp, data->bufp, need);
+ break;
+ }
+ memcpy (intp, data->bufp, data->size);
+ intp += data->size;
+ need -= data->size;
+ }
+ if (data == NULL)
+ {
+ /* We don't have enough bytes to form an integer. */
+ return need;
+ }
+
+ count = u.i;
+ start = data;
+ startoff = need;
+
+ /*
+ * We have an integer in COUNT. We have gotten all the data
+ * from INBUF in all buffers before START, and we have gotten
+ * STARTOFF bytes from START. See if we have enough bytes
+ * remaining in INBUF.
+ */
+ need = count - (start->size - startoff);
+ if (need <= 0)
+ {
+ stop = start;
+ stopwant = count;
+ }
+ else
+ {
+ for (data = start->next; data != NULL; data = data->next)
+ {
+ if (need <= data->size)
+ break;
+ need -= data->size;
+ }
+ if (data == NULL)
+ {
+ /* We don't have enough bytes. */
+ return need;
+ }
+ stop = data;
+ stopwant = need;
+ }
+
+ /*
+ * We have enough bytes. Free any buffers in INBUF before
+ * START, and remove STARTOFF bytes from START, so that we can
+ * forget about STARTOFF.
+ */
+ start->bufp += startoff;
+ start->size -= startoff;
+
+ if (start->size == 0)
+ start = start->next;
+
+ if (stop->size == stopwant)
+ {
+ stop = stop->next;
+ stopwant = 0;
+ }
+
+ while (inbuf->data != start)
+ {
+ data = inbuf->data;
+ inbuf->data = data->next;
+ data->next = free_buffer_data;
+ free_buffer_data = data;
+ }
+
+ /*
+ * We want to copy over the bytes from START through STOP. We
+ * only want STOPWANT bytes from STOP.
+ */
+
+ if (start != stop)
+ {
+ /* Attach the buffers from START through STOP to OUTBUF. */
+ for (data = start; data->next != stop; data = data->next)
+ ;
+ inbuf->data = stop;
+ data->next = NULL;
+ buf_append_data (outbuf, start, data);
+ }
+
+ if (stopwant > 0)
+ {
+ buf_output (outbuf, stop->bufp, stopwant);
+ stop->bufp += stopwant;
+ stop->size -= stopwant;
+ }
+ }
+
+ /*NOTREACHED*/
+}
+
+/* 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;
+
+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 (!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);
+ server_cleanup (0);
+ exit (EXIT_FAILURE);
+}
+
+static void
+input_memory_error (buf)
+ struct buffer *buf;
+{
+ outbuf_memory_error (buf);
+}
+
+/* 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 (command)
+ 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;
+
+ (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 = open ("/dev/null", O_RDONLY);
+ if (dev_null_fd < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+
+ /* 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.data = protocol.last = NULL;
+ protocol.fd = protocol_pipe[1];
+ protocol.output = 1;
+ protocol.nonblocking = 0;
+ protocol.memory_error = protocol_memory_error;
+
+ saved_output.data = saved_output.last = NULL;
+ saved_output.fd = -1;
+ saved_output.output = 0;
+ saved_output.nonblocking = 0;
+ saved_output.memory_error = protocol_memory_error;
+ saved_outerr = saved_output;
+
+ 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);
+
+ /*
+ * 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)
+ {
+ printf ("E internal error: FD_SETSIZE not big enough.\nerror \n");
+ goto error_exit;
+ }
+
+ stdoutbuf.data = stdoutbuf.last = NULL;
+ stdoutbuf.fd = stdout_pipe[0];
+ stdoutbuf.output = 0;
+ stdoutbuf.nonblocking = 0;
+ stdoutbuf.memory_error = input_memory_error;
+
+ stderrbuf.data = stderrbuf.last = NULL;
+ stderrbuf.fd = stderr_pipe[0];
+ stderrbuf.output = 0;
+ stderrbuf.nonblocking = 0;
+ stderrbuf.memory_error = input_memory_error;
+
+ protocol_inbuf.data = protocol_inbuf.last = NULL;
+ protocol_inbuf.fd = protocol_pipe[0];
+ protocol_inbuf.output = 0;
+ protocol_inbuf.nonblocking = 0;
+ protocol_inbuf.memory_error = 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);
+ }
+
+ 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;
+
+ status = buf_input_data (&protocol_inbuf, &count_read);
+
+ /*
+ * 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;
+ if (count_needed <= 0)
+ count_needed = buf_copy_counted (&buf_to_net,
+ &protocol_inbuf);
+
+ if (status == -1)
+ protocol_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);
+ }
+ }
+
+ /*
+ * 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);
+ /*
+ * 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.
+ */
+ printf ("E Terminated with fatal signal %d\n", sig);
+
+ /* Test for a core dump. Is this portable? */
+ if (status & 0x80)
+ {
+ printf ("E Core dumped; preserving %s on server.\n\
+E CVS locks may need cleaning up.\n",
+ server_temp_dir);
+ 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_send_output (&buf_to_net);
+ }
+
+ if (errs)
+ /* We will have printed an error message already. */
+ printf ("error \n");
+ else
+ printf ("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;
+ }
+ 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))
+ {
+ while (read (flowcontrol_pipe[0], buf, 1) == 1)
+ {
+ if (*buf == 'S') /* Stop */
+ paused = 1;
+ else if (*buf == 'G') /* Go */
+ paused = 0;
+ else
+ return; /* ??? */
+ }
+ }
+ }
+}
+#endif /* SERVER_FLOWCONTROL */
+
+static void output_dir PROTO((char *, char *));
+
+static void
+output_dir (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ if (use_dir_and_repos)
+ {
+ 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,
+ "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n",
+ (server_active) ? 'S' : ' ', /* silly */
+ name, version, 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);
+}
+
+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 (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);
+}
+
+static void
+serve_diff (arg)
+ char *arg;
+{
+ do_cvs_command (diff);
+}
+
+static void
+serve_log (arg)
+ char *arg;
+{
+ do_cvs_command (cvslog);
+}
+
+static void
+serve_add (arg)
+ char *arg;
+{
+ do_cvs_command (add);
+}
+
+static void
+serve_remove (arg)
+ char *arg;
+{
+ do_cvs_command (cvsremove);
+}
+
+static void
+serve_status (arg)
+ char *arg;
+{
+ do_cvs_command (status);
+}
+
+static void
+serve_rdiff (arg)
+ char *arg;
+{
+ do_cvs_command (patch);
+}
+
+static void
+serve_tag (arg)
+ char *arg;
+{
+ do_cvs_command (tag);
+}
+
+static void
+serve_rtag (arg)
+ char *arg;
+{
+ do_cvs_command (rtag);
+}
+
+static void
+serve_import (arg)
+ char *arg;
+{
+ do_cvs_command (import);
+}
+
+static void
+serve_admin (arg)
+ char *arg;
+{
+ do_cvs_command (admin);
+}
+
+static void
+serve_history (arg)
+ char *arg;
+{
+ do_cvs_command (history);
+}
+
+static void
+serve_release (arg)
+ char *arg;
+{
+ do_cvs_command (release);
+}
+
+static void serve_watch_on PROTO ((char *));
+
+static void
+serve_watch_on (arg)
+ char *arg;
+{
+ do_cvs_command (watch_on);
+}
+
+static void serve_watch_off PROTO ((char *));
+
+static void
+serve_watch_off (arg)
+ char *arg;
+{
+ do_cvs_command (watch_off);
+}
+
+static void serve_watch_add PROTO ((char *));
+
+static void
+serve_watch_add (arg)
+ char *arg;
+{
+ do_cvs_command (watch_add);
+}
+
+static void serve_watch_remove PROTO ((char *));
+
+static void
+serve_watch_remove (arg)
+ char *arg;
+{
+ do_cvs_command (watch_remove);
+}
+
+static void serve_watchers PROTO ((char *));
+
+static void
+serve_watchers (arg)
+ char *arg;
+{
+ do_cvs_command (watchers);
+}
+
+static void serve_editors PROTO ((char *));
+
+static void
+serve_editors (arg)
+ char *arg;
+{
+ do_cvs_command (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);
+}
+
+static void serve_init PROTO ((char *));
+
+static void
+serve_init (arg)
+ char *arg;
+{
+ CVSroot = malloc (strlen (arg) + 1);
+ if (CVSroot == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (CVSroot, arg);
+
+ do_cvs_command (init);
+}
+
+static void serve_annotate PROTO ((char *));
+
+static void
+serve_annotate (arg)
+ char *arg;
+{
+ do_cvs_command (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)
+ {
+ printf ("E Out of memory\n");
+ return;
+ }
+ strcpy (tempdir, server_temp_dir);
+ strcat (tempdir, "/checkout-dir");
+ status = mkdir_p (tempdir);
+ if (status != 0 && status != EEXIST)
+ {
+ printf ("E Cannot create %s\n", tempdir);
+ print_error (errno);
+ free (tempdir);
+ return;
+ }
+
+ if (chdir (tempdir) < 0)
+ {
+ printf ("E Cannot change to directory %s\n", tempdir);
+ print_error (errno);
+ free (tempdir);
+ return;
+ }
+ free (tempdir);
+ }
+ do_cvs_command (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;
+{
+ 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");
+}
+
+void
+server_updated (file, update_dir, repository, updated, file_info, checksum)
+ char *file;
+ char *update_dir;
+ char *repository;
+ enum server_updated_arg4 updated;
+ struct stat *file_info;
+ unsigned char *checksum;
+{
+ char *short_pathname;
+
+ if (noexec)
+ return;
+
+ short_pathname = xmalloc (strlen (update_dir) + strlen (file) + 10);
+ if (update_dir[0] == '\0')
+ strcpy (short_pathname, file);
+ else
+ sprintf (short_pathname, "%s/%s", update_dir, file);
+
+ if (entries_line != NULL && scratched_file == NULL)
+ {
+ FILE *f;
+ struct stat sb;
+ struct buffer_data *list, *last;
+ unsigned long size;
+ char size_text[80];
+
+ if (stat (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", short_pathname);
+ }
+
+ 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)
+ buf_output0 (&protocol, "Updated ");
+ else if (updated == SERVER_MERGED)
+ buf_output0 (&protocol, "Merged ");
+ else if (updated == SERVER_PATCHED)
+ buf_output0 (&protocol, "Patched ");
+ else
+ abort ();
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, file);
+ buf_output (&protocol, "\n", 1);
+
+ new_entries_line ();
+
+ {
+ char *mode_string;
+
+ /* FIXME: When we check out files the umask of the server
+ (set in .bashrc if rsh is in use, or set in main.c in
+ the kerberos case, I think) affects what mode we send,
+ and it shouldn't. */
+ if (file_info != NULL)
+ mode_string = mode_to_string (file_info->st_mode);
+ else
+ mode_string = mode_to_string (sb.st_mode);
+ buf_output0 (&protocol, mode_string);
+ buf_output0 (&protocol, "\n");
+ free (mode_string);
+ }
+
+ list = last = NULL;
+ size = 0;
+ if (sb.st_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 (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.
+ */
+ && sb.st_size > 100)
+ {
+ int status, fd, gzip_status;
+ pid_t gzip_pid;
+
+ fd = open (file, O_RDONLY | OPEN_BINARY, 0);
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+ fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid);
+ f = fdopen (fd, "rb");
+ status = buf_read_file_to_eof (f, &list, &last);
+ size = buf_chain_length (list);
+ if (status == -2)
+ (*protocol.memory_error) (&protocol);
+ else if (status != 0)
+ error (1, ferror (f) ? errno : 0, "reading %s",
+ short_pathname);
+ if (fclose (f) == EOF)
+ error (1, errno, "reading %s", short_pathname);
+ if (waitpid (gzip_pid, &gzip_status, 0) == -1)
+ error (1, errno, "waiting for gzip process %ld",
+ (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, 0, "gzip exited %d", gzip_status);
+ /* Prepending length with "z" is flag for using gzip here. */
+ buf_output0 (&protocol, "z");
+ }
+ else
+ {
+ long status;
+
+ size = sb.st_size;
+ f = fopen (file, "rb");
+ if (f == NULL)
+ error (1, errno, "reading %s", short_pathname);
+ status = buf_read_file (f, sb.st_size, &list, &last);
+ if (status == -2)
+ (*protocol.memory_error) (&protocol);
+ else if (status != 0)
+ error (1, ferror (f) ? errno : 0, "reading %s",
+ short_pathname);
+ if (fclose (f) == EOF)
+ error (1, errno, "reading %s", short_pathname);
+ }
+ }
+
+ sprintf (size_text, "%lu\n", size);
+ buf_output0 (&protocol, size_text);
+
+ buf_append_data (&protocol, list, last);
+ /* 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)
+ /* But if we are joining, we'll need the file when we call
+ join_file. */
+ && !joining ())
+ unlink (file);
+ }
+ else if (scratched_file != NULL && entries_line == NULL)
+ {
+ if (strcmp (scratched_file, file) != 0)
+ error (1, 0,
+ "CVS server internal error: `%s' vs. `%s' scratched",
+ scratched_file,
+ file);
+ free (scratched_file);
+ scratched_file = NULL;
+
+ if (kill_scratched_file)
+ buf_output0 (&protocol, "Removed ");
+ else
+ buf_output0 (&protocol, "Remove-entry ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, 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:
+ free (short_pathname);
+}
+
+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)
+ char *update_dir;
+ char *repository;
+ char *tag;
+ char *date;
+{
+ static int set_sticky_supported = -1;
+ 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)
+ {
+ 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 = 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;
+ gzip_level = level;
+}
+
+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)
+ {
+ printf ("Module-expansion %s", mwhere);
+ if (mfile != NULL)
+ {
+ printf ("/%s", mfile);
+ }
+ printf ("\n");
+ }
+ else
+ {
+ /* We may not need to do this anymore -- check the definition
+ of aliases before removing */
+ if (*pargc == 1)
+ printf ("Module-expansion %s\n", dir);
+ else
+ for (i = 1; i < *pargc; ++i)
+ printf ("Module-expansion %s/%s\n", dir, argv[i]);
+ }
+ return 0;
+}
+
+static void
+serve_expand_modules (arg)
+ char *arg;
+{
+ int i;
+ int err;
+ DBM *db;
+ err = 0;
+
+ /*
+ * FIXME: error handling is bogus; do_module can write to stdout and/or
+ * stderr and we're not using do_cvs_command.
+ */
+
+ 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. */
+ printf ("error \n");
+ else
+ printf ("ok\n");
+}
+
+void
+server_prog (dir, name, which)
+ char *dir;
+ char *name;
+ enum progs which;
+{
+ if (!supported_response ("Set-checkin-prog"))
+ {
+ printf ("E \
+warning: this client does not support -i or -u flags in the modules file.\n");
+ return;
+ }
+ switch (which)
+ {
+ case PROG_CHECKIN:
+ printf ("Set-checkin-prog ");
+ break;
+ case PROG_UPDATE:
+ printf ("Set-update-prog ");
+ break;
+ }
+ printf ("%s\n%s\n", dir, name);
+}
+
+static void
+serve_checkin_prog (arg)
+ char *arg;
+{
+ FILE *f;
+ f = fopen (CVSADM_CIPROG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (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;
+ pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
+ sprintf(pending_error_text, "E cannot write to %s", CVSADM_CIPROG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (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;
+ f = fopen (CVSADM_UPROG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (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;
+ pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
+ sprintf(pending_error_text, "E cannot write to %s", CVSADM_UPROG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (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),
+ REQ_LINE("Valid-responses", serve_valid_responses, rq_essential),
+ REQ_LINE("valid-requests", serve_valid_requests, rq_essential),
+ REQ_LINE("Repository", serve_repository, rq_essential),
+ REQ_LINE("Directory", serve_directory, rq_optional),
+ REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional),
+ REQ_LINE("Static-directory", serve_static_directory, rq_optional),
+ REQ_LINE("Sticky", serve_sticky, rq_optional),
+ REQ_LINE("Checkin-prog", serve_checkin_prog, rq_optional),
+ REQ_LINE("Update-prog", serve_update_prog, rq_optional),
+ REQ_LINE("Entry", serve_entry, rq_essential),
+ REQ_LINE("Modified", serve_modified, rq_essential),
+ REQ_LINE("Lost", serve_lost, rq_optional),
+ REQ_LINE("UseUnchanged", serve_enable_unchanged, rq_enableme),
+ REQ_LINE("Unchanged", serve_unchanged, rq_optional),
+ REQ_LINE("Notify", serve_notify, rq_optional),
+ REQ_LINE("Questionable", serve_questionable, rq_optional),
+ REQ_LINE("Case", serve_case, rq_optional),
+ REQ_LINE("Argument", serve_argument, rq_essential),
+ REQ_LINE("Argumentx", serve_argumentx, rq_essential),
+ REQ_LINE("Global_option", serve_global_option, rq_optional),
+ REQ_LINE("Set", serve_set, rq_optional),
+ REQ_LINE("expand-modules", serve_expand_modules, rq_optional),
+ 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, rq_optional),
+ REQ_LINE("log", serve_log, rq_optional),
+ REQ_LINE("add", serve_add, rq_optional),
+ REQ_LINE("remove", serve_remove, rq_optional),
+ REQ_LINE("update-patches", serve_ignore, rq_optional),
+ REQ_LINE("gzip-file-contents", serve_gzip_contents, rq_optional),
+ REQ_LINE("status", serve_status, rq_optional),
+ REQ_LINE("rdiff", serve_rdiff, rq_optional),
+ REQ_LINE("tag", serve_tag, rq_optional),
+ REQ_LINE("rtag", serve_rtag, rq_optional),
+ REQ_LINE("import", serve_import, rq_optional),
+ REQ_LINE("admin", serve_admin, rq_optional),
+ REQ_LINE("export", serve_export, rq_optional),
+ REQ_LINE("history", serve_history, rq_optional),
+ REQ_LINE("release", serve_release, rq_optional),
+ REQ_LINE("watch-on", serve_watch_on, rq_optional),
+ REQ_LINE("watch-off", serve_watch_off, rq_optional),
+ REQ_LINE("watch-add", serve_watch_add, rq_optional),
+ REQ_LINE("watch-remove", serve_watch_remove, rq_optional),
+ REQ_LINE("watchers", serve_watchers, rq_optional),
+ REQ_LINE("editors", serve_editors, rq_optional),
+ REQ_LINE("init", serve_init, rq_optional),
+ REQ_LINE("annotate", serve_annotate, rq_optional),
+ REQ_LINE("noop", serve_noop, rq_optional),
+ REQ_LINE(NULL, NULL, rq_optional)
+
+#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;
+ printf ("Valid-requests");
+ for (rq = requests; rq->name != NULL; rq++)
+ if (rq->func != NULL)
+ printf (" %s", rq->name);
+ printf ("\nok\n");
+}
+
+#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 len;
+ char *cmd;
+ char *temp_dir;
+
+ if (dont_delete_temp)
+ 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
+
+ /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
+ temp_dir = getenv ("TMPDIR");
+ if (temp_dir == NULL || temp_dir[0] == '\0')
+ temp_dir = "/tmp";
+ chdir(temp_dir);
+
+ len = strlen (server_temp_dir) + 80;
+ cmd = malloc (len);
+ if (cmd == NULL)
+ {
+ printf ("E Cannot delete %s on server; out of memory\n",
+ server_temp_dir);
+ return;
+ }
+ sprintf (cmd, "rm -rf %s", server_temp_dir);
+ system (cmd);
+ free (cmd);
+}
+
+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. */
+
+ /* Since we're in the server parent process, error should use the
+ protocol to report error messages. */
+ error_use_protocol = 1;
+
+ /*
+ * Put Rcsbin at the start of PATH, so that rcs programs can find
+ * themselves.
+ */
+#ifdef HAVE_PUTENV
+ if (Rcsbin != NULL && *Rcsbin)
+ {
+ char *p;
+ char *env;
+
+ p = getenv ("PATH");
+ if (p != NULL)
+ {
+ env = malloc (strlen (Rcsbin) + strlen (p) + sizeof "PATH=:");
+ if (env != NULL)
+ sprintf (env, "PATH=%s:%s", Rcsbin, p);
+ }
+ else
+ {
+ env = malloc (strlen (Rcsbin) + sizeof "PATH=");
+ if (env != NULL)
+ sprintf (env, "PATH=%s", Rcsbin);
+ }
+ if (env == NULL)
+ {
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+ exit (EXIT_FAILURE);
+ }
+ putenv (env);
+ }
+#endif
+
+ /* OK, now figure out where we stash our temporary files. */
+ {
+ char *p;
+
+ /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
+ char *temp_dir = getenv ("TMPDIR");
+ if (temp_dir == NULL || temp_dir[0] == '\0')
+ temp_dir = "/tmp";
+
+ server_temp_dir = malloc (strlen (temp_dir) + 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");
+ exit (EXIT_FAILURE);
+ }
+ strcpy (server_temp_dir, temp_dir);
+
+ /* 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 ());
+ }
+
+ (void) SIG_register (SIGHUP, server_cleanup);
+ (void) SIG_register (SIGINT, server_cleanup);
+ (void) SIG_register (SIGQUIT, server_cleanup);
+ (void) SIG_register (SIGPIPE, server_cleanup);
+ (void) SIG_register (SIGTERM, server_cleanup);
+
+ /* 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");
+ exit (EXIT_FAILURE);
+ }
+
+ argument_count = 1;
+ argument_vector[0] = "Dummy argument 0";
+
+ buf_to_net.data = buf_to_net.last = NULL;
+ buf_to_net.fd = STDOUT_FILENO;
+ buf_to_net.output = 1;
+ buf_to_net.nonblocking = 0;
+ buf_to_net.memory_error = outbuf_memory_error;
+
+ server_active = 1;
+
+ while (1)
+ {
+ char *cmd, *orig_cmd;
+ struct request *rq;
+
+ orig_cmd = cmd = read_line (stdin);
+ if (cmd == NULL)
+ break;
+ if (cmd == NO_MEM_ERROR)
+ {
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+ break;
+ }
+ 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;
+ (*rq->func) (cmd);
+ break;
+ }
+ if (rq->name == NULL)
+ {
+ if (!print_pending_error ())
+ printf ("error unrecognized request `%s'\n", cmd);
+ }
+ free (orig_cmd);
+ }
+ server_cleanup (0);
+ return 0;
+}
+
+
+#ifdef AUTH_SERVER_SUPPORT
+
+extern char *crypt PROTO((const char *, const char *));
+
+/* This was test code, which we may need again. */
+#if 0
+ /* If we were invoked this way, then stdin comes from the
+ client and stdout/stderr writes to it. */
+ int c;
+ while ((c = getc (stdin)) != EOF && c != '*')
+ {
+ printf ("%c", toupper (c));
+ fflush (stdout);
+ }
+ exit (0);
+#endif /* 1/0 */
+
+
+/*
+ * 0 means no entry found for this user.
+ * 1 means entry found and password matches.
+ * 2 means entry found, but password does not match.
+ */
+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;
+ int found_it = 0;
+ int namelen;
+
+ filename = xmalloc (strlen (repository)
+ + 1
+ + strlen ("CVSROOT")
+ + 1
+ + strlen ("passwd")
+ + 1);
+
+ strcpy (filename, repository);
+ strcat (filename, "/CVSROOT");
+ strcat (filename, "/passwd");
+
+ fp = 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 (1)
+ {
+ linebuf = read_line(fp);
+ if (linebuf == NULL)
+ {
+ free (linebuf);
+ break;
+ }
+ if (linebuf == NO_MEM_ERROR)
+ {
+ error (0, errno, "out of memory");
+ break;
+ }
+ if ((strncmp (linebuf, username, namelen) == 0)
+ && (linebuf[namelen] == ':'))
+ {
+ found_it = 1;
+ break;
+ }
+ free (linebuf);
+
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", filename);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", filename);
+
+ /* If found_it != 0, then linebuf contains the information we need. */
+ if (found_it)
+ {
+ char *found_password;
+
+ strtok (linebuf, ":");
+ found_password = strtok (NULL, ": \n");
+ *host_user_ptr = strtok (NULL, ": \n");
+ if (*host_user_ptr == NULL) *host_user_ptr = username;
+ if (strcmp (found_password, crypt (password, found_password)) == 0)
+ retval = 1;
+ else
+ retval = 2;
+ }
+ else
+ {
+ *host_user_ptr = NULL;
+ retval = 0;
+ }
+
+ free (filename);
+
+ return retval;
+}
+
+
+/* Return a hosting username if password matches, else NULL. */
+char *
+check_password (username, password, repository)
+ char *username, *password, *repository;
+{
+ int rc;
+ char *host_user;
+
+ /* 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 == 1)
+ return host_user;
+ else if (rc == 2)
+ return 0;
+ else if (rc == 0)
+ {
+ /* No cvs password found, so try /etc/passwd. */
+
+ struct passwd *pw;
+ char *found_passwd;
+
+ pw = getpwnam (username);
+ if (pw == NULL)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 %s: no such user\n", username);
+ exit (EXIT_FAILURE);
+ }
+ found_passwd = pw->pw_passwd;
+
+ if (found_passwd && *found_passwd)
+ return ((! strcmp (found_passwd, crypt (password, found_passwd)))
+ ? username : NULL);
+ else if (password && *password)
+ return username;
+ else
+ return NULL;
+ }
+ else
+ {
+ /* Something strange happened. We don't know what it was, but
+ we certainly won't grant authorization. */
+ return NULL;
+ }
+}
+
+
+/* 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
+authenticate_connection ()
+{
+ char tmp[PATH_MAX];
+ char repository[PATH_MAX];
+ char username[PATH_MAX];
+ char password[PATH_MAX];
+ char *host_user;
+ char *descrambled_password;
+ struct passwd *pw;
+ 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.
+ */
+
+ /* Since we're in the server parent process, error should use the
+ protocol to report error messages. */
+ error_use_protocol = 1;
+
+ /* Make sure the protocol starts off on the right foot... */
+ fgets (tmp, PATH_MAX, stdin);
+ if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
+ verify_and_exit = 1;
+ else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0)
+ error (1, 0, "bad auth protocol start: %s", tmp);
+
+ /* Get the three important pieces of information in order. */
+ fgets (repository, PATH_MAX, stdin);
+ fgets (username, PATH_MAX, stdin);
+ fgets (password, PATH_MAX, 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. */
+ fgets (tmp, PATH_MAX, 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);
+ }
+
+ /* We need the real cleartext before we hash it. */
+ descrambled_password = descramble (password);
+ host_user = check_password (username, descrambled_password, repository);
+ if (host_user)
+ {
+ printf ("I LOVE YOU\n");
+ fflush (stdout);
+ memset (descrambled_password, 0, strlen (descrambled_password));
+ free (descrambled_password);
+ }
+ else
+ {
+ printf ("I HATE YOU\n");
+ fflush (stdout);
+ memset (descrambled_password, 0, strlen (descrambled_password));
+ free (descrambled_password);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Don't go any farther if we're just responding to "cvs login". */
+ if (verify_and_exit)
+ exit (0);
+
+ /* Switch to run as this user. */
+ pw = getpwnam (host_user);
+ if (pw == NULL)
+ {
+ error (1, 0,
+ "fatal error, aborting.\nerror 0 %s: no such user\n",
+ username);
+ }
+
+#if HAVE_INITGROUPS
+ initgroups (pw->pw_name, pw->pw_gid);
+#endif /* HAVE_INITGROUPS */
+
+ setgid (pw->pw_gid);
+ setuid (pw->pw_uid);
+ /* Inhibit access by randoms. Don't want people randomly
+ changing our temporary tree before we check things in. */
+ umask (077);
+
+#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 /* AUTH_SERVER_SUPPORT */
+
+
+#endif /* SERVER_SUPPORT */
+
+/* Output LEN bytes at STR. If LEN is zero, then output up to (not including)
+ the first '\0' byte. Should not be called from the server parent process
+ (yet at least, in the future it might be extended so that works). */
+
+void
+cvs_output (str, len)
+ char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+ if (error_use_protocol)
+ /* Eventually we'll probably want to make it so this case works,
+ but for now, callers who want to output something with
+ error_use_protocol in effect can just printf the "M foo"
+ themselves. */
+ abort ();
+#ifdef SERVER_SUPPORT
+ 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;
+ char *p = str;
+
+ while (to_write > 0)
+ {
+ written = fwrite (str, 1, to_write, stdout);
+ if (written == 0)
+ break;
+ p += written;
+ to_write -= written;
+ }
+ }
+}
+
+/* Like CVS_OUTPUT but output is for stderr not stdout. */
+
+void
+cvs_outerr (str, len)
+ char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+ if (error_use_protocol)
+ /* Eventually we'll probably want to make it so this case works,
+ but for now, callers who want to output something with
+ error_use_protocol in effect can just printf the "E foo"
+ themselves. */
+ abort ();
+#ifdef SERVER_SUPPORT
+ 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;
+ char *p = str;
+
+ while (to_write > 0)
+ {
+ written = fwrite (str, 1, to_write, stderr);
+ if (written == 0)
+ break;
+ p += written;
+ to_write -= written;
+ }
+ }
+}
diff --git a/contrib/cvs/src/server.h b/contrib/cvs/src/server.h
new file mode 100644
index 0000000..30ddb8c
--- /dev/null
+++ b/contrib/cvs/src/server.h
@@ -0,0 +1,138 @@
+/* Interface between the server and the rest of CVS. */
+
+/* Miscellaneous stuff which isn't actually particularly server-specific. */
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+#endif
+
+#ifdef SERVER_SUPPORT
+
+/*
+ * Nonzero if we are using the server. Used by various places to call
+ * server-specific functions.
+ */
+extern int server_active;
+extern int server_expanding;
+
+/* Server functions exported to the rest of CVS. */
+
+/* Run the server. */
+extern int server PROTO((int argc, char **argv));
+
+/* We have a new Entries line for a file. TAG or DATE can be NULL. */
+extern void server_register
+ PROTO((char *name, char *version, char *timestamp,
+ char *options, char *tag, char *date, char *conflict));
+
+/*
+ * We want to nuke the Entries line for a file, and (unless
+ * server_scratch_entry_only is subsequently called) the file itself.
+ */
+extern void server_scratch PROTO((char *name));
+
+/*
+ * The file which just had server_scratch called on it needs to have only
+ * the Entries line removed, not the file itself.
+ */
+extern void server_scratch_entry_only PROTO((void));
+
+/*
+ * We just successfully checked in FILE (which is just the bare
+ * filename, with no directory). REPOSITORY is the directory for the
+ * repository.
+ */
+extern void server_checked_in
+ PROTO((char *file, char *update_dir, char *repository));
+
+extern void server_copy_file
+ PROTO((char *file, char *update_dir, char *repository, char *newfile));
+
+/*
+ * We just successfully updated FILE (bare filename, no directory).
+ * REPOSITORY is the directory for the repository. This is called
+ * after server_register or server_scratch, in the latter case the
+ * file is to be removed. UPDATED indicates whether the file is now
+ * up to date (SERVER_UPDATED, yes, SERVER_MERGED, no, SERVER_PATCHED,
+ * yes, but file is a diff from user version to repository version).
+ */
+enum server_updated_arg4 {SERVER_UPDATED, SERVER_MERGED, SERVER_PATCHED};
+extern void server_updated
+ PROTO((char *file, char *update_dir, char *repository,
+ enum server_updated_arg4 updated, struct stat *,
+ unsigned char *checksum));
+
+/* Set the Entries.Static flag. */
+extern void server_set_entstat PROTO((char *update_dir, char *repository));
+/* Clear it. */
+extern void server_clear_entstat PROTO((char *update_dir, char *repository));
+
+/* Set or clear a per-directory sticky tag or date. */
+extern void server_set_sticky PROTO((char *update_dir, char *repository,
+ char *tag,
+ char *date));
+/* Send Template response. */
+extern void server_template PROTO ((char *, char *));
+
+extern void server_update_entries
+ PROTO((char *file, char *update_dir, char *repository,
+ enum server_updated_arg4 updated));
+
+enum progs {PROG_CHECKIN, PROG_UPDATE};
+extern void server_prog PROTO((char *, char *, enum progs));
+extern void server_cleanup PROTO((int sig));
+
+#ifdef SERVER_FLOWCONTROL
+/* Pause if it's convenient to avoid memory blowout */
+extern void server_pause_check PROTO((void));
+#endif /* SERVER_FLOWCONTROL */
+
+#endif /* SERVER_SUPPORT */
+
+/* Stuff shared with the client. */
+struct request
+{
+ /* Name of the request. */
+ char *name;
+
+#ifdef SERVER_SUPPORT
+ /*
+ * Function to carry out the request. ARGS is the text of the command
+ * after name and, if present, a single space, have been stripped off.
+ */
+ void (*func) PROTO((char *args));
+#endif
+
+ /* Stuff for use by the client. */
+ enum {
+ /*
+ * Failure to implement this request can imply a fatal
+ * error. This should be set only for commands which were in the
+ * original version of the protocol; it should not be set for new
+ * commands.
+ */
+ rq_essential,
+
+ /* Some servers might lack this request. */
+ rq_optional,
+
+ /*
+ * Set by the client to one of the following based on what this
+ * server actually supports.
+ */
+ rq_supported,
+ rq_not_supported,
+
+ /*
+ * If the server supports this request, and we do too, tell the
+ * server by making the request.
+ */
+ rq_enableme
+ } status;
+};
+
+/* Table of requests ending with an entry with a NULL name. */
+extern struct request requests[];
+
+extern int use_unchanged;
diff --git a/contrib/cvs/src/status.c b/contrib/cvs/src/status.c
new file mode 100644
index 0000000..277da0c
--- /dev/null
+++ b/contrib/cvs/src/status.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Status Information
+ */
+
+#include "cvs.h"
+
+static Dtype status_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int status_fileproc PROTO((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",
+ NULL
+};
+
+int
+status (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+
+ if (argc == -1)
+ usage (status_usage);
+
+ optind = 1;
+ 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");
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* XXX This should only need to send file info; the file
+ contents themselves will not be examined. */
+ send_files (argc, argv, local, 0);
+
+ 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, argc, argv, local,
+ W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+
+ return (err);
+}
+
+/*
+ * display the status of a file
+ */
+/* ARGSUSED */
+static int
+status_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Ctype status;
+ char *sstat;
+ Vers_TS *vers;
+
+ status = Classify_File (finfo->file, (char *) NULL, (char *) NULL, (char *) NULL,
+ 1, 0, finfo->repository, finfo->entries, finfo->rcs, &vers,
+ finfo->update_dir, 0);
+ 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:
+ 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 = "Unresolved Conflict";
+ 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;
+ default:
+ sstat = "Classify Error";
+ break;
+ }
+
+ (void) printf ("===================================================================\n");
+ if (vers->ts_user == NULL)
+ (void) printf ("File: no file %s\t\tStatus: %s\n\n", finfo->file, sstat);
+ else
+ (void) printf ("File: %-17s\tStatus: %s\n\n", finfo->file, sstat);
+
+ if (vers->vn_user == NULL)
+ (void) printf (" Working revision:\tNo entry for %s\n", finfo->file);
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ (void) printf (" Working revision:\tNew file!\n");
+#ifdef SERVER_SUPPORT
+ else if (server_active)
+ (void) printf (" Working revision:\t%s\n", vers->vn_user);
+#endif
+ else
+ (void) printf (" Working revision:\t%s\t%s\n", vers->vn_user,
+ vers->ts_rcs);
+
+ if (vers->vn_rcs == NULL)
+ (void) printf (" Repository revision:\tNo revision control file\n");
+ else
+ (void) printf (" Repository revision:\t%s\t%s\n", vers->vn_rcs,
+ vers->srcfile->path);
+
+ if (vers->entdata)
+ {
+ Entnode *edata;
+
+ edata = vers->entdata;
+ if (edata->tag)
+ {
+ if (vers->vn_rcs == NULL)
+ (void) printf (
+ " Sticky Tag:\t\t%s - MISSING from RCS file!\n",
+ edata->tag);
+ else
+ {
+ if (isdigit (edata->tag[0]))
+ (void) printf (" Sticky Tag:\t\t%s\n", edata->tag);
+ else
+ {
+ char *branch = NULL;
+
+ if (RCS_isbranch (finfo->rcs, edata->tag))
+ branch = RCS_whatbranch(finfo->rcs, edata->tag);
+
+ (void) printf (" Sticky Tag:\t\t%s (%s: %s)\n",
+ edata->tag,
+ branch ? "branch" : "revision",
+ branch ? branch : vers->vn_rcs);
+
+ if (branch)
+ free (branch);
+ }
+ }
+ }
+ else if (!really_quiet)
+ (void) printf (" Sticky Tag:\t\t(none)\n");
+
+ if (edata->date)
+ (void) printf (" Sticky Date:\t\t%s\n", edata->date);
+ else if (!really_quiet)
+ (void) printf (" Sticky Date:\t\t(none)\n");
+
+ if (edata->options && edata->options[0])
+ (void) printf (" Sticky Options:\t%s\n", edata->options);
+ else if (!really_quiet)
+ (void) printf (" Sticky Options:\t(none)\n");
+
+ if (long_format && vers->srcfile)
+ {
+ List *symbols = RCS_symbols(vers->srcfile);
+
+ (void) printf ("\n Existing Tags:\n");
+ if (symbols)
+ {
+ xrcsnode = finfo->rcs;
+ (void) walklist (symbols, tag_list_proc, NULL);
+ }
+ else
+ (void) printf ("\tNo Tags Exist\n");
+ }
+ }
+
+ (void) printf ("\n");
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+status_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Examining %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Print out a tag and its type
+ */
+static int
+tag_list_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ char *branch = NULL;
+
+ if (RCS_isbranch (xrcsnode, p->key))
+ branch = RCS_whatbranch(xrcsnode, p->key) ;
+
+ (void) printf ("\t%-25.25s\t(%s: %s)\n", p->key,
+ branch ? "branch" : "revision",
+ branch ? branch : p->data);
+
+ if (branch)
+ free (branch);
+
+ return (0);
+}
diff --git a/contrib/cvs/src/subr.c b/contrib/cvs/src/subr.c
new file mode 100644
index 0000000..8ed9177
--- /dev/null
+++ b/contrib/cvs/src/subr.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Various useful functions for the CVS support code.
+ */
+
+#include "cvs.h"
+
+extern char *getlogin ();
+
+/*
+ * malloc some data and die if it fails
+ */
+char *
+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)
+ error (1, 0, "can not allocate %lu bytes", (unsigned long) bytes);
+ 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)
+ error (1, 0, "can not reallocate %lu bytes", (unsigned long) bytes);
+ return (cp);
+}
+
+/*
+ * 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';
+}
+
+/*
+ * Recover the space allocated by Find_Names() and line2argv()
+ */
+void
+free_names (pargc, argv)
+ int *pargc;
+ char **argv;
+{
+ register int i;
+
+ for (i = 0; i < *pargc; i++)
+ { /* only do through *pargc */
+ free (argv[i]);
+ }
+ *pargc = 0; /* and set it to zero when done */
+}
+
+/*
+ * Convert a line into argc/argv components and return the result in the
+ * arguments as passed. Use free_names() to return the memory allocated here
+ * back to the free pool.
+ */
+void
+line2argv (pargc, argv, line)
+ int *pargc;
+ char **argv;
+ char *line;
+{
+ char *cp;
+
+ *pargc = 0;
+ for (cp = strtok (line, " \t"); cp; cp = strtok ((char *) NULL, " \t"))
+ {
+ argv[*pargc] = xstrdup (cp);
+ (*pargc)++;
+ }
+}
+
+/*
+ * Returns the number of dots ('.') found in an RCS revision number
+ */
+int
+numdots (s)
+ const char *s;
+{
+ int dots = 0;
+
+ for (; *s; s++)
+ {
+ if (*s == '.')
+ dots++;
+ }
+ return (dots);
+}
+
+/*
+ * Get the caller's login from his uid. If the real uid is "root" try LOGNAME
+ * USER or getlogin(). If getlogin() and getpwuid() both fail, return
+ * the uid as a string.
+ */
+char *
+getcaller ()
+{
+ static char uidname[20];
+ struct passwd *pw;
+ char *name;
+ uid_t uid;
+
+ uid = getuid ();
+ if (uid == (uid_t) 0)
+ {
+ /* super-user; try getlogin() to distinguish */
+ if (((name = getlogin ()) || (name = getenv("LOGNAME")) ||
+ (name = getenv("USER"))) && *name)
+ return (name);
+ }
+ if ((pw = (struct passwd *) getpwuid (uid)) == NULL)
+ {
+ (void) sprintf (uidname, "uid%lu", (unsigned long) uid);
+ return (uidname);
+ }
+ return (pw->pw_name);
+}
+
+#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)
+ char *rev1;
+ char *rev2;
+{
+ int dots;
+ char gca[PATH_MAX];
+ char *p[2];
+ int j[2];
+
+ if (rev1 == NULL || rev2 == NULL)
+ {
+ error (0, 0, "sanity failure in gca");
+ abort();
+ }
+
+ /* 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;
+ 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';
+ }
+
+ return (xstrdup (gca));
+}
+
+/*
+ * 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 the original argument or a pointer to readonly
+ * static storage.
+ */
+char *
+make_message_rcslegal (message)
+ char *message;
+{
+ if ((message == NULL) || (*message == '\0') || isspace (*message))
+ {
+ char *t;
+
+ if (message)
+ for (t = message; *t; t++)
+ if (!isspace (*t))
+ return message;
+
+ return "*** empty log message ***\n";
+ }
+
+ return message;
+}
diff --git a/contrib/cvs/src/tag.c b/contrib/cvs/src/tag.c
new file mode 100644
index 0000000..2e30009
--- /dev/null
+++ b/contrib/cvs/src/tag.c
@@ -0,0 +1,781 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * Tag
+ *
+ * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
+ * Uses the checked out revision in the current directory.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+static int check_fileproc PROTO((struct file_info *finfo));
+static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+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((char *dir, char *repos, char *update_dir));
+static int tag_fileproc PROTO((struct file_info *finfo));
+
+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 */
+
+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] [-r tag|-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-[rD]\tExisting tag or date.\n",
+ "\t-f\tForce a head revision if tag etc not found.\n",
+ "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
+ "\t-F\tMove tag if it already exists\n",
+ NULL
+};
+
+int
+tag (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+
+ if (argc == -1)
+ usage (tag_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "FQqlRdr: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 '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 (local)
+ send_arg("-l");
+ if (delete_flag)
+ send_arg("-d");
+ 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_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ 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,
+ argc, argv, local, W_LOCAL, 0, 1,
+ (char *) NULL, 1, 0);
+
+ if (err)
+ {
+ error (1, 0, "correct the above errors first!");
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (tag_fileproc, (FILESDONEPROC) NULL, tag_dirproc,
+ (DIRLEAVEPROC) NULL, argc, argv, local,
+ W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+ 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 (finfo)
+ 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->repository, (char *) NULL, (char *) NULL,
+ (char *) NULL, finfo->file, 0, 0,
+ finfo->entries, finfo->rcs);
+ if (vers->srcfile == NULL)
+ {
+ if (!really_quiet)
+ error (0, 0, "nothing known about %s", finfo->file);
+ return (1);
+ }
+ p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match, 0);
+ if (p->data != NULL)
+ {
+ int addit = 1;
+ char *oversion;
+
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
+ if (oversion == NULL)
+ {
+ if (delete_flag)
+ {
+ addit = 0;
+ }
+ }
+ 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(err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ 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(*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("%s %s %s %s",
+ filter,
+ symtag,
+ delete_flag ? "del" : force_tag_move ? "mov" : "add",
+ repository);
+ walklist(tlist, pretag_list_proc, NULL);
+ return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
+}
+
+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 (finfo)
+ struct file_info *finfo;
+{
+ char *version, *oversion;
+ char *nversion = NULL;
+ char *rev;
+ Vers_TS *vers;
+ int retcode = 0;
+
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 0, 0, finfo->entries, finfo->rcs);
+
+ if ((numtag != NULL) || (date != NULL))
+ {
+ nversion = RCS_getversion(vers->srcfile,
+ numtag,
+ date,
+ force_tag_match, 0);
+ 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, 0);
+ if (version == NULL || vers->srcfile == NULL)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ free (version);
+
+ if ((retcode = RCS_deltag(vers->srcfile->path, symtag, 1)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag %s from %s", symtag,
+ vers->srcfile->path);
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ /* warm fuzzies */
+ if (!really_quiet)
+ {
+ (void) printf ("D %s\n", finfo->fullname);
+ }
+
+ 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, 0);
+ if (oversion != NULL)
+ {
+ int isbranch = RCS_isbranch (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 */
+ (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);
+ freevers_ts (&vers);
+ return (0);
+ }
+ free (oversion);
+ }
+
+ if ((retcode = RCS_settag(vers->srcfile->path, 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);
+ }
+
+ /* more warm fuzzies */
+ if (!really_quiet)
+ {
+ (void) printf ("T %s\n", finfo->fullname);
+ }
+
+ if (nversion != NULL)
+ {
+ free (nversion);
+ }
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+tag_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "%s %s", delete_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;
+};
+
+/* Pass as a static until we get around to fixing start_recursion to pass along
+ a void * where we can stash it. */
+static struct val_args *val_args_static;
+
+static int val_fileproc PROTO ((struct file_info *finfo));
+
+static int
+val_fileproc (finfo)
+ struct file_info *finfo;
+{
+ RCSNode *rcsdata;
+ struct val_args *args = val_args_static;
+ 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, 0);
+ 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 ((char *, char *, char *));
+
+static Dtype
+val_direntproc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ /* 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 (name[0]))
+ {
+ char *p;
+ for (p = name; *p != '\0'; ++p)
+ {
+ if (!(isdigit (*p) || *p == '.'))
+ error (1, 0, "\
+Numeric tag %s contains characters other than digits and '.'", name);
+ }
+ return;
+ }
+
+ mytag.dptr = name;
+ mytag.dsize = strlen (name);
+
+ valtags_filename = xmalloc (strlen (CVSroot) + sizeof CVSROOTADM
+ + sizeof CVSROOTADM_HISTORY + 20);
+ strcpy (valtags_filename, CVSroot);
+ 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. */
+
+ the_val_args.name = name;
+ the_val_args.found = 0;
+ val_args_static = &the_val_args;
+
+ which = W_REPOS | W_ATTIC;
+
+ if (repository != NULL)
+ {
+ if (repository[0] == '\0')
+ which |= W_LOCAL;
+ else
+ {
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+ if (chdir (repository) < 0)
+ error (1, errno, "cannot change to %s directory", repository);
+ }
+ }
+
+ err = start_recursion (val_fileproc, (FILESDONEPROC) NULL,
+ val_direntproc, (DIRLEAVEPROC) NULL,
+ argc, argv, local, which, aflag,
+ 1, NULL, 1, 0);
+ 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);
+}
diff --git a/contrib/cvs/src/update.c b/contrib/cvs/src/update.c
new file mode 100644
index 0000000..2478316
--- /dev/null
+++ b/contrib/cvs/src/update.c
@@ -0,0 +1,1830 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ *
+ * "update" updates the version in the present directory with respect to the RCS
+ * repository. The present version must have been created by "checkout". The
+ * user can keep up-to-date by calling "update" whenever he feels like it.
+ *
+ * The present version can be committed by "commit", but this keeps the version
+ * in tact.
+ *
+ * Arguments following the options are taken to be file names to be updated,
+ * rather than updating the entire directory.
+ *
+ * Modified or non-existent RCS files are checked out and reported as U
+ * <user_file>
+ *
+ * Modified user files are reported as M <user_file>. If both the RCS file and
+ * the user file have been modified, the user file is replaced by the result
+ * of rcsmerge, and a backup file is written for the user in .#file.version.
+ * If this throws up irreconcilable differences, the file is reported as C
+ * <user_file>, and as M <user_file> otherwise.
+ *
+ * Files added but not yet committed are reported as A <user_file>. Files
+ * removed but not yet committed are reported as R <user_file>.
+ *
+ * If the current directory contains subdirectories that hold concurrent
+ * versions, these are updated too. If the -d option was specified, new
+ * directories added to the repository are automatically created and updated
+ * as well.
+ */
+
+#include "cvs.h"
+#ifdef SERVER_SUPPORT
+#include "md5.h"
+#endif
+#include "watch.h"
+#include "fileattr.h"
+#include "edit.h"
+
+static int checkout_file PROTO((char *file, char *repository, List *entries,
+ RCSNode *rcsnode, Vers_TS *vers_ts, char *update_dir));
+#ifdef SERVER_SUPPORT
+static int patch_file PROTO((char *file, char *repository, List *entries,
+ RCSNode*rcsnode, Vers_TS *vers_ts, char *update_dir,
+ int *docheckout, struct stat *file_info,
+ unsigned char *checksum));
+#endif
+static int isemptydir PROTO((char *dir));
+static int merge_file PROTO((char *file, char *repository, List *entries,
+ Vers_TS *vers, char *update_dir));
+static int scratch_file PROTO((char *file, char *repository, List * entries,
+ char *update_dir));
+static Dtype update_dirent_proc PROTO((char *dir, char *repository, char *update_dir));
+static int update_dirleave_proc PROTO((char *dir, int err, char *update_dir));
+static int update_fileproc PROTO ((struct file_info *));
+static int update_filesdone_proc PROTO((int err, char *repository,
+ char *update_dir));
+static int write_letter PROTO((char *file, int letter, char *update_dir));
+#ifdef SERVER_SUPPORT
+static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts,
+ char *update_dir, List *entries, char *repository));
+#else
+static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts,
+ char *update_dir, List *entries));
+#endif
+
+static char *options = NULL;
+static char *tag = NULL;
+static char *date = NULL;
+static char *join_rev1, *date_rev1;
+static char *join_rev2, *date_rev2;
+static int aflag = 0;
+static int force_tag_match = 1;
+static int update_build_dirs = 0;
+static int update_prune_dirs = 0;
+static int pipeout = 0;
+#ifdef SERVER_SUPPORT
+static int 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-d\tBuild directories, like checkout does.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-p\tSend updates to standard output.\n",
+ "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+ "\t-r rev\tUpdate using specified revision/tag.\n",
+ "\t-D date\tSet date to update from.\n",
+ "\t-j rev\tMerge in changes made between current revision and rev.\n",
+ "\t-I ign\tMore files to ignore (! to reset).\n",
+ "\t-W spec\tWrappers specification line.\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 = 1;
+ while ((c = getopt (argc, argv, "ApPflRQqduk:r:D:j:I:W:")) != -1)
+ {
+ switch (c)
+ {
+ case 'A':
+ aflag = 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;
+ else
+#endif
+ usage (update_usage);
+ break;
+ case '?':
+ default:
+ usage (update_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* 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. */
+ 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 (update_prune_dirs)
+ send_arg("-P");
+ client_prune_dirs = update_prune_dirs;
+ option_with_arg ("-r", tag);
+ if (date)
+ client_senddate (date);
+ if (join_rev1)
+ option_with_arg ("-j", join_rev1);
+ if (join_rev2)
+ option_with_arg ("-j", join_rev2);
+
+ /* 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. */
+ if (failed_patches == NULL)
+ {
+ struct request *rq;
+
+ for (rq = requests; rq->name != NULL; rq++)
+ {
+ if (strcmp (rq->name, "update-patches") == 0)
+ {
+ if (rq->status == rq_supported)
+ {
+ send_arg("-u");
+ }
+ break;
+ }
+ }
+ }
+
+ if (failed_patches == NULL)
+ {
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_files (argc, argv, local, aflag);
+ }
+ else
+ {
+ int i;
+
+ (void) printf ("%s client: refetching unpatchable files\n",
+ program_name);
+
+ if (toplevel_wd[0] != '\0'
+ && chdir (toplevel_wd) < 0)
+ {
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+
+ for (i = 0; i < failed_patches_count; i++)
+ (void) unlink_file (failed_patches[i]);
+ send_file_names (failed_patches_count, failed_patches, 0);
+ send_files (failed_patches_count, failed_patches, local,
+ aflag);
+ }
+
+ failed_patches = NULL;
+ failed_patches_count = 0;
+
+ send_to_server ("update\012", 0);
+
+ status = get_responses_and_close ();
+ if (status != 0)
+ return status;
+
+ } while (failed_patches != NULL);
+
+ return 0;
+ }
+#endif
+
+ if (tag != NULL)
+ tag_check_valid (tag, argc, argv, local, aflag, "");
+ /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2
+ yet (make sure to handle ':' correctly if we do, though). */
+
+ /*
+ * 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);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (".", Name_Repository (NULL, NULL), tag, date);
+#endif
+ }
+ }
+
+ /* 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;
+
+ /* call the recursion processor */
+ err = start_recursion (update_fileproc, update_filesdone_proc,
+ update_dirent_proc, update_dirleave_proc,
+ argc, argv, local, which, aflag, 1,
+ preload_update_dir, 1, 0);
+
+ /* see if we need to sleep before returning */
+ if (last_register_time)
+ {
+ time_t now;
+
+ (void) time (&now);
+ if (now == last_register_time)
+ sleep (1); /* to avoid time-stamp races */
+ }
+
+ return (err);
+}
+
+/*
+ * This is the callback proc for update. It is called for each file in each
+ * directory by the recursion code. The current directory is the local
+ * instantiation. file is the file name we are to operate on. update_dir is
+ * set to the path relative to where we started (for pretty printing).
+ * repository is the repository. entries and srcfiles are the pre-parsed
+ * entries and source control files.
+ *
+ * This routine decides what needs to be done for each file and does the
+ * appropriate magic for checkout
+ */
+static int
+update_fileproc (finfo)
+ struct file_info *finfo;
+{
+ int retval;
+ Ctype status;
+ Vers_TS *vers;
+
+ status = Classify_File (finfo->file, tag, date, options, force_tag_match,
+ aflag, finfo->repository, finfo->entries, finfo->rcs, &vers,
+ finfo->update_dir, pipeout);
+ 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->file, finfo->repository, finfo->entries, finfo->rcs,
+ vers, finfo->update_dir);
+ 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;
+ (void) write_letter (finfo->file, 'C', finfo->update_dir);
+ break;
+ case T_NEEDS_MERGE: /* needs merging */
+ if (noexec)
+ {
+ retval = 1;
+ (void) write_letter (finfo->file, 'C', finfo->update_dir);
+ }
+ else
+ {
+ if (wrap_merge_is_copy (finfo->file))
+ /* Should we be warning the user that we are
+ * overwriting the user's copy of the file? */
+ retval = checkout_file (finfo->file, finfo->repository, finfo->entries,
+ finfo->rcs, vers, finfo->update_dir);
+ else
+ retval = merge_file (finfo->file, finfo->repository, finfo->entries,
+ vers, finfo->update_dir);
+ }
+ break;
+ case T_MODIFIED: /* locally modified */
+ retval = 0;
+ 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)
+ {
+ /*
+ * If the timestamps differ, look for Conflict
+ * indicators to see if 'C' anyway.
+ */
+ run_setup ("%s", GREP);
+ run_arg (RCS_MERGE_PAT);
+ run_arg (finfo->file);
+ retcode = run_exec (RUN_TTY, DEVNULL,
+ RUN_TTY,RUN_NORMAL);
+ if (retcode == -1)
+ {
+ error (1, errno,
+ "fork failed while examining conflict in `%s'",
+ finfo->fullname);
+ }
+ }
+ if (!retcode)
+ {
+ (void) write_letter (finfo->file, 'C', finfo->update_dir);
+ 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)
+ retval = write_letter (finfo->file, 'M', finfo->update_dir);
+ 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->file, finfo->repository, finfo->entries, finfo->rcs,
+ vers, finfo->update_dir, &docheckout,
+ &file_info, checksum);
+ if (! docheckout)
+ {
+ if (server_active && retval == 0)
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ SERVER_PATCHED, &file_info,
+ checksum);
+ break;
+ }
+ }
+ /* Fall through. */
+ /* If we're not running as a server, just check the
+ file out. It's simpler and faster than starting up
+ two new processes (diff and patch). */
+ /* Fall through. */
+#endif
+ case T_CHECKOUT: /* needs checkout */
+ retval = checkout_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs,
+ vers, finfo->update_dir);
+#ifdef SERVER_SUPPORT
+ if (server_active && retval == 0)
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+#endif
+ break;
+ case T_ADDED: /* added but not committed */
+ retval = write_letter (finfo->file, 'A', finfo->update_dir);
+ break;
+ case T_REMOVED: /* removed but not committed */
+ retval = write_letter (finfo->file, 'R', finfo->update_dir);
+ break;
+ case T_REMOVE_ENTRY: /* needs to be un-registered */
+ retval = scratch_file (finfo->file, finfo->repository, finfo->entries, finfo->update_dir);
+#ifdef SERVER_SUPPORT
+ if (server_active && retval == 0)
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) 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)
+#ifdef SERVER_SUPPORT
+ join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries, finfo->repository);
+#else
+ join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries);
+#endif
+
+ /* 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;
+{
+ (void) write_letter (file, '?', dir);
+}
+
+/* ARGSUSED */
+static int
+update_filesdone_proc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ /* if this directory has an ignore list, process it then free it */
+ if (ignlist)
+ {
+ ignore_files (ignlist, update_dir, 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 );
+ }
+
+ return (err);
+}
+
+/*
+ * update_dirent_proc () is called back by the recursion processor before a
+ * sub-directory is processed for update. In this case, update_dirent proc
+ * will probably create the directory unless -d isn't specified and this is a
+ * new directory. A return code of 0 indicates the directory should be
+ * processed by the recursion code. A return of non-zero indicates the
+ * recursion code should skip this directory.
+ */
+static Dtype
+update_dirent_proc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ if (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);
+
+ if (noexec)
+ {
+ /* ignore the missing dir if -n is specified */
+ error (0, 0, "New directory `%s' -- ignored", dir);
+ return (R_SKIP_ALL);
+ }
+ else
+ {
+ /* otherwise, create the dir and appropriate adm files */
+ make_directory (dir);
+ Create_Admin (dir, update_dir, repository, tag, date);
+ }
+ }
+ /* 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[PATH_MAX];
+
+ (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
+ }
+
+ /* keep the CVS/Tag file current with the specified arguments */
+ if (aflag || tag || date)
+ {
+ WriteTag (dir, tag, date);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (update_dir, repository, tag, date);
+#endif
+ }
+
+ /* initialize the ignore list for this directory */
+ ignlist = getlist ();
+ }
+
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Updating %s", update_dir);
+
+ return (R_PROCESS);
+}
+
+/*
+ * update_dirleave_proc () is called back by the recursion code upon leaving
+ * a directory. It will prune empty directories if needed and will execute
+ * any appropriate update programs.
+ */
+/* ARGSUSED */
+static int
+update_dirleave_proc (dir, err, update_dir)
+ char *dir;
+ int err;
+ char *update_dir;
+{
+ FILE *fp;
+
+ /* run the update_prog if there is one */
+ if (err == 0 && !pipeout && !noexec &&
+ (fp = fopen (CVSADM_UPROG, "r")) != NULL)
+ {
+ char *cp;
+ char *repository;
+ char line[MAXLINELEN];
+
+ repository = Name_Repository ((char *) NULL, update_dir);
+ if (fgets (line, sizeof (line), fp) != NULL)
+ {
+ if ((cp = strrchr (line, '\n')) != NULL)
+ *cp = '\0';
+ run_setup ("%s %s", line, repository);
+ (void) printf ("%s %s: Executing '", program_name, command_name);
+ run_print (stdout);
+ (void) printf ("'\n");
+ (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ }
+ (void) fclose (fp);
+ free (repository);
+ }
+
+ /* FIXME: chdir ("..") loses with symlinks. */
+ /* Prune empty dirs on the way out - if necessary */
+ (void) chdir ("..");
+ if (update_prune_dirs && isemptydir (dir))
+ {
+ /* 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);
+ }
+
+ return (err);
+}
+
+/*
+ * Returns 1 if the argument directory is completely empty, other than the
+ * existence of the CVS directory entry. Zero otherwise.
+ */
+static int
+isemptydir (dir)
+ char *dir;
+{
+ DIR *dirp;
+ struct dirent *dp;
+
+ if ((dirp = opendir (dir)) == NULL)
+ {
+ error (0, 0, "cannot open directory %s for empty check", dir);
+ return (0);
+ }
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 &&
+ strcmp (dp->d_name, CVSADM) != 0)
+ {
+ (void) closedir (dirp);
+ return (0);
+ }
+ }
+ (void) closedir (dirp);
+ return (1);
+}
+
+/*
+ * scratch the Entries file entry associated with a file
+ */
+static int
+scratch_file (file, repository, entries, update_dir)
+ char *file;
+ char *repository;
+ List *entries;
+ char *update_dir;
+{
+ history_write ('W', update_dir, "", file, repository);
+ Scratch_Entry (entries, file);
+ (void) unlink_file (file);
+ return (0);
+}
+
+/*
+ * check out a file - essentially returns the result of the fork on "co".
+ */
+static int
+checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
+ char *file;
+ char *repository;
+ List *entries;
+ RCSNode *rcsnode;
+ Vers_TS *vers_ts;
+ char *update_dir;
+{
+ char backup[PATH_MAX];
+ int set_time, retval = 0;
+ int retcode = 0;
+ int status;
+ int file_is_dead;
+
+ /* don't screw with backup files if we're going to stdout */
+ if (!pipeout)
+ {
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file);
+ if (isfile (file))
+ rename_file (file, backup);
+ else
+ (void) unlink_file (backup);
+ }
+
+ 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)
+ {
+ (void) fprintf (stderr, "\
+===================================================================\n");
+ if (update_dir[0])
+ (void) fprintf (stderr, "Checking out %s/%s\n",
+ update_dir, file);
+ else
+ (void) fprintf (stderr, "Checking out %s\n", file);
+ (void) fprintf (stderr, "RCS: %s\n", vers_ts->srcfile->path);
+ (void) fprintf (stderr, "VERS: %s\n", vers_ts->vn_rcs);
+ (void) fprintf (stderr, "***************\n");
+ }
+ }
+
+ status = RCS_checkout (vers_ts->srcfile->path,
+ pipeout ? NULL : file, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY, 0, 0);
+ }
+ if (file_is_dead || status == 0)
+ {
+ if (!pipeout)
+ {
+ Vers_TS *xvers_ts;
+ int resurrecting;
+
+ resurrecting = 0;
+
+ if (file_is_dead && joining())
+ {
+ if (RCS_getversion (vers_ts->srcfile, join_rev1,
+ date_rev1, 1, 0)
+ || (join_rev2 != NULL &&
+ RCS_getversion (vers_ts->srcfile, join_rev2,
+ date_rev2, 1, 0)))
+ {
+ /* when joining, we need to get dead files checked
+ out. Try harder. */
+ /* I think that RCS_FLAGS_FORCE is here only because
+ passing -f to co used to enable checking out
+ a dead revision in the old version of death
+ support which used a hacked RCS instead of using
+ the RCS state. */
+ retcode = RCS_checkout (vers_ts->srcfile->path, file,
+ vers_ts->vn_rcs,
+ vers_ts->options, RUN_TTY,
+ RCS_FLAGS_FORCE, 0);
+ if (retcode != 0)
+ {
+ error (retcode == -1 ? 1 : 0,
+ retcode == -1 ? errno : 0,
+ "could not check out %s", file);
+ (void) unlink_file (backup);
+ return (retcode);
+ }
+ file_is_dead = 0;
+ resurrecting = 1;
+ }
+ else
+ {
+ /* If the file is dead and does not contain either of
+ the join revisions, then we don't want to check it
+ out. */
+ return 0;
+ }
+ }
+
+ if (cvswrite == TRUE
+ && !file_is_dead
+ && !fileattr_get (file, "_watched"))
+ xchmod (file, 1);
+
+ {
+ /* 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 (file, getcaller (), NULL);
+
+ memset (&args, 0, sizeof args);
+ args.remove_temp = 1;
+ watch_modify_watchers (file, &args);
+ }
+
+ /* set the time from the RCS file iff it was unknown before */
+ if (vers_ts->vn_user == NULL ||
+ strncmp (vers_ts->ts_rcs, "Initial", 7) == 0)
+ {
+ set_time = 1;
+ }
+ else
+ set_time = 0;
+
+ wrap_fromcvs_process_file (file);
+
+ xvers_ts = Version_TS (repository, options, tag, date, file,
+ force_tag_match, set_time, entries, rcsnode);
+ if (strcmp (xvers_ts->options, "-V4") == 0)
+ xvers_ts->options[0] = '\0';
+
+ (void) time (&last_register_time);
+
+ if (file_is_dead)
+ {
+ if (xvers_ts->vn_user != NULL)
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "warning: %s is not (any longer) pertinent",
+ file);
+ else
+ error (0, 0,
+ "warning: %s/%s is not (any longer) pertinent",
+ update_dir, file);
+ }
+ Scratch_Entry (entries, file);
+ if (unlink_file (file) < 0 && ! existence_error (errno))
+ {
+ if (update_dir[0] == '\0')
+ error (0, errno, "cannot remove %s", file);
+ else
+ error (0, errno, "cannot remove %s/%s", update_dir,
+ file);
+ }
+ }
+ else
+ Register (entries, file,
+ resurrecting ? "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', update_dir, xvers_ts->vn_rcs, file,
+ repository);
+
+ freevers_ts (&xvers_ts);
+
+ if (!really_quiet && !file_is_dead)
+ {
+ write_letter (file, 'U', update_dir);
+ }
+ }
+ }
+ else
+ {
+ int old_errno = errno; /* save errno value over the rename */
+
+ if (!pipeout && isfile (backup))
+ rename_file (backup, file);
+
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+ "could not check out %s", file);
+
+ retval = retcode;
+ }
+
+ if (!pipeout)
+ (void) unlink_file (backup);
+
+ return (retval);
+}
+
+#ifdef SERVER_SUPPORT
+/* Patch a file. Runs rcsdiff. 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 (file, repository, entries, rcsnode, vers_ts, update_dir,
+ docheckout, file_info, checksum)
+ char *file;
+ char *repository;
+ List *entries;
+ RCSNode *rcsnode;
+ Vers_TS *vers_ts;
+ char *update_dir;
+ int *docheckout;
+ struct stat *file_info;
+ unsigned char *checksum;
+{
+ char backup[PATH_MAX];
+ char file1[PATH_MAX];
+ char file2[PATH_MAX];
+ int retval = 0;
+ int retcode = 0;
+ int fail;
+ FILE *e;
+
+ *docheckout = 0;
+
+ if (pipeout || joining ())
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file);
+ if (isfile (file))
+ rename_file (file, backup);
+ else
+ (void) unlink_file (backup);
+
+ (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, file);
+ (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, 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. */
+ if (noexec)
+ retcode = 0;
+ else
+ retcode = RCS_checkout (vers_ts->srcfile->path, NULL,
+ vers_ts->vn_user,
+ vers_ts->options, file1, 0, 0);
+ if (retcode != 0)
+ fail = 1;
+ else
+ {
+ e = fopen (file1, "r");
+ if (e == NULL)
+ fail = 1;
+ else
+ {
+ if (fseek (e, (long) -1, SEEK_END) == 0
+ && getc (e) != '\n')
+ {
+ fail = 1;
+ }
+ fclose (e);
+ }
+ }
+
+ if (! fail)
+ {
+ /* Check it out into file, and then move to file2, so that we
+ can get the right modes into *FILE_INFO. We can't check it
+ out directly into file2 because co doesn't understand how
+ to do that. */
+ retcode = RCS_checkout (vers_ts->srcfile->path, file,
+ vers_ts->vn_rcs,
+ vers_ts->options, RUN_TTY, 0, 0);
+ if (retcode != 0)
+ fail = 1;
+ else
+ {
+ if (!isreadable (file))
+ {
+ /* File is dead. */
+ fail = 1;
+ }
+ else
+ {
+ rename_file (file, file2);
+ if (cvswrite == TRUE
+ && !fileattr_get (file, "_watched"))
+ xchmod (file2, 1);
+ e = fopen (file2, "r");
+ if (e == NULL)
+ fail = 1;
+ else
+ {
+ struct MD5Context context;
+ int nl;
+ unsigned char buf[8192];
+ unsigned len;
+
+ nl = 0;
+
+ /* Compute the MD5 checksum and make sure there is
+ a trailing newline. */
+ MD5Init (&context);
+ while ((len = fread (buf, 1, sizeof buf, e)) != 0)
+ {
+ nl = buf[len - 1] == '\n';
+ MD5Update (&context, buf, len);
+ }
+ MD5Final (checksum, &context);
+
+ if (ferror (e) || ! nl)
+ {
+ fail = 1;
+ }
+
+ fclose (e);
+ }
+ }
+ }
+ }
+
+ retcode = 0;
+ if (! fail)
+ {
+ /* FIXME: This whole thing with diff/patch is rather more
+ convoluted than necessary (lots of forks and execs, need to
+ worry about versions of diff and patch, etc.). Also, we
+ send context lines which aren't needed (in the rare case in
+ which the diff doesn't apply, the checksum would catches it).
+ Solution perhaps is to librarify the RCS routines which apply
+ deltas or something equivalent. */
+ /* This is -c, not -u, because we have no way of knowing which
+ DIFF is in use. */
+ run_setup ("%s -c %s %s", DIFF, file1, file2);
+
+ /* A retcode of 0 means no differences. 1 means some differences. */
+ if ((retcode = run_exec (RUN_TTY, file, RUN_TTY, RUN_NORMAL)) != 0
+ && retcode != 1)
+ {
+ fail = 1;
+ }
+ else
+ {
+#define BINARY "Binary"
+ char buf[sizeof BINARY];
+ unsigned int c;
+
+ /* Check the diff output to make sure patch will be handle it. */
+ e = fopen (file, "r");
+ if (e == NULL)
+ error (1, errno, "could not open diff output file %s", file);
+ 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 (repository, options, tag, date, file,
+ force_tag_match, 0, entries, rcsnode);
+ if (strcmp (xvers_ts->options, "-V4") == 0)
+ xvers_ts->options[0] = '\0';
+
+ Register (entries, file, xvers_ts->vn_rcs,
+ xvers_ts->ts_user, xvers_ts->options,
+ xvers_ts->tag, xvers_ts->date, NULL);
+
+ if (stat (file2, file_info) < 0)
+ error (1, errno, "could not stat %s", file2);
+
+ /* If this is really Update and not Checkout, recode history */
+ if (strcmp (command_name, "update") == 0)
+ history_write ('P', update_dir, xvers_ts->vn_rcs, file,
+ repository);
+
+ freevers_ts (&xvers_ts);
+
+ if (!really_quiet)
+ {
+ write_letter (file, 'P', update_dir);
+ }
+ }
+ else
+ {
+ int old_errno = errno; /* save errno value over the rename */
+
+ if (isfile (backup))
+ rename_file (backup, file);
+
+ if (retcode != 0 && retcode != 1)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+ "could not diff %s", file);
+
+ *docheckout = 1;
+ retval = retcode;
+ }
+
+ (void) unlink_file (backup);
+ (void) unlink_file (file1);
+ (void) unlink_file (file2);
+
+ return (retval);
+}
+#endif
+
+/*
+ * Several of the types we process only print a bit of information consisting
+ * of a single letter and the name.
+ */
+static int
+write_letter (file, letter, update_dir)
+ char *file;
+ int letter;
+ char *update_dir;
+{
+ if (!really_quiet)
+ {
+ char buf[2];
+ buf[0] = letter;
+ buf[1] = ' ';
+ cvs_output (buf, 2);
+ if (update_dir[0])
+ {
+ cvs_output (update_dir, 0);
+ cvs_output ("/", 1);
+ }
+ cvs_output (file, 0);
+ cvs_output ("\n", 1);
+ }
+ return (0);
+}
+
+/*
+ * Do all the magic associated with a file which needs to be merged
+ */
+static int
+merge_file (file, repository, entries, vers, update_dir)
+ char *file;
+ char *repository;
+ List *entries;
+ Vers_TS *vers;
+ char *update_dir;
+{
+ char user[PATH_MAX];
+ char backup[PATH_MAX];
+ int status;
+ int retcode = 0;
+
+ /*
+ * The users currently modified file is moved to a backup file name
+ * ".#filename.version", so that it will stay around for a few days
+ * before being automatically removed by some cron daemon. The "version"
+ * is the version of the file that the user was most up-to-date with
+ * before the merge.
+ */
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
+ if (update_dir[0])
+ (void) sprintf (user, "%s/%s", update_dir, file);
+ else
+ (void) strcpy (user, file);
+
+ (void) unlink_file (backup);
+ copy_file (file, backup);
+ xchmod (file, 1);
+
+ status = RCS_merge(vers->srcfile->path,
+ 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, user);
+ error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+ user, backup);
+ rename_file (backup, file);
+ return (1);
+ }
+
+ if (strcmp (vers->options, "-V4") == 0)
+ vers->options[0] = '\0';
+ (void) time (&last_register_time);
+ {
+ char *cp = 0;
+
+ if (status)
+ cp = time_stamp (file);
+ Register (entries, file, vers->vn_rcs, vers->ts_rcs, 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 (file, update_dir, repository, backup);
+ server_updated (file, update_dir, repository, SERVER_MERGED,
+ (struct stat *) NULL, (unsigned char *) NULL);
+ }
+#endif
+
+ if (!noexec && !xcmp (backup, file))
+ {
+ printf ("%s already contains the differences between %s and %s\n",
+ user, vers->vn_user, vers->vn_rcs);
+ history_write ('G', update_dir, vers->vn_rcs, file, repository);
+ return (0);
+ }
+
+ if (status == 1)
+ {
+ if (!noexec)
+ error (0, 0, "conflicts found in %s", user);
+
+ write_letter (file, 'C', update_dir);
+
+ history_write ('C', update_dir, vers->vn_rcs, file, repository);
+
+ }
+ else if (retcode == -1)
+ {
+ error (1, errno, "fork failed while examining update of %s", user);
+ }
+ else
+ {
+ write_letter (file, 'M', update_dir);
+ history_write ('G', update_dir, vers->vn_rcs, file, repository);
+ }
+ return (0);
+}
+
+/*
+ * Do all the magic associated with a file which needs to be joined
+ * (-j option)
+ */
+static void
+#ifdef SERVER_SUPPORT
+join_file (file, rcsnode, vers, update_dir, entries, repository)
+ char *repository;
+#else
+join_file (file, rcsnode, vers, update_dir, entries)
+#endif
+ char *file;
+ RCSNode *rcsnode;
+ Vers_TS *vers;
+ char *update_dir;
+ List *entries;
+{
+ char user[PATH_MAX];
+ char backup[PATH_MAX];
+ 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;
+
+ if (wrap_merge_is_copy (file))
+ {
+ /* FIXME: Should be including update_dir in message. */
+ error (0, 0,
+ "Cannot merge %s because it is a merge-by-copy file.", file);
+ return;
+ }
+
+ /* determine if we need to do anything at all */
+ if (vers->srcfile == NULL ||
+ vers->srcfile->path == NULL)
+ {
+ return;
+ }
+
+ /* in all cases, use two revs. */
+
+ /* if only one rev is specified, it becomes the second rev */
+ if (jrev2 == NULL)
+ {
+ jrev2 = jrev1;
+ jrev1 = NULL;
+ jdate2 = jdate1;
+ jdate1 = NULL;
+ }
+
+ /* The file in the working directory doesn't exist in CVS/Entries.
+ FIXME: Shouldn't this case result in additional processing (if
+ the file was added going from rev1 to rev2, then do the equivalent
+ of a "cvs add")? (yes; easier said than done.. :-) */
+ if (vers->vn_user == NULL)
+ {
+ /* No merge possible YET. */
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s is present in revision %s as of %s",
+ file, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s is present in revision %s",
+ file, jrev2);
+ return;
+ }
+
+ /* Fix for bug CVS/193:
+ * Used to dump core if the file had been removed on the current branch.
+ */
+ if (strcmp(vers->vn_user, "0") == 0)
+ {
+ error(0, 0,
+ "file %s has been deleted",
+ file);
+ return;
+ }
+
+ /* convert the second rev spec, walking branches and dates. */
+
+ rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, 0);
+ if (rev2 == NULL)
+ {
+ if (!quiet)
+ {
+ if (jdate2 != NULL)
+ error (0, 0,
+ "cannot find revision %s as of %s in file %s",
+ jrev2, jdate2, file);
+ else
+ error (0, 0,
+ "cannot find revision %s in file %s",
+ jrev2, file);
+ }
+ return;
+ }
+
+ /* skip joining identical revs */
+ if (strcmp (rev2, vers->vn_user) == 0)
+ {
+ /* No merge necessary. */
+ free (rev2);
+ return;
+ }
+
+ if (jrev1 == NULL)
+ {
+ char *tst;
+ /* if the first rev is missing, then it is implied to be the
+ greatest common ancestor of both the join rev, and the
+ checked out rev. */
+
+ /* FIXME: What is this check for '!' about? If it is legal to
+ have '!' in the first character of vn_user, it isn't
+ documented at struct vers_ts in cvs.h. */
+ tst = vers->vn_user;
+ if (*tst == '!')
+ {
+ /* file was dead. merge anyway and pretend it's been
+ added. */
+ ++tst;
+ Register (entries, file, "0", vers->ts_user, vers->options,
+ vers->tag, (char *) 0, (char *) 0);
+ }
+ rev1 = gca (tst, rev2);
+ if (rev1 == NULL)
+ {
+ /* this should not be possible */
+ error (0, 0, "bad gca");
+ abort();
+ }
+
+ tst = RCS_gettag (vers->srcfile, rev2, 1, 0);
+ if (tst == NULL)
+ {
+ /* this should not be possible. */
+ error (0, 0, "cannot find gca");
+ abort();
+ }
+
+ free (tst);
+
+ /* these two cases are noops */
+ if (strcmp (rev1, rev2) == 0)
+ {
+ free (rev1);
+ free (rev2);
+ return;
+ }
+ }
+ else
+ {
+ /* otherwise, convert the first rev spec, walking branches and
+ dates. */
+
+ rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, 0);
+ if (rev1 == NULL)
+ {
+ if (!quiet) {
+ if (jdate1 != NULL)
+ error (0, 0,
+ "cannot find revision %s as of %s in file %s",
+ jrev1, jdate1, file);
+ else
+ error (0, 0,
+ "cannot find revision %s in file %s",
+ jrev1, file);
+ }
+ return;
+ }
+ }
+
+ /* do the join */
+
+#if 0
+ dome {
+ /* special handling when two revisions are specified */
+ if (join_rev1 && join_rev2)
+ {
+ rev = RCS_getversion (vers->srcfile, join_rev2, date_rev2, 1, 0);
+ if (rev == NULL)
+ {
+ if (!quiet && date_rev2 == NULL)
+ error (0, 0,
+ "cannot find revision %s in file %s", join_rev2, file);
+ return;
+ }
+
+ baserev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0);
+ if (baserev == NULL)
+ {
+ if (!quiet && date_rev1 == NULL)
+ error (0, 0,
+ "cannot find revision %s in file %s", join_rev1, file);
+ free (rev);
+ return;
+ }
+
+ /*
+ * nothing to do if:
+ * second revision matches our BASE revision (vn_user) &&
+ * both revisions are on the same branch
+ */
+ if (strcmp (vers->vn_user, rev) == 0 &&
+ numdots (baserev) == numdots (rev))
+ {
+ /* might be the same branch. take a real look */
+ char *dot = strrchr (baserev, '.');
+ int len = (dot - baserev) + 1;
+
+ if (strncmp (baserev, rev, len) == 0)
+ return;
+ }
+ }
+ else
+ {
+ rev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0);
+ if (rev == NULL)
+ return;
+ if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */
+ {
+ free (rev);
+ return;
+ }
+
+ baserev = RCS_whatbranch (file, join_rev1, rcsnode);
+ if (baserev)
+ {
+ char *cp;
+
+ /* we get a branch -- turn it into a revision, or NULL if trunk */
+ if ((cp = strrchr (baserev, '.')) == NULL)
+ {
+ free (baserev);
+ baserev = (char *) NULL;
+ }
+ else
+ *cp = '\0';
+ }
+ }
+ if (baserev && strcmp (baserev, rev) == 0)
+ {
+ /* they match -> nothing to do */
+ free (rev);
+ free (baserev);
+ return;
+ }
+ }
+#endif
+
+ /* OK, so we have two revisions; continue on */
+
+#ifdef SERVER_SUPPORT
+ if (server_active && !isreadable (file))
+ {
+ int retcode;
+ /* The file is up to date. Need to check out the current contents. */
+ retcode = RCS_checkout (vers->srcfile->path, "", vers->vn_user, NULL,
+ RUN_TTY, 0, 0);
+ if (retcode != 0)
+ error (1, retcode == -1 ? errno : 0,
+ "failed to check out %s file", file);
+ }
+#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.
+ */
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
+ if (update_dir[0])
+ (void) sprintf (user, "%s/%s", update_dir, file);
+ else
+ (void) strcpy (user, file);
+
+ (void) unlink_file (backup);
+ copy_file (file, backup);
+ xchmod (file, 1);
+
+ options = vers->options;
+#ifdef HAVE_RCS5
+#if 0
+ if (*options == '\0')
+ options = "-kk"; /* to ignore keyword expansions */
+#endif
+#endif
+
+ status = RCS_merge (vers->srcfile->path, options, rev1, rev2);
+ if (status != 0 && status != 1)
+ {
+ error (0, status == -1 ? errno : 0,
+ "could not merge revision %s of %s", rev2, user);
+ error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+ user, backup);
+ rename_file (backup, file);
+ }
+ free (rev1);
+ free (rev2);
+
+#ifdef SERVER_SUPPORT
+ /*
+ * If we're in server mode, then we need to re-register the file
+ * even if there were no conflicts (status == 0).
+ * This tells server_updated() to send the modified file back to
+ * the client.
+ */
+ if (status == 1 || (status == 0 && server_active))
+#else
+ if (status == 1)
+#endif
+ {
+ char *cp = 0;
+
+ if (status)
+ cp = time_stamp (file);
+ Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options,
+ vers->tag, vers->date, cp);
+ if (cp)
+ free(cp);
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_copy_file (file, update_dir, repository, backup);
+ server_updated (file, update_dir, repository, SERVER_MERGED,
+ (struct stat *) NULL, (unsigned char *) NULL);
+ }
+#endif
+}
+
+int
+joining ()
+{
+ return (join_rev1 != NULL);
+}
diff --git a/contrib/cvs/src/update.h b/contrib/cvs/src/update.h
new file mode 100644
index 0000000..bad6562
--- /dev/null
+++ b/contrib/cvs/src/update.h
@@ -0,0 +1,21 @@
+/* Declarations for update.c.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+int do_update PROTO((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 joining PROTO((void));
diff --git a/contrib/cvs/src/vers_ts.c b/contrib/cvs/src/vers_ts.c
new file mode 100644
index 0000000..34983a1
--- /dev/null
+++ b/contrib/cvs/src/vers_ts.c
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * Copyright (c) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS 1.4 kit.
+ */
+
+#include "cvs.h"
+
+#ifdef SERVER_SUPPORT
+static void time_stamp_server PROTO((char *, Vers_TS *));
+#endif
+
+/*
+ * Fill in and return a Vers_TS structure "user" is the name of the local
+ * file; entries is the entries file - preparsed for our pleasure. rcs is
+ * the current source control file - preparsed for our pleasure.
+ */
+Vers_TS *
+Version_TS (repository, options, tag, date, user, force_tag_match,
+ set_time, entries, rcs)
+ char *repository;
+ char *options;
+ char *tag;
+ char *date;
+ char *user;
+ int force_tag_match;
+ int set_time;
+ List *entries;
+ RCSNode *rcs;
+{
+ Node *p;
+ RCSNode *rcsdata;
+ Vers_TS *vers_ts;
+ struct stickydirtag *sdtp;
+
+ /* get a new Vers_TS struct */
+ vers_ts = (Vers_TS *) xmalloc (sizeof (Vers_TS));
+ 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 (entries == NULL)
+ {
+ sdtp = NULL;
+ p = NULL;
+ }
+ else
+ {
+ p = findnode_fn (entries, user);
+ sdtp = (struct stickydirtag *) entries->list->data; /* list-private */
+ }
+
+ if (p != NULL)
+ {
+ Entnode *entdata = (Entnode *) p->data;
+
+ vers_ts->vn_user = xstrdup (entdata->version);
+ vers_ts->ts_rcs = xstrdup (entdata->timestamp);
+ 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);
+ }
+ if (!options || (options && *options == '\0'))
+ {
+ if (!(sdtp && sdtp->aflag))
+ vers_ts->options = xstrdup (entdata->options);
+ }
+ vers_ts->entdata = entdata;
+ }
+
+ /*
+ * -k options specified on the command line override (and overwrite)
+ * options stored in the entries file
+ */
+ if (options)
+ vers_ts->options = xstrdup (options);
+ else if (!vers_ts->options)
+ {
+ if (sdtp && sdtp->aflag == 0)
+ vers_ts->options = xstrdup (sdtp->options);
+ else if (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 (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);
+ if (!vers_ts->date)
+ vers_ts->date = xstrdup (sdtp->date);
+ }
+
+ /* Now look up the info on the source controlled file */
+ if (rcs != NULL)
+ {
+ rcsdata = rcs;
+ rcsdata->refcount++;
+ }
+ else if (repository != NULL)
+ rcsdata = RCS_parse (user, 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
+ {
+ vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag,
+ vers_ts->date, force_tag_match, 1);
+ if (vers_ts->vn_rcs == NULL)
+ vers_ts->vn_tag = NULL;
+ else
+ {
+ char *colon = strchr (vers_ts->vn_rcs, ':');
+ if (colon)
+ {
+ vers_ts->vn_tag = xstrdup (colon+1);
+ *colon = '\0';
+ }
+ 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)
+ {
+ struct utimbuf t;
+
+ memset ((char *) &t, 0, sizeof (t));
+ if (vers_ts->vn_rcs &&
+ (t.actime = t.modtime = RCS_getrevtime (rcsdata,
+ vers_ts->vn_rcs, (char *) 0, 0)) != -1)
+ (void) utime (user, &t);
+ }
+ }
+
+ /* get user file time-stamp in ts_user */
+ if (entries != (List *) NULL)
+ {
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ time_stamp_server (user, vers_ts);
+ else
+#endif
+ vers_ts->ts_user = time_stamp (user);
+ }
+
+ 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)
+ char *file;
+ Vers_TS *vers_ts;
+{
+ struct stat sb;
+ char *cp;
+
+ if (stat (file, &sb) < 0)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "cannot stat temp file");
+ if (use_unchanged)
+ {
+ /* 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 (vers_ts->entdata == NULL)
+ mark_lost (vers_ts);
+ else if (vers_ts->entdata->timestamp
+ && vers_ts->entdata->timestamp[0] == '=')
+ mark_unchanged (vers_ts);
+ else
+ mark_lost (vers_ts);
+ }
+ else
+ {
+ /* Missing file in the temp directory means that the file
+ was not modified. */
+ mark_unchanged (vers_ts);
+ }
+ }
+ else if (sb.st_mtime == 0)
+ {
+ if (use_unchanged)
+ /* We shouldn't reach this case any more! */
+ abort ();
+
+ /* Special code used by server.c to indicate the file was lost. */
+ mark_lost (vers_ts);
+ }
+ 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 (stat (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/contrib/cvs/src/version.c b/contrib/cvs/src/version.c
new file mode 100644
index 0000000..4848e82
--- /dev/null
+++ b/contrib/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.8.1";
+
+#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/contrib/cvs/src/watch.c b/contrib/cvs/src/watch.c
new file mode 100644
index 0000000..0873489
--- /dev/null
+++ b/contrib/cvs/src/watch.c
@@ -0,0 +1,521 @@
+/* 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "cvs.h"
+#include "edit.h"
+#include "fileattr.h"
+#include "watch.h"
+
+const char *const watch_usage[] =
+{
+ "Usage: %s %s [on|off|add|remove] [-l] [-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",
+ "-a (add/remove): Specify what actions, one of\n",
+ " edit,unedit,commit,all,none\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 ((struct file_info *finfo));
+
+static int
+addremove_fileproc (finfo)
+ struct file_info *finfo;
+{
+ watch_modify_watchers (finfo->file, &the_args);
+ return 0;
+}
+
+static int addremove_filesdoneproc PROTO ((int, char *, char *));
+
+static int
+addremove_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ 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 = 1;
+ while ((c = getopt (argc, argv, "la:")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ 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_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ 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,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 1, 0);
+
+ lock_tree_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 [files...]\n",
+ NULL
+};
+
+static int watchers_fileproc PROTO ((struct file_info *finfo));
+
+static int
+watchers_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char *them;
+ char *p;
+
+ them = fileattr_get0 (finfo->file, "_watchers");
+ if (them == NULL)
+ return 0;
+
+ fputs (finfo->fullname, stdout);
+
+ p = them;
+ while (1)
+ {
+ putc ('\t', stdout);
+ while (*p != '>' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ /* Only happens if attribute is misformed. */
+ putc ('\n', stdout);
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ while (1)
+ {
+ while (*p != '+' && *p != ',' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ putc ('\n', stdout);
+ goto out;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ }
+ putc ('\n', stdout);
+ }
+ out:;
+ return 0;
+}
+
+int
+watchers (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (watchers_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ 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_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ 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,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 1, 0);
+}
diff --git a/contrib/cvs/src/watch.h b/contrib/cvs/src/watch.h
new file mode 100644
index 0000000..d279c71
--- /dev/null
+++ b/contrib/cvs/src/watch.h
@@ -0,0 +1,56 @@
+/* Interface to "cvs watch add", "cvs watchers", and related 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+extern const char *const watch_usage[];
+
+/* Flags to pass between the various functions making up the
+ add/remove code. All in a single structure in case there is some
+ need to make the code reentrant some day. */
+
+struct addremove_args {
+ /* A flag for each watcher type. */
+ int edit;
+ int unedit;
+ int commit;
+
+ /* Are we adding or removing (non-temporary) edit,unedit,and/or commit
+ watches? */
+ int adding;
+
+ /* Should we add a temporary edit watch? */
+ int add_tedit;
+ /* Should we add a temporary unedit watch? */
+ int add_tunedit;
+ /* Should we add a temporary commit watch? */
+ int add_tcommit;
+
+ /* Should we remove all temporary watches? */
+ int remove_temp;
+
+ /* Should we set the default? This is here for passing among various
+ routines in watch.c (a good place for it if there is ever any reason
+ to make the stuff reentrant), not for watch_modify_watchers. */
+ int setting_default;
+};
+
+/* Modify the watchers for FILE. *WHAT tells what to do to them.
+ If FILE is NULL, modify default args (WHAT->SETTING_DEFAULT is
+ not used). */
+extern void watch_modify_watchers PROTO ((char *file,
+ struct addremove_args *what));
+
+extern int watch_add PROTO ((int argc, char **argv));
+extern int watch_remove PROTO ((int argc, char **argv));
diff --git a/contrib/cvs/src/wrapper.c b/contrib/cvs/src/wrapper.c
new file mode 100644
index 0000000..8a6ff94
--- /dev/null
+++ b/contrib/cvs/src/wrapper.c
@@ -0,0 +1,374 @@
+#include "cvs.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
+
+ 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 *conflictHook;
+ 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;
+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()
+{
+ char file[PATH_MAX];
+ struct passwd *pw;
+
+ /* Then add entries found in repository, if it exists */
+ (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_WRAPPER);
+ if (isfile (file)){
+ wrap_add_file(file,0);
+ }
+
+ /* Then add entries found in home dir, (if user has one) and file exists */
+ if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir){
+ (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTWRAPPER);
+ if (isfile (file)){
+ wrap_add_file (file, 0);
+ }
+ }
+
+ /* Then add entries found in CVSWRAPPERS environment variable. */
+ wrap_add (getenv (WRAPPER_ENV), 0);
+}
+
+/*
+ * 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[1024];
+
+ wrap_restore_saved();
+ wrap_kill_temp();
+
+ /* load the file */
+ if (!(fp = fopen (file, "r")))
+ return;
+ while (fgets (line, sizeof (line), fp))
+ wrap_add (line, temp);
+ (void) fclose (fp);
+}
+
+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->conflictHook)
+ free(e->conflictHook);
+}
+
+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(*line))
+ ++line;
+ for(temp=line;*line && !isspace(*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)
+ ;
+
+ if(line==temp+1)
+ break;
+
+ ctemp=*line;
+ *line='\0';
+ switch(opt){
+ case 'f':
+ 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':
+ 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 'c':
+ if(e.conflictHook)
+ free(e.conflictHook);
+ /* FIXME: error message should say where the bad value
+ came from. */
+ e.conflictHook=expand_path (temp, "<wrapper>", 0);
+ if (!e.conflictHook)
+ 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;
+ 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]->conflictHook=e->conflictHook;
+ wrap_list[x]->mergeMethod=e->mergeMethod;
+}
+
+/* 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_saved_count;
+ char *temp;
+
+ for(x=0;x<count;++x)
+ if (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_CONFLICT:
+ temp=wrap_list[x]->conflictHook;
+ break;
+ default:
+ abort ();
+ }
+ if(temp==NULL)
+ return (0);
+ else
+ return (1);
+ }
+ return (0);
+}
+
+WrapperEntry *
+wrap_matching_entry (name)
+ const char *name;
+{
+ int x,count=wrap_count+wrap_saved_count;
+
+ for(x=0;x<count;++x)
+ if (fnmatch (wrap_list[x]->wildCard, name, 0) == 0)
+ return wrap_list[x];
+ return (WrapperEntry *)NULL;
+}
+
+char *
+wrap_tocvs_process_file(fileName)
+ const char *fileName;
+{
+ WrapperEntry *e=wrap_matching_entry(fileName);
+ static char buf[L_tmpnam+1];
+
+ if(e==NULL || e->tocvsFilter==NULL)
+ return NULL;
+
+ tmpnam(buf);
+
+ run_setup(e->tocvsFilter,fileName,buf);
+ run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY );
+
+ 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;
+}
+
+char *
+wrap_fromcvs_process_file(fileName)
+ const char *fileName;
+{
+ WrapperEntry *e=wrap_matching_entry(fileName);
+ static char buf[PATH_MAX];
+
+ if(e==NULL || e->fromcvsFilter==NULL)
+ return NULL;
+
+ run_setup(e->fromcvsFilter,fileName);
+ run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL );
+ return buf;
+}
diff --git a/contrib/cvs/stamp-h.in b/contrib/cvs/stamp-h.in
new file mode 100644
index 0000000..bb5e5e3
--- /dev/null
+++ b/contrib/cvs/stamp-h.in
@@ -0,0 +1 @@
+Wed Oct 5 15:45:29 EDT 1994
diff --git a/contrib/cvs/tools/ChangeLog b/contrib/cvs/tools/ChangeLog
new file mode 100644
index 0000000..c6b6e1c7d
--- /dev/null
+++ b/contrib/cvs/tools/ChangeLog
@@ -0,0 +1,7 @@
+Sun Apr 14 11:07:43 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * .cvsignore: new file.
+
+ * Makefile.in (subdir): `tools', not `contrib'.
+
+ * Added ChangeLog (this file), and subdir `pcl-cvs'.
diff --git a/contrib/cvs/tools/Makefile.in b/contrib/cvs/tools/Makefile.in
new file mode 100644
index 0000000..c83d1a8
--- /dev/null
+++ b/contrib/cvs/tools/Makefile.in
@@ -0,0 +1,76 @@
+# Makefile for GNU CVS auxiliary tools.
+# 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.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# $CVSid: @(#)Makefile.in 1.6 94/10/22 $
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+# Where to put the manual pages.
+mandir = $(prefix)/man
+
+# Use cp if you don't have install.
+INSTALL = @INSTALL@
+
+DISTFILES = ChangeLog README .cvsignore Makefile.in
+
+all: Makefile
+.PHONY: all
+
+install: all
+ @echo "pcl-cvs not installed"
+.PHONY: install
+
+tags:
+.PHONY: tags
+
+TAGS:
+.PHONY: TAGS
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+clean:
+ /bin/rm -f *.o core
+.PHONY: clean
+
+distclean: clean
+ rm -f Makefile pcl-cvs/Makefile
+.PHONY: distclean
+
+realclean: distclean
+.PHONY: realclean
+
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+ cd pcl-cvs; ${MAKE} dist-dir DISTDIR="../${DISTDIR}/pcl-cvs"
+.PHONY: dist-dir
+
+subdir = tools
+Makefile: ../config.status Makefile.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
diff --git a/contrib/cvs/tools/README b/contrib/cvs/tools/README
new file mode 100644
index 0000000..af7bd7e
--- /dev/null
+++ b/contrib/cvs/tools/README
@@ -0,0 +1,5 @@
+ This subdirectory contains tools that can be used with CVS.
+Note that they will not necessarily be installed when you "make
+install" from the top-level of the CVS source tree.
+
+pcl-cvs ............................. an Emacs interface to CVS.
diff --git a/contrib/cvs/tools/pcl-cvs/ChangeLog b/contrib/cvs/tools/pcl-cvs/ChangeLog
new file mode 100644
index 0000000..0192b93
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/ChangeLog
@@ -0,0 +1,891 @@
+Mon Apr 15 01:34:27 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * Makefile: removed. Why was this in the repository in the first
+ place?
+
+ * Makefile.in (BATCHFLAGS): don't pass -q to Emacs when compiling,
+ because Emacs probably can't find Elib's cookie.el[c] if we do
+ that. (Actually, it still can't, but that may be due to a bug in
+ Emacs).
+
+ * INSTALL: reflect changed location of elib in the CVS dist.
+
+Sun Apr 14 12:18:12 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * pcl-cvs.el (cookie): Changed "(load \"cookie\")" to
+ "(require 'cookie)", since elib is now included again.
+ Moved "(provide 'pcl-cvs)" to the end of the file, so it's not
+ provided if the package didn't load successfully.
+
+ * Makefile.in (subdir): tools/pcl-cvs now, not contrib/pcl-cvs.
+ (BATCHFLAGS): removed "-n" from BATCHFLAGS. Emacs 19.30 does not
+ know about this flag.
+ (OBJDIR_DISTFILES): don't include .elc files here, add comment
+ explaining why.
+
+Thu Apr 11 20:22:34 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * pcl-cvs.el (cvs-mode-map): conform to Emacs 19 keybinding
+ conventions by not binding any regular letters under C-c.
+
+Fri Feb 9 14:29:07 1996 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (mostlyclean clean realclean): Remove 'realclean'
+ from this target list; it's already defined later in the file.
+
+Tue Jan 23 13:02:24 1996 Greg A. Woods <woods@most.weird.com>
+
+ * pcl-cvs.el (pcl-cvs-bugs-address): change the default address
+ as suggested by Per Cederqvist.
+ * pcl-cvs.el: removed comments refering to Signum, etc.
+
+Sun Jan 21 12:51:12 1996 Greg A. Woods <woods@most.weird.com>
+
+ * pcl-cvs.el (cvs-parse-stderr): fix typo (missing '\') that was
+ causing occasional un-reported, un-traced, failures that simply
+ said something like "RE missing '\(' or '\\('" -- hopefully this
+ is the last such bug!
+
+Tue Jan 16 13:57:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in: Rename "dist" target back to "dist-dir". The
+ latter is what actually gets used.
+ (pcl-cvs.dvi): Restore srcdir to pcl-cvs.texinfo. Fix typo
+ (pcl-cvs.texifo -> pcl-cvs.texinfo).
+ (TEXINDEX,TEX,SET_TEXINPUTS): New variables.
+ (.el.elc): Copy .el file to build dir so .elc file gets put there.
+ (dist-dir): Fix typo (cvs.info -> pcl-cvs.info).
+ * cookie.el: New file, copied from elib 1.0.
+ * README: Remove note about requiring elib; it claimed that CVS
+ contained a copy of elib, but it lied.
+ * pcl-cvs.el: Change (require 'cookie) to (load "cookie.el").
+ * pcl-cvs-lucid.el: Change (require 'pcl-cvs) to (load "pcl-cvs.el").
+
+Fri Jan 12 10:32:14 1996 Greg A. Woods <woods@most.weird.com>
+
+ * pcl-cvs.elc, pcl-cvs-lucid.elc: removed
+
+ * pcl-cvs.el: run through the spell checker...
+ - noted some free variables in comments
+ (cvs-inhibit-copyright-message): move this above
+ cvs-startup-message to keep the compiler quiet
+
+ * compile-all.el: removed (use make for dependency checking!)
+
+ * Makefile.in: tweak various comments and echo messages...
+ (elcfiles): removed this target.
+ (.SUFFIXES, .el.elc): added support for elisp files.
+ (CORE): new macro -- list of files all .elc depend on [still empty]
+ (BATCHFLAGS): new macro -- flags to pass to emacs
+ (OBJDIR_DISTFILES): added ELCFILES to be shipped in distribution
+
+ * README: fix the RCS Id.
+
+ * INSTALL: re-copy formatted makeinfo output from pcl-cvs.info,
+ just to keep everything in proper synchronisation.
+
+ * pcl-cvs.texinfo (Pcl-cvs installation): update to match Karl's
+ new wording from INSTALL.
+
+Wed Jan 10 22:04:35 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * INSTALL: make first item read a little more smoothly.
+
+ * README: note that pcl-cvs has been tested under 19.30.
+
+Wed Jan 10 17:59:00 1996 Greg A. Woods <woods@most.weird.com>
+
+ * ChangeLog.woods: these are changes integrated in from my
+ own pcl-cvs repository module, and based on the original PCL-CVS
+ Version 1.05 release. They include most, if not all, of the
+ changes from the Cygnus and Cyclic CVS contrib versions of
+ PCL-CVS (i.e. the changes noted below).
+
+Sat Dec 30 15:01:45 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * pcl-cvs.el (cvs-changelog-ours-p): check that
+ `add-log-full-name' and `add-log-mailing-address' are non-nil, in
+ addition to checking that they are boundp.
+
+Thu Dec 21 16:45:48 1995 Karl Fogel <kfogel@occs.cs.oberlin.edu>
+
+ * pcl-cvs.el (cvs-parse-stderr): ignore kerberos connection
+ failure, since CVS will automatically try rsh next. I think this
+ is okay because if a person needs to know that kerberos failed,
+ then chances are the rsh failed too, and *that* error message will
+ clue them in that something's afoot.
+
+Wed Nov 22 11:01:50 1995 Joshua Cowan <jcowan@hermit.reslife.okstate.edu>
+
+ * pcl-cvs.el (cvs-changelog-ours-p): use `user-full-name' if
+ `add-log-full-name' unbound, as not every uses the stuff in
+ add-log.el. Same with `add-log-mailing-address'.
+ (cvs-changelog-entries): change to `change-log-mode' unless
+ already in it.
+
+Sun Jul 9 20:57:11 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * "/bin/rmdir" as default, not "/usr/local/bin/rmdir".
+
+Fri Jun 16 15:24:34 1995 Jim Kingdon (kingdon@cyclic.com)
+
+ * pcl-cvs.elc, pcl-cvs-lucid.elc: Added.
+
+ * Makefile.in: Rename from Makefile and set srcdir.
+
+Thu May 18 17:10:27 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ Automatically guess CVS log entries from ChangeLog contents.
+ * pcl-cvs.el (cvs-mode-changelog-commit): New command.
+ (cvs-changelog-full-paragraphs): New variable.
+ (cvs-changelog-name, cvs-narrow-changelog,
+ cvs-changelog-paragraph, cvs-changelog-subparagraph,
+ cvs-changelog-entry, cvs-changelog-ours-p, cvs-relative-path,
+ cvs-changelog-entries, cvs-changelog-insert-entries, cvs-union,
+ cvs-insert-changelog-entries, cvs-edit-delete-common-indentation):
+ New functions.
+ (cvs-mode-map): Bind 'C' to cvs-mode-changelog-commit.
+ (cvs-mode): Mention cvs-mode-changelog-commit in docstring.
+
+ Give the info files names ending in ".info".
+ * Makefile (INFOFILES, install_info): Change pcl-cvs to
+ pcl-cvs.info.
+ (pcl-cvs.info): Target renamed from pcl-cvs.
+ (DISTFILES): pcl-cvs removed; we handle the info files explicitly
+ in the dist-dir target.
+ (dist-dir): Depend on pcl-cvs.info. Distribute pcl-cvs.info*.
+ * pcl-cvs.texinfo: Change @setfilename appropriately.
+ * INSTALL: Updated.
+ * .cvsignore: Correctly ignore the info files.
+
+ * README: Note that pcl-cvs has been tested under 19.28, and that
+ the "cookie" naming conflict was resolved in 19.11.
+
+ * Makefile (pcl-cvs-lucid.elc): Changed this target from
+ pcl-cvs-lucid.el. That's a source file, for goodness' sake!
+
+Tue May 9 13:56:50 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Change references to "Cygnus's remote CVS" to "Cyclic CVS".
+
+Wed May 3 13:55:27 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * pcl-cvs.el (cvs-parse-stderr): Handle colons after both
+ "rcsmerge" and "warning".
+
+Fri Apr 28 22:38:14 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile (ELFILES): Include pcl-cvs-startup.el.
+ (info, pcl-cvs): Call makeinfo appropriately for modern versions.
+ (pcl-cvs.aux): List dependency on pcl-cvs.texinfo.
+ (pcl-cvs.ps): New target.
+ (DVIPS): New variable.
+ (dist-dir): Renamed from dist, updated to accept DISTDIR value
+ passed from parent.
+ (DISTFILES): New varible.
+ (pcl-cvs.elc, pcl-cvs-lucid.elc): Add targets to elcfiles target.
+
+Tue Apr 25 21:33:49 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * pcl-cvs.el: (cvs-parse-stderr): Recognize "conflicts" as well as
+ "overlaps" before "during merge."
+
+Thu Feb 16 12:17:20 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * pcl-cvs.el (cvs-parse-stderr): Recognize "conflicts found in..."
+ messages attributed to "cvs server", as well as "cvs update".
+
+Sat Feb 4 01:47:01 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * pcl-cvs.el: Deal with the 'P' action, produced by remote CVS.
+ (cvs-parse-stdout): Treat 'P' like 'U' --- file is updated.
+
+Tue Jan 31 23:31:39 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * pcl-cvs.el (cvs-cvsroot-required): New variable.
+ (cvs-do-update): If cvs-cvsroot-required is not set, don't complain if
+ CVSROOT and cvs-cvsroot are both unset.
+
+Sun Jan 22 21:22:22 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * pcl-cvs.el (cvs-parse-stderr):
+ Some changes for Cygnus's Remote CVS. Treat
+ messages like "cvs server: Updating DIRECTORY" as we treat those like
+ "cvs update: Updating DIRECTORY". Ignore other messages starting with
+ "cvs server".
+
+ * pcl-cvs.el (cvs-parse-stderr): Re-indent.
+
+ * .cvsignore: Add ignore list for Texinfo litter.
+
+ * Makefile (lispdir): Set appropriately for totoro.
+ * pcl-cvs.el (cvs-program, cvs-diff-program, cvs-rmdir-program): Same.
+
+Tue Jun 1 00:00:03 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * Release 1.05. (This release was promised before the end of May,
+ but I didn't quite make it. No, I didn't fake the date above).
+
+Mon May 31 01:32:25 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * Removed the elib sub-directory. Users must now get the Elib
+ library separately.
+ * pcl-cvs.texinfo: Document it.
+
+ * pcl-cvs-lucid.el: A new version, supplied by Jamie Zawinsky,
+ added.
+
+ * pcl-cvs Id 68: Transform RCS keywords
+ * Makefile (pcl-cvs-$(VER)): Remove the $ signs in most files in
+ the distribution.
+
+ * pcl-cvs Id 76: Extra " in cvs-mode-add.
+ * pcl-cvs.el (cvs-mode-add): Don't add the extra level of quotes
+ around the log message, since it doesn't work with CVS.
+
+ * pcl-cvs Id 56: '-d <CVSROOT>' support in pcl-cvs
+ * pcl-cvs.el (cvs-change-cvsroot): New function.
+
+ * pcl-cvs Id 77: *cvs* isn't cleared properly
+ * pcl-cvs.el (cvs-do-update): Always erase the *cvs* buffer and
+ re-create the collection.
+
+ * pcl-cvs.el (cvs-do-update): Set mode-line-process in the *cvs*
+ buffer.
+ * pcl-cvs.el (cvs-mode): Reset mode-line-process.
+
+ * pcl-cvs Id 59: sort .cvsignore alphabetically!
+ * pcl-cvs.el (cvs-sort-ignore-file): New variable.
+ * pcl-cvs.el (cvs-mode-ignore): Use it.
+ * pcl-cvs.texinfo: Document it.
+
+ * pcl-cvs Id 75: Require final newline.
+ * pcl-cvs.el (cvs-commit-buffer-require-final-newline): New
+ variable.
+ * pcl-cvs.el (cvs-edit-done): Use it.
+ * pcl-cvs.texinfo: Document it.
+
+ * pcl-cvs Id 72: make clean deletes lucid-emacs.el
+ * dist-makefile (ELCFILES): Fixed a typo.
+
+ * pcl-cvs Id 46: "cvs remove f" "touch f" "cvs update f" -> parse err.
+ * pcl-cvs.el (cvs-fileinfo->type): New type: REM-EXIST.
+ * pcl-cvs.el (cvs-shadow-entry-p): A REMOVED that follows a
+ REM-EXIST is a shadow.
+ * pcl-cvs.el (cvs-parse-stderr): Recognize the "should be removed
+ and is still there" message.
+ * pcl-cvs.el (cvs-pp): Recognize REM-EXIST.
+ * pcl-cvs.el (cvs-mode-undo-local-changes): Recognize and complain
+ about REM-EXIST. Defensive test added: complain about unknown types.
+
+ * pcl-cvs.el (cvs-mode-add): Add an extra level of quotes around
+ the log message. This is apparently needed by RCVS. <This change
+ has been removed. --ceder>.
+
+ * pcl-cvs.el (cvs-parse-stderr): Ignore output from RCVS.
+
+Tue Apr 27 00:48:40 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * pcl-cvs.el (cvs-startup-message): Now a defconst instead of a
+ defvar.
+ * pcl-cvs.el (cvs-mode-commit): Add a defvar for it.
+
+ * dist-makefile (EMACS): Use $(EMACS) instead of hard-coding 'emacs'.
+
+Sat Apr 17 12:47:10 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * Release 1.04.
+
+ * pcl-cvs.texinfo: Updated the Contributors node.
+
+ * pcl-cvs Id 58: Lucid GNU Emacs support
+ * pcl-cvs-lucid.el: New file, contributed by the people at Lucid.
+ * pcl-cvs.el: Autoload pcl-cvs-lucid if running in an Lucid GNU
+ Emacs.
+ * compile-all.el: (files-to-compile): Add pcl-cvs-lucid.
+ * dist-makefile (ELFILES, ELCFILES): Dito.
+
+ * pcl-cvs Id 55: cvs-diff-backup swaps old and new version.
+ * pcl-cvs.el (cvs-diff-backup-extractor): Old version should be
+ first.
+ * pcl-cvs.el (cvs-mode-diff-backup): Call cvs-backup-diffable
+ correctly.
+
+ * pcl-cvs Id 64: elib substitute
+ * dist-makefile (install): Warn about Elib.
+ * pcl-cvs.texinfo: Talk about Elib.
+
+ * pcl-cvs Id 50: Committing the *commit* buffer twice.
+ * pcl-cvs.el (cvs-edit-done): Report an error if cvs-commit-list
+ is empty, and empty it when the commit is done.
+
+ * pcl-cvs Id 56: '-d <CVSROOT>' support.
+ * pcl-cvs.el (cvs-cvsroot): New variable.
+ * pcl-cvs.el (cvs-do-update, all callers of cvs-execute-list): Use
+ it everywhere CVS is called, to override CVSROOT.
+ * pcl-cvs.texinfo (Customization): Document it.
+
+Thu Apr 1 00:34:55 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * pcl-cvs.el (cvs-retrieve-revision-to-tmpfile): Exit status nil
+ from call-process means everything was successful in some Emacs
+ versions.
+
+ * pcl-cvs.el (cvs-mode-map): Bind "q" to bury-buffer.
+ * pcl-cvs.texinfo: Document it.
+
+Thu Mar 11 00:05:03 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * Release 1.03-Emerge (not released).
+
+ * Makefile (pcl-cvs-$(VER)): Don't includ elib-dll-debug.el in the
+ distribution. (It's included as elib/dll-debug.el).
+
+ * pcl-cvs.el (cvs-mode): Document the "e" key (cvs-mode-emerge).
+
+Tue Mar 9 00:02:57 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * pcl-cvs.texinfo (Emerge): New node.
+
+ * pcl-cvs.el (cvs-kill-buffer-visiting): New function.
+
+ * pcl-cvs.el (cvs-mode-emerge): Handle Conflict and Merged files.
+
+ * pcl-cvs.el (cvs-retrieve-revision-to-tmpfile): Handle any revision.
+
+ * pcl-cvs.el (cvs-fileinfo-*): Store base-revision instead of
+ backup-file.
+
+ * pcl-cvs.el (cvs-backup-diffable): The file is only diffable if
+ the backup file is readable.
+
+ * pcl-cvs.el (cvs-mode-map): Bind "e" to cvs-mode-emerge instead
+ of cvs-mode-find-file (which is anyhow bound to "f").
+
+Mon Mar 8 23:06:52 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * pcl-cvs.el (cvs-mode-emerge): New function. Currently only
+ handles emerge of Modified files.
+
+ * pcl-cvs.el (cvs-retrieve-revision-to-tmpfile): New function.
+
+Sun Jan 24 20:07:18 1993 Per Cederqvist (ceder@lysator.liu.se)
+
+ * elib-dll-debug.el: Moved to elib.
+
+Mon Jan 18 00:35:59 1993 Per Cederqvist (ceder@mauritz)
+
+ * pcl-cvs.el (cvs-do-update): Added a probably unnecessary sit-for.
+
+ * Release 1.03-Elib-0.05.1 (not released).
+
+ * Elib 0.05 compatibility:
+ * elib-dll-debug.el, pcl-cvs-buffer.el, test-dll.el: Fix the
+ require strings.
+ * pcl-cvs.el (cvs-pp): Insert the string.
+
+ * Release 1.03-Elib-0.05 (not released).
+
+ * elib: New directory, containing the parts of elib that are
+ required for pcl-cvs. Changes to the files in that directory
+ that are present in Elib are documented in the ChangeLog of
+ Elib, not here.
+ * Makefile (pcl-cvs-$(VER)): Copy the new dir to the distribution.
+ * dist-makefile (ELFILES, ELCFILES): Don't include the Elib files.
+
+Fri Jan 8 02:43:49 1993 Per Cederqvist (ceder@konrad)
+
+ * pcl-cvs.el (cvs-mode-map): Bind "e" to cvs-mode-find-file, like
+ in dired.
+
+Sun Jan 3 23:25:13 1993 Per Cederqvist (ceder@konrad)
+
+ * elib-dll.el, elib-node.el, cookie.el: Moved to the elib package.
+ Pcl-cvs now requires elib.
+
+Tue Dec 29 22:06:57 1992 Per Cederqvist (ceder@konrad)
+
+ * pcl-cvs.el: Tracked the latest (last?) rename of all functions
+ in cookie.el.
+
+Thu Sep 24 00:29:16 1992 Per Cederqvist (ceder@robert)
+
+ * pcl-cvs.texinfo (Archives): This version is not distributed with
+ CVS 1.3, so don't claim that it is.
+
+Fri Aug 21 15:17:08 1992 Per Cederqvist (ceder@maskros)
+
+ * pcl-cvs.el (cvs-parse-stderr): Fixed two "(set head" that should
+ be "(setq head".
+
+Thu Aug 20 05:53:58 1992 Per Cederqvist (ceder@robin)
+
+ * cookie.el: Changes to this file is documented in the ChangeLog
+ of elib in the future.
+
+Tue Aug 18 03:30:28 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el: Don't use cookie-last-tin (which no longer exists).
+
+ * cookie.el: Use prefix cookie:: for internal functions.
+
+ * cookie.el: (cookie:enter-after, cookie:enter-before,
+ cookie:nth-cookie): Implemented.
+ * cookie.el: No longer define (impl).
+
+ * cookie.el: More renames:
+ cookie:next-cookie -> cookie:goto-next-tin
+ cookie:previous-cookie -> cookie:goto-previous-tin
+ tin-next -> cookie:next-tin
+ tin-previous -> cookie:previous-tin
+ tin-nth -> cookie:nth-tin
+ tin-delete -> cookie:delete-tin
+ cookie:collect -> cookie:collect-cookies
+ cookie:tin-collect -> cookie:collect-tins
+ (new) -> cookie:tin-collect-cookies
+ (new) -> cookie:tin-collect-tins
+ cookie:refresh -> cookie:refresh-all
+ tin-invalidate-tins -> cookie:invalidate-tins
+
+Mon Aug 17 01:39:49 1992 Per Cederqvist (ceder@robin)
+
+ * cookie.el (cookie:set-buffer-bind-dll-let*): New macro. Used in
+ many places instead of cookie:set-buffer-bind-dll.
+ * cookie.el (cookie:set-buffer-bind-dll): Renamed the macro
+ cookie:set-buffer to this.
+
+ * pcl-cvs.el (cvs-use-temp-buffer): Set default-directory.
+
+Sun Aug 16 20:51:30 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-add-sub): Fixed call to cvs-add-file-update-buffer.
+
+Sat Aug 8 20:28:21 1992 Per Cederqvist (ceder@robin)
+
+ * Release 1.03-Cookie-II (not released).
+
+ * pcl-cvs.el (cvs-mode-diff-cvs): Don't care about the exit status
+ from ``cvs diff''.
+
+ * pcl-cvs.el (cvs-mode): Document cvs-mode-undo-local-changes.
+ * pcl-cvs.el (cvs-diffable): New function.
+
+ * pcl-cvs.el: Use the new cookie package.
+ * pcl-cvs.el (cvs-cookie-handle): New variable.
+ * pcl-cvs.el (cvs-do-update): User the new cookie:create
+ interface, and cookie:clear if the buffer already existed. Make
+ the buffer read-only.
+ * pcl-cvs.el (cvs-mode-next-line, cvs-mode-previous-line): New
+ functions (used instead of cookie:next-cookie and
+ cookie:previous-cookie).
+
+ * cookie.el: Major redesign. The handle that is passed to all
+ cookie functions is now a new datatype, and not the buffer that
+ the cookies resides in. This way it is possible to have more than
+ one set of cookies in a buffer. Things that used to be
+ buffer-local variables are now fields in the handle data type.
+ cookie-last-tin is no longer available.
+ * cookie.el (cookie:create): The buffer is not cleared, nor set to
+ be read-only.
+ * cookie.el (cookie:next-cookie, cookie:previous-cookie): Since
+ the first argument is now a handle and not a buffer, these can no
+ longer be called interactively. You have to write a small wrapper
+ about them.
+ * cookie.el (cookie:buffer): New function.
+
+Tue Aug 4 03:02:25 1992 Per Cederqvist (ceder@robert)
+
+ * pcl-cvs.texinfo (Bugs): Renamed "Reporting bugs and ideas" to
+ "Bugs" and added a table of known bugs/FAQ:s.
+
+Mon Aug 3 00:19:39 1992 Per Cederqvist (ceder@robert)
+
+ * pcl-cvs.el, pcl-cvs.texinfo: Big Renaming Time!
+ The commands that operate in the *cvs* buffer:
+ cvs-add-change-log-entry-other-window -> cvs-mode-add-change-log-entry-other-window
+ cvs-mark-all-files -> cvs-mode-mark-all-files
+ cvs-revert-updated-buffers -> cvs-mode-revert-updated-buffers
+ cvs-undo-local-changes -> cvs-mode-undo-local-changes
+ cvs-unmark-up -> cvs-mode-unmark-up
+ cvs-acknowledge -> cvs-mode-acknowledge
+ cvs-unmark-all-files -> cvs-mode-unmark-all-files
+ cvs-add -> cvs-mode-add
+ cvs-diff-backup -> cvs-mode-diff-backup
+ cvs-commit -> cvs-mode-commit
+ cvs-diff-cvs -> cvs-mode-diff-cvs
+ cvs-find-file -> cvs-mode-find-file
+ cvs-update-no-prompt -> cvs-mode-update-no-prompt
+ cvs-ignore -> cvs-mode-ignore
+ cvs-log -> cvs-mode-log
+ cvs-mark -> cvs-mode-mark
+ cvs-find-file-other-window -> cvs-mode-find-file-other-window
+ cvs-remove-file -> cvs-mode-remove-file
+ cvs-status -> cvs-mode-status
+ cvs-remove-handled -> cvs-mode-remove-handled
+ cvs-unmark -> cvs-mode-unmark
+
+ * pcl-cvs.el (cvs-cvs-diff-flags): Variable deleted.
+ * pcl-cvs.el (cvs-diff-cvs): Use cvs-diff-flags instead.
+ * pcl-cvs.texinfo (Customization): Update the doc.
+
+ * pcl-cvs.el (cvs-diff-cvs): Handle exit status 0 (no diffs), 1
+ (diffs) and other (error).
+ * pcl-cvs.el (cvs-execute-list): Add support for this kind of
+ thing.
+
+ * Revert buffers for committed files:
+ * pcl-cvs.el (cvs-auto-revert-after-commit): New variable.
+ * pcl-cvs.texinfo (Committing changes, Customization): Document
+ it.
+ * pcl-cvs.el (cvs-after-commit-function): New function.
+
+ * pcl-cvs.el (cvs-execute-list): Return the exit status or nil.
+ * pcl-cvs.el (cvs-edit-done, cvs-diff-cvs, cvs-remove-file,
+ cvs-undo-local-changes, cvs-add, cvs-status, cvs-log): Use the
+ exit status to generate an error message.
+
+
+ * pcl-cvs.el (cvs-do-update): It should be "cvs -n update -l", not
+ "cvs -l update -n". Put the -n and/or -l in the message that is
+ displayed in the *cvs* buffer during the update.
+
+Sat Aug 1 00:55:49 1992 Per Cederqvist (ceder@robert)
+
+ * cookie.el (cookie-sort): New function.
+
+ * cookie.el (cookie-clear): Rewritten. No longer clears all local
+ variables.
+
+Tue Jul 28 17:21:17 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-parse-stderr): Try to handle the output from RCS
+ when it is compiled without DIFF3_BIN and a conflict occurs.
+
+ * pcl-cvs.texinfo (Getting Started): Fixed typo.
+
+ * pcl-cvs-startup.el (cvs-update-other-window): Make the autoload
+ be interactive.
+
+Mon Jul 27 19:36:40 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-revert-updated-buffers, cvs-revert-fileinfo):
+ New functions.
+ * pcl-cvs.texinfo (Reverting your buffers): Document it.
+
+ * pcl-cvs.el (cvs-fileinfo->full-path): New function.
+ * pcl-cvs.el (cvs-full-path): Use it.
+
+ * cookie.el (cookie-map, cookie-map-reverse): Better doc-
+ string. Removed the unused local variable 'result'.
+
+ * compile-all.el: Renamed elib-files to files-to-compare.
+ * compile-all.el (compile-pcl-cvs): Bind load-path in a let
+ statement instead of globally.
+
+Thu Jul 23 19:02:41 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-do-update): Check that CVSROOT is set.
+ * pcl-cvs.el (cvs-diff-cvs): Check that cvs-cvs-diff-flags is a
+ list.
+ * pcl-cvs.el (cvs-diff-backup): Check that cvs-diff-flags is a
+ list.
+
+Tue Jul 21 11:27:39 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-parse-error): Make the *cvs* buffer writeable
+ before trying to write the email message. Require sendmail before
+ trying to switch to mail-mode.
+
+ * pcl-cvs.el (cvs-do-update): Check that cvs-program exists.
+
+ * pcl-cvs.el (cvs-skip-line): Fixed bracketing error.
+
+Mon Jul 20 10:31:51 1992 Per Cederqvist (ceder@robin)
+
+ * Release 1.03.
+
+ * pcl-cvs.el, cookie.el: Indentation fixes.
+
+ * Makefile (pcl-cvs-$(VER)): Include NEWS in the distribution.
+
+ * pcl-cvs.el (cvs-rm-program): Deleted.
+ * pcl-cvs.el (cvs-rmdir-program, cvs-lock-file): New variables.
+
+ * Handle lock files in a nicer way:
+ * pcl-cvs.el (cvs-update-filter, cvs-delete-lock,
+ cvs-lock-file-p): New functions.
+ * pcl-cvs.el (cvs-do-update, cvs-sentinel): Redirect stdout to the
+ temporary file, not stderr. Use cvs-update-filter.
+ * pcl-cvs.el (cvs-parse-update): New arguments.
+ * pcl-cvs.el (cvs-parse-buffer): Renamed to cvs-parse-update.
+ * pcl-cvs.el (cvs-stderr-file): Renamed to cvs-stdout-file.
+ * pcl-cvs.texinfo (Miscellaneous commands, Updating the
+ directory): Document cvs-delete-lock.
+
+ * pcl-cvs.el (cvs-mode): Don't reset buffer-read-only.
+
+ * pcl-cvs.el (cvs-find-file-other-window): Don't save-some-buffers.
+
+Thu Jul 16 00:19:58 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el, test-cookie-el: Use the new names from cookie.el.
+
+ * cookie.el: Big Renaming Time!
+ External functions:
+ cookie-next -> tin-next
+ cookie-previous -> tin-previous
+ cookie-nth -> tin-nth
+ cookie-delete -> tin-delete
+ cookie-filter-tins -> tin-filter
+ cookie-get-selection -> tin-get-selection
+ cookie-start-marker -> tin-start-marker
+ cookie-end-marker -> tin-end-marker
+ cookie-invalidate-tins -> tin-invalidate-tins
+ cookie-collect-tins -> tin-collect
+ cookie-collect-cookies -> cookie-collect
+ Internal functions:
+ cookie-create-tin -> cookie-create-wrapper
+ cookie-tin-start-marker -> cookie-wrapper-start-marker
+ cookie-tin-cookie-safe -> cookie-wrapper-cookie-safe
+ cookie-tin-cookie -> cookie-wrapper-cookie
+ set-cookie-tin-start-marker -> cookie-wrapper-set-start-marker
+ set-cookie-tin-cookie -> cookie-wrapper-set-cookie
+ cookie-tin-p -> cookie-wrapper-p
+ cookie-create-tin-and-insert -> cookie-create-wrapper-and-insert
+
+ * pcl-cvs.el (cvs-find-file, cvs-find-file-other-window): Signal
+ an appropriate error message if the *cvs* buffer is empty.
+
+ * cookie.el (cookie-create): Make the buffer read-only.
+ * cookie.el (cookie-create-tin-and-insert, cookie-refresh,
+ cookie-delete-tin-internal, cookie-refresh-tin): Bind
+ buffer-read-only to nil while changing the contents of
+ the buffer.
+
+ * pcl-cvs.el (cvs-byte-compile-files): New function.
+ * pcl-cvs.texinfo (Miscellaneous commands): Document it.
+
+ * pcl-cvs.el (cvs-diff-ignore-marks): New variable.
+ * pcl-cvs.el (cvs-diff-cvs, cvs-diff-backup): Don't consider
+ marked files to be selected if a prefix argument is given XOR the
+ variable cvs-diff-ignore-marks is non-nil.
+ * pcl-cvs.el (cvs-get-marked): New optional argument `ignore-marks'.
+ * pcl-cvs.texinfo (Customization, Viewing differences): Document
+ this behaviour.
+
+ * pcl-cvs.el (cvs-undo-local-changes): New function.
+ * pcl-cvs.texinfo (Undoing changes): Document
+ cvs-undo-local-changes.
+ * pcl-cvs.el (cvs-mode-map): cvs-unmark-all-files moved from "U"
+ to "ESC DEL". cvs-undo-local-changes bound to "U".
+ * pcl-cvs.texinfo (Marking files): Document ESC DEL.
+
+ * pcl-cvs.el (cvs-skip-line): New arguments. All callers updated.
+ Now calls cvs-parse-error if a parse error occurs.
+ * pcl-cvs.el (cvs-parse-error): New function that creates a bug
+ report.
+ * pcl-cvs.el (cvs-parse-stderr, cvs-parse-stdout): New arguments.
+ The only caller (cvs-parse-buffer) updated. Call cvs-parse-error
+ in case of parse error.
+
+ * pcl-cvs.el (pcl-cvs-version): New variable.
+
+ * cookie.el (cookie-create): Kill all local variables in the buffer.
+
+Fri Jul 10 11:17:40 1992 Per Cederqvist (ceder@robin)
+
+ * Release 1.03beta1.
+
+Thu Jul 9 03:12:00 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-update-running): New variable.
+ * pcl-cvs.el (cvs-do-update): Use it instead of the previous local
+ variable cvs-process (that no longer exists). Make sure that only
+ one `cvs update' runs at any given moment.
+ * pcl-cvs.el (cvs-sentinel): Reset cvs-update-running when the
+ update process exits.
+
+ * pcl-cvs.el (cvs-update): Switch to the *cvs* buffer.
+ * pcl-cvs.el (cvs-update-other-window): New function.
+ * pcl-cvs-startup.el (cvs-update-other-window): Added a autoload
+ for it.
+ * pcl-cvs.el (cvs-do-update): Don't pop up any buffer in a window
+ - let cvs-update or cvs-update-other-window handle that. Also
+ don't kill the *cvs* buffer, but rather insert a "Running cvs..."
+ message into it.
+ * pcl-cvs.el (cvs-parse-buffer): Don't change the window
+ configuration.
+
+ * pcl-cvs.el (cvs-create-fileinfo, cvs-pp, cvs-fileninfo->type):
+ New type for a fileinfo: MESSAGE.
+
+ * pcl-cvs.el (cvs-cvs-buffer): Deleted the variable. Use
+ cvs-buffer-name instead. (I no longer have any plans to allow more
+ than one cvs update to run at the same time - things only get
+ confusing). Changed all places where cvs-cvs-buffer was used.
+
+ * pcl-cvs.el: Take care of update programs (the -u option in the
+ modules file):
+ * pcl-cvs.el (cvs-update-prog-output-skip-regexp): New variable.
+ * pcl-cvs.el (cvs-parse-stdout): Skip output from the update
+ program (using cvs-update-prog-output-skip-regexp).
+ * pcl-cvs.texinfo (Future enhancements): Document that the
+ solution is not as good as it should be.
+ * pcl-cvs.texinfo (Customization): Document the variable.
+
+Wed Jul 8 20:29:44 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-do-update): Check that this-dir really exists
+ and is a directory, and that this-dir/CVS exists and is a
+ directory.
+
+Tue Jul 7 01:02:24 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.texinfo (Customization): Document TMPDIR.
+
+ * This chunk of modifications should make it possible to run
+ pcl-cvs on hosts that do not line-buffer stdout (such as
+ DECstation). They work by diverting stdout and stderr from
+ `cvs update' and later sorting them together.
+ * pcl-cvs.el (cvs-parse-stderr): Don't fail to parse conflict
+ data.
+ * pcl-cvs.el (cvs-remove-stdout-shadows, cvs-shadow-entry-p): New
+ functions.
+ * pcl-cvs.el (cvs-parse-buffer): Use it.
+ * pcl-cvs.el (cvs-remove-empty-directories): New function.
+ * pcl-cvs.el (cvs-remove-handled, cvs-parse-buffer): Use it.
+ * pcl-cvs.el (cvs-get-current-dir): New argument ROOT-DIR. All
+ calls to cvs-get-current-dir updated.
+ * pcl-cvs.el (cvs-do-update): Allocate a tmp file. Use cvs-shell
+ (typically /bin/sh) to redirect stderr from CVS to the tmp file.
+ * pcl-cvs.el (cvs-sentinel): Handle the tmp file. Remove it when
+ it is parsed.
+ * pcl-cvs.el (cvs-parse-buffer): New argument STDERR-BUFFER. All
+ calls to cvs-parse-buffer updated. Rewritten to handle the
+ separation of stderr and stdout.
+ * pcl-cvs.el (cvs-shell, cvs-stderr-file): New variables.
+ * pcl-cvs.el (cvs-compare-fileinfos, cvs-parse-stderr,
+ cvs-parse-stdout): New functions.
+
+ * pcl-cvs.el (cvs-parse-buffer): Some modifications for output
+ from RCS 5.6.
+
+Tue Apr 7 09:11:27 1992 Per Cederqvist (ceder@leopold)
+
+ * Release 1.02.
+
+ * pcl-cvs.el (cvs-diff-backup, cvs-edit-done, cvs-status): Call
+ save-some-buffers.
+
+ * pcl-cvs.el (cvs-diff-backup-extractor): Fixed syntax error.
+
+ * Makefile, README, compile-all.el, dist-makefile, pcl-cvs.el,
+ pcl-cvs.texinfo (XXRELEASEXX): A magic string that is substituted
+ for the current release number when a distribution is made.
+ (Release 1.01 says that it is release 1.00).
+
+ * pcl-cvs.el (cvs-find-file): Added missing pair of parenthesis.
+
+Mon Mar 30 14:25:26 1992 Per Cederqvist (ceder@leopold)
+
+ * Release 1.01.
+
+ * pcl-cvs.el (cvs-parse-buffer): The message when waiting for a
+ lock has been changed.
+
+Sun Mar 29 05:29:57 1992 Per Cederqvist (ceder@leopold)
+
+ * Release 1.00.
+
+ * pcl-cvs.el (cvs-do-update, cvs-sentinel, cvs-parse-buffer):
+ Major rewrite of buffer and window selection and handling.
+ The *cvs* buffer is now killed whenever a new "cvs update" is
+ initiated. The -update buffer is replaced with the *cvs*
+ buffer when the update is completed.
+
+Sat Mar 28 21:03:05 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-delete-unused-temporary-buffers): Fixed it.
+
+ * pcl-cvs.el (cvs-auto-remove-handled): New variable.
+ * pcl-cvs.el (cvs-edit-done): Use it.
+ * pcl-cvs.texinfo (Customization, Removing handled entries):
+ Document it.
+
+ * pcl-cvs.el (cvs-mode): Turn of the undo feature. It really
+ isn't useful in a cookie buffer...
+
+ * pcl-cvs.el (cvs-edit-done): Committing a file now looks more
+ like diffing a file. The window handling is better.
+ * pcl-cvs.el (cvs-use-temp-buffer): The &optional switch is no
+ longer needed.
+
+Mon Mar 23 00:20:33 1992 Per Cederqvist (ceder@robin)
+
+ * Release 0.97.
+
+ * pcl-cvs.el (default-directory): Make sure it always ends in a
+ slash. fileinfo->dir does NOT end in a slash, and I had forgotten
+ to call file-name-as-directory in various places.
+
+ * pcl-cvs.el (cvs-diff-backup-extractor): Signal an error if a
+ fileinfo without backup file is given.
+
+ * pcl-cvs.el (cvs-mode): Added documentation.
+
+ * pcl-cvs.el (cvs-execute-list): Fix the order of files in the
+ same directory.
+
+ * pcl-cvs.el (cvs-log-flags, cvs-status-flags): New variables.
+ * pcl-cvs.el (cvs-log, cvs-status): Use them.
+ * pcl-cvs.texinfo (Customization): Document them.
+
+ * pcl-cvs.el (cvs-diff-backup): Filter non-backup-diffable files
+ at an earlier stage, like cvs-commit does.
+
+ * pcl-cvs.el (cvs-diff-flags): New variable.
+ * pcl-cvs.el (cvs-diff-backup): Use it.
+ * pcl-cvs.texinfo (Customization): Document it.
+
+ * pcl-cvs.el (cvs-execute-single-file-list): Remove &rest before
+ last argument. No callers needed updating.
+
+ * pcl-cvs.el (cvs-execute-list): Remove the &rest before the last
+ argument (constant-args). Update all callers of cvs-execute-list
+ to use the new calling convention.
+ * pcl-cvs.el (cvs-cvs-diff-flags): Now a list of strings instead
+ of a string.
+ * pcl-cvs.texinfo (Customization): Document the change to
+ cvs-cvs-diff-flags.
+
+ * Release 0.96.
+
+ * pcl-cvs.el (cvs-cvs-diff-flags): New variable.
+ * pcl-cvs.el (cvs-diff-cvs): Use it.
+ * pcl-cvs.texinfo (Customization, Viewing differences): Document it.
+
+ * pcl-cvs.el (cvs-use-temp-buffe): Don't switch to the temporary
+ buffer. Use display-buffer and set-buffer instead. This way
+ cvs-log, cvs-status, cvs-diff-cvs and friends don't select the
+ temporary buffer. The cursor will remain in the *cvs* buffer.
+
+Sun Mar 22 21:50:18 1992 Per Cederqvist (ceder@robin)
+
+ * pcl-cvs.el (cvs-find-file, cvs-find-file-other-window): Don't
+ prompt when reading in a directory in dired.
+
+ * Makefile (pcl-cvs-$(VER)): Include pcl-cvs-startup.el in the
+ distribution.
+
+ * dist-makefile (pcl-cvs.dvi): Don't fail even if texindex does
+ not exist.
+
+ * pcl-cvs.texinfo (@setchapternewpage): Changed from 'off' to 'on'.
+ * pcl-cvs.texinfo (Variable index): Joined into function index.
+ * pcl-cvs.texinfo (Key index): add a description about the key.
+ * pcl-cvs.texinfo: Many other small changes.
+
+Wed Mar 18 01:58:38 1992 Per Cederqvist (ceder@leopold)
+
+ * Use GNU General Public License version 2.
+
diff --git a/contrib/cvs/tools/pcl-cvs/INSTALL b/contrib/cvs/tools/pcl-cvs/INSTALL
new file mode 100644
index 0000000..56ff80d
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/INSTALL
@@ -0,0 +1,94 @@
+This text is copied from the TeXinfo manual for pcl-cvs.
+
+Installation of the pcl-cvs program
+===================================
+
+ 1. Possibly edit the file `Makefile' to reflect the situation at your
+ site. We say "possibly" because the version of pcl-cvs included
+ with CVS uses a configuration mechanism integrated with the overall
+ mechanisms used by the CVS build and install procedures. Thus the
+ file `Makefile' will be generated automatically from the file
+ `Makefile.in', and it should not be necessary to edit it further.
+
+ If you do have to edit the `Makefile', the only things you have to
+ change is the definition of `lispdir' and `infodir'. The elisp
+ files will be copied to `lispdir', and the info file(s) to
+ `infodir'.
+
+ 2. Configure pcl-cvs.el
+
+ There are a couple of pathnames that you have to check to make
+ sure that they match your system. They appear early in the file
+ `pcl-cvs.el'.
+
+ *NOTE:* If your system is running emacs 18.57 or earlier you MUST
+ uncomment the line that says:
+ (setq delete-exited-processes nil)
+
+ Setting `delete-exited-processes' to `nil' works around a bug in
+ emacs that causes it to dump core. The bug was fixed in emacs
+ 18.58.
+
+ 3. Release 1.05 and later of pcl-cvs requires parts of the Elib
+ library, version 1.0 or later. Elib is available via anonymous
+ ftp from prep.ai.mit.edu in `pub/gnu/elib-1.0.tar.gz', and from a
+ lot of other sites that mirror prep. Get Elib, and install it,
+ before proceeding.
+
+ *NOTE:* The version of pcl-cvs included with CVS includes a copy
+ of Elib in the sub-directory `elib' under the `contrib/elib'
+ directory.
+
+ 4. Type `make install' in the source directory. This will
+ byte-compile all `.el' files and copy the `*.elc' files into the
+ directory you specified in step 1.
+
+ If you want to install the `*.el' files too, you can type `make
+ install-el' to do so.
+
+ If you only want to create the compiled elisp files, but don't
+ want to install them, you can type `make' without parameters.
+
+ 5. Edit the file `default.el' in your emacs lisp directory (usually
+ `/usr/gnu/lib/emacs/site-lisp' or something similar) and enter the
+ contents of the file `pcl-cvs-startup.el' into it. It contains a
+ couple of `auto-load's that facilitates the use of pcl-cvs.
+
+
+Installation of the on-line manual.
+===================================
+
+ 1. Create the info file(s) `pcl-cvs.info*' from `pcl-cvs.texinfo' by
+ typing `make info'. If you don't have the program `makeinfo' you
+ can get it by anonymous ftp from e.g. `prep.ai.mit.edu' as
+ `pub/gnu/texinfo-3.7.tar.gz' (there might be a newer version there
+ when you read this).
+
+ 2. Install the info file(s) `pcl-cvs.info*' into your standard `info'
+ directory. You should be able to do this by typing `make
+ install-info'.
+
+ 3. Edit the file `dir' in the `info' directory and enter one line to
+ contain a pointer to the info file(s) `pcl-cvs.info*'. The line
+ can, for instance, look like this:
+
+ * Pcl-cvs: (pcl-cvs). An Emacs front-end to CVS.
+
+How to make typeset documentation from pcl-cvs.texinfo
+======================================================
+
+ If you have TeX installed at your site, you can make a typeset manual
+from `pcl-cvs.texinfo'.
+
+ 1. Run TeX by typing ``make pcl-cvs.dvi''. You will not get the
+ indices unless you have the `texindex' program.
+
+ 2. Convert the resulting device independent file `pcl-cvs.dvi' to a
+ form which your printer can output and print it. If you have a
+ postscript printer there is a program, `dvi2ps', which does. There
+ is also a program which comes together with TeX, `dvips', which
+ you can use.
+
+
+--
+#ident "@(#)cvs/contrib/pcl-cvs:$Name: $Id$"
diff --git a/contrib/cvs/tools/pcl-cvs/Makefile.in b/contrib/cvs/tools/pcl-cvs/Makefile.in
new file mode 100644
index 0000000..ca881d0
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/Makefile.in
@@ -0,0 +1,238 @@
+# Makefile for pcl-cvs, an Emacs interface to CVS.
+# NOTE: pcl-cvs requires Elib to run. See ../../contrib/elib/.
+
+#
+#ident "@(#)original: dist-makefile,v 1.19 1993/05/31 22:43:45 ceder Exp "
+#
+#ident "@(#)elisp/pcl-cvs:$Name: $:$Id: Makefile.in,v 1.3 1996/04/15 06:33:20 kfogel Exp $"
+#
+# Makefile for pcl-cvs release 1.05-CVS-$Name: $.
+# Copyright (C) 1992, 1993 Per Cederqvist
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+SHELL = /bin/sh
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+# Where to put the system-wide supplementary files
+libdir = $(prefix)/lib
+
+# Where to put the Info files
+infodir = $(prefix)/info
+
+# Where to put the manual pages.
+mandir = $(prefix)/man
+
+# Used to batch-byte-compile files.
+EMACS = emacs
+# compile with noninteractive environment
+BATCHFLAGS = -batch
+
+# This is the directory in which the ELCFILES will be installed.
+lispdir = $(libdir)/emacs/site-lisp
+
+#### End of system configuration section. ####
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+# Just in case...
+SHELL = /bin/sh
+@SET_MAKE@
+
+DISTFILES = \
+ .cvsignore ChangeLog INSTALL Makefile.in NEWS README \
+ ${ELFILES} \
+ pcl-cvs.texinfo texinfo.tex
+
+
+# OBJDIR_DISTFILES used to include the byte-compiled elisp files, but
+# this seems wrong because the person building the dist cannot have
+# made the appropriate site-specific modifications to pcl-cvs.el.
+# Therefore, I've taken the .elc files out of OBJDIR_DISTFILES for
+# now, pending the Right Solution to this problem (which probably
+# involves moving the site-specific modification section of pcl-cvs.el
+# to a separate file and having autoconf generate as much of the file
+# as possible). -Karl
+#
+# OBJDIR_DISTFILES = $(ELCFILES) pcl-cvs.aux pcl-cvs.ps
+OBJDIR_DISTFILES = pcl-cvs.aux pcl-cvs.ps
+
+
+# files that contain key macro definitions. almost everything
+# depends on them because the byte-compiler inlines macro
+# expansions. everything also depends on the byte compiler
+# options file since this might do odd things like turn off
+# certain compiler optimizations.
+CORE =
+
+ELFILES = pcl-cvs.el pcl-cvs-lucid.el pcl-cvs-startup.el
+ELCFILES = pcl-cvs.elc pcl-cvs-lucid.elc
+INFOFILES = pcl-cvs.info*
+TEXTMPS = pcl-cvs.aux pcl-cvs.log pcl-cvs.toc pcl-cvs.dvi pcl-cvs.cp \
+ pcl-cvs.fn pcl-cvs.vr pcl-cvs.tp pcl-cvs.ky pcl-cvs.pg \
+ pcl-cvs.cps pcl-cvs.fns pcl-cvs.kys pcl-cvs.pgs pcl-cvs.tps \
+ pcl-cvs.vrs
+
+# Use cp if you don't have install.
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+
+MAKEINFO = makeinfo
+
+SET_TEXINPUTS = TEXINPUTS=.:$(srcdir):$$TEXINPUTS
+
+# Don Knuth's TeX formatter
+TEX = tex
+
+# auxiliary program for sorting Texinfo indices
+TEXINDEX = texindex
+
+DVIPS = dvips
+DVIPSFLAGS =
+
+# CYGNUS LOCAL: install does not depend on info
+all: $(ELCFILES) # info
+.PHONY: all
+
+.SUFFIXES: .el .elc
+# We copy the .el file to the build dir--is there a cleaner way to get
+# emacs to compile the .el file from srcdir and put the .elc in the build dir?
+# (that is also why we have separate rules for pcl-cvs.elc and
+# pcl-cvs-lucid.elc rather than just using a .el.elc rule).
+pcl-cvs.elc: pcl-cvs.el
+ @echo "You can probably ignore free variable and unknown function warnings..."
+ if test -f pcl-cvs.el; then \
+ : OK, we are building in srcdir ; \
+ else \
+ ln $(srcdir)/pcl-cvs.el . ; \
+ fi
+ $(EMACS) $(BATCHFLAGS) -f batch-byte-compile pcl-cvs.el
+pcl-cvs-lucid.elc: pcl-cvs-lucid.el
+ @echo "You can probably ignore free variable and unknown function warnings..."
+ if test -f pcl-cvs-lucid.el; then \
+ : OK, we are building in srcdir ; \
+ else \
+ ln $(srcdir)/pcl-cvs-lucid.el . ; \
+ fi
+ $(EMACS) $(BATCHFLAGS) -f batch-byte-compile pcl-cvs-lucid.el
+
+check installcheck:
+ @echo "$@ not supported in this makefile..."
+.PHONY: check installcheck
+
+# CYGNUS LOCAL: install does not depend on install-info
+install: install-elc # install-info install-el
+
+install-el: $(ELFILES)
+ for i in $(ELFILES) ; do \
+ $(INSTALL_DATA) $$i $(lispdir)/$$i ; \
+ done
+
+install-elc: $(ELCFILES)
+ for i in $(ELCFILES) ; do \
+ $(INSTALL_DATA) $$i $(lispdir)/$$i ; \
+ done
+
+install-info: info
+ test -f pcl-cvs.info || cd $(srcdir); \
+ for i in *.info* ; do \
+ $(INSTALL_DATA) $$i $(infodir)/$$i ; \
+ done
+
+.PHONY: install install-el install-elc install-info
+
+# mkinstalldirs isn't supported for CVS yet....
+installdirs: $(top_srcdir)/mkinstalldirs
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(lispdir) $(infodir)
+.PHONY: installdirs
+
+uninstall:
+ @echo "$@ not yet supported in this makefile..."
+.PHONY: uninstall
+
+info: pcl-cvs.info
+.PHONY: info
+
+pcl-cvs.info: pcl-cvs.texinfo
+ $(MAKEINFO) ${srcdir}/pcl-cvs.texinfo -o pcl-cvs.info
+
+dvi: pcl-cvs.dvi
+.PHONY: dvi
+
+# this mess seems to be necessary to make the index right...
+pcl-cvs.dvi pcl-cvs.aux: pcl-cvs.texinfo
+ $(SET_TEXINPUTS) $(TEX) $(srcdir)/pcl-cvs.texinfo
+ $(SET_TEXINPUTS) $(TEX) $(srcdir)/pcl-cvs.texinfo
+ -$(TEXINDEX) pcl-cvs.cp pcl-cvs.fn pcl-cvs.vr pcl-cvs.tp pcl-cvs.ky \
+ pcl-cvs.pg
+ $(SET_TEXINPUTS) $(TEX) $(srcdir)/pcl-cvs.texinfo
+
+pcl-cvs.ps: pcl-cvs.dvi
+ $(DVIPS) $(DVIPSFLAGS) pcl-cvs.dvi -o pcl-cvs.ps
+
+mostlyclean clean:
+ rm -f *~ core $(ELCFILES) $(INFOFILES) $(TEXTMPS)
+.PHONY: mostlyclean clean
+
+distclean: clean
+ rm -f Makefile tags TAGS
+.PHONY: distclean
+
+realclean maintainer-clean: distclean
+ rm -f pcl-cvs.info* pcl-cvs.ps
+.PHONY: realclean maintainer-clean
+
+# you can't use ctags for lisp...
+tags TAGS:
+ etags *.el
+.PHONY: tags
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+dist-dir: ${OBJDIR_DISTFILES} ${DISTFILES} pcl-cvs.info
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+ ln ${OBJDIR_DISTFILES} ${DISTDIR}
+ if [ -f pcl-cvs.info-1 ]; \
+ then ln -f pcl-cvs.info-* ${DISTDIR}; \
+ else : Pacify Ultrix sh; \
+ fi
+.PHONY: dist-dir
+
+subdir = tools/pcl-cvs
+Makefile: ../../config.status Makefile.in
+ cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
+
+# CYGNUS LOCAL: don't depend on auto-re-config
+#../config.status: ../configure
+# cd .. ; $(SHELL) config.status --recheck
+
+# CYGNUS LOCAL: don't depend on auto-re-config
+#../configure: ../configure.in
+# cd $(top_srcdir) ; autoconf
diff --git a/contrib/cvs/tools/pcl-cvs/NEWS b/contrib/cvs/tools/pcl-cvs/NEWS
new file mode 100644
index 0000000..48b0b66
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/NEWS
@@ -0,0 +1,149 @@
+This is the NEWS file for pcl-cvs, an Emacs elisp front-end to CVS.
+
+User-visible changes in the un-official CVS release of pcl-cvs
+from the official 1.05 release to 1.05-CVS-$Name: $:
+
+* Support for using ChangeLog files, including hooks to automatically
+ guess CVS log entries from ChangeLog contents.
+
+* Support for client/server CVS (versions 1.5 through 1.7 and newer).
+
+* New commands for tagging files and directory trees (still needs to
+ be made to run in the background).
+
+* Better support for recognizing and handling unknown directories.
+
+* An attempt at new ediff and emerge interfaces (still needs work!),
+ including attempts to make vendor-branch merging work.
+
+* In a possibly misguided attempt to make it easier to see the effects
+ of changes that affect several files, diff output is now stored in a
+ uniqe buffer for each file.
+
+* Some commands now have default flags (cvs-*-flags).
+
+* Proper quoting of command line arguments displayed in *cvs-tmp*.
+
+* More hacking with getting CVSROOT right, though probably all
+ pointless, since CVS should do the right thing all the time.
+
+* Elib is back, at least in the CVS distribution.
+
+* Lots of minor bug fixes, tweaks, cleanup, re-indentation, etc.
+
+* Some minor tweaks, fixes, re-indentation, etc., in the
+ documentation.
+
+
+User-visible changes in pcl-cvs from 1.04 to 1.05:
+
+* Elib is no longer distributed with pcl-cvs. You must get Elib
+ separately, for instance from ftp.lysator.liu.se in pub/emacs.
+
+* The Lucid Emacs support works again.
+
+* A new function, cvs-change-cvsroot, can be used to interactively
+ switch between CVS repositories.
+
+* The mode line in the *cvs* buffer now indicates when a "cvs update"
+ is running.
+
+* The .cvsignore file is automatically sorted alphabetically (to
+ reduce the risk of conflicts when two people add different files
+ simultaneously). This behaviour can be turned off with
+ cvs-sort-ignore-file.
+
+* A trailing newline is always added in commit log messages. This
+ behaviour can be turned off with
+ cvs-commit-buffer-require-final-newline.
+
+* This version of pcl-cvs should work together with RCVS. I have not
+ tested this myself, though.
+
+* Plus some bug fixes. (Note that the version of cookie.el that is
+ distributed with pcl-cvs 1.04 contains errors that affects pcl-cvs.
+ You should get Elib 0.07).
+
+
+User-visible changes in pcl-cvs from 1.03 to 1.04:
+
+* Support for Emerge. Hitting "e" on a file that is Modified, Merged
+ or in Conflict will start Emerge, an interactive file merger written
+ in Emacs Lisp. This requires Emerge version 4. Emerge is not
+ included in this package. If you can't find it anywhere else, you
+ can get in from ftp.lysator.liu.se in pub/emacs. This package makes
+ it a lot easier to resolve conflicts.
+
+* Emacs will now automatically revert your buffers when the CVS
+ commands pcl-cvs issues causes the file to change. This automatic
+ revert never occurs if the buffer contents did not agree with the
+ file prior to the command.
+
+* If you are running Lucid GNU Emacs, you will get some fonts and
+ mouse support. This was contributed from people at Lucid.
+
+* The variable cvs-cvsroot can be used to select the location if the
+ repository. You no longer need to exit Emacs, setenv CVSROOT, and
+ start a new Emacs if you work with multiple repositories.
+
+* The "q" key can be used to hide the *cvs* buffer.
+
+* The name of the commands in the *cvs* have changed. If it was called
+ cvs-foo, it will now be called cvs-mode-foo. See the ChangeLog
+ entry from Tue Aug 4 03:02:25 1992 for a complete list of changes.
+
+* The variable cvs-cvs-diff-flags is no longer used. Instead,
+ cvs-diff-flags is always used.
+
+* Plus a lot of bug fixes.
+
+
+User-visible changes in pcl-cvs from 1.02 to 1.03:
+
+* Output from CVS to stdout and stderr is separated and parsed
+ independently. In that way pcl-cvs should work regardless of
+ whether stdout is buffered or line-buffered. Pcl-cvs should now
+ work with CVS 1.3 without modifications on hosts such as
+ DECstations.
+
+* Pcl-cvs now fully supports RCS version 5.6 as well as 5.5.
+
+* New functions:
+
+ + cvs-undo-local-changes ("U") - Undo all your modifications
+ to a file and get the newest
+ version from the repository.
+ + cvs-update-other-window - Similar to cvs-update.
+ + cvs-byte-compile-files - Byte compile the selected files.
+
+* cvs-update now displays the *cvs* buffer, which initially contains a
+ small message ("Running `cvs update' in /foo/bar/gazonk/...") until
+ the update is ready. The *cvs* buffer no longer pops up when the
+ update is ready. It often failed to pop up, due to race conditions
+ that are very hard to solve (and I doubt that they were at all
+ solvable).
+
+* cvs-unmark-all-files is moved from "U" to "ESC DEL" to be
+ "compatible" with dired.
+
+* cvs-diff ("d") and cvs-diff-backup ("b") can be configured to work
+ on only the file the cursor is positioned on, and ignore any marked
+ files. A prefix argument toggles this.
+
+* Only one `cvs update' can be run at a time. (It was previously
+ possible to start more than one simultaneously, but pcl-cvs could
+ not really handle more than one.)
+
+* Some rudimentary support for programs that CVS runs at update (due
+ to the -u switch in the modules file).
+
+* Pcl-cvs now automatically generates a bug report if it can't parse
+ the output from CVS.
+
+* The *cvs* buffer is read-only.
+
+* Pcl-cvs now creates temporary files in $TMPDIR if that environment
+ variable is set (otherwise it uses /tmp).
+
+---End of file NEWS---
+#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: NEWS,v 1.1 1996/04/14 15:17:54 kfogel Exp $"
diff --git a/contrib/cvs/tools/pcl-cvs/README b/contrib/cvs/tools/pcl-cvs/README
new file mode 100644
index 0000000..9574c14
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/README
@@ -0,0 +1,25 @@
+This is the readme file for pcl-cvs, release 1.05-CVS-$Name: $.
+
+Pcl-cvs is a front-end to CVS versions 1.5 through 1.7. It integrates
+the most frequently used CVS commands into an emacs interface.
+
+There may be some configuration that needs to be done in pcl-cvs.el to
+get it to work. See the instructions in the file INSTALL.
+
+Full documentation is in Texinfo format in the file pcl-cvs.texinfo. To
+browse this document online, or in the emacs info mode, you will need to
+process this file with the makeinfo program, which can also be found on
+prep.ai.mit.edu in pub/gnu.
+
+If you have been using a previous version of pcl-cvs (for instance the
+official 1.05 release, or any previous releases) you should read through
+the file NEWS to see what has changed.
+
+This release has been tested under, Emacs 19.28 and Emacs 19.30.
+
+Per Cederqvist
+(updated by Jim Blandy, Greg A. Woods, Karl Fogel)
+
+--
+#OrigId "@(#) Id: README,v 1.14 1993/05/31 22:43:36 ceder Exp "
+#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: README,v 1.1 1996/04/14 15:17:55 kfogel Exp $"
diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs-lucid.el b/contrib/cvs/tools/pcl-cvs/pcl-cvs-lucid.el
new file mode 100644
index 0000000..8695f67
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs-lucid.el
@@ -0,0 +1,134 @@
+;;; Mouse and font support for PCL-CVS 1.3 running in Lucid GNU Emacs
+;; @(#) Id: pcl-cvs-lucid.el,v 1.2 1993/05/31 19:37:34 ceder Exp
+;; Copyright (C) 1992-1993 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+;; This simply adds a menu of the common CVS commands to the menubar and to
+;; the right mouse button. Clicking right moves point, and then pops up a
+;; menu from which commands can be executed.
+;;
+;; This could stand to be a lot more clever: for example, the "Commit Changes"
+;; command should only be active on files for which there is something to
+;; commit. Also, some indication of which files the command applies to
+;; (especially in the presence of multiple marked files) would be nice.
+;;
+;; Middle-click runs find-file.
+
+
+;(require 'pcl-cvs)
+(load "pcl-cvs.el")
+
+(defvar cvs-menu
+ '("CVS"
+ ["Find File" cvs-mode-find-file t]
+ ["Find File Other Window" cvs-mode-find-file-other-window t]
+ ["Interactively Merge (emerge)" cvs-mode-emerge t]
+ ["Diff against Repository" cvs-mode-diff-cvs t]
+ ["Diff against Backup Version" cvs-mode-diff-backup t]
+ "----"
+ ["Commit Changes to Repository" cvs-mode-commit t]
+ ["Revert File from Repository" cvs-mode-undo-local-changes t]
+ ["Add File to Repository" cvs-mode-add t]
+ ["Remove File from Repository" cvs-mode-remove-file t]
+ ["Ignore File" cvs-mode-ignore t]
+ ["Hide File" cvs-mode-acknowledge t]
+ ["Hide Handled Files" cvs-mode-remove-handled t]
+ "----"
+ ["Add ChangeLog Entry" cvs-mode-add-change-log-entry-other-window t]
+ ["Show CVS Log" cvs-mode-log t]
+ ["Show CVS Status" cvs-mode-status t]
+ "----"
+ ["Mark File" cvs-mode-mark t]
+ ["Unmark File" cvs-mode-unmark t]
+ ["Mark All Files" cvs-mode-mark-all-files t]
+ ["Unmark All Files" cvs-mode-unmark-all-files t]
+ "----"
+ ["Quit" bury-buffer t]
+ ))
+
+(defun cvs-menu (e)
+ (interactive "e")
+ (mouse-set-point e)
+ (beginning-of-line)
+ (or (looking-at "^[* ] ") (error "No CVS file line here"))
+ (popup-menu cvs-menu))
+
+(defun cvs-mouse-find-file (e)
+ (interactive "e")
+ (mouse-set-point e)
+ (beginning-of-line)
+ (or (looking-at "^[* ] ") (error "No CVS file line here"))
+ (cvs-mode-find-file (point)))
+
+(define-key cvs-mode-map 'button3 'cvs-menu)
+(define-key cvs-mode-map 'button2 'cvs-mouse-find-file)
+
+(make-face 'cvs-header-face)
+(make-face 'cvs-filename-face)
+(make-face 'cvs-status-face)
+
+(or (face-differs-from-default-p 'cvs-header-face)
+ (copy-face 'italic 'cvs-header-face))
+
+(or (face-differs-from-default-p 'cvs-filename-face)
+ (copy-face 'bold 'cvs-filename-face))
+
+(or (face-differs-from-default-p 'cvs-status-face)
+ (copy-face 'bold-italic 'cvs-status-face))
+
+
+(defun pcl-mode-motion-highlight-line (event)
+ (if (save-excursion
+ (let* ((window (event-window event))
+ (buffer (and window (window-buffer window)))
+ (point (and buffer (event-point event))))
+ (and point
+ (progn
+ (set-buffer buffer)
+ (goto-char point)
+ (beginning-of-line)
+ (looking-at "^[* ] ")))))
+ (mode-motion-highlight-line event)))
+
+(defconst pcl-cvs-font-lock-keywords
+ '(("^In directory \\(.+\\)$" 1 cvs-header-face)
+ ("^[* ] \\w+ +\\(ci\\)" 1 cvs-status-face)
+ ("^[* ] \\(Conflict\\|Merged\\)" 1 cvs-status-face)
+ ("^[* ] \\w+ +\\(ci +\\)?\\(.+\\)$" 2 cvs-filename-face)
+ )
+ "Patterns to highlight in the *cvs* buffer.")
+
+(defun pcl-cvs-fontify ()
+ ;;
+ ;; set up line highlighting
+ (require 'mode-motion)
+ (setq mode-motion-hook 'pcl-mode-motion-highlight-line)
+ ;;
+ ;; set up menubar
+ (if (and current-menubar (not (assoc "CVS" current-menubar)))
+ (progn
+ (set-buffer-menubar (copy-sequence current-menubar))
+ (add-menu nil "CVS" (cdr cvs-menu))))
+ ;;
+ ;; fontify mousable lines
+ (set (make-local-variable 'font-lock-keywords) pcl-cvs-font-lock-keywords)
+ (font-lock-mode 1)
+ )
+
+(add-hook 'cvs-mode-hook 'pcl-cvs-fontify)
diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs-startup.el b/contrib/cvs/tools/pcl-cvs/pcl-cvs-startup.el
new file mode 100644
index 0000000..9db7a5f
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs-startup.el
@@ -0,0 +1,17 @@
+;;;#ident "@(#)OrigId: pcl-cvs-startup.el,v 1.4 1993/05/31 18:40:33 ceder Exp "
+;;;
+;;;#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs-startup.el,v 1.1 1996/04/14 15:17:59 kfogel Exp $"
+;;;
+(autoload 'cvs-update "pcl-cvs"
+ "Run a 'cvs update' in the current working directory. Feed the
+output to a *cvs* buffer and run cvs-mode on it.
+If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run."
+ t)
+
+(autoload 'cvs-update-other-window "pcl-cvs"
+ "Run a 'cvs update' in the current working directory. Feed the
+output to a *cvs* buffer, display it in the other window, and run
+cvs-mode on it.
+
+If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run."
+ t)
diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs.el b/contrib/cvs/tools/pcl-cvs/pcl-cvs.el
new file mode 100644
index 0000000..269b02f
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs.el
@@ -0,0 +1,3450 @@
+;;;
+;;;#ident "@(#)OrigId: pcl-cvs.el,v 1.93 1993/05/31 22:44:00 ceder Exp "
+;;;
+;;;#ident "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs.el,v 1.2 1996/04/14 20:09:45 kfogel Exp $"
+;;;
+;;; pcl-cvs.el -- A Front-end to CVS 1.3 or later.
+;;; Release 1.05-CVS-$Name: $.
+;;; Copyright (C) 1991, 1992, 1993 Per Cederqvist
+
+;;; This program is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; This program is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with this program; if not, write to the Free Software
+;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; See below for installation instructions.
+
+;;; This package requires ELIB-1.0 to run. Elib is included in the
+;;; CVS distribution in the contrib/elib/ subdirectory, but you can
+;;; also download it at the following URL:
+;;;
+;;; ftp://ftp.lysator.liu.se/pub/emacs/elib-1.0.tar.gz
+;;;
+
+;;; There is an TeXinfo file that describes this package. You should read it
+;;; to get the most from this package.
+
+;;; Mail questions and bug reports regarding this version (as included in
+;;; CVS-1.7 or newer) to the pcl-cvs support team at <pcl-cvs@cyclic.com>.
+
+;;; Don't try to use this with CVS 1.2 or earlier. It won't work. Get CVS 1.7
+;;; or newer. Use the version of RCS best suited for the version of CVS you're
+;;; using.
+
+(require 'cookie) ; from ELIB-1.0
+(require 'add-log) ; for all the ChangeLog goodies
+
+;;; -------------------------------------------------------
+;;; START OF THINGS TO CHECK WHEN INSTALLING
+
+;; also use $GNU here, since may folks might install CVS as a GNU package
+;;
+(defvar local-path (cond
+ ((getenv "LOCAL")
+ (getenv "LOCAL"))
+ ((getenv "GNU")
+ (getenv "GNU"))
+ (t
+ "/usr/local"))
+ "*Path prefix for most locally installed things.")
+
+;; this isn't likely to be right all the time....
+;;
+(defvar local-gnu-path (cond
+ ((getenv "GNU")
+ (getenv "GNU"))
+ (t
+ "/usr/local")) ; or "/usr/gnu"?
+ "*Path prefix for locally installed GNU software.")
+
+(defvar cvs-program (concat local-path "/bin/cvs")
+ "*Full path to the cvs executable.")
+
+;; SunOS-4.1.1_U1 has "diff.c 1.12 88/08/04 SMI; from UCB 4.6 86/04/03"
+;;
+(defvar cvs-diff-program (concat local-gnu-path "/bin/diff")
+ "*Full path to the best diff program you've got.
+NOTE: there are some nasty bugs in the context diff variants of some vendor
+versions, such as the one in SunOS-4.1.1_U1")
+
+(defvar cvs-rmdir-program "/bin/rmdir"
+ "*Full path to the rmdir program. Typically /bin/rmdir.")
+
+(defvar cvs-shell "/bin/sh"
+ "*Full path to a shell that can do redirection on stdout.")
+
+;;; Options to control various features:
+
+(defvar cvs-changelog-full-paragraphs t
+ "If non-nil, include full ChangeLog paragraphs in the CVS log.
+This may be set in the ``local variables'' section of a ChangeLog, to
+indicate the policy for that ChangeLog.
+
+A ChangeLog paragraph is a bunch of log text containing no blank lines;
+a paragraph usually describes a set of changes with a single purpose,
+but perhaps spanning several functions in several files. Changes in
+different paragraphs are unrelated.
+
+You could argue that the CVS log entry for a file should contain the
+full ChangeLog paragraph mentioning the change to the file, even though
+it may mention other files, because that gives you the full context you
+need to understand the change. This is the behaviour you get when this
+variable is set to t.
+
+On the other hand, you could argue that the CVS log entry for a change
+should contain only the text for the changes which occurred in that
+file, because the CVS log is per-file. This is the behaviour you get
+when this variable is set to nil.")
+
+(defvar cvs-cvsroot-required nil
+ "*Specifies whether CVS needs to be told where the repository is.
+
+In CVS 1.3, if your CVSROOT environment variable is not set, and you
+do not set the `cvs-cvsroot' lisp variable, CVS will have no idea
+where to find the repository, and refuse to run. CVS 1.4 and later
+store the repository path with the working directories, so most
+operations don't need to be told where the repository is.
+
+If you work with multiple repositories with CVS 1.4, it's probably
+advisable to leave your CVSROOT environment variable unset, set this
+variable to nil, and let CVS figure out where the repository is for
+itself.")
+
+(defvar cvs-cvsroot nil
+ "*Specifies where the (current) cvs master repository is.
+Overrides the $CVSROOT variable by sending \" -d dir\" to all cvs commands.
+This switch is useful if you have multiple CVS repositories, and are not using
+a modern version of CVS that stores the current repository in CVS/Root.")
+
+;; Uncomment the following line if you are running on 18.57 or earlier.
+;(setq delete-exited-processes nil)
+;; Emacs version 18.57 and earlier is likely to crash if
+;; delete-exited-processes is t, since the sentinel uses lots of
+;; memory, and 18.57 forgets to GCPROT a variable if
+;; delete-exited-processes is t.
+
+;;; END OF THINGS TO CHECK WHEN INSTALLING
+;;; --------------------------------------------------------
+
+(defconst pcl-cvs-version "1.05-CVS-$Name: $"
+ "A string denoting the current release version of pcl-cvs.")
+
+;; You are NOT allowed to disable this message by default. However, you
+;; are encouraged to inform your users that by adding
+;; (setq cvs-inhibit-copyright-message t)
+;; to their .emacs they can get rid of it. Just don't add that line
+;; to your default.el!
+(defvar cvs-inhibit-copyright-message nil
+ "*Non-nil means don't display a Copyright message in the ``*cvs*'' buffer.")
+
+(defconst cvs-startup-message
+ (if cvs-inhibit-copyright-message
+ "PCL-CVS release 1.05-CVS-$Name: $"
+ "PCL-CVS release 1.05 from CVS release $Name: $.
+Copyright (C) 1992, 1993 Per Cederqvist
+Pcl-cvs comes with absolutely no warranty; for details consult the manual.
+This is free software, and you are welcome to redistribute it under certain
+conditions; again, consult the TeXinfo manual for details.")
+ "*Startup message for CVS.")
+
+(defconst pcl-cvs-bugs-address "pcl-cvs-auto-bugs@cyclic.com"
+ "The destination address used for the default bug report form.")
+
+(defvar cvs-stdout-file nil
+ "Name of the file that holds the output that CVS sends to stdout.
+This variable is buffer local.")
+
+(defvar cvs-lock-file nil
+ "Full path to a lock file that CVS is waiting for (or was waiting for).")
+
+(defvar cvs-bakprefix ".#"
+ "The prefix that CVS prepends to files when rcsmerge'ing.")
+
+(defvar cvs-erase-input-buffer nil
+ "*Non-nil if input buffers should be cleared before asking for new info.")
+
+(defvar cvs-auto-remove-handled nil
+ "*Non-nil if cvs-mode-remove-handled should be called automatically.
+If this is set to any non-nil value, entries that do not need to be checked in
+will be removed from the *cvs* buffer after every cvs-mode-commit command.")
+
+(defvar cvs-auto-remove-handled-directories nil
+ "*Non-nil if cvs-mode-remove-handled and cvs-update should automatically
+remove empty directories.
+If this is set to any non-nil value, directories that do not contain any files
+to be checked in will be removed from the *cvs* buffer.")
+
+(defvar cvs-sort-ignore-file t
+ "*Non-nil if cvs-mode-ignore should sort the .cvsignore automatically.")
+
+(defvar cvs-auto-revert-after-commit t
+ "*Non-nil if committed buffers should be automatically reverted.")
+
+(defconst cvs-cursor-column 14
+ "Column to position cursor in in cvs-mode.
+Column 0 is left-most column.")
+
+(defvar cvs-mode-map nil
+ "Keymap for the cvs mode.")
+
+(defvar cvs-edit-mode-map nil
+ "Keymap for the cvs edit mode (used when editing cvs log messages).")
+
+(defvar cvs-buffer-name "*cvs*"
+ "Name of the cvs buffer.")
+
+(defvar cvs-commit-prompt-buffer "*cvs-commit-message*"
+ "Name of buffer in which the user is prompted for a log message when
+committing files.")
+
+(defvar cvs-commit-buffer-require-final-newline t
+ "*t says silently put a newline at the end of commit log messages.
+Non-nil but not t says ask user whether to add a newline in each such case.
+nil means don't add newlines.")
+
+(defvar cvs-temp-buffer-name "*cvs-tmp*"
+ "*Name of the cvs temporary buffer.
+Output from cvs is placed here by synchronous commands.")
+
+(defvar cvs-diff-ignore-marks nil
+ "*Non-nil if cvs-diff and cvs-mode-diff-backup should ignore any marked files.
+Normally they run diff on the files that are marked (with cvs-mode-mark),
+or the file under the cursor if no files are marked. If this variable
+is set to a non-nil value they will always run diff on the file on the
+current line.")
+
+;;; (setq cvs-status-flags '("-v"))
+(defvar cvs-status-flags '("-v")
+ "*List of flags to pass to ``cvs status''. Default is \"-v\".")
+
+;;; (setq cvs-log-flags nil)
+(defvar cvs-log-flags nil
+ "*List of flags to pass to ``cvs log''. Default is none.")
+
+;;; (setq cvs-tag-flags nil)
+(defvar cvs-tag-flags nil
+ "*List of extra flags to pass to ``cvs tag''. Default is none.")
+
+;;; (setq cvs-rtag-flags nil)
+(defvar cvs-rtag-flags nil
+ "*List of extra flags to pass to ``cvs rtag''. Default is none.")
+
+;;; (setq cvs-diff-flags '("-u"))
+(defvar cvs-diff-flags '("-u")
+ "*List of flags to use as flags to pass to ``diff'' and ``cvs diff''.
+Used by cvs-mode-diff-cvs and cvs-mode-diff-backup. Default is \"-u\".
+
+Set this to \"-u\" to get a Unidiff format, or \"-c\" to get context diffs.")
+
+;;; (setq cvs-update-optional-flags nil)
+(defvar cvs-update-optional-flags nil
+ "*List of strings to use as optional flags to pass to ``cvs update''. Used
+by cvs-do-update, called by cvs-update, cvs-update-other-window,
+cvs-mode-update-no-prompt, and cvs-examine. Default is none.
+
+For example set this to \"-j VENDOR_PREV_RELEASE -j VENDOR_TOP_RELEASE\" to
+perform an update after a new vendor release has been imported.
+
+To restrict the update to the current working directory, set this to \"-l\".")
+
+(defvar cvs-update-prog-output-skip-regexp "$"
+ "*A regexp that matches the end of the output from all cvs update programs.
+That is, output from any programs that are run by CVS (by the flag -u in the
+`modules' file - see cvs(5)) when `cvs update' is performed should terminate
+with a line that this regexp matches. It is enough that some part of the line
+is matched.
+
+The default (a single $) fits programs without output.")
+
+;;; --------------------------------------------------------
+;;; The variables below are used internally by pcl-cvs. You should
+;;; never change them.
+
+(defvar cvs-buffers-to-delete nil
+ "List of temporary buffers that should be discarded as soon as possible.
+Due to a bug in emacs 18.57 the sentinel can't discard them reliably.")
+
+(defvar cvs-update-running nil
+ "This is set to nil when no process is running, and to
+the process when a cvs update process is running.")
+
+(defvar cvs-cookie-handle nil
+ "Handle for the cookie structure that is displayed in the *cvs* buffer.")
+
+(defvar cvs-commit-list nil
+ "Used internally by pcl-cvs.")
+
+;;; The cvs data structure:
+;;;
+;;; When the `cvs update' is ready we parse the output. Every file
+;;; that is affected in some way is added as a cookie of fileinfo
+;;; (as defined below).
+;;;
+
+;;; cvs-fileinfo
+
+;;; Constructor:
+
+(defun cvs-create-fileinfo (type
+ dir
+ file-name
+ full-log)
+ "Create a fileinfo from all parameters.
+Arguments: TYPE DIR FILE-NAME FULL-LOG.
+A fileinfo is a vector with the following fields:
+
+[0] handled True if this file doesn't require further action.
+[1] marked t/nil
+[2] type One of
+ UPDATED - file copied from repository
+ PATCHED - file update with patch from repository
+ MODIFIED - modified by you, unchanged in
+ repository
+ ADDED - added by you, not yet committed
+ REMOVED - removed by you, not yet committed
+ CVS-REMOVED- removed, since file no longer exists
+ in the repository.
+ MERGED - successful merge
+ CONFLICT - conflict when merging (if pcl-cvs did it)
+ REM-CONFLICT-removed in repository, but altered
+ locally.
+ MOD-CONFLICT-removed locally, changed in repository.
+ REM-EXIST - removed locally, but still exists.
+ DIRCHANGE - A change of directory.
+ UNKNOWN - An unknown file.
+ UNKNOWN-DIR- An unknown directory.
+ MOVE-AWAY - A file that is in the way.
+ REPOS-MISSING- The directory has vanished from the
+ repository.
+ MESSAGE - This is a special fileinfo that is used
+ to display a text that should be in
+ full-log.
+[3] dir Directory the file resides in. Should not end with slash.
+[4] file-name The file name.
+[5] backup-file The name of a backup file created during a merge.
+ Only valid for MERGED and CONFLICT files.
+[6] base-revision The revision that the working file was based on.
+ Only valid for MERGED and CONFLICT files.
+[7] head-revision The revision that the newly merged changes came from
+ Only valid for MERGED and CONFLICT files.
+[8] backup-revision The revision of the cvs backup file (original working rev.)
+ Only valid for MERGED and CONFLICT files.
+[9] cvs-diff-buffer A buffer that contains a 'cvs diff file'.
+[10] vendor-diff-buffer A buffer that contains a 'diff base-file head-file'.
+[11] backup-diff-buffer A buffer that contains a 'diff file backup-file'.
+[12] full-log The output from cvs, unparsed.
+[13] mod-time Modification time of file used for *-diff-buffer."
+
+ (cons
+ 'CVS-FILEINFO
+ (vector nil nil type dir file-name nil nil nil nil nil nil nil full-log nil nil)))
+
+;;; Selectors:
+
+(defun cvs-fileinfo->handled (cvs-fileinfo)
+ "Get the `handled' field from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 0))
+
+(defun cvs-fileinfo->marked (cvs-fileinfo)
+ "Check if CVS-FILEINFO is marked."
+ (elt (cdr cvs-fileinfo) 1))
+
+(defun cvs-fileinfo->type (cvs-fileinfo)
+ "Get type from CVS-FILEINFO.
+Type is one of UPDATED, PATCHED, MODIFIED, ADDED, REMOVED, CVS-REMOVED, MERGED,
+CONFLICT, REM-CONFLICT, MOD-CONFLICT, REM-EXIST, DIRCHANGE, UNKNOWN,
+UNKNOWN-DIR, MOVE-AWAY, REPOS-MISSING or MESSAGE."
+ (elt (cdr cvs-fileinfo) 2))
+
+(defun cvs-fileinfo->dir (cvs-fileinfo)
+ "Get dir from CVS-FILEINFO.
+The directory name does not end with a slash."
+ (elt (cdr cvs-fileinfo) 3))
+
+(defun cvs-fileinfo->file-name (cvs-fileinfo)
+ "Get file-name from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 4))
+
+(defun cvs-fileinfo->backup-file (cvs-fileinfo)
+ "Get backup-file from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 5))
+
+(defun cvs-fileinfo->base-revision (cvs-fileinfo)
+ "Get the base revision from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 6))
+
+(defun cvs-fileinfo->head-revision (cvs-fileinfo)
+ "Get the head revision from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 7))
+
+(defun cvs-fileinfo->backup-revision (cvs-fileinfo)
+ "Get the backup revision from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 8))
+
+(defun cvs-fileinfo->cvs-diff-buffer (cvs-fileinfo)
+ "Get cvs-diff-buffer from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 9))
+
+(defun cvs-fileinfo->vendor-diff-buffer (cvs-fileinfo)
+ "Get backup-diff-buffer from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 10))
+
+(defun cvs-fileinfo->backup-diff-buffer (cvs-fileinfo)
+ "Get backup-diff-buffer from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 11))
+
+(defun cvs-fileinfo->full-log (cvs-fileinfo)
+ "Get full-log from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 12))
+
+(defun cvs-fileinfo->mod-time (cvs-fileinfo)
+ "Get mod-time from CVS-FILEINFO."
+ (elt (cdr cvs-fileinfo) 13))
+
+;;; Modifiers:
+
+(defun cvs-set-fileinfo->handled (cvs-fileinfo newval)
+ "Set handled in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 0 newval))
+
+(defun cvs-set-fileinfo->marked (cvs-fileinfo newval)
+ "Set marked in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 1 newval))
+
+(defun cvs-set-fileinfo->type (cvs-fileinfo newval)
+ "Set type in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 2 newval))
+
+(defun cvs-set-fileinfo->dir (cvs-fileinfo newval)
+ "Set dir in CVS-FILEINFO to NEWVAL.
+The directory should now end with a slash."
+ (aset (cdr cvs-fileinfo) 3 newval))
+
+(defun cvs-set-fileinfo->file-name (cvs-fileinfo newval)
+ "Set file-name in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 4 newval))
+
+(defun cvs-set-fileinfo->backup-file (cvs-fileinfo newval)
+ "Set backup-file in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 5 newval))
+
+(defun cvs-set-fileinfo->base-revision (cvs-fileinfo newval)
+ "Set base-revision in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 6 newval))
+
+(defun cvs-set-fileinfo->head-revision (cvs-fileinfo newval)
+ "Set head-revision in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 7 newval))
+
+(defun cvs-set-fileinfo->backup-revision (cvs-fileinfo newval)
+ "Set backup-revision in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 8 newval))
+
+(defun cvs-set-fileinfo->cvs-diff-buffer (cvs-fileinfo newval)
+ "Set cvs-diff-buffer in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 9 newval))
+
+(defun cvs-set-fileinfo->vendor-diff-buffer (cvs-fileinfo newval)
+ "Set vendor-diff-buffer in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 10 newval))
+
+(defun cvs-set-fileinfo->backup-diff-buffer (cvs-fileinfo newval)
+ "Set backup-diff-buffer in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 11 newval))
+
+(defun cvs-set-fileinfo->full-log (cvs-fileinfo newval)
+ "Set full-log in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 12 newval))
+
+(defun cvs-set-fileinfo->mod-time (cvs-fileinfo newval)
+ "Set full-log in CVS-FILEINFO to NEWVAL."
+ (aset (cdr cvs-fileinfo) 13 newval))
+
+;;; Predicate:
+
+(defun cvs-fileinfo-p (object)
+ "Return t if OBJECT is a cvs-fileinfo."
+ (eq (car-safe object) 'CVS-FILEINFO))
+
+;;;; End of types.
+
+;;----------
+(defun cvs-use-temp-buffer ()
+ "Display a temporary buffer in another window and select it.
+The selected window will not be changed. The temporary buffer will
+be erased and writable."
+
+ (let ((dir default-directory))
+ (display-buffer (get-buffer-create cvs-temp-buffer-name))
+ (set-buffer cvs-temp-buffer-name)
+ (setq buffer-read-only nil)
+ (setq default-directory dir)
+ (erase-buffer)))
+
+;;----------
+(defun cvs-examine (directory &optional local)
+ "Run a 'cvs -n update' in the current working directory.
+That is, check what needs to be done, but don't change the disc.
+Feed the output to a *cvs* buffer and run cvs-mode on it.
+If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run.
+WARNING: this doesn't work very well yet...."
+
+ ;; TODO: this should do everything cvs-update does...
+ ;; for example, for CONFLICT files, it should setup fileinfo appropriately
+
+ (interactive (list (read-file-name "CVS Update (directory): "
+ nil default-directory nil)
+ current-prefix-arg))
+ (cvs-do-update directory local 'noupdate))
+
+;;----------
+(defun cvs-update (directory &optional local)
+ "Run a 'cvs update' in the current working directory. Feed the
+output to a *cvs* buffer and run cvs-mode on it.
+If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run."
+
+ (interactive (list (read-file-name "CVS Update (directory): "
+ nil default-directory nil)
+ current-prefix-arg))
+ (cvs-do-update directory local nil)
+ (switch-to-buffer cvs-buffer-name))
+
+;;----------
+(defun cvs-update-other-window (directory &optional local)
+ "Run a 'cvs update' in the current working directory. Feed the
+output to a *cvs* buffer, display it in the other window, and run
+cvs-mode on it.
+
+If optional prefix argument LOCAL is non-nil, 'cvs update -l' is run."
+
+ (interactive (list (read-file-name "CVS Update other window (directory): "
+ nil default-directory nil)
+ current-prefix-arg))
+ (cvs-do-update directory local nil)
+ (switch-to-buffer-other-window cvs-buffer-name))
+
+;;----------
+(defun cvs-filter (predicate list &rest extra-args)
+ "Apply PREDICATE to each element on LIST.
+Args: PREDICATE LIST &rest EXTRA-ARGS.
+
+Return a new list consisting of those elements that PREDICATE
+returns non-nil for.
+
+If more than two arguments are given the remaining args are
+passed to PREDICATE."
+
+ ;; Avoid recursion - this should work for LONG lists also!
+ (let* ((head (cons 'dummy-header nil))
+ (tail head))
+ (while list
+ (if (apply predicate (car list) extra-args)
+ (setq tail (setcdr tail (list (car list)))))
+ (setq list (cdr list)))
+ (cdr head)))
+
+;;----------
+(defun cvs-mode-update-no-prompt ()
+ "Run cvs update in current directory."
+
+ (interactive)
+ (cvs-do-update default-directory nil nil))
+
+;;----------
+(defun cvs-do-update (directory local dont-change-disc)
+ "Do a 'cvs update' in DIRECTORY.
+Args: DIRECTORY LOCAL DONT-CHANGE-DISC.
+
+If LOCAL is non-nil 'cvs update -l' is executed.
+If DONT-CHANGE-DISC is non-nil 'cvs -n update' is executed.
+Both LOCAL and DONT-CHANGE-DISC may be non-nil simultaneously.
+
+*Note*: DONT-CHANGE-DISC does not yet work. The parser gets confused."
+
+ (save-some-buffers)
+ ;; Ensure that it is safe to do an update. If not, ask user
+ ;; for confirmation.
+ (if (and (boundp 'cvs-cookie-handle) (collection-buffer cvs-cookie-handle))
+ (if (collection-collect-tin
+ cvs-cookie-handle
+ '(lambda (cookie) (eq (cvs-fileinfo->type cookie) 'CONFLICT)))
+ (if (not
+ (yes-or-no-p
+ "Only update if conflicts have been resolved. Continue? "))
+ (error "Update aborted by user request."))))
+ (if (not (file-exists-p cvs-program))
+ (error "%s: file not found (check setting of cvs-program)"
+ cvs-program))
+ (let* ((this-dir (file-name-as-directory (expand-file-name directory)))
+ (update-buffer (generate-new-buffer
+ (concat " " (file-name-nondirectory
+ (substring this-dir 0 -1))
+ "-update")))
+ (temp-name (make-temp-name
+ (concat (file-name-as-directory
+ (or (getenv "TMPDIR") "/tmp"))
+ "pcl-cvs.")))
+ (args nil))
+
+ ;; Check that this-dir exists and is a directory that is under CVS contr.
+
+ (if (not (file-directory-p this-dir))
+ (error "%s is not a directory." this-dir))
+ (if (not (file-directory-p (concat this-dir "CVS")))
+ (error "%s does not contain CVS controlled files." this-dir))
+ (if (file-readable-p (concat this-dir "CVS/Root"))
+ (save-excursion ; read CVS/Root into cvs-cvsroot
+ (find-file (concat this-dir "CVS/Root"))
+ (goto-char (point-min))
+ (setq cvs-cvsroot (buffer-substring (point)
+ (progn (end-of-line) (point))))
+ (if (not cvs-cvsroot)
+ (error "Invalid contents of %sCVS/Root" this-dir))
+ (kill-buffer (current-buffer)))
+ (if (and cvs-cvsroot-required
+ (not (or (getenv "CVSROOT") cvs-cvsroot)))
+ (error "Both cvs-cvsroot and environment variable CVSROOT are unset, and no CVS/Root.")))
+
+ ;; Check that at most one `cvs update' is run at any time.
+
+ (if (and cvs-update-running (process-status cvs-update-running)
+ (or (eq (process-status cvs-update-running) 'run)
+ (eq (process-status cvs-update-running) 'stop)))
+ (error "Can't run two `cvs update' simultaneously."))
+
+ (if (not (listp cvs-update-optional-flags))
+ (error "cvs-update-optional-flags should be set using cvs-set-update-optional-flags"))
+
+ ;; Generate "-d /master -n update -l".
+ (setq args (concat (if cvs-cvsroot (concat " -d " cvs-cvsroot))
+ (if dont-change-disc " -n ")
+ " update "
+ (if local " -l ")
+ (if cvs-update-optional-flags
+ (mapconcat 'identity
+ (copy-sequence cvs-update-optional-flags)
+ " "))))
+
+ ;; Set up the buffer that receives the stderr output from "cvs update".
+ (set-buffer update-buffer)
+ (setq default-directory this-dir)
+ (make-local-variable 'cvs-stdout-file)
+ (setq cvs-stdout-file temp-name)
+
+ (setq cvs-update-running
+ (let ((process-connection-type nil)) ; Use a pipe, not a pty.
+ (start-process "cvs" update-buffer cvs-shell "-c"
+ (concat cvs-program " " args " > " temp-name))))
+
+ (setq mode-line-process
+ (concat ": "
+ (symbol-name (process-status cvs-update-running))))
+ (set-buffer-modified-p (buffer-modified-p)) ; Update the mode line.
+ (set-process-sentinel cvs-update-running 'cvs-sentinel)
+ (set-process-filter cvs-update-running 'cvs-update-filter)
+ (set-marker (process-mark cvs-update-running) (point-min))
+
+ (save-excursion
+ (set-buffer (get-buffer-create cvs-buffer-name))
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (cvs-mode))
+
+ (setq cvs-cookie-handle
+ (collection-create
+ cvs-buffer-name 'cvs-pp
+ cvs-startup-message ;See comment above cvs-startup-message.
+ "---------- End -----"))
+
+ (cookie-enter-first
+ cvs-cookie-handle
+ (cvs-create-fileinfo
+ 'MESSAGE nil nil (concat "\n Running `cvs " args "' in " this-dir
+ "...\n")))
+
+ (save-excursion
+ (set-buffer cvs-buffer-name)
+ (setq mode-line-process
+ (concat ": "
+ (symbol-name (process-status cvs-update-running))))
+ (set-buffer-modified-p (buffer-modified-p)) ; Update the mode line.
+ (setq buffer-read-only t))
+
+ ;; Work around a bug in emacs 18.57 and earlier.
+ (setq cvs-buffers-to-delete
+ (cvs-delete-unused-temporary-buffers cvs-buffers-to-delete)))
+
+ ;; The following line is said to improve display updates on some
+ ;; emacses. It shouldn't be needed, but it does no harm.
+ (sit-for 0))
+
+;;----------
+(defun cvs-delete-unused-temporary-buffers (list)
+ "Delete all buffers on LIST that is not visible.
+Return a list of all buffers that still is alive."
+
+ (cond
+ ((null list) nil)
+ ((get-buffer-window (car list))
+ (cons (car list)
+ (cvs-delete-unused-temporary-buffers (cdr list))))
+ (t
+ (kill-buffer (car list))
+ (cvs-delete-unused-temporary-buffers (cdr list)))))
+
+;;----------
+(put 'cvs-mode 'mode-class 'special)
+
+;;----------
+(defun cvs-mode ()
+ "\\<cvs-mode-map>Mode used for pcl-cvs, a front-end to CVS.
+
+To get to the \"*cvs*\" buffer you should use ``\\[execute-extended-command] cvs-update''.
+
+Full documentation is in the Texinfo file. Here are the most useful commands:
+
+\\[cvs-mode-previous-line] Move up. \\[cvs-mode-next-line] Move down.
+\\[cvs-mode-commit] Commit file. \\[cvs-mode-update-no-prompt] Re-update directory.
+\\[cvs-mode-mark] Mark file/dir. \\[cvs-mode-unmark] Unmark file/dir.
+\\[cvs-mode-mark-all-files] Mark all files. \\[cvs-mode-unmark-all-files] Unmark all files.
+\\[cvs-mode-find-file] Edit file/run Dired. \\[cvs-mode-find-file-other-window] Find file or run Dired in other window.
+\\[cvs-mode-ignore] Add file to ./.cvsignore. \\[cvs-mode-add-change-log-entry-other-window] Write ChangeLog in other window.
+\\[cvs-mode-add] Add to repository. \\[cvs-mode-remove-file] Remove file.
+\\[cvs-mode-diff-cvs] Diff with base revision. \\[cvs-mode-diff-backup] Diff backup file.
+\\[cvs-mode-ediff] Ediff base rev & backup. \\[cvs-mode-diff-vendor] Show merge from vendor branch.
+\\[cvs-mode-emerge] Emerge base rev & backup. \\[cvs-mode-diff-backup] Diff backup file.
+\\[cvs-mode-acknowledge] Delete line from buffer. \\[cvs-mode-remove-handled] Remove processed entries.
+\\[cvs-mode-log] Run ``cvs log''. \\[cvs-mode-status] Run ``cvs status''.
+\\[cvs-mode-tag] Run ``cvs tag''. \\[cvs-mode-rtag] Run ``cvs rtag''.
+\\[cvs-mode-changelog-commit] Like \\[cvs-mode-commit], but get default log text from ChangeLog.
+\\[cvs-mode-undo-local-changes] Revert the last checked in version - discard your changes to the file.
+
+Entry to this mode runs cvs-mode-hook.
+This description is updated for release 1.05-CVS-$Name: $ of pcl-cvs.
+
+All bindings:
+\\{cvs-mode-map}"
+
+ (interactive)
+ (setq major-mode 'cvs-mode)
+ (setq mode-name "CVS")
+ (setq mode-line-process nil)
+;; for older v18 emacs
+;;(buffer-flush-undo (current-buffer))
+ (buffer-disable-undo (current-buffer))
+ (make-local-variable 'goal-column)
+ (setq goal-column cvs-cursor-column)
+ (use-local-map cvs-mode-map)
+ (run-hooks 'cvs-mode-hook))
+
+;;----------
+(defun cvs-sentinel (proc msg)
+ "Sentinel for the cvs update process.
+This is responsible for parsing the output from the cvs update when
+it is finished."
+
+ (cond
+ ((null (buffer-name (process-buffer proc)))
+ ;; buffer killed
+ (set-process-buffer proc nil))
+ ((memq (process-status proc) '(signal exit))
+ (let* ((obuf (current-buffer))
+ (omax (point-max))
+ (opoint (point)))
+ ;; save-excursion isn't the right thing if
+ ;; process-buffer is current-buffer
+ (unwind-protect
+ (progn
+ (set-buffer (process-buffer proc))
+ (setq mode-line-process
+ (concat ": "
+ (symbol-name (process-status proc))))
+ (let* ((out-file cvs-stdout-file)
+ (stdout-buffer (find-file-noselect out-file)))
+ (save-excursion
+ (set-buffer stdout-buffer)
+ (rename-buffer (concat " "
+ (file-name-nondirectory out-file)) t))
+ (cvs-parse-update stdout-buffer (process-buffer proc))
+ (setq cvs-buffers-to-delete
+ (cons (process-buffer proc)
+ (cons stdout-buffer
+ cvs-buffers-to-delete)))
+ (delete-file out-file)))
+ (set-buffer-modified-p (buffer-modified-p))
+ (setq cvs-update-running nil))
+ (if (equal obuf (process-buffer proc))
+ nil
+ (set-buffer (process-buffer proc))
+ (if (< opoint omax)
+ (goto-char opoint))
+ (set-buffer obuf))))))
+
+;;----------
+(defun cvs-update-filter (proc string)
+ "Filter function for pcl-cvs.
+This function gets the output that CVS sends to stderr. It inserts it
+into (process-buffer proc) but it also checks if CVS is waiting for a
+lock file. If so, it inserts a message cookie in the *cvs* buffer."
+
+ (let ((old-buffer (current-buffer))
+ (data (match-data)))
+ (unwind-protect
+ (progn
+ (set-buffer (process-buffer proc))
+ (save-excursion
+ ;; Insert the text, moving the process-marker.
+ (goto-char (process-mark proc))
+ (insert string)
+ (set-marker (process-mark proc) (point))
+ ;; Delete any old lock message
+ (if (tin-nth cvs-cookie-handle 1)
+ (tin-delete cvs-cookie-handle
+ (tin-nth cvs-cookie-handle 1)))
+ ;; Check if CVS is waiting for a lock.
+ (beginning-of-line 0) ;Move to beginning of last
+ ;complete line.
+ (cond
+ ((looking-at
+ "^cvs \\(update\\|server\\): \\[..:..:..\\] waiting for \\(.*\\)lock in \\(.*\\)$")
+ (setq cvs-lock-file (buffer-substring (match-beginning 3)
+ (match-end 3)))
+ (cookie-enter-last
+ cvs-cookie-handle
+ (cvs-create-fileinfo
+ 'MESSAGE nil nil
+ (concat "\tWaiting for "
+ (buffer-substring (match-beginning 2)
+ (match-end 2))
+ "lock in " cvs-lock-file
+ ".\n\t (type M-x cvs-delete-lock to delete it)")))))))
+ (store-match-data data)
+ (set-buffer old-buffer))))
+
+;;----------
+(defun cvs-delete-lock ()
+ "Delete the lock file that CVS is waiting for.
+Note that this can be dangerous. You should only do this
+if you are convinced that the process that created the lock is dead."
+
+ (interactive)
+ (cond
+ ((not (or (file-exists-p
+ (concat (file-name-as-directory cvs-lock-file) "#cvs.lock"))
+ (cvs-filter (function cvs-lock-file-p)
+ (directory-files cvs-lock-file))))
+ (error "No lock files found."))
+ ((yes-or-no-p (concat "Really delete locks in " cvs-lock-file "? "))
+ ;; Re-read the directory -- the locks might have disappeared.
+ (let ((locks (cvs-filter (function cvs-lock-file-p)
+ (directory-files cvs-lock-file))))
+ (while locks
+ (delete-file (concat (file-name-as-directory cvs-lock-file)
+ (car locks)))
+ (setq locks (cdr locks)))
+ (cvs-remove-directory
+ (concat (file-name-as-directory cvs-lock-file) "#cvs.lock"))))))
+
+;;----------
+(defun cvs-remove-directory (dir)
+ "Remove a directory."
+
+ (if (file-directory-p dir)
+ (call-process cvs-rmdir-program nil nil nil dir)
+ (error "Not a directory: %s" dir))
+ (if (file-exists-p dir)
+ (error "Could not remove directory %s" dir)))
+
+;;----------
+(defun cvs-lock-file-p (file)
+ "Return true if FILE looks like a CVS lock file."
+
+ (or
+ (string-match "^#cvs.tfl.[0-9]+$" file)
+ (string-match "^#cvs.rfl.[0-9]+$" file)
+ (string-match "^#cvs.wfl.[0-9]+$" file)))
+
+;;----------
+(defun cvs-quote-multiword-string (str)
+ "Return STR surrounded in single quotes if it contains whitespace."
+ (cond ((string-match "[ \t\n]" str)
+ (concat "'" str "'"))
+ (t
+ str)))
+
+;;----------
+;; this should be in subr.el or some similar place....
+(defun parse-string (str &optional regexp)
+ "Explode the string STR into a list of words ala strtok(3). Optional REGEXP
+defines regexp matching word separator, which defaults to \"[ \\t\\n]+\"."
+ (let (str-list ; new list
+ str-token ; "index" of next token
+ (str-start 0) ; "index" of current token
+ (str-sep (if regexp
+ regexp
+ "[ \t\n]+")))
+ (while (setq str-token (string-match str-sep str str-start))
+ (setq str-list
+ (nconc str-list
+ (list (substring str str-start str-token))))
+ (setq str-start (match-end 0)))
+ ;; tag on the remainder as the final item
+ (if (not (>= str-start (length str)))
+ (setq str-list
+ (nconc str-list
+ (list (substring str str-start)))))
+ str-list))
+
+;;----------
+(defun cvs-make-list (str)
+ "Return list of words made from the string STR."
+ (cond ((string-match "[ \t\n]+" str)
+ (let ((new-str (parse-string str "[ \t\n]+")))
+ ;; this is ugly, but assume if the first element is empty, there are
+ ;; no more elements.
+ (cond ((string= (car new-str) "")
+ nil)
+ (t
+ new-str))))
+ ((string= str "")
+ nil)
+ (t
+ (list str))))
+
+;;----------
+(defun cvs-skip-line (stdout stderr regexp &optional arg)
+ "Like forward-line, but check that the skipped line matches REGEXP.
+Args: STDOUT STDERR REGEXP &optional ARG.
+
+If it doesn't match REGEXP a bug report is generated and displayed.
+STDOUT and STDERR is only used to do that.
+
+If optional ARG, a number, is given the ARGth parenthesized expression
+in the REGEXP is returned as a string.
+Point should be in column 1 when this function is called."
+
+ (cond
+ ((looking-at regexp)
+ (forward-line 1)
+ (if arg
+ (buffer-substring (match-beginning arg)
+ (match-end arg))))
+ (t
+ (cvs-parse-error stdout
+ stderr
+ (if (eq (current-buffer) stdout)
+ 'STDOUT
+ 'STDERR)
+ (point)
+ regexp))))
+
+;;----------
+(defun cvs-get-current-dir (root-dir dirname)
+ "Return current working directory, suitable for cvs-parse-update.
+Args: ROOT-DIR DIRNAME.
+
+Concatenates ROOT-DIR and DIRNAME to form an absolute path."
+
+ (if (string= "." dirname)
+ (substring root-dir 0 -1)
+ (concat root-dir dirname)))
+
+;;----------
+(defun cvs-compare-fileinfos (a b)
+ "Compare fileinfo A with fileinfo B and return t if A is `less'."
+
+ (cond
+ ;; Sort acording to directories.
+ ((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t)
+ ((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil)
+ ;; The DIRCHANGE entry is always first within the directory.
+ ((and (eq (cvs-fileinfo->type a) 'DIRCHANGE)
+ (not (eq (cvs-fileinfo->type b) 'DIRCHANGE))) t)
+ ((and (eq (cvs-fileinfo->type b) 'DIRCHANGE)
+ (not (eq (cvs-fileinfo->type a) 'DIRCHANGE))) nil)
+ ;; All files are sorted by file name.
+ ((string< (cvs-fileinfo->file-name a) (cvs-fileinfo->file-name b)))))
+
+;;----------
+(defun cvs-parse-error (stdout-buffer stderr-buffer err-buf pos &optional indicator)
+ "Handle a parse error when parsing the output from cvs.
+Args: STDOUT-BUFFER STDERR-BUFFER ERR-BUF POS &optional INDICATOR.
+
+ERR-BUF should be 'STDOUT or 'STDERR."
+
+ (setq pos (1- pos))
+ (set-buffer cvs-buffer-name)
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (insert "To: " pcl-cvs-bugs-address "\n")
+ (insert "Subject: pcl-cvs release" pcl-cvs-version " parse error.\n")
+ (insert (concat mail-header-separator "\n"))
+ (insert "This bug report is automatically generated by pcl-cvs\n")
+ (insert "because it doesn't understand some output from CVS. Below\n")
+ (insert "is detailed information about the error. Please send\n")
+ (insert "this, together with any information you think might be\n")
+ (insert "useful for me to fix the bug, to the address above. But\n")
+ (insert "please check the \"known problems\" section of the\n")
+ (insert "documentation first. Note that this buffer contains\n")
+ (insert "information that you might consider confidential. You\n")
+ (insert "are encouraged to read through it before sending it.\n")
+ (insert "\n")
+ (insert "Press C-c C-c to send this email.\n\n")
+ (insert "Please state the version of these programs you are using:\n\n")
+ (insert "RCS: \ndiff: \n\n")
+
+ (let* ((stdout (save-excursion (set-buffer stdout-buffer) (buffer-string)))
+ (stderr (save-excursion (set-buffer stderr-buffer) (buffer-string)))
+ (errstr (if (eq err-buf 'STDOUT) stdout stderr))
+ (errline-end (string-match "\n" errstr pos))
+ (errline (substring errstr pos errline-end)))
+ (insert (format "Offending line (%d chars): >" (- errline-end pos)))
+ (insert errline)
+ (insert "<\n")
+ (insert "Sent to " (symbol-name err-buf) " at pos " (format "%d\n" pos))
+ (if indicator
+ (insert "Optional args: \"" indicator "\".\n"))
+ (insert "\nEmacs-version: " (emacs-version) "\n")
+ (insert "Pcl-cvs Version: "
+ "@(#)OrigId: pcl-cvs.el,v 1.93 1993/05/31 22:44:00 ceder Exp\n")
+ (insert "CVS Version: "
+ "@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs.el,v 1.2 1996/04/14 20:09:45 kfogel Exp $\n\n")
+ (insert (format "--- Contents of stdout buffer (%d chars) ---\n"
+ (length stdout)))
+ (insert stdout)
+ (insert "--- End of stdout buffer ---\n")
+ (insert (format "--- Contents of stderr buffer (%d chars) ---\n"
+ (length stderr)))
+ (insert stderr)
+ (insert "--- End of stderr buffer ---\n")
+ (insert "\nEnd of bug report.\n")
+ (require 'sendmail)
+ (mail-mode)
+ (error "CVS parse error - please report this bug.")))
+
+;;----------
+(defun cvs-parse-update (stdout-buffer stderr-buffer)
+ "Parse the output from `cvs update'.
+
+Args: STDOUT-BUFFER STDERR-BUFFER.
+
+This functions parses the from `cvs update' (which should be
+separated in its stdout- and stderr-components) and prints a
+pretty representation of it in the *cvs* buffer.
+
+Signals an error if unexpected output was detected in the buffer."
+
+ (let* ((head (cons 'dummy nil))
+ (tail (cvs-parse-stderr stdout-buffer stderr-buffer
+ head default-directory))
+ (root-dir default-directory))
+ (cvs-parse-stdout stdout-buffer stderr-buffer tail root-dir)
+ (setq head (sort (cdr head) (function cvs-compare-fileinfos)))
+ (collection-clear cvs-cookie-handle)
+ (collection-append-cookies cvs-cookie-handle head)
+ (cvs-remove-stdout-shadows)
+ (if cvs-auto-remove-handled-directories
+ (cvs-remove-empty-directories))
+ (set-buffer cvs-buffer-name)
+ (cvs-mode)
+ (goto-char (point-min))
+ (tin-goto-previous cvs-cookie-handle (point-min) 1)
+ (setq default-directory root-dir)))
+
+;;----------
+(defun cvs-remove-stdout-shadows ()
+ "Remove entries in the *cvs* buffer that comes from both stdout and stderr.
+If there is two entries for a single file the second one should be
+deleted. (Remember that sort uses a stable sort algorithm, so one can
+be sure that the stderr entry is always first)."
+
+ (collection-filter-tins cvs-cookie-handle
+ (function
+ (lambda (tin)
+ (not (cvs-shadow-entry-p tin))))))
+
+;;----------
+(defun cvs-shadow-entry-p (tin)
+ "Return non-nil if TIN is a shadow entry.
+Args: TIN.
+
+A TIN is a shadow entry if the previous tin contains the same file."
+
+ (let* ((previous-tin (tin-previous cvs-cookie-handle tin))
+ (curr (tin-cookie cvs-cookie-handle tin))
+ (prev (and previous-tin
+ (tin-cookie cvs-cookie-handle previous-tin))))
+ (and
+ prev curr
+ (string= (cvs-fileinfo->file-name prev)
+ (cvs-fileinfo->file-name curr))
+ (string= (cvs-fileinfo->dir prev)
+ (cvs-fileinfo->dir curr))
+ (or
+ (and (eq (cvs-fileinfo->type prev) 'CONFLICT)
+ (eq (cvs-fileinfo->type curr) 'CONFLICT))
+ (and (eq (cvs-fileinfo->type prev) 'MERGED)
+ (eq (cvs-fileinfo->type curr) 'MODIFIED))
+ (and (eq (cvs-fileinfo->type prev) 'REM-EXIST)
+ (eq (cvs-fileinfo->type curr) 'REMOVED))))))
+
+;;----------
+(defun cvs-find-backup-file (filename &optional dirname)
+ "Look for a backup file for FILENAME, optionally in directory DIRNAME, and if
+there is one, return the name of the first file found as a string."
+
+ (if (eq dirname nil)
+ (setq dirname default-directory))
+ (car (directory-files dirname nil (concat "^\\" cvs-bakprefix filename
+ "\\."))))
+
+;;----------
+(defun cvs-find-backup-revision (filename)
+ "Take FILENAME as the name of a cvs backup file and return the revision of
+that file as a string."
+
+ (substring filename
+ (+ 1 (string-match "\\.\\([0-9.]+\\)$" filename))))
+
+;;----------
+(defun cvs-parse-stderr (stdout-buffer stderr-buffer head dir)
+ "Parse the output from CVS that is written to stderr.
+Args: STDOUT-BUFFER STDERR-BUFFER HEAD DIR
+
+STDOUT-BUFFER holds the output that cvs sent to stdout. It is only
+used to create a bug report in case there is a parse error.
+STDERR-BUFFER is the buffer that holds the output to parse.
+HEAD is a cons-cell, the head of the list that is built.
+DIR is the directory the `cvs update' was run in.
+
+This function returns the last cons-cell in the list that is built."
+
+ (save-window-excursion
+ (set-buffer stderr-buffer)
+ (goto-char (point-min))
+ (let ((current-dir dir)
+ (root-dir dir))
+
+ (while (< (point) (point-max))
+ (cond
+
+ ;; CVS is descending a subdirectory.
+
+ ((looking-at
+ "^cvs \\(server\\|update\\): Updating \\(.*\\)$")
+ (setq current-dir
+ (cvs-get-current-dir
+ root-dir
+ (buffer-substring (match-beginning 2) (match-end 2))))
+ (setcdr head (list (cvs-create-fileinfo
+ 'DIRCHANGE
+ current-dir
+ "." ; the old version had nil here???
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))
+ (setq head (cdr head))
+ (forward-line 1))
+
+ ;; File removed, since it is removed (by third party) in repository.
+
+ ((or (looking-at
+ "^cvs \\(update\\|server\\): warning: \\(.*\\) is not (any longer) pertinent")
+ (looking-at
+ "^cvs \\(update\\|server\\): \\(.*\\) is no longer in the repository"))
+
+ (setcdr head (list (cvs-create-fileinfo
+ 'CVS-REMOVED
+ current-dir
+ (file-name-nondirectory
+ (buffer-substring (match-beginning 2)
+ (match-end 2)))
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))
+ (setq head (cdr head))
+ (forward-line 1))
+
+ ;; File removed by you, but recreated by cvs. Ignored. Will say
+ ;; "Updated" on the next line.
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): warning: .* was lost$")
+ (forward-line 1))
+
+ ;; File unknown for some reason.
+ ;; FIXME: is it really a good idea to add this as unknown here?
+
+ ((looking-at
+ "cvs \\(update\\|server\\): nothing known about \\(.*\\)$")
+ (let ((filename (buffer-substring (match-beginning 2)
+ (match-end 2))))
+ (if (file-directory-p filename)
+ (setcdr head (list (cvs-create-fileinfo
+ 'UNKNOWN-DIR
+ current-dir
+ "."
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))
+ (setcdr head (list (cvs-create-fileinfo
+ 'UNKNOWN
+ current-dir
+ (file-name-nondirectory filename)
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))))
+ (setq head (cdr head))
+ (forward-line 1))
+
+ ;; A file that has been created by you, but added to the cvs
+ ;; repository by another.
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): move away \\(.*\\); it is in the way$")
+ (setcdr head (list (cvs-create-fileinfo
+ 'MOVE-AWAY
+ current-dir
+ (file-name-nondirectory
+ (buffer-substring (match-beginning 2)
+ (match-end 2)))
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))
+ (setq head (cdr head))
+ (forward-line 1))
+
+ ;; Cvs waits for a lock. Ignore.
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): \\[..:..:..\\] waiting for .*lock in ")
+ (forward-line 1))
+
+ ;; File removed in repository, but edited by you.
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): conflict: \\(.*\\) is modified but no longer in the repository$")
+ (setcdr head (list
+ (cvs-create-fileinfo
+ 'REM-CONFLICT
+ current-dir
+ (file-name-nondirectory
+ (buffer-substring (match-beginning 2)
+ (match-end 2)))
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))
+ (setq head (cdr head))
+ (forward-line 1))
+
+ ;; File removed in repository, but edited by someone else.
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): conflict: removed \\(.*\\) was modified by second party")
+ (setcdr head
+ (list
+ (cvs-create-fileinfo
+ 'MOD-CONFLICT
+ current-dir
+ (buffer-substring (match-beginning 1)
+ (match-end 1))
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))
+ (setq head (cdr head))
+ (forward-line 1))
+
+ ;; File removed in repository, but not in local directory.
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): \\(.*\\) should be removed and is still there")
+ (setcdr head
+ (list
+ (cvs-create-fileinfo
+ 'REM-EXIST
+ current-dir
+ (buffer-substring (match-beginning 2)
+ (match-end 2))
+ (buffer-substring (match-beginning 0)
+ (match-end 0)))))
+ (setq head (cdr head))
+ (forward-line 1))
+
+ ;; Error searching for repository
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): in directory ")
+ (let ((start (point)))
+ (forward-line 1)
+ (cvs-skip-line stdout-buffer stderr-buffer
+ (regexp-quote "cvs [update aborted]: there is no repository "))
+ (setcdr head (list (cvs-create-fileinfo
+ 'REPOS-MISSING
+ current-dir
+ nil
+ (buffer-substring start (point)))))
+ (setq head (cdr head))))
+
+ ;; Silly warning from attempted conflict resolution. Ignored.
+ ;; FIXME: Should it be?
+ ;; eg.: "cvs update: cannot find revision APC-web-update in file .cvsignore"
+ ;;
+ ((looking-at
+ "^cvs \\(update\\|server\\): cannot find revision \\(.*\\) in file \\(.*\\)$")
+ (forward-line 1)
+ (message "%s" (buffer-substring (match-beginning 0) (match-end 0))))
+
+ ;; CVS has decided to merge someone elses changes into this document.
+ ;; About to start an rcsmerge operation...
+ ;;
+ ((looking-at
+ "^RCS file: ")
+
+ ;; skip the "RCS file:" line...
+ (forward-line 1)
+
+ (let ((complex-start (point))
+ base-revision ; the first revision retrieved to merge from
+ head-revision ; the second revision retrieved to merge from
+ filename ; the name of the file being merged
+ backup-file ; the name of the backup of the working file
+ backup-revision) ; the revision of the original working file
+
+ (setq base-revision
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^retrieving revision \\(.*\\)$"
+ 1))
+ (setq head-revision
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^retrieving revision \\(.*\\)$"
+ 1))
+ (setq filename
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$"
+ 1))
+ (setq backup-file
+ (cvs-find-backup-file filename current-dir))
+ (setq backup-revision
+ (cvs-find-backup-revision backup-file))
+
+ ;; Was there a conflict during the merge?
+
+ (cond
+
+ ;;;; From CVS-1.3 & RCS-5.6.0.1 with GNU-Diffutils-2.5:
+ ;;;; "cvs update -j OLD-REV -j NEW-REV ."
+ ;;
+ ;; RCS file: /big/web-CVS/apc/cmd/Main/logout.sh,v
+ ;; retrieving revision 1.1.1.1
+ ;; retrieving revision 1.1.1.2
+ ;; Merging differences between 1.1.1.1 and 1.1.1.2 into logout.sh
+ ;; rcsmerge warning: overlaps during merge
+
+ ((looking-at
+ ;; Allow both RCS 5.5 and 5.6. (5.6 prints "rcs" and " warning").
+ "^\\(rcs\\)?merge[:]*\\( warning\\)?: \\((overlaps\\|conflicts\\) during merge$")
+
+ ;; Yes, this is a conflict.
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^\\(rcs\\)?merge[:]*\\( warning\\)?: \\(overlaps\\|conflicts\\) during merge$")
+
+ ;; this line doesn't seem to appear in all cases -- perhaps only
+ ;; in "-j A -j B" usage, in which case this indicates ????
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^cvs \\(update\\|server\\): conflicts found in ")
+
+ (let ((fileinfo
+ (cvs-create-fileinfo
+ 'CONFLICT current-dir
+ filename
+ (buffer-substring complex-start (point)))))
+
+ ;; squirrel away info about the files that were retrieved for merging
+ (cvs-set-fileinfo->base-revision fileinfo base-revision)
+ (cvs-set-fileinfo->head-revision fileinfo head-revision)
+ (cvs-set-fileinfo->backup-revision fileinfo backup-revision)
+ (cvs-set-fileinfo->backup-file fileinfo backup-file)
+
+ (setcdr head (list fileinfo))
+ (setq head (cdr head))))
+
+ ;; Was it a conflict, and was RCS compiled without DIFF3_BIN, in
+ ;; which case this is a failed conflict resolution?
+
+ ((looking-at
+ ;; Allow both RCS 5.5 and 5.6. (5.6 prints "rcs" and " warning").
+ "^\\(rcs\\)?merge\\( warning\\)?: overlaps or other problems during merge$")
+
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^\\(rcs\\)?merge\\( warning\\)?: overlaps or other problems during merge$")
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^cvs update: could not merge ")
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^cvs update: restoring .* from backup file ")
+ (let ((fileinfo
+ (cvs-create-fileinfo
+ 'CONFLICT current-dir
+ filename
+ (buffer-substring complex-start (point)))))
+ (setcdr head (list fileinfo))
+ (setq head (cdr head))))
+
+ ;; Not a conflict; it must be a succesful merge.
+
+ (t
+ (let ((fileinfo
+ (cvs-create-fileinfo
+ 'MERGED current-dir
+ filename
+ (buffer-substring complex-start (point)))))
+ (cvs-set-fileinfo->base-revision fileinfo base-revision)
+ (cvs-set-fileinfo->head-revision fileinfo head-revision)
+ (cvs-set-fileinfo->backup-revision fileinfo backup-revision)
+ (cvs-set-fileinfo->backup-file fileinfo backup-file)
+ (setcdr head (list fileinfo))
+ (setq head (cdr head)))))))
+
+ ;; Error messages from CVS (incomplete)
+
+ ((looking-at
+ "^cvs \\(update\\|server\\): \\(invalid option .*\\)$")
+ (error "Interface problem with CVS: %s"
+ (buffer-substring (match-beginning 2) (match-end 2))))
+
+ ;; network errors
+
+ ;; Kerberos connection attempted but failed. This is not
+ ;; really an error, as CVS will automatically fall back to
+ ;; rsh. Plus it tries kerberos, if available, even when rsh
+ ;; is what you really wanted.
+
+ ((looking-at
+ "^cvs update: kerberos connect:.*$")
+ (forward-line 1)
+ (message "Remote CVS: %s"
+ (buffer-substring (match-beginning 0) (match-end 0))))
+
+ ;; And when kerberos *does* fail, cvs prints out some stuff
+ ;; as it tries rsh. Ignore that stuff too.
+
+ ((looking-at
+ "^cvs update: trying to start server using rsh$")
+ (forward-line 1))
+
+ ((looking-at
+ "^\\([^:]*\\) Connection timed out")
+ (error "Remote CVS: %s"
+ (buffer-substring (match-beginning 0) (match-end 0))))
+
+ ((looking-at
+ "^Permission denied.")
+ (error "Remote CVS: %s"
+ (buffer-substring (match-beginning 0) (match-end 0))))
+
+ ((looking-at
+ "^cvs \\[update aborted\\]: premature end of file from server")
+ (error "Remote CVS: %s"
+ (buffer-substring (match-beginning 0) (match-end 0))))
+
+ ;; Empty line. Probably inserted by mistake by user (or developer :-)
+ ;; Ignore.
+
+ ((looking-at
+ "^$")
+ (forward-line 1))
+
+ ;; top-level parser (cond) default clause
+
+ (t
+ (cvs-skip-line stdout-buffer stderr-buffer
+ "^UN-MATCHABLE-OUTPUT"))))))
+
+ ;; cause this function to return the head of the parser output list
+ head)
+
+;;----------
+(defun cvs-parse-stdout (stdout-buffer stderr-buffer head root-dir)
+ "Parse the output from CVS that is written to stderr.
+Args: STDOUT-BUFFER STDERR-BUFFER HEAD ROOT-DIR
+
+STDOUT-BUFFER is the buffer that holds the output to parse.
+STDERR-BUFFER holds the output that cvs sent to stderr. It is only
+used to create a bug report in case there is a parse error.
+
+HEAD is a cons-cell, the head of the list that is built.
+ROOT-DIR is the directory the `cvs update' was run in.
+
+This function doesn't return anything particular."
+
+ (save-window-excursion
+ (set-buffer stdout-buffer)
+ (goto-char (point-min))
+ (while (< (point) (point-max))
+ (cond
+
+ ;; M: The file is modified by the user, and untouched in the repository.
+ ;; A: The file is "cvs add"ed, but not "cvs ci"ed.
+ ;; R: The file is "cvs remove"ed, but not "cvs ci"ed.
+ ;; C: Conflict (only useful if a join was done and stderr has info...)
+ ;; U: The file is copied from the repository.
+ ;; ?: Unknown file or directory.
+
+ ((looking-at
+ "^\\([MARCUP?]\\) \\(.*\\)$")
+ (let*
+ ((c (char-after (match-beginning 1)))
+ (full-path (concat (file-name-as-directory root-dir)
+ (buffer-substring (match-beginning 2)
+ (match-end 2))))
+ (isdir (file-directory-p full-path))
+ (fileinfo (cvs-create-fileinfo
+ (cond ((eq c ?M) 'MODIFIED)
+ ((eq c ?A) 'ADDED)
+ ((eq c ?R) 'REMOVED)
+ ((eq c ?C) 'CONFLICT)
+ ((eq c ?U) 'UPDATED)
+ ((eq c ?P) 'PATCHED)
+ ((eq c ??) (if isdir
+ 'UNKNOWN-DIR
+ 'UNKNOWN)))
+ (substring (file-name-directory full-path) 0 -1)
+ (file-name-nondirectory full-path)
+ (buffer-substring (match-beginning 0) (match-end 0)))))
+ ;; Updated and Patched files require no further action.
+ (if (memq c '(?U ?P))
+ (cvs-set-fileinfo->handled fileinfo t))
+
+ ;; Link this last on the list.
+ (setcdr head (list fileinfo))
+ (setq head (cdr head))
+ (forward-line 1)))
+
+ ;; Executing a program because of the -u option in modules.
+ ((looking-at
+ "^cvs \\(update\\|server\\): Executing")
+ ;; Skip by any output the program may generate to stdout.
+ ;; Note that pcl-cvs will get seriously confused if the
+ ;; program prints anything to stderr.
+ (re-search-forward cvs-update-prog-output-skip-regexp)
+ (forward-line 1))
+
+ (t
+ (cvs-parse-error stdout-buffer stderr-buffer 'STDOUT (point)
+ "cvs-parse-stdout"))))))
+
+;;----------
+(defun cvs-pp (fileinfo)
+ "Pretty print FILEINFO. Insert a printed representation in current buffer.
+For use by the cookie package."
+
+ (let ((a (cvs-fileinfo->type fileinfo))
+ (s (if (cvs-fileinfo->marked fileinfo)
+ "*" " "))
+ (f (cvs-fileinfo->file-name fileinfo))
+ (ci (if (cvs-fileinfo->handled fileinfo)
+ " " "ci")))
+ (insert
+ (cond
+ ((eq a 'UPDATED)
+ (format "%s Updated %s" s f))
+ ((eq a 'PATCHED)
+ (format "%s Patched %s" s f))
+ ((eq a 'MODIFIED)
+ (format "%s Modified %s %s" s ci f))
+ ((eq a 'MERGED)
+ (format "%s Merged %s %s" s ci f))
+ ((eq a 'CONFLICT)
+ (format "%s Conflict %s" s f))
+ ((eq a 'ADDED)
+ (format "%s Added %s %s" s ci f))
+ ((eq a 'REMOVED)
+ (format "%s Removed %s %s" s ci f))
+ ((eq a 'UNKNOWN)
+ (format "%s Unknown %s" s f))
+ ((eq a 'UNKNOWN-DIR)
+ (format "%s Unknown dir %s" s f))
+ ((eq a 'CVS-REMOVED)
+ (format "%s Removed from repository: %s" s f))
+ ((eq a 'REM-CONFLICT)
+ (format "%s Conflict: Removed from repository, changed by you: %s" s f))
+ ((eq a 'MOD-CONFLICT)
+ (format "%s Conflict: Removed by you, changed in repository: %s" s f))
+ ((eq a 'REM-EXIST)
+ (format "%s Conflict: Removed by you, but still exists: %s" s f))
+ ((eq a 'DIRCHANGE)
+ (format "\nIn directory %s:" (cvs-fileinfo->dir fileinfo)))
+ ((eq a 'MOVE-AWAY)
+ (format "%s Move away %s - it is in the way" s f))
+ ((eq a 'REPOS-MISSING)
+ (format " This repository directory is missing! Remove this directory manually."))
+ ((eq a 'MESSAGE)
+ (cvs-fileinfo->full-log fileinfo))
+ (t
+ (format "%s Internal error! %s" s f))))))
+
+
+;;; You can define your own keymap in .emacs. pcl-cvs.el won't overwrite it.
+
+(if cvs-mode-map
+ nil
+ (setq cvs-mode-map (make-keymap))
+ (suppress-keymap cvs-mode-map)
+ (define-prefix-command 'cvs-mode-map-control-c-prefix)
+ (define-key cvs-mode-map "\C-?" 'cvs-mode-unmark-up)
+ (define-key cvs-mode-map "\C-k" 'cvs-mode-acknowledge)
+ (define-key cvs-mode-map "\C-n" 'cvs-mode-next-line)
+ (define-key cvs-mode-map "\C-p" 'cvs-mode-previous-line)
+ ;; ^C- keys are used to set various flags to control CVS features
+ (define-key cvs-mode-map "\C-c" 'cvs-mode-map-control-c-prefix)
+ (define-key cvs-mode-map "\C-c\C-c" 'cvs-change-cvsroot)
+ (define-key cvs-mode-map "\C-c\C-d" 'cvs-set-diff-flags)
+ (define-key cvs-mode-map "\C-c\C-l" 'cvs-set-log-flags)
+ (define-key cvs-mode-map "\C-c\C-s" 'cvs-set-status-flags)
+ (define-key cvs-mode-map "\C-c\C-u" 'cvs-set-update-optional-flags)
+ ;; M- keys are usually those that operate on modules
+ (define-key cvs-mode-map "\M-\C-?" 'cvs-mode-unmark-all-files)
+ (define-key cvs-mode-map "\M-C" 'cvs-mode-rcs2log) ; i.e. "Create a ChangeLog"
+ (define-key cvs-mode-map "\M-a" 'cvs-mode-admin)
+ (define-key cvs-mode-map "\M-c" 'cvs-mode-checkout)
+ (define-key cvs-mode-map "\M-o" 'cvs-mode-checkout-other-window)
+ (define-key cvs-mode-map "\M-p" 'cvs-mode-rdiff) ; i.e. "create a Patch"
+ (define-key cvs-mode-map "\M-r" 'cvs-mode-release)
+ (define-key cvs-mode-map "\M-t" 'cvs-mode-rtag)
+ ;; keys that operate on files
+ (define-key cvs-mode-map " " 'cvs-mode-next-line)
+ (define-key cvs-mode-map "?" 'describe-mode)
+ (define-key cvs-mode-map "A" 'cvs-mode-add-change-log-entry-other-window)
+ (define-key cvs-mode-map "B" 'cvs-mode-byte-compile-files)
+ (define-key cvs-mode-map "C" 'cvs-mode-changelog-commit)
+ (define-key cvs-mode-map "E" 'cvs-mode-emerge)
+ (define-key cvs-mode-map "G" 'cvs-update)
+ (define-key cvs-mode-map "M" 'cvs-mode-mark-all-files)
+ (define-key cvs-mode-map "Q" 'cvs-examine)
+ (define-key cvs-mode-map "R" 'cvs-mode-revert-updated-buffers)
+ (define-key cvs-mode-map "U" 'cvs-mode-undo-local-changes)
+ (define-key cvs-mode-map "a" 'cvs-mode-add)
+ (define-key cvs-mode-map "b" 'cvs-mode-diff-backup)
+ (define-key cvs-mode-map "c" 'cvs-mode-commit)
+ (define-key cvs-mode-map "d" 'cvs-mode-diff-cvs)
+ (define-key cvs-mode-map "e" 'cvs-mode-ediff)
+ (define-key cvs-mode-map "f" 'cvs-mode-find-file)
+ (define-key cvs-mode-map "g" 'cvs-mode-update-no-prompt)
+ (define-key cvs-mode-map "i" 'cvs-mode-ignore)
+ (define-key cvs-mode-map "l" 'cvs-mode-log)
+ (define-key cvs-mode-map "m" 'cvs-mode-mark)
+ (define-key cvs-mode-map "n" 'cvs-mode-next-line)
+ (define-key cvs-mode-map "o" 'cvs-mode-find-file-other-window)
+ (define-key cvs-mode-map "p" 'cvs-mode-previous-line)
+ (define-key cvs-mode-map "q" 'bury-buffer)
+ (define-key cvs-mode-map "r" 'cvs-mode-remove-file)
+ (define-key cvs-mode-map "s" 'cvs-mode-status)
+ (define-key cvs-mode-map "t" 'cvs-mode-tag)
+ (define-key cvs-mode-map "u" 'cvs-mode-unmark)
+ (define-key cvs-mode-map "v" 'cvs-mode-diff-vendor)
+ (define-key cvs-mode-map "x" 'cvs-mode-remove-handled))
+
+;;----------
+(defun cvs-get-marked (&optional ignore-marks ignore-contents)
+ "Return a list of all selected tins.
+Args: &optional IGNORE-MARKS IGNORE-CONTENTS.
+
+If there are any marked tins, and IGNORE-MARKS is nil, return them. Otherwise,
+if the cursor selects a directory, return all files in it, unless there are
+none, in which case just return the directory; or unless IGNORE-CONTENTS is not
+nil, in which case also just return the directory. Otherwise return (a list
+containing) the file the cursor points to, or an empty list if it doesn't point
+to a file at all."
+
+ (cond
+ ;; Any marked cookies?
+ ((and (not ignore-marks)
+ (collection-collect-tin cvs-cookie-handle 'cvs-fileinfo->marked)))
+ ;; Nope.
+ ((and (not ignore-contents)
+ (let ((sel (tin-locate cvs-cookie-handle (point))))
+ (cond
+ ;; If a directory is selected, all it members are returned.
+ ((and sel (eq (cvs-fileinfo->type (tin-cookie cvs-cookie-handle
+ sel))
+ 'DIRCHANGE))
+ (let ((retsel
+ (collection-collect-tin cvs-cookie-handle
+ 'cvs-dir-member-p
+ (cvs-fileinfo->dir (tin-cookie
+ cvs-cookie-handle sel)))))
+ (if retsel
+ retsel
+ (list sel))))
+ (t
+ (list sel))))))
+ (t
+ (list (tin-locate cvs-cookie-handle (point))))))
+
+;;----------
+(defun cvs-dir-member-p (fileinfo dir)
+ "Return true if FILEINFO represents a file in directory DIR."
+
+ (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE))
+ (string= (cvs-fileinfo->dir fileinfo) dir)))
+
+;;----------
+(defun cvs-dir-empty-p (tin)
+ "Return non-nil if TIN is a directory that is empty.
+Args: CVS-BUF TIN."
+
+ (and (eq (cvs-fileinfo->type (tin-cookie cvs-cookie-handle tin)) 'DIRCHANGE)
+ (or (not (tin-next cvs-cookie-handle tin))
+ (eq (cvs-fileinfo->type
+ (tin-cookie cvs-cookie-handle
+ (tin-next cvs-cookie-handle tin)))
+ 'DIRCHANGE))))
+
+;;----------
+(defun cvs-mode-revert-updated-buffers ()
+ "Revert any buffers that are UPDATED, PATCHED, MERGED or CONFLICT."
+
+ (interactive)
+ (cookie-map (function cvs-revert-fileinfo) cvs-cookie-handle))
+
+;;----------
+(defun cvs-revert-fileinfo (fileinfo)
+ "Revert the buffer that holds the file in FILEINFO if it has changed,
+and if the type is UPDATED, PATCHED, MERGED or CONFLICT."
+
+ (let* ((type (cvs-fileinfo->type fileinfo))
+ (file (cvs-fileinfo->full-path fileinfo))
+ (buffer (get-file-buffer file)))
+ ;; For a revert to happen...
+ (cond
+ ((and
+ ;; ...the type must be one that justifies a revert...
+ (or (eq type 'UPDATED)
+ (eq type 'PATCHED)
+ (eq type 'MERGED)
+ (eq type 'CONFLICT))
+ ;; ...and the user must be editing the file...
+ buffer)
+ (save-excursion
+ (set-buffer buffer)
+ (cond
+ ((buffer-modified-p)
+ (error "%s: edited since last cvs-update."
+ (buffer-file-name)))
+ ;; Go ahead and revert the file.
+ (t (revert-buffer 'dont-use-auto-save-file 'dont-ask))))))))
+
+;;----------
+(defun cvs-mode-remove-handled ()
+ "Remove all lines that are handled.
+Empty directories are removed."
+
+ (interactive)
+ ;; Pass one: remove files that are handled.
+ (collection-filter-cookies cvs-cookie-handle
+ (function
+ (lambda (fileinfo)
+ (not (cvs-fileinfo->handled fileinfo)))))
+ ;; Pass two: remove empty directories.
+ (if cvs-auto-remove-handled-directories
+ (cvs-remove-empty-directories)))
+
+;;----------
+(defun cvs-remove-empty-directories ()
+ "Remove empty directories in the *cvs* buffer."
+
+ (collection-filter-tins cvs-cookie-handle
+ (function
+ (lambda (tin)
+ (not (cvs-dir-empty-p tin))))))
+
+;;----------
+(defun cvs-mode-mark (pos)
+ "Mark a fileinfo.
+Args: POS.
+
+If the fileinfo is a directory, all the contents of that directory are marked
+instead. A directory can never be marked. POS is a buffer position."
+
+ (interactive "d")
+ (let* ((tin (tin-locate cvs-cookie-handle pos))
+ (sel (tin-cookie cvs-cookie-handle tin)))
+ (cond
+ ;; Does POS point to a directory? If so, mark all files in that directory.
+ ((eq (cvs-fileinfo->type sel) 'DIRCHANGE)
+ (cookie-map
+ (function (lambda (f dir)
+ (cond
+ ((cvs-dir-member-p f dir)
+ (cvs-set-fileinfo->marked f t)
+ t)))) ; Tell cookie to redisplay this cookie.
+ cvs-cookie-handle
+ (cvs-fileinfo->dir sel)))
+ (t
+ (cvs-set-fileinfo->marked sel t)
+ (tin-invalidate cvs-cookie-handle tin)
+ (tin-goto-next cvs-cookie-handle pos 1)))))
+
+;;----------
+(defun cvs-committable (tin)
+ "Check if the TIN is committable.
+It is committable if it
+ a) is not handled and
+ b) is either MODIFIED, ADDED, REMOVED, MERGED or CONFLICT."
+
+ (let* ((fileinfo (tin-cookie cvs-cookie-handle tin))
+ (type (cvs-fileinfo->type fileinfo)))
+ (and (not (cvs-fileinfo->handled fileinfo))
+ (or (eq type 'MODIFIED)
+ (eq type 'ADDED)
+ (eq type 'REMOVED)
+ (eq type 'MERGED)
+ (eq type 'CONFLICT)))))
+
+;;----------
+(defun cvs-mode-commit ()
+ "Check in all marked files, or the current file.
+The user will be asked for a log message in a buffer.
+If cvs-erase-input-buffer is non-nil that buffer will be erased.
+Otherwise mark and point will be set around the entire contents of the
+buffer so that it is easy to kill the contents of the buffer with \\[kill-region]."
+
+ (interactive)
+ (let* ((cvs-buf (current-buffer))
+ (marked (cvs-filter (function cvs-committable)
+ (cvs-get-marked))))
+ (if (null marked)
+ (error "Nothing to commit!")
+ (pop-to-buffer (get-buffer-create cvs-commit-prompt-buffer))
+ (goto-char (point-min))
+
+ (if cvs-erase-input-buffer
+ (erase-buffer)
+ (push-mark (point-max)))
+ (cvs-edit-mode)
+ (make-local-variable 'cvs-commit-list)
+ (setq cvs-commit-list marked)
+ (message "Press C-c C-c when you are done editing."))))
+
+;;----------
+(defun cvs-edit-done ()
+ "Commit the files to the repository."
+
+ (interactive)
+ (if (null cvs-commit-list)
+ (error "You have already committed the files"))
+ (if (and (> (point-max) 1)
+ (/= (char-after (1- (point-max))) ?\n)
+ (or (eq cvs-commit-buffer-require-final-newline t)
+ (and cvs-commit-buffer-require-final-newline
+ (yes-or-no-p
+ (format "Buffer %s does not end in newline. Add one? "
+ (buffer-name))))))
+ (save-excursion
+ (goto-char (point-max))
+ (insert ?\n)))
+ (save-some-buffers)
+ (let ((cc-list cvs-commit-list)
+ (cc-buffer (get-buffer cvs-buffer-name))
+ (msg-buffer (current-buffer))
+ (msg (buffer-substring (point-min) (point-max))))
+ (pop-to-buffer cc-buffer)
+ (bury-buffer msg-buffer)
+ (cvs-use-temp-buffer)
+ (message "Committing...")
+ (if (cvs-execute-list cc-list cvs-program
+ (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "commit" "-m" msg)
+ (list "commit" "-m" msg))
+ "Committing %s...")
+ (error "Something went wrong. Check the %s buffer carefully."
+ cvs-temp-buffer-name))
+ ;; FIXME: don't do any of this if the commit fails.
+ (let ((ccl cc-list))
+ (while ccl
+ (cvs-after-commit-function (tin-cookie cvs-cookie-handle (car ccl)))
+ (setq ccl (cdr ccl))))
+ (apply 'tin-invalidate cvs-cookie-handle cc-list)
+ (set-buffer msg-buffer)
+ (setq cvs-commit-list nil)
+ (set-buffer cc-buffer)
+ (if cvs-auto-remove-handled
+ (cvs-mode-remove-handled)))
+
+ (message "Committing... Done."))
+
+;;----------
+(defun cvs-after-commit-function (fileinfo)
+ "Do everything that needs to be done when FILEINFO has been committed.
+The fileinfo->handle is set, and if the buffer is present it is reverted."
+
+ (cvs-set-fileinfo->handled fileinfo t)
+ (if cvs-auto-revert-after-commit
+ (let* ((file (cvs-fileinfo->full-path fileinfo))
+ (buffer (get-file-buffer file)))
+ ;; For a revert to happen...
+ (if buffer
+ ;; ...the user must be editing the file...
+ (save-excursion
+ (set-buffer buffer)
+ (if (not (buffer-modified-p))
+ ;; ...but it must be unmodified.
+ (revert-buffer 'dont-use-auto-save-file 'dont-ask)))))))
+
+;;----------
+(defun cvs-execute-list (tin-list program constant-args &optional message-fmt)
+ "Run PROGRAM on all elements on TIN-LIST.
+Args: TIN-LIST PROGRAM CONSTANT-ARGS.
+
+The PROGRAM will be called with pwd set to the directory the files reside
+in. CONSTANT-ARGS should be a list of strings. The arguments given to the
+program will be CONSTANT-ARGS followed by all the files (from TIN-LIST) that
+resides in that directory. If the files in TIN-LIST resides in different
+directories the PROGRAM will be run once for each directory (if all files in
+the same directory appears after each other).
+
+Any output from PROGRAM will be inserted in the current buffer.
+
+This function return nil if all went well, or the numerical exit status or a
+signal name as a string. Note that PROGRAM might be called several times. This
+will return non-nil if something goes wrong, but there is no way to know which
+process that failed.
+
+If MESSAGE-FMT is not nil, then message is called to display progress with
+MESSAGE-FMT as the string. MESSAGE-FMT should contain one %s for the arg-list
+being passed to PROGRAM."
+
+ ;; FIXME: something seems wrong with the error checking here....
+
+ (let ((exitstatus nil))
+ (while tin-list
+ (let ((current-dir (cvs-fileinfo->dir (tin-cookie cvs-cookie-handle
+ (car tin-list))))
+ arg-list
+ arg-str)
+
+ ;; Collect all marked files in this directory.
+
+ (while (and tin-list
+ (string= current-dir
+ (cvs-fileinfo->dir (tin-cookie cvs-cookie-handle
+ (car tin-list)))))
+ (setq arg-list
+ (cons (cvs-fileinfo->file-name
+ (tin-cookie cvs-cookie-handle (car tin-list)))
+ arg-list))
+ (setq tin-list (cdr tin-list)))
+
+ (setq arg-list (nreverse arg-list))
+
+ ;; Execute the command on all the files that were collected.
+
+ (if message-fmt
+ (message message-fmt
+ (mapconcat 'cvs-quote-multiword-string
+ arg-list
+ " ")))
+ (setq default-directory (file-name-as-directory current-dir))
+ (insert (format "=== cd %s\n" default-directory))
+ (insert (format "=== %s %s\n\n"
+ program
+ (mapconcat 'cvs-quote-multiword-string
+ (nconc (copy-sequence constant-args)
+ arg-list)
+ " ")))
+ (let ((res (apply 'call-process program nil t t
+ (nconc (copy-sequence constant-args) arg-list))))
+ ;; Remember the first, or highest, exitstatus.
+ (if (and (not (and (integerp res) (zerop res)))
+ (or (null exitstatus)
+ (and (integerp exitstatus) (= 1 exitstatus))))
+ (setq exitstatus res)))
+ (goto-char (point-max))
+ (if message-fmt
+ (message message-fmt
+ (mapconcat 'cvs-quote-multiword-string
+ (nconc (copy-sequence arg-list) '("Done."))
+ " ")))
+ exitstatus))))
+
+;;----------
+;;;; +++ not currently used!
+(defun cvs-execute-single-file-list (tin-list extractor program constant-args
+ &optional cleanup message-fmt)
+ "Run PROGRAM on all elements on TIN-LIST.
+Args: TIN-LIST EXTRACTOR PROGRAM CONSTANT-ARGS &optional CLEANUP.
+
+The PROGRAM will be called with pwd set to the directory the files
+reside in. CONSTANT-ARGS is a list of strings to pass as arguments to
+PROGRAM. The arguments given to the program will be CONSTANT-ARGS
+followed by the list that EXTRACTOR returns.
+
+EXTRACTOR will be called once for each file on TIN-LIST. It is given
+one argument, the cvs-fileinfo. It can return t, which means ignore
+this file, or a list of arguments to send to the program.
+
+If CLEANUP is not nil, the filenames returned by EXTRACTOR are deleted.
+
+If MESSAGE-FMT is not nil, then message is called to display progress with
+MESSAGE-FMT as the string. MESSAGE-FMT should contain one %s for the arg-list
+being passed to PROGRAM."
+
+ (while tin-list
+ (let ((current-dir (file-name-as-directory
+ (cvs-fileinfo->dir
+ (tin-cookie cvs-cookie-handle
+ (car tin-list)))))
+ (arg-list
+ (funcall extractor
+ (tin-cookie cvs-cookie-handle (car tin-list)))))
+
+ ;; Execute the command unless extractor returned t.
+
+ (if (eq arg-list t)
+ nil
+ (setq default-directory current-dir)
+ (insert (format "=== cd %s\n" default-directory))
+ (insert (format "=== %s %s\n\n"
+ program
+ (mapconcat 'cvs-quote-multiword-string
+ (nconc (copy-sequence constant-args)
+ arg-list)
+ " ")))
+ (if message-fmt
+ (message message-fmt (mapconcat 'cvs-quote-multiword-string
+ arg-list
+ " ")))
+ (apply 'call-process program nil t t
+ (nconc (copy-sequence constant-args) arg-list))
+ (goto-char (point-max))
+ (if message-fmt
+ (message message-fmt (mapconcat 'cvs-quote-multiword-string
+ (nconc arg-list '("Done."))
+ " ")))
+ (if cleanup
+ (while arg-list
+;;;; (kill-buffer ?????)
+ (delete-file (car arg-list))
+ (setq arg-list (cdr arg-list))))))
+ (setq tin-list (cdr tin-list))))
+
+;;----------
+(defun cvs-edit-mode ()
+ "\\<cvs-edit-mode-map>Mode for editing cvs log messages.
+Commands:
+\\[cvs-edit-done] checks in the file when you are ready.
+This mode is based on fundamental mode."
+
+ (interactive)
+ (use-local-map cvs-edit-mode-map)
+ (setq major-mode 'cvs-edit-mode)
+ (setq mode-name "CVS Log")
+ (auto-fill-mode 1))
+
+;;----------
+(if cvs-edit-mode-map
+ nil
+ (setq cvs-edit-mode-map (make-sparse-keymap))
+ (define-prefix-command 'cvs-edit-mode-control-c-prefix)
+ (define-key cvs-edit-mode-map "\C-c" 'cvs-edit-mode-control-c-prefix)
+ (define-key cvs-edit-mode-map "\C-c\C-c" 'cvs-edit-done))
+
+;;----------
+(defun cvs-diffable (tins)
+ "Return a list of all tins on TINS that it makes sense to run
+``cvs diff'' on."
+
+ ;; +++ There is an unnecessary (nreverse) here. Get the list the
+ ;; other way around instead!
+ (let ((result nil))
+ (while tins
+ (let ((type (cvs-fileinfo->type
+ (tin-cookie cvs-cookie-handle (car tins)))))
+ (if (or (eq type 'MODIFIED)
+ (eq type 'UPDATED)
+ (eq type 'PATCHED)
+ (eq type 'MERGED)
+ (eq type 'CONFLICT)
+ (eq type 'REMOVED) ;+++Does this line make sense?
+ (eq type 'ADDED)) ;+++Does this line make sense?
+ (setq result (cons (car tins) result)))
+ (setq tins (cdr tins))))
+ (nreverse result)))
+
+;;----------
+(defun cvs-mode-diff-cvs (&optional ignore-marks)
+ "Diff the selected files against the head revisions in the repository.
+
+If the variable cvs-diff-ignore-marks is non-nil any marked files will not be
+considered to be selected. An optional prefix argument will invert the
+influence from cvs-diff-ignore-marks.
+
+The flags in the variable cvs-diff-flags will be passed to ``cvs diff''.
+
+The resulting diffs are placed in the cvs-fileinfo->cvs-diff-buffer."
+
+ (interactive "P")
+ (if (not (listp cvs-diff-flags))
+ (error "cvs-diff-flags should be set using cvs-set-diff-flags."))
+ (save-some-buffers)
+ (message "cvsdiffing...")
+ (let ((marked-file-list (cvs-diffable
+ (cvs-get-marked
+ (or (and ignore-marks (not cvs-diff-ignore-marks))
+ (and (not ignore-marks) cvs-diff-ignore-marks))))))
+ (while marked-file-list
+ (let ((fileinfo-to-diff (tin-cookie cvs-cookie-handle
+ (car marked-file-list)))
+ (local-def-directory (file-name-as-directory
+ (cvs-fileinfo->dir
+ (tin-cookie cvs-cookie-handle
+ (car marked-file-list))))))
+ (message "cvsdiffing %s..."
+ (cvs-fileinfo->file-name fileinfo-to-diff))
+
+ ;; FIXME: this seems messy to test and set buffer name at this point....
+ (if (not (cvs-fileinfo->cvs-diff-buffer fileinfo-to-diff))
+ (cvs-set-fileinfo->cvs-diff-buffer fileinfo-to-diff
+ (concat "*cvs-diff-"
+ (cvs-fileinfo->file-name
+ fileinfo-to-diff)
+ "-in-"
+ local-def-directory
+ "*")))
+ (display-buffer (get-buffer-create
+ (cvs-fileinfo->cvs-diff-buffer fileinfo-to-diff)))
+ (set-buffer (cvs-fileinfo->cvs-diff-buffer fileinfo-to-diff))
+ (setq buffer-read-only nil)
+ (setq default-directory local-def-directory)
+ (erase-buffer)
+ (insert (format "=== cd %s\n" default-directory))
+ (insert (format "=== cvs %s\n\n"
+ (mapconcat 'cvs-quote-multiword-string
+ (nconc (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "diff")
+ '("diff"))
+ (copy-sequence cvs-diff-flags)
+ (list (cvs-fileinfo->file-name
+ fileinfo-to-diff)))
+ " ")))
+ (if (apply 'call-process cvs-program nil t t
+ (nconc (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "diff")
+ '("diff"))
+ (copy-sequence cvs-diff-flags)
+ (list (cvs-fileinfo->file-name fileinfo-to-diff))))
+ (message "cvsdiffing %s... Done."
+ (cvs-fileinfo->file-name fileinfo-to-diff))
+ (message "cvsdiffing %s... No differences found."
+ (cvs-fileinfo->file-name fileinfo-to-diff)))
+ (goto-char (point-max))
+ (setq marked-file-list (cdr marked-file-list)))))
+ (message "cvsdiffing... Done."))
+
+;;----------
+(defun cvs-mode-diff-backup (&optional ignore-marks)
+ "Diff the files against the backup file.
+This command can be used on files that are marked with \"Merged\"
+or \"Conflict\" in the *cvs* buffer.
+
+If the variable cvs-diff-ignore-marks is non-nil any marked files will
+not be considered to be selected. An optional prefix argument will
+invert the influence from cvs-diff-ignore-marks.
+
+The flags in cvs-diff-flags will be passed to ``diff''.
+
+The resulting diffs are placed in the cvs-fileinfo->backup-diff-buffer."
+
+ (interactive "P")
+ (if (not (listp cvs-diff-flags))
+ (error "cvs-diff-flags should be set using cvs-set-diff-flags."))
+ (save-some-buffers)
+ (let ((marked-file-list (cvs-filter
+ (function cvs-backup-diffable)
+ (cvs-get-marked
+ (or
+ (and ignore-marks (not cvs-diff-ignore-marks))
+ (and (not ignore-marks) cvs-diff-ignore-marks))))))
+ (if (null marked-file-list)
+ (error "No ``Conflict'' or ``Merged'' file selected!"))
+ (message "backup diff...")
+ (while marked-file-list
+ (let ((fileinfo-to-diff (tin-cookie cvs-cookie-handle
+ (car marked-file-list)))
+ (local-def-directory (file-name-as-directory
+ (cvs-fileinfo->dir
+ (tin-cookie cvs-cookie-handle
+ (car marked-file-list)))))
+ (backup-temp-files (cvs-diff-backup-extractor
+ (tin-cookie cvs-cookie-handle
+ (car marked-file-list)))))
+ (message "backup diff %s..."
+ (cvs-fileinfo->file-name fileinfo-to-diff))
+
+ ;; FIXME: this seems messy to test and set buffer name at this point....
+ (if (not (cvs-fileinfo->backup-diff-buffer fileinfo-to-diff))
+ (cvs-set-fileinfo->backup-diff-buffer fileinfo-to-diff
+ (concat "*cvs-diff-"
+ (cvs-fileinfo->backup-file
+ fileinfo-to-diff)
+ "-to-"
+ (cvs-fileinfo->file-name
+ fileinfo-to-diff)
+ "-in"
+ local-def-directory
+ "*")))
+ (display-buffer (get-buffer-create
+ (cvs-fileinfo->backup-diff-buffer fileinfo-to-diff)))
+ (set-buffer (cvs-fileinfo->backup-diff-buffer fileinfo-to-diff))
+ (setq buffer-read-only nil)
+ (setq default-directory local-def-directory)
+ (erase-buffer)
+ (insert (format "=== cd %s\n" default-directory))
+ (insert (format "=== %s %s\n\n"
+ cvs-diff-program
+ (mapconcat 'cvs-quote-multiword-string
+ (nconc (copy-sequence cvs-diff-flags)
+ backup-temp-files)
+ " ")))
+ (apply 'call-process cvs-diff-program nil t t
+ (nconc (copy-sequence cvs-diff-flags) backup-temp-files))
+ (goto-char (point-max))
+ (message "backup diff %s... Done."
+ (cvs-fileinfo->file-name fileinfo-to-diff))
+ (setq marked-file-list (cdr marked-file-list)))))
+ (message "backup diff... Done."))
+
+;;----------
+(defun cvs-mode-diff-vendor (&optional ignore-marks)
+ "Diff the revisions merged into the current file. I.e. show what changes
+were merged in.
+
+This command can be used on files that are marked with \"Merged\"
+or \"Conflict\" in the *cvs* buffer.
+
+If the variable cvs-diff-ignore-marks is non-nil any marked files will
+not be considered to be selected. An optional prefix argument will
+invert the influence from cvs-diff-ignore-marks.
+
+The flags in cvs-diff-flags will be passed to ``diff''.
+
+The resulting diffs are placed in the cvs-fileinfo->vendor-diff-buffer."
+
+ (interactive "P")
+ (if (not (listp cvs-diff-flags))
+ (error "cvs-diff-flags should be set using cvs-set-diff-flags."))
+ (save-some-buffers)
+ (let ((marked-file-list (cvs-filter
+ (function cvs-vendor-diffable)
+ (cvs-get-marked
+ (or
+ (and ignore-marks (not cvs-diff-ignore-marks))
+ (and (not ignore-marks) cvs-diff-ignore-marks))))))
+ (if (null marked-file-list)
+ (error "No ``Conflict'' or ``Merged'' file selected!"))
+ (message "vendor diff...")
+ (while marked-file-list
+ (let ((fileinfo-to-diff (tin-cookie cvs-cookie-handle
+ (car marked-file-list)))
+ (local-def-directory (file-name-as-directory
+ (cvs-fileinfo->dir
+ (tin-cookie cvs-cookie-handle
+ (car marked-file-list)))))
+ (vendor-temp-files (cvs-diff-vendor-extractor
+ (tin-cookie cvs-cookie-handle
+ (car marked-file-list)))))
+ (message "vendor diff %s..."
+ (cvs-fileinfo->file-name fileinfo-to-diff))
+ (if (not (cvs-fileinfo->vendor-diff-buffer fileinfo-to-diff))
+ (cvs-set-fileinfo->vendor-diff-buffer fileinfo-to-diff
+ (concat "*cvs-diff-"
+ (cvs-fileinfo->file-name
+ fileinfo-to-diff)
+ "-of-"
+ (cvs-fileinfo->base-revision
+ fileinfo-to-diff)
+ "-to-"
+ (cvs-fileinfo->head-revision
+ fileinfo-to-diff)
+ "-in-"
+ local-def-directory
+ "*")))
+ (display-buffer (get-buffer-create
+ (cvs-fileinfo->vendor-diff-buffer fileinfo-to-diff)))
+ (set-buffer (cvs-fileinfo->vendor-diff-buffer fileinfo-to-diff))
+ (setq buffer-read-only nil)
+ (setq default-directory local-def-directory)
+ (erase-buffer)
+ (insert (format "=== cd %s\n" default-directory))
+ (insert (format "=== %s %s\n\n"
+ cvs-diff-program
+ (mapconcat 'cvs-quote-multiword-string
+ (nconc (copy-sequence cvs-diff-flags)
+ vendor-temp-files)
+ " ")))
+ (apply 'call-process cvs-diff-program nil t t
+ (nconc (copy-sequence cvs-diff-flags) vendor-temp-files))
+ (goto-char (point-max))
+ (message "vendor diff %s... Done."
+ (cvs-fileinfo->file-name fileinfo-to-diff))
+ (while vendor-temp-files
+ (cvs-kill-buffer-visiting (car vendor-temp-files))
+ (delete-file (car vendor-temp-files))
+ (setq vendor-temp-files (cdr vendor-temp-files)))
+ (setq marked-file-list (cdr marked-file-list)))))
+ (message "vendor diff... Done."))
+
+;;----------
+(defun cvs-backup-diffable (tin)
+ "Check if the TIN is backup-diffable.
+It must have a backup file to be diffable."
+
+ (file-readable-p
+ (cvs-fileinfo->backup-file (tin-cookie cvs-cookie-handle tin))))
+
+;;----------
+(defun cvs-vendor-diffable (tin)
+ "Check if the TIN is vendor-diffable.
+It must have head and base revision info to be diffable."
+
+ (and
+ (cvs-fileinfo->base-revision (tin-cookie cvs-cookie-handle tin))
+ (cvs-fileinfo->head-revision (tin-cookie cvs-cookie-handle tin))))
+
+;;----------
+(defun cvs-diff-backup-extractor (fileinfo)
+ "Return the filename and the name of the backup file as a list.
+Signal an error if there is no backup file."
+
+ (if (not (file-readable-p (cvs-fileinfo->backup-file fileinfo)))
+ (error "%s has no backup file."
+ (concat
+ (file-name-as-directory (cvs-fileinfo->dir fileinfo))
+ (cvs-fileinfo->file-name fileinfo))))
+ (list (cvs-fileinfo->backup-file fileinfo)
+ (cvs-fileinfo->file-name fileinfo)))
+
+;;----------
+(defun cvs-diff-vendor-extractor (fileinfo)
+ "Retrieve and return the filenames of the vendor branch revisions as a list.
+Signal an error if there is no info for the vendor revisions."
+
+ (list (cvs-retrieve-revision-to-tmpfile fileinfo
+ (cvs-fileinfo->base-revision
+ fileinfo))
+ (cvs-retrieve-revision-to-tmpfile fileinfo
+ (cvs-fileinfo->head-revision
+ fileinfo))))
+
+;;----------
+(defun cvs-mode-find-file-other-window (pos)
+ "Select a buffer containing the file in another window.
+Args: POS."
+
+ (interactive "d")
+ (let ((tin (tin-locate cvs-cookie-handle pos)))
+ (if tin
+ (let ((type (cvs-fileinfo->type (tin-cookie cvs-cookie-handle tin))))
+ (cond
+ ((or (eq type 'REMOVED)
+ (eq type 'CVS-REMOVED))
+ (error "Can't visit a removed file."))
+ ((eq type 'DIRCHANGE)
+ (let ((obuf (current-buffer))
+ (odir default-directory))
+ (setq default-directory
+ (file-name-as-directory
+ (cvs-fileinfo->dir
+ (tin-cookie cvs-cookie-handle tin))))
+ (dired-other-window default-directory)
+ (set-buffer obuf)
+ (setq default-directory odir)))
+ (t
+ (find-file-other-window (cvs-full-path tin)))))
+ (error "There is no file to find."))))
+
+;;----------
+(defun cvs-fileinfo->full-path (fileinfo)
+ "Return the full path for the file that is described in FILEINFO."
+
+ (concat
+ (file-name-as-directory
+ (cvs-fileinfo->dir fileinfo))
+ (cvs-fileinfo->file-name fileinfo)))
+
+;;----------
+(defun cvs-full-path (tin)
+ "Return the full path for the file that is described in TIN."
+
+ (cvs-fileinfo->full-path (tin-cookie cvs-cookie-handle tin)))
+
+;;----------
+(defun cvs-mode-find-file (pos)
+ "Select a buffer containing the file in another window.
+Args: POS."
+
+ (interactive "d")
+ (let* ((cvs-buf (current-buffer))
+ (tin (tin-locate cvs-cookie-handle pos)))
+ (if tin
+ (let* ((fileinfo (tin-cookie cvs-cookie-handle tin))
+ (type (cvs-fileinfo->type fileinfo)))
+ (cond
+ ((or (eq type 'REMOVED)
+ (eq type 'CVS-REMOVED))
+ (error "Can't visit a removed file."))
+ ((eq type 'DIRCHANGE)
+ (let ((odir default-directory))
+ (setq default-directory
+ (file-name-as-directory (cvs-fileinfo->dir fileinfo)))
+ (dired default-directory)
+ (set-buffer cvs-buf)
+ (setq default-directory odir)))
+ (t
+ (find-file (cvs-full-path tin)))))
+ (error "There is no file to find."))))
+
+;;----------
+(defun cvs-mode-mark-all-files ()
+ "Mark all files.
+Directories are not marked."
+
+ (interactive)
+ (cookie-map (function (lambda (cookie)
+ (cond
+ ((not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE))
+ (cvs-set-fileinfo->marked cookie t)
+ t))))
+ cvs-cookie-handle))
+
+;;----------
+(defun cvs-mode-unmark (pos)
+ "Unmark a fileinfo.
+Args: POS."
+
+ (interactive "d")
+ (let* ((tin (tin-locate cvs-cookie-handle pos))
+ (sel (tin-cookie cvs-cookie-handle tin)))
+ (cond
+ ((eq (cvs-fileinfo->type sel) 'DIRCHANGE)
+ (cookie-map
+ (function (lambda (f dir)
+ (cond
+ ((cvs-dir-member-p f dir)
+ (cvs-set-fileinfo->marked f nil)
+ t))))
+ cvs-cookie-handle
+ (cvs-fileinfo->dir sel)))
+ (t
+ (cvs-set-fileinfo->marked sel nil)
+ (tin-invalidate cvs-cookie-handle tin)
+ (tin-goto-next cvs-cookie-handle pos 1)))))
+
+;;----------
+(defun cvs-mode-unmark-all-files ()
+ "Unmark all files.
+Directories are also unmarked, but that doesn't matter, since
+they should always be unmarked."
+
+ (interactive)
+ (cookie-map (function (lambda (cookie)
+ (cvs-set-fileinfo->marked cookie nil)
+ t))
+ cvs-cookie-handle))
+
+;;----------
+(defun cvs-do-removal (tins)
+ "Remove files.
+Args: TINS.
+
+TINS is a list of tins that the user wants to delete. The files are deleted.
+If the type of the tin is 'UNKNOWN or 'UNKNOWN-DIR the tin is removed from the
+buffer. If it is anything else the file is added to a list that should be `cvs
+remove'd and the tin is changed to be of type 'REMOVED.
+
+Returns a list of tins files that should be `cvs remove'd."
+
+ (cvs-use-temp-buffer)
+ (mapcar 'cvs-insert-full-path tins)
+ (cond
+ ((and tins (yes-or-no-p (format "Delete %d files? " (length tins))))
+ (let (files-to-remove)
+ (while tins
+ (let* ((tin (car tins))
+ (fileinfo (tin-cookie cvs-cookie-handle tin))
+ (filepath (cvs-full-path tin))
+ (type (cvs-fileinfo->type fileinfo)))
+ (if (or (eq type 'REMOVED)
+ (eq type 'CVS-REMOVED))
+ nil
+ ;; if it doesn't exist, as a file or directory, ignore it
+ (cond ((file-directory-p filepath)
+ (call-process cvs-rmdir-program nil nil nil filepath))
+ ((file-exists-p filepath)
+ (delete-file filepath)))
+ (if (or (eq type 'UNKNOWN)
+ (eq type 'UNKNOWN-DIR)
+ (eq type 'MOVE-AWAY))
+ (tin-delete cvs-cookie-handle tin)
+ (setq files-to-remove (cons tin files-to-remove))
+ (cvs-set-fileinfo->type fileinfo 'REMOVED)
+ (cvs-set-fileinfo->handled fileinfo nil)
+ (tin-invalidate cvs-cookie-handle tin))))
+ (setq tins (cdr tins)))
+ files-to-remove))
+ (t nil)))
+
+;;----------
+(defun cvs-mode-remove-file ()
+ "Remove all marked files."
+
+ (interactive)
+ (let ((files-to-remove (cvs-do-removal (cvs-get-marked))))
+ (if (null files-to-remove)
+ nil
+ (cvs-use-temp-buffer)
+ (message "removing from repository...")
+ (if (cvs-execute-list files-to-remove cvs-program
+ (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "remove")
+ '("remove"))
+ "removing %s from repository...")
+ (error "CVS exited with non-zero exit status.")
+ (message "removing from repository... Done.")))))
+
+;;----------
+(defun cvs-mode-undo-local-changes ()
+ "Undo local changes to all marked files.
+The file is removed and `cvs update FILE' is run."
+
+ (interactive)
+ (let ((tins-to-undo (cvs-get-marked)))
+ (cvs-use-temp-buffer)
+ (mapcar 'cvs-insert-full-path tins-to-undo)
+ (cond
+ ((and tins-to-undo (yes-or-no-p (format "Undo changes to %d files? "
+ (length tins-to-undo))))
+ (let (files-to-update)
+ (while tins-to-undo
+ (let* ((tin (car tins-to-undo))
+ (fileinfo (tin-cookie cvs-cookie-handle tin))
+ (type (cvs-fileinfo->type fileinfo)))
+ (cond
+ ((or
+ (eq type 'UPDATED)
+ (eq type 'PATCHED)
+ (eq type 'MODIFIED)
+ (eq type 'MERGED)
+ (eq type 'CONFLICT)
+ (eq type 'CVS-REMOVED)
+ (eq type 'REM-CONFLICT)
+ (eq type 'MOVE-AWAY)
+ (eq type 'REMOVED))
+ (if (not (eq type 'REMOVED))
+ (delete-file (cvs-full-path tin)))
+ (setq files-to-update (cons tin files-to-update))
+ (cvs-set-fileinfo->type fileinfo 'UPDATED)
+ (cvs-set-fileinfo->handled fileinfo t)
+ (tin-invalidate cvs-cookie-handle tin))
+
+ ((eq type 'MOD-CONFLICT)
+ (error "Use cvs-mode-add instead on %s."
+ (cvs-fileinfo->file-name fileinfo)))
+
+ ((eq type 'REM-CONFLICT)
+ (error "Can't deal with a file you have removed and recreated."))
+
+ ((eq type 'DIRCHANGE)
+ (error "Undo on directories not supported (yet)."))
+
+ ((eq type 'ADDED)
+ (error "There is no old revision to get for %s"
+ (cvs-fileinfo->file-name fileinfo)))
+ (t (error "cvs-mode-undo-local-changes: can't handle an %s"
+ type)))
+
+ (setq tins-to-undo (cdr tins-to-undo))))
+ (cvs-use-temp-buffer)
+ (message "Re-getting files from repository...")
+ (if (cvs-execute-list files-to-update cvs-program
+ (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "update")
+ '("update"))
+ "Re-getting %s from repository...")
+ (error "CVS exited with non-zero exit status.")
+ (message "Re-getting files from repository... Done.")))))))
+
+;;----------
+(defun cvs-mode-acknowledge ()
+ "Remove all marked files from the buffer."
+
+ (interactive)
+ (mapcar (function (lambda (tin)
+ (tin-delete cvs-cookie-handle tin)))
+ (cvs-get-marked)))
+
+;;----------
+(defun cvs-mode-unmark-up (pos)
+ "Unmark the file on the previous line.
+Takes one argument POS, a buffer position."
+
+ (interactive "d")
+ (let ((tin (tin-goto-previous cvs-cookie-handle pos 1)))
+ (cond
+ (tin
+ (cvs-set-fileinfo->marked (tin-cookie cvs-cookie-handle tin)
+ nil)
+ (tin-invalidate cvs-cookie-handle tin)))))
+
+;;----------
+(defun cvs-mode-previous-line (arg)
+ "Go to the previous line.
+If a prefix argument is given, move by that many lines."
+
+ (interactive "p")
+ (tin-goto-previous cvs-cookie-handle (point) arg))
+
+;;----------
+(defun cvs-mode-next-line (arg)
+ "Go to the next line.
+If a prefix argument is given, move by that many lines."
+
+ (interactive "p")
+ (tin-goto-next cvs-cookie-handle (point) arg))
+
+;;----------
+(defun cvs-add-file-update-buffer (tin)
+ "Sub-function to cvs-mode-add. Internal use only. Update the display. Return
+non-nil if `cvs add' should be called on this file.
+Args: TIN.
+
+Returns 'DIR, 'ADD, 'ADD-DIR, or 'RESURRECT."
+
+ (let ((fileinfo (tin-cookie cvs-cookie-handle tin)))
+ (cond
+ ((eq (cvs-fileinfo->type fileinfo) 'UNKNOWN-DIR)
+ (cvs-set-fileinfo->full-log fileinfo "new directory added with cvs-mode-add")
+ 'ADD-DIR)
+ ((eq (cvs-fileinfo->type fileinfo) 'UNKNOWN)
+ (cvs-set-fileinfo->type fileinfo 'ADDED)
+ (cvs-set-fileinfo->full-log fileinfo "new file added with cvs-mode-add")
+ (tin-invalidate cvs-cookie-handle tin)
+ 'ADD)
+ ((eq (cvs-fileinfo->type fileinfo) 'REMOVED)
+ (cvs-set-fileinfo->type fileinfo 'UPDATED)
+ (cvs-set-fileinfo->full-log fileinfo "file resurrected with cvs-mode-add")
+ (cvs-set-fileinfo->handled fileinfo t)
+ (tin-invalidate cvs-cookie-handle tin)
+ 'RESURRECT))))
+
+;;----------
+(defun cvs-add-sub (cvs-buf candidates)
+ "Internal use only.
+Args: CVS-BUF CANDIDATES.
+
+CANDIDATES is a list of tins. Updates the CVS-BUF and returns a list of lists.
+The first list is unknown tins that shall be `cvs add -m msg'ed.
+The second list is unknown directory tins that shall be `cvs add -m msg'ed.
+The third list is removed files that shall be `cvs add'ed (resurrected)."
+
+ (let (add add-dir resurrect)
+ (while candidates
+ (let ((type (cvs-add-file-update-buffer (car candidates))))
+ (cond ((eq type 'ADD)
+ (setq add (cons (car candidates) add)))
+ ((eq type 'ADD-DIR)
+ (setq add-dir (cons (car candidates) add-dir)))
+ ((eq type 'RESURRECT)
+ (setq resurrect (cons (car candidates) resurrect)))))
+ (setq candidates (cdr candidates)))
+ (list add add-dir resurrect)))
+
+;;----------
+(defun cvs-mode-add ()
+ "Add marked files to the cvs repository."
+
+ (interactive)
+ (let* ((buf (current-buffer))
+ (marked (cvs-get-marked))
+ (result (cvs-add-sub buf marked))
+ (added (car result))
+ (newdirs (car (cdr result)))
+ (resurrect (car (cdr (cdr result))))
+ (msg (if (or added newdirs)
+ (read-from-minibuffer "Enter description: "))))
+
+ (if (or resurrect (or added newdirs))
+ (cvs-use-temp-buffer))
+
+ (cond (resurrect
+ (message "Resurrecting files from repository...")
+ (if (cvs-execute-list resurrect
+ cvs-program
+ (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "add")
+ '("add"))
+ "Resurrecting %s from repository...")
+ (error "CVS exited with non-zero exit status.")
+ (message "Resurrecting files from repository... Done."))))
+
+ (cond (added
+ (message "Adding new files to repository...")
+ (if (cvs-execute-list added
+ cvs-program
+ (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "add" "-m" msg)
+ (list "add" "-m" msg))
+ "Adding %s to repository...")
+ (error "CVS exited with non-zero exit status.")
+ (message "Adding new files to repository... Done."))))
+
+ (cond (newdirs
+ (message "Adding new directories to repository...")
+ (if (cvs-execute-list newdirs
+ cvs-program
+ (if cvs-cvsroot
+ (list "-d" cvs-cvsroot "add" "-m" msg)
+ (list "add" "-m" msg))
+ "Adding %s to repository...")
+ (error "CVS exited with non-zero exit status.")
+ (while newdirs
+ (let* ((tin (car newdirs))
+ (fileinfo (tin-cookie cvs-cookie-handle tin))
+ (newdir (cvs-fileinfo->file-name fileinfo)))
+ (cvs-set-fileinfo->dir fileinfo
+ (concat (cvs-fileinfo->dir fileinfo)
+ "/"
+ newdir))
+ (cvs-set-fileinfo->type fileinfo 'DIRCHANGE)
+ (cvs-set-fileinfo->file-name fileinfo ".")
+ (tin-invalidate cvs-cookie-handle tin)
+ (setq newdirs (cdr newdirs))))
+ ;; FIXME: this should really run cvs-update-no-prompt on the
+ ;; subdir and insert everthing in the current list.
+ (message "You must re-update to visit the new directories."))))))
+
+;;----------
+(defun cvs-mode-ignore ()
+ "Arrange so that CVS ignores the selected files and directories.
+This command ignores files/dirs that are flagged as `Unknown'."
+
+ (interactive)
+ (mapcar (function (lambda (tin)
+ (let* ((fileinfo (tin-cookie cvs-cookie-handle tin))
+ (type (cvs-fileinfo->type fileinfo)))
+ (cond ((or (eq type 'UNKNOWN)
+ (eq type 'UNKNOWN-DIR))
+ (cvs-append-to-ignore fileinfo)
+ (tin-delete cvs-cookie-handle tin))))))
+ (cvs-get-marked)))
+
+;;----------
+(defun cvs-append-to-ignore (fileinfo)
+ "Append the file in fileinfo to the .cvsignore file"
+
+ (save-window-excursion
+ (set-buffer (find-file-noselect (concat (file-name-as-directory
+ (cvs-fileinfo->dir fileinfo))
+ ".cvsignore")))
+ (goto-char (point-max))
+ (if (not (zerop (current-column)))
+ (insert "\n"))
+ (insert (cvs-fileinfo->file-name fileinfo) "\n")
+ (if cvs-sort-ignore-file
+ (sort-lines nil (point-min) (point-max)))
+ (save-buffer)))
+
+;;----------
+(defun cvs-mode-status ()
+ "Show cvs status for all marked files."
+
+ (interactive)
+ (save-some-buffers)
+ (if (not (listp cvs-status-flags))
+ (error "cvs-status-flags should be set using cvs-set-status-flags."))
+ (let ((marked (cvs-get-marked nil t)))
+ (cvs-use-temp-buffer)
+ (message "Running cvs status ...")
+ (if (cvs-execute-list marked
+ cvs-program
+ (append (if cvs-cvsroot (list "-d" cvs-cvsroot))
+ (list "-Q" "status")
+ cvs-status-flags)
+ "Running cvs -Q status %s...")
+ (error "CVS exited with non-zero exit status.")
+ (message "Running cvs -Q status ... Done."))))
+
+;;----------
+(defun cvs-mode-log ()
+ "Display the cvs log of all selected files."
+
+ (interactive)
+ (if (not (listp cvs-log-flags))
+ (error "cvs-log-flags should be set using cvs-set-log-flags."))
+ (let ((marked (cvs-get-marked nil t)))
+ (cvs-use-temp-buffer)
+ (message "Running cvs log ...")
+ (if (cvs-execute-list marked
+ cvs-program
+ (append (if cvs-cvsroot (list "-d" cvs-cvsroot))
+ (list "log")
+ cvs-log-flags)
+ "Running cvs log %s...")
+ (error "CVS exited with non-zero exit status.")
+ (message "Running cvs log ... Done."))))
+
+;;----------
+(defun cvs-mode-tag ()
+ "Run 'cvs tag' on all selected files."
+
+ (interactive)
+ (if (not (listp cvs-tag-flags))
+ (error "cvs-tag-flags should be set using cvs-set-tag-flags."))
+ (let ((marked (cvs-get-marked nil t))
+ (tag-args (cvs-make-list (read-string "Tag name (and flags): "))))
+ (cvs-use-temp-buffer)
+ (message "Running cvs tag ...")
+ (if (cvs-execute-list marked
+ cvs-program
+ (append (if cvs-cvsroot (list "-d" cvs-cvsroot))
+ (list "tag")
+ cvs-tag-flags
+ tag-args)
+ "Running cvs tag %s...")
+ (error "CVS exited with non-zero exit status.")
+ (message "Running cvs tag ... Done."))))
+
+;;----------
+(defun cvs-mode-rtag ()
+ "Run 'cvs rtag' on all selected files."
+
+ (interactive)
+ (if (not (listp cvs-rtag-flags))
+ (error "cvs-rtag-flags should be set using cvs-set-rtag-flags."))
+ (let ((marked (cvs-get-marked nil t))
+ ;; FIXME: should give selection from the modules file
+ (module-name (read-string "Module name: "))
+ ;; FIXME: should also ask for an existing tag *or* date
+ (rtag-args (cvs-make-list (read-string "Tag name (and flags): "))))
+ (cvs-use-temp-buffer)
+ (message "Running cvs rtag ...")
+ (if (cvs-execute-list marked
+ cvs-program
+ (append (if cvs-cvsroot (list "-d" cvs-cvsroot))
+ (list "rtag")
+ cvs-rtag-flags
+ rtag-args
+ (list module-name))
+ "Running cvs rtag %s...")
+ (error "CVS rtag exited with non-zero exit status.")
+ (message "Running cvs rtag ... Done."))))
+
+;;----------
+(defun cvs-mode-byte-compile-files ()
+ "Run byte-compile-file on all selected files that end in '.el'."
+
+ (interactive)
+ (let ((marked (cvs-get-marked)))
+ (while marked
+ (let ((filename (cvs-full-path (car marked))))
+ (if (string-match "\\.el$" filename)
+ (byte-compile-file filename)))
+ (setq marked (cdr marked)))))
+
+;;----------
+(defun cvs-insert-full-path (tin)
+ "Insert full path to the file described in TIN in the current buffer."
+
+ (insert (format "%s\n" (cvs-full-path tin))))
+
+;;----------
+(defun cvs-mode-add-change-log-entry-other-window (pos)
+ "Add a ChangeLog entry in the ChangeLog of the current directory.
+Args: POS."
+
+ (interactive "d")
+ (let* ((cvs-buf (current-buffer))
+ (odir default-directory)
+ (obfname buffer-file-name)
+ (tin (tin-locate cvs-cookie-handle pos))
+ (fileinfo (tin-cookie cvs-cookie-handle tin))
+ (fname (cvs-fileinfo->file-name fileinfo))
+ (dname (file-name-as-directory (cvs-fileinfo->dir fileinfo))))
+ (setq change-log-default-name nil) ; this rarely correct in 19.28
+ (setq buffer-file-name (cond (fname
+ fname)
+ (t
+ nil)))
+ (setq default-directory (cond (dname
+ dname)
+ (t
+ odir)))
+ (add-change-log-entry-other-window)
+ (set-buffer cvs-buf)
+ (setq default-directory odir)
+ (setq buffer-file-name obfname)))
+
+;;----------
+(defun print-cvs-tin (foo)
+ "Debug utility."
+
+ (let ((cookie (tin-cookie cvs-cookie-handle foo))
+ (stream (get-buffer-create "pcl-cvs-debug")))
+ (princ "==============\n" stream)
+ (princ (cvs-fileinfo->file-name cookie) stream)
+ (princ "\n" stream)
+ (princ (cvs-fileinfo->dir cookie) stream)
+ (princ "\n" stream)
+ (princ (cvs-fileinfo->full-log cookie) stream)
+ (princ "\n" stream)
+ (princ (cvs-fileinfo->marked cookie) stream)
+ (princ "\n" stream)))
+
+;;----------
+;; NOTE: the variable cvs-emerge-tmp-head-file will be "free" when compiling
+(defun cvs-mode-emerge (pos)
+ "Emerge appropriate revisions of the selected file.
+Args: POS."
+
+ (interactive "d")
+ (let* ((cvs-buf (current-buffer))
+ (tin (tin-locate cvs-cookie-handle pos)))
+ (if (boundp 'cvs-emerge-tmp-head-file)
+ (error "There can only be one emerge session active at a time."))
+ (if tin
+ (let* ((fileinfo (tin-cookie cvs-cookie-handle tin))
+ (type (cvs-fileinfo->type fileinfo)))
+ (cond
+ ((eq type 'MODIFIED) ; merge repository head rev. with working file
+ (require 'emerge)
+ (setq cvs-emerge-tmp-head-file ; trick to prevent multiple runs
+ (cvs-retrieve-revision-to-tmpfile fileinfo))
+ (unwind-protect
+ (if (not (emerge-files
+ t ; arg
+ (cvs-fileinfo->full-path fileinfo) ; file-A
+ ;; this is an un-avoidable compiler reference to a free variable
+ cvs-emerge-tmp-head-file ; file-B
+ (cvs-fileinfo->full-path fileinfo) ; file-out
+ nil ; start-hooks
+ '(lambda () ; quit-hooks
+ (delete-file cvs-emerge-tmp-head-file)
+ (makunbound 'cvs-emerge-tmp-head-file))))
+ (error "Emerge session failed"))))
+
+ ;; re-do the same merge rcsmerge supposedly just did....
+ ((or (eq type 'MERGED)
+ (eq type 'CONFLICT)) ; merge backup-working=A, head=B, base=ancestor
+ (require 'emerge)
+ (setq cvs-emerge-tmp-head-file ; trick to prevent multiple runs
+ (cvs-retrieve-revision-to-tmpfile fileinfo
+ (cvs-fileinfo->head-revision
+ fileinfo)))
+ (let ((cvs-emerge-tmp-backup-working-file
+ (cvs-fileinfo->backup-file fileinfo))
+ (cvs-emerge-tmp-ancestor-file
+ (cvs-retrieve-revision-to-tmpfile fileinfo
+ (cvs-fileinfo->base-revision
+ fileinfo))))
+ (unwind-protect
+ (if (not (emerge-files-with-ancestor
+ t ; arg
+ cvs-emerge-tmp-backup-working-file ; file-A
+ ;; this is an un-avoidable compiler reference to a free variable
+ cvs-emerge-tmp-head-file ; file-B
+ cvs-emerge-tmp-ancestor-file ; file-ancestor
+ (cvs-fileinfo->full-path fileinfo) ; file-out
+ nil ; start-hooks
+ '(lambda () ; quit-hooks
+ (delete-file cvs-emerge-tmp-backup-file)
+ (delete-file cvs-emerge-tmp-ancestor-file)
+ (delete-file cvs-emerge-tmp-head-file)
+ (makunbound 'cvs-emerge-tmp-head-file))))
+ (error "Emerge session failed")))))
+ (t
+ (error "Can only e-merge \"Modified\", \"Merged\" or \"Conflict\" files"))))
+ (error "There is no file to e-merge."))))
+
+;;----------
+;; NOTE: the variable ediff-version may be "free" when compiling
+(defun cvs-mode-ediff (pos)
+ "Ediff appropriate revisions of the selected file.
+Args: POS."
+
+ (interactive "d")
+ (if (boundp 'cvs-ediff-tmp-head-file)
+ (error "There can only be one ediff session active at a time."))
+ (require 'ediff)
+ (if (and (boundp 'ediff-version)
+ (>= (string-to-number ediff-version) 2.0)) ; FIXME real number?
+ (run-ediff-from-cvs-buffer pos)
+ (cvs-old-ediff-interface pos)))
+
+(defun cvs-old-ediff-interface (pos)
+ "Emerge like interface for older ediffs.
+Args: POS"
+
+ (let* ((cvs-buf (current-buffer))
+ (tin (tin-locate cvs-cookie-handle pos)))
+ (if tin
+ (let* ((fileinfo (tin-cookie cvs-cookie-handle tin))
+ (type (cvs-fileinfo->type fileinfo)))
+ (cond
+ ((eq type 'MODIFIED) ; diff repository head rev. with working file
+ ;; should this be inside the unwind-protect, and should the
+ ;; makeunbound be an unwindform?
+ (setq cvs-ediff-tmp-head-file ; trick to prevent multiple runs
+ (cvs-retrieve-revision-to-tmpfile fileinfo))
+ (unwind-protect
+ (if (not (ediff-files ; check correct ordering of args
+ (cvs-fileinfo->full-path fileinfo) ; file-A
+ ;; this is an un-avoidable compiler reference to a free variable
+ cvs-ediff-tmp-head-file ; file-B
+ '(lambda () ; startup-hooks
+ (make-local-hook 'ediff-cleanup-hooks)
+ (add-hook 'ediff-cleanup-hooks
+ '(lambda ()
+ (ediff-janitor)
+ (delete-file cvs-ediff-tmp-head-file)
+ (makunbound 'cvs-ediff-tmp-head-file))
+ nil t))))
+ (error "Ediff session failed"))))
+
+ ;; look at the merge rcsmerge supposedly just did....
+ ((or (eq type 'MERGED)
+ (eq type 'CONFLICT)) ; diff backup-working=A, head=B, base=ancestor
+ (if (not (boundp 'ediff-version))
+ (error "ediff version way too old for 3-way diff"))
+ (if (<= (string-to-number ediff-version) 1.9) ; FIXME real number?
+ (error "ediff version %s too old for 3-way diff" ediff-version))
+ (setq cvs-ediff-tmp-head-file ; trick to prevent multiple runs
+ (cvs-retrieve-revision-to-tmpfile fileinfo
+ (cvs-fileinfo->head-revision
+ fileinfo)))
+ (let ((cvs-ediff-tmp-backup-working-file
+ (cvs-fileinfo->backup-file fileinfo))
+ (cvs-ediff-tmp-ancestor-file
+ (cvs-retrieve-revision-to-tmpfile fileinfo
+ (cvs-fileinfo->base-revision
+ fileinfo))))
+ (unwind-protect
+ (if (not (ediff-files3 ; check correct ordering of args
+ cvs-ediff-tmp-backup-working-file ; file-A
+ ;; this is an un-avoidable compiler reference to a free variable
+ cvs-ediff-tmp-head-file ; file-B
+ cvs-ediff-tmp-ancestor-file ; file-ancestor
+ '(lambda () ; start-hooks
+ (make-local-hook 'ediff-cleanup-hooks)
+ (add-hook 'ediff-cleanup-hooks
+ '(lambda ()
+ (ediff-janitor)
+ (delete-file cvs-ediff-tmp-backup-file)
+ (delete-file cvs-ediff-tmp-ancestor-file)
+ (delete-file cvs-ediff-tmp-head-file)
+ (makunbound 'cvs-ediff-tmp-head-file))
+ nil t))))
+ (error "Ediff session failed")))))
+
+ ((not (or (eq type 'UNKNOWN)
+ (eq type 'UNKNOWN-DIR))) ; i.e. UPDATED or PATCHED ????
+ ;; this should really diff the current working file with the previous
+ ;; rev. on the current branch (i.e. not the head, since that's what
+ ;; the current file should be)
+ (setq cvs-ediff-tmp-head-file ; trick to prevent multiple runs
+ (cvs-retrieve-revision-to-tmpfile fileinfo
+ (read-string "Rev #/tag to diff against: "
+ (cvs-fileinfo->head-revision
+ fileinfo))))
+ (unwind-protect
+ (if (not (ediff-files ; check correct ordering of args
+ (cvs-fileinfo->full-path fileinfo) ; file-A
+ ;; this is an un-avoidable compiler reference to a free variable
+ cvs-ediff-tmp-head-file ; file-B
+ '(lambda () ; startup-hooks
+ (make-local-hook 'ediff-cleanup-hooks)
+ (add-hook 'ediff-cleanup-hooks
+ '(lambda ()
+ (ediff-janitor)
+ (delete-file cvs-ediff-tmp-head-file)
+ (makunbound 'cvs-ediff-tmp-head-file))
+ nil t))))
+ (error "Ediff session failed"))))
+ (t
+ (error "Can not ediff \"Unknown\" files"))))
+ (error "There is no file to ediff."))))
+
+;;----------
+(defun cvs-retrieve-revision-to-tmpfile (fileinfo &optional revision)
+ "Retrieve the latest revision of the file in FILEINFO to a temporary file.
+If second optional argument REVISION is given, retrieve that revision instead."
+
+ (let
+ ((temp-name (make-temp-name
+ (concat (file-name-as-directory
+ (or (getenv "TMPDIR") "/tmp"))
+ "pcl-cvs." revision))))
+ (cvs-kill-buffer-visiting temp-name)
+ (if (and revision
+ (stringp revision)
+ (not (string= revision "")))
+ (message "Retrieving revision %s..." revision)
+ (message "Retrieving latest revision..."))
+ (let ((res (call-process cvs-shell nil nil nil "-c"
+ (concat cvs-program " update -p "
+ (if (and revision
+ (stringp revision)
+ (not (string= revision "")))
+ (concat "-r " revision " ")
+ "")
+ (cvs-fileinfo->full-path fileinfo)
+ " > " temp-name))))
+ (if (and res (not (and (integerp res) (zerop res))))
+ (error "Something went wrong retrieving revision %s: %s"
+ revision res))
+
+ (if revision
+ (message "Retrieving revision %s... Done." revision)
+ (message "Retrieving latest revision... Done."))
+ (save-excursion
+ (set-buffer (find-file-noselect temp-name))
+ (rename-buffer (concat " " (file-name-nondirectory temp-name)) t))
+ temp-name)))
+
+;;----------
+(defun cvs-kill-buffer-visiting (filename)
+ "If there is any buffer visiting FILENAME, kill it (without confirmation)."
+
+ (let ((l (buffer-list)))
+ (while l
+ (if (string= (buffer-file-name (car l)) filename)
+ (kill-buffer (car l)))
+ (setq l (cdr l)))))
+
+;;----------
+(defun cvs-change-cvsroot ()
+ "Ask for a new cvsroot."
+
+ (interactive)
+ (cvs-set-cvsroot (read-file-name "New CVSROOT: " cvs-cvsroot)))
+
+;;----------
+(defun cvs-set-cvsroot (newroot)
+ "Change the cvsroot."
+
+ (if (or (file-directory-p (expand-file-name "CVSROOT" newroot))
+ (y-or-n-p (concat "Warning: no CVSROOT found inside repository."
+ " Change cvs-cvsroot anyhow?")))
+ (setq cvs-cvsroot newroot)))
+
+;;----------
+(defun cvs-set-diff-flags ()
+ "Ask for new setting of cvs-diff-flags."
+
+ (interactive)
+ (let ((old-value (mapconcat 'identity
+ (copy-sequence cvs-diff-flags) " ")))
+ (setq cvs-diff-flags
+ (cvs-make-list (read-string "Diff flags: " old-value)))))
+
+;;----------
+(defun cvs-set-update-optional-flags ()
+ "Ask for new setting of cvs-update-optional-flags."
+
+ (interactive)
+ (let ((old-value (mapconcat 'identity
+ (copy-sequence cvs-update-optional-flags) " ")))
+ (setq cvs-update-optional-flags
+ (cvs-make-list (read-string "Update optional flags: " old-value)))))
+
+;;----------
+(defun cvs-set-status-flags ()
+ "Ask for new setting of cvs-status-flags."
+
+ (interactive)
+ (let ((old-value (mapconcat 'identity
+ (copy-sequence cvs-status-flags) " ")))
+ (setq cvs-status-flags
+ (cvs-make-list (read-string "Status flags: " old-value)))))
+
+;;----------
+(defun cvs-set-log-flags ()
+ "Ask for new setting of cvs-log-flags."
+
+ (interactive)
+ (let ((old-value (mapconcat 'identity
+ (copy-sequence cvs-log-flags) " ")))
+ (setq cvs-log-flags
+ (cvs-make-list (read-string "Log flags: " old-value)))))
+
+;;----------
+(defun cvs-set-tag-flags ()
+ "Ask for new setting of cvs-tag-flags."
+
+ (interactive)
+ (let ((old-value (mapconcat 'identity
+ (copy-sequence cvs-tag-flags) " ")))
+ (setq cvs-tag-flags
+ (cvs-make-list (read-string "Tag flags: " old-value)))))
+
+;;----------
+(defun cvs-set-rtag-flags ()
+ "Ask for new setting of cvs-rtag-flags."
+
+ (interactive)
+ (let ((old-value (mapconcat 'identity
+ (copy-sequence cvs-rtag-flags) " ")))
+ (setq cvs-rtag-flags
+ (cvs-make-list (read-string "Rtag flags: " old-value)))))
+
+;;----------
+(if (string-match "Lucid" emacs-version)
+ (progn
+ (autoload 'pcl-cvs-fontify "pcl-cvs-lucid")
+ (add-hook 'cvs-mode-hook 'pcl-cvs-fontify)))
+
+(defun cvs-changelog-name (directory)
+ "Return the name of the ChangeLog file that handles DIRECTORY.
+This is in DIRECTORY or one of its parents.
+Signal an error if we can't find an appropriate ChangeLog file."
+ (let ((dir (file-name-as-directory directory))
+ file)
+ (while (and dir
+ (not (file-exists-p
+ (setq file (expand-file-name "ChangeLog" dir)))))
+ (let ((last dir))
+ (setq dir (file-name-directory (directory-file-name dir)))
+ (if (equal last dir)
+ (setq dir nil))))
+ (or dir
+ (error "Can't find ChangeLog for %s" directory))
+ file))
+
+(defun cvs-narrow-changelog ()
+ "Narrow to the top page of the current buffer, a ChangeLog file.
+Actually, the narrowed region doesn't include the date line.
+A \"page\" in a ChangeLog file is the area between two dates."
+ (or (eq major-mode 'change-log-mode)
+ (error "cvs-narrow-changelog: current buffer isn't a ChangeLog"))
+
+ (goto-char (point-min))
+
+ ;; Skip date line and subsequent blank lines.
+ (forward-line 1)
+ (if (looking-at "[ \t\n]*\n")
+ (goto-char (match-end 0)))
+
+ (let ((start (point)))
+ (forward-page 1)
+ (narrow-to-region start (point))
+ (goto-char (point-min))))
+
+(defun cvs-changelog-paragraph ()
+ "Return the bounds of the ChangeLog paragraph containing point.
+If we are between paragraphs, return the previous paragraph."
+ (save-excursion
+ (beginning-of-line)
+ (if (looking-at "^[ \t]*$")
+ (skip-chars-backward " \t\n" (point-min)))
+ (list (progn
+ (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit)
+ (goto-char (match-end 0)))
+ (point))
+ (if (re-search-forward "^[ \t\n]*$" nil t)
+ (match-beginning 0)
+ (point)))))
+
+(defun cvs-changelog-subparagraph ()
+ "Return the bounds of the ChangeLog subparagraph containing point.
+A subparagraph is a block of non-blank lines beginning with an asterisk.
+If we are between sub-paragraphs, return the previous subparagraph."
+ (save-excursion
+ (end-of-line)
+ (if (search-backward "*" nil t)
+ (list (progn (beginning-of-line) (point))
+ (progn
+ (forward-line 1)
+ (if (re-search-forward "^[ \t]*[\n*]" nil t)
+ (match-beginning 0)
+ (point-max))))
+ (list (point) (point)))))
+
+(defun cvs-changelog-entry ()
+ "Return the bounds of the ChangeLog entry containing point.
+The variable `cvs-changelog-full-paragraphs' decides whether an
+\"entry\" is a paragraph or a subparagraph; see its documentation string
+for more details."
+ (if cvs-changelog-full-paragraphs
+ (cvs-changelog-paragraph)
+ (cvs-changelog-subparagraph)))
+
+;; NOTE: the variable user-full-name may be "free" when compiling
+(defun cvs-changelog-ours-p ()
+ "See if ChangeLog entry at point is for the current user, today.
+Return non-nil iff it is."
+ ;; Code adapted from add-change-log-entry.
+ (looking-at (concat (regexp-quote (substring (current-time-string)
+ 0 10))
+ ".* "
+ (regexp-quote (substring (current-time-string) -4))
+ "[ \t]+"
+ (regexp-quote (if (and (boundp 'add-log-full-name)
+ add-log-full-name)
+ add-log-full-name
+ user-full-name))
+ " <"
+ (regexp-quote (if (and (boundp 'add-log-mailing-address)
+ add-log-mailing-address)
+ add-log-mailing-address
+ user-mail-address)))))
+
+(defun cvs-relative-path (base child)
+ "Return a directory path relative to BASE for CHILD.
+If CHILD doesn't seem to be in a subdirectory of BASE, just return
+the full path to CHILD."
+ (let ((base (file-name-as-directory (expand-file-name base)))
+ (child (expand-file-name child)))
+ (or (string= base (substring child 0 (length base)))
+ (error "cvs-relative-path: %s isn't in %s" child base))
+ (substring child (length base))))
+
+(defun cvs-changelog-entries (file)
+ "Return the ChangeLog entries for FILE, and the ChangeLog they came from.
+The return value looks like this:
+ (LOGBUFFER (ENTRYSTART . ENTRYEND) ...)
+where LOGBUFFER is the name of the ChangeLog buffer, and each
+\(ENTRYSTART . ENTRYEND\) pair is a buffer region."
+ (save-excursion
+ (set-buffer (find-file-noselect
+ (cvs-changelog-name
+ (file-name-directory
+ (expand-file-name file)))))
+ (or (eq major-mode 'change-log-mode)
+ (change-log-mode))
+ (goto-char (point-min))
+ (if (looking-at "[ \t\n]*\n")
+ (goto-char (match-end 0)))
+ (if (not (cvs-changelog-ours-p))
+ (list (current-buffer))
+ (save-restriction
+ (cvs-narrow-changelog)
+ (goto-char (point-min))
+
+ ;; Search for the name of FILE relative to the ChangeLog. If that
+ ;; doesn't occur anywhere, they're not using full relative
+ ;; filenames in the ChangeLog, so just look for FILE; we'll accept
+ ;; some false positives.
+ (let ((pattern (cvs-relative-path
+ (file-name-directory buffer-file-name) file)))
+ (if (or (string= pattern "")
+ (not (save-excursion
+ (search-forward pattern nil t))))
+ (setq pattern file))
+
+ (let (texts)
+ (while (search-forward pattern nil t)
+ (let ((entry (cvs-changelog-entry)))
+ (setq texts (cons entry texts))
+ (goto-char (elt entry 1))))
+
+ (cons (current-buffer) texts)))))))
+
+(defun cvs-changelog-insert-entries (buffer regions)
+ "Insert those regions in BUFFER specified in REGIONS.
+Sort REGIONS front-to-back first."
+ (let ((regions (sort regions 'car-less-than-car))
+ (last))
+ (while regions
+ (if (and last (< last (car (car regions))))
+ (newline))
+ (setq last (elt (car regions) 1))
+ (apply 'insert-buffer-substring buffer (car regions))
+ (setq regions (cdr regions)))))
+
+(defun cvs-union (set1 set2)
+ "Return the union of SET1 and SET2, according to `equal'."
+ (while set2
+ (or (member (car set2) set1)
+ (setq set1 (cons (car set2) set1)))
+ (setq set2 (cdr set2)))
+ set1)
+
+(defun cvs-insert-changelog-entries (files)
+ "Given a list of files FILES, insert the ChangeLog entries for them."
+ (let ((buffer-entries nil))
+
+ ;; Add each buffer to buffer-entries, and associate it with the list
+ ;; of entries we want from that file.
+ (while files
+ (let* ((entries (cvs-changelog-entries (car files)))
+ (pair (assq (car entries) buffer-entries)))
+ (if pair
+ (setcdr pair (cvs-union (cdr pair) (cdr entries)))
+ (setq buffer-entries (cons entries buffer-entries))))
+ (setq files (cdr files)))
+
+ ;; Now map over each buffer in buffer-entries, sort the entries for
+ ;; each buffer, and extract them as strings.
+ (while buffer-entries
+ (cvs-changelog-insert-entries (car (car buffer-entries))
+ (cdr (car buffer-entries)))
+ (if (and (cdr buffer-entries) (cdr (car buffer-entries)))
+ (newline))
+ (setq buffer-entries (cdr buffer-entries)))))
+
+(defun cvs-edit-delete-common-indentation ()
+ "Unindent the current buffer rigidly until at least one line is flush left."
+ (save-excursion
+ (let ((common 100000))
+ (goto-char (point-min))
+ (while (< (point) (point-max))
+ (if (not (looking-at "^[ \t]*$"))
+ (setq common (min common (current-indentation))))
+ (forward-line 1))
+ (indent-rigidly (point-min) (point-max) (- common)))))
+
+(defun cvs-mode-changelog-commit ()
+ "Check in all marked files, or the current file.
+Ask the user for a log message in a buffer.
+
+This is just like `\\[cvs-mode-commit]', except that it tries to provide
+appropriate default log messages by looking at the ChangeLog. The
+idea is to write your ChangeLog entries first, and then use this
+command to commit your changes.
+
+To select default log text, we:
+- find the ChangeLog entries for the files to be checked in,
+- verify that the top entry in the ChangeLog is on the current date
+ and by the current user; if not, we don't provide any default text,
+- search the ChangeLog entry for paragraphs containing the names of
+ the files we're checking in, and finally
+- use those paragraphs as the log text."
+
+ (interactive)
+
+ (let* ((cvs-buf (current-buffer))
+ (marked (cvs-filter (function cvs-committable)
+ (cvs-get-marked))))
+ (if (null marked)
+ (error "Nothing to commit!")
+ (pop-to-buffer (get-buffer-create cvs-commit-prompt-buffer))
+ (goto-char (point-min))
+
+ (erase-buffer)
+ (cvs-insert-changelog-entries
+ (mapcar (lambda (tin)
+ (let ((cookie (tin-cookie cvs-cookie-handle tin)))
+ (expand-file-name
+ (cvs-fileinfo->file-name cookie)
+ (cvs-fileinfo->dir cookie))))
+ marked))
+ (cvs-edit-delete-common-indentation)
+
+ (cvs-edit-mode)
+ (make-local-variable 'cvs-commit-list)
+ (setq cvs-commit-list marked)
+ (message "Press C-c C-c when you are done editing."))))
+
+(provide 'pcl-cvs)
+
+;;;; end of file pcl-cvs.el
diff --git a/contrib/cvs/tools/pcl-cvs/pcl-cvs.texinfo b/contrib/cvs/tools/pcl-cvs/pcl-cvs.texinfo
new file mode 100644
index 0000000..8bd3619
--- /dev/null
+++ b/contrib/cvs/tools/pcl-cvs/pcl-cvs.texinfo
@@ -0,0 +1,1565 @@
+\input texinfo @c -*-texinfo-*-
+
+@comment OrigId: pcl-cvs.texinfo,v 1.45 1993/05/31 22:38:15 ceder Exp
+@comment @@(#)cvs/contrib/pcl-cvs:$Name: $:$Id: pcl-cvs.texinfo,v 1.1 1996/04/14 15:18:04 kfogel Exp $
+
+@comment Documentation for the GNU Emacs CVS mode.
+@comment Copyright (C) 1992 Per Cederqvist
+
+@comment This file is part of the pcl-cvs distribution.
+
+@comment Pcl-cvs is free software; you can redistribute it and/or modify
+@comment it under the terms of the GNU General Public License as published by
+@comment the Free Software Foundation; either version 1, or (at your option)
+@comment any later version.
+
+@comment Pcl-cvs is distributed in the hope that it will be useful,
+@comment but WITHOUT ANY WARRANTY; without even the implied warranty of
+@comment MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+@comment GNU General Public License for more details.
+
+@comment You should have received a copy of the GNU General Public License
+@comment along with pcl-cvs; see the file COPYING. If not, write to
+@comment the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+@setfilename pcl-cvs.info
+@settitle Pcl-cvs - The Emacs Front-End to CVS
+@setchapternewpage on
+
+@ifinfo
+Copyright @copyright{} 1992 Per Cederqvist
+
+Permission is granted to make and distribute verbatim copies of
+this manual provided the copyright notice and this permission notice
+are preserved on all copies.
+
+@ignore
+Permission is granted to process this file through Tex and print the
+results, provided the printed document carries copying permission
+notice identical to this one except for the removal of this paragraph
+(this paragraph not being relevant to the printed manual).
+
+@end ignore
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided also that the
+section entitled ``GNU General Public License'' is included exactly as
+in the original, and provided that the entire resulting derived work is
+distributed under the terms of a permission notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that the section entitled ``GNU General Public License'' and
+this permission notice may be included in translations approved by the
+Free Software Foundation instead of in the original English.
+@end ifinfo
+
+@synindex vr fn
+@comment The titlepage section does not appear in the Info file.
+@titlepage
+@sp 4
+@comment The title is printed in a large font.
+@center @titlefont{User's Guide}
+@sp
+@center @titlefont{to}
+@sp
+@center @titlefont{pcl-cvs - the Emacs Front-End to CVS}
+@sp 2
+@center release 1.05-CVS-$Name: $
+@comment -release-
+@sp 3
+@center Per Cederqvist
+@sp 3
+@center last updated 20 Nov 1995
+@comment -date-
+
+@comment The following two commands start the copyright page
+@comment for the printed manual. This will not appear in the Info file.
+@page
+@vskip 0pt plus 1filll
+Copyright @copyright{} 1992 Per Cederqvist
+
+Permission is granted to make and distribute verbatim copies of
+this manual provided the copyright notice and this permission notice
+are preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided also that the
+section entitled ``GNU General Public License'' is included exactly as
+in the original, and provided that the entire resulting derived work is
+distributed under the terms of a permission notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that the section entitled ``GNU General Public License'' and
+this permission notice may be included in translations approved by the
+Free Software Foundation instead of in the original English.
+@end titlepage
+
+@comment ================================================================
+@comment The real text starts here
+@comment ================================================================
+
+@node Top, Installation, (dir), (dir)
+@comment node-name, next, previous, up
+
+
+@ifinfo
+This info manual describes pcl-cvs which is a GNU Emacs front-end to
+CVS. It works with CVS versions 1.5 through 1.7 and newer, and possibly
+CVS-1.3 and CVS-1.4A2. This manual is updated to release
+1.05-CVS-$Name: $ of pcl-cvs.
+@end ifinfo
+@comment -release-
+
+@menu
+* Installation:: How to install pcl-cvs on your system.
+* About pcl-cvs:: Authors and ftp sites.
+
+* Getting started:: An introduction with a walk-through example.
+* Buffer contents:: An explanation of the buffer contents.
+* Commands:: All commands, grouped by type.
+
+* Customization:: How you can tailor pcl-cvs to suit your needs.
+* Future enhancements:: Future enhancements of pcl-cvs.
+* Bugs:: Bugs (known and unknown).
+* COPYING:: GNU General Public License
+* Function and Variable Index:: List of functions and variables.
+* Concept Index:: List of concepts.
+* Key Index:: List of keystrokes.
+
+ --- The Detailed Node Listing ---
+
+Installation
+
+* Pcl-cvs installation:: How to install pcl-cvs on your system.
+* On-line manual installation:: How to install the on-line manual.
+* Typeset manual installation:: How to create typeset documentation
+ about pcl-cvs.
+
+About pcl-cvs
+
+* Contributors:: Contributors to pcl-cvs.
+* Archives:: Where can I get a copy of Pcl-Cvs?
+
+Buffer contents
+
+* File status:: The meaning of the second field.
+* Selected files:: How selection works.
+
+Commands
+
+* Updating the directory:: Commands to update the local directory
+* Movement commands:: How to move up and down in the buffer
+* Marking files:: How to mark files that other commands
+ will later operate on.
+* Committing changes:: Checking in your modifications to the
+ CVS repository.
+* Editing files:: Loading files into Emacs.
+* Getting info about files:: Display the log and status of files.
+* Adding and removing files:: Adding and removing files
+* Undoing changes:: Undoing changes
+* Removing handled entries:: Uninteresting lines can easily be removed.
+* Ignoring files:: Telling CVS to ignore generated files.
+* Viewing differences:: Commands to @samp{diff} different versions.
+* Invoking Ediff:: Running @samp{ediff} from @samp{*cvs*} buffer.
+* Invoking Emerge:: Running @samp{emerge} from @samp{*cvs*} buffer.
+* Reverting your buffers:: Reverting your buffers
+* Miscellaneous commands:: Miscellaneous commands
+@end menu
+
+
+@node Installation, About pcl-cvs, Top, Top
+@comment node-name, next, previous, up
+
+@chapter Installation
+@cindex Installation
+
+This section describes the installation of pcl-cvs, the GNU Emacs CVS
+front-end. You should install not only the elisp files themselves, but
+also the on-line documentation so that your users will know how to use
+it. You can create typeset documentation from the file
+@file{pcl-cvs.texinfo} as well as an on-line info file. The following
+steps are also described in the file @file{INSTALL} in the source
+directory.
+
+@menu
+* Pcl-cvs installation:: How to install pcl-cvs on your system.
+* On-line manual installation:: How to install the on-line manual.
+* Typeset manual installation:: How to create typeset documentation
+ about pcl-cvs.
+@end menu
+
+
+@node Pcl-cvs installation, On-line manual installation, Installation, Installation
+@comment node-name, next, previous, up
+@section Installation of the pcl-cvs program
+@cindex Installation of elisp files
+
+@enumerate
+@item
+Possibly edit the file @file{Makefile} to reflect the situation at your
+site. We say "possibly" because the version of pcl-cvs included with
+CVS uses a configuration mechanism integrated with the overall
+mechanisms used by the CVS build and install procedures. Thus the file
+@code{Makefile} will be generated automatically from the file
+@code{Makefile.in}, and it should not be necessary to edit it further.
+
+If you do have to edit the @file{Makefile}, the only things you have to
+change is the definition of @code{lispdir} and @code{infodir}. The
+elisp files will be copied to @code{lispdir}, and the info file(s) to
+@code{infodir}.
+
+@item
+Configure pcl-cvs.el
+
+There are a couple of pathnames that you have to check to make sure that
+they match your system. They appear early in the file
+@samp{pcl-cvs.el}.
+
+@strong{NOTE:} If your system is running emacs 18.57 or earlier you MUST
+uncomment the line that says:
+@example
+(setq delete-exited-processes nil)
+@end example
+
+Setting @code{delete-exited-processes} to @code{nil} works around a bug
+in emacs that causes it to dump core. The bug was fixed in emacs
+18.58.@refill
+
+@item
+Release 1.05 and later of pcl-cvs requires parts of the Elib library,
+version 1.0 or later. Elib is available via anonymous ftp from
+prep.ai.mit.edu in @file{pub/gnu/elib-1.0.tar.gz}, and from a lot of
+other sites that mirror prep. Get Elib, and install it, before
+proceeding.
+
+@strong{NOTE:} The version of pcl-cvs included with CVS includes a copy
+of Elib in the sub-directory @file{elib} under the
+@file{contrib/pcl-cvs} directory.
+
+@item
+Type @samp{make install} in the source directory. This will
+byte-compile all @file{.el} files and copy the @file{*.elc} files into
+the directory you specified in step 1.
+
+If you want to install the @file{*.el} files too, you can type
+@samp{make install-el} to do so.
+
+If you only want to create the compiled elisp files, but don't want to
+install them, you can type @samp{make} without parameters.
+
+@item
+Edit the file @file{default.el} in your emacs lisp directory (usually
+@file{/usr/gnu/lib/emacs/site-lisp} or something similar) and enter the
+contents of the file @file{pcl-cvs-startup.el} into it. It contains a
+couple of @code{auto-load}s that facilitates the use of pcl-cvs.
+
+@end enumerate
+
+
+@node On-line manual installation, Typeset manual installation, Pcl-cvs installation, Installation
+@comment node-name, next, previous, up
+
+@section Installation of the on-line manual.
+@cindex Manual installation (on-line)
+@cindex Installation of on-line manual
+@cindex Generating the on-line manual
+@cindex On-line manual (how to generate)
+@cindex Info-file (how to generate)
+
+@enumerate
+@item
+Create the info file(s) @file{pcl-cvs.info*} from @file{pcl-cvs.texinfo}
+by typing @samp{make info}. If you don't have the program
+@samp{makeinfo} you can get it by anonymous ftp from
+e.g. @samp{prep.ai.mit.edu} as @file{pub/gnu/texinfo-3.7.tar.gz} (there
+might be a newer version there when you read this).@refill
+
+@item
+Install the info file(s) @file{pcl-cvs.info*} into your standard
+@file{info} directory. You should be able to do this by typing
+@samp{make install-info}.@refill
+
+@item
+Edit the file @file{dir} in the @file{info} directory and enter one line
+to contain a pointer to the info file(s) @file{pcl-cvs.info*}. The line
+can, for instance, look like this:@refill
+
+@example
+* Pcl-cvs: (pcl-cvs). An Emacs front-end to CVS.
+@end example
+@end enumerate
+
+
+@node Typeset manual installation, , On-line manual installation, Installation
+@comment node-name, next, previous, up
+
+@section How to make typeset documentation from pcl-cvs.texinfo
+@cindex Manual installation (typeset)
+@cindex Installation of typeset manual
+@cindex Printing a manual
+@cindex TeX - generating a typeset manual
+@cindex Generating a typeset manual
+
+If you have @TeX{} installed at your site, you can make a typeset manual
+from @file{pcl-cvs.texinfo}.
+
+@enumerate
+@item
+Run @TeX{} by typing `@samp{make pcl-cvs.dvi}'. You will not get the
+indices unless you have the @code{texindex} program.
+
+@item
+Convert the resulting device independent file @file{pcl-cvs.dvi} to a
+form which your printer can output and print it. If you have a
+postscript printer there is a program, @code{dvi2ps}, which does. There
+is also a program which comes together with @TeX{}, @code{dvips}, which
+you can use.
+
+@end enumerate
+
+
+@node About pcl-cvs, Getting started, Installation, Top
+@comment node-name, next, previous, up
+
+@chapter About pcl-cvs
+@cindex About pcl-cvs
+
+Pcl-cvs is a front-end to CVS versions 1.5 through 1.7 and newer; and
+possibly verison 1.3 and 1.4A2. It integrates the most frequently used
+CVS commands into an emacs interface.
+
+@menu
+* Contributors:: Contributors to pcl-cvs.
+* Archives:: Where can I get a copy of Pcl-Cvs?
+@end menu
+
+
+@node Contributors, Archives, About pcl-cvs, About pcl-cvs
+@comment node-name, next, previous, up
+
+@section Contributors to pcl-cvs
+@cindex Contributors
+@cindex Authors
+
+Contributions to the package are welcome. I have limited time to work
+on this project, but I will gladly add any code that you contribute to
+me to this package (@pxref{Bugs}).
+
+The following persons have made contributions to pcl-cvs.
+
+@itemize @bullet
+@item
+Brian Berliner wrote CVS, together with some other contributors.
+Without his work on CVS this package would be useless@dots{}
+
+@item
+Per Cederqvist wrote most of the otherwise unattributed functions in
+pcl-cvs as well as all documentation.
+
+@item
+Inge Wallin (@samp{inge@@lysator.liu.se}) wrote the skeleton to
+@file{pcl-cvs.texinfo}, and gave useful comments on it. He also wrote
+the files @file{elib-node.el} and @file{compile-all.el}. The file
+@file{cookie.el} was inspired by Inge.@refill
+
+@item
+Linus Tolke (@samp{linus@@lysator.liu.se}) contributed useful comments
+on both the functionality and the documentation.@refill
+
+@item
+Jamie Zawinski (@samp{jwz@@lucid.com}) contributed
+@file{pcl-cvs-lucid.el}.
+
+@item
+Leif Lonnblad contributed RCVS support. (Since superceded by the new
+remote CVS support.)
+
+@item
+Jim Blandy (@samp{jimb@@cyclic.com}) contributed hooks to automatically
+guess CVS log entries from ChangeLog contents; and initial support of
+the new Cygnus / Cyclic remote CVS; as well as various sundry bug fixes
+and cleanups.
+
+@item
+Jim Kingdon (@samp{kingdon@@cyclic.com}) contributed lots of fixes to
+the build and install procedure.
+
+@item
+Greg A. Woods (@samp{woods@@planix.com}) contributed code to implement
+the use of per-file diff buffers; and vendor join diffs with emerge and
+ediff; as well as various an sundry bug fixes and cleanups.
+@end itemize
+
+Apart from these, a lot of people have send me suggestions, ideas,
+requests, bug reports and encouragement. Thanks a lot! Without your
+there would be no new releases of pcl-cvs.
+
+
+@node Archives, , Contributors, About pcl-cvs
+@comment node-name, next, previous, up
+
+@section Where can I get pcl-cvs?
+@cindex Sites
+@cindex Archives
+@cindex Ftp-sites
+@cindex Getting pcl-cvs
+@cindex Email archives
+
+The current release of pcl-cvs is included in CVS-1.7.
+
+The author's release of pcl-cvs can be fetched via anonymous ftp from
+@code{ftp.lysator.liu.se}, (IP no. 130.236.254.1) in the directory
+@code{pub/emacs}. If you don't live in Scandinavia you should probably
+check with archie to see if there is a site closer to you that archives
+pcl-cvs.
+
+New releases will be announced to appropriate newsgroups. If you send
+your email address to me I will add you to my list of people to mail
+when I make a new release.
+
+
+@node Getting started, Buffer contents, About pcl-cvs, Top
+@comment node-name, next, previous, up
+
+@chapter Getting started
+@cindex Introduction
+@cindex Example run
+
+This document assumes that you know what CVS is, and that you at least
+knows the fundamental concepts of CVS. If that is not the case you
+should read the man page for CVS.
+
+Pcl-cvs is only useful once you have checked out a module. So before
+you invoke it you must have a copy of a module somewhere in the file
+system.
+
+You invoke pcl-cvs by typing @kbd{M-x cvs-update RET}. If your emacs
+responds with @samp{[No match]} your system administrator has not
+installed pcl-cvs properly. Try @kbd{M-x load-library RET pcl-cvs RET}.
+If that also fails - talk to your root. If it succeeds you might put
+this line in your @file{.emacs} file so that you don't have to type the
+@samp{load-library} command every time you wish to use pcl-cvs:
+
+@example
+(autoload 'cvs-update "pcl-cvs" nil t)
+@end example
+
+The function @code{cvs-update} will ask for a directory. The command
+@samp{cvs update} will be run in that directory. (It should contain
+files that have been checked out from a CVS archive.) The output from
+@code{cvs} will be parsed and presented in a table in a buffer called
+@samp{*cvs*}. It might look something like this:
+
+@example
+PCL-CVS release 1.05-CVS-$Name: $.
+@comment -release-
+
+In directory /users/ceder/FOO/test:
+ Updated bar
+ Updated file.txt
+ Modified ci namechange
+ Updated newer
+
+In directory /users/ceder/FOO/test/sub:
+ Modified ci ChangeLog
+---------- End -----
+@end example
+
+In this example the two files (@file{bar}, @file{file.txt}, and
+@file{newer}) that are marked with @samp{Updated} have been copied from
+the CVS repository to @file{/users/ceder/FOO/test/} since someone else
+have checked in newer versions of them. Two files (@file{namechange}
+and @file{sub/ChangeLog}) have been modified locally, and needs to be
+checked in.
+
+You can move the cursor up and down in the buffer with @kbd{C-n} and
+@kbd{C-p} or @kbd{n} and @kbd{p}. If you press @kbd{c} on one of the
+@samp{Modified} files that file will be checked in to the CVS
+repository. @xref{Committing changes}. You can press @kbd{x} to get rid
+of the "uninteresting" files that have only been @samp{Updated} (and
+don't require any further action from you).@refill
+
+You can also easily get a @samp{diff} between your modified file and the
+base version that you started from, and you can get the output from
+@samp{cvs log} and @samp{cvs status} on the listed files simply by
+pressing a key (@pxref{Getting info about files}).
+
+
+@node Buffer contents, Commands, Getting started, Top
+@comment node-name, next, previous, up
+
+@chapter Buffer contents
+@cindex Buffer contents
+
+The display contains four columns. They contain, from left to right:
+
+@itemize @bullet
+@item
+An asterisk when the file is @dfn{marked} (@pxref{Selected
+files}).@refill
+@item
+The status of the file. See @xref{File status}, for more
+information.@refill
+@item
+A "need to be checked in"-marker (@samp{ci}).
+@item
+The file name.
+@end itemize
+
+@menu
+* File status:: The meaning of the second field.
+* Selected files:: How selection works.
+@end menu
+
+
+@node File status, Selected files, Buffer contents, Buffer contents
+@comment node-name, next, previous, up
+
+@section File status
+@cindex File status
+@cindex Updated (file status)
+@cindex Patched (file status)
+@cindex Modified (file status)
+@cindex Merged (file status)
+@cindex Conflict (file status)
+@cindex Added (file status)
+@cindex Removed (file status)
+@cindex Unknown (file status)
+@cindex Removed from repository (file status)
+@cindex Removed from repository, changed by you (file status)
+@cindex Removed by you, changed in repository (file status)
+@cindex Move away @var{file} - it is in the way (file status)
+@cindex This repository is missing!@dots{} (file status)
+
+The @samp{file status} field can have the following values:
+
+@table @samp
+
+@item Updated
+The file was brought up to date with respect to the repository. This is
+done for any file that exists in the repository but not in your source,
+and for files that you haven't changed but are not the most recent
+versions available in the repository.@refill
+
+@item Patched
+The file was brought up to date with respect to a remote repository by
+way of fetching and applying a patch to the file in your source. This
+is done for any file that exists in a remote repository and in your
+source; of which you haven't changed locally but is not the most recent
+version available in the remote repository.@refill
+
+@item Modified
+The file is modified in your working directory, and there was no
+modification to the same file in the repository.@refill
+
+@item Merged
+The file is modified in your working directory, and there were
+modifications in the repository as well as in your copy, but they were
+merged successfully, without conflict, in your working directory.@refill
+
+@item Conflict
+A conflict was detected while trying to merge your changes to @var{file}
+with changes from the source repository. @var{file} (the copy in your
+working directory) is now the output of the @samp{rcsmerge} command on
+the two versions; an unmodified copy of your file is also in your
+working directory, with the name @file{.#@var{file}.@var{version}},
+where @var{version} is the RCS revision that your modified file started
+from. @xref{Viewing differences}, for more details.@refill
+
+@item Added
+The file has been added by you, but it still needs to be checked in to
+the repository.@refill
+
+@item Removed
+The file has been removed by you, but it needs to be checked in to the
+repository. You can resurrect it by typing @kbd{a} (@pxref{Adding and
+removing files}).@refill
+
+@item Unknown
+A file that was detected in your directory, but that neither appears in
+the repository, nor is present on the list of files that CVS should
+ignore.@refill
+
+@end table
+
+There are also a few special cases, that rarely occur, which have longer
+strings in the fields:
+
+@table @samp
+@item Removed from repository
+The file has been removed from your directory since someone has removed
+it from the repository. (It is still present in the Attic directory, so
+no permanent loss has occurred). This, unlike the other entries in this
+table, is not an error condition.@refill
+
+@item Removed from repository, changed by you
+You have modified a file that someone have removed from the repository.
+You can correct this situation by removing the file manually (see
+@pxref{Adding and removing files}).@refill
+
+@item Removed by you, changed in repository
+You have removed a file, and before you committed the removal someone
+committed a change to that file. You could use @kbd{a} to resurrect the
+file (see @pxref{Adding and removing files}).@refill
+
+@item Move away @var{file} - it is in the way
+For some reason CVS does not like the file @var{file}. Rename or remove
+it.@refill
+
+@item This repository is missing! Remove this dir manually.
+It is impossible to remove a directory in the CVS repository in a clean
+way. Someone have tried to remove one, and CVS gets confused. Remove
+your copy of the directory.@refill
+@end table
+
+
+@node Selected files, , File status, Buffer contents
+@comment node-name, next, previous, up
+
+@section Selected files
+@cindex Selected files
+@cindex Marked files
+@cindex File selection
+@cindex Active files
+
+Many of the commands works on the current set of @dfn{selected} files.
+
+@itemize @bullet
+@item
+If there are any files that are marked they constitute the set of
+selected files.@refill
+@item
+Otherwise, if the cursor points to a file, that file is the selected
+file.@refill
+@item
+Otherwise, if the cursor points to a directory, all the files in that
+directory that appears in the buffer are the selected files.
+@end itemize
+
+This scheme might seem a little complicated, but once one get used to
+it, it is quite powerful.
+
+@xref{Marking files} tells how you mark and unmark files.
+
+
+@node Commands, Customization, Buffer contents, Top
+@comment node-name, next, previous, up
+
+@chapter Commands
+
+@iftex
+This chapter describes all the commands that you can use in pcl-cvs.
+@end iftex
+@ifinfo
+The nodes in this menu contains explanations about all the commands that
+you can use in pcl-cvs. They are grouped together by type.
+@end ifinfo
+
+@menu
+* Updating the directory:: Commands to update the local directory
+* Movement commands:: How to move up and down in the buffer
+* Marking files:: How to mark files that other commands
+ will later operate on.
+* Committing changes:: Checking in your modifications to the
+ CVS repository.
+* Editing files:: Loading files into Emacs.
+* Getting info about files:: Display the log and status of files.
+* Adding and removing files:: Adding and removing files
+* Undoing changes:: Undoing changes
+* Removing handled entries:: Uninteresting lines can easily be removed.
+* Ignoring files:: Telling CVS to ignore generated files.
+* Viewing differences:: Commands to @samp{diff} different versions.
+* Invoking Ediff:: Running @samp{ediff} from @samp{*cvs*} buffer.
+* Invoking Emerge:: Running @samp{emerge} from @samp{*cvs*} buffer.
+* Reverting your buffers:: Reverting your buffers
+* Miscellaneous commands:: Miscellaneous commands
+@end menu
+
+
+@node Updating the directory, Movement commands, Commands, Commands
+@comment node-name, next, previous, up
+
+@section Updating the directory
+@findex cvs-update
+@findex cvs-mode-update-no-prompt
+@findex cvs-delete-lock
+@cindex Getting the *cvs* buffer
+@kindex g - Rerun @samp{cvs update}
+
+
+@table @kbd
+
+@item M-x cvs-update
+Run a @samp{cvs update} command. You will be asked for the directory in
+which the @samp{cvs update} will be run. The output will be parsed by
+pcl-cvs, and the result printed in the @samp{*cvs*} buffer (see
+@pxref{Buffer contents} for a description of the contents).@refill
+
+By default, @samp{cvs-update} will descend recursively into
+subdirectories. You can avoid that behavior by giving a prefix
+argument to it (e.g., by typing @kbd{C-u M-x cvs-update RET}).@refill
+
+All other commands in pcl-cvs requires that you have a @samp{*cvs*}
+buffer. This is the command that you use to get one.@refill
+
+CVS uses lock files in the repository to ensure the integrity of the
+data files in the repository. They might be left behind i.e. if a
+workstation crashes in the middle of a CVS operation. CVS outputs a
+message when it is waiting for a lock file to go away. Pcl-cvs will
+show the same message in the *cvs* buffer, together with instructions
+for deleting the lock files. You should normally not have to delete
+them manually --- just wait a little while and the problem should fix
+itself. But if the lock files doesn't disappear you can delete them
+with @kbd{M-x cvs-delete-lock RET}.@refill
+
+@item g
+This will run @samp{cvs update} again. It will always use the same
+buffer that was used with the previous @samp{cvs update}. Give a prefix
+argument to avoid descending into subdirectories. This runs the command
+@samp{cvs-mode-update-no-prompt}.@refill
+
+@item G
+This will run @samp{cvs update} and prompt for a new directory to
+update. This runs the command @samp{cvs-update}.@refill
+
+@end table
+
+
+@node Movement commands, Marking files, Updating the directory, Commands
+@comment node-name, next, previous, up
+
+@section Movement Commands
+@cindex Movement Commands
+@findex cookie-next-cookie
+@findex cookie-previous-cookie
+@kindex SPC - Move down one file
+@kindex C-n - Move down one file
+@kindex n - Move down one file
+@kindex C-p - Move up one file
+@kindex p - Move up on file
+
+You can use most normal Emacs commands to move forward and backward in
+the buffer. Some keys are rebound to functions that take advantage of
+the fact that the buffer is a pcl-cvs buffer:
+
+
+@table @kbd
+@item SPC
+@itemx C-n
+@itemx n
+These keys move the cursor one file forward, towards the end of the
+buffer (@code{cookie-next-cookie}).
+
+@item C-p
+@itemx p
+These keys move one file backward, towards the beginning of the buffer
+(@code{cookie-previous-cookie}).
+@end table
+
+
+@node Marking files, Committing changes, Movement commands, Commands
+@comment node-name, next, previous, up
+
+@section Marking files
+@cindex Selecting files (commands to mark files)
+@cindex Marking files
+@kindex m - marking a file
+@kindex M - marking all files
+@kindex u - unmark a file
+@kindex ESC DEL - unmark all files
+@kindex DEL - unmark previous file
+@findex cvs-mode-mark
+@findex cvs-mode-unmark
+@findex cvs-mode-mark-all-files
+@findex cvs-mode-unmark-all-files
+@findex cvs-mode-unmark-up
+
+Pcl-cvs works on a set of @dfn{selected files} (@pxref{Selected files}).
+You can mark and unmark files with these commands:
+
+@table @kbd
+@item m
+This marks the file that the cursor is positioned on. If the cursor is
+positioned on a directory all files in that directory will be marked.
+(@code{cvs-mode-mark}).
+
+@item u
+Unmark the file that the cursor is positioned on. If the cursor is on a
+directory, all files in that directory will be unmarked.
+(@code{cvs-mode-unmark}).@refill
+
+@item M
+Mark @emph{all} files in the buffer (@code{cvs-mode-mark-all-files}).
+
+@item @key{ESC} @key{DEL}
+Unmark @emph{all} files (@code{cvs-mode-unmark-all-files}).
+
+@item @key{DEL}
+Unmark the file on the previous line, and move point to that line
+(@code{cvs-mode-unmark-up}).
+@end table
+
+
+@node Committing changes, Editing files, Marking files, Commands
+@comment node-name, next, previous, up
+
+@section Committing changes
+@cindex Committing changes
+@cindex Ci
+@findex cvs-mode-commit
+@findex cvs-mode-changelog-commit
+@kindex c - commit files
+@kindex C - commit files with ChangeLog message
+@vindex cvs-erase-input-buffer (variable)
+@vindex cvs-auto-revert-after-commit (variable)
+@cindex Commit buffer
+@cindex Edit buffer
+@cindex Erasing commit message
+@cindex Reverting buffers after commit
+
+@table @kbd
+
+@item c
+All files that have a "need to be checked in"-marker (@pxref{Buffer
+contents}) can be checked in with the @kbd{c} command. It checks in all
+selected files (@pxref{Selected files}) (except those who lack the
+"ci"-marker - they are ignored). Pressing @kbd{c} causes
+@code{cvs-mode-commit} to be run.@refill
+
+When you press @kbd{c} you will get a buffer called
+@samp{*cvs-commit-message*}. Enter the log message for the file(s) in
+it. When you are ready you should press @kbd{C-c C-c} to actually
+commit the files (using @code{cvs-edit-done}).
+
+Normally the @samp{*cvs-commit-message*} buffer will retain the log
+message from the previous commit, but if the variable
+@code{cvs-erase-input-buffer} is set to a non-@code{nil} value the
+buffer will be erased. Point and mark will always be located around the
+entire buffer so that you can easily erase it with @kbd{C-w}
+(@samp{kill-region}).@refill
+
+If you are editing the files in your emacs an automatic
+@samp{revert-buffer} will be performed. (If the file contains
+@samp{$@asis{Id}$} keywords @samp{cvs commit} will write a new file with
+the new values substituted. The auto-revert makes sure that you get
+them into your buffer). The revert will not occur if you have modified
+your buffer, or if @samp{cvs-auto-revert-after-commit} is set to
+@samp{nil}.@refill
+
+@item C
+This is just like @samp{cvs-mode-commit}, except that it tries to
+provide appropriate default log messages by looking at the
+@samp{ChangeLog}s in the current directory. The idea is to write your
+ChangeLog entries first, and then use this command to commit your
+changes. Pressing @kbd{C} causes @code{cvs-mode-changelog-commit} to be
+run.@refill
+
+To select default log text, pcl-cvs:
+@itemize @minus
+@item
+finds the ChangeLogs for the files to be checked in;
+@item
+verifies that the top entry in the ChangeLog is on the current date and
+by the current user; if not, no default text is provided;
+@item
+search the ChangeLog entry for paragraphs containing the names of the
+files we're checking in; and finally
+@item
+uses those paragraphs as the default log text in the
+@samp{*cvs-commit-message*} buffer.
+@end itemize
+
+You can then commit the @samp{ChangeLog} file once per day without any
+log message.@refill
+
+@end table
+
+
+@node Editing files, Getting info about files, Committing changes, Commands
+@comment node-name, next, previous, up
+
+@section Editing files
+@cindex Editing files
+@cindex Finding files
+@cindex Loading files
+@cindex Dired
+@cindex Invoking dired
+@findex cvs-mode-find-file
+@findex cvs-mode-find-file-other-window
+@findex cvs-mode-add-change-log-entry-other-window
+@kindex f - find file or directory
+@kindex o - find file in other window
+@kindex A - add ChangeLog entry
+
+There are currently three commands that can be used to find a file (that
+is, load it into a buffer and start editing it there). These commands
+work on the line that the cursor is situated at. They ignore any marked
+files.
+
+@table @kbd
+@item f
+Find the file that the cursor points to. Run @samp{dired}
+@ifinfo
+(@pxref{Dired,,,Emacs})
+@end ifinfo
+if the cursor points to a directory (@code{cvs-mode-find-file}).@refill
+
+@item o
+Like @kbd{f}, but use another window
+(@code{cvs-mode-find-file-other-window}).@refill
+
+@item A
+Invoke @samp{add-change-log-entry-other-window} to edit a
+@samp{ChangeLog} file. The @samp{ChangeLog} will be found in the
+directory of the file the cursor points to.
+(@code{cvs-mode-add-change-log-entry-other-window}).@refill
+@end table
+
+
+@node Getting info about files, Adding and removing files, Editing files, Commands
+@comment node-name, next, previous, up
+
+@section Getting info about files
+@cindex Status (cvs command)
+@cindex Log (RCS/cvs command)
+@cindex Getting status
+@kindex l - run @samp{cvs log}
+@kindex s - run @samp{cvs status}
+@findex cvs-mode-log
+@findex cvs-mode-status
+
+Both of the following commands can be customized.
+@xref{Customization}.@refill
+
+@table @kbd
+@item l
+Run @samp{cvs log} on all selected files, and show the result in a
+temporary buffer (@code{cvs-mode-log}).
+
+@item s
+Run @samp{cvs status} on all selected files, and show the result in a
+temporary buffer (@code{cvs-mode-status}).
+@end table
+
+
+@node Adding and removing files, Undoing changes, Getting info about files, Commands
+@comment node-name, next, previous, up
+
+@section Adding and removing files
+@cindex Adding files
+@cindex Removing files
+@cindex Resurrecting files
+@cindex Deleting files
+@cindex Putting files under CVS control
+@kindex a - add a file
+@kindex r - remove a file
+@findex cvs-mode-add
+@findex cvs-mode-remove-file
+
+The following commands are available to make it easy to add and remove
+files from the CVS repository.
+
+@table @kbd
+@item a
+Add all selected files. This command can be used on @samp{Unknown}
+files (see @pxref{File status}). The status of the file will change to
+@samp{Added}, and you will have to use @kbd{c} (@samp{cvs-mode-commit}, see
+@pxref{Committing changes}) to really add the file to the
+repository.@refill
+
+This command can also be used on @samp{Removed} files (before you commit
+them) to resurrect them.
+
+Selected files that are neither @samp{Unknown} nor @samp{Removed} will
+be ignored by this command.
+
+The command that is run is @code{cvs-mode-add}.
+
+@item r
+This command removes the selected files (after prompting for
+confirmation). The files are @samp{rm}ed from your directory and
+(unless the status was @samp{Unknown}; @pxref{File status}) they will
+also be @samp{cvs remove}d. If the files were @samp{Unknown} they will
+disappear from the buffer. Otherwise their status will change to
+@samp{Removed}, and you must use @kbd{c} (@samp{cvs-mode-commit},
+@pxref{Committing changes}) to commit the removal.@refill
+
+The command that is run is @code{cvs-mode-remove-file}.
+@end table
+
+
+@node Undoing changes, Removing handled entries, Adding and removing files, Commands
+@comment node-name, next, previous, up
+
+@section Undoing changes
+@cindex Undo changes
+@cindex Flush changes
+@kindex U - undo changes
+@findex cvs-mode-undo-local-changes
+
+@table @kbd
+@item U
+If you have modified a file, and for some reason decide that you don't
+want to keep the changes, you can undo them with this command. It works
+by removing your working copy of the file and then getting the latest
+version from the repository (@code{cvs-mode-undo-local-changes}.
+@end table
+
+
+@node Removing handled entries, Ignoring files, Undoing changes, Commands
+@comment node-name, next, previous, up
+
+@section Removing handled entries
+@cindex Expunging uninteresting entries
+@cindex Uninteresting entries, getting rid of them
+@cindex Getting rid of uninteresting lines
+@cindex Removing uninteresting (processed) lines
+@cindex Handled lines, removing them
+@kindex x - remove processed entries
+@kindex C-k - remove selected entries
+@findex cvs-mode-remove-handled
+@findex cvs-mode-acknowledge
+
+@table @kbd
+@item x
+This command allows you to remove all entries that you have processed.
+More specifically, the lines for @samp{Updated} files (@pxref{File
+status} and files that have been checked in (@pxref{Committing changes})
+are removed from the buffer. If a directory becomes empty the heading
+for that directory is also removed. This makes it easier to get an
+overview of what needs to be done.
+
+The command is called @code{cvs-mode-remove-handled}. If
+@samp{cvs-auto-remove-handled} is set to non-@code{nil} this will
+automatically be performed after every commit.@refill
+
+@item C-k
+This command can be used for lines that @samp{cvs-mode-remove-handled} would
+not delete, but that you want to delete (@code{cvs-mode-acknowledge}).
+@end table
+
+
+@node Ignoring files, Viewing differences, Removing handled entries, Commands
+@comment node-name, next, previous, up
+
+@section Ignoring files
+@kindex i - ignoring files
+@findex cvs-mode-ignore
+
+@table @kbd
+@item i
+Arrange so that CVS will ignore the selected files. The file names are
+added to the @file{.cvsignore} file in the corresponding directory. If
+the @file{.cvsignore} doesn't exist it will be created.
+
+The @file{.cvsignore} file should normally be added to the repository,
+but you could ignore it also if you like it better that way.
+
+This runs @code{cvs-mode-ignore}.
+@end table
+
+
+@node Viewing differences, Invoking Ediff, Ignoring files, Commands
+@comment node-name, next, previous, up
+
+@section Viewing differences
+@cindex Diff
+@cindex Ediff
+@cindex Invoking ediff
+@cindex Conflicts, how to resolve them
+@cindex Viewing differences
+@kindex d - run @samp{cvs diff}
+@kindex b - diff backup file
+@findex cvs-mode-diff-cvs
+@findex cvs-mode-diff-backup
+@vindex cvs-diff-ignore-marks (variable)
+
+@table @kbd
+@item d
+Display a @samp{cvs diff} between the selected files and the RCS version
+that they are based on. @xref{Customization} describes how you can send
+flags to @samp{cvs diff}. If @var{cvs-diff-ignore-marks} is set to a
+non-@code{nil} value or if a prefix argument is given (but not both) any
+marked files will not be considered to be selected.
+(@code{cvs-mode-diff-cvs}).@refill
+
+@item b
+If CVS finds a conflict while merging two versions of a file (during a
+@samp{cvs update}, @pxref{Updating the directory}) it will save the
+original file in a file called @file{.#@var{FILE}.@var{VERSION}} where
+@var{FILE} is the name of the file, and @var{VERSION} is the RCS version
+number that your file was based on.@refill
+
+With the @kbd{b} command you can run a @samp{diff} on the files
+@file{.#@var{FILE}.@var{VERSION}} and @file{@var{FILE}}. You can get a
+context- or Unidiff by setting @samp{cvs-diff-flags} -
+@pxref{Customization}. This command only works on files that have
+status @samp{Conflict} or @samp{Merged}.@refill
+
+If @var{cvs-diff-ignore-marks} is set to a non-@code{nil} value or if a
+prefix argument is given (but not both) any marked files will not be
+considered to be selected. (@code{cvs-mode-diff-backup}).@refill
+@end table
+
+
+@node Invoking Ediff, Invoking Emerge, Viewing differences, Commands
+@comment node-name, next, previous, up
+
+@section Running ediff
+@cindex Ediff
+@cindex Invoking ediff
+@cindex Viewing differences
+@cindex Conflicts, resolving
+@cindex Resolving conflicts
+@kindex e - invoke @samp{ediff}
+@findex cvs-mode-ediff
+@findex run-ediff-from-cvs-buffer
+@findex cvs-old-ediff-interface
+
+@table @kbd
+@item e
+This command works
+slightly different depending on the version of @samp{ediff} and the file
+status.@refill
+
+With modern versions of @samp{ediff}, this command invokes
+@samp{run-ediff-from-cvs-buffer} on one file.@refill
+
+@strong{Note:} When the file status is @samp{Merged} or @samp{Conflict},
+CVS has already performed a merge. The resulting file is not used in
+any way if you use this command. If you use the @kbd{q} command inside
+@samp{ediff} (to successfully terminate a merge) the file that CVS
+created will be overwritten.@refill
+
+Older versions of @samp{ediff} use an interface similar to
+@samp{emerge}. The function @samp{cvs-old-ediff-interface} is invoked
+if the version of @samp{ediff} you have doesn't support
+@samp{run-ediff-from-cvs-buffer}. These older versions do not support
+merging of revisions.@refill
+
+@table @asis
+@item @samp{Modified}
+Run @samp{ediff-files} with your working file as file A, and the latest
+revision in the repository as file B.
+
+@item @samp{Merged}
+@itemx @samp{Conflict}
+Run @samp{ediff-files3} with your working file (as it was prior to your
+invocation of @samp{cvs-update}) as file A, the latest revision in the
+repository as file B, and the revision that you based your local
+modifications on as ancestor.
+
+@item @samp{Updated}
+@itemx @samp{Patched}
+Run @samp{ediff-files} with your working file as file A, and a given
+revision in the repository as file B. You are prompted for the revision
+to ediff against, and you may specify either a tag name or a numerical
+revision number (@pxref{Getting info about files}).
+@end table
+
+@end table
+
+@node Invoking Emerge, Reverting your buffers, Invoking Ediff, Commands
+@comment node-name, next, previous, up
+
+@section Running emerge
+@cindex Emerge
+@cindex Ediff
+@cindex Viewing differences
+@cindex Invoking emerge
+@cindex Conflicts, resolving
+@cindex Resolving conflicts
+@kindex E - invoke @samp{emerge}
+@findex cvs-mode-emerge
+
+@table @kbd
+@item E
+Invoke @samp{emerge} on one file. This command works slightly different
+depending on the file status.
+
+@table @asis
+@item @samp{Modified}
+Run @samp{emerge-files} with your working file as file A, and the latest
+revision in the repository as file B.
+
+@item @samp{Merged}
+@itemx @samp{Conflict}
+Run @samp{emerge-files-with-ancestor} with your working file (as it was
+prior to your invocation of @samp{cvs-update}) as file A, the latest
+revision in the repository as file B, and the revision that you based
+your local modifications on as ancestor.
+@end table
+
+@strong{Note:} When the file status is @samp{Merged} or @samp{Conflict},
+CVS has already performed a merge. The resulting file is not used in
+any way if you use this command. If you use the @kbd{q} command inside
+@samp{emerge} (to successfully terminate the merge) the file that CVS
+created will be overwritten.
+
+@end table
+
+
+@node Reverting your buffers, Miscellaneous commands, Invoking Emerge, Commands
+@comment node-name, next, previous, up
+
+@section Reverting your buffers
+@findex cvs-mode-revert-updated-buffers
+@kindex R - revert buffers
+@cindex Syncing buffers
+@cindex Reverting buffers
+
+@table @kbd
+@item R
+If you are editing (or just viewing) a file in a buffer, and that file
+is changed by CVS during a @samp{cvs-update}, all you have to do is type
+@kbd{R} in the *cvs* buffer to read in the new versions of the
+files.@refill
+
+All files that are @samp{Updated}, @samp{Merged} or in @samp{Conflict}
+are reverted from the disk. Any other files are ignored. Only files
+that you were already editing are read.@refill
+
+An error is signalled if you have modified the buffer since it was last
+changed. (@code{cvs-mode-revert-updated-buffers}).@refill
+@end table
+
+
+@node Miscellaneous commands, , Reverting your buffers, Commands
+@comment node-name, next, previous, up
+
+@section Miscellaneous commands
+@findex cvs-byte-compile-files
+@cindex Recompiling elisp files
+@cindex Byte compilation
+@cindex Getting rid of lock files
+@cindex Lock files
+@kindex q - bury the *cvs* buffer
+@findex bury-buffer
+
+@table @kbd
+@item M-x cvs-byte-compile-files
+Byte compile all selected files that end in .el.
+
+@item M-x cvs-delete-lock
+This command can be used in any buffer, and deletes the lock files that
+the *cvs* buffer informs you about. You should normally never have to
+use this command since CVS tries very carefully to always remove the
+lock files itself.
+
+You can only use this command when a message in the *cvs* buffer tells
+you so. You should wait a while before using this command in case
+someone else is running a cvs command.
+
+@item q
+Bury the *cvs* buffer. (@code{bury-buffer}).
+
+@end table
+
+
+@node Customization, Future enhancements, Commands, Top
+@comment node-name, next, previous, up
+
+@chapter Customization
+@vindex cvs-erase-input-buffer (variable)
+@vindex cvs-inhibit-copyright-message (variable)
+@vindex cvs-diff-flags (variable)
+@vindex cvs-diff-ignore-marks (variable)
+@vindex cvs-log-flags (variable)
+@vindex cvs-status-flags (variable)
+@vindex cvs-auto-remove-handled (variable)
+@vindex cvs-update-prog-output-skip-regexp (variable)
+@vindex cvs-cvsroot (variable)
+@vindex TMPDIR (environment variable)
+@vindex cvs-auto-revert-after-commit (variable)
+@vindex cvs-commit-buffer-require-final-newline (variable)
+@vindex cvs-sort-ignore-file (variable)
+@cindex Inhibiting the Copyright message.
+@cindex Copyright message, getting rid of it
+@cindex Getting rid of the Copyright message.
+@cindex Customization
+@cindex Variables, list of all
+@cindex Erasing the input buffer
+@cindex Context diff, how to get
+@cindex Unidiff, how to get
+@cindex Automatically remove handled files
+@cindex -u option in modules file
+@cindex Modules file (-u option)
+@cindex Update program (-u option in modules file)
+@cindex Reverting buffers after commit
+@cindex Require final newline
+@cindex Automatically inserting newline
+@cindex Commit message, inserting newline
+@cindex Sorting the .cvsignore file
+@cindex .cvsignore file, sorting
+@cindex Automatically sorting .cvsignore
+
+If you have an idea about any customization that would be handy but
+isn't present in this list, please tell me! @xref{Bugs} for info on how
+to reach me.@refill
+
+@table @samp
+@item cvs-erase-input-buffer
+If set to anything else than @code{nil} the edit buffer will be erased
+before you write the log message (@pxref{Committing changes}).
+
+@item cvs-inhibit-copyright-message
+The copyright message that is displayed on startup can be annoying after
+a while. Set this variable to @samp{t} if you want to get rid of it.
+(But don't set this to @samp{t} in the system defaults file - new users
+should see this message at least once).
+
+@item cvs-diff-flags
+A list of strings to pass as arguments to the @samp{cvs diff} and
+@samp{diff} programs. This is used by @samp{cvs-mode-diff-cvs} and
+@samp{cvs-mode-diff-backup} (key @kbd{b}, @pxref{Viewing differences}). If
+you prefer the Unidiff format you could add this line to your
+@file{.emacs} file:@refill
+
+@example
+(setq cvs-diff-flags '("-u"))
+@end example
+
+@item cvs-diff-ignore-marks
+If this variable is non-@code{nil} or if a prefix argument is given (but
+not both) to @samp{cvs-mode-diff-cvs} or @samp{cvs-mode-diff-backup}
+marked files are not considered selected.
+
+@item cvs-log-flags
+List of strings to send to @samp{cvs log}. Used by @samp{cvs-mode-log}
+(key @kbd{l}, @pxref{Getting info about files}).
+
+@item cvs-status-flags
+List of strings to send to @samp{cvs status}. Used by @samp{cvs-mode-status}
+(key @kbd{s}, @pxref{Getting info about files}).
+
+@item cvs-auto-remove-handled
+If this variable is set to any non-@code{nil} value
+@samp{cvs-mode-remove-handled} will be called every time you check in
+files, after the check-in is ready. @xref{Removing handled
+entries}.@refill
+
+@item cvs-auto-revert-after-commit
+If this variable is set to any non-@samp{nil} value any buffers you have
+that visit a file that is committed will be automatically reverted.
+This variable is default @samp{t}. @xref{Committing changes}.@refill
+
+@item cvs-update-prog-output-skip-regexp
+The @samp{-u} flag in the @file{modules} file can be used to run a command
+whenever a @samp{cvs update} is performed (see cvs(5)). This regexp
+is used to search for the last line in that output. It is normally set
+to @samp{"$"}. That setting is only correct if the command outputs
+nothing. Note that pcl-cvs will get very confused if the command
+outputs @emph{anything} to @samp{stderr}.
+
+@item cvs-cvsroot
+This variable can be set to override @samp{CVSROOT}. It should be a
+string. If it is set then everytime a cvs command is run it will be
+called as @samp{cvs -d @var{cvs-cvsroot}@dots{}} This can be useful if
+your site has several repositories.
+
+@item TMPDIR
+Pcl-cvs uses this @emph{environment variable} to decide where to put the
+temporary files it needs. It defaults to @file{/tmp} if it is not set.
+
+@item cvs-commit-buffer-require-final-newline
+When you enter a log message in the @samp{*cvs-commit-message*} buffer
+pcl-cvs will normally automatically insert a trailing newline, unless
+there already is one. This behavior can be controlled via
+@samp{cvs-commit-buffer-require-final-newline}. If it is @samp{t} (the
+default behavior), a newline will always be appended. If it is
+@samp{nil}, newlines will never be appended. Any other value causes
+pcl-cvs to ask the user whenever there is no trailing newline in the
+commit message buffer.
+
+@item cvs-sort-ignore-file
+If this variable is set to any non-@samp{nil} value the
+@file{.cvsignore} will always be sorted whenever you use
+@samp{cvs-mode-ignore} to add a file to it. This option is on by
+default.
+
+@end table
+
+
+@node Future enhancements, Bugs, Customization, Top
+@comment node-name, next, previous, up
+
+@chapter Future enhancements
+@cindex Enhancements
+
+Pcl-cvs is still under development and needs a number of enhancements to
+be called complete. Below is my current wish-list for future releases
+of pcl-cvs. Please, let me know which of these features you want most.
+They are listed below in approximately the order that I currently think
+I will implement them in.
+
+@itemize @bullet
+@item
+Rewritten parser code. There are many situations where pcl-cvs will
+fail to recognize the output from CVS. The situation could be greatly
+increased.
+
+@item
+@samp{cvs-status}. This will run @samp{cvs status} in a directory and
+produce a buffer that looks pretty much like the current *cvs* buffer.
+That buffer will include information for all version-controlled files.
+(There will be a simple keystroke to remove all "uninteresting" files,
+that is, files that are "Up-to-date"). In this new buffer you will be
+able to update a file, commit a file, et c. The big win with this is
+that you will be able to watch the differences between your current
+working file and the head revision in the repository before you update
+the file, and you can then choose to update it or let it wait for a
+while longer.
+
+@item
+Log mode. When this mode is finished you will be able to move around
+(using @kbd{n} and @kbd{p}) between the revisions of a file, mark two of
+them, and run a diff between them. You will be able to hide branches
+(similar to the way you can hide sub-paragraphs in outline-mode) and do
+merges between revisions. Other ideas about this are welcome.
+
+@item
+The current model for marks in the *cvs* buffer seems to be confusing.
+I am considering to use the VM model instead, where marks are normally
+inactive. To activate the mark, you issue a command like
+@samp{cvs-mode-next-command-uses-marks}. I might implement a flag so
+that you can use either version. Feedback on this before I start coding
+it is very welcome.
+
+@item
+It should be possible to run commands such as @samp{cvs log}, @samp{cvs
+status} and @samp{cvs commit} directly from a buffer containing a file,
+instead of having to @samp{cvs-update}. If the directory contains many
+files the @samp{cvs-update} can take quite some time, especially on a
+slow machine. I planed to put these kind of commands on the prefix
+@kbd{C-c C-v}, but that turned out to be used by for instance c++-mode.
+If you have any suggestions for a better prefix key, please let me know.
+
+@item
+Increased robustness. For instance, you can not currently press
+@kbd{C-g} when you are entering the description of a file that you are
+adding without confusing pcl-cvs.
+
+@item
+Support for multiple active *cvs* buffers.
+
+@item
+Dired support. I have an experimental @file{dired-cvs.el} that works
+together with CVS 1.2. Unfortunately I wrote it on top of a
+non-standard @file{dired.el}, so it must be rewritten.@refill
+
+@item
+An ability to send user-supplied options to all the cvs commands.
+
+@item
+Pcl-cvs is not at all clever about what it should do when @samp{cvs
+update} runs a program (due to the @samp{-u} option in the
+@file{modules} file --- see @samp{cvs(5)}). The current release uses a
+regexp to search for the end. At the very least that regexp should be
+configured for different modules. Tell me if you have any idea about
+what is the right thing to do. In a perfect world the program should
+also be allowed to print to @samp{stderr} without causing pcl-cvs to
+crash.
+@end itemize
+
+
+If you miss something in this wish-list, let me know! I don't promise
+that I will write it, but I will at least try to coordinate the efforts
+of making a good Emacs front end to CVS. See @xref{Bugs} for
+information about how to reach me.@refill
+
+So far, I have written most of pcl-cvs in my all-to-rare spare time. If
+you want pcl-cvs to be developed faster you can write a contract with
+Signum Support to do the extension. You can reach Signum Support by
+email to @samp{info@@signum.se} or via mail to Signum Support AB, Box
+2044, S-580 02 Linkoping, Sweden. Phone: +46 (0) 13 - 21 46 00. Fax:
++46 (0) 13 - 21 47 00.
+
+
+@node Bugs, COPYING, Future enhancements, Top
+@comment node-name, next, previous, up
+
+@chapter Bugs (known and unknown)
+@cindex Reporting bugs and ideas
+@cindex Bugs, how to report them
+@cindex Author, how to reach
+@cindex Email to the author
+@cindex Known bugs
+@cindex Bugs, known
+@cindex FAQ
+@cindex Problems, list of common
+
+If you find a bug or misfeature, don't hesitate to tell me! Send email
+to @samp{ceder@@lysator.liu.se}.
+
+If you have ideas for improvements, or if you have written some
+extensions to this package, I would like to hear from you. I hope that
+you find this package useful!
+
+Below is a partial list of currently known problems with pcl-cvs version
+1.05.
+
+@table @asis
+@item Commit causes Emacs to hang
+Emacs waits for the @samp{cvs commit} command to finish before you can
+do anything. If you start a background job from the loginfo file you
+must take care that it closes @samp{stdout} and @samp{stderr} if you do
+not want to wait for it. (You do that with @samp{background-command &>-
+2&>- &} if you are starting @samp{background-command} from a
+@samp{/bin/sh} shell script).
+
+Your emacs will also hang if there was a lock file in the repository.
+In this case you can type @kbd{C-g} to get control over your emacs
+again.
+
+@item Name clash in Emacs 19
+This is really a bug in Elib or the Emacs 19 distribution. Both Elib and
+Emacs 19.6 through at least 19.10 contains a file named
+@file{cookie.el}. One of the files will have to be renamed, and we are
+currently negotiating about which of the files to rename.
+
+@item Commands while cvs-update is running
+It is possible to type commands in the *cvs* buffer while the update is
+running, but error messages is all that you will get. The error
+messages should be better.
+
+@item Unexpected output from CVS
+Unexpected output from CVS confuses pcl-cvs. It will currently create a
+bug report that you can mail to me. It should do something more
+civilized.
+@end table
+
+
+@node COPYING, Function and Variable Index, Bugs, Top
+@comment node-name, next, previous, up
+
+@appendix GNU GENERAL PUBLIC LICENSE
+@c @include gpl.texinfo
+
+
+@node Function and Variable Index, Concept Index, COPYING, Top
+@comment node-name, next, previous, up
+
+@unnumbered Function and Variable Index
+
+@printindex fn
+
+
+@node Concept Index, Key Index, Function and Variable Index, Top
+@comment node-name, next, previous, up
+
+@unnumbered Concept Index
+
+@printindex cp
+
+
+@node Key Index, , Concept Index, Top
+@comment node-name, next, previous, up
+
+@unnumbered Key Index
+
+@printindex ky
+
+@summarycontents
+@contents
+@bye
OpenPOWER on IntegriCloud